1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 19:46:05 +01:00
home-manager/modules/programs/anki/default.nix
June Stepp 131f4e22c3 anki: rename sync.passwordFile, improve documentation
The account password is different from the sync key needed here. Add
instructions for how to get the sync key, and make the option name
accurate.
2025-09-23 13:44:15 -05:00

348 lines
9.3 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 ];
imports = [
(lib.mkRenamedOptionModule
[ "programs" "anki" "sync" "passwordFile" ]
[ "programs" "anki" "sync" "keyFile" ]
)
];
options.programs.anki = {
enable = lib.mkEnableOption "Anki";
package = lib.mkPackageOption pkgs "anki" { };
language = lib.mkOption {
type = lib.types.nonEmptyStr;
default = "en_US";
example = "ja_JP";
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.";
};
keyFile = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
description = ''
Path to a file containing the sync account sync key. This is different from
the account password.
To get the sync key, follow these steps:
- Enable this Home Manager module: `programs.anki.enable = true`
- Open Anki.
- Navigate to the sync settings page. (Tools > Preferences > Syncing)
- Log in to your AnkiWeb account.
- Select "Yes" to the prompt about saving preferences and syncing.
- A Home Manager warning prompt will show. Select "Show details...".
- Get your sync key from the message: "syncKey changed from \`None\` to \`<YOUR SYNC KEY WILL BE HERE>\`"
'';
};
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";
toggle_names_textcolors = 1;
};
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";
};
}