diff --git a/modules/misc/news/2025/06/2025-06-04_18-00-00.nix b/modules/misc/news/2025/06/2025-06-04_18-00-00.nix new file mode 100644 index 00000000..4800f9ed --- /dev/null +++ b/modules/misc/news/2025/06/2025-06-04_18-00-00.nix @@ -0,0 +1,8 @@ +{ config, ... }: +{ + time = "2025-06-04T18:00:00+00:00"; + condition = config.programs.thunderbird.enable; + message = '' + 'programs.thunderbird' now supports declaration of calendars using 'accounts.calendar.accounts'. + ''; +} diff --git a/modules/programs/thunderbird.nix b/modules/programs/thunderbird.nix index 1ed156d9..bfc3ac1b 100644 --- a/modules/programs/thunderbird.nix +++ b/modules/programs/thunderbird.nix @@ -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..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 diff --git a/tests/modules/programs/thunderbird/thunderbird-expected-first-darwin.js b/tests/modules/programs/thunderbird/thunderbird-expected-first-darwin.js index f967de98..5c076f90 100644 --- a/tests/modules/programs/thunderbird/thunderbird-expected-first-darwin.js +++ b/tests/modules/programs/thunderbird/thunderbird-expected-first-darwin.js @@ -1,5 +1,20 @@ // Generated by Home Manager. +user_pref("calendar.list.sortOrder", "calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73 imperative_cal calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.cache.enabled", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.calendar-main-in-composite", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.name", "holidays"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.readOnly", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.type", "ics"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.uri", "https://www.thunderbird.net/media/caldata/autogen/GermanHolidays.ics"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.username", null); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.cache.enabled", true); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.calendar-main-default", true); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.calendar-main-in-composite", true); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.name", "calendar"); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.type", "caldav"); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.uri", "https://my.caldav.server/calendar"); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.username", "testuser"); user_pref("general.useragent.override", ""); user_pref("mail.account.account_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc.identities", "id_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc"); user_pref("mail.account.account_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc.server", "server_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc"); diff --git a/tests/modules/programs/thunderbird/thunderbird-expected-first-linux.js b/tests/modules/programs/thunderbird/thunderbird-expected-first-linux.js index 3ff844fc..dd9c2fe6 100644 --- a/tests/modules/programs/thunderbird/thunderbird-expected-first-linux.js +++ b/tests/modules/programs/thunderbird/thunderbird-expected-first-linux.js @@ -1,5 +1,20 @@ // Generated by Home Manager. +user_pref("calendar.list.sortOrder", "calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73 imperative_cal calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.cache.enabled", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.calendar-main-in-composite", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.name", "holidays"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.readOnly", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.type", "ics"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.uri", "https://www.thunderbird.net/media/caldata/autogen/GermanHolidays.ics"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.username", null); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.cache.enabled", true); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.calendar-main-default", true); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.calendar-main-in-composite", true); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.name", "calendar"); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.type", "caldav"); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.uri", "https://my.caldav.server/calendar"); +user_pref("calendar.registry.calendar_5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73.username", "testuser"); user_pref("general.useragent.override", ""); user_pref("mail.account.account_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc.identities", "id_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc"); user_pref("mail.account.account_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc.server", "server_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc"); diff --git a/tests/modules/programs/thunderbird/thunderbird-expected-second-darwin.js b/tests/modules/programs/thunderbird/thunderbird-expected-second-darwin.js index bda3cde0..ca3549a2 100644 --- a/tests/modules/programs/thunderbird/thunderbird-expected-second-darwin.js +++ b/tests/modules/programs/thunderbird/thunderbird-expected-second-darwin.js @@ -1,5 +1,18 @@ // Generated by Home Manager. +user_pref("calendar.list.sortOrder", "calendar1 calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6"); +user_pref("calendar.registry.calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6.cache.enabled", true); +user_pref("calendar.registry.calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6.calendar-main-in-composite", true); +user_pref("calendar.registry.calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6.name", "local"); +user_pref("calendar.registry.calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6.type", "storage"); +user_pref("calendar.registry.calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6.uri", "moz-storage-calendar://"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.cache.enabled", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.calendar-main-in-composite", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.name", "holidays"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.readOnly", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.type", "ics"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.uri", "https://www.thunderbird.net/media/caldata/autogen/GermanHolidays.ics"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.username", null); user_pref("general.useragent.override", ""); user_pref("mail.account.account_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc.identities", "id_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc"); user_pref("mail.account.account_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc.server", "server_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc"); diff --git a/tests/modules/programs/thunderbird/thunderbird-expected-second-linux.js b/tests/modules/programs/thunderbird/thunderbird-expected-second-linux.js index 636edbe5..ca124dbb 100644 --- a/tests/modules/programs/thunderbird/thunderbird-expected-second-linux.js +++ b/tests/modules/programs/thunderbird/thunderbird-expected-second-linux.js @@ -1,5 +1,18 @@ // Generated by Home Manager. +user_pref("calendar.list.sortOrder", "calendar1 calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6"); +user_pref("calendar.registry.calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6.cache.enabled", true); +user_pref("calendar.registry.calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6.calendar-main-in-composite", true); +user_pref("calendar.registry.calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6.name", "local"); +user_pref("calendar.registry.calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6.type", "storage"); +user_pref("calendar.registry.calendar_25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6.uri", "moz-storage-calendar://"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.cache.enabled", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.calendar-main-in-composite", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.name", "holidays"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.readOnly", true); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.type", "ics"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.uri", "https://www.thunderbird.net/media/caldata/autogen/GermanHolidays.ics"); +user_pref("calendar.registry.calendar_474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a.username", null); user_pref("general.useragent.override", ""); user_pref("mail.account.account_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc.identities", "id_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc"); user_pref("mail.account.account_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc.server", "server_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc"); diff --git a/tests/modules/programs/thunderbird/thunderbird.nix b/tests/modules/programs/thunderbird/thunderbird.nix index d8215d06..173e7611 100644 --- a/tests/modules/programs/thunderbird/thunderbird.nix +++ b/tests/modules/programs/thunderbird/thunderbird.nix @@ -58,6 +58,37 @@ }; }; + accounts.calendar.accounts = { + calendar = { + thunderbird = { + enable = true; + profiles = [ "first" ]; + }; + primary = true; + remote = { + type = "caldav"; + url = "https://my.caldav.server/calendar"; + userName = "testuser"; + }; + }; + holidays = { + thunderbird = { + enable = true; + readOnly = true; + }; + remote = { + type = "http"; + url = "https://www.thunderbird.net/media/caldata/autogen/GermanHolidays.ics"; + }; + }; + local = { + thunderbird = { + enable = true; + profiles = [ "second" ]; + }; + }; + }; + programs.thunderbird = { enable = true; package = config.lib.test.mkStubPackage { @@ -86,17 +117,25 @@ "imperative_account" "hm-account" ]; - }; - - second.settings = { - "second.setting" = "some-test-setting"; - second.nested.evenFurtherNested = [ - 1 - 2 - 3 + calendarAccountsOrder = [ + "calendar" + "imperative_cal" + "holidays" ]; }; - second.accountsOrder = [ "account1" ]; + + second = { + settings = { + "second.setting" = "some-test-setting"; + second.nested.evenFurtherNested = [ + 1 + 2 + 3 + ]; + }; + accountsOrder = [ "account1" ]; + calendarAccountsOrder = [ "calendar1" ]; + }; }; settings = {