This repository has been archived on 2026-05-25. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
zen-browser-flake/hm-module.nix
Gustavo "Guz" L de Mello 5c8c7aabef fix(hm-module,spaces): missing columns when Zen is opened before the spaces table is created
Appearently, opening Zen before the spaces service runs could cause the
table not having the theme columns. Because of this, the CREATE TABLE IF
NOT EXISTS query would not run, but the columns would still not be
present. This commit adds the column via a bash script plus an ALTER
TABLE query, mimicking more what Zen does in it JavaScript code.
2025-08-21 11:51:27 -03:00

346 lines
11 KiB
Nix

{
home-manager,
self,
name,
}: {
config,
pkgs,
lib,
...
}: let
inherit
(lib)
getAttrFromPath
isPath
mkIf
mkOption
setAttrByPath
types
;
cfg = getAttrFromPath modulePath config;
applicationName = "Zen Browser";
modulePath = [
"programs"
"zen-browser"
];
linuxConfigPath = ".zen";
darwinConfigPath = "Library/Application Support/Zen";
configPath = "${config.home.homeDirectory}/${(
if pkgs.stdenv.isDarwin
then darwinConfigPath
else linuxConfigPath
)}";
mkFirefoxModule = import "${home-manager.outPath}/modules/programs/firefox/mkFirefoxModule.nix";
in {
imports = [
(mkFirefoxModule {
inherit modulePath;
name = applicationName;
wrappedPackageName = "zen-${name}";
unwrappedPackageName = "zen-${name}-unwrapped";
visible = true;
platforms = {
linux = {
vendorPath = linuxConfigPath;
configPath = linuxConfigPath;
};
darwin = {
configPath = darwinConfigPath;
};
};
})
];
options = setAttrByPath modulePath {
profiles = mkOption {
type = with types;
attrsOf (submodule ({...}: {
options = {
spacesForce = mkOption {
type = bool;
description = "Whether delete existing spaces not declared in the configuration.";
default = false;
};
spaces = mkOption {
type = attrsOf (submodule ({name, ...}: {
options = {
name = mkOption {
type = str;
description = "Name of the space.";
default = name;
};
id = mkOption {
type = str;
description = "REQUIRED. Unique Version 4 UUID for space.";
};
position = mkOption {
type = ints.unsigned;
description = "Position of space in the left bar.";
default = 1000;
};
icon = mkOption {
type = nullOr (either str path);
description = "Emoji or icon URI to be used as space icon.";
apply = v:
if isPath v
then "file://${v}"
else v;
default = null;
};
container = mkOption {
type = nullOr ints.unsigned;
description = "Container ID to be used in space";
default = null;
};
theme.type = mkOption {
type = nullOr str;
default = "gradient";
};
theme.colors = mkOption {
type = nullOr (listOf (submodule ({...}: {
options = {
red = mkOption {
type = int;
default = 0;
};
green = mkOption {
type = int;
default = 0;
};
blue = mkOption {
type = int;
default = 0;
};
custom = mkOption {
type = bool;
default = false;
};
algorithm = mkOption {
type = enum ["complementary" "floating" "analogous"];
default = "floating";
};
primary = mkOption {
type = bool;
default = true;
};
lightness = mkOption {
type = int;
default = 0;
};
position.x = mkOption {
type = int;
default = 0;
};
position.y = mkOption {
type = int;
default = 0;
};
type = mkOption {
type = enum ["undefined" "explicit-lightness"];
default = "undefined";
};
};
})));
default = [];
};
theme.opacity = mkOption {
type = nullOr float;
default = 0.5;
};
theme.rotation = mkOption {
type = nullOr int;
default = null;
};
theme.texture = mkOption {
type = nullOr float;
default = 0.0;
};
};
}));
default = {};
};
};
}));
};
};
config = mkIf cfg.enable {
programs.zen-browser = {
package =
(pkgs.wrapFirefox (self.packages.${pkgs.stdenv.hostPlatform.system}."${name}-unwrapped".override {
# Seems like zen uses relative (to the original binary) path to the policies.json file
# and ignores the overrides by pkgs.wrapFirefox
policies = cfg.policies;
}) {}).override
{
nativeMessagingHosts = cfg.nativeMessagingHosts;
};
policies = {
DisableAppUpdate = lib.mkDefault true;
DisableTelemetry = lib.mkDefault true;
};
};
systemd.user.services."zen-browser-spaces-activation" = let
inherit
(lib)
attrByPath
concatMapAttrsStringSep
concatMapStringsSep
concatStringsSep
elemAt
filterAttrs
getExe
getExe'
isStringLike
lists
mapAttrsToList
optionalString
pipe
;
hasSpaces = pipe cfg.profiles [
(mapAttrsToList (n: v: v.spaces != {}))
(lists.any (v: v))
];
sqlite3 = getExe' pkgs.sqlite "sqlite3";
# Reference: https://github.com/zen-browser/desktop/blob/4e2dfd8a138fd28767bb4799a3ca9d8aab80430e/src/zen/workspaces/ZenWorkspacesStorage.mjs#L25-L55
initSpacesTable = pkgs.writeShellScriptBin "init-spaces-table" ''
placesFile=$1
${sqlite3} $placesFile "${concatStringsSep " " [
"CREATE TABLE IF NOT EXISTS zen_workspaces ("
"id INTEGER PRIMARY KEY,"
"uuid TEXT UNIQUE NOT NULL,"
"name TEXT NOT NULL,"
"icon TEXT,"
"container_id INTEGER,"
"position INTEGER NOT NULL DEFAULT 0,"
"created_at INTEGER NOT NULL,"
"updated_at INTEGER NOT NULL"
");"
]}" || exit 1
columns=($(${sqlite3} "$placesFile" "SELECT name FROM pragma_table_info('zen_workspaces');"))
if [[ ! "''${columns[@]}" =~ "theme_type" ]]; then
${sqlite3} "$placesFile" "ALTER TABLE zen_workspaces ADD COLUMN theme_type TEXT;" || exit 1
fi
if [[ ! "''${columns[@]}" =~ "theme_colors" ]]; then
${sqlite3} "$placesFile" "ALTER TABLE zen_workspaces ADD COLUMN theme_colors TEXT;" || exit 1
fi
if [[ ! "''${columns[@]}" =~ "theme_opacity" ]]; then
${sqlite3} "$placesFile" "ALTER TABLE zen_workspaces ADD COLUMN theme_opacity REAL;" || exit 1
fi
if [[ ! "''${columns[@]}" =~ "theme_rotation" ]]; then
${sqlite3} "$placesFile" "ALTER TABLE zen_workspaces ADD COLUMN theme_rotation INTEGER;" || exit 1
fi
if [[ ! "''${columns[@]}" =~ "theme_texture" ]]; then
${sqlite3} "$placesFile" "ALTER TABLE zen_workspaces ADD COLUMN theme_texture REAL;" || exit 1
fi
'';
# Source: https://github.com/zen-browser/desktop/blob/4e2dfd8a138fd28767bb4799a3ca9d8aab80430e/src/zen/workspaces/ZenWorkspacesStorage.mjs#L141-L149
updateSpacesTable = spaces:
pkgs.writeText "insert.sql" ''
INSERT OR REPLACE INTO zen_workspaces (
uuid,
name,
icon,
container_id,
"position",
theme_type,
theme_colors,
theme_opacity,
theme_rotation,
theme_texture,
created_at,
updated_at
) VALUES ${pipe spaces [
(mapAttrsToList (_: space: [
"{${space.id}}"
space.name
(attrByPath ["icon"] null space)
(attrByPath ["container"] null space)
(attrByPath ["position"] 0 space)
(attrByPath ["theme" "type"] "gradient" space)
(map (color: {
inherit (color) algorithm lightness position type;
c = [color.red color.green color.blue];
isCustom = color.custom;
isPrimary = color.primary;
}) (attrByPath ["theme" "colors"] [] space))
(attrByPath ["theme" "opacity"] 0.5 space)
(attrByPath ["theme" "rotation"] null space)
(attrByPath ["theme" "texture"] 0.0 space)
]))
(map (row:
map (
v:
with builtins;
if isStringLike v
then "'${v}'"
else if (isList v) || (isAttrs v)
then "'${toJSON v}'"
else if isNull v
then "NULL"
else toString v
)
row))
(map (row:
row
++ [
"COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = ${elemAt row 0}), strftime('%s', 'now'))"
"strftime('%s', 'now')"
]))
(map (row: concatStringsSep "," row))
(concatMapStringsSep "," (row: "(${row})"))
]}
'';
filterSpacesTable = spaces:
pkgs.writeText "filter.sql" ''
DELETE FROM zen_workspaces ${
if spaces != {}
then "WHERE "
else ""
}${
concatMapAttrsStringSep " AND " (n: v: "NOT uuid = '{${v.id}}'") spaces
}
'';
in
mkIf hasSpaces {
Install.WantedBy = ["default.target"];
Unit.After = ["home-manager-${config.home.username}.service"];
Service = {
Type = "oneshot";
ExecStart =
pipe cfg.profiles [
(filterAttrs (_: v: v.spaces != {}))
(mapAttrsToList (n: v: let
placesFile = "${configPath}/${n}/places.sqlite";
in (pkgs.writeShellScriptBin "zen-browser-spaces-${n}" ''
mkdir -p "${configPath}/${n}"
${getExe initSpacesTable} "${placesFile}"
${sqlite3} "${placesFile}" ".read ${updateSpacesTable v.spaces}"
${optionalString v.spacesForce
''${sqlite3} "${placesFile}" ".read ${filterSpacesTable v.spaces}"''}
'')))
(list: map (pkg: getExe pkg) list)
];
};
};
};
}