diff --git a/.gitignore b/.gitignore index b2be92b..83d2073 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ result +*.qcow2 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d4c17c5 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +NixOS-speak +=== + +## Aims +NixOS as of now is [not accessible to blind people](https://github.com/NixOS/nixpkgs/issues/30760), this projects aim is to mend this issue. + +## Getting Started +### Getting the installation image +As of now an existing installation of Nix is needed (see [Installing a Binary Distribution](https://nixos.org/nix/manual/#ch-installing-binary)). +If there are enough people interested I may end up providing pre-built images. + +After installing nix just clone this repo and run +```nix +nix-build release.nix -A image. +``` + +The currently available targets are: +- minimal +- mate + +### Integrate this module into your existing NixOS configuration +Installing the speak submodule is as simple as adding: +```nix +{ config, ... }: + +{ + imports = [ + (fetchTarball git.ophanim.de/derped/NixOS-Speak/archive/master.tar.gz) + ]; + + config.speak = { + enable = true; + config.speakup = { + synth = "soft"; + key_echo = "0"; + punc_level = "0"; + reading_punc = "0"; + soft = { + rate = "7"; + vol = "0"; + }; + }; + }; +} +``` +to your configuration. + +## Status/Progress +Current Feature Progress (more will be added later): +- TTY + - ✓ espeak + - ✓ espeakup +- Desktop + - ❌ orca + - ❌ speechd diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..3e5fa25 --- /dev/null +++ b/default.nix @@ -0,0 +1,7 @@ +{ ... }: + +{ +imports = [ + ./options/espeak.nix +]; +} diff --git a/extra/exampleConfig.nix b/extra/exampleConfig.nix new file mode 100644 index 0000000..d0fb32b --- /dev/null +++ b/extra/exampleConfig.nix @@ -0,0 +1,17 @@ +{ config, ... }: + +{ + imports = [ ./.. ]; + config.speak = { + enable = true; + # runAsRoot = false; + config.speakup = { + soft = { + direct = "1"; + rate = "7"; + }; + }; + }; + + config.sound.enable = true; +} diff --git a/extra/mate.nix b/extra/mate.nix new file mode 100644 index 0000000..3a3316f --- /dev/null +++ b/extra/mate.nix @@ -0,0 +1,54 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + addToXDGDirs = p: '' + if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then + export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name} + fi + if [ -d "${p}/lib/girepository-1.0" ]; then + export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0 + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib + fi + ''; +in { + environment.systemPackages = with pkgs; [ + orca + glib.bin + ]; + services.gnome3.at-spi2-core.enable = true; + services.xserver = { + desktopManager.mate.enable = true; + displayManager = { + gdm = { + enable = true; + autoSuspend = false; + autoLogin = { + enable = true; + user = "nixos"; + }; + }; + sessionCommands = '' + if test "$XDG_CURRENT_DESKTOP" = "MATE"; then + export XDG_MENU_PREFIX=mate- + # Let caja find extensions + export CAJA_EXTENSION_DIRS=$CAJA_EXTENSION_DIRS''${CAJA_EXTENSION_DIRS:+:}${config.system.path}/lib/caja/extensions-2.0 + # Let caja extensions find gsettings schemas + ${concatMapStrings (p: '' + if [ -d "${p}/lib/caja/extensions-2.0" ]; then + ${addToXDGDirs p} + fi + '') config.environment.systemPackages} + # Add mate-control-center paths to some XDG variables because its schemas are needed by mate-settings-daemon, and mate-settings-daemon is a dependency for mate-control-center (that is, they are mutually recursive) + ${addToXDGDirs pkgs.mate.mate-control-center} + # Auto start orca screen-reader + GSETTINGS_BACKEND=dconf ${pkgs.glib.bin}/bin/gsettings set org.gnome.desktop.a11y.applications screen-reader-enabled true + GSETTINGS_BACKEND=dconf ${pkgs.glib.bin}/bin/gsettings set org.mate.interface accessibility true + GSETTINGS_BACKEND=dconf ${pkgs.glib.bin}/bin/gsettings set org.mate.applications-at-visual startup true + export GTK_MODULES=gail:atk-bridge + fi + ''; + }; + }; +} diff --git a/options/espeak.nix b/options/espeak.nix new file mode 100644 index 0000000..d84b21e --- /dev/null +++ b/options/espeak.nix @@ -0,0 +1,116 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config; + speak = cfg.speak; +in rec { + options.speak = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable NixOS speak module. + ''; + }; + installPackages = mkOption { + type = types.bool; + default = true; + description = '' + Add spech related packages to environment. + ''; + }; + user = mkOption { + type = types.str; + default = "root"; + description = '' + User which runs services like espeakup and speech-dispatcher. + User needs to be in audio group. + ''; + }; + config = { + speakup = mkOption { + type = types.attrs; + default = {}; + example = '' + { + synth = "soft"; + punc_level = "0"; + reading_punc = "0"; + soft = { + rate = "6"; + }; + } + ''; + }; + }; + }; + + config = mkIf speak.enable rec { + boot.kernelModules = [ "speakup_soft" ]; + nixpkgs.config.packageOverrides = { + espeakup = pkgs.callPackage ../pkgs/espeakup.nix {}; + }; + + # needed for orca + services.gnome3.at-spi2-core.enable = mkDefault (cfg.services.xserver.enable); + + # allow users in the audio group to use and configure speakup_soft + services.udev.extraRules = '' + KERNEL=="synth", SUBSYSTEM=="misc", GROUP="audio", MODE="0660" + KERNEL=="softsynth", SUBSYSTEM=="misc", GROUP="audio", MODE="0660" + KERNEL=="softsynthu", SUBSYSTEM=="misc", GROUP="audio", MODE="0660" + ''; + + # See https://aur.archlinux.org/cgit/aur.git/tree/espeakup.service?h=espeakup-git + systemd.services = { + speakup-configure = let + # This whole thing still feels a little bit awkward + pathList = collect (v: isList v) (mapAttrsRecursive (p: v: p) speak.config.speakup); + mkCommand = p: v: "echo ${v} > /sys/accessibility/speakup/${p}"; + commandList = (map (l: (mkCommand (concatStringsSep "/" l) (attrByPath l "0" speak.config.speakup))) pathList); + in { + enable = true; + description = "configure speakup."; + wantedBy = [ "default.target" ]; + wants = [ "systemd-udev-settle.service" ]; + after = [ "systemd-udev-settle.service" "sound.target" ]; + script = (concatStringsSep "\n" commandList); + serviceConfig.Type = "oneshot"; + }; + espeakup = { + enable = (speak.user == "root"); + description = "Software speech output system wide settings."; + wantedBy = [ "default.target" ]; + wants = [ "systemd-udev-settle.service" ]; + after = [ "systemd-udev-settle.service" "sound.target" ]; + + serviceConfig = { + Type = "forking"; + ExecStart = "${pkgs.espeakup}/bin/espeakup"; + ExecStop = "${pkgs.utillinux.bin}/bin/kill -HUP $MAINPID"; + Restart= "always"; + }; + }; + }; + systemd.user.services.espeakup = { + enable = (speak.user != "root"); + description = "Software speech output system wide settings."; + wantedBy = [ "default.target" ]; + wants = [ "systemd-udev-settle.service" ]; + after = [ "systemd-udev-settle.service" "sound.target" ]; + + serviceConfig = { + Type = "forking"; + ExecStart = "${pkgs.espeakup}/bin/espeakup"; + ExecStop = "${pkgs.utillinux.bin}/bin/kill -HUP $MAINPID"; + Restart= "always"; + }; + }; + environment.systemPackages = optionals speak.installPackages (with pkgs; [ + (lowPrio espeak-classic) espeak-ng + espeakup + ]); + }; +} diff --git a/pkgs/espeakup.nix b/pkgs/espeakup.nix new file mode 100644 index 0000000..90b87d2 --- /dev/null +++ b/pkgs/espeakup.nix @@ -0,0 +1,26 @@ +{ stdenv, fetchFromGitHub +, espeak-classic }: + +stdenv.mkDerivation rec { + name = "espeakup"; + version = "0.80.${rev}"; + rev = "e69d61b2d88d8dc679558dea03c48130127102ac"; + + buildInputs = [ espeak-classic ]; + + src = fetchFromGitHub { + owner = "williamh"; + repo = name; + inherit rev; + sha256 = "0zs5asvxavbibpi98pnhl9y2yc701nxf7h17xm6a1q3liippvix1"; + }; + + patchPhase = '' + substituteInPlace Makefile --replace "/usr/local" $out + ''; + meta = with stdenv.lib; { + description = "espeakup is a program which makes it possible for speakup to use the espeak software synthesizer."; + homepage = "https://github.com/williamh/espeakup"; + platforms = platforms.linux; + }; +} diff --git a/release.nix b/release.nix new file mode 100644 index 0000000..0b0329e --- /dev/null +++ b/release.nix @@ -0,0 +1,59 @@ +# This file is heavily inspired by +{ nixpkgs ? # builtins.fetchTarball https://github.com/NixOS/nixpkgs-channels/archive/nixos-20.03.tar.gz +, targetSystem ? builtins.currentSystem +, speak ? ./. #builtins.fetchGit ./. +, configuration ? ./extra/exampleConfig.nix #{ imports = [ (speak + builtins.toPath "/options/espeak.nix") ]; config.speak.enable = true; config.sound.enable = true; } +}: + +with builtins; +with import (nixpkgs + (toPath "/lib")); + +let + pkgs = import nixpkgs { system = targetSystem; }; + tarball = pkgs.releaseTools.sourceTarball { + name = "speak-tarball"; + src = speak; + }; + modulePath = (nixpkgs + (toPath "/nixos/modules")); + makeTarget = target: + with import nixpkgs { system = targetSystem; }; + ((import (nixpkgs + (toPath "/nixos/lib/eval-config.nix")) { + system = targetSystem; + modules = target.modules ++ [ configuration ]; + }).config.system.build); + makeSubTargets = target: { + isoImage = hydraJob (makeTarget (mergeAttrsConcatenateValues target { + modules = [ + { isoImage.isoBaseName = "nixos-${target.name}"; } + ]; + })).isoImage; + tarball = hydraJob (makeTarget (mergeAttrsConcatenateValues target { + modules = [ (modulePath + toPath "/installer/cd-dvd/system-tarball.nix") ]; + })).tarball; + vm = (makeTarget (mergeAttrsConcatenateValues target { + modules = [ (modulePath + toPath "/virtualisation/qemu-vm.nix") { config.virtualisation.qemu.options = [ "-soundhw hda" ]; } ]; + })).vm; + }; + targets = rec { + minimal = { + name = "minimal"; + modules = [ + (modulePath + (toPath "/installer/cd-dvd/installation-cd-minimal.nix")) + (modulePath + (toPath "/installer/cd-dvd/channel.nix")) + ]; + }; + mate = { + name = "mate"; + modules = [ + (modulePath + (toPath "/installer/cd-dvd/installation-cd-graphical-base.nix")) + ./modules/mate.nix + (modulePath + (toPath "/installer/cd-dvd/channel.nix")) + ]; + }; + }; + allTargets = mapAttrs (name: value: (makeSubTargets value)) targets; +in { + inherit tarball; + image = allTargets; + pkgs.espeakup = hydraJob (pkgs.callPackage ./pkgs/espeakup.nix {}); +}