diff --git a/modules/home/services/numen/default.nix b/modules/home/services/numen/default.nix
new file mode 100644
index 0000000..8cde203
--- /dev/null
+++ b/modules/home/services/numen/default.nix
@@ -0,0 +1,78 @@
+{ config, lib, pkgs, ... }:
+# Source: https://github.com/Lykos153/numen-nix
+with lib;
+
+let
+ cfg = config.services.numen;
+in
+{
+ options.services.numen = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ };
+
+ numenPkg = mkOption {
+ type = types.package;
+ default = pkgs.internal.numen;
+ };
+
+ # models = mkOption {
+ # type = types.uniq types.listOf types.package;
+ # default = [vosk-model-small-en-us];
+ # example = "[vosk-model-small-en-us]";
+ # description = ''
+ # List of vosk models to be loaded by numen. They can be referred to using the index, eg. model0 or model1.
+ # '';
+ # };
+
+ model = mkOption {
+ type = types.pathInStore;
+ default = "${pkgs.internal.vosk-model-small-en-us}/usr/share/vosk-models/small-en-us/";
+ example = "vosk-model-small-en-us";
+ description = ''
+ Vosk model to be loaded by numen.
+ '';
+ };
+
+ phrases = mkOption {
+ type = types.listOf types.path;
+ default = [ ];
+ description = ''
+ Phrases to be loaded by numen. If empty, the default phrases are used.
+ '';
+ };
+
+ extraArgs = mkOption {
+ type = types.singleLineStr;
+ default = "";
+ description = ''
+ Additional arguments to be passed to numen.
+ '';
+ };
+
+ dotoolXkbLayout = mkOption {
+ type = types.singleLineStr;
+ default = "en";
+ description = ''
+ The XKB keyboard layout that should be used by dotool.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.numen = {
+ Unit = {
+ Description = "Numen voice control";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+ Install.WantedBy = [ "graphical-session.target" ];
+ Service.Environment = [
+ "DOTOOL_XKB_LAYOUT=${cfg.dotoolXkbLayout}"
+ "NUMEN_MODEL=${cfg.model}"
+ ];
+ Service.ExecStart = "${cfg.numenPkg}/bin/numen ${cfg.extraArgs} ${lib.strings.concatStringsSep " " cfg.phrases}";
+ };
+ };
+}
diff --git a/packages/dotool/default.nix b/packages/dotool/default.nix
new file mode 100644
index 0000000..e7d4e5f
--- /dev/null
+++ b/packages/dotool/default.nix
@@ -0,0 +1,18 @@
+{ fetchFromSourcehut, buildGoModule, pkg-config, libxkbcommon, tree }:
+# Source: https://github.com/Lykos153/numen-nix
+buildGoModule rec {
+ pname = "dotool";
+ version = "1.5";
+ src = fetchFromSourcehut {
+ owner = "~geb";
+ repo = pname;
+ rev = version;
+ hash = "sha256-4QmTHeU3TnpRATKIvilkIA3i2hDjM5zQwSvmRvoWuNE=";
+ };
+ vendorHash = "sha256-IQ847LHDYJPboWL/6lQNJ4vPPD/+xkrGI2LSZ7kBnp4=";
+ nativeBuildInputs = [ pkg-config tree ];
+ buildInputs = [ libxkbcommon ];
+ postInstall = ''
+ install -D $src/80-dotool.rules $out/lib/udev/rules.d/80-dotool.rules
+ '';
+}
diff --git a/packages/intel-media-sdk/default.nix b/packages/intel-media-sdk/default.nix
new file mode 100644
index 0000000..4898edf
--- /dev/null
+++ b/packages/intel-media-sdk/default.nix
@@ -0,0 +1,66 @@
+{
+ lib,
+ stdenv,
+ fetchFromGitHub,
+ cmake,
+ pkgs,
+ ...
+}:
+
+stdenv.mkDerivation rec {
+ pname = "intel-media-sdk";
+ version = "23.2.2";
+
+ src = fetchFromGitHub {
+ owner = "Intel-Media-SDK";
+ repo = "MediaSDK";
+ rev = "intel-mediasdk-${version}";
+ hash = "sha256-wno3a/ZSKvgHvZiiJ0Gq9GlrEbfHCizkrSiHD6k/Loo=";
+ };
+
+ patches = [
+ # Search oneVPL-intel-gpu in NixOS specific /run/opengl-driver/lib directory
+ # See https://github.com/NixOS/nixpkgs/pull/315425
+ ./nixos-search-onevplrt-in-run-opengl-driver-lib.patch
+ # https://github.com/Intel-Media-SDK/MediaSDK/pull/3005
+ (pkgs.fetchpatch {
+ name = "include-cstdint-explicitly.patch";
+ url = "https://github.com/Intel-Media-SDK/MediaSDK/commit/a4f37707c1bfdd5612d3de4623ffb2d21e8c1356.patch";
+ hash = "sha256-OPwGzcMTctJvHcKn5bHqV8Ivj4P7+E4K9WOKgECqf04=";
+ })
+ ];
+
+ nativeBuildInputs = [
+ pkgs.cmake
+ pkgs.pkg-config
+ ];
+ buildInputs = [
+ pkgs.libdrm
+ pkgs.libva
+ pkgs.xorg.libpciaccess
+ pkgs.xorg.libX11
+ pkgs.xorg.libXau
+ pkgs.xorg.libXdmcp
+ pkgs.xorg.libpthreadstubs
+ ];
+ nativeCheckInputs = [ pkgs.gtest ];
+
+ cmakeFlags = [
+ "-DBUILD_SAMPLES=OFF"
+ "-DBUILD_TESTS=${if doCheck then "ON" else "OFF"}"
+ "-DUSE_SYSTEM_GTEST=ON"
+ ];
+
+ doCheck = true;
+
+ meta = with lib; {
+ description = "Intel Media SDK";
+ mainProgram = "mfx-tracer-config";
+ license = licenses.mit;
+ maintainers = with maintainers; [
+ midchildan
+ pjungkamp
+ ];
+ platforms = [ "x86_64-linux" "i686-linux" ];
+ };
+}
diff --git a/packages/intel-media-sdk/nixos-search-onevplrt-in-run-opengl-driver-lib.patch b/packages/intel-media-sdk/nixos-search-onevplrt-in-run-opengl-driver-lib.patch
new file mode 100644
index 0000000..5057de1
--- /dev/null
+++ b/packages/intel-media-sdk/nixos-search-onevplrt-in-run-opengl-driver-lib.patch
@@ -0,0 +1,45 @@
+From aceb689ae69857def8a26a8d1ceb114ccfbb2569 Mon Sep 17 00:00:00 2001
+From: Philipp Jungkamp
+Date: Tue, 28 May 2024 19:22:29 +0200
+Subject: [PATCH] NixOS: Search ONEVPLRT in /run/opengl-driver/lib
+
+---
+ api/mfx_dispatch/linux/mfxloader.cpp | 2 ++
+ .../suites/mfx_dispatch/linux/mfx_dispatch_test_cases_libs.cpp | 1 +
+ 2 files changed, 3 insertions(+)
+
+diff --git a/api/mfx_dispatch/linux/mfxloader.cpp b/api/mfx_dispatch/linux/mfxloader.cpp
+index 39b6bff1..f76ed65d 100644
+--- a/api/mfx_dispatch/linux/mfxloader.cpp
++++ b/api/mfx_dispatch/linux/mfxloader.cpp
+@@ -193,6 +193,7 @@ mfxStatus LoaderCtx::Init(mfxInitParam& par)
+ if (selected_runtime && strcmp(selected_runtime, "ONEVPL") == 0) {
+ libs.emplace_back(ONEVPLRT);
+ libs.emplace_back(MFX_MODULES_DIR "/" ONEVPLRT);
++ libs.emplace_back("/run/opengl-driver/lib/" ONEVPLRT);
+ } else if ((selected_runtime && strcmp(selected_runtime, "MSDK") == 0) || (platform != MFX_HW_UNKNOWN)) {
+ if (MFX_IMPL_BASETYPE(par.Implementation) == MFX_IMPL_AUTO ||
+ MFX_IMPL_BASETYPE(par.Implementation) == MFX_IMPL_AUTO_ANY) {
+@@ -213,6 +214,7 @@ mfxStatus LoaderCtx::Init(mfxInitParam& par)
+ } else {
+ libs.emplace_back(ONEVPLRT);
+ libs.emplace_back(MFX_MODULES_DIR "/" ONEVPLRT);
++ libs.emplace_back("/run/opengl-driver/lib/" ONEVPLRT);
+ }
+
+ mfxStatus mfx_res = MFX_ERR_UNSUPPORTED;
+diff --git a/tests/unit/suites/mfx_dispatch/linux/mfx_dispatch_test_cases_libs.cpp b/tests/unit/suites/mfx_dispatch/linux/mfx_dispatch_test_cases_libs.cpp
+index dedee0b3..9657da4b 100644
+--- a/tests/unit/suites/mfx_dispatch/linux/mfx_dispatch_test_cases_libs.cpp
++++ b/tests/unit/suites/mfx_dispatch/linux/mfx_dispatch_test_cases_libs.cpp
+@@ -123,6 +123,7 @@ TEST_P(DispatcherLibsTestParametrized, ShouldEnumerateCorrectLibNames)
+ {
+ libs.emplace_back(ONEVPLRT);
+ libs.emplace_back(modules_dir + "/" + ONEVPLRT);
++ libs.emplace_back("/run/opengl-driver/lib/" + ONEVPLRT);
+ }
+
+ for (const std::string& lib : libs)
+--
+2.44.0
+
diff --git a/packages/numen/default.nix b/packages/numen/default.nix
new file mode 100644
index 0000000..b3ab5a0
--- /dev/null
+++ b/packages/numen/default.nix
@@ -0,0 +1,88 @@
+{ fetchFromSourcehut
+, stdenv
+, buildGoModule
+, makeWrapper
+, scdoc
+, pkgs
+, lib
+, alsa-utils
+, libxkbcommon
+, gnused
+, gawk
+, coreutils
+, libnotify
+, dmenu
+, procps
+}:
+# Source: https://github.com/Lykos153/numen-nix
+buildGoModule rec {
+ pname = "numen";
+ version = "master";
+ vendorHash = "sha256-Y3CbAnIK+gEcUfll9IlEGZE/s3wxdhAmTJkj9zlAtoQ=";
+ src = fetchFromSourcehut {
+ owner = "~geb";
+ repo = pname;
+ rev = version;
+ hash = "sha256-haiaMBq9xbcDd83Kmm00Xc7823U+90DworOZk9H2n9w=";
+ };
+
+ allowGoReference = true;
+ preBuild = ''
+ export CGO_CFLAGS="-I${pkgs.internal.vosk-bin}/include"
+ export CGO_LDFLAGS="-L${pkgs.internal.vosk-bin}/lib"
+ '';
+ nativeBuildInputs = [
+ makeWrapper
+ scdoc
+ ];
+ ldflags = [
+ "-X main.Version=${version}"
+ "-X main.DefaultModelPackage=vosk-model-small-en-us"
+ "-X main.DefaultModelPaths=${pkgs.internal.vosk-model-small-en-us}/usr/share/vosk-models/small-en-us"
+ "-X main.DefaultPhrasesDir=${placeholder "out"}/etc/numen/phrases"
+ ];
+ # This is necessary because while the scripts are copied relative to
+ # the nix store, the hard-coded paths inside the scripts themselves
+ # still point outside of the store.
+ patchPhase = ''
+ substituteInPlace scripts/* \
+ --replace /etc/numen/scripts "$out/etc/numen/scripts" \
+ --replace sed ${gnused}/bin/sed \
+ --replace awk ${gawk}/bin/awk \
+ --replace cat ${coreutils}/bin/cat \
+ --replace notify-send ${libnotify}/bin/notify-send
+ substituteInPlace scripts/menu \
+ --replace "-dmenu" "-${dmenu}/bin/dmenu"
+ substituteInPlace scripts/displaying \
+ --replace "(pgrep" "(${procps}/bin/pgrep" \
+ --replace "(ps" "(${procps}/bin/ps"
+ substituteInPlace phrases/* \
+ --replace /etc/numen/scripts "$out/etc/numen/scripts" \
+ --replace numenc "$out/bin/numenc"
+ substituteInPlace numenc \
+ --replace /bin/echo "${coreutils}/bin/echo" \
+ --replace cat "${coreutils}/bin/cat"
+ '';
+ installPhase = ''
+ runHook preInstall
+
+ install -Dm755 $GOPATH/bin/numen -t "$out/bin"
+ install -Dm755 numenc -t "$out/bin"
+ install -Dm755 scripts/* -t "$out/scripts"
+ install -Dm644 phrases/* -t "$out/prases"
+ sed -i "s:/etc/numen/scripts:${placeholder "out"}/scripts:g" \
+ $out/scripts/* \
+ $out/prases/*
+ mkdir -p "$out/usr/share/man/man1" || exit
+ scdoc < doc/numen.1.scd > "$out/usr/share/man/man1/numen.1" || exit
+ echo Installed Successfully.
+
+
+ runHook postInstall
+ '';
+ postFixup = ''
+ wrapProgram $out/bin/numen \
+ --prefix PATH : ${lib.makeBinPath [ pkgs.internal.dotool alsa-utils ]} \
+ --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath [ libxkbcommon stdenv.cc.cc.lib ]} \
+ '';
+}
diff --git a/packages/vosk-bin/default.nix b/packages/vosk-bin/default.nix
new file mode 100644
index 0000000..f2de716
--- /dev/null
+++ b/packages/vosk-bin/default.nix
@@ -0,0 +1,40 @@
+{ stdenv, fetchurl, unzip, system }:
+# Source https://github.com/Lykos153/numen-nix
+let
+ getSource = system: version: let
+ sources = {
+ x86_64-linux = {
+ systemString = "linux-x86_64";
+ sha256 = "sha256-u9yO2FxDl59kQxQoiXcOqVy/vFbP+1xdzXOvqHXF+7I=";
+ };
+ aarch64-linux = {
+ systemString = "linux-aarch64";
+ sha256 = "sha256-ReldN3Vd6wdWjnlJfX/rqMA67lqeBx3ymWGqAj/ZRUE=";
+ };
+ i686-linux = {
+ systemString = "linux-x86";
+ sha256 = "sha256-tTnvwieAlIvZji7LnBuSygizxVKhh0T3ICq3hAW44fk=";
+ };
+ };
+ in {
+ url = "https://github.com/alphacep/vosk-api/releases/download/v${version}/vosk-${(builtins.getAttr system sources).systemString}-${version}.zip";
+ sha256 = (builtins.getAttr system sources).sha256;
+
+ };
+in
+stdenv.mkDerivation rec {
+ # todo: other arches as well.
+ name = "vosk-bin";
+ version = "0.3.45";
+ src = fetchurl (getSource system version);
+ nativeBuildInputs = [ unzip ];
+ unpackCmd = "unzip $curSrc";
+
+ installPhase = ''
+ mkdir -p $out/lib
+ mv libvosk.so $out/lib/
+ mkdir -p $out/include
+ mv vosk_api.h $out/include/
+ '';
+}
+
diff --git a/packages/vosk-model-small-en-us/default.nix b/packages/vosk-model-small-en-us/default.nix
new file mode 100644
index 0000000..ff4a850
--- /dev/null
+++ b/packages/vosk-model-small-en-us/default.nix
@@ -0,0 +1,18 @@
+{ stdenv, fetchurl, unzip }:
+# Source: https://github.com/Lykos153/numen-nix
+stdenv.mkDerivation {
+ name = "vosk-model-small-en-us";
+ version = "0.15";
+ src = fetchurl {
+ url =
+ "https://alphacephei.com/kaldi/models/vosk-model-small-en-us-0.15.zip";
+ sha256 = "sha256-MPJiQsTrRJ+UjkLLMC3XpobLKaNCOoNn+Z/0F4CUJJg=";
+ };
+ nativeBuildInputs = [ unzip ];
+ unpackCmd = "unzip $curSrc";
+
+ installPhase = ''
+ mkdir -p $out/usr/share/vosk-models
+ cp -r . $out/usr/share/vosk-models/small-en-us
+ '';
+}