1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-12-01 06:31:04 +01:00
home-manager/modules/programs/anki/default.nix
2025-07-04 23:45:35 -05:00

327 lines
8.5 KiB
Nix

{
lib,
config,
pkgs,
...
}:
let
helper = import ./helper.nix { inherit lib config pkgs; };
ankiBaseDir =
if pkgs.stdenv.hostPlatform.isDarwin then
"Library/Application Support/Anki2"
else
"${config.xdg.dataHome}/Anki2";
cfg = config.programs.anki;
in
{
meta.maintainers = [ lib.maintainers.junestepp ];
options.programs.anki = {
enable = lib.mkEnableOption "Anki";
package = lib.mkPackageOption pkgs "anki" { };
language = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
example = "en_US";
description = ''
Display language. Should be an underscore separated language tag.
See <https://github.com/ankitects/anki/blob/main/pylib/anki/lang.py> for
supported tags.
'';
};
videoDriver = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"opengl"
"angle"
"software"
"metal"
"vulkan"
"d3d11"
]);
default = null;
example = "opengl";
description = "Video driver to use.";
};
theme = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"followSystem"
"light"
"dark"
]);
default = null;
example = "dark";
description = "Theme to use.";
};
style = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"anki"
"native"
]);
default = null;
example = "native";
description = "Widgets style.";
};
uiScale = lib.mkOption {
type = with lib.types; nullOr (numbers.between 0.0 1.0);
default = null;
example = 1.0;
description = "User interface scale.";
};
hideTopBar = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
description = "Hide top bar during review.";
};
hideTopBarMode = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"fullscreen"
"always"
]);
default = null;
example = "fullscreen";
description = "When to hide the top bar when `hideTopBar` is enabled.";
};
hideBottomBar = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
description = "Hide bottom bar during review.";
};
hideBottomBarMode = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"fullscreen"
"always"
]);
default = null;
example = "fullscreen";
description = "When to hide the bottom bar when `hideBottomBar` is enabled.";
};
reduceMotion = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
description = "Disable various animations and transitions of the user interface.";
};
minimalistMode = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
description = "Minimalist user interface mode.";
};
spacebarRatesCard = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
description = "Spacebar (or enter) also answers card.";
};
legacyImportExport = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
description = "Use legacy (pre 2.1.55) import/export handling code.";
};
answerKeys = lib.mkOption {
type =
with lib.types;
listOf (submodule {
options = {
ease = lib.mkOption {
type = with lib.types; int;
example = 3;
description = ''
Number associated with an answer button.
By default, 1 = Again, 2 = Hard, 3 = Good, and 4 = Easy.
'';
};
key = lib.mkOption {
type = with lib.types; str;
example = "3";
description = ''
Keyboard shortcut for this answer button. The shortcut should be in
the string format used by <https://doc.qt.io/qt-6/qkeysequence.html>.
'';
};
};
});
default = [ ];
example = [
{
ease = 1;
key = "left";
}
{
ease = 2;
key = "up";
}
{
ease = 3;
key = "right";
}
{
ease = 4;
key = "down";
}
];
description = ''
Overrides for choosing what keyboard shortcut activates each
answer button. The Anki default will be used for ones without an
override defined.
'';
};
sync = {
username = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
example = "lovelearning@email.com";
description = "Sync account username.";
};
usernameFile = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
description = "Path to a file containing the sync account username.";
};
passwordFile = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
description = "Path to a file containing the sync account password.";
};
url = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
example = "http://example.com/anki-sync/";
description = ''
Custom sync server URL. See <https://docs.ankiweb.net/sync-server.html>.
'';
};
autoSync = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
description = "Automatically sync on profile open/close.";
};
syncMedia = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
description = "Synchronize audio and images too.";
};
autoSyncMediaMinutes = lib.mkOption {
type = with lib.types; nullOr ints.unsigned;
default = null;
example = 15;
description = ''
Automatically sync media every X minutes. Set this to 0 to disable
periodic media syncing.
'';
};
networkTimeout = lib.mkOption {
type = with lib.types; nullOr ints.unsigned;
default = null;
example = 60;
description = "Network timeout in seconds.";
};
};
addons = lib.mkOption {
type = with lib.types; listOf package;
default = [ ];
example = lib.literalExpression ''
[
# When the add-on is already available in nixpkgs
pkgs.ankiAddons.anki-connect
# When the add-on is not available in nixpkgs
(pkgs.anki-utils.buildAnkiAddon (finalAttrs: {
pname = "recolor";
version = "3.1";
src = pkgs.fetchFromGitHub {
owner = "AnKing-VIP";
repo = "AnkiRecolor";
rev = finalAttrs.version;
sparseCheckout = [ "src/addon" ];
hash = "sha256-28DJq2l9DP8O6OsbNQCZ0pm4S6CQ3Yz0Vfvlj+iQw8Y=";
};
sourceRoot = "''${finalAttrs.src.name}/src/addon";
}))
# When the add-on needs to be configured
pkgs.ankiAddons.passfail2.withConfig {
config = {
again_button_name = "not quite";
good_button_name = "excellent";
};
user_files = ./dir-to-be-merged-into-addon-user-files-dir;
};
]
'';
description = "List of Anki add-on packages to install.";
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = !(cfg.sync.username != null && cfg.sync.usernameFile != null);
message = ''
The `programs.anki.sync` `username` option is mutually exclusive with
the `usernameFile` option.
'';
}
{
assertion = cfg.package ? withAddons;
message = ''
The value of `programs.anki.package` doesn't support declaratively managing
add-ons. Make sure you are using `pkgs.anki`.
'';
}
];
home.packages = [
(cfg.package.withAddons (
[
helper.homeManagerAnkiAddon
helper.syncConfigAnkiAddon
]
++ cfg.addons
))
];
home.file."${ankiBaseDir}/gldriver6" = lib.mkIf (cfg.videoDriver != null) {
source = "${helper.ankiConfig}/gldriver6";
};
home.file."${ankiBaseDir}/prefs21.db".source = "${helper.ankiConfig}/prefs21.db";
};
}