thunderbird: support declaration of calendars (#5484)

Adds calendar support using the `accounts.calendar.accounts.*` options.
This commit is contained in:
DerRockWolf 2025-06-16 02:21:02 +02:00 committed by GitHub
parent cef3e0adc0
commit 66523b0efe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 256 additions and 20 deletions

View file

@ -32,11 +32,14 @@ let
moduleName = "programs.thunderbird";
enabledAccounts = attrValues (
lib.filterAttrs (_: a: a.thunderbird.enable) config.accounts.email.accounts
);
filterEnabled = accounts: attrValues (lib.filterAttrs (_: a: a.thunderbird.enable) accounts);
addId = map (a: a // { id = builtins.hashString "sha256" a.name; });
enabledAccountsWithId = map (a: a // { id = builtins.hashString "sha256" a.name; }) enabledAccounts;
enabledEmailAccounts = filterEnabled config.accounts.email.accounts;
enabledEmailAccountsWithId = addId enabledEmailAccounts;
enabledCalendarAccounts = filterEnabled config.accounts.calendar.accounts;
enabledCalendarAccountsWithId = addId enabledCalendarAccounts;
thunderbirdConfigPath = if isDarwin then "Library/Thunderbird" else ".thunderbird";
@ -159,6 +162,36 @@ let
)
// account.thunderbird.settings id;
toThunderbirdCalendar =
calendar: _:
let
inherit (calendar) id;
in
{
"calendar.registry.calendar_${id}.name" = calendar.name;
"calendar.registry.calendar_${id}.calendar-main-in-composite" = true;
"calendar.registry.calendar_${id}.cache.enabled" = true;
}
// optionalAttrs (calendar.remote == null) {
"calendar.registry.calendar_${id}.type" = "storage";
"calendar.registry.calendar_${id}.uri" = "moz-storage-calendar://";
}
// optionalAttrs (calendar.remote != null) {
"calendar.registry.calendar_${id}.type" =
if (calendar.remote.type == "http") then "ics" else calendar.remote.type;
"calendar.registry.calendar_${id}.uri" = calendar.remote.url;
"calendar.registry.calendar_${id}.username" = calendar.remote.userName;
}
// optionalAttrs calendar.primary {
"calendar.registry.calendar_${id}.calendar-main-default" = true;
}
// optionalAttrs calendar.thunderbird.readOnly {
"calendar.registry.calendar_${id}.readOnly" = true;
}
// optionalAttrs (calendar.thunderbird.color != "") {
"calendar.registry.calendar_${id}.color" = calendar.thunderbird.color;
};
toThunderbirdFeed =
feed: profile:
let
@ -212,7 +245,7 @@ let
''
+ lib.concatStrings (map (f: mkFilterToIniString f) filters);
getEmailAccountsForProfile =
getAccountsForProfile =
profileName: accounts:
(filter (
a: a.thunderbird.profiles == [ ] || lib.any (p: p == profileName) a.thunderbird.profiles
@ -346,6 +379,26 @@ in
]
'';
};
calendarAccountsOrder = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
Custom ordering of calendar accounts. The accounts are specified
by their name. For declarative accounts, it must be the name
of their attribute in `config.accounts.calendar.accounts`.
Enabled accounts that aren't listed here appear in an arbitrary
order after the ordered accounts.
'';
example = ''
[
"my-awesome-account"
"private"
"work"
"holidays"
/* Other accounts in arbitrary order */
]
'';
};
withExternalGnupg = mkOption {
type = types.bool;
@ -608,6 +661,41 @@ in
)
);
};
accounts.calendar.accounts = mkOption {
type =
with types;
attrsOf (submodule {
options.thunderbird = {
enable = lib.mkEnableOption "the Thunderbird mail client for this account";
profiles = mkOption {
type = with types; listOf str;
default = [ ];
example = literalExpression ''
[ "profile1" "profile2" ]
'';
description = ''
List of Thunderbird profiles for which this account should be
enabled. If this list is empty (the default), this account will
be enabled for all declared profiles.
'';
};
readOnly = mkOption {
type = bool;
default = false;
description = "Mark calendar as read only";
};
color = mkOption {
type = str;
default = "";
example = "#dc8add";
description = "Display color of the calendar in hex";
};
};
});
};
};
config = mkIf cfg.enable {
@ -635,7 +723,9 @@ in
(
let
profiles = lib.catAttrs "name" profilesWithId;
selectedProfiles = lib.concatMap (a: a.thunderbird.profiles) enabledAccounts;
selectedProfiles = lib.concatMap (a: a.thunderbird.profiles) (
enabledEmailAccounts ++ enabledCalendarAccounts
);
in
{
assertion = (lib.intersectLists profiles selectedProfiles) == selectedProfiles;
@ -647,6 +737,27 @@ in
+ (concatStringsSep "," selectedProfiles);
}
)
(
let
foundCalendars = filter (
a: a.remote != null && a.remote.type == "google_calendar"
) enabledCalendarAccounts;
in
{
assertion = length foundCalendars == 0;
message =
'''accounts.calendar.accounts.<name>.remote.type = "google_calendar";' is not directly supported by Thunderbird, ''
+ "but declared for these calendars: "
+ (concatStringsSep ", " (lib.catAttrs "name" foundCalendars))
+ "\n"
+ ''
To use google calendars in Thunderbird choose 'type = "caldav"' instead.
The 'url' will be "https://apidata.googleusercontent.com/caldav/v2/ID/events/", replace ID with the "Calendar ID".
The ID can be found in the Google Calendar web app: Settings > Settings for my calendars > scroll to "Integrate calendar" > copy the "Calendar ID".
'';
}
)
];
home.packages = [
@ -677,14 +788,14 @@ in
"${thunderbirdProfilesPath}/${name}/user.js" =
let
emailAccounts = getEmailAccountsForProfile name enabledAccountsWithId;
emailAccounts = getAccountsForProfile name enabledEmailAccountsWithId;
calendarAccounts = getAccountsForProfile name enabledCalendarAccountsWithId;
smtp = filter (a: a.smtp != null) emailAccounts;
feedAccounts = map (f: f // { id = builtins.hashString "sha256" f.name; }) (
attrValues profile.feedAccounts
);
feedAccounts = addId (attrValues profile.feedAccounts);
# NOTE: `calendarAccounts` not added here as calendars are not part of the 'Mail' view
accounts = emailAccounts ++ feedAccounts;
orderedAccounts =
@ -707,6 +818,23 @@ in
lib.optionals (accounts != [ ]) (
accountsOrderIds ++ (lib.lists.subtractLists accountsOrderIds enabledAccountsIds)
);
orderedCalendarAccounts =
let
accountNameToId = builtins.listToAttrs (
map (a: {
name = a.name;
value = "calendar_${a.id}";
}) calendarAccounts
);
accountsOrderIds = map (a: accountNameToId."${a}" or a) profile.calendarAccountsOrder;
enabledAccountsIds = (lib.attrsets.mapAttrsToList (name: value: value) accountNameToId);
in
lib.optionals (calendarAccounts != [ ]) (
accountsOrderIds ++ (lib.lists.subtractLists accountsOrderIds enabledAccountsIds)
);
in
{
text = mkUserJs (builtins.foldl' (a: b: a // b) { } (
@ -717,6 +845,10 @@ in
"mail.accountmanager.accounts" = concatStringsSep "," orderedAccounts;
})
(optionalAttrs (length orderedCalendarAccounts != 0) {
"calendar.list.sortOrder" = concatStringsSep " " orderedCalendarAccounts;
})
(optionalAttrs (length smtp != 0) {
"mail.smtpservers" = concatStringsSep "," (map (a: "smtp_${a.id}") smtp);
})
@ -726,6 +858,7 @@ in
profile.settings
]
++ (map (a: toThunderbirdAccount a profile) emailAccounts)
++ (map (c: toThunderbirdCalendar c profile) calendarAccounts)
++ (map (f: toThunderbirdFeed f profile) feedAccounts)
)) profile.extraConfig;
};
@ -755,7 +888,7 @@ in
let
emailAccountsWithFilters = (
filter (a: a.thunderbird.messageFilters != [ ]) (
getEmailAccountsForProfile name enabledAccountsWithId
getAccountsForProfile name enabledEmailAccountsWithId
)
);
in