Merge branch 'trunk' into plasma-panel-spacer-extended

This commit is contained in:
Heitor Augusto 2024-09-27 16:03:01 -03:00 committed by GitHub
commit 9075c576af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 7102 additions and 2073 deletions

View File

@ -1,10 +1,16 @@
name: "Check"
name: "Nix Checks"
on:
pull_request:
paths:
- '**/*.nix'
- 'flake.lock'
- 'script/**'
# cancel previous runs when pushing new changes
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
checks:
runs-on: ubuntu-latest

View File

@ -1,8 +1,14 @@
name: GitHub Pages
name: GitHub Pages Docs Generation
on:
push:
branches:
- trunk
- trunk
paths:
- 'flake.nix'
- 'flake.lock'
- 'modules/**'
- 'docs/**'
jobs:
publish:
strategy:
@ -25,4 +31,4 @@ jobs:
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
publish_dir: ./public

View File

@ -33,16 +33,19 @@ broken when used with plasma 5. If you want the best experience with
At the moment `plasma-manager` supports configuring the following:
- KDE configuration files (via the `files` module)
- Global themes, colorschemes, icons, cursortheme, wallpaper (via the `workspace` module)
- Desktop icons, widgets, and mouse actions (via the `desktop` module)
- Configuration of spectacle shortcuts (via the `spectacle` module)
- Shortcuts (via the `shortcuts` module)
- Hotkeys (via the `hotkeys` module)
- Panels and Extra Widgets (via the `panels` module)
- Keyboards, Touchpads and Mice (via the `input` module)
- KRunner (via the `krunner` module)
- Screen locker (via the `kscreenlocker` module)
- Fonts (via the `fonts` module)
- Window Rules (via the `window-rules` module)
- KDE apps (via the `apps` module). In particular the following kde apps have
modules in `plasma-manager`:
- ghostwriter
- kate
- konsole
- okular
@ -76,6 +79,10 @@ We provide some examples to help you get started. These are located in the
With more to come! These should give you some idea how to get started with
`plasma-manager`.
Additionally,
[the manual section containing all the supported plasma-manager options](https://nix-community.github.io/plasma-manager/options.xhtml)
may come in handy.
## Make your configuration more declarative with overrideConfig
By default `plasma-manager` will simply write the specified configurations to
various config-files and leave all other options alone. This way settings not

View File

@ -1,4 +1,6 @@
{ pkgs ? import <nixpkgs> { } }:
{
pkgs ? import <nixpkgs> { },
}:
rec {
docs = import ./docs {
inherit pkgs;

View File

@ -1,7 +1,12 @@
{ pkgs, lib, ... }:
let
dontCheckModules = { _module.check = false; };
modules = [ ../modules dontCheckModules ];
dontCheckModules = {
_module.check = false;
};
modules = [
../modules
dontCheckModules
];
githubDeclaration = user: repo: branch: subpath: {
url = "https://github.com/${user}/${repo}/blob/${branch}/${subpath}";
@ -10,30 +15,40 @@ let
pmPath = toString ./..;
transformOptions =
opt: opt // {
declarations = (map
(decl:
if (lib.hasPrefix pmPath (toString decl)) then (githubDeclaration "nix-community" "plasma-manager" "trunk" (lib.removePrefix "/" (lib.removePrefix pmPath (toString decl)))) else decl)
opt.declarations);
opt:
opt
// {
declarations = (
map (
decl:
if (lib.hasPrefix pmPath (toString decl)) then
(githubDeclaration "nix-community" "plasma-manager" "trunk" (
lib.removePrefix "/" (lib.removePrefix pmPath (toString decl))
))
else
decl
) opt.declarations
);
};
buildOptionsDocs = (args@{ modules, ... }:
buildOptionsDocs = (
args@{ modules, ... }:
let
opts = (lib.evalModules {
inherit modules;
class = "homeManager";
}).options;
opts =
(lib.evalModules {
inherit modules;
class = "homeManager";
}).options;
options = builtins.removeAttrs opts [ "_module" ];
in
pkgs.buildPackages.nixosOptionsDoc {
inherit options;
inherit transformOptions;
warningsAreErrors = false;
});
}
);
pmOptionsDoc = buildOptionsDocs {
inherit modules;
};
pmOptionsDoc = buildOptionsDocs { inherit modules; };
plasma-manager-options = pkgs.callPackage ./plasma-manager-options.nix {
nixos-render-docs = pkgs.nixos-render-docs;
plasma-manager-options = pmOptionsDoc.optionsJSON;
@ -42,4 +57,5 @@ let
in
{
html = plasma-manager-options;
json = pmOptionsDoc.optionsJSON;
}

View File

@ -1,9 +1,10 @@
{ stdenv
, nixos-render-docs
, plasma-manager-options
, revision
, lib
, documentation-highlighter
{
stdenv,
nixos-render-docs,
plasma-manager-options,
revision,
lib,
documentation-highlighter,
}:
let
outputPath = "share/doc/plasma-manager";

View File

@ -175,7 +175,7 @@
icon = "view-media-track";
};
preferredSource = "spotify";
showPlaybackControls = true;
musicControls.showPlaybackControls = true;
songText = {
displayInSeparateLines = true;
maximumWidth = 640;
@ -213,29 +213,47 @@
];
powerdevil = {
powerButtonAction = "lockScreen";
autoSuspend = {
action = "shutDown";
idleTimeout = 1000;
AC = {
powerButtonAction = "lockScreen";
autoSuspend = {
action = "shutDown";
idleTimeout = 1000;
};
turnOffDisplay = {
idleTimeout = 1000;
idleTimeoutWhenLocked = "immediately";
};
};
turnOffDisplay = {
idleTimeout = 1000;
idleTimeoutWhenLocked = "immediately";
battery = {
powerButtonAction = "sleep";
whenSleepingEnter = "standbyThenHibernate";
};
lowBattery = {
whenLaptopLidClosed = "hibernate";
};
};
kwin = {
edgeBarrier = 0; # Disables the edge-barriers introduced in plasma 6.1
cornerBarrier = false;
scripts.polonium.enable = true;
};
kscreenlocker = {
lockOnResume = true;
timeout = 10;
};
#
# Some mid-level settings:
#
shortcuts = {
ksmserver = {
"Lock Session" = [ "Screensaver" "Meta+Ctrl+Alt+L" ];
"Lock Session" = [
"Screensaver"
"Meta+Ctrl+Alt+L"
];
};
kwin = {
@ -247,7 +265,6 @@
};
};
#
# Some low-level settings:
#

View File

@ -1,9 +1,7 @@
{ pkgs, ... }:
{
imports = [
<plasma-manager/modules>
];
imports = [ <plasma-manager/modules> ];
programs.plasma = {
enable = true;
@ -41,19 +39,19 @@
{
location = "top";
height = 26;
widgets = [
"org.kde.plasma.appmenu"
];
widgets = [ "org.kde.plasma.appmenu" ];
}
];
#
# Some mid-level settings:
#
shortcuts = {
ksmserver = {
"Lock Session" = [ "Screensaver" "Meta+Ctrl+Alt+L" ];
"Lock Session" = [
"Screensaver"
"Meta+Ctrl+Alt+L"
];
};
kwin = {
@ -65,7 +63,6 @@
};
};
#
# Some low-level settings:
#

View File

@ -14,7 +14,13 @@
};
};
outputs = inputs@ { nixpkgs, home-manager, plasma-manager, ... }:
outputs =
inputs@{
nixpkgs,
home-manager,
plasma-manager,
...
}:
let
# Replace with your username
username = "jdoe";

View File

@ -14,7 +14,13 @@
};
};
outputs = inputs@ { nixpkgs, home-manager, plasma-manager, ... }:
outputs =
inputs@{
nixpkgs,
home-manager,
plasma-manager,
...
}:
let
# Replace with your username
username = "jdoe";

View File

@ -8,7 +8,8 @@
home-manager.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = inputs@{ self, ... }:
outputs =
inputs@{ self, ... }:
let
# Systems that can run tests:
supportedSystems = [
@ -21,36 +22,49 @@
forAllSystems = inputs.nixpkgs.lib.genAttrs supportedSystems;
# Attribute set of nixpkgs for each system:
nixpkgsFor = forAllSystems (system:
import inputs.nixpkgs { inherit system; });
nixpkgsFor = forAllSystems (system: import inputs.nixpkgs { inherit system; });
in
{
homeManagerModules.plasma-manager = { ... }: {
imports = [ ./modules ];
};
homeManagerModules.plasma-manager =
{ ... }:
{
imports = [ ./modules ];
};
packages = forAllSystems (system:
let pkgs = nixpkgsFor.${system}; in
packages = forAllSystems (
system:
let
pkgs = nixpkgsFor.${system};
docs = import ./docs {
inherit pkgs;
lib = pkgs.lib;
};
in
{
default = self.packages.${system}.rc2nix;
demo = (inputs.nixpkgs.lib.nixosSystem {
inherit system;
modules = [
(import test/demo.nix {
home-manager-module = inputs.home-manager.nixosModules.home-manager;
plasma-module = self.homeManagerModules.plasma-manager;
})
(_: { environment.systemPackages = [ self.packages.${system}.rc2nix ]; })
];
}).config.system.build.vm;
demo =
(inputs.nixpkgs.lib.nixosSystem {
inherit system;
modules = [
(import test/demo.nix {
home-manager-module = inputs.home-manager.nixosModules.home-manager;
plasma-module = self.homeManagerModules.plasma-manager;
})
(_: { environment.systemPackages = [ self.packages.${system}.rc2nix ]; })
];
}).config.system.build.vm;
docs-html = docs.html;
docs-json = docs.json;
rc2nix = pkgs.writeShellApplication {
name = "rc2nix";
runtimeInputs = with pkgs; [ python3 ];
text = ''python3 ${script/rc2nix.py} "$@"'';
};
});
}
);
apps = forAllSystems (system: {
default = self.apps.${system}.rc2nix;
@ -66,28 +80,26 @@
};
});
checks = forAllSystems (system:
{
default = nixpkgsFor.${system}.callPackage ./test/basic.nix {
home-manager-module = inputs.home-manager.nixosModules.home-manager;
plasma-module = self.homeManagerModules.plasma-manager;
};
});
checks = forAllSystems (system: {
default = nixpkgsFor.${system}.callPackage ./test/basic.nix {
home-manager-module = inputs.home-manager.nixosModules.home-manager;
plasma-module = self.homeManagerModules.plasma-manager;
};
});
formatter = forAllSystems (system: nixpkgsFor.${system}.treefmt);
devShells = forAllSystems (system: {
default = nixpkgsFor.${system}.mkShell {
buildInputs = with nixpkgsFor.${system}; [
nixfmt-rfc-style
ruby
ruby.devdoc
(
python3.withPackages (
pyPkgs: [
pyPkgs.python-lsp-server
pyPkgs.black
pyPkgs.isort
]
)
)
(python3.withPackages (pyPkgs: [
pyPkgs.python-lsp-server
pyPkgs.black
pyPkgs.isort
]))
];
};
});

View File

@ -35,13 +35,10 @@ let
"Colors:Window" = colorUIKeys;
};
in
(lib.mkMerge
(lib.mapAttrsToList
(group: keys: {
"kdeglobals"."${group}" = (lib.mkMerge
(map
(key:
{ "${key}"."persistent" = (lib.mkDefault true); })
keys));
})
ignoreKeys))
(lib.mkMerge (
lib.mapAttrsToList (group: keys: {
"kdeglobals"."${group}" = (
lib.mkMerge (map (key: { "${key}"."persistent" = (lib.mkDefault true); }) keys)
);
}) ignoreKeys
))

View File

@ -1,7 +1,8 @@
{ lib, config, ... }:
let
widgets = (import ../modules/widgets { inherit lib; });
panelToLayout = panel:
panelToLayout =
panel:
let
inherit (widgets.lib) addWidgetStmts stringIfNotNull;
inherit (lib) boolToString;
@ -14,9 +15,14 @@ let
'';
in
''
${if (panel.screen == "all") then "for (screenID = 0; screenID < screenCount; screenID++)"
else if (builtins.isList panel.screen) then "for (var screenID in [${builtins.concatStringsSep "," (map builtins.toString panel.screen)}])"
else ""}
${
if (panel.screen == "all") then
"for (screenID = 0; screenID < screenCount; screenID++)"
else if (builtins.isList panel.screen) then
"for (var screenID in [${builtins.concatStringsSep "," (map builtins.toString panel.screen)}])"
else
""
}
{
const panel = new Panel();
panel.height = ${toString panel.height};

View File

@ -1,4 +1,5 @@
{lib, ...}: let
{ lib, ... }:
let
#=== ENUMS ===
enums = {
# QFont::StyleHint
@ -93,18 +94,24 @@
};
};
inherit (builtins) attrNames mapAttrs removeAttrs isAttrs;
inherit (builtins)
attrNames
mapAttrs
removeAttrs
isAttrs
;
inherit (lib) filterAttrs;
toEnums = v: lib.types.enum (attrNames v);
in
mapAttrs (_: toEnums) (removeAttrs enums ["styleStrategy"])
// {
styleStrategy = mapAttrs (_: toEnums) (filterAttrs (_: isAttrs) enums.styleStrategy);
mapAttrs (_: toEnums) (removeAttrs enums [ "styleStrategy" ])
// {
styleStrategy = mapAttrs (_: toEnums) (filterAttrs (_: isAttrs) enums.styleStrategy);
# Converts a font specified by the given attrset to a string representation compatible with
# QFont::fromString and QFont::toString.
fontToString = {
# Converts a font specified by the given attrset to a string representation compatible with
# QFont::fromString and QFont::toString.
fontToString =
{
family,
pointSize ? null,
pixelSize ? null,
@ -119,18 +126,22 @@ in
letterSpacing ? 0,
wordSpacing ? 0,
stretch ? "anyStretch",
styleStrategy ? {},
styleStrategy ? { },
styleName ? null,
}: let
inherit (builtins) isString toString foldl' bitOr;
}:
let
inherit (builtins)
isString
toString
foldl'
bitOr
;
styleStrategy' = let
match = s: enums.styleStrategy.${s}.${styleStrategy.${s} or "default"};
ifSet = k:
if styleStrategy.${k} or false
then enums.styleStrategy.${k}
else 0;
in
styleStrategy' =
let
match = s: enums.styleStrategy.${s}.${styleStrategy.${s} or "default"};
ifSet = k: if styleStrategy.${k} or false then enums.styleStrategy.${k} else 0;
in
foldl' bitOr 0 [
(match "prefer")
(match "matchingPrefer")
@ -140,40 +151,34 @@ in
(ifSet "noFontMerging")
];
sizeToString = s:
if s == null
then "-1"
else toString s;
sizeToString = s: if s == null then "-1" else toString s;
numOrEnum = attrs: s:
if isString s
then toString attrs.${s}
else toString s;
numOrEnum = attrs: s: if isString s then toString attrs.${s} else toString s;
zeroOrOne = b:
if b
then "1"
else "0";
zeroOrOne = b: if b then "1" else "0";
in
assert lib.assertMsg (lib.xor (pointSize != null) (pixelSize != null))
"Exactly one of `pointSize` and `pixelSize` has to be set.";
builtins.concatStringsSep "," ([
family
(sizeToString pointSize)
(sizeToString pixelSize)
(toString enums.styleHint.${styleHint})
(numOrEnum enums.weight weight)
(numOrEnum enums.style style)
(zeroOrOne underline)
(zeroOrOne strikeOut)
(zeroOrOne fixedPitch)
"0"
(toString enums.capitalization.${capitalization})
(toString enums.spacingType.${letterSpacingType})
(toString letterSpacing)
(toString wordSpacing)
(numOrEnum enums.stretch stretch)
(toString styleStrategy')
]
++ lib.optional (styleName != null) styleName);
}
assert lib.assertMsg (lib.xor (pointSize != null) (
pixelSize != null
)) "Exactly one of `pointSize` and `pixelSize` has to be set.";
builtins.concatStringsSep "," (
[
family
(sizeToString pointSize)
(sizeToString pixelSize)
(toString enums.styleHint.${styleHint})
(numOrEnum enums.weight weight)
(numOrEnum enums.style style)
(zeroOrOne underline)
(zeroOrOne strikeOut)
(zeroOrOne fixedPitch)
"0"
(toString enums.capitalization.${capitalization})
(toString enums.spacingType.${letterSpacingType})
(toString letterSpacing)
(toString wordSpacing)
(numOrEnum enums.stretch stretch)
(toString styleStrategy')
]
++ lib.optional (styleName != null) styleName
);
}

View File

@ -2,42 +2,62 @@
let
##############################################################################
# Types for storing settings.
basicSettingsType = (with lib.types;
nullOr (oneOf [ bool float int str ]));
advancedSettingsType = (with lib.types; submodule {
options = {
value = lib.mkOption {
type = basicSettingsType;
default = null;
description = "The value for some key.";
basicSettingsType = (
with lib.types;
nullOr (oneOf [
bool
float
int
str
])
);
advancedSettingsType = (
with lib.types;
submodule {
options = {
value = lib.mkOption {
type = basicSettingsType;
default = null;
description = "The value for some key.";
};
immutable = lib.mkOption {
type = bool;
default = config.programs.plasma.immutableByDefault;
description = ''
Whether to make the key immutable. This corresponds to adding [$i] to
the end of the key.
'';
};
shellExpand = lib.mkOption {
type = bool;
default = false;
description = ''
Whether to mark the key for shell expansion. This corresponds to
adding [$e] to the end of the key.
'';
};
persistent = lib.mkOption {
type = bool;
default = false;
description = ''
When overrideConfig is enabled and the key is persistent,
plasma-manager will leave it unchanged after activation.
'';
};
escapeValue = lib.mkOption {
type = bool;
default = true;
description = ''
Whether to escape the value according to kde's escape-format. See:
https://github.com/KDE/kconfig/blob/44f98ff5cb9008436ba5ba385cae03bbd0ab33e6/src/core/kconfigini.cpp#L882
for info about this format.
'';
};
};
immutable = lib.mkOption {
type = bool;
default = config.programs.plasma.immutableByDefault;
description = ''
Whether to make the key immutable. This corresponds to adding [$i] to
the end of the key.
'';
};
shellExpand = lib.mkOption {
type = bool;
default = false;
description = ''
Whether to mark the key for shell expansion. This corresponds to
adding [$e] to the end of the key.
'';
};
persistent = lib.mkOption {
type = bool;
default = false;
description = ''
When overrideConfig is enabled and the key is persistent,
plasma-manager will leave it unchanged after activation.
'';
};
};
});
coercedSettingsType = with lib.types;
}
);
coercedSettingsType =
with lib.types;
coercedTo basicSettingsType (value: { inherit value; }) advancedSettingsType;
in
{

View File

@ -1,30 +1,55 @@
{ lib, ... }:
{
wallpaperPictureOfTheDayType = with lib.types; submodule {
options = {
provider = lib.mkOption {
type = nullOr (enum [ "apod" "bing" "flickr" "natgeo" "noaa" "wcpotd" "epod" "simonstalenhag" ]);
description = "The provider for the Picture of the Day plugin.";
};
updateOverMeteredConnection = lib.mkOption {
type = bool;
default = false;
description = "Whether to update the wallpaper on a metered connection.";
wallpaperPictureOfTheDayType =
with lib.types;
submodule {
options = {
provider = lib.mkOption {
type = nullOr (enum [
"apod"
"bing"
"flickr"
"natgeo"
"noaa"
"wcpotd"
"epod"
"simonstalenhag"
]);
description = "The provider for the Picture of the Day plugin.";
};
updateOverMeteredConnection = lib.mkOption {
type = bool;
default = false;
description = "Whether to update the wallpaper on a metered connection.";
};
};
};
};
wallpaperSlideShowType = with lib.types; submodule {
options = {
path = lib.mkOption {
type = either path (listOf path);
description = "The path(s) where the wallpapers are located.";
};
interval = lib.mkOption {
type = int;
default = 300;
description = "The length between wallpaper switches.";
wallpaperSlideShowType =
with lib.types;
submodule {
options = {
path = lib.mkOption {
type = either path (listOf path);
description = "The path(s) where the wallpapers are located.";
};
interval = lib.mkOption {
type = int;
default = 300;
description = "The length between wallpaper switches.";
};
};
};
# Values are taken from
# https://invent.kde.org/plasma/kdeplasma-addons/-/blob/bc53d651cf60709396c9229f8c582ec8a9d2ee53/applets/mediaframe/package/contents/ui/ConfigGeneral.qml#L148-170
wallpaperFillModeTypes = {
"stretch" = 0; # a.k.a. Scaled
"preserveAspectFit" = 1; # a.k.a. Scaled Keep Proportions
"preserveAspectCrop" = 2; # a.k.a. Scaled And Cropped
"tile" = 3;
"tileVertically" = 4;
"tileHorizontally" = 5;
"pad" = 6; # a.k.a. Centered
};
}

View File

@ -1,4 +1,8 @@
{ pkgs, lib, config }:
{
pkgs,
lib,
config,
}:
let
writeConfigScript = pkgs.writeShellApplication {
@ -12,17 +16,19 @@ let
# attribute-set as json. Here a is the attribute-set.
#
# Type: AttrSet -> string
writeConfig = json: overrideConfig: resetFilesList:
writeConfig =
json: overrideConfig: resetFilesList:
let
jsonStr = builtins.toJSON json;
# Writing to file handles special characters better than passing it in as
# an argument to the script.
jsonFile = pkgs.writeText "data.json" jsonStr;
resetFilesStr = builtins.toString
(if overrideConfig then
resetFilesStr = builtins.toString (
if overrideConfig then
resetFilesList ++ [ "${config.xdg.dataHome}/plasma-manager/last_run_*" ]
else
resetFilesList);
resetFilesList
);
immutableByDefault = (builtins.toString config.programs.plasma.immutableByDefault);
in
''

View File

@ -2,6 +2,7 @@
{
imports = [
./ghostwriter.nix
./konsole.nix
./kate
./okular.nix

View File

@ -0,0 +1,737 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.programs.ghostwriter;
qfont = import ../../lib/qfont.nix { inherit lib; };
createThemes = lib.attrsets.mapAttrs' (
name: value:
lib.attrsets.nameValuePair ("ghostwriter/themes/${name}.json") ({
enable = true;
source = value;
})
);
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex (x: x == value)
(throw "getIndexFromEnum (ghostwriter): Value ${value} isn't present in the enum. This is a bug")
enum;
getBoolFromEnum =
enum: value:
if value == null then
null
else if (getIndexFromEnum enum value) == 0 then
false
else
true;
styleStrategyType = lib.types.submodule {
options = with qfont.styleStrategy; {
prefer = lib.mkOption {
type = prefer;
default = "default";
description = ''
Which type of font is preferred by the font when finding an appropriate default family.
`default`, `bitmap`, `device`, `outline`, `forceOutline` correspond to the
`PreferDefault`, `PreferBitmap`, `PreferDevice`, `PreferOutline`, `ForceOutline` enum flags
respectively.
'';
};
matchingPrefer = lib.mkOption {
type = matchingPrefer;
default = "default";
description = ''
Whether the font matching process prefers exact matches, of best quality matches.
`default` corresponds to not setting any enum flag, and `exact` and `quality`
correspond to `PreferMatch` and `PreferQuality` enum flags respectively.
'';
};
antialiasing = lib.mkOption {
type = antialiasing;
default = "default";
description = ''
Whether antialiasing is preferred for this font.
`default` corresponds to not setting any enum flag, and `prefer` and `disable`
correspond to `PreferAntialias` and `NoAntialias` enum flags respectively.
'';
};
noSubpixelAntialias = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
If set to true, this font will try to avoid subpixel antialiasing.
Corresponds to the `NoSubpixelAntialias` enum flag.
'';
};
noFontMerging = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
If set to true, this font will not try to find a substitute font when encountering missing glyphs.
Corresponds to the `NoFontMerging` enum flag.
'';
};
preferNoShaping = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
If set to true, this font will not try to apply shaping rules that may be required for some scripts
(e.g. Indic scripts), increasing performance if these rules are not required.
Corresponds to the `PreferNoShaping` enum flag.
'';
};
};
};
fontType = lib.types.submodule {
options = {
family = lib.mkOption {
type = lib.types.str;
description = "The font family of this font.";
example = "Noto Sans";
};
pointSize = lib.mkOption {
type = lib.types.nullOr lib.types.numbers.positive;
default = null;
description = ''
The point size of this font.
Could be a decimal, but usually an integer. Mutually exclusive with pixel size.
'';
};
pixelSize = lib.mkOption {
type = lib.types.nullOr lib.types.ints.u16;
default = null;
description = ''
The pixel size of this font.
Mutually exclusive with point size.
'';
};
styleHint = lib.mkOption {
type = qfont.styleHint;
default = "anyStyle";
description = ''
The style hint of this font.
See https://doc.qt.io/qt-6/qfont.html#StyleHint-enum for more.
'';
};
weight = lib.mkOption {
type = lib.types.either (lib.types.ints.between 1 1000) qfont.weight;
default = "normal";
description = ''
The weight of the font, either as a number between 1 to 1000 or as a pre-defined weight string.
See https://doc.qt.io/qt-6/qfont.html#Weight-enum for more.
'';
};
style = lib.mkOption {
type = qfont.style;
default = "normal";
description = "The style of the font.";
};
underline = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether the font is underlined.";
};
strikeOut = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether the font is struck out.";
};
fixedPitch = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether the font has a fixed pitch.";
};
capitalization = lib.mkOption {
type = qfont.capitalization;
default = "mixedCase";
description = ''
The capitalization settings for this font.
See https://doc.qt.io/qt-6/qfont.html#Capitalization-enum for more.
'';
};
letterSpacingType = lib.mkOption {
type = qfont.spacingType;
default = "percentage";
description = ''
Whether to use percentage or absolute spacing for this font.
See https://doc.qt.io/qt-6/qfont.html#SpacingType-enum for more.
'';
};
letterSpacing = lib.mkOption {
type = lib.types.number;
default = 0;
description = ''
The amount of letter spacing for this font.
Could be a percentage or an absolute spacing change (positive increases spacing, negative decreases spacing),
based on the selected `letterSpacingType`.
'';
};
wordSpacing = lib.mkOption {
type = lib.types.number;
default = 0;
description = ''
The amount of word spacing for this font, in pixels.
Positive values increase spacing while negative ones decrease spacing.
'';
};
stretch = lib.mkOption {
type = lib.types.either (lib.types.ints.between 1 4000) qfont.stretch;
default = "anyStretch";
description = ''
The stretch factor for this font, as an integral percentage (i.e. 150 means a 150% stretch),
or as a pre-defined stretch factor string.
'';
};
styleStrategy = lib.mkOption {
type = styleStrategyType;
default = { };
description = ''
The strategy for matching similar fonts to this font.
See https://doc.qt.io/qt-6/qfont.html#StyleStrategy-enum for more.
'';
};
styleName = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
The style name of this font, overriding the `style` and `weight` parameters when set.
Used for special fonts that have styles beyond traditional settings.
'';
};
};
};
in
{
options.programs.ghostwriter = {
enable = lib.mkEnableOption ''
Enable configuration management for Ghostwriter.
'';
font = lib.mkOption {
type = lib.types.nullOr fontType;
default = null;
example = {
family = "Noto Sans";
pointSize = 12;
};
description = ''
The font to use for Ghostwriter.
'';
apply = font: if font == null then null else ''"${qfont.fontToString font}"'';
};
locale = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "en_US";
description = ''
The locale to use for Ghostwriter.
'';
};
package =
lib.mkPackageOption pkgs
[
"kdePackages"
"ghostwriter"
]
{
example = "pkgs.kdePackages.ghostwriter";
extraDescription = ''
Use `pkgs.libsForQt5.ghostwriter` in Plasma5 and
`pkgs.kdePackages.ghostwriter` in Plasma6.
'';
};
editor = {
styling = {
blockquoteStyle =
let
enumVals = [
"simple"
"italic"
];
in
lib.mkOption {
type = lib.types.nullOr (lib.types.enum enumVals);
default = null;
example = "simple";
description = "The style of blockquotes.";
apply = getBoolFromEnum enumVals;
};
editorWidth =
let
enumVals = [
"narrow"
"medium"
"wide"
"full"
];
in
lib.mkOption {
type = lib.types.nullOr (lib.types.enum enumVals);
default = null;
example = "medium";
description = "The width of the editor.";
apply = getIndexFromEnum enumVals;
};
emphasisStyle =
let
enumVals = [
"italic"
"underline"
];
in
lib.mkOption {
type = lib.types.nullOr (lib.types.enum enumVals);
default = null;
example = "bold";
description = "The style of emphasis.";
apply = getBoolFromEnum enumVals;
};
focusMode =
let
enumVals = [
"sentence"
"currentLine"
"threeLines"
"paragraph"
"typewriter"
];
in
lib.mkOption {
type = lib.types.nullOr (lib.types.enum enumVals);
default = null;
example = "sentence";
description = "The focus mode to use.";
apply =
focusMode:
if focusMode == null then
null
else
builtins.elemAt
[
1
2
3
4
5
]
(
lib.lists.findFirstIndex (x: x == focusMode)
(throw "editor.styling.focusMode: Value ${focusMode} isn't present in the enum. This is a bug")
enumVals
);
};
useLargeHeadings = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to use large headings.";
};
};
tabulation = {
insertSpacesForTabs = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
description = ''
Whether to insert spaces for tabs.
'';
};
tabWidth = lib.mkOption {
type = lib.types.nullOr lib.types.ints.positive;
default = null;
description = ''
The width of a tab.
'';
};
};
typing = {
automaticallyMatchCharacters = {
enable = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to automatically match characters.";
};
characters = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = ''\"'([{*_`<'';
description = "The characters to automatically match.";
apply = chars: if chars == null then null else ''"${chars}"'';
};
};
bulletPointCycling = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to cycle through bullet points.";
};
};
};
general = {
display = {
hideMenubarInFullscreen = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to hide the menubar in fullscreen mode.";
};
interfaceStyle =
let
enumVals = [
"rounded"
"square"
];
in
lib.mkOption {
type = lib.types.nullOr (lib.types.enum enumVals);
default = null;
example = "rounded";
description = "The interface style to use for Ghostwriter.";
apply = getIndexFromEnum enumVals;
};
showCurrentTimeInFullscreen = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to show the current time in fullscreen mode.";
};
showUnbreakableSpace = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to show unbreakable space.";
};
};
fileSaving = {
autoSave = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to enable auto-save.";
};
backupFileOnSave = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to backup the file on save.";
};
backupLocation = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
example = "/home/user/.local/share/ghostwriter/backups";
description = ''
The location to store backups of the Ghostwriter configuration.
'';
};
};
session = {
openLastFileOnStartup = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to open the last file on startup.";
};
rememberRecentFiles = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to remember recent files.";
};
};
};
preview = {
codeFont = lib.mkOption {
type = lib.types.nullOr fontType;
default = null;
example = {
family = "Hack";
pointSize = 12;
};
description = ''
The code font to use for the preview.
'';
apply = font: if font == null then null else ''"${qfont.fontToString font}"'';
};
commandLineOptions = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
Additional command line options to pass to the preview command.
'';
};
markdownVariant = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
example = "cmark-gfm";
description = ''
The markdown variant to use for the preview.
'';
};
openByDefault = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = ''
Whether to open the preview by default.
'';
};
textFont = lib.mkOption {
type = lib.types.nullOr fontType;
default = null;
example = {
family = "Inter";
pointSize = 12;
};
description = ''
The text font to use for the preview.
'';
apply = font: if font == null then null else ''"${qfont.fontToString font}"'';
};
};
spelling = {
autoDetectLanguage = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to auto-detect the language.";
};
checkerEnabledByDefault = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether the checker is enabled by default.";
};
ignoreUppercase = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to ignore uppercase words.";
};
ignoredWords = lib.mkOption {
type = lib.types.nullOr (lib.types.listOf lib.types.str);
default = null;
example = [
"Amarok"
"KHTML"
"NixOS"
];
description = "Words to ignore in the spell checker.";
apply =
ignoredWords: if ignoredWords == null then null else builtins.concatStringsSep ", " ignoredWords;
};
liveSpellCheck = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to enable live spell checking.";
};
skipRunTogether = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether to skip run-together words.";
};
};
theme = {
name = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
example = "Ghostwriter";
description = ''
The name of the theme to use.
'';
};
customThemes = lib.mkOption {
type = with lib.types; attrsOf path;
default = { };
description = ''
Custom themes to be added to the installation. The key is their name.
Choose them in `programs.ghostwriter.theme.name`.
'';
};
};
window.sidebarOpen = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
example = true;
description = "Whether the sidebar is open by default.";
};
};
config = (
lib.mkIf cfg.enable {
assertions = [
{
assertion = cfg.spelling.ignoredWords == null || cfg.locale != null;
message = "ghostwriter: Ignored words can only be set if a locale is set.";
}
];
home.packages = [ cfg.package ];
programs.plasma.configFile = {
"kde.org/ghostwriter.conf" = (
lib.mkMerge [
# Font
(lib.mkIf (cfg.font != null) { Style.editorFont = cfg.font; })
# Locale
(lib.mkIf (cfg.locale != null) { Application.locale = cfg.locale; })
# Editor > Styling
(lib.mkIf (cfg.editor.styling.blockquoteStyle != null) {
Style.blockquoteStyle = cfg.editor.styling.blockquoteStyle;
})
(lib.mkIf (cfg.editor.styling.emphasisStyle != null) {
Style.underlineInsteadOfItalics = cfg.editor.styling.emphasisStyle;
})
(lib.mkIf (cfg.editor.styling.editorWidth != null) {
Style.editorWidth = cfg.editor.styling.editorWidth;
})
(lib.mkIf (cfg.editor.styling.focusMode != null) {
Style.focusMode = cfg.editor.styling.focusMode;
})
(lib.mkIf (cfg.editor.styling.useLargeHeadings != null) {
Style.largeHeadings = cfg.editor.styling.useLargeHeadings;
})
# Editor > Tabulation
(lib.mkIf (cfg.editor.tabulation.insertSpacesForTabs != null) {
Tabs.insertSpacesForTabs = cfg.editor.tabulation.insertSpacesForTabs;
})
(lib.mkIf (cfg.editor.tabulation.tabWidth != null) {
Tabs.tabWidth = cfg.editor.tabulation.tabWidth;
})
# Editor > Typing
(lib.mkIf (cfg.editor.typing.automaticallyMatchCharacters.enable != null) {
Typing.autoMatchEnabled = cfg.editor.typing.automaticallyMatchCharacters.enable;
})
(lib.mkIf (cfg.editor.typing.automaticallyMatchCharacters.characters != null) {
Typing.autoMatchFilter = {
value = cfg.editor.typing.automaticallyMatchCharacters.characters;
escapeValue = false;
};
})
(lib.mkIf (cfg.editor.typing.bulletPointCycling != null) {
Typing.bulletPointCyclingEnabled = cfg.editor.typing.bulletPointCycling;
})
# General > Display
(lib.mkIf (cfg.general.display.hideMenubarInFullscreen != null) {
Style.hideMenuBarInFullscreen = cfg.general.display.hideMenubarInFullscreen;
})
(lib.mkIf (cfg.general.display.interfaceStyle != null) {
Style.interfaceStyle = cfg.general.display.interfaceStyle;
})
(lib.mkIf (cfg.general.display.showCurrentTimeInFullscreen != null) {
Style.displayTimeInFullScreen = cfg.general.display.showCurrentTimeInFullscreen;
})
(lib.mkIf (cfg.general.display.showUnbreakableSpace != null) {
style.showUnbreakableSpace = cfg.general.display.showUnbreakableSpace;
})
# General > File Saving
(lib.mkIf (cfg.general.fileSaving.autoSave != null) {
Save.autoSave = cfg.general.fileSaving.autoSave;
})
(lib.mkIf (cfg.general.fileSaving.backupFileOnSave != null) {
Save.backupFile = cfg.general.fileSaving.backupFileOnSave;
})
(lib.mkIf (cfg.general.fileSaving.backupLocation != null) {
Backup.location = cfg.general.fileSaving.backupLocation;
})
# General > Session
(lib.mkIf (cfg.general.session.openLastFileOnStartup != null) {
Session.restoreSession = cfg.general.session.openLastFileOnStartup;
})
(lib.mkIf (cfg.general.session.rememberRecentFiles != null) {
Session.rememberFileHistory = cfg.general.session.rememberRecentFiles;
})
# Spelling
(lib.mkIf (cfg.spelling.liveSpellCheck != null) {
Spelling = {
liveSpellCheck = cfg.spelling.liveSpellCheck;
};
})
# Preview options
(lib.mkIf (cfg.preview.codeFont != null) { Preview.codeFont = cfg.preview.codeFont; })
(lib.mkIf (cfg.preview.commandLineOptions != null) {
Preview.lastUsedExporterParams = cfg.preview.commandLineOptions;
})
(lib.mkIf (cfg.preview.markdownVariant != null) {
Preview.lastUsedExporter = cfg.preview.markdownVariant;
})
(lib.mkIf (cfg.preview.openByDefault != null) {
Preview.htmlPreviewOpen = cfg.preview.openByDefault;
})
(lib.mkIf (cfg.preview.textFont != null) { Preview.textFont = cfg.preview.textFont; })
# Theme
(lib.mkIf (cfg.theme.name != null) { Style.theme = cfg.theme.name; })
# Window
(lib.mkIf (cfg.window.sidebarOpen != null) { Window.sidebarOpen = cfg.window.sidebarOpen; })
]
);
"KDE/Sonnet.conf".General = (
lib.mkMerge [
(lib.mkIf (cfg.spelling.autoDetectLanguage != null) {
autodetectLanguage = cfg.spelling.autoDetectLanguage;
})
(lib.mkIf (cfg.spelling.checkerEnabledByDefault != null) {
checkerEnabledByDefault = cfg.spelling.checkerEnabledByDefault;
})
(lib.mkIf (cfg.spelling.ignoreUppercase != null) {
checkUppercase = !cfg.spelling.ignoreUppercase;
})
(lib.mkIf (cfg.spelling.ignoredWords != null && cfg.locale != null) {
"ignore_${cfg.locale}" = cfg.spelling.ignoredWords;
})
(lib.mkIf (cfg.locale != null) { defaultLanguage = cfg.locale; })
]
);
};
xdg.dataFile = (createThemes cfg.theme.customThemes);
}
);
}

View File

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.programs.kate;
@ -7,12 +12,12 @@ let
# 0 is not tab & not undoByShiftTab
# 1 is tab & undoByShiftTab
# 2 is not tab & undoByShiftTab
tabHandlingMode = indentSettings:
if (!indentSettings.undoByShiftTab && !indentSettings.tabFromEverywhere) then 0 else
(
if (indentSettings.undoByShiftTab && indentSettings.tabFromEverywhere) then 1 else
2
);
tabHandlingMode =
indentSettings:
if (!indentSettings.undoByShiftTab && !indentSettings.tabFromEverywhere) then
0
else
(if (indentSettings.undoByShiftTab && indentSettings.tabFromEverywhere) then 1 else 2);
checkThemeNameScript = pkgs.writeShellApplication {
name = "checkThemeName";
@ -20,22 +25,20 @@ let
text = builtins.readFile ./check-theme-name-free.sh;
};
checkThemeName = name:
''
${checkThemeNameScript}/bin/checkThemeName ${name}
'';
checkThemeName = name: ''
${checkThemeNameScript}/bin/checkThemeName ${name}
'';
script = pkgs.writeScript "kate-check" (checkThemeName cfg.editor.theme.name);
getIndexFromEnum = enum: value:
if value == null
then null
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex
(x: x == value)
(throw "getIndexFromEnum (kate): Value ${value} isn't present in the enum. This is a bug")
enum;
lib.lists.findFirstIndex (
x: x == value
) (throw "getIndexFromEnum (kate): Value ${value} isn't present in the enum. This is a bug") enum;
qfont = import ../../../lib/qfont.nix { inherit lib; };
@ -237,15 +240,21 @@ in
Enable configuration management for kate.
'';
package = lib.mkPackageOption pkgs [ "kdePackages" "kate" ] {
example = "pkgs.libsForQt5.kate";
extraDescription = ''
Which kate package to install. Use `pkgs.libsForQt5.kate` in Plasma5 and
`pkgs.kdePackages.kate` in Plasma6. Use `null` if home-manager should not install kate
(use this if you want to manage the settings of this user of a system-wide kate
installation).
'';
};
package =
lib.mkPackageOption pkgs
[
"kdePackages"
"kate"
]
{
example = "pkgs.libsForQt5.kate";
extraDescription = ''
Which kate package to install. Use `pkgs.libsForQt5.kate` in Plasma5 and
`pkgs.kdePackages.kate` in Plasma6. Use `null` if home-manager should not install kate
(use this if you want to manage the settings of this user of a system-wide kate
installation).
'';
};
# ==================================
# INDENTATION
@ -306,15 +315,20 @@ in
type = lib.types.bool;
};
inputMode = let
enumVals = [ "normal" "vi" ];
in lib.mkOption {
type = lib.types.enum enumVals;
description = "The input mode for the editor.";
default = "normal";
example = "vi";
apply = getIndexFromEnum enumVals;
};
inputMode =
let
enumVals = [
"normal"
"vi"
];
in
lib.mkOption {
type = lib.types.enum enumVals;
description = "The input mode for the editor.";
default = "normal";
example = "vi";
apply = getIndexFromEnum enumVals;
};
font = lib.mkOption {
type = fontType;
@ -367,25 +381,30 @@ in
config.programs.kate.editor.theme = {
# kate's naming scheme is ${themename}.theme
# which is why we use the same naming scheme here
name = lib.mkIf (cfg.enable && null != cfg.editor.theme.src) (lib.mkForce (builtins.fromJSON (builtins.readFile cfg.editor.theme.src))."metadata"."name");
name = lib.mkIf (cfg.enable && null != cfg.editor.theme.src) (
lib.mkForce (builtins.fromJSON (builtins.readFile cfg.editor.theme.src))."metadata"."name"
);
};
# This won't override existing files since the home-manager activation fails in that case
config.xdg.dataFile."${cfg.editor.theme.name}.theme" = lib.mkIf (cfg.enable && null != cfg.editor.theme.src)
{
source = cfg.editor.theme.src;
target = "org.kde.syntax-highlighting/themes/${cfg.editor.theme.name}.theme";
};
config.xdg.dataFile."${cfg.editor.theme.name}.theme" =
lib.mkIf (cfg.enable && null != cfg.editor.theme.src)
{
source = cfg.editor.theme.src;
target = "org.kde.syntax-highlighting/themes/${cfg.editor.theme.name}.theme";
};
config = {
home.packages = lib.mkIf (cfg.enable && cfg.package != null) [ cfg.package ];
# In case of using a custom theme, check that there is no name collision
home.activation.checkKateTheme = lib.mkIf (cfg.enable && cfg.editor.theme.src != null) (lib.hm.dag.entryBefore [ "writeBoundary" ]
# No `$DRY_RUN_CMD`, since even a dryrun should fail if checks fail
''
${script}
'');
home.activation.checkKateTheme = lib.mkIf (cfg.enable && cfg.editor.theme.src != null) (
lib.hm.dag.entryBefore [ "writeBoundary" ]
# No `$DRY_RUN_CMD`, since even a dryrun should fail if checks fail
''
${script}
''
);
# In case of using a system theme, there should be a check that there exists such a theme
# but I could not figure out where to find them
@ -393,12 +412,11 @@ in
# See also [the original PR](https://github.com/nix-community/plasma-manager/pull/95#issue-2206192839)
};
# ==================================
# LSP Servers
options.programs.kate.lsp.customServers = lib.mkOption {
default = { };
type = lib.types.attrs;
default = null;
type = lib.types.nullOr lib.types.attrs;
description = ''
Add more lsp server settings here. Check out the format on the
[KDE page](https://docs.kde.org/stable5/en/kate/kate/kate-application-plugin-lspclient.html).
@ -406,7 +424,7 @@ in
'';
};
config.xdg.configFile."kate/lspclient/settings.json" = {
config.xdg.configFile."kate/lspclient/settings.json" = lib.mkIf (cfg.lsp.customServers != null) {
text = builtins.toJSON { servers = cfg.lsp.customServers; };
};
@ -476,7 +494,10 @@ in
};
"KTextEditor View" = {
"Chars To Enclose Selection" = cfg.editor.brackets.characters;
"Chars To Enclose Selection" = {
value = cfg.editor.brackets.characters;
escapeValue = false;
};
"Bracket Match Preview" = cfg.editor.brackets.highlightMatching;
"Auto Brackets" = cfg.editor.brackets.automaticallyAddClosing;
"Input Mode" = cfg.editor.inputMode;

View File

@ -1,12 +1,26 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
inherit (import ../../lib/types.nix { inherit lib; inherit config; }) basicSettingsType;
inherit
(import ../../lib/types.nix {
inherit lib;
inherit config;
})
basicSettingsType
;
# used as shown in the example in the library docs:
# https://ryantm.github.io/nixpkgs/functions/library/attrsets/#function-library-lib.attrsets.mapAttrs-prime
createColorSchemes = lib.attrsets.mapAttrs' (name: value: lib.attrsets.nameValuePair
("konsole/${name}.colorscheme")
({ enable = true; source = value; })
createColorSchemes = lib.attrsets.mapAttrs' (
name: value:
lib.attrsets.nameValuePair ("konsole/${name}.colorscheme") ({
enable = true;
source = value;
})
);
cfg = config.programs.konsole;
@ -42,11 +56,11 @@ let
name = lib.mkOption {
type = lib.types.str;
/*
TODO: Set default to null after adding an assertion
Konsole needs to have a font set to be able to change font size
Since I couldn't get that to work I'll just set a default font
Not ideal since IMO we should only write things that are set explicitly
by the user but ehh it is what it is
TODO: Set default to null after adding an assertion
Konsole needs to have a font set to be able to change font size
Since I couldn't get that to work I'll just set a default font
Not ideal since IMO we should only write things that are set explicitly
by the user but ehh it is what it is
*/
default = "Hack";
example = "Hack";
@ -131,17 +145,14 @@ in
config = lib.mkIf (cfg.enable) {
programs.plasma.configFile."konsolerc" = lib.mkMerge [
(
lib.mkIf (cfg.defaultProfile != null) {
"Desktop Entry"."DefaultProfile" = "${cfg.defaultProfile}.profile";
}
)
(
lib.mkIf (cfg.extraConfig != null) (lib.mapAttrs
(groupName: groupAttrs:
(lib.mapAttrs (keyName: keyAttrs: { value = keyAttrs; }) groupAttrs))
cfg.extraConfig)
)
(lib.mkIf (cfg.defaultProfile != null) {
"Desktop Entry"."DefaultProfile" = "${cfg.defaultProfile}.profile";
})
(lib.mkIf (cfg.extraConfig != null) (
lib.mapAttrs (
groupName: groupAttrs: (lib.mapAttrs (keyName: keyAttrs: { value = keyAttrs; }) groupAttrs)
) cfg.extraConfig
))
{
"UiSettings"."ColorScheme" = lib.mkIf (cfg.ui.colorScheme != null) {
value = cfg.ui.colorScheme;
@ -153,50 +164,44 @@ in
];
xdg.dataFile = lib.mkMerge [
(lib.mkIf (cfg.profiles != { })
(
lib.mkMerge ([
(
lib.mkMerge (
lib.mapAttrsToList
(
attrName: profile:
let
# Use the name from the name option if it's set
profileName = if builtins.isString profile.name then profile.name else attrName;
fontString = lib.mkIf (profile.font.name != null) "${profile.font.name},${builtins.toString profile.font.size}";
in
(lib.mkIf (cfg.profiles != { }) (
lib.mkMerge ([
(lib.mkMerge (
lib.mapAttrsToList (
attrName: profile:
let
# Use the name from the name option if it's set
profileName = if builtins.isString profile.name then profile.name else attrName;
fontString = lib.mkIf (
profile.font.name != null
) "${profile.font.name},${builtins.toString profile.font.size}";
in
{
"konsole/${profileName}.profile".text = lib.generators.toINI { } (
lib.recursiveUpdate {
"General" = (
{
"konsole/${profileName}.profile".text = lib.generators.toINI { }
(lib.recursiveUpdate
{
"General" = (
{
"Name" = profileName;
# Konsole generated profiles seem to always have this
"Parent" = "FALLBACK/";
} //
(lib.optionalAttrs (profile.command != null) { "Command" = profile.command; })
);
"Appearance" = (
{
# If the font size is not set we leave a comma at the end after the name
# We should fix this probs but konsole doesn't seem to care ¯\_(ツ)_/¯
"Font" = fontString.content;
} //
(lib.optionalAttrs (profile.colorScheme != null) { "ColorScheme" = profile.colorScheme; })
);
}
profile.extraConfig
);
"Name" = profileName;
# Konsole generated profiles seem to always have this
"Parent" = "FALLBACK/";
}
)
cfg.profiles
)
)
])
)
)
// (lib.optionalAttrs (profile.command != null) { "Command" = profile.command; })
);
"Appearance" = (
{
# If the font size is not set we leave a comma at the end after the name
# We should fix this probs but konsole doesn't seem to care ¯\_(ツ)_/¯
"Font" = fontString.content;
}
// (lib.optionalAttrs (profile.colorScheme != null) { "ColorScheme" = profile.colorScheme; })
);
} profile.extraConfig
);
}
) cfg.profiles
))
])
))
(createColorSchemes cfg.customColorSchemes)
];
};

View File

@ -1,7 +1,8 @@
{ config
, lib
, pkgs
, ...
{
config,
lib,
pkgs,
...
}:
let

View File

@ -3,10 +3,12 @@
{
imports = [
./apps
./desktop.nix
./files.nix
./fonts.nix
./hotkeys.nix
./input.nix
./krunner.nix
./kscreenlocker.nix
./kwin.nix
./panels.nix

538
modules/desktop.nix Normal file
View File

@ -0,0 +1,538 @@
{ config, lib, ... }:
let
cfg = config.programs.plasma;
widgets = import ./widgets { inherit lib; };
desktopIconSortingModeId = {
manual = -1;
name = 0;
size = 1;
date = 2;
type = 6;
};
mouseActions = {
applicationLauncher = "org.kde.applauncher";
contextMenu = "org.kde.contextmenu";
paste = "org.kde.paste";
switchActivity = "switchactivity";
switchVirtualDesktop = "org.kde.switchdesktop";
switchWindow = "switchwindow";
};
mouseActionNamesEnum = lib.types.enum (builtins.attrNames mouseActions);
# Becomes true if any option under "cfg.desktop.icons" is set to something other than null.
anyDesktopFolderSettingsSet =
let
recurse =
l: lib.any (v: if builtins.isAttrs v then recurse v else v != null) (builtins.attrValues l);
in
recurse cfg.desktop.icons;
# Becomes true if any option under "cfg.desktop.mouseActions" is set to something other than null.
anyDesktopMouseActionsSet = lib.any (v: v != null) (builtins.attrValues cfg.desktop.mouseActions);
in
{
imports = [
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"icons"
"arrangement"
]
[
"programs"
"plasma"
"desktop"
"icons"
"arrangement"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"icons"
"alignment"
]
[
"programs"
"plasma"
"desktop"
"icons"
"alignment"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"icons"
"lockInPlace"
]
[
"programs"
"plasma"
"desktop"
"icons"
"lockInPlace"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"icons"
"sorting"
"mode"
]
[
"programs"
"plasma"
"desktop"
"icons"
"sorting"
"mode"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"icons"
"sorting"
"descending"
]
[
"programs"
"plasma"
"desktop"
"icons"
"sorting"
"descending"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"icons"
"sorting"
"foldersFirst"
]
[
"programs"
"plasma"
"desktop"
"icons"
"sorting"
"foldersFirst"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"icons"
"size"
]
[
"programs"
"plasma"
"desktop"
"icons"
"size"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"icons"
"folderPreviewPopups"
]
[
"programs"
"plasma"
"desktop"
"icons"
"folderPreviewPopups"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"icons"
"previewPlugins"
]
[
"programs"
"plasma"
"desktop"
"icons"
"previewPlugins"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"mouseActions"
"leftClick"
]
[
"programs"
"plasma"
"desktop"
"mouseActions"
"leftClick"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"mouseActions"
"middleClick"
]
[
"programs"
"plasma"
"desktop"
"mouseActions"
"middleClick"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"mouseActions"
"rightClick"
]
[
"programs"
"plasma"
"desktop"
"mouseActions"
"rightClick"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"desktop"
"mouseActions"
"verticalScroll"
]
[
"programs"
"plasma"
"desktop"
"mouseActions"
"verticalScroll"
]
)
];
options.programs.plasma.desktop = {
icons = {
arrangement = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"leftToRight"
"topToBottom"
]);
default = null;
example = "topToBottom";
description = ''
The direction, in which desktop icons are to be arranged.
'';
};
alignment = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"left"
"right"
]);
default = null;
example = "right";
description = ''
Whether to align the icons on the left (the default) or right
side of the screen.
'';
};
lockInPlace = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
description = ''
Locks the position of all desktop icons to the order and placement
defined by `arrangement`, `alignment` and the `sorting` options
so they cant be manually moved.
'';
};
sorting = {
mode = lib.mkOption {
type = with lib.types; nullOr (enum (builtins.attrNames desktopIconSortingModeId));
default = null;
example = "type";
description = ''
Specifies the sort mode for the desktop icons. By default they are
sorted by name.
'';
apply = sortMode: if (sortMode == null) then null else desktopIconSortingModeId.${sortMode};
};
descending = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
description = ''
Reverses the sorting order if enabled. Sorting is ascending by default.
'';
};
foldersFirst = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = false;
description = ''
Folders are sorted separately from files by default. This means
folders appear first, sorted for example ascending by name,
followed by files, also sorted ascending by name.
If this option is disabled, all items are sorted irrespective
of their type.
'';
};
};
size = lib.mkOption {
type = with lib.types; nullOr (ints.between 0 6);
default = null;
example = 2;
description = ''
The desktop icon size, which is normally configured via a slider
with seven possible values ranging from small (0) to large (6).
The fourth position (3) is the default.
'';
};
folderPreviewPopups = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = false;
description = ''
Enables the arrow button when hovering over a folder on the desktop
which shows a preview popup of the folders contents.
Is enabled by default.
'';
};
previewPlugins = lib.mkOption {
type = with lib.types; nullOr (listOf str);
default = null;
example = [
"audiothumbnail"
"fontthumbnail"
];
description = ''
Configures the preview plugins used to preview desktop files and folders.
'';
};
};
mouseActions = {
leftClick = lib.mkOption {
type = lib.types.nullOr mouseActionNamesEnum;
default = null;
example = "appLauncher";
description = "Action for a left click on the desktop.";
apply = value: if (value == null) then null else mouseActions.${value};
};
middleClick = lib.mkOption {
type = lib.types.nullOr mouseActionNamesEnum;
default = null;
example = "switchWindow";
description = "Action for a click on the desktop with the middle mouse button.";
apply = value: if (value == null) then null else mouseActions.${value};
};
rightClick = lib.mkOption {
type = lib.types.nullOr mouseActionNamesEnum;
default = null;
example = "contextMenu";
description = "Action for a right click on the desktop.";
apply = value: if (value == null) then null else mouseActions.${value};
};
verticalScroll = lib.mkOption {
type = lib.types.nullOr mouseActionNamesEnum;
default = null;
example = "switchVirtualDesktop";
description = "Action for scrolling (vertically) while hovering over the desktop.";
apply = value: if (value == null) then null else mouseActions.${value};
};
};
widgets = lib.mkOption {
type = with lib.types; nullOr (listOf widgets.desktopType);
default = null;
example = [
{
name = "org.kde.plasma.digitalclock";
position = {
horizontal = 51;
vertical = 100;
};
size = {
width = 250;
height = 250;
};
config.Appearance.showDate = false;
}
{
plasmusicToolbar = {
position = {
horizontal = 51;
vertical = 300;
};
size = {
width = 250;
height = 400;
};
background = "transparentShadow";
};
}
];
description = ''
A list of widgets to be added to the desktop.
'';
apply = option: if option == null then null else (map widgets.desktopConvert option);
};
};
config = (
lib.mkIf cfg.enable {
programs.plasma.startup = {
desktopScript."set_desktop_folder_settings" = (
lib.mkIf anyDesktopFolderSettingsSet {
text = ''
// Desktop folder settings
let allDesktops = desktops();
for (const desktop of allDesktops) {
desktop.currentConfigGroup = ["General"];
${
lib.optionalString (
cfg.desktop.icons.arrangement == "topToBottom"
) ''desktop.writeConfig("arrangement", 1);''
}
${
lib.optionalString (cfg.desktop.icons.alignment == "right") ''desktop.writeConfig("alignment", 1);''
}
${
lib.optionalString (cfg.desktop.icons.lockInPlace == true) ''desktop.writeConfig("locked", true);''
}
${widgets.lib.stringIfNotNull cfg.desktop.icons.size ''desktop.writeConfig("iconSize", ${builtins.toString cfg.desktop.icons.size});''}
${
lib.optionalString (
cfg.desktop.icons.folderPreviewPopups == false
) ''desktop.writeConfig("popups", false);''
}
${widgets.lib.stringIfNotNull cfg.desktop.icons.previewPlugins ''desktop.writeConfig("previewPlugins", "${lib.strings.concatStringsSep "," cfg.desktop.icons.previewPlugins}");''}
${widgets.lib.stringIfNotNull cfg.desktop.icons.sorting.mode ''desktop.writeConfig("sortMode", ${builtins.toString cfg.desktop.icons.sorting.mode});''}
${
lib.optionalString (
cfg.desktop.icons.sorting.descending == true
) ''desktop.writeConfig("sortDesc", true);''
}
${
lib.optionalString (
cfg.desktop.icons.sorting.foldersFirst == false
) ''desktop.writeConfig("sortDirsFirst", false);''
}
}
'';
priority = 3;
}
);
desktopScript."set_desktop_mouse_actions" = (
lib.mkIf anyDesktopMouseActionsSet {
text = ''
// Mouse actions
let configFile = ConfigFile('plasma-org.kde.plasma.desktop-appletsrc');
configFile.group = 'ActionPlugins';
// References the section [ActionPlugins][0].
let actionPluginSubSection = ConfigFile(configFile, 0)
${widgets.lib.stringIfNotNull cfg.desktop.mouseActions.leftClick ''actionPluginSubSection.writeEntry("LeftButton;NoModifier", "${cfg.desktop.mouseActions.leftClick}");''}
${widgets.lib.stringIfNotNull cfg.desktop.mouseActions.middleClick ''actionPluginSubSection.writeEntry("MiddleButton;NoModifier", "${cfg.desktop.mouseActions.middleClick}");''}
${widgets.lib.stringIfNotNull cfg.desktop.mouseActions.rightClick ''actionPluginSubSection.writeEntry("RightButton;NoModifier", "${cfg.desktop.mouseActions.rightClick}");''}
${widgets.lib.stringIfNotNull cfg.desktop.mouseActions.verticalScroll ''actionPluginSubSection.writeEntry("wheel:Vertical;NoModifier", "${cfg.desktop.mouseActions.verticalScroll}");''}
'';
priority = 3;
restartServices = [ "plasma-plasmashell" ];
}
);
desktopScript."set_desktop_widgets" = (
lib.mkIf (cfg.desktop.widgets != null) {
text = ''
// Desktop widgets
let allDesktops = desktops();
// Remove all desktop widgets
allDesktops.forEach((desktop) => {
desktop.widgets().forEach((widget) => {
widget.remove();
});
});
for (let i = 0; i < allDesktops.length; i++) {
const desktop = allDesktops[i];
${widgets.lib.addDesktopWidgetStmts "desktop" "desktopWidgets" cfg.desktop.widgets}
}
'';
priority = 2;
}
);
};
}
);
}

View File

@ -1,65 +1,93 @@
# Low-level access to changing Plasma settings.
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
inherit (import ../lib/writeconfig.nix { inherit lib pkgs config; }) writeConfig;
inherit (import ../lib/types.nix { inherit lib; inherit config; }) coercedSettingsType;
inherit
(import ../lib/types.nix {
inherit lib;
inherit config;
})
coercedSettingsType
;
# Helper function to prepend the appropriate path prefix (e.g. XDG_CONFIG_HOME) to file
prependPath = prefix: attrset:
lib.mapAttrs'
(path: config: { name = "${prefix}/${path}"; value = config; })
attrset;
prependPath =
prefix: attrset:
lib.mapAttrs' (path: config: {
name = "${prefix}/${path}";
value = config;
}) attrset;
plasmaCfg = config.programs.plasma;
cfg =
(prependPath config.home.homeDirectory plasmaCfg.file) //
(prependPath config.xdg.configHome plasmaCfg.configFile) //
(prependPath config.xdg.dataHome plasmaCfg.dataFile);
(prependPath config.home.homeDirectory plasmaCfg.file)
// (prependPath config.xdg.configHome plasmaCfg.configFile)
// (prependPath config.xdg.dataHome plasmaCfg.dataFile);
fileSettingsType = with lib.types; attrsOf (attrsOf (attrsOf coercedSettingsType));
##############################################################################
# Generate a script that will use write_config.py to update all
# settings.
resetFilesList = (map (f: "${config.xdg.configHome}/${f}") (lib.lists.subtractLists plasmaCfg.resetFilesExclude plasmaCfg.resetFiles));
resetFilesList = (
map (f: "${config.xdg.configHome}/${f}") (
lib.lists.subtractLists plasmaCfg.resetFilesExclude plasmaCfg.resetFiles
)
);
script = pkgs.writeScript "plasma-config" (writeConfig cfg plasmaCfg.overrideConfig resetFilesList);
##############################################################################
# Generate a script that will remove all the current config files.
defaultResetFiles = (if plasmaCfg.overrideConfig then [
"baloofilerc"
"dolphinrc"
"ffmpegthumbsrc"
"kactivitymanagerdrc"
"katerc"
"kcminputrc"
"kded5rc"
"kded6rc"
"kdeglobals"
"kgammarc"
"kglobalshortcutsrc"
"khotkeysrc"
"kiorc"
"klaunchrc"
"klipperrc"
"kmixrc"
"krunnerrc"
"kscreenlockerrc"
"kservicemenurc"
"ksmserverrc"
"ksplashrc"
"kwalletrc"
"kwin_rules_dialogrc"
"kwinrc"
"kwinrulesrc"
"kxkbrc"
"plasma-localerc"
"plasmanotifyrc"
"plasmarc"
"plasmashellrc"
"powerdevilrc"
"systemsettingsrc"
] else lib.optional (builtins.length plasmaCfg.window-rules > 0) "kwinrulesrc");
defaultResetFiles = (
if plasmaCfg.overrideConfig then
[
"auroraerc"
"baloofilerc"
"dolphinrc"
"ffmpegthumbsrc"
"kactivitymanagerdrc"
"katerc"
"kcminputrc"
"KDE/Sonnet.conf"
"kde.org/ghostwriter.conf"
"kded5rc"
"kded6rc"
"kdeglobals"
"kgammarc"
"kglobalshortcutsrc"
"khotkeysrc"
"kiorc"
"klaunchrc"
"klipperrc"
"kmixrc"
"krunnerrc"
"kscreenlockerrc"
"kservicemenurc"
"ksmserverrc"
"ksplashrc"
"kwalletrc"
"kwin_rules_dialogrc"
"kwinrc"
"kwinrulesrc"
"kxkbrc"
"plasma_calendar_alternatecalendar"
"plasma_calendar_astronomicalevents"
"plasma_calendar_holiday_regions"
"plasma-localerc"
"plasmanotifyrc"
"plasmarc"
"plasmashellrc"
"powerdevilrc"
"systemsettingsrc"
]
else
lib.optional (builtins.length plasmaCfg.window-rules > 0) "kwinrulesrc"
);
in
{
options.programs.plasma = {
@ -119,16 +147,49 @@ in
};
imports = [
(lib.mkRenamedOptionModule [ "programs" "plasma" "files" ] [ "programs" "plasma" "configFile" ])
(lib.mkRenamedOptionModule [ "programs" "plasma" "overrideConfigFiles" ] [ "programs" "plasma" "resetFiles" ])
(lib.mkRenamedOptionModule [ "programs" "plasma" "overrideConfigExclude" ] [ "programs" "plasma" "resetFilesExclude" ])
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"files"
]
[
"programs"
"plasma"
"configFile"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"overrideConfigFiles"
]
[
"programs"
"plasma"
"resetFiles"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"overrideConfigExclude"
]
[
"programs"
"plasma"
"resetFilesExclude"
]
)
];
config.home.activation = lib.mkIf plasmaCfg.enable {
configure-plasma = (lib.hm.dag.entryAfter [ "writeBoundary" ]
''
configure-plasma = (
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
$DRY_RUN_CMD ${script}
'');
''
);
};
}

View File

@ -1,7 +1,4 @@
{ lib
, config
, ...
}:
{ lib, config, ... }:
let
inherit (lib) mkIf mkOption types;
qfont = import ../lib/qfont.nix { inherit lib; };

View File

@ -1,5 +1,10 @@
# Global hotkeys (user-defined keyboard shortcuts):
{ pkgs, config, lib, ... }:
{
pkgs,
config,
lib,
...
}:
let
cfg = config.programs.plasma;
@ -11,60 +16,62 @@ let
description = "Plasma Manager";
};
commandType = { name, ... }: {
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
description = "Command hotkey name.";
};
commandType =
{ name, ... }:
{
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
description = "Command hotkey name.";
};
comment = lib.mkOption {
type = lib.types.str;
default = name;
description = "Optional comment to display in the KDE settings UI.";
};
comment = lib.mkOption {
type = lib.types.str;
default = name;
description = "Optional comment to display in the KDE settings UI.";
};
key = lib.mkOption {
type = lib.types.str;
description = "The key combination that triggers the action.";
default = "";
};
key = lib.mkOption {
type = lib.types.str;
description = "The key combination that triggers the action.";
default = "";
};
keys = lib.mkOption {
type = with lib.types; listOf str;
description = "The key combinations that trigger the action.";
default = [ ];
};
keys = lib.mkOption {
type = with lib.types; listOf str;
description = "The key combinations that trigger the action.";
default = [ ];
};
command = lib.mkOption {
type = lib.types.str;
description = "The command to execute.";
};
command = lib.mkOption {
type = lib.types.str;
description = "The command to execute.";
};
logs.enabled = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Connect command's stdin and stdout to systemd journal with systemd-cat.";
};
logs.enabled = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Connect command's stdin and stdout to systemd journal with systemd-cat.";
};
logs.identifier = lib.mkOption {
type = lib.types.str;
default = lib.trivial.pipe name [
lib.strings.toLower
(builtins.replaceStrings [ " " ] [ "-" ])
(n: "${group.name}-${n}")
];
description = "Identifier passed down to systemd-cat.";
};
logs.identifier = lib.mkOption {
type = lib.types.str;
default = lib.trivial.pipe name [
lib.strings.toLower
(builtins.replaceStrings [ " " ] [ "-" ])
(n: "${group.name}-${n}")
];
description = "Identifier passed down to systemd-cat.";
};
logs.extraArgs = lib.mkOption {
type = lib.types.str;
default = "";
description = "Additional arguments provided to systemd-cat.";
logs.extraArgs = lib.mkOption {
type = lib.types.str;
default = "";
description = "Additional arguments provided to systemd-cat.";
};
};
};
};
in
{
options.programs.plasma.hotkeys = {
@ -75,36 +82,35 @@ in
};
};
config = lib.mkIf
(cfg.enable && builtins.length (builtins.attrNames cfg.hotkeys.commands) != 0)
{
xdg.desktopEntries."${group.name}" = {
name = group.description;
noDisplay = true;
type = "Application";
actions = lib.mapAttrs
(_: command: {
name = command.name;
exec =
if command.logs.enabled then
"${pkgs.systemd}/bin/systemd-cat --identifier=${command.logs.identifier} ${command.logs.extraArgs} ${commandString command.command}"
else (commandString command.command);
})
cfg.hotkeys.commands;
};
programs.plasma.configFile."kglobalshortcutsrc"."${group.desktop}" = {
_k_friendly_name = group.description;
} // lib.mapAttrs
(_: command:
let
keys = command.keys ++ lib.optionals (command.key != "") [ command.key ];
in
lib.concatStringsSep "," [
(lib.concatStringsSep "\t" (map (lib.escape [ "," ]) keys))
"" # List of default keys, not needed.
command.comment
])
cfg.hotkeys.commands;
config = lib.mkIf (cfg.enable && builtins.length (builtins.attrNames cfg.hotkeys.commands) != 0) {
xdg.desktopEntries."${group.name}" = {
name = group.description;
noDisplay = true;
type = "Application";
actions = lib.mapAttrs (_: command: {
name = command.name;
exec =
if command.logs.enabled then
"${pkgs.systemd}/bin/systemd-cat --identifier=${command.logs.identifier} ${command.logs.extraArgs} ${commandString command.command}"
else
(commandString command.command);
}) cfg.hotkeys.commands;
};
programs.plasma.configFile."kglobalshortcutsrc"."${group.desktop}" =
{
_k_friendly_name = group.description;
}
// lib.mapAttrs (
_: command:
let
keys = command.keys ++ lib.optionals (command.key != "") [ command.key ];
in
lib.concatStringsSep "," [
(lib.concatStringsSep "\t" (map (lib.escape [ "," ]) keys))
"" # List of default keys, not needed.
command.comment
]
) cfg.hotkeys.commands;
};
}

View File

@ -4,7 +4,17 @@ with lib;
let
cfg = config.programs.plasma;
numlockSettings = [ "on" "off" "unchanged" ];
numlockSettings = [
"on"
"off"
"unchanged"
];
switchModes = [
"global"
"desktop"
"winClass"
"window"
];
scrollMethods = {
twoFingers = 1;
@ -15,6 +25,44 @@ let
twoFingers = 2;
};
capitalizeWord =
word:
let
firstLetter = builtins.substring 0 1 word;
rest = builtins.substring 1 (builtins.stringLength word - 1) word;
in
"${toUpper firstLetter}${rest}";
layoutType = types.submodule {
options = {
layout = mkOption {
type = types.str;
example = "us";
description = ''
Keyboard layout.
'';
};
variant = mkOption {
type = with types; nullOr str;
default = null;
example = "eng";
description = ''
Keyboard layout variant.
'';
apply = builtins.toString;
};
displayName = mkOption {
type = with types; nullOr str;
default = null;
example = "us";
description = ''
Keyboard layout display name.
'';
apply = builtins.toString;
};
};
};
touchPadType = types.submodule {
options = {
enable = mkOption {
@ -142,7 +190,12 @@ let
apply = method: if (method == null) then null else rightClickMethods."${method}";
};
twoFingerTap = mkOption {
type = with types; nullOr (enum [ "rightClick" "middleClick" ]);
type =
with types;
nullOr (enum [
"rightClick"
"middleClick"
]);
default = null;
example = "twoFingers";
description = ''
@ -152,14 +205,15 @@ let
};
};
};
touchPadToConfig = touchpad:
touchPadToConfig =
touchpad:
let
touchName = touchpad.name;
touchVendor = touchpad.vendorId;
touchProduct = touchpad.productId;
touchVendor = builtins.toString (lib.fromHexString touchpad.vendorId);
touchProduct = builtins.toString (lib.fromHexString touchpad.productId);
in
{
"Libinput/${touchVendor}/${touchProduct}/${lib.escape ["/"] touchName}" = {
"Libinput/${touchVendor}/${touchProduct}/${lib.escape [ "/" ] touchName}" = {
Enabled = touchpad.enable;
DisableWhileTyping = touchpad.disableWhileTyping;
LeftHanded = touchpad.leftHanded;
@ -244,11 +298,23 @@ let
'';
};
accelerationProfile = mkOption {
type = with types; nullOr (enum [ "none" "default" ]);
type =
with types;
nullOr (enum [
"none"
"default"
]);
default = null;
example = "none";
description = "Mouse acceleration profile.";
apply = profile: if profile == "none" then 1 else if profile == "default" then 2 else null;
apply =
profile:
if profile == "none" then
1
else if profile == "default" then
2
else
null;
};
naturalScroll = mkOption {
type = with types; nullOr bool;
@ -269,11 +335,12 @@ let
};
};
mouseToConfig = mouse:
mouseToConfig =
mouse:
let
mouseName = mouse.name;
mouseVendor = mouse.vendorId;
mouseProduct = mouse.productId;
mouseVendor = builtins.toString (lib.fromHexString mouse.vendorId);
mouseProduct = builtins.toString (lib.fromHexString mouse.productId);
in
{
"Libinput/${mouseVendor}/${mouseProduct}/${mouseName}" = {
@ -288,12 +355,80 @@ let
};
in
{
config.assertions = [
(
let
validChars = [
"0"
"1"
"2"
"3"
"4"
"5"
"6"
"7"
"8"
"9"
"a"
"b"
"c"
"d"
"e"
"f"
];
hexChars = hexStr: builtins.tail (lib.reverseList (builtins.tail (lib.splitString "" hexStr)));
hexCodeInvalid =
hex:
!(lib.all (c: builtins.elem (lib.toLower c) validChars) (hexChars hex))
&& (builtins.stringLength hex) > 0;
allHexCodes = lib.flatten (
(map (t: [
t.vendorId
t.productId
]) (cfg.input.touchpads ++ cfg.input.mice))
);
invalidHexCodes = builtins.filter hexCodeInvalid allHexCodes;
in
{
assertion = (builtins.length invalidHexCodes) == 0;
message = "Invalid hex-code for product or vendor-ID in the input module in plasma-manager: ${builtins.head invalidHexCodes}";
}
)
];
# Keyboard options
options.programs.plasma.input.keyboard = {
layouts = mkOption {
type = with types; nullOr (listOf str);
model = mkOption {
type = with types; nullOr str;
default = null;
example = [ "es" "us" ];
example = "pc104";
description = ''
Keyboard model.
'';
};
switchingPolicy = mkOption {
type = with types; nullOr (enum switchModes);
default = null;
example = "global";
description = ''
Switching policy for keyboard layouts.
'';
apply = policy: if policy == null then null else capitalizeWord policy;
};
layouts = mkOption {
type = with types; nullOr (listOf layoutType);
default = null;
example = [
{ layout = "us"; }
{
layout = "ca";
variant = "eng";
}
{
layout = "us";
variant = "intl";
displayName = "usi";
}
];
description = ''
Keyboard layouts to use.
'';
@ -316,27 +451,53 @@ in
'';
};
repeatRate = mkOption {
type = with types; nullOr (numbers.between 0.20 100.0);
type = with types; nullOr (numbers.between 0.2 100.0);
default = null;
example = 50.0;
description = ''
How quick the inputs should be repeated when holding down a key.
'';
};
options = mkOption {
type = with types; nullOr (listOf str);
default = null;
example = [
"altwin:meta_alt"
"caps:shift"
"custom:types"
];
description = ''
Keyboard options.
'';
};
};
config.programs.plasma.configFile."kxkbrc" = mkIf (cfg.enable) (
mkMerge [
(
mkIf (cfg.input.keyboard.layouts != null) {
Layout = {
Use.value = true;
LayoutList.value = strings.concatStringsSep "," cfg.input.keyboard.layouts;
};
}
)
]
);
config.programs.plasma.configFile."kxkbrc" = mkIf (cfg.enable) (mkMerge [
(mkIf (cfg.input.keyboard.layouts != null) {
Layout = {
Use.value = true;
LayoutList.value = strings.concatStringsSep "," (map (l: l.layout) cfg.input.keyboard.layouts);
VariantList.value = strings.concatStringsSep "," (map (l: l.variant) cfg.input.keyboard.layouts);
DisplayNames.value = strings.concatStringsSep "," (map (l: l.displayName) cfg.input.keyboard.layouts);
};
})
(mkIf (cfg.input.keyboard.options != null) {
Layout = {
ResetOldOptions.value = true;
Options.value = strings.concatStringsSep "," cfg.input.keyboard.options;
};
})
(mkIf (cfg.input.keyboard.model != null) {
Layout = {
Model.value = cfg.input.keyboard.model;
};
})
(mkIf (cfg.input.keyboard.switchingPolicy != null) {
Layout = {
SwitchMode.value = cfg.input.keyboard.switchingPolicy;
};
})
]);
# Touchpads options
options.programs.plasma.input.touchpads = mkOption {
@ -385,14 +546,15 @@ in
config.programs.plasma.configFile."kcminputrc" = mkIf (cfg.enable) (mkMerge [
{
Keyboard = (lib.filterAttrs (k: v: v != null) {
NumLock = (lists.findFirstIndex (x: x == cfg.input.keyboard.numlockOnStartup) null numlockSettings);
RepeatDelay = cfg.input.keyboard.repeatDelay;
RepeatRate = cfg.input.keyboard.repeatRate;
});
Keyboard = (
lib.filterAttrs (k: v: v != null) {
NumLock = (lists.findFirstIndex (x: x == cfg.input.keyboard.numlockOnStartup) null numlockSettings);
RepeatDelay = cfg.input.keyboard.repeatDelay;
RepeatRate = cfg.input.keyboard.repeatRate;
}
);
}
(mkMerge (map touchPadToConfig cfg.input.touchpads))
(mkMerge (map mouseToConfig cfg.input.mice))
]
);
]);
}

58
modules/krunner.nix Normal file
View File

@ -0,0 +1,58 @@
{ config, lib, ... }:
let
cfg = config.programs.plasma;
in
{
options.programs.plasma.krunner = {
position = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"top"
"center"
]);
default = null;
example = "center";
description = "Position of KRunner on screen.";
};
activateWhenTypingOnDesktop = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
description = "Activate KRunner when typing on the desktop.";
};
historyBehavior = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"disabled"
"enableSuggestions"
"enableAutoComplete"
]);
default = null;
example = "disabled";
description = "Behavior of KRunners history.";
};
};
config.programs.plasma.configFile."krunnerrc" = (
lib.mkMerge [
(lib.mkIf (cfg.krunner.position != null) {
General.FreeFloating = cfg.krunner.position == "center";
})
(lib.mkIf (cfg.krunner.activateWhenTypingOnDesktop != null) {
General.ActivateWhenTypingOnDesktop = cfg.krunner.activateWhenTypingOnDesktop;
})
(lib.mkIf (cfg.krunner.historyBehavior != null) {
General.historyBehavior = (
if cfg.krunner.historyBehavior == "enableSuggestions" then
"CompletionSuggestion"
else if cfg.krunner.historyBehavior == "enableAutoComplete" then
"ImmediateCompletion"
else
"Disabled"
);
})
]
);
}

View File

@ -1,83 +1,271 @@
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
cfg = config.programs.plasma;
inherit (import ../lib/wallpapers.nix { inherit lib; }) wallpaperPictureOfTheDayType wallpaperSlideShowType;
inherit (import ../lib/wallpapers.nix { inherit lib; })
wallpaperPictureOfTheDayType
wallpaperSlideShowType
;
in
{
options.programs.plasma.kscreenlocker = {
wallpaper = lib.mkOption {
type = with lib.types; nullOr path;
autoLock = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = lib.literalExpression ''"''${pkgs.kdePackages.plasma-workspace-wallpapers}/share/wallpapers/Kay/contents/images/1080x1920.png"'';
example = true;
description = ''
The wallpaper for the lockscreen. Can be either be the path to an image file or a kpackage.
Sets whether the screen will be locked after the specified time.
'';
};
wallpaperPictureOfTheDay = lib.mkOption {
type = lib.types.nullOr wallpaperPictureOfTheDayType;
lockOnResume = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = { provider = "apod"; };
example = false;
description = ''
Allows you to set wallpaper using the picture of the day plugin. Needs the provider.
Whether to lock the screen when the system resumes from sleep.
'';
};
wallpaperSlideShow = lib.mkOption {
type = lib.types.nullOr wallpaperSlideShowType;
timeout = lib.mkOption {
type = with lib.types; nullOr ints.unsigned;
default = null;
example = lib.literalExpression ''{ path = "''${pkgs.kdePackages.plasma-workspace-wallpapers}/share/wallpapers/"; }'';
example = 5;
description = ''
Allows you to set wallpaper using the slideshow plugin. Needs the path
to at least one directory.
Sets the minutes after which the screen is locked.
'';
};
wallpaperPlainColor = lib.mkOption {
type = lib.types.nullOr lib.types.str;
passwordRequired = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = "0,64,174,256";
example = true;
description = ''
Allows you to set wallpaper using a plain color. Color is a comma-seperated R,G,B,A string. Alpha optional (default is 256).
Whether the password is required to unlock the screen.
'';
};
passwordRequiredDelay = lib.mkOption {
type = with lib.types; nullOr ints.unsigned;
default = null;
example = 5;
description = ''
The time it takes in seconds for the password to be required after the screen is locked.
'';
};
lockOnStartup = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = false;
description = ''
Whether to lock the screen on startup.
NOTE: This option is not provided in the system settings.
'';
};
appearance = {
alwaysShowClock = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = false;
description = ''
Whether to always show the clock on the lockscreen, even if the unlock dialog is not shown.
'';
};
showMediaControls = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = false;
description = ''
Whether to show media controls on the lockscreen.
'';
};
wallpaper = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
example = lib.literalExpression ''"''${pkgs.kdePackages.plasma-workspace-wallpapers}/share/wallpapers/Kay/contents/images/1080x1920.png"'';
description = ''
The wallpaper for the lockscreen. Can be either be the path to an image file or a kpackage.
'';
};
wallpaperPictureOfTheDay = lib.mkOption {
type = lib.types.nullOr wallpaperPictureOfTheDayType;
default = null;
example = {
provider = "apod";
};
description = ''
Allows you to set wallpaper using the picture of the day plugin. Needs the provider.
'';
};
wallpaperSlideShow = lib.mkOption {
type = lib.types.nullOr wallpaperSlideShowType;
default = null;
example = lib.literalExpression ''{ path = "''${pkgs.kdePackages.plasma-workspace-wallpapers}/share/wallpapers/"; }'';
description = ''
Allows you to set wallpaper using the slideshow plugin. Needs the path
to at least one directory.
'';
};
wallpaperPlainColor = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "0,64,174,256";
description = ''
Allows you to set wallpaper using a plain color. Color is a comma-seperated R,G,B,A string. Alpha optional (default is 256).
'';
};
};
};
imports = [
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"kscreenlocker"
"wallpaper"
]
[
"programs"
"plasma"
"kscreenlocker"
"appearance"
"wallpaper"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"kscreenlocker"
"wallpaperPictureOfTheDay"
]
[
"programs"
"plasma"
"kscreenlocker"
"appearance"
"wallpaperPictureOfTheDay"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"kscreenlocker"
"wallpaperSlideShow"
]
[
"programs"
"plasma"
"kscreenlocker"
"appearance"
"wallpaperSlideShow"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"kscreenlocker"
"wallpaperPlainColor"
]
[
"programs"
"plasma"
"kscreenlocker"
"appearance"
"wallpaperPlainColor"
]
)
];
config = {
assertions = [
{
assertion =
let
wallpapers = with cfg.workspace; [ wallpaperSlideShow wallpaper wallpaperPictureOfTheDay wallpaperPlainColor ];
wallpapers = with cfg.kscreenlocker.appearance; [
wallpaperSlideShow
wallpaper
wallpaperPictureOfTheDay
wallpaperPlainColor
];
in
lib.count (x: x != null) wallpapers <= 1;
message = "Can set only one of wallpaper, wallpaperSlideShow, wallpaperPictureOfTheDay, and wallpaperPlainColor for kscreenlocker.";
}
];
programs.plasma.configFile.kscreenlockerrc = (lib.mkMerge [
(lib.mkIf (cfg.kscreenlocker.wallpaper != null) {
Greeter.WallpaperPlugin = "org.kde.image";
"Greeter/Wallpaper/org.kde.image/General".Image = (builtins.toString cfg.kscreenlocker.wallpaper);
})
(lib.mkIf (cfg.kscreenlocker.wallpaperPictureOfTheDay != null) {
Greeter.WallpaperPlugin = "org.kde.potd";
"Greeter/Wallpaper/org.kde.potd/General" = {
Provider = cfg.kscreenlocker.wallpaperPictureOfTheDay.provider;
UpdateOverMeteredConnection = with cfg.kscreenlocker.wallpaperPictureOfTheDay;
(lib.mkIf (updateOverMeteredConnection != null) (if updateOverMeteredConnection then 1 else 0));
};
})
(lib.mkIf (cfg.kscreenlocker.wallpaperSlideShow != null) {
Greeter.WallpaperPlugin = "org.kde.slideshow";
"Greeter/Wallpaper/org.kde.slideshow/General" = {
SlidePaths = with cfg.kscreenlocker.wallpaperSlideShow;
(if ((builtins.isPath path) || (builtins.isString path)) then
(builtins.toString cfg.kscreenlocker.wallpaperSlideShow.path) else
(builtins.concatStringsSep "," cfg.kscreenlocker.wallpaperSlideShow.path));
SlideInterval = cfg.kscreenlocker.wallpaperSlideShow.interval;
};
})
(lib.mkIf (cfg.kscreenlocker.wallpaperPlainColor != null) {
Greeter.WallpaperPlugin = "org.kde.color";
"Greeter/Wallpaper/org.kde.color/General".Color = cfg.kscreenlocker.wallpaperPlainColor;
})
]);
programs.plasma.configFile.kscreenlockerrc = (
lib.mkMerge [
(lib.mkIf (cfg.kscreenlocker.appearance.wallpaper != null) {
Greeter.WallpaperPlugin = "org.kde.image";
"Greeter/Wallpaper/org.kde.image/General".Image = (
builtins.toString cfg.kscreenlocker.appearance.wallpaper
);
})
(lib.mkIf (cfg.kscreenlocker.appearance.wallpaperPictureOfTheDay != null) {
Greeter.WallpaperPlugin = "org.kde.potd";
"Greeter/Wallpaper/org.kde.potd/General" = {
Provider = cfg.kscreenlocker.appearance.wallpaperPictureOfTheDay.provider;
UpdateOverMeteredConnection =
with cfg.kscreenlocker.appearance.wallpaperPictureOfTheDay;
(lib.mkIf (updateOverMeteredConnection != null) (if updateOverMeteredConnection then 1 else 0));
};
})
(lib.mkIf (cfg.kscreenlocker.appearance.wallpaperSlideShow != null) {
Greeter.WallpaperPlugin = "org.kde.slideshow";
"Greeter/Wallpaper/org.kde.slideshow/General" = {
SlidePaths =
with cfg.kscreenlocker.appearance.wallpaperSlideShow;
(
if ((builtins.isPath path) || (builtins.isString path)) then
(builtins.toString cfg.kscreenlocker.appearance.wallpaperSlideShow.path)
else
(builtins.concatStringsSep "," cfg.kscreenlocker.appearance.wallpaperSlideShow.path)
);
SlideInterval = cfg.kscreenlocker.appearance.wallpaperSlideShow.interval;
};
})
(lib.mkIf (cfg.kscreenlocker.appearance.wallpaperPlainColor != null) {
Greeter.WallpaperPlugin = "org.kde.color";
"Greeter/Wallpaper/org.kde.color/General".Color = cfg.kscreenlocker.appearance.wallpaperPlainColor;
})
(lib.mkIf (cfg.kscreenlocker.appearance.alwaysShowClock != null) {
"Greeter/LnF/General".alwaysShowClock = cfg.kscreenlocker.appearance.alwaysShowClock;
})
(lib.mkIf (cfg.kscreenlocker.appearance.showMediaControls != null) {
"Greeter/LnF/General".showMediaControls = cfg.kscreenlocker.appearance.showMediaControls;
})
(lib.mkIf (cfg.kscreenlocker.autoLock != null) { Daemon.Autolock = cfg.kscreenlocker.autoLock; })
(lib.mkIf (cfg.kscreenlocker.lockOnResume != null) {
Daemon.LockOnResume = cfg.kscreenlocker.lockOnResume;
})
(lib.mkIf (cfg.kscreenlocker.timeout != null) { Daemon.Timeout = cfg.kscreenlocker.timeout; })
(lib.mkIf (cfg.kscreenlocker.passwordRequiredDelay != null) {
Daemon.LockGrace = cfg.kscreenlocker.passwordRequiredDelay;
})
(lib.mkIf (cfg.kscreenlocker.passwordRequired != null) {
Daemon.RequirePassword = cfg.kscreenlocker.passwordRequired;
})
(lib.mkIf (cfg.kscreenlocker.lockOnStartup != null) {
Daemon.LockOnStart = cfg.kscreenlocker.lockOnStartup;
})
]
);
};
}

View File

@ -1,4 +1,9 @@
{ config, lib, ... }:
{
config,
lib,
pkgs,
...
}:
with lib;
@ -32,31 +37,28 @@ let
};
# Gets a list with long names and turns it into short names
getShortNames = wantedButtons:
lists.forEach
(
lists.flatten (
lists.forEach wantedButtons (currentButton:
lists.remove null (
lists.imap0
(index: value:
if value == currentButton then "${toString index}" else null
)
validTitlebarButtons.longNames
)
)
getShortNames =
wantedButtons:
lists.forEach (lists.flatten (
lists.forEach wantedButtons (
currentButton:
lists.remove null (
lists.imap0 (
index: value: if value == currentButton then "${toString index}" else null
) validTitlebarButtons.longNames
)
)
getShortNameFromIndex;
)) getShortNameFromIndex;
# Gets the index and returns the short name in that position
getShortNameFromIndex = position: builtins.elemAt validTitlebarButtons.shortNames (strings.toInt position);
getShortNameFromIndex =
position: builtins.elemAt validTitlebarButtons.shortNames (strings.toInt position);
virtualDesktopNameAttrs = names:
builtins.listToAttrs
(imap1 (i: v: (nameValuePair "Name_${builtins.toString i}" v)) names);
virtualDesktopNameAttrs =
names: builtins.listToAttrs (imap1 (i: v: (nameValuePair "Name_${builtins.toString i}" v)) names);
capitalizeWord = word:
capitalizeWord =
word:
let
firstLetter = builtins.substring 0 1 word;
rest = builtins.substring 1 (builtins.stringLength word - 1) word;
@ -64,19 +66,77 @@ let
"${toUpper firstLetter}${rest}";
removeColon = string: builtins.replaceStrings [ ":" ] [ "" ] string;
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex (
x: x == value
) (throw "getIndexFromEnum (kwin): Value ${value} isn't present in the enum. This is a bug") enum;
convertPoloniumFilter = list: if list == null then null else builtins.concatStringsSep ", " list;
tilingLayoutType = types.submodule {
options = {
id = mkOption {
type = types.str;
description = "The id of the layout.";
example = "cf5c25c2-4217-4193-add6-b5971cb543f2";
};
tiles = mkOption {
type = with types; attrsOf anything;
example = {
layoutDirection = "horizontal";
tiles = [
{ width = 0.5; }
{
layoutDirection = "vertical";
tiles = [
{ height = 0.5; }
{ height = 0.5; }
];
width = 0.5;
}
];
};
apply = builtins.toJSON;
};
};
};
in
{
imports = [
(lib.mkRenamedOptionModule
[ "programs" "plasma" "kwin" "virtualDesktops" "animation" ]
[ "programs" "plasma" "kwin" "effects" "desktopSwitching" "animation" ])
[
"programs"
"plasma"
"kwin"
"virtualDesktops"
"animation"
]
[
"programs"
"plasma"
"kwin"
"effects"
"desktopSwitching"
"animation"
]
)
];
options.programs.plasma.kwin = {
titlebarButtons.right = mkOption {
type = with types; nullOr (listOf (enum validTitlebarButtons.longNames));
default = null;
example = [ "help" "minimize" "maximize" "close" ];
example = [
"help"
"minimize"
"maximize"
"close"
];
description = ''
Title bar buttons to be placed on the right.
'';
@ -84,7 +144,10 @@ in
titlebarButtons.left = mkOption {
type = with types; nullOr (listOf (enum validTitlebarButtons.longNames));
default = null;
example = [ "on-all-desktops" "keep-above-windows" ];
example = [
"on-all-desktops"
"keep-above-windows"
];
description = ''
Title bar buttons to be placed on the left.
'';
@ -103,7 +166,13 @@ in
};
minimization = {
animation = mkOption {
type = with types; nullOr (enum [ "squash" "magiclamp" ]);
type =
with types;
nullOr (enum [
"squash"
"magiclamp"
"off"
]);
default = null;
example = "magiclamp";
description = "The effect when windows are minimized.";
@ -134,14 +203,27 @@ in
description = "Arrange desktops in a virtual cube.";
};
desktopSwitching.animation = mkOption {
type = with types; nullOr (enum [ "fade" "slide" ]);
type =
with types;
nullOr (enum [
"fade"
"slide"
"off"
]);
default = null;
example = "fade";
description = "The animation used when switching virtual desktop.";
};
windowOpenClose = {
animation = mkOption {
type = with types; nullOr (enum [ "fade" "glide" "scale" ]);
type =
with types;
nullOr (enum [
"fade"
"glide"
"scale"
"off"
]);
default = null;
example = "glide";
description = "The animation used when opening/closing windows.";
@ -191,7 +273,12 @@ in
names = mkOption {
type = with types; nullOr (listOf str);
default = null;
example = [ "Desktop 1" "Desktop 2" "Desktop 3" "Desktop 4" ];
example = [
"Desktop 1"
"Desktop 2"
"Desktop 3"
"Desktop 4"
];
description = ''
The names of your virtual desktops. When set, the number of virtual
desktops is automatically detected and doesn't need to be specified.
@ -224,7 +311,13 @@ in
description = "Enable the night light effect.";
};
mode = mkOption {
type = with types; nullOr (enum [ "constant" "location" "times" ]);
type =
with types;
nullOr (enum [
"constant"
"location"
"times"
]);
default = null;
example = "times";
description = "The mode of the night light effect.";
@ -298,162 +391,371 @@ in
example = false;
description = "When enabled, prevents the cursor from crossing at screen-corners.";
};
tiling = {
padding = mkOption {
type = with types; nullOr ints.positive;
default = null;
example = 10;
description = "The padding between windows in tiling.";
};
layout = mkOption {
type = with types; nullOr tilingLayoutType;
default = null;
example = {
id = "cf5c25c2-4217-4193-add6-b5971cb543f2";
tiles = {
layoutDirection = "horizontal";
tiles = [
{ width = 0.5; }
{
layoutDirection = "vertical";
tiles = [
{ height = 0.5; }
{ height = 0.5; }
];
width = 0.5;
}
];
};
};
};
};
scripts = {
polonium = {
enable = mkOption {
type = with types; nullOr bool;
default = null;
example = true;
description = "Whether to enable Polonium";
};
settings = {
borderVisibility =
let
enumVals = [
"noBorderAll"
"noBorderTiled"
"borderSelected"
"borderAll"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "noBorderAll";
description = "The border visibility setting for Polonium";
apply = getIndexFromEnum enumVals;
};
callbackDelay = mkOption {
type = with types; nullOr (ints.between 1 200);
default = null;
example = 100;
description = "The callback delay setting for Polonium";
};
enableDebug = mkOption {
type = with types; nullOr bool;
default = null;
example = true;
description = "Whether to enable debug for Polonium";
};
filter = {
processes = mkOption {
type = with types; nullOr (listOf str);
default = null;
example = [
"firefox"
"chromium"
];
description = "The processes to filter for Polonium";
apply = convertPoloniumFilter;
};
windowTitles = mkOption {
type = with types; nullOr (listOf str);
default = null;
example = [
"Discord"
"Telegram"
];
description = "The window titles to filter for Polonium";
apply = convertPoloniumFilter;
};
};
layout = {
engine =
let
enumVals = [
"binaryTree"
"half"
"threeColumn"
"monocle"
"kwin"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "binaryTree";
description = "The layout engine setting for Polonium";
apply = getIndexFromEnum enumVals;
};
insertionPoint =
let
enumVals = [
"left"
"right"
"activeWindow"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "top";
description = "The insertion point setting for Polonium";
apply = getIndexFromEnum enumVals;
};
rotate = mkOption {
type = with types; nullOr bool;
default = null;
example = true;
description = "Whether to rotate layout for Polonium";
};
};
maximizeSingleWindow = mkOption {
type = with types; nullOr bool;
default = null;
example = true;
description = "Whether to maximize single window for Polonium";
};
resizeAmount = mkOption {
type = with types; nullOr (ints.between 1 450);
default = null;
example = 100;
description = "The resize amount setting for Polonium";
};
saveOnTileEdit = mkOption {
type = with types; nullOr bool;
default = null;
example = true;
description = "Whether to save on tile edit for Polonium";
};
tilePopups = mkOption {
type = with types; nullOr bool;
default = null;
example = true;
description = "Whether to tile popups for Polonium";
};
};
};
};
};
config.assertions = [
{
assertion =
cfg.kwin.virtualDesktops.number == null ||
cfg.kwin.virtualDesktops.names == null ||
cfg.kwin.virtualDesktops.number == (builtins.length cfg.kwin.virtualDesktops.names);
message = "programs.plasma.virtualDesktops.number doesn't match the length of programs.plasma.virtualDesktops.names.";
}
{
assertion =
cfg.kwin.virtualDesktops.rows == null ||
(cfg.kwin.virtualDesktops.names == null && cfg.kwin.virtualDesktops.number == null) ||
(cfg.kwin.virtualDesktops.number != null && cfg.kwin.virtualDesktops.number >= cfg.kwin.virtualDesktops.rows) ||
(cfg.kwin.virtualDesktops.names != null && (builtins.length cfg.kwin.virtualDesktops.names) >= cfg.kwin.virtualDesktops.rows);
message = "KWin cannot have more rows virtual desktops.";
}
{
assertion = cfg.kwin.effects.minimization.duration == null || cfg.kwin.effects.minimization.animation == "magiclamp";
message = "programs.plasma.kwin.effects.minimization.duration is only supported for the magic lamp effect";
}
{
assertion = (cfg.kwin.nightLight.enable == null || cfg.kwin.nightLight.enable == false) || cfg.kwin.nightLight.mode != null;
message = "programs.plasma.kwin.nightLight.mode must be set when programs.plasma.kwin.nightLight.enable is true.";
}
{
assertion = cfg.kwin.nightLight.mode != "Times" || (cfg.kwin.nightLight.time.morning != null && cfg.kwin.nightLight.time.evening != null);
message = "programs.plasma.kwin.nightLight.time.morning and programs.plasma.kwin.nightLight.time.evening must be set when programs.plasma.kwin.nightLight.mode is set to times.";
}
{
assertion = cfg.kwin.nightLight.mode != "Location" || (cfg.kwin.nightLight.location.latitude != null && cfg.kwin.nightLight.location.longitude != null);
message = "programs.plasma.kwin.nightLight.location.latitude and programs.plasma.kwin.nightLight.location.longitude must be set when programs.plasma.kwin.nightLight.mode is set to location.";
}
{
assertion = cfg.kwin.nightLight.time.morning == null || builtins.stringLength cfg.kwin.nightLight.time.morning == 4;
message = "programs.plasma.kwin.nightLight.time.morning must have the exact length of 4. If it doesn't have, it means that it doesn't have this time format: HH:MM";
}
{
assertion = cfg.kwin.nightLight.time.evening == null || builtins.stringLength cfg.kwin.nightLight.time.evening == 4;
message = "programs.plasma.kwin.nightLight.time.evening must have the exact length of 4. If it doesn't have, it means that it doesn't have this time format: HH:MM";
}
];
config.programs.plasma.configFile."kwinrc" = mkIf (cfg.enable)
(mkMerge [
# Titlebar buttons
(
mkIf (cfg.kwin.titlebarButtons.left != null) {
"org.kde.kdecoration2".ButtonsOnLeft = strings.concatStrings (getShortNames cfg.kwin.titlebarButtons.left);
config = (
mkIf cfg.enable {
assertions = [
{
assertion =
cfg.kwin.virtualDesktops.number == null
|| cfg.kwin.virtualDesktops.names == null
|| cfg.kwin.virtualDesktops.number == (builtins.length cfg.kwin.virtualDesktops.names);
message = "programs.plasma.virtualDesktops.number doesn't match the length of programs.plasma.virtualDesktops.names.";
}
)
(
mkIf (cfg.kwin.titlebarButtons.right != null) {
"org.kde.kdecoration2".ButtonsOnRight = strings.concatStrings (getShortNames cfg.kwin.titlebarButtons.right);
{
assertion =
cfg.kwin.virtualDesktops.rows == null
|| (cfg.kwin.virtualDesktops.names == null && cfg.kwin.virtualDesktops.number == null)
|| (
cfg.kwin.virtualDesktops.number != null
&& cfg.kwin.virtualDesktops.number >= cfg.kwin.virtualDesktops.rows
)
|| (
cfg.kwin.virtualDesktops.names != null
&& (builtins.length cfg.kwin.virtualDesktops.names) >= cfg.kwin.virtualDesktops.rows
);
message = "KWin cannot have more rows virtual desktops.";
}
)
{
assertion =
cfg.kwin.effects.minimization.duration == null
|| cfg.kwin.effects.minimization.animation == "magiclamp";
message = "programs.plasma.kwin.effects.minimization.duration is only supported for the magic lamp effect";
}
{
assertion =
(cfg.kwin.nightLight.enable == null || cfg.kwin.nightLight.enable == false)
|| cfg.kwin.nightLight.mode != null;
message = "programs.plasma.kwin.nightLight.mode must be set when programs.plasma.kwin.nightLight.enable is true.";
}
{
assertion =
cfg.kwin.nightLight.mode != "Times"
|| (cfg.kwin.nightLight.time.morning != null && cfg.kwin.nightLight.time.evening != null);
message = "programs.plasma.kwin.nightLight.time.morning and programs.plasma.kwin.nightLight.time.evening must be set when programs.plasma.kwin.nightLight.mode is set to times.";
}
{
assertion =
cfg.kwin.nightLight.mode != "Location"
|| (
cfg.kwin.nightLight.location.latitude != null && cfg.kwin.nightLight.location.longitude != null
);
message = "programs.plasma.kwin.nightLight.location.latitude and programs.plasma.kwin.nightLight.location.longitude must be set when programs.plasma.kwin.nightLight.mode is set to location.";
}
{
assertion =
cfg.kwin.nightLight.time.morning == null
|| builtins.stringLength cfg.kwin.nightLight.time.morning == 4;
message = "programs.plasma.kwin.nightLight.time.morning must have the exact length of 4. If it doesn't have, it means that it doesn't have this time format: HH:MM";
}
{
assertion =
cfg.kwin.nightLight.time.evening == null
|| builtins.stringLength cfg.kwin.nightLight.time.evening == 4;
message = "programs.plasma.kwin.nightLight.time.evening must have the exact length of 4. If it doesn't have, it means that it doesn't have this time format: HH:MM";
}
];
# Effects
(mkIf (cfg.kwin.effects.shakeCursor.enable != null) {
Plugins.shakecursorEnabled = cfg.kwin.effects.shakeCursor.enable;
})
(mkIf (cfg.kwin.effects.minimization.animation != null) {
Plugins = {
magiclampEnabled = cfg.kwin.effects.minimization.animation == "magiclamp";
squashEnabled = cfg.kwin.effects.minimization.animation == "squash";
};
})
(mkIf (cfg.kwin.effects.minimization.duration != null) {
Effect-magiclamp.AnimationDuration = cfg.kwin.effects.minimization.duration;
})
(mkIf (cfg.kwin.effects.wobblyWindows.enable != null) {
Plugins.wobblywindowsEnabled = cfg.kwin.effects.wobblyWindows.enable;
})
(mkIf (cfg.kwin.effects.translucency.enable != null) {
Plugins.translucencyEnabled = cfg.kwin.effects.translucency.enable;
})
(mkIf (cfg.kwin.effects.windowOpenClose.animation != null) {
Plugins = {
glideEnabled = cfg.kwin.effects.windowOpenClose.animation == "glide";
fadeEnabled = cfg.kwin.effects.windowOpenClose.animation == "fade";
scaleEnabled = cfg.kwin.effects.windowOpenClose.animation == "scale";
};
})
(mkIf (cfg.kwin.effects.fps.enable != null) {
Plugins.showfpsEnabled = cfg.kwin.effects.fps.enable;
})
(mkIf (cfg.kwin.effects.cube.enable != null) {
Plugins.cubeEnabled = cfg.kwin.effects.cube.enable;
})
(mkIf (cfg.kwin.effects.desktopSwitching.animation != null) {
Plugins.slideEnabled = cfg.kwin.effects.desktopSwitching.animation == "slide";
Plugins.fadedesktopEnabled = cfg.kwin.effects.desktopSwitching.animation == "fade";
})
(mkIf (cfg.kwin.effects.fallApart.enable != null) {
Plugins.fallapartEnabled = cfg.kwin.effects.fallApart.enable;
})
(mkIf (cfg.kwin.effects.snapHelper.enable != null) {
Plugins.snaphelperEnabled = cfg.kwin.effects.snapHelper.enable;
})
(mkIf (cfg.kwin.effects.blur.enable != null) {
Plugins.blurEnabled = cfg.kwin.effects.blur.enable;
})
(mkIf (cfg.kwin.effects.dimInactive.enable != null) {
Plugins.diminactiveEnabled = cfg.kwin.effects.dimInactive.enable;
})
(mkIf (cfg.kwin.effects.dimAdminMode.enable != null) {
Plugins.dimscreenEnabled = cfg.kwin.effects.dimAdminMode.enable;
})
(mkIf (cfg.kwin.effects.slideBack.enable != null) {
Plugins.slidebackEnabled = cfg.kwin.effects.slideBack.enable;
})
home.packages = with pkgs; [ ] ++ optionals (cfg.kwin.scripts.polonium.enable == true) [ polonium ];
# Virtual Desktops
(mkIf (cfg.kwin.virtualDesktops.number != null) {
Desktops.Number = cfg.kwin.virtualDesktops.number;
})
(mkIf (cfg.kwin.virtualDesktops.rows != null) {
Desktops.Rows = cfg.kwin.virtualDesktops.rows;
})
(mkIf (cfg.kwin.virtualDesktops.names != null) {
Desktops = mkMerge [
{
Number = builtins.length cfg.kwin.virtualDesktops.names;
}
(virtualDesktopNameAttrs cfg.kwin.virtualDesktops.names)
];
})
programs.plasma.configFile."kwinrc" = (
mkMerge [
# Titlebar buttons
(mkIf (cfg.kwin.titlebarButtons.left != null) {
"org.kde.kdecoration2".ButtonsOnLeft = strings.concatStrings (
getShortNames cfg.kwin.titlebarButtons.left
);
})
(mkIf (cfg.kwin.titlebarButtons.right != null) {
"org.kde.kdecoration2".ButtonsOnRight = strings.concatStrings (
getShortNames cfg.kwin.titlebarButtons.right
);
})
# Borderless maximized windows
(mkIf (cfg.kwin.borderlessMaximizedWindows != null) {
Windows = {
BorderlessMaximizedWindows = cfg.kwin.borderlessMaximizedWindows;
};
})
# Effects
(mkIf (cfg.kwin.effects.shakeCursor.enable != null) {
Plugins.shakecursorEnabled = cfg.kwin.effects.shakeCursor.enable;
})
(mkIf (cfg.kwin.effects.minimization.animation != null) {
Plugins = {
magiclampEnabled = cfg.kwin.effects.minimization.animation == "magiclamp";
squashEnabled = cfg.kwin.effects.minimization.animation == "squash";
};
})
(mkIf (cfg.kwin.effects.minimization.duration != null) {
Effect-magiclamp.AnimationDuration = cfg.kwin.effects.minimization.duration;
})
(mkIf (cfg.kwin.effects.wobblyWindows.enable != null) {
Plugins.wobblywindowsEnabled = cfg.kwin.effects.wobblyWindows.enable;
})
(mkIf (cfg.kwin.effects.translucency.enable != null) {
Plugins.translucencyEnabled = cfg.kwin.effects.translucency.enable;
})
(mkIf (cfg.kwin.effects.windowOpenClose.animation != null) {
Plugins = {
glideEnabled = cfg.kwin.effects.windowOpenClose.animation == "glide";
fadeEnabled = cfg.kwin.effects.windowOpenClose.animation == "fade";
scaleEnabled = cfg.kwin.effects.windowOpenClose.animation == "scale";
};
})
(mkIf (cfg.kwin.effects.fps.enable != null) {
Plugins.showfpsEnabled = cfg.kwin.effects.fps.enable;
})
(mkIf (cfg.kwin.effects.cube.enable != null) {
Plugins.cubeEnabled = cfg.kwin.effects.cube.enable;
})
(mkIf (cfg.kwin.effects.desktopSwitching.animation != null) {
Plugins.slideEnabled = cfg.kwin.effects.desktopSwitching.animation == "slide";
Plugins.fadedesktopEnabled = cfg.kwin.effects.desktopSwitching.animation == "fade";
})
(mkIf (cfg.kwin.effects.fallApart.enable != null) {
Plugins.fallapartEnabled = cfg.kwin.effects.fallApart.enable;
})
(mkIf (cfg.kwin.effects.snapHelper.enable != null) {
Plugins.snaphelperEnabled = cfg.kwin.effects.snapHelper.enable;
})
(mkIf (cfg.kwin.effects.blur.enable != null) {
Plugins.blurEnabled = cfg.kwin.effects.blur.enable;
})
(mkIf (cfg.kwin.effects.dimInactive.enable != null) {
Plugins.diminactiveEnabled = cfg.kwin.effects.dimInactive.enable;
})
(mkIf (cfg.kwin.effects.dimAdminMode.enable != null) {
Plugins.dimscreenEnabled = cfg.kwin.effects.dimAdminMode.enable;
})
(mkIf (cfg.kwin.effects.slideBack.enable != null) {
Plugins.slidebackEnabled = cfg.kwin.effects.slideBack.enable;
})
# Night Light
(mkIf (cfg.kwin.nightLight.enable != null) {
NightColor = {
Active = cfg.kwin.nightLight.enable;
DayTemperature = cfg.kwin.nightLight.temperature.day;
EveningBeginFixed = cfg.kwin.nightLight.time.evening;
LatitudeFixed = cfg.kwin.nightLight.location.latitude;
LongitudeFixed = cfg.kwin.nightLight.location.longitude;
Mode = cfg.kwin.nightLight.mode;
MorningBeginFixed = cfg.kwin.nightLight.time.morning;
NightTemperature = cfg.kwin.nightLight.temperature.night;
TransitionTime = cfg.kwin.nightLight.transitionTime;
};
})
# Virtual Desktops
(mkIf (cfg.kwin.virtualDesktops.number != null) {
Desktops.Number = cfg.kwin.virtualDesktops.number;
})
(mkIf (cfg.kwin.virtualDesktops.rows != null) { Desktops.Rows = cfg.kwin.virtualDesktops.rows; })
(mkIf (cfg.kwin.virtualDesktops.names != null) {
Desktops = mkMerge [
{ Number = builtins.length cfg.kwin.virtualDesktops.names; }
(virtualDesktopNameAttrs cfg.kwin.virtualDesktops.names)
];
})
(mkIf (cfg.kwin.cornerBarrier != null) {
EdgeBarrier.CornerBarrier = cfg.kwin.cornerBarrier;
})
(mkIf (cfg.kwin.edgeBarrier != null) {
EdgeBarrier.EdgeBarrier = cfg.kwin.edgeBarrier;
})
]);
# Borderless maximized windows
(mkIf (cfg.kwin.borderlessMaximizedWindows != null) {
Windows = {
BorderlessMaximizedWindows = cfg.kwin.borderlessMaximizedWindows;
};
})
# Night Light
(mkIf (cfg.kwin.nightLight.enable != null) {
NightColor = {
Active = cfg.kwin.nightLight.enable;
DayTemperature = cfg.kwin.nightLight.temperature.day;
EveningBeginFixed = cfg.kwin.nightLight.time.evening;
LatitudeFixed = cfg.kwin.nightLight.location.latitude;
LongitudeFixed = cfg.kwin.nightLight.location.longitude;
Mode = cfg.kwin.nightLight.mode;
MorningBeginFixed = cfg.kwin.nightLight.time.morning;
NightTemperature = cfg.kwin.nightLight.temperature.night;
TransitionTime = cfg.kwin.nightLight.transitionTime;
};
})
(mkIf (cfg.kwin.cornerBarrier != null) { EdgeBarrier.CornerBarrier = cfg.kwin.cornerBarrier; })
(mkIf (cfg.kwin.edgeBarrier != null) { EdgeBarrier.EdgeBarrier = cfg.kwin.edgeBarrier; })
(mkIf (cfg.kwin.scripts.polonium.enable != null) {
Plugins.poloniumEnabled = cfg.kwin.scripts.polonium.enable;
Script-polonium = {
Borders = cfg.kwin.scripts.polonium.settings.borderVisibility;
Debug = cfg.kwin.scripts.polonium.settings.enableDebug;
EngineType = cfg.kwin.scripts.polonium.settings.layout.engine;
FilterCaption = cfg.kwin.scripts.polonium.settings.filter.windowTitles;
FilterProcess = cfg.kwin.scripts.polonium.settings.filter.processes;
InsertionPoint = cfg.kwin.scripts.polonium.settings.layout.insertionPoint;
MaximizeSingle = cfg.kwin.scripts.polonium.settings.maximizeSingleWindow;
ResizeAmount = cfg.kwin.scripts.polonium.settings.resizeAmount;
RotateLayout = cfg.kwin.scripts.polonium.settings.layout.rotate;
SaveOnTileEdit = cfg.kwin.scripts.polonium.settings.saveOnTileEdit;
TilePopups = cfg.kwin.scripts.polonium.settings.tilePopups;
TimerDelay = cfg.kwin.scripts.polonium.settings.callbackDelay;
};
})
(mkIf (cfg.kwin.tiling.padding != null) {
Tiling = {
padding = cfg.kwin.tiling.padding;
};
})
(mkIf (cfg.kwin.tiling.layout != null) {
"Tiling/${cfg.kwin.tiling.layout.id}" = {
tiles = {
escapeValue = false;
value = cfg.kwin.tiling.layout.tiles;
};
};
})
]
);
}
);
}

View File

@ -1,18 +1,25 @@
{ lib
, config
, pkgs
, ...
} @ args:
{
lib,
config,
pkgs,
...
}@args:
let
cfg = config.programs.plasma;
hasWidget = widgetName: builtins.any (panel: builtins.any (widget: widget.name == widgetName) panel.widgets) cfg.panels;
desktopWidgets = if cfg.desktop.widgets != null then cfg.desktop.widgets else [ ];
hasWidget =
widgetName:
builtins.any (panel: builtins.any (widget: widget.name == widgetName) panel.widgets) cfg.panels
|| builtins.any (widget: widget.name == widgetName) desktopWidgets;
# An attrset keeping track of the packages which should be added when a
# widget is present in the config.
additionalWidgetPackages = with pkgs; {
"com.github.antroids.application-title-bar" = [ application-title-bar ];
plasmusic-toolbar = [ plasmusic-toolbar ];
"luisbocanegra.panel.colorizer" = [ plasma-panel-colorizer ];
"org.kde.windowbuttons" = [ kdePackages.applet-window-buttons6 ];
"org.dhruv8sh.kara" = [ kara ];
"luisbocanegra.panelspacer.extended" = [ plasma-panel-spacer-extended ];
};
# An attrset of service-names and widgets/conditions. If any of the
@ -23,143 +30,179 @@ let
"plasma-plasmashell" = [
{
widget = "org.kde.plasma.systemmonitor";
cond = widget: ((builtins.hasAttr "org.kde.ksysguard.piechart/General" widget.config) && (builtins.hasAttr "showLegend" widget.config."org.kde.ksysguard.piechart/General"));
cond =
widget:
(
(builtins.hasAttr "org.kde.ksysguard.piechart/General" widget.config)
&& (builtins.hasAttr "showLegend" widget.config."org.kde.ksysguard.piechart/General")
);
}
];
};
widgetsOfName = name: (lib.filter (w: w.name == name) (lib.flatten (map (panel: panel.widgets) cfg.panels)));
shouldRestart = service:
widgetsOfName =
name: (lib.filter (w: w.name == name) (lib.flatten (map (panel: panel.widgets) cfg.panels)));
shouldRestart =
service:
(
let candidates = serviceRestarts."${service}";
in (builtins.any (x: x) (map (v: (builtins.any v.cond (widgetsOfName v.widget))) candidates))
let
candidates = serviceRestarts."${service}";
in
(builtins.any (x: x) (map (v: (builtins.any v.cond (widgetsOfName v.widget))) candidates))
);
widgets = import ./widgets args;
panelType = lib.types.submodule ({ config, ... }: {
options = {
height = lib.mkOption {
type = lib.types.int;
default = 44;
description = "The height of the panel.";
panelType = lib.types.submodule (
{ config, ... }:
{
options = {
height = lib.mkOption {
type = lib.types.int;
default = 44;
description = "The height of the panel.";
};
offset = lib.mkOption {
type = with lib.types; nullOr int;
default = null;
example = 100;
description = "The offset of the panel from the anchor-point.";
};
minLength = lib.mkOption {
type = with lib.types; nullOr int;
default = null;
example = 1000;
description = "The minimum required length/width of the panel.";
};
maxLength = lib.mkOption {
type = with lib.types; nullOr int;
default = null;
example = 1600;
description = "The maximum allowed length/width of the panel.";
};
lengthMode = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"fit"
"fill"
"custom"
]);
default = if config.minLength != null || config.maxLength != null then "custom" else null;
example = "fit";
description = "The length mode of the panel. Defaults to `custom` if either `minLength` or `maxLength` is set.";
};
location = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"top"
"bottom"
"left"
"right"
"floating"
]);
default = "bottom";
example = "left";
description = "The location of the panel.";
};
alignment = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"left"
"center"
"right"
]);
default = "center";
example = "right";
description = "The alignment of the panel.";
};
hiding = lib.mkOption {
type =
with lib.types;
nullOr (enum [
"none"
"autohide"
# Plasma 5 only
"windowscover"
"windowsbelow"
# Plasma 6 only
"dodgewindows"
"normalpanel"
"windowsgobelow"
]);
default = null;
example = "autohide";
description = ''
The hiding mode of the panel. Here windowscover and windowsbelow are
plasma 5 only, while dodgewindows, windowsgobelow and normalpanel are
plasma 6 only.
'';
};
floating = lib.mkEnableOption "Enable or disable floating style.";
widgets = lib.mkOption {
type = lib.types.listOf widgets.type;
default = [
"org.kde.plasma.kickoff"
"org.kde.plasma.pager"
"org.kde.plasma.icontasks"
"org.kde.plasma.marginsseparator"
"org.kde.plasma.systemtray"
"org.kde.plasma.digitalclock"
"org.kde.plasma.showdesktop"
];
example = [
"org.kde.plasma.kickoff"
"org.kde.plasma.icontasks"
"org.kde.plasma.marginsseparator"
"org.kde.plasma.digitalclock"
];
description = ''
The widgets to use in the panel. To get the names, it may be useful
to look in the share/plasma/plasmoids folder of the nix-package the
widget/plasmoid is from. Some packages which include some
widgets/plasmoids are for example plasma-desktop and
plasma-workspace.
'';
apply = map widgets.convert;
};
screen = lib.mkOption {
type =
with lib.types;
nullOr (oneOf [
ints.unsigned
(listOf ints.unsigned)
(enum [ "all" ])
]);
default = null;
description = ''
The screen the panel should appear on. Can be an int, or a list of ints,
starting from 0, representing the ID of the screen the panel should
appear on. Alternatively it can be set to "all" if the panel should
appear on all the screens.
'';
};
extraSettings = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
Extra lines to add to the layout.js. See
https://develop.kde.org/docs/plasma/scripting/ for inspiration.
'';
};
};
offset = lib.mkOption {
type = with lib.types; nullOr int;
default = null;
example = 100;
description = "The offset of the panel from the anchor-point.";
};
minLength = lib.mkOption {
type = with lib.types; nullOr int;
default = null;
example = 1000;
description = "The minimum required length/width of the panel.";
};
maxLength = lib.mkOption {
type = with lib.types; nullOr int;
default = null;
example = 1600;
description = "The maximum allowed length/width of the panel.";
};
lengthMode = lib.mkOption {
type = with lib.types; nullOr (enum [ "fit" "fill" "custom" ]);
default =
if config.minLength != null || config.maxLength != null then
"custom"
else
null;
example = "fit";
description = "The length mode of the panel. Defaults to `custom` if either `minLength` or `maxLength` is set.";
};
location = lib.mkOption {
type = with lib.types; nullOr (enum [ "top" "bottom" "left" "right" "floating" ]);
default = "bottom";
example = "left";
description = "The location of the panel.";
};
alignment = lib.mkOption {
type = with lib.types; nullOr (enum [ "left" "center" "right" ]);
default = "center";
example = "right";
description = "The alignment of the panel.";
};
hiding = lib.mkOption {
type = with lib.types; nullOr (enum [
"none"
"autohide"
# Plasma 5 only
"windowscover"
"windowsbelow"
# Plasma 6 only
"dodgewindows"
"normalpanel"
"windowsgobelow"
]);
default = null;
example = "autohide";
description = ''
The hiding mode of the panel. Here windowscover and windowsbelow are
plasma 5 only, while dodgewindows, windowsgobelow and normalpanel are
plasma 6 only.
'';
};
floating = lib.mkEnableOption "Enable or disable floating style.";
widgets = lib.mkOption {
type = lib.types.listOf widgets.type;
default = [
"org.kde.plasma.kickoff"
"org.kde.plasma.pager"
"org.kde.plasma.icontasks"
"org.kde.plasma.marginsseparator"
"org.kde.plasma.systemtray"
"org.kde.plasma.digitalclock"
"org.kde.plasma.showdesktop"
];
example = [
"org.kde.plasma.kickoff"
"org.kde.plasma.icontasks"
"org.kde.plasma.marginsseparator"
"org.kde.plasma.digitalclock"
];
description = ''
The widgets to use in the panel. To get the names, it may be useful
to look in the share/plasma/plasmoids folder of the nix-package the
widget/plasmoid is from. Some packages which include some
widgets/plasmoids are for example plasma-desktop and
plasma-workspace.
'';
apply = map widgets.convert;
};
screen = lib.mkOption {
type = with lib.types; nullOr (oneOf [ ints.unsigned (listOf ints.unsigned) (enum [ "all" ]) ]);
default = null;
description = ''
The screen the panel should appear on. Can be an int, or a list of ints,
starting from 0, representing the ID of the screen the panel should
appear on. Alternatively it can be set to "any" if the panel should
appear on all the screens.
'';
};
extraSettings = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
Extra lines to add to the layout.js. See
https://develop.kde.org/docs/plasma/scripting/ for inspiration.
'';
};
};
});
}
);
anyPanelOrWallpaperSet = ((cfg.workspace.wallpaper != null) ||
(cfg.workspace.wallpaperSlideShow != null) ||
(cfg.workspace.wallpaperPictureOfTheDay != null) ||
(cfg.workspace.wallpaperPlainColor != null) ||
((builtins.length cfg.panels) > 0));
anyPanelSet = (builtins.length cfg.panels) > 0;
in
{
imports = [
(lib.mkRemovedOptionModule [ "programs" "plasma" "extraWidgets" ] "Place the widget packages in home.packages or environment.systemPackages instead.")
(lib.mkRemovedOptionModule [
"programs"
"plasma"
"extraWidgets"
] "Place the widget packages in home.packages or environment.systemPackages instead.")
];
options.programs.plasma.panels = lib.mkOption {
@ -167,83 +210,55 @@ in
default = [ ];
};
# Wallpaper and panels are in the same script since the resetting of the
# panels in the panels-script also has a tendency to reset the wallpaper, so
# these should run at the same time.
config = (lib.mkIf cfg.enable {
home.packages = (lib.flatten (lib.filter (x: x != null)
(lib.mapAttrsToList
(widgetName: packages: if (hasWidget widgetName) then packages else null)
additionalWidgetPackages)));
config = (
lib.mkIf cfg.enable {
home.packages = (
lib.flatten (
lib.filter (x: x != null) (
lib.mapAttrsToList (
widgetName: packages: if (hasWidget widgetName) then packages else null
) additionalWidgetPackages
)
)
);
programs.plasma.startup.desktopScript."panels_and_wallpaper" = (lib.mkIf anyPanelOrWallpaperSet
(
let
anyPanels = ((builtins.length cfg.panels) > 0);
anyNonDefaultScreens = ((builtins.any (panel: panel.screen != null)) cfg.panels);
panelPreCMD = (if anyPanels then ''
# We delete plasma-org.kde.plasma.desktop-appletsrc to hinder it
# growing indefinitely. See:
# https://github.com/nix-community/plasma-manager/issues/76
[ -f ${config.xdg.configHome}/plasma-org.kde.plasma.desktop-appletsrc ] && rm ${config.xdg.configHome}/plasma-org.kde.plasma.desktop-appletsrc
'' else "");
panelLayoutStr = (if anyPanels then (import ../lib/panel.nix { inherit lib; inherit config; }) else "");
panelPostCMD = (if anyNonDefaultScreens then ''
sed -i 's/^lastScreen\\x5b$i\\x5d=/lastScreen[$i]=/' ${config.xdg.configHome}/plasma-org.kde.plasma.desktop-appletsrc
'' else "");
# This meaningless comment inserts the URL into the desktop-script
# which means that when the wallpaper is updated, the sha256 hash
# changes and the script will be re-run.
wallpaperDesktopScript = (if (cfg.workspace.wallpaper != null) then ''
// Wallpaper to set later: ${cfg.workspace.wallpaper}
'' else "");
wallpaperPostCMD = (if (cfg.workspace.wallpaper != null) then ''
plasma-apply-wallpaperimage ${cfg.workspace.wallpaper}
'' else "");
wallpaperSlideShow = (if (cfg.workspace.wallpaperSlideShow != null) then ''
// Wallpaper slideshow
let allDesktops = desktops();
for (var desktopIndex = 0; desktopIndex < allDesktops.length; desktopIndex++) {
var desktop = allDesktops[desktopIndex];
desktop.wallpaperPlugin = "org.kde.slideshow";
desktop.currentConfigGroup = Array("Wallpaper", "org.kde.slideshow", "General");
desktop.writeConfig("SlidePaths", ${with cfg.workspace.wallpaperSlideShow; if ((builtins.isPath path) || (builtins.isString path)) then
"\"" + (builtins.toString path) + "\"" else
"[" + (builtins.concatStringsSep "," (map (s: "\"" + s + "\"") path)) + "]"});
desktop.writeConfig("SlideInterval", "${builtins.toString cfg.workspace.wallpaperSlideShow.interval}");
}
'' else "");
wallpaperPOTD = (if (cfg.workspace.wallpaperPictureOfTheDay != null) then ''
// Wallpaper POTD
let allDesktops = desktops();
for (const desktop of allDesktops) {
desktop.wallpaperPlugin = "org.kde.potd";
desktop.currentConfigGroup = ["Wallpaper", "org.kde.potd", "General"];
desktop.writeConfig("Provider", "${cfg.workspace.wallpaperPictureOfTheDay.provider}");
desktop.writeConfig("UpdateOverMeteredConnection", "${if (cfg.workspace.wallpaperPictureOfTheDay.updateOverMeteredConnection) then "1" else "0"}");
programs.plasma.startup.desktopScript."panels" = (
lib.mkIf anyPanelSet (
let
anyNonDefaultScreens = ((builtins.any (panel: panel.screen != null)) cfg.panels);
panelPreCMD = ''
# We delete plasma-org.kde.plasma.desktop-appletsrc to hinder it
# growing indefinitely. See:
# https://github.com/nix-community/plasma-manager/issues/76
[ -f ${config.xdg.configHome}/plasma-org.kde.plasma.desktop-appletsrc ] && rm ${config.xdg.configHome}/plasma-org.kde.plasma.desktop-appletsrc
'';
panelLayoutStr = (
import ../lib/panel.nix {
inherit lib;
inherit config;
}
'' else "");
wallpaperPlainColor = (if (cfg.workspace.wallpaperPlainColor != null) then ''
// Wallpaper plain color
let allDesktops = desktops();
for (var desktopIndex = 0; desktopIndex < allDesktops.length; desktopIndex++) {
var desktop = allDesktops[desktopIndex];
desktop.wallpaperPlugin = "org.kde.color";
desktop.currentConfigGroup = Array("Wallpaper", "org.kde.color", "General");
desktop.writeConfig("Color", "${cfg.workspace.wallpaperPlainColor}");
}
'' else ""
);
in
{
preCommands = panelPreCMD;
text = panelLayoutStr + wallpaperDesktopScript + wallpaperSlideShow + wallpaperPOTD + wallpaperPlainColor;
postCommands = panelPostCMD + wallpaperPostCMD;
restartServices =
(lib.unique (if anyNonDefaultScreens then [ "plasma-plasmashell" ] else [ ])
++ (lib.filter (service: shouldRestart service) (builtins.attrNames serviceRestarts)));
priority = 2;
}
));
});
);
panelPostCMD = (
if anyNonDefaultScreens then
''
sed -i 's/^lastScreen\\x5b$i\\x5d=/lastScreen[$i]=/' ${config.xdg.configHome}/plasma-org.kde.plasma.desktop-appletsrc
''
else
""
);
in
{
preCommands = panelPreCMD;
text = panelLayoutStr;
postCommands = panelPostCMD;
restartServices = (
lib.unique (if anyNonDefaultScreens then [ "plasma-plasmashell" ] else [ ])
++ (lib.filter (service: shouldRestart service) (builtins.attrNames serviceRestarts))
);
priority = 2;
}
)
);
}
);
}

View File

@ -2,107 +2,297 @@
let
cfg = config.programs.plasma;
# Values can be found at:
# https://github.com/KDE/powerdevil/blob/master/daemon/powerdevilenums.h
powerButtonActions = {
nothing = 0;
sleep = 1;
hibernate = 2;
shutDown = 8;
lockScreen = 32;
showLogoutScreen = null;
showLogoutScreen = 16;
turnOffScreen = 64;
};
autoSuspendActions = {
nothing = 0;
sleep = null;
hibernate = 2;
sleep = 1;
shutDown = 8;
};
in
{
config.assertions = [
{
assertion = (cfg.powerdevil.autoSuspend.action != autoSuspendActions.nothing || cfg.powerdevil.autoSuspend.idleTimeout == null);
message = "Setting programs.plasma.powerdevil.autoSuspend.idleTimeout for autosuspend-action \"nothing\" is not supported.";
}
{
assertion = (cfg.powerdevil.turnOffDisplay.idleTimeout != -1 || cfg.powerdevil.turnOffDisplay.idleTimeoutWhenLocked == null);
message = "Setting programs.plasma.powerdevil.turnOffDisplay.idleTimeoutWhenLocked for when idleTimeout is \"never\" is not supported.";
}
];
options = {
programs.plasma.powerdevil = {
powerButtonAction = lib.mkOption {
type = with lib.types; nullOr (enum (builtins.attrNames powerButtonActions));
whenSleepingEnterActions = {
standby = 1;
hybridSleep = 2;
standbyThenHibernate = 3;
};
whenLaptopLidClosedActions = {
doNothing = 0;
sleep = 1;
hibernate = 2;
shutdown = 8;
lockScreen = 32;
turnOffScreen = 64;
};
# Since AC and battery allows the same options we create a function here which
# can generate the options by just specifying the type (i.e. "AC" or
# "battery").
createPowerDevilOptions = type: {
powerButtonAction = lib.mkOption {
type = with lib.types; nullOr (enum (builtins.attrNames powerButtonActions));
default = null;
example = "nothing";
description = ''
The action, when on ${type}, to perform when the power button is pressed.
'';
apply = action: if (action == null) then null else powerButtonActions."${action}";
};
autoSuspend = {
action = lib.mkOption {
type = with lib.types; nullOr (enum (builtins.attrNames autoSuspendActions));
default = null;
example = "nothing";
description = ''
The action to perform when the power button is pressed.
The action, when on ${type}, to perform after a certain period of inactivity.
'';
apply = action: if (action == null) then null else powerButtonActions."${action}";
apply = action: if (action == null) then null else autoSuspendActions."${action}";
};
autoSuspend = {
action = lib.mkOption {
type = with lib.types; nullOr (enum (builtins.attrNames autoSuspendActions));
default = null;
example = "nothing";
description = ''
The action to perform after a certain period of inactivity.
'';
apply = action: if (action == null) then null else autoSuspendActions."${action}";
};
idleTimeout = lib.mkOption {
type = with lib.types; nullOr (ints.between 60 600000);
default = null;
example = 600;
description = ''
The duration (in seconds) the computer must be idle until the
auto-suspend action is executed.
'';
};
idleTimeout = lib.mkOption {
type = with lib.types; nullOr (ints.between 60 600000);
default = null;
example = 600;
description = ''
The duration (in seconds), when on ${type}, the computer must be idle
until the auto-suspend action is executed.
'';
};
turnOffDisplay = {
idleTimeout = lib.mkOption {
type = with lib.types; nullOr (either (enum [ "never" ]) (ints.between 30 600000));
};
whenSleepingEnter = lib.mkOption {
type = with lib.types; nullOr (enum (builtins.attrNames whenSleepingEnterActions));
default = null;
example = "standbyThenHibernate";
description = ''
The state, when on ${type}, to enter when sleeping.
'';
apply = action: if (action == null) then null else whenSleepingEnterActions."${action}";
};
whenLaptopLidClosed = lib.mkOption {
type = with lib.types; nullOr (enum (builtins.attrNames whenLaptopLidClosedActions));
default = null;
example = "shutdown";
description = ''
The action, when on ${type}, to perform when the laptop lid is closed.
'';
apply = action: if (action == null) then null else whenLaptopLidClosedActions."${action}";
};
inhibitLidActionWhenExternalMonitorConnected = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
description = ''
If enabled, the lid action will be inhibited when an external monitor is connected.
'';
};
turnOffDisplay = {
idleTimeout = lib.mkOption {
type = with lib.types; nullOr (either (enum [ "never" ]) (ints.between 30 600000));
default = null;
example = 300;
description = ''
The duration (in seconds), when on ${type}, the computer must be idle
(when unlocked) until the display turns off.
'';
apply =
timeout:
if (timeout == null) then
null
else if (timeout == "never") then
-1
else
timeout;
};
idleTimeoutWhenLocked = lib.mkOption {
type =
with lib.types;
nullOr (
either (enum [
"whenLockedAndUnlocked"
"immediately"
]) (ints.between 20 600000)
);
default = null;
example = 60;
description = ''
The duration (in seconds), when on ${type}, the computer must be idle
(when locked) until the display turns off.
'';
apply =
timeout:
if (timeout == null) then
null
else if (timeout == "whenLockedAndUnlocked") then
-2
else if (timeout == "immediately") then
0
else
timeout;
};
};
dimDisplay = {
enable = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = false;
description = "Enable or disable screen dimming.";
};
idleTimeout = lib.mkOption {
type = with lib.types; nullOr (ints.between 20 600000);
default = null;
example = 300;
description = ''
The duration (in seconds), when on ${type}, the computer must be idle
until the display starts dimming.
'';
};
};
};
# By the same logic as createPowerDevilOptions, we can generate the
# configuration. cfgSectName is here the name of the section in powerdevilrc,
# while optionsName is the name of the "namespace" where we should draw the
# options from (i.e. powerdevil.AC or powerdevil.battery).
createPowerDevilConfig = cfgSectName: optionsName: {
"${cfgSectName}/SuspendAndShutdown" = {
PowerButtonAction = cfg.powerdevil.${optionsName}.powerButtonAction;
AutoSuspendAction = cfg.powerdevil.${optionsName}.autoSuspend.action;
AutoSuspendIdleTimeoutSec = cfg.powerdevil.${optionsName}.autoSuspend.idleTimeout;
SleepMode = cfg.powerdevil.${optionsName}.whenSleepingEnter;
LidAction = cfg.powerdevil.${optionsName}.whenLaptopLidClosed;
InhibitLidActionWhenExternalMonitorPresent =
cfg.powerdevil.${optionsName}.inhibitLidActionWhenExternalMonitorConnected;
};
"${cfgSectName}/Display" = {
TurnOffDisplayIdleTimeoutSec = cfg.powerdevil.${optionsName}.turnOffDisplay.idleTimeout;
TurnOffDisplayIdleTimeoutWhenLockedSec =
cfg.powerdevil.${optionsName}.turnOffDisplay.idleTimeoutWhenLocked;
DimDisplayWhenIdle =
if (cfg.powerdevil.${optionsName}.dimDisplay.enable != null) then
cfg.powerdevil.${optionsName}.dimDisplay.enable
else if (cfg.powerdevil.${optionsName}.dimDisplay.idleTimeout != null) then
true
else
null;
DimDisplayIdleTimeoutSec = cfg.powerdevil.${optionsName}.dimDisplay.idleTimeout;
};
};
in
{
imports = [
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"powerdevil"
"powerButtonAction"
]
[
"programs"
"plasma"
"powerdevil"
"AC"
"powerButtonAction"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"powerdevil"
"autoSuspend"
]
[
"programs"
"plasma"
"powerdevil"
"AC"
"autoSuspend"
]
)
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"powerdevil"
"turnOffDisplay"
]
[
"programs"
"plasma"
"powerdevil"
"AC"
"turnOffDisplay"
]
)
];
config.assertions =
let
createAssertions = type: [
{
assertion = (
cfg.powerdevil.${type}.autoSuspend.action != autoSuspendActions.nothing
|| cfg.powerdevil.${type}.autoSuspend.idleTimeout == null
);
message = "Setting programs.plasma.powerdevil.${type}.autoSuspend.idleTimeout for autosuspend-action \"nothing\" is not supported.";
}
{
assertion = (
cfg.powerdevil.${type}.turnOffDisplay.idleTimeout != -1
|| cfg.powerdevil.${type}.turnOffDisplay.idleTimeoutWhenLocked == null
);
message = "Setting programs.plasma.powerdevil.${type}.turnOffDisplay.idleTimeoutWhenLocked for idleTimeout \"never\" is not supported.";
}
{
assertion = (
cfg.powerdevil.${type}.dimDisplay.enable != false
|| cfg.powerdevil.${type}.dimDisplay.idleTimeout == null
);
message = "Cannot set programs.plasma.powerdevil.${type}.dimDisplay.idleTimeout when programs.plasma.powerdevil.${type}.dimDisplay.enable is disabled.";
}
];
in
(createAssertions "AC") ++ (createAssertions "battery") ++ (createAssertions "lowBattery");
options = {
programs.plasma.powerdevil = {
AC = (createPowerDevilOptions "AC");
battery = (createPowerDevilOptions "battery");
lowBattery = (createPowerDevilOptions "lowBattery");
general = {
pausePlayersOnSuspend = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = 300;
example = false;
description = ''
The duration (in seconds) the computer must be idle (when unlocked)
until the display turns off.
If enabled, pause media players when the system is suspended.
'';
apply = timeout:
if (timeout == null) then null else
if (timeout == "never") then -1
else timeout;
};
idleTimeoutWhenLocked = lib.mkOption {
type = with lib.types; nullOr (either (enum [ "whenLockedAndUnlocked" "immediately" ]) (ints.between 20 600000));
default = null;
example = 60;
description = ''
The duration (in seconds) the computer must be idle (when locked)
until the display turns off.
'';
apply = timeout:
if (timeout == null) then null else
if (timeout == "whenLockedAndUnlocked") then -2 else
if (timeout == "immediately") then 0
else timeout;
};
};
};
};
config.programs.plasma.configFile = lib.mkIf cfg.enable {
powerdevilrc = lib.filterAttrs (k: v: v != null) {
"AC/SuspendAndShutdown" = {
PowerButtonAction = cfg.powerdevil.powerButtonAction;
AutoSuspendAction = cfg.powerdevil.autoSuspend.action;
AutoSuspendIdleTimeoutSec = cfg.powerdevil.autoSuspend.idleTimeout;
};
"AC/Display" = {
TurnOffDisplayIdleTimeoutSec = cfg.powerdevil.turnOffDisplay.idleTimeout;
TurnOffDisplayIdleTimeoutWhenLockedSec = cfg.powerdevil.turnOffDisplay.idleTimeoutWhenLocked;
};
};
powerdevilrc = lib.filterAttrsRecursive (k: v: v != null) (
(createPowerDevilConfig "AC" "AC")
// (createPowerDevilConfig "Battery" "battery")
// (createPowerDevilConfig "LowBattery" "lowBattery")
// {
General = {
pausePlayersOnSuspend = cfg.powerdevil.general.pausePlayersOnSuspend;
};
}
);
};
}

View File

@ -6,43 +6,57 @@ let
# Checks if the shortcut is in the "service" group, in which case we need to
# write the values a little differently.
isService = group:
isService =
group:
let
startString = "services/";
in
(builtins.substring 0 (builtins.stringLength startString) group) == startString;
# Convert one shortcut into a settings attribute set.
shortcutToConfigValue = group: _action: skey:
shortcutToConfigValue =
group: _action: skey:
let
# Keys are expected to be a list:
keys =
if builtins.isList skey
then
if builtins.isList skey then
(if ((builtins.length skey) == 0) then [ "none" ] else skey)
else [ skey ];
else
[ skey ];
# Don't allow un-escaped commas:
escape = lib.escape [ "," ];
keysStr = (if ((builtins.length keys) == 1) then (escape (builtins.head keys)) else "\t" + (lib.concatStringsSep "\t" (map escape keys)));
keysStr = (
if ((builtins.length keys) == 1) then
(escape (builtins.head keys))
else
"\t" + (lib.concatStringsSep "\t" (map escape keys))
);
in
(if (isService group) then
keysStr
else
(lib.concatStringsSep "," [
(
if (isService group) then
keysStr
"" # List of default keys, not needed.
"" # Display string, not needed.
]));
else
(lib.concatStringsSep "," [
keysStr
"" # List of default keys, not needed.
"" # Display string, not needed.
])
);
shortcutsToSettings = groups:
lib.mapAttrs
(group: attrs: (lib.mapAttrs (shortcutToConfigValue group) attrs))
groups;
shortcutsToSettings =
groups: lib.mapAttrs (group: attrs: (lib.mapAttrs (shortcutToConfigValue group) attrs)) groups;
in
{
options.programs.plasma.shortcuts = lib.mkOption {
type = with lib.types; attrsOf (attrsOf (oneOf [ (listOf str) str ]));
type =
with lib.types;
attrsOf (
attrsOf (oneOf [
(listOf str)
str
])
);
default = { };
description = ''
An attribute set where the keys are application groups and the
@ -51,7 +65,6 @@ in
};
config = lib.mkIf cfg.enable {
programs.plasma.configFile."kglobalshortcutsrc" =
shortcutsToSettings cfg.shortcuts;
programs.plasma.configFile."kglobalshortcutsrc" = shortcutsToSettings cfg.shortcuts;
};
}

View File

@ -6,7 +6,12 @@ in
{
options.programs.plasma.spectacle.shortcuts = {
captureActiveWindow = lib.mkOption {
type = with lib.types; nullOr (oneOf [ (listOf str) str ]);
type =
with lib.types;
nullOr (oneOf [
(listOf str)
str
]);
default = null;
example = "Meta+Print";
description = ''
@ -15,7 +20,12 @@ in
};
captureCurrentMonitor = lib.mkOption {
type = with lib.types; nullOr (oneOf [ (listOf str) str ]);
type =
with lib.types;
nullOr (oneOf [
(listOf str)
str
]);
default = null;
example = "Print";
description = ''
@ -24,7 +34,12 @@ in
};
captureEntireDesktop = lib.mkOption {
type = with lib.types; nullOr (oneOf [ (listOf str) str ]);
type =
with lib.types;
nullOr (oneOf [
(listOf str)
str
]);
default = null;
example = "Shift+Print";
description = ''
@ -33,7 +48,12 @@ in
};
captureRectangularRegion = lib.mkOption {
type = with lib.types; nullOr (oneOf [ (listOf str) str ]);
type =
with lib.types;
nullOr (oneOf [
(listOf str)
str
]);
default = null;
example = "Meta+Shift+S";
description = ''
@ -42,7 +62,12 @@ in
};
captureWindowUnderCursor = lib.mkOption {
type = with lib.types; nullOr (oneOf [ (listOf str) str ]);
type =
with lib.types;
nullOr (oneOf [
(listOf str)
str
]);
default = null;
example = "Meta+Ctrl+Print";
description = ''
@ -51,7 +76,12 @@ in
};
launch = lib.mkOption {
type = with lib.types; nullOr (oneOf [ (listOf str) str ]);
type =
with lib.types;
nullOr (oneOf [
(listOf str)
str
]);
default = null;
example = "Meta+S";
description = ''
@ -60,7 +90,12 @@ in
};
launchWithoutCapturing = lib.mkOption {
type = with lib.types; nullOr (oneOf [ (listOf str) str ]);
type =
with lib.types;
nullOr (oneOf [
(listOf str)
str
]);
default = null;
example = "Meta+Alt+S";
description = ''
@ -69,7 +104,12 @@ in
};
recordRegion = lib.mkOption {
type = with lib.types; nullOr (oneOf [ (listOf str) str ]);
type =
with lib.types;
nullOr (oneOf [
(listOf str)
str
]);
default = null;
example = "Meta+Shift+R";
description = ''
@ -78,7 +118,12 @@ in
};
recordScreen = lib.mkOption {
type = with lib.types; nullOr (oneOf [ (listOf str) str ]);
type =
with lib.types;
nullOr (oneOf [
(listOf str)
str
]);
default = null;
example = "Meta+Alt+R";
description = ''
@ -87,7 +132,12 @@ in
};
recordWindow = lib.mkOption {
type = with lib.types; nullOr (oneOf [ (listOf str) str ]);
type =
with lib.types;
nullOr (oneOf [
(listOf str)
str
]);
default = null;
example = "Meta+Ctrl+R";
description = ''
@ -98,56 +148,34 @@ in
config = lib.mkIf cfg.enable {
programs.plasma.shortcuts."org.kde.spectacle.desktop" = lib.mkMerge [
(
lib.mkIf (cfg.spectacle.shortcuts.captureActiveWindow != null) {
ActiveWindowScreenShot = cfg.spectacle.shortcuts.captureActiveWindow;
}
)
(
lib.mkIf (cfg.spectacle.shortcuts.captureCurrentMonitor != null) {
CurrentMonitorScreenShot = cfg.spectacle.shortcuts.captureCurrentMonitor;
}
)
(
lib.mkIf (cfg.spectacle.shortcuts.captureEntireDesktop != null) {
FullScreenScreenShot = cfg.spectacle.shortcuts.captureEntireDesktop;
}
)
(
lib.mkIf (cfg.spectacle.shortcuts.captureRectangularRegion != null) {
RectangularRegionScreenShot = cfg.spectacle.shortcuts.captureRectangularRegion;
}
)
(
lib.mkIf (cfg.spectacle.shortcuts.captureWindowUnderCursor != null) {
WindowUnderCursorScreenShot = cfg.spectacle.shortcuts.captureWindowUnderCursor;
}
)
(
lib.mkIf (cfg.spectacle.shortcuts.launch != null) {
_launch = cfg.spectacle.shortcuts.launch;
}
)
(
lib.mkIf (cfg.spectacle.shortcuts.launchWithoutCapturing != null) {
OpenWithoutScreenshot = cfg.spectacle.shortcuts.launchWithoutCapturing;
}
)
(
lib.mkIf (cfg.spectacle.shortcuts.recordRegion != null) {
RecordRegion = cfg.spectacle.shortcuts.recordRegion;
}
)
(
lib.mkIf (cfg.spectacle.shortcuts.recordScreen != null) {
RecordScreen = cfg.spectacle.shortcuts.recordScreen;
}
)
(
lib.mkIf (cfg.spectacle.shortcuts.recordWindow != null) {
RecordWindow = cfg.spectacle.shortcuts.recordWindow;
}
)
(lib.mkIf (cfg.spectacle.shortcuts.captureActiveWindow != null) {
ActiveWindowScreenShot = cfg.spectacle.shortcuts.captureActiveWindow;
})
(lib.mkIf (cfg.spectacle.shortcuts.captureCurrentMonitor != null) {
CurrentMonitorScreenShot = cfg.spectacle.shortcuts.captureCurrentMonitor;
})
(lib.mkIf (cfg.spectacle.shortcuts.captureEntireDesktop != null) {
FullScreenScreenShot = cfg.spectacle.shortcuts.captureEntireDesktop;
})
(lib.mkIf (cfg.spectacle.shortcuts.captureRectangularRegion != null) {
RectangularRegionScreenShot = cfg.spectacle.shortcuts.captureRectangularRegion;
})
(lib.mkIf (cfg.spectacle.shortcuts.captureWindowUnderCursor != null) {
WindowUnderCursorScreenShot = cfg.spectacle.shortcuts.captureWindowUnderCursor;
})
(lib.mkIf (cfg.spectacle.shortcuts.launch != null) { _launch = cfg.spectacle.shortcuts.launch; })
(lib.mkIf (cfg.spectacle.shortcuts.launchWithoutCapturing != null) {
OpenWithoutScreenshot = cfg.spectacle.shortcuts.launchWithoutCapturing;
})
(lib.mkIf (cfg.spectacle.shortcuts.recordRegion != null) {
RecordRegion = cfg.spectacle.shortcuts.recordRegion;
})
(lib.mkIf (cfg.spectacle.shortcuts.recordScreen != null) {
RecordScreen = cfg.spectacle.shortcuts.recordScreen;
})
(lib.mkIf (cfg.spectacle.shortcuts.recordWindow != null) {
RecordWindow = cfg.spectacle.shortcuts.recordWindow;
})
];
};
}

View File

@ -19,12 +19,22 @@ let
default = [ ];
description = "Services to restart after the script has been run.";
};
runAlwaysOption = lib.mkOption {
type = lib.types.bool;
default = false;
example = true;
description = ''
When enabled the script will run even if no changes have been made
since last successful run.
'';
};
startupScriptType = lib.types.submodule {
options = {
text = textOption;
priority = priorityOption;
restartServices = restartServicesOption;
runAlways = runAlwaysOption;
};
};
desktopScriptType = lib.types.submodule {
@ -32,6 +42,7 @@ let
text = textOption;
priority = priorityOption;
restartServices = restartServicesOption;
runAlways = runAlwaysOption;
preCommands = lib.mkOption {
type = lib.types.str;
description = "Commands to run before the desktop script lines.";
@ -45,25 +56,46 @@ let
};
};
createScriptContentRunOnce = name: sha256sumFile: script: text: ''
last_update="$(sha256sum ${sha256sumFile})"
last_update_file=${config.xdg.dataHome}/plasma-manager/last_run_${name}
if [ -f "$last_update_file" ]; then
stored_last_update=$(cat "$last_update_file")
fi
if ! [ "$last_update" = "$stored_last_update" ]; then
echo "Running script: ${name}"
success=1
trap 'success=0' ERR
${text}
if [ $success -eq 1 ]; then
echo "$last_update" > "$last_update_file"
${
builtins.concatStringsSep "\n" (
map (
s: "echo ${s} >> ${config.xdg.dataHome}/plasma-manager/services_to_restart"
) script.restartServices
)
}
fi
fi
'';
createScriptContentRunAlways = name: text: ''
echo "Running script: ${name}"
${text}
'';
createScriptContent = name: sha256sumFile: script: text: {
"plasma-manager/${cfg.startup.scriptsDir}/${builtins.toString script.priority}_${name}.sh" = {
text = ''
#!/bin/sh
last_update="$(sha256sum ${sha256sumFile})"
last_update_file=${config.xdg.dataHome}/plasma-manager/last_run_${name}
if [ -f "$last_update_file" ]; then
stored_last_update=$(cat "$last_update_file")
fi
if ! [ "$last_update" = "$stored_last_update" ]; then
success=1
trap 'success=0' ERR
${text}
if [ $success -eq 1 ]; then
echo "$last_update" > "$last_update_file"
${builtins.concatStringsSep "\n" (map (s: "echo ${s} >> ${config.xdg.dataHome}/plasma-manager/services_to_restart") script.restartServices)}
fi
fi
${
if script.runAlways then
(createScriptContentRunAlways name text)
else
(createScriptContentRunOnce name sha256sumFile script text)
}
'';
executable = true;
};
@ -102,81 +134,114 @@ in
};
};
config.xdg = lib.mkIf
(cfg.enable &&
(builtins.length (builtins.attrNames cfg.startup.startupScript) != 0 ||
(builtins.length (builtins.attrNames cfg.startup.desktopScript)) != 0))
{
dataFile = lib.mkMerge [
# Autostart scripts
(lib.mkMerge
(lib.mapAttrsToList
(name: script: createScriptContent name "$0" script script.text)
cfg.startup.startupScript))
# Desktop scripts
(lib.mkMerge
((lib.mapAttrsToList
(name: script:
let layoutScriptPath = "${config.xdg.dataHome}/plasma-manager/${cfg.startup.dataDir}/desktop_script_${name}.js";
in createScriptContent "desktop_script_${name}" layoutScriptPath script
''
${script.preCommands}
qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.evaluateScript "$(cat ${layoutScriptPath})"
${script.postCommands}
'')
cfg.startup.desktopScript) ++
(lib.mapAttrsToList
(name: content: {
config.xdg =
lib.mkIf
(
cfg.enable
&& (
builtins.length (builtins.attrNames cfg.startup.startupScript) != 0
|| (builtins.length (builtins.attrNames cfg.startup.desktopScript)) != 0
)
)
{
dataFile = lib.mkMerge [
# Autostart scripts
(lib.mkMerge (
lib.mapAttrsToList (
name: script: createScriptContent "script_${name}" "$0" script script.text
) cfg.startup.startupScript
))
# Desktop scripts
(lib.mkMerge (
(lib.mapAttrsToList (
name: script:
let
layoutScriptPath = "${config.xdg.dataHome}/plasma-manager/${cfg.startup.dataDir}/desktop_script_${name}.js";
in
createScriptContent "desktop_script_${name}" layoutScriptPath script ''
${script.preCommands}
qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.evaluateScript "$(cat ${layoutScriptPath})"
${script.postCommands}
''
) cfg.startup.desktopScript)
++ (lib.mapAttrsToList (name: content: {
"plasma-manager/${cfg.startup.dataDir}/desktop_script_${name}.js" = {
text = content.text;
};
})
cfg.startup.desktopScript)))
# Datafiles
(lib.mkMerge
(lib.mapAttrsToList
(name: content: {
}) cfg.startup.desktopScript)
))
# Datafiles
(lib.mkMerge (
lib.mapAttrsToList (name: content: {
"plasma-manager/${cfg.startup.dataDir}/${name}" = {
text = content;
};
})
cfg.startup.dataFile)
)
# Autostart script runner
{
"plasma-manager/${topScriptName}" = {
text = ''
#!/bin/sh
}) cfg.startup.dataFile
))
# Autostart script runner
{
"plasma-manager/${topScriptName}" = {
text = ''
#!/bin/sh
services_restart_file="${config.xdg.dataHome}/plasma-manager/services_to_restart"
services_restart_file="${config.xdg.dataHome}/plasma-manager/services_to_restart"
# Reset the file keeping track of which scripts to restart.
# Technically can be put at the end as well (maybe better, at
# least assuming the file hasn't been tampered with of some sort).
if [ -f $services_restart_file ]; then rm $services_restart_file; fi
# Reset the file keeping track of which scripts to restart.
# Technically can be put at the end as well (maybe better, at
# least assuming the file hasn't been tampered with of some sort).
if [ -f $services_restart_file ]; then rm $services_restart_file; fi
for script in ${config.xdg.dataHome}/plasma-manager/${cfg.startup.scriptsDir}/*.sh; do
for script in ${config.xdg.dataHome}/plasma-manager/${cfg.startup.scriptsDir}/*.sh; do
[ -x "$script" ] && $script
done
# Restart the services
if [ -f $services_restart_file ]; then
for service in $(sort $services_restart_file | uniq); do
systemctl --user restart $service
done
fi
'';
executable = true;
};
}
];
configFile."autostart/plasma-manager-autostart.desktop".text = ''
[Desktop Entry]
Type=Application
Name=Plasma Manager theme application
Exec=${config.xdg.dataHome}/plasma-manager/${topScriptName}
X-KDE-autostart-condition=ksmserver
'';
};
# Restart the services
if [ -f $services_restart_file ]; then
for service in $(sort $services_restart_file | uniq); do
systemctl --user restart $service
done
fi
'';
executable = true;
};
}
];
configFile."autostart/plasma-manager-autostart.desktop".text = ''
[Desktop Entry]
Type=Application
Name=Plasma Manager theme application
Exec=${config.xdg.dataHome}/plasma-manager/${topScriptName}
X-KDE-autostart-condition=ksmserver
'';
};
# Due to the fact that running certain desktop-scripts can reset what has
# been applied by other desktop-script (for example running the panel
# desktop-script will reset the wallpaper), we make it so that if any of the
# desktop-scripts have been modified, that we must re-run all the
# desktop-scripts, not just the ones who have been changed.
config.programs.plasma.startup.startupScript."reset_lastrun_desktopscripts" =
lib.mkIf (cfg.startup.desktopScript != { })
{
text = ''
should_reset=0
for ds in ${config.xdg.dataHome}/plasma-manager/data/desktop_script_*.js; do
ds_name="$(basename $ds)"
ds_name="''${ds_name%.js}"
ds_shafile="${config.xdg.dataHome}/plasma-manager/last_run_"$ds_name
if ! [ -f "$ds_shafile" ]; then
echo "Resetting desktop-script last_run-files since $ds_name is a new desktop-script"
should_reset=1
elif ! [ "$(cat $ds_shafile)" = "$(sha256sum $ds)" ]; then
echo "Resetting desktop-script last_run-files since $ds_name has changed content"
should_reset=1
fi
done
[ $should_reset = 1 ] && rm ${config.xdg.dataHome}/plasma-manager/last_run_desktop_script_*
'';
runAlways = true;
};
}

View File

@ -0,0 +1,64 @@
{ lib, ... }:
let
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
mkBoolOption =
description:
lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
inherit description;
};
in
{
appMenu = {
opts = {
position = lib.mkOption {
type = positionType;
example = {
horizontal = 100;
vertical = 300;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = lib.mkOption {
type = sizeType;
example = {
width = 500;
height = 50;
};
description = "The size of the widget. (Only for desktop widget)";
};
compactView = mkBoolOption "Whether to show the app menu in a compact view";
settings = lib.mkOption {
type = configValueType;
default = null;
example = {
Appearance = {
compactView = true;
};
};
description = ''
Extra configuration for the widget
'';
apply = settings: if settings == null then { } else settings;
};
};
convert =
{
position,
size,
compactView,
settings,
}:
{
name = "org.kde.plasma.appmenu";
config = lib.recursiveUpdate {
General = lib.filterAttrs (_: v: v != null) { inherit compactView; };
} settings;
};
};
}

View File

@ -1,14 +1,19 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
mkBoolOption = description: lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
inherit description;
};
mkBoolOption =
description:
lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
inherit description;
};
convertHorizontalAlignment = horizontalAlignment:
convertHorizontalAlignment =
horizontalAlignment:
let
mappings = {
left = 1;
@ -17,9 +22,13 @@ let
justify = 8;
};
in
if horizontalAlignment == null then
null
else
mappings.${horizontalAlignment} or (throw "Invalid enum value: ${horizontalAlignment}");
convertVerticalAlignment = verticalAlignment:
convertVerticalAlignment =
verticalAlignment:
let
mappings = {
top = 1;
@ -28,14 +37,17 @@ let
baseline = 256;
};
in
if verticalAlignment == null then
null
else
mappings.${verticalAlignment} or (throw "Invalid enum value: ${verticalAlignment}");
getIndexFromEnum = enum: value:
if value == null
then null
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex
(x: x == value)
lib.lists.findFirstIndex (x: x == value)
(throw "getIndexFromEnum (application-title-bar widget): Value ${value} isn't present in the enum. This is a bug")
enum;
@ -43,8 +55,15 @@ let
options = {
bold = mkBoolOption "Enable bold text.";
fit =
let enumVals = [ "fixedSize" "horizontalFit" "verticalFit" "fit" ];
in mkOption {
let
enumVals = [
"fixedSize"
"horizontalFit"
"verticalFit"
"fit"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "fixedSize";
@ -86,9 +105,14 @@ let
titleReplacementType = types.submodule {
options = {
type =
let enumVals = [ "string" "regexp" ];
in mkOption {
type =
let
enumVals = [
"string"
"regexp"
];
in
mkOption {
type = types.enum enumVals;
default = null;
example = "string";
@ -113,6 +137,22 @@ in
description = "KDE plasmoid with window title and buttons";
opts = {
position = mkOption {
type = positionType;
example = {
horizontal = 100;
vertical = 300;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = mkOption {
type = sizeType;
example = {
width = 500;
height = 50;
};
description = "The size of the widget. (Only for desktop widget)";
};
layout = {
widgetMargins = mkOption {
type = types.nullOr types.ints.unsigned;
@ -125,20 +165,42 @@ in
description = "The spacing between elements.";
};
horizontalAlignment = mkOption {
type = types.enum [ "left" "right" "center" "justify" ];
default = "left";
type = types.nullOr (
types.enum [
"left"
"right"
"center"
"justify"
]
);
default = null;
example = "left";
description = "The horizontal alignment of the widget.";
apply = convertHorizontalAlignment;
};
verticalAlignment = mkOption {
type = types.enum [ "top" "center" "bottom" "baseline" ];
default = "center";
type = types.nullOr (
types.enum [
"top"
"center"
"bottom"
"baseline"
]
);
default = null;
example = "center";
description = "The vertical alignment of the widget.";
apply = convertVerticalAlignment;
};
showDisabledElements =
let enumVals = [ "deactivated" "hideKeepSpace" "hide" ];
in mkOption {
let
enumVals = [
"deactivated"
"hideKeepSpace"
"hide"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "deactivated";
@ -147,17 +209,21 @@ in
};
fillFreeSpace = mkBoolOption "Whether the widget should fill the free space on the panel.";
elements = mkOption {
type = types.nullOr (types.listOf (types.enum [
"windowCloseButton"
"windowMinimizeButton"
"windowMaximizeButton"
"windowKeepAboveButton"
"windowKeepBelowButton"
"windowShadeButton"
"windowTitle"
"windowIcon"
"spacer"
]));
type = types.nullOr (
types.listOf (
types.enum [
"windowCloseButton"
"windowMinimizeButton"
"windowMaximizeButton"
"windowKeepAboveButton"
"windowKeepBelowButton"
"windowShadeButton"
"windowTitle"
"windowIcon"
"spacer"
]
)
);
default = null;
example = [ "windowTitle" ];
description = ''
@ -167,8 +233,15 @@ in
};
windowControlButtons = {
iconSource =
let enumVals = [ "plasma" "breeze" "aurorae" "oxygen" ];
in mkOption {
let
enumVals = [
"plasma"
"breeze"
"aurorae"
"oxygen"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "plasma";
@ -223,11 +296,13 @@ in
size = 11;
};
description = "The font settings of the window title.";
apply = font: lib.optionalAttrs (font != null) {
windowTitleFontBold = font.bold;
windowTitleFontSize = font.size;
windowTitleFontSizeMode = font.fit;
};
apply =
font:
lib.optionalAttrs (font != null) {
windowTitleFontBold = font.bold;
windowTitleFontSize = font.size;
windowTitleFontSizeMode = font.fit;
};
};
hideEmptyTitle = mkBoolOption "Whether to hide the window title when it's empty.";
undefinedWindowTitle = mkOption {
@ -236,8 +311,15 @@ in
description = "The text to show when the window title is undefined.";
};
source =
let enumVals = [ "appName" "decoration" "genericAppName" "alwaysUndefined" ];
in mkOption {
let
enumVals = [
"appName"
"decoration"
"genericAppName"
"alwaysUndefined"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "appName";
@ -261,28 +343,34 @@ in
bottom = 0;
};
description = "The margins around the window title.";
apply = margins: lib.optionalAttrs (margins != null) {
windowTitleMarginsLeft = margins.left;
windowTitleMarginsRight = margins.right;
windowTitleMarginsTop = margins.top;
windowTitleMarginsBottom = margins.bottom;
};
apply =
margins:
lib.optionalAttrs (margins != null) {
windowTitleMarginsLeft = margins.left;
windowTitleMarginsRight = margins.right;
windowTitleMarginsTop = margins.top;
windowTitleMarginsBottom = margins.bottom;
};
};
};
overrideForMaximized = {
enable = mkBoolOption "Whether to override the settings for maximized windows.";
elements = mkOption {
type = types.nullOr (types.listOf (types.enum [
"windowCloseButton"
"windowMinimizeButton"
"windowMaximizeButton"
"windowKeepAboveButton"
"windowKeepBelowButton"
"windowShadeButton"
"windowTitle"
"windowIcon"
"spacer"
]));
type = types.nullOr (
types.listOf (
types.enum [
"windowCloseButton"
"windowMinimizeButton"
"windowMaximizeButton"
"windowKeepAboveButton"
"windowKeepBelowButton"
"windowShadeButton"
"windowTitle"
"windowIcon"
"spacer"
]
)
);
default = null;
example = [ "windowTitle" ];
description = ''
@ -291,7 +379,12 @@ in
};
source =
let
enumVals = [ "appName" "decoration" "genericAppName" "alwaysUndefined" ];
enumVals = [
"appName"
"decoration"
"genericAppName"
"alwaysUndefined"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
@ -310,8 +403,14 @@ in
};
behavior = {
activeTaskSource =
let enumVals = [ "activeTask" "lastActiveTask" "lastActiveMaximized" ];
in mkOption {
let
enumVals = [
"activeTask"
"lastActiveTask"
"lastActiveMaximized"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "activeTask";
@ -431,14 +530,16 @@ in
}
];
description = "The replacements for the window title.";
apply = replacements: lib.optionalAttrs (replacements != null) {
titleReplacementsPatterns = map (r: r.originalTitle) replacements;
titleReplacementsTemplates = map (r: r.newTitle) replacements;
titleReplacementsTypes = map (r: r.type) replacements;
};
apply =
replacements:
lib.optionalAttrs (replacements != null) {
titleReplacementsPatterns = map (r: r.originalTitle) replacements;
titleReplacementsTemplates = map (r: r.newTitle) replacements;
titleReplacementsTypes = map (r: r.type) replacements;
};
};
settings = mkOption {
type = with types; nullOr (attrsOf (attrsOf (either (oneOf [ bool float int str ]) (listOf (oneOf [ bool float int str ])))));
type = configValueType;
default = null;
example = {
Appearance = {
@ -450,21 +551,25 @@ in
See available options at https://github.com/antroids/application-title-bar/blob/main/package/contents/config/main.xml
'';
apply = settings: if settings == null then {} else settings;
apply = settings: if settings == null then { } else settings;
};
};
convert =
{ layout
, windowControlButtons
, windowTitle
, overrideForMaximized
, behavior
, mouseAreaDrag
, mouseAreaClick
, mouseAreaWheel
, titleReplacements
, settings
}: {
{
position,
size,
layout,
windowControlButtons,
windowTitle,
overrideForMaximized,
behavior,
mouseAreaDrag,
mouseAreaClick,
mouseAreaWheel,
titleReplacements,
settings,
}:
{
name = "com.github.antroids.application-title-bar";
config = lib.recursiveUpdate {
Appearance = lib.filterAttrs (_: v: v != null) (
@ -500,42 +605,40 @@ in
// windowTitle.font
// windowTitle.margins
);
Behavior = lib.filterAttrs (_: v: v != null) (
{
# Behavior
widgetActiveTaskSource = behavior.activeTaskSource;
widgetActiveTaskFilterByActivity = behavior.filterByActivity;
widgetActiveTaskFilterByScreen = behavior.filterByScreen;
widgetActiveTaskFilterByVirtualDesktop = behavior.filterByVirtualDesktop;
widgetActiveTaskFilterNotMaximized = behavior.disableForNotMaximized;
disableButtonsForNotHoveredWidget = behavior.disableButtonsForNotHovered;
Behavior = lib.filterAttrs (_: v: v != null) ({
# Behavior
widgetActiveTaskSource = behavior.activeTaskSource;
widgetActiveTaskFilterByActivity = behavior.filterByActivity;
widgetActiveTaskFilterByScreen = behavior.filterByScreen;
widgetActiveTaskFilterByVirtualDesktop = behavior.filterByVirtualDesktop;
widgetActiveTaskFilterNotMaximized = behavior.disableForNotMaximized;
disableButtonsForNotHoveredWidget = behavior.disableButtonsForNotHovered;
# Mouse area drag
windowTitleDragEnabled = mouseAreaDrag.enable;
windowTitleDragOnlyMaximized = mouseAreaDrag.onlyMaximized;
windowTitleDragThreshold = mouseAreaDrag.threshold;
widgetMouseAreaLeftDragAction = mouseAreaDrag.leftDragAction;
widgetMouseAreaMiddleDragAction = mouseAreaDrag.middleDragAction;
# Mouse area drag
windowTitleDragEnabled = mouseAreaDrag.enable;
windowTitleDragOnlyMaximized = mouseAreaDrag.onlyMaximized;
windowTitleDragThreshold = mouseAreaDrag.threshold;
widgetMouseAreaLeftDragAction = mouseAreaDrag.leftDragAction;
widgetMouseAreaMiddleDragAction = mouseAreaDrag.middleDragAction;
# Mouse area click
widgetMouseAreaClickEnabled = mouseAreaClick.enable;
widgetMouseAreaLeftClickAction = mouseAreaClick.leftButtonClick;
widgetMouseAreaLeftDoubleClickAction = mouseAreaClick.leftButtonDoubleClick;
widgetMouseAreaLeftLongPressAction = mouseAreaClick.leftButtonLongClick;
widgetMouseAreaMiddleClickAction = mouseAreaClick.middleButtonClick;
widgetMouseAreaMiddleDoubleClickAction = mouseAreaClick.middleButtonDoubleClick;
widgetMouseAreaMiddleLongPressAction = mouseAreaClick.middleButtonLongClick;
# Mouse area click
widgetMouseAreaClickEnabled = mouseAreaClick.enable;
widgetMouseAreaLeftClickAction = mouseAreaClick.leftButtonClick;
widgetMouseAreaLeftDoubleClickAction = mouseAreaClick.leftButtonDoubleClick;
widgetMouseAreaLeftLongPressAction = mouseAreaClick.leftButtonLongClick;
widgetMouseAreaMiddleClickAction = mouseAreaClick.middleButtonClick;
widgetMouseAreaMiddleDoubleClickAction = mouseAreaClick.middleButtonDoubleClick;
widgetMouseAreaMiddleLongPressAction = mouseAreaClick.middleButtonLongClick;
# Mouse area wheel
widgetMouseAreaWheelEnabled = mouseAreaWheel.enable;
widgetMouseAreaWheelFirstEventDistance = mouseAreaWheel.firstEventDistance;
widgetMouseAreaWheelNextEventDistance = mouseAreaWheel.nextEventDistance;
widgetMouseAreaWheelUpAction = mouseAreaWheel.wheelUp;
widgetMouseAreaWheelDownAction = mouseAreaWheel.wheelDown;
widgetMouseAreaWheelLeftAction = mouseAreaWheel.wheelLeft;
widgetMouseAreaWheelRightAction = mouseAreaWheel.wheelRight;
}
);
# Mouse area wheel
widgetMouseAreaWheelEnabled = mouseAreaWheel.enable;
widgetMouseAreaWheelFirstEventDistance = mouseAreaWheel.firstEventDistance;
widgetMouseAreaWheelNextEventDistance = mouseAreaWheel.nextEventDistance;
widgetMouseAreaWheelUpAction = mouseAreaWheel.wheelUp;
widgetMouseAreaWheelDownAction = mouseAreaWheel.wheelDown;
widgetMouseAreaWheelLeftAction = mouseAreaWheel.wheelLeft;
widgetMouseAreaWheelRightAction = mouseAreaWheel.wheelRight;
});
TitleReplacements = titleReplacements;
} settings;
};

View File

@ -1,9 +1,30 @@
{ lib, ... }: {
{ lib, ... }:
let
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
in
{
battery = {
description = "The battery indicator widget.";
# See https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/batterymonitor/package/contents/config/main.xml for the accepted raw options
opts = {
position = lib.mkOption {
type = positionType;
example = {
horizontal = 250;
vertical = 50;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = lib.mkOption {
type = sizeType;
example = {
width = 500;
height = 500;
};
description = "The size of the widget. (Only for desktop widget)";
};
showPercentage = lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
@ -11,24 +32,29 @@
description = "Enable to show the battery percentage as a small label over the battery icon.";
};
settings = lib.mkOption {
type = with lib.types; nullOr (attrsOf (attrsOf (either (oneOf [ bool float int str ]) (listOf (oneOf [ bool float int str ])))));
type = configValueType;
default = null;
example = {
General = {
showPercentage = true;
};
};
apply = settings: if settings == null then {} else settings;
apply = settings: if settings == null then { } else settings;
};
};
convert = { showPercentage, settings }: {
name = "org.kde.plasma.battery";
config = lib.recursiveUpdate {
General = lib.filterAttrs (_: v: v != null) {
inherit showPercentage;
};
} settings;
};
convert =
{
position,
size,
showPercentage,
settings,
}:
{
name = "org.kde.plasma.battery";
config = lib.recursiveUpdate {
General = lib.filterAttrs (_: v: v != null) { inherit showPercentage; };
} settings;
};
};
}

View File

@ -1,28 +1,68 @@
{ lib, ... } @ args:
{ lib, ... }@args:
let
args' = args // {
widgets = self;
};
sources = lib.attrsets.mergeAttrsList (map (s: import s args') [
./application-title-bar.nix
./battery.nix
./digital-clock.nix
./icon-tasks.nix
./kickoff.nix
./plasmusic-toolbar.nix
./system-monitor.nix
./system-tray.nix
]);
sources = lib.attrsets.mergeAttrsList (
map (s: import s args') [
./app-menu.nix
./application-title-bar.nix
./battery.nix
./digital-clock.nix
./icon-tasks.nix
./keyboard-layout.nix
./kicker.nix
./kickerdash.nix
./kickoff.nix
./panel-spacer.nix
./plasma-panel-colorizer.nix
./plasmusic-toolbar.nix
./system-monitor.nix
./system-tray.nix
]
);
positionType = lib.types.submodule {
options = {
horizontal = lib.mkOption {
type = lib.types.ints.unsigned;
example = 500;
description = "The horizontal position of the widget.";
};
vertical = lib.mkOption {
type = lib.types.ints.unsigned;
example = 500;
description = "The vertical position of the widget.";
};
};
};
sizeType = lib.types.submodule {
options = {
width = lib.mkOption {
type = lib.types.ints.unsigned;
example = 500;
description = "The width of the widget.";
};
height = lib.mkOption {
type = lib.types.ints.unsigned;
example = 500;
description = "The height of the widget.";
};
};
};
compositeWidgetType = lib.pipe sources [
(builtins.mapAttrs
(_: s: lib.mkOption {
(builtins.mapAttrs (
_: s:
lib.mkOption {
inherit (s) description;
type = lib.types.submodule (submoduleArgs: {
options = if builtins.isFunction s.opts then s.opts submoduleArgs else s.opts;
});
}))
}
))
lib.types.attrTag
];
@ -39,7 +79,62 @@ let
example = {
General.icon = "nix-snowflake-white";
};
description = ''
description = ''
Configuration options for the widget.
See https://develop.kde.org/docs/plasma/scripting/keys/ for an (incomplete) list of options
that can be set here.
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
example = ''
(widget) => {
widget.currentConfigGroup = ["General"];
widget.writeConfig("title", "My widget");
}
'';
description = ''
Extra configuration for the widget in JavaScript.
Should be a lambda/anonymous function that takes the widget as its sole argument,
which can then be called by the script.
'';
};
};
};
desktopSimpleWidgetType = lib.types.submodule {
options = {
name = lib.mkOption {
type = lib.types.str;
example = "org.kde.plasma.kickoff";
description = "The name of the widget to add.";
};
position = lib.mkOption {
type = positionType;
example = {
horizontal = 500;
vertical = 500;
};
description = "The position of the widget.";
};
size = lib.mkOption {
type = sizeType;
example = {
width = 500;
height = 500;
};
description = "The size of the widget.";
};
config = lib.mkOption {
type = (import ./lib.nix (args // { widgets = self; })).configValueType;
default = null;
example = {
General.icon = "nix-snowflake-white";
};
description = ''
Configuration options for the widget.
See https://develop.kde.org/docs/plasma/scripting/keys/ for an (incomplete) list of options
@ -68,15 +163,72 @@ let
isKnownWidget = lib.flip builtins.hasAttr sources;
self = {
inherit isKnownWidget;
inherit isKnownWidget positionType sizeType;
type = lib.types.oneOf [ lib.types.str compositeWidgetType simpleWidgetType ];
type = lib.types.oneOf [
lib.types.str
compositeWidgetType
simpleWidgetType
];
desktopType = lib.types.oneOf [
compositeWidgetType
desktopSimpleWidgetType
];
lib = import ./lib.nix (args // { widgets = self; });
convert = widget:
desktopConvert =
widget:
let
inherit (builtins) length head attrNames mapAttrs isAttrs isString;
inherit (builtins)
length
head
attrNames
mapAttrs
isAttrs
isString
;
keys = attrNames widget;
type = head keys;
base = {
config = null;
extraConfig = "";
};
converters = mapAttrs (_: s: s.convert) sources;
in
if isAttrs widget && length keys == 1 && isKnownWidget type then
let
convertedWidget = converters.${type} widget.${type};
in
base
// convertedWidget
// {
position =
if isAttrs widget.${type}.position then
widget.${type}.position
else
(throw "Desktop widget requires a position");
size =
if isAttrs widget.${type}.size then
widget.${type}.size
else
(throw "Desktop widget requires a size");
}
else
widget; # not a known composite type
convert =
widget:
let
inherit (builtins)
length
head
attrNames
mapAttrs
isAttrs
isString
;
keys = attrNames widget;
type = head keys;
@ -90,7 +242,8 @@ let
base // { name = widget; }
else if isAttrs widget && length keys == 1 && isKnownWidget type then
base // converters.${type} widget.${type}
else widget; # not a known composite type
else
widget; # not a known composite type
};
in
self

View File

@ -1,19 +1,23 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
mkBoolOption = description: mkOption {
type = with types; nullOr bool;
default = null;
inherit description;
};
mkBoolOption =
description:
mkOption {
type = with types; nullOr bool;
default = null;
inherit description;
};
getIndexFromEnum = enum: value:
if value == null
then null
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex
(x: x == value)
lib.lists.findFirstIndex (x: x == value)
(throw "getIndexFromEnum (digital-clock widget): Value ${value} isn't present in the enum. This is a bug")
enum;
@ -51,30 +55,57 @@ in
opts = {
# See https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/digital-clock/package/contents/config/main.xml for the accepted raw options
position = mkOption {
type = positionType;
example = {
horizontal = 250;
vertical = 50;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = mkOption {
type = sizeType;
example = {
width = 500;
height = 500;
};
description = "The size of the widget. (Only for desktop widget)";
};
date = {
enable = mkBoolOption "Enable showing the current date.";
format =
let
enumVals = [ "shortDate" "longDate" "isoDate" ];
enumVals = [
"shortDate"
"longDate"
"isoDate"
];
in
mkOption {
type = with types; nullOr (either (enum enumVals) (submodule {
options.custom = mkOption {
type = str;
example = "ddd d";
description = "The custom date format to use.";
};
}));
type =
with types;
nullOr (
either (enum enumVals) (submodule {
options.custom = mkOption {
type = str;
example = "ddd d";
description = "The custom date format to use.";
};
})
);
default = null;
example = { custom = "d.MM.yyyy"; };
example = {
custom = "d.MM.yyyy";
};
description = ''
The date format used for this clock.
Could be as a short date, long date, a ISO 8601 date (yyyy-mm-dd), or a custom date format.
Short and long date formats are locale-dependent.
'';
apply = f:
apply =
f:
if f == null then
{ }
else if f ? custom then
@ -84,12 +115,17 @@ in
}
else
{ dateFormat = f; };
};
position =
let enumVals = [ "adaptive" "besideTime" "belowTime" ];
in mkOption {
let
enumVals = [
"adaptive"
"besideTime"
"belowTime"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "belowTime";
@ -104,8 +140,14 @@ in
time = {
showSeconds =
let enumVals = [ "never" "onlyInTooltip" "always" ];
in mkOption {
let
enumVals = [
"never"
"onlyInTooltip"
"always"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "always";
@ -117,8 +159,14 @@ in
apply = getIndexFromEnum enumVals;
};
format =
let enumVals = [ "12h" "default" "24h" ];
in mkOption {
let
enumVals = [
"12h"
"default"
"24h"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "24h";
@ -135,7 +183,10 @@ in
selected = mkOption {
type = with types; nullOr (listOf str);
default = null;
example = [ "Europe/Berlin" "Asia/Shanghai" ];
example = [
"Europe/Berlin"
"Asia/Shanghai"
];
description = ''
The timezones that are configured for this clock.
@ -153,8 +204,14 @@ in
};
changeOnScroll = mkBoolOption "Allow changing the displayed timezone by scrolling on the widget with the mouse wheel.";
format =
let enumVals = [ "code" "city" "offset" ];
in mkOption {
let
enumVals = [
"code"
"city"
"offset"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "code";
@ -173,8 +230,18 @@ in
calendar = {
firstDayOfWeek =
let enumVals = [ "sunday" "monday" "tuesday" "wednesday" "thursday" "friday" "saturday" ];
in mkOption {
let
enumVals = [
"sunday"
"monday"
"tuesday"
"wednesday"
"thursday"
"friday"
"saturday"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "monday";
@ -206,7 +273,8 @@ in
If null, then it will use the system font and automatically expand to fill available space.
'';
apply = font:
apply =
font:
{
autoFontAndSize = (font == null);
}
@ -220,7 +288,7 @@ in
};
};
settings = mkOption {
type = with types; nullOr (attrsOf (attrsOf (either (oneOf [ bool float int str ]) (listOf (oneOf [ bool float int str ])))));
type = configValueType;
default = null;
example = {
Appearance = {
@ -232,18 +300,22 @@ in
See https://develop.kde.org/docs/plasma/scripting/keys/ for an list of options
'';
apply = settings: if settings == null then {} else settings;
apply = settings: if settings == null then { } else settings;
};
};
convert =
{ date
, time
, timeZone
, calendar
, font
, settings
}: {
{
position,
size,
date,
time,
timeZone,
calendar,
font,
settings,
}:
{
name = "org.kde.plasma.digitalclock";
config = lib.recursiveUpdate {
Appearance = lib.filterAttrs (_: v: v != null) (

View File

@ -1,14 +1,19 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
mkBoolOption = description: mkOption {
type = with types; nullOr bool;
default = null;
inherit description;
};
mkBoolOption =
description:
mkOption {
type = with types; nullOr bool;
default = null;
inherit description;
};
convertSpacing = spacing:
convertSpacing =
spacing:
let
mappings = {
small = 0;
@ -16,33 +21,60 @@ let
large = 3;
};
in
mappings.${spacing} or (throw "Invalid spacing: ${spacing}");
getIndexFromEnum = enum: value:
if value == null
then null
if spacing == null then
null
else if builtins.isString spacing then
mappings.${spacing} or (throw "Invalid spacing: ${spacing}")
else
lib.lists.findFirstIndex
(x: x == value)
spacing;
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex (x: x == value)
(throw "getIndexFromEnum (icon-tasks widget): Value ${value} isn't present in the enum. This is a bug")
enum;
positionToReverse = position:
positionToReverse =
position:
let
mappings = { left = true; right = false; };
mappings = {
left = true;
right = false;
};
in
mappings.${position} or (throw "Invalid position: ${position}");
if position == null then null else mappings.${position} or (throw "Invalid position: ${position}");
in
{
iconTasks = {
description = "Icons Only Task Manager shows tasks only by their icon and not by icon and title of the window opened.";
opts = {
position = mkOption {
type = positionType;
example = {
horizontal = 250;
vertical = 50;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = mkOption {
type = sizeType;
example = {
width = 500;
height = 500;
};
description = "The size of the widget. (Only for desktop widget)";
};
launchers = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [ "applications:org.kde.dolphin.desktop" "applications:org.kde.konsole.desktop" ];
example = [
"applications:org.kde.dolphin.desktop"
"applications:org.kde.konsole.desktop"
];
description = "The list of launcher tasks on the widget. Usually .desktop file or executable URLs. Special URLs such as preferred://browser that expand to default applications are supported.";
};
appearance = {
@ -52,11 +84,17 @@ in
fill = mkBoolOption "Whether task manager should occupy all available space.";
rows = {
multirowView = mkOption {
type = types.enum [ "never" "lowSpace" "always" ];
type = types.enum [
"never"
"lowSpace"
"always"
];
default = "never";
example = "lowSpace";
description = "When to use multi-row view.";
apply = multirowView: if multirowView == "never" then false else (if multirowView == "always" then true else null);
apply =
multirowView:
if multirowView == "never" then false else (if multirowView == "always" then true else null);
};
maximum = mkOption {
type = types.nullOr types.ints.positive;
@ -66,8 +104,17 @@ in
};
};
iconSpacing = mkOption {
type = types.enum [ "small" "medium" "large" ];
default = "medium";
type = types.nullOr (
types.oneOf [
(types.enum [
"small"
"medium"
"large"
])
types.ints.positive
]
);
default = null;
example = "small";
description = "The spacing between icons.";
apply = convertSpacing;
@ -76,8 +123,13 @@ in
behavior = {
grouping = {
method =
let enumVals = [ "none" "byProgramName" ];
in mkOption {
let
enumVals = [
"none"
"byProgramName"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "none";
@ -85,8 +137,15 @@ in
apply = getIndexFromEnum enumVals;
};
clickAction =
let enumVals = [ "cycle" "showTooltips" "showPresentWindowsEffect" "showTextualList" ];
in mkOption {
let
enumVals = [
"cycle"
"showTooltips"
"showPresentWindowsEffect"
"showTextualList"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "cycle";
@ -95,8 +154,16 @@ in
};
};
sortingMethod =
let enumVals = [ "none" "manually" "alphabetically" "byDesktop" "byActivity" ];
in mkOption {
let
enumVals = [
"none"
"manually"
"alphabetically"
"byDesktop"
"byActivity"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "manually";
@ -105,8 +172,17 @@ in
};
minimizeActiveTaskOnClick = mkBoolOption "Whether to minimize the currently-active task when clicked. If false, clicking on the currently-active task will do nothing.";
middleClickAction =
let enumVals = [ "none" "close" "newInstance" "toggleMinimized" "toggleGrouping" "bringToCurrentDesktop" ];
in mkOption {
let
enumVals = [
"none"
"close"
"newInstance"
"toggleMinimized"
"toggleGrouping"
"bringToCurrentDesktop"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "bringToCurrentDesktop";
@ -121,71 +197,94 @@ in
onlyInCurrentScreen = mkBoolOption "Whether to show only window tasks that are on the same screen as the widget.";
onlyInCurrentDesktop = mkBoolOption "Whether to only show tasks that are on the current virtual desktop.";
onlyInCurrentActivity = mkBoolOption "Whether to show only tasks that are on the current activity.";
onlyMinimized = mkBoolOption "Whether to show only window tasks that are minimized.";
onlyMinimized = mkOption {
type = types.nullOr types.bool;
default = null;
example = true;
description = "Whether to show only window tasks that are minimized.";
apply =
onlyMinimized:
if onlyMinimized == null then
null
else if onlyMinimized == true then
1
else
0;
};
};
unhideOnAttentionNeeded = mkBoolOption "Whether to unhide if a window wants attention.";
newTasksAppearOn = mkOption {
type = types.enum [ "left" "right" ];
default = "right";
example = "left";
type = types.nullOr (
types.enum [
"left"
"right"
]
);
default = null;
example = "right";
description = "Whether new tasks should appear in the left or right.";
apply = positionToReverse;
};
};
settings = mkOption {
type = with types; nullOr (attrsOf (attrsOf (either (oneOf [ bool float int str ]) (listOf (oneOf [ bool float int str ])))));
type = configValueType;
default = null;
example = {
General = {
launchers = [ "applications:org.kde.dolphin.desktop" "applications:org.kde.konsole.desktop" ];
launchers = [
"applications:org.kde.dolphin.desktop"
"applications:org.kde.konsole.desktop"
];
};
};
description = "Extra configuration options for the widget.";
apply = settings: if settings == null then {} else settings;
apply = settings: if settings == null then { } else settings;
};
};
convert =
{ appearance
, behavior
, launchers
, settings
}: {
{
position,
size,
appearance,
behavior,
launchers,
settings,
}:
{
name = "org.kde.plasma.icontasks";
config = lib.recursiveUpdate {
General = lib.filterAttrs (_: v: v != null) (
{
launchers = launchers;
General = lib.filterAttrs (_: v: v != null) ({
launchers = launchers;
# Appearance
showToolTips = appearance.showTooltips;
highlightWindows = appearance.highlightWindows;
indicateAudioStreams = appearance.indicateAudioStreams;
fill = appearance.fill;
# Appearance
showToolTips = appearance.showTooltips;
highlightWindows = appearance.highlightWindows;
indicateAudioStreams = appearance.indicateAudioStreams;
fill = appearance.fill;
forceStripes = appearance.rows.multirowView;
maxStripes = appearance.rows.maximum;
forceStripes = appearance.rows.multirowView;
maxStripes = appearance.rows.maximum;
iconSpacing = appearance.iconSpacing;
iconSpacing = appearance.iconSpacing;
# Behavior
groupingStrategy = behavior.grouping.method;
groupedTaskVisualization = behavior.grouping.clickAction;
sortingStrategy = behavior.sortingMethod;
minimizeActiveTaskOnClick = behavior.minimizeActiveTaskOnClick;
middleClickAction = behavior.middleClickAction;
# Behavior
groupingStrategy = behavior.grouping.method;
groupedTaskVisualization = behavior.grouping.clickAction;
sortingStrategy = behavior.sortingMethod;
minimizeActiveTaskOnClick = behavior.minimizeActiveTaskOnClick;
middleClickAction = behavior.middleClickAction;
wheelEnabled = behavior.wheel.switchBetweenTasks;
wheelSkipMinimized = behavior.wheel.ignoreMinimizedTasks;
wheelEnabled = behavior.wheel.switchBetweenTasks;
wheelSkipMinimized = behavior.wheel.ignoreMinimizedTasks;
showOnlyCurrentScreen = behavior.showTasks.onlyInCurrentScreen;
showOnlyCurrentDesktop = behavior.showTasks.onlyInCurrentDesktop;
showOnlyCurrentActivity = behavior.showTasks.onlyInCurrentActivity;
showOnlyMinimized = behavior.showTasks.onlyMinimized;
showOnlyCurrentScreen = behavior.showTasks.onlyInCurrentScreen;
showOnlyCurrentDesktop = behavior.showTasks.onlyInCurrentDesktop;
showOnlyCurrentActivity = behavior.showTasks.onlyInCurrentActivity;
showOnlyMinimized = behavior.showTasks.onlyMinimized;
unhideOnAttention = behavior.unhideOnAttentionNeeded;
reverseMode = behavior.newTasksAppearOn;
}
);
unhideOnAttention = behavior.unhideOnAttentionNeeded;
reverseMode = behavior.newTasksAppearOn;
});
} settings;
};
};

View File

@ -0,0 +1,77 @@
{ lib, ... }:
let
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex (x: x == value)
(throw "getIndexFromEnum (keyboard-layout widget): Value ${value} isn't present in the enum. This is a bug")
enum;
in
{
keyboardLayout = {
description = "The keyboard layout indicator widget.";
opts = {
position = lib.mkOption {
type = positionType;
example = {
horizontal = 250;
vertical = 50;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = lib.mkOption {
type = sizeType;
example = {
width = 500;
height = 500;
};
description = "The size of the widget. (Only for desktop widget)";
};
displayStyle =
let
enumVals = [
"label"
"flag"
"labelOverFlag"
];
in
lib.mkOption {
type = with lib.types; nullOr (enum enumVals);
default = null;
example = "labelOverFlag";
description = "Keyboard layout indicator display style.";
apply = getIndexFromEnum enumVals;
};
settings = lib.mkOption {
type = configValueType;
default = null;
example = {
General = {
displayStyle = 1;
};
};
apply = settings: if settings == null then { } else settings;
};
};
convert =
{
position,
size,
displayStyle,
settings,
}:
{
name = "org.kde.plasma.keyboardlayout";
config = lib.recursiveUpdate {
General = lib.filterAttrs (_: v: v != null) { inherit displayStyle; };
} settings;
};
};
}

165
modules/widgets/kicker.nix Normal file
View File

@ -0,0 +1,165 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
mkBoolOption =
description:
mkOption {
type = with types; nullOr bool;
default = null;
example = true;
inherit description;
};
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex (x: x == value)
(throw "getIndexFromEnum (kicker widget): Value ${value} isn't present in the enum. This is a bug")
enum;
checkPath =
path:
if path == null then
null
else if lib.strings.hasPrefix "/" path then
path
else
throw "checkPath (kicker widget): Path ${path} is not an absolute path.";
in
{
kicker = {
description = ''
Kicker is a launcher, which is also known as Application Menu.
Kicker does not have fancy features, like the other launchers,
but provides a tightly arranged interface.
'';
opts = {
position = mkOption {
type = positionType;
example = {
horizontal = 250;
vertical = 50;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = mkOption {
type = sizeType;
example = {
width = 500;
height = 500;
};
description = "The size of the widget. (Only for desktop widget)";
};
icon = mkOption {
type = types.nullOr types.str;
default = null;
example = "start-here-kde-symbolic";
description = "The icon to use for the kickoff button.";
};
customButtonImage = mkOption {
type = types.nullOr types.str;
default = null;
example = "/home/user/pictures/custom-button.png";
description = "The absolute path image to use for the custom button.";
apply = checkPath;
};
applicationNameFormat =
let
enumVals = [
"nameOnly"
"genericNameOnly"
"nameAndGenericName"
"genericNameAndName"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "nameOnly";
description = "The format of the application name to display.";
apply = getIndexFromEnum enumVals;
};
behavior = {
sortAlphabetically = mkBoolOption "Whether to sort the applications alphabetically.";
flattenCategories = mkBoolOption "Whether to flatten top-level menu categories to a single level instead of displaying sub-categories.";
showIconsOnRootLevel = mkBoolOption "Whether to show icons on the root level of the menu.";
};
categories = {
show = {
recentApplications = mkBoolOption "Whether to show recent applications.";
recentFiles = mkBoolOption "Whether to show recent files.";
};
order =
let
enumVals = [
"recentFirst"
"popularFirst"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "recentFirst";
description = "The order in which to show the categories.";
apply = getIndexFromEnum enumVals;
};
};
search = {
alignResultsToBottom = mkBoolOption "Whether to align the search results to the bottom of the screen.";
expandSearchResults = mkBoolOption "Whether to expand the search results to bookmarks, files and emails.";
};
settings = mkOption {
type = configValueType;
default = null;
example = {
General = {
icon = "nix-snowflake-white";
};
};
description = "Extra configuration options for the widget.";
apply = settings: if settings == null then { } else settings;
};
};
convert =
{
position,
size,
icon,
customButtonImage,
applicationNameFormat,
behavior,
categories,
search,
settings,
}:
{
name = "org.kde.plasma.kicker";
config = lib.recursiveUpdate {
General = lib.filterAttrs (_: v: v != null) ({
inherit icon customButtonImage;
inherit (search) alignResultsToBottom;
useCustomButtonImage = (customButtonImage != null);
appNameFormat = applicationNameFormat;
alphaSort = behavior.sortAlphabetically;
limitDepth = behavior.flattenCategories;
showIconsRootLevel = behavior.showIconsOnRootLevel;
showRecentApps = categories.show.recentApplications;
showRecentDocs = categories.show.recentFiles;
recentOrdering = categories.order;
useExtraRunners = search.expandSearchResults;
});
} settings;
};
};
}

View File

@ -0,0 +1,154 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
mkBoolOption =
description:
mkOption {
type = with types; nullOr bool;
default = null;
example = true;
inherit description;
};
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex (x: x == value)
(throw "getIndexFromEnum (kickerdash widget): Value ${value} isn't present in the enum. This is a bug")
enum;
checkPath =
path:
if path == null then
null
else if lib.strings.hasPrefix "/" path then
path
else
throw "checkPath (kickerdash widget): Path ${path} is not an absolute path.";
in
{
kickerdash = {
description = "Application Dashboard (kickerdash) is an alternative launcher which fills the whole desktop.";
opts = {
position = mkOption {
type = positionType;
example = {
horizontal = 250;
vertical = 50;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = mkOption {
type = sizeType;
example = {
width = 500;
height = 500;
};
description = "The size of the widget. (Only for desktop widget)";
};
icon = mkOption {
type = types.nullOr types.str;
default = null;
example = "start-here-kde-symbolic";
description = "The icon to use for the kickoff button.";
};
customButtonImage = mkOption {
type = types.nullOr types.str;
default = null;
example = "/home/user/pictures/custom-button.png";
description = "The absolute path image to use for the custom button.";
apply = checkPath;
};
applicationNameFormat =
let
enumVals = [
"nameOnly"
"genericNameOnly"
"nameAndGenericName"
"genericNameAndName"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "nameOnly";
description = "The format of the application name to display.";
apply = getIndexFromEnum enumVals;
};
behavior = {
sortAlphabetically = mkBoolOption "Whether to sort the applications alphabetically.";
};
categories = {
show = {
recentApplications = mkBoolOption "Whether to show recent applications.";
recentFiles = mkBoolOption "Whether to show recent files.";
};
order =
let
enumVals = [
"recentFirst"
"popularFirst"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "recentFirst";
description = "The order in which to show the categories.";
apply = getIndexFromEnum enumVals;
};
};
search = {
expandSearchResults = mkBoolOption "Whether to expand the search results to bookmarks, files and emails.";
};
settings = mkOption {
type = configValueType;
default = null;
example = {
General = {
icon = "nix-snowflake-white";
};
};
description = "Extra configuration options for the widget.";
apply = settings: if settings == null then { } else settings;
};
};
convert =
{
position,
size,
icon,
customButtonImage,
applicationNameFormat,
behavior,
categories,
search,
settings,
}:
{
name = "org.kde.plasma.kickerdash";
config = lib.recursiveUpdate {
General = lib.filterAttrs (_: v: v != null) ({
inherit icon customButtonImage;
useCustomButtonImage = (customButtonImage != null);
appNameFormat = applicationNameFormat;
alphaSort = behavior.sortAlphabetically;
showRecentApps = categories.show.recentApplications;
showRecentDocs = categories.show.recentFiles;
recentOrdering = categories.order;
useExtraRunners = search.expandSearchResults;
});
} settings;
};
};
}

View File

@ -1,26 +1,37 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
mkBoolOption = description: mkOption {
type = with types; nullOr bool;
default = null;
inherit description;
};
mkBoolOption =
description:
mkOption {
type = with types; nullOr bool;
default = null;
inherit description;
};
getIndexFromEnum = enum: value:
if value == null
then null
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex
(x: x == value)
lib.lists.findFirstIndex (x: x == value)
(throw "getIndexFromEnum (kickoff widget): Value ${value} isn't present in the enum. This is a bug")
enum;
convertSidebarPosition = sidebarPosition:
convertSidebarPosition =
sidebarPosition:
let
mappings = { left = false; right = true; };
mappings = {
left = false;
right = true;
};
in
if sidebarPosition == null then
null
else
mappings.${sidebarPosition} or (throw "Invalid sidebar position: ${sidebarPosition}");
in
{
@ -28,11 +39,32 @@ in
description = "Kickoff is the default application launcher of the Plasma desktop.";
opts = {
position = mkOption {
type = positionType;
example = {
horizontal = 250;
vertical = 50;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = mkOption {
type = sizeType;
example = {
width = 500;
height = 500;
};
description = "The size of the widget. (Only for desktop widget)";
};
icon = mkOption {
type = types.nullOr types.str;
default = null;
example = "start-here-kde-symbolic";
description = "The icon to use for the kickoff button.";
description = ''
The icon to use for the kickoff button.
This can also be used to specify a custom image for the kickoff button.
To do this, set the value to a absolute path to the image file.
'';
};
label = mkOption {
type = types.nullOr types.str;
@ -43,15 +75,25 @@ in
sortAlphabetically = mkBoolOption "Whether to sort menu contents alphabetically or use manual/system sort order.";
compactDisplayStyle = mkBoolOption "Whether to use a compact display style for list items.";
sidebarPosition = mkOption {
type = types.enum [ "left" "right" ];
default = "left";
example = "right";
type = types.nullOr (
types.enum [
"left"
"right"
]
);
default = null;
example = "left";
description = "The position of the sidebar.";
apply = convertSidebarPosition;
};
favoritesDisplayMode =
let enumVals = [ "grid" "list" ];
in mkOption {
let
enumVals = [
"grid"
"list"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "list";
@ -59,8 +101,13 @@ in
apply = getIndexFromEnum enumVals;
};
applicationsDisplayMode =
let enumVals = [ "grid" "list" ];
in mkOption {
let
enumVals = [
"grid"
"list"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "grid";
@ -68,8 +115,15 @@ in
apply = getIndexFromEnum enumVals;
};
showButtonsFor =
let enumVals = [ "power" "session" "custom" "powerAndSession" ];
in mkOption {
let
enumVals = [
"power"
"session"
"custom"
"powerAndSession"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "powerAndSession";
@ -78,50 +132,66 @@ in
};
showActionButtonCaptions = mkBoolOption "Whether to display captions ('shut down', 'log out', etc.) for the footer action buttons";
pin = mkBoolOption "Whether the popup should remain open when another window is activated.";
popupHeight = mkOption {
type = with types; nullOr ints.positive;
default = null;
example = 500;
};
popupWidth = mkOption {
type = with types; nullOr ints.positive;
default = null;
example = 700;
};
settings = mkOption {
type = with types; nullOr (attrsOf (attrsOf (either (oneOf [ bool float int str ]) (listOf (oneOf [ bool float int str ])))));
type = configValueType;
default = null;
example = {
General = {
icon = "nix-snowflake-white";
};
popupHeight = 500;
};
description = "Extra configuration options for the widget.";
apply = settings: if settings == null then {} else settings;
apply = settings: if settings == null then { } else settings;
};
};
convert =
{ icon
, label
, sortAlphabetically
, compactDisplayStyle
, sidebarPosition
, favoritesDisplayMode
, applicationsDisplayMode
, showButtonsFor
, showActionButtonCaptions
, pin
, settings
}: {
{
position,
size,
icon,
label,
sortAlphabetically,
compactDisplayStyle,
sidebarPosition,
favoritesDisplayMode,
applicationsDisplayMode,
showButtonsFor,
showActionButtonCaptions,
pin,
popupHeight,
popupWidth,
settings,
}:
{
name = "org.kde.plasma.kickoff";
config = lib.recursiveUpdate {
General = lib.filterAttrs (_: v: v != null) (
{
icon = icon;
menuLabel = label;
alphaSort = sortAlphabetically;
compactMode = compactDisplayStyle;
paneSwap = sidebarPosition;
favoritesDisplay = favoritesDisplayMode;
applicationsDisplay = applicationsDisplayMode;
primaryActions = showButtonsFor;
showActionButtonCaptions = showActionButtonCaptions;
config = lib.recursiveUpdate (lib.filterAttrsRecursive (_: v: v != null) {
popupHeight = popupHeight;
popupWidth = popupWidth;
# Other useful options
pin = pin;
}
);
} settings;
General = {
inherit icon pin;
menuLabel = label;
alphaSort = sortAlphabetically;
compactMode = compactDisplayStyle;
paneSwap = sidebarPosition;
favoritesDisplay = favoritesDisplayMode;
applicationsDisplay = applicationsDisplayMode;
primaryActions = showButtonsFor;
showActionButtonCaptions = showActionButtonCaptions;
};
}) settings;
};
};
}

View File

@ -5,50 +5,119 @@ let
concatMapStringsSep
concatStringsSep
mapAttrsToList
splitString;
filterAttrs
splitString
;
configValueTypes = with lib.types; oneOf [ bool float int str ];
configValueType = with lib.types; nullOr (attrsOf (attrsOf (either configValueTypes (listOf configValueTypes))));
configValueTypes =
with lib.types;
oneOf [
bool
float
int
str
];
configValueTypeInner = with lib.types; either configValueTypes (listOf configValueTypes);
configValueType =
with lib.types;
nullOr (attrsOf (either configValueTypeInner (attrsOf configValueTypeInner)));
# any value or null -> string -> string
# If value is null, returns the empty string, otherwise returns the provided string
stringIfNotNull = v: optionalString (v != null);
# Converts each datatype into an expression which can be parsed in JavaScript
valToJS = v: if (builtins.isString v) then ''"${v}"'' else if (builtins.isBool v) then (lib.boolToString v) else (builtins.toString v);
valToJS =
v:
if (builtins.isString v) then
''"${v}"''
else if (builtins.isBool v) then
(lib.boolToString v)
else
(builtins.toString v);
# Converts a list of to a single string, that can be parsed as a string list in JavaScript
toJSList = values: ''[${concatMapStringsSep ", " valToJS values}]'';
setWidgetSettings = var: settings:
setWidgetSettings =
var: settings:
let
perConfig = group: key: value: ''${var}.writeConfig("${key}", ${
if builtins.isList value
then toJSList value
else valToJS value
});'';
perConfig =
key: value:
''${var}.writeConfig("${key}", ${
if builtins.isList value then toJSList value else valToJS value
});'';
perGroup = group: configs: ''
${var}.currentConfigGroup = ${toJSList (splitString "/" group)};
${concatStringsSep "\n" (mapAttrsToList (perConfig group) configs)}
${concatStringsSep "\n" (mapAttrsToList perConfig configs)}
'';
groups = (filterAttrs (_: value: builtins.isAttrs value) settings);
topLevel = (filterAttrs (_: value: !builtins.isAttrs value) settings);
in
concatStringsSep "\n" (mapAttrsToList perGroup settings);
concatStringsSep "\n" (
(lib.optional (topLevel != { }) "${var}.currentConfigGroup = [];")
++ (mapAttrsToList perConfig topLevel)
++
addWidgetStmts = containment: var: ws:
(mapAttrsToList perGroup groups)
);
addWidgetStmts =
containment: var: ws:
let
widgetConfigsToStmts = { name, config, ... }: ''
var w = ${var}["${name}"];
${setWidgetSettings "w" config}
'';
widgetConfigsToStmts =
{ name, config, ... }:
''
var w = ${var}["${name}"];
${setWidgetSettings "w" config}
'';
addStmt = { name, config, extraConfig }@widget: ''
${var}["${name}"] = ${containment}.addWidget("${name}");
${stringIfNotNull config (widgetConfigsToStmts widget)}
${lib.optionalString (extraConfig != "") ''
(${extraConfig})(${var}["${name}"]);
''}
'';
addStmt =
{
name,
config,
extraConfig,
}@widget:
''
${var}["${name}"] = ${containment}.addWidget("${name}");
${stringIfNotNull config (widgetConfigsToStmts widget)}
${lib.optionalString (extraConfig != "") ''
(${extraConfig})(${var}["${name}"]);
''}
'';
in
''
const ${var} = {};
${lib.concatMapStringsSep "\n" addStmt ws}
'';
addDesktopWidgetStmts =
containment: var: ws:
let
widgetConfigsToStmts =
{ name, config, ... }:
''
var w = ${var}["${name}"];
${setWidgetSettings "w" config}
'';
addStmt =
{
name,
position,
size,
config,
extraConfig,
}@widget:
''
${var}["${name}"] = ${containment}.addWidget("${name}", ${toString position.horizontal}, ${toString position.vertical}, ${toString size.width}, ${toString size.height});
${stringIfNotNull config (widgetConfigsToStmts widget)}
${lib.optionalString (extraConfig != "") ''
(${extraConfig})(${var}["${name}"]);
''}
'';
in
''
const ${var} = {};
@ -60,5 +129,7 @@ in
stringIfNotNull
setWidgetSettings
addWidgetStmts
configValueType;
addDesktopWidgetStmts
configValueType
;
}

View File

@ -0,0 +1,74 @@
{ lib, ... }:
let
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
mkBoolOption =
description:
lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
example = true;
inherit description;
};
in
{
panelSpacer = {
opts = {
position = lib.mkOption {
type = positionType;
example = {
horizontal = 100;
vertical = 300;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = lib.mkOption {
type = sizeType;
example = {
width = 500;
height = 50;
};
description = "The size of the widget. (Only for desktop widget)";
};
expanding = mkBoolOption "Whether the spacer should expand to fill the available space.";
length = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
example = 50;
description = ''
The length of the spacer.
If expanding is set to true, this value is ignored.
'';
};
settings = lib.mkOption {
type = configValueType;
default = null;
example = {
General = {
expanding = true;
};
};
description = ''
Extra configuration for the widget
'';
apply = settings: if settings == null then { } else settings;
};
};
convert =
{
position,
size,
expanding,
length,
settings,
}:
{
name = "org.kde.plasma.panelspacer";
config = lib.recursiveUpdate {
General = lib.filterAttrs (_: v: v != null) { inherit expanding length; };
} settings;
};
};
}

View File

@ -0,0 +1,925 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
mkBoolOption =
description:
mkOption {
type = types.nullOr types.bool;
default = null;
example = true;
inherit description;
};
systemColors = [
"text"
"disabledText"
"highlightedText"
"activeText"
"link"
"visitedLink"
"negativeText"
"neutralText"
"positiveText"
"background"
"highlight"
"activeBackground"
"linkBackground"
"visitedLinkBackground"
"negativeBackground"
"neutralBackground"
"positiveBackground"
"alternateBackground"
"focus"
"hover"
];
systemColorSets = [
"view"
"window"
"button"
"selection"
"tooltip"
"complementary"
"header"
];
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex (x: x == value)
(throw "getIndexFromEnum (plasma-panel-colorizer widget): Value ${value} isn't present in the enum. This is a bug")
enum;
convertColorList = colors: if colors == null then null else builtins.concatStringsSep " " colors;
convertWidgets =
widgets: if widgets == null then null else "|" + builtins.concatStringsSep "|" widgets;
convertWidgetMarginRules =
rules:
if rules == null then
null
else
let
widgetToString =
widget:
"${widget.widgetId},${toString widget.margin.vertical},${toString widget.margin.horizontal}";
in
builtins.concatStringsSep "|" (map widgetToString rules);
widgetMarginRuleType = types.submodule {
options = {
widgetId = mkOption {
type = types.str;
example = "org.kde.plasma.kickoff";
description = "Widget id";
};
margin = {
vertical = mkOption {
type = types.int;
example = 5;
description = "Vertical margin value";
};
horizontal = mkOption {
type = types.int;
example = 5;
description = "Horizontal margin value";
};
};
};
};
in
{
plasmaPanelColorizer = {
description = "Fully-featured widget to bring Latte-Dock and WM status bar customization features to the default KDE Plasma panel";
opts = {
position = mkOption {
type = positionType;
example = {
horizontal = 250;
vertical = 50;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = mkOption {
type = sizeType;
example = {
width = 500;
height = 500;
};
description = "The size of the widget. (Only for desktop widget)";
};
general = {
enable = mkBoolOption "Whether to enable the widget";
hideWidget = mkBoolOption "Whether to hide the widget";
};
presetAutoLoading = {
normal = mkOption {
type = types.nullOr types.str;
default = null;
example = "Normal preset";
description = "Preset to load when panel is on 'normal' state";
};
floating = mkOption {
type = types.nullOr types.str;
default = null;
example = "Floating preset";
description = "Preset to load when panel is on 'floating' state";
};
touchingWindow = mkOption {
type = types.nullOr types.str;
default = null;
example = "Touching window preset";
description = "Preset to load when panel is on 'touching window' state";
};
maximized = mkOption {
type = types.nullOr types.str;
default = null;
example = "Maximized preset";
description = "Preset to load when panel is on 'maximized' state";
};
};
widgetBackground = {
enable = mkBoolOption "Whether to enable the widget background configuration";
colorMode = {
mode =
let
enumVals = [
"static"
"animated"
];
in
mkOption {
type = types.nullOr (types.enum enumVals);
default = null;
example = "static";
description = "The color mode to use for the widget background";
apply = getIndexFromEnum enumVals;
};
animationInterval = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 3000;
description = "The interval in milliseconds between each color change";
};
animationSmoothing = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 800;
description = "The time in milliseconds it takes to transition between colors";
};
};
colors = {
source =
let
enumVals = [
"custom"
"system"
"customList"
"random"
];
in
mkOption {
type = types.nullOr (types.enum enumVals);
default = null;
example = "custom";
description = "The source of the colors to use for the widget background";
apply = getIndexFromEnum enumVals;
};
customColor = mkOption {
type = types.nullOr types.str;
default = null;
example = "#ff0000";
description = "The custom color to use for the widget background";
};
system = {
color = mkOption {
type = types.nullOr (types.enum systemColors);
default = null;
example = "text";
description = "The system color to use for the widget background";
apply = getIndexFromEnum systemColors;
};
colorSet = mkOption {
type = types.nullOr (types.enum systemColorSets);
default = null;
example = "view";
description = "The system color variant to use for the widget background";
apply = getIndexFromEnum systemColorSets;
};
};
customColorList = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [
"#ff0000"
"#00ff00"
"#0000ff"
];
description = "The list of custom colors to use for the widget background";
apply = convertColorList;
};
contrastCorrection = {
enable = mkBoolOption "Whether to enable contrast correction for the widget background";
saturation = {
enable = mkBoolOption "Whether to enable saturation correction for the widget background";
value = mkOption {
type = types.nullOr (types.numbers.between 0 1);
default = null;
example = 0.5;
description = "The value to use for the saturation correction";
};
};
lightness = mkOption {
type = types.nullOr (types.numbers.between 0 1);
default = null;
example = 0.5;
description = "The value to use for the lightness correction";
};
};
};
shape = {
opacity = mkOption {
type = types.nullOr (types.numbers.between 0 1);
default = null;
example = 0.5;
description = "The opacity to use for the widget background";
};
radius = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The radius to use for the widget background";
};
line = {
enable = mkBoolOption "Whether to enable the line for the widget background";
position =
let
enumVals = [
"top"
"bottom"
"left"
"right"
];
in
mkOption {
type = types.nullOr (types.enum enumVals);
default = null;
example = "top";
description = "The position to use for the line of the widget background";
apply = getIndexFromEnum enumVals;
};
width = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The width to use for the line of the widget background";
};
horizontalOffset = mkOption {
type = types.nullOr types.int;
default = null;
example = 5;
description = "The X offset to use for the line of the widget background";
};
verticalOffset = mkOption {
type = types.nullOr types.int;
default = null;
example = 5;
description = "The Y offset to use for the line of the widget background";
};
};
outline = {
colorSource =
let
enumVals = [
"custom"
"system"
];
in
mkOption {
type = types.nullOr (types.enum enumVals);
default = null;
example = "custom";
description = "The source of the color to use for the outline of the widget background";
apply = getIndexFromEnum enumVals;
};
customColor = mkOption {
type = types.nullOr types.str;
default = null;
example = "#ff0000";
description = "The custom color to use for the outline of the widget background";
};
system = {
color = mkOption {
type = types.nullOr (types.enum systemColors);
default = null;
example = "text";
description = "The system color to use for the outline of the widget background";
apply = getIndexFromEnum systemColors;
};
colorSet = mkOption {
type = types.nullOr (types.enum systemColorSets);
default = null;
example = "view";
description = "The system color variant to use for the outline of the widget background";
apply = getIndexFromEnum systemColorSets;
};
};
opacity = mkOption {
type = types.nullOr (types.numbers.between 0 1);
default = null;
example = 0.5;
description = "The opacity to use for the outline of the widget background";
};
width = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The width to use for the outline of the widget background";
};
};
shadow = {
color = mkOption {
type = types.nullOr types.str;
default = null;
example = "#7f000000";
description = "The color to use for the shadow of the widget background";
};
size = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The size to use for the shadow of the widget background";
};
horizontalOffset = mkOption {
type = types.nullOr types.int;
default = null;
example = 5;
description = "The X offset to use for the shadow of the widget background";
};
verticalOffset = mkOption {
type = types.nullOr types.int;
default = null;
example = 5;
description = "The Y offset to use for the shadow of the widget background";
};
};
};
};
textAndIcons = {
enable = mkBoolOption "Whether to enable the text and icons configuration";
colorMode = {
mode =
let
enumVals = [
"static"
"interval"
];
in
mkOption {
type = types.nullOr (types.enum enumVals);
default = null;
example = "static";
description = "The color mode to use for the text and icons";
apply = getIndexFromEnum enumVals;
};
interval = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 3000;
description = "The interval in milliseconds between each color change";
};
};
colors = {
source =
let
enumVals = [
"custom"
"system"
"widgetBackground"
"customList"
"random"
];
in
mkOption {
type = types.nullOr (types.enum enumVals);
default = null;
example = "custom";
description = "The source of the colors to use for the text and icons";
apply = getIndexFromEnum enumVals;
};
customColor = mkOption {
type = types.nullOr types.str;
default = null;
example = "#ff0000";
description = "The custom color to use for the text and icons";
};
system = {
color = mkOption {
type = types.nullOr (types.enum systemColors);
default = null;
example = "text";
description = "The system color to use for the text and icons";
apply = getIndexFromEnum systemColors;
};
colorSet = mkOption {
type = types.nullOr (types.enum systemColorSets);
default = null;
example = "view";
description = "The system color variant to use for the text and icons";
apply = getIndexFromEnum systemColorSets;
};
};
customColorList = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [
"#ff0000"
"#00ff00"
"#0000ff"
];
description = "The list of custom colors to use for the text and icons";
apply = convertColorList;
};
opacity = mkOption {
type = types.nullOr (types.numbers.between 0 1);
default = null;
example = 0.5;
description = "The opacity to use for the text and icons";
};
contrastCorrection = {
enable = mkBoolOption "Whether to enable contrast correction for the text and icons";
saturation = {
enable = mkBoolOption "Whether to enable saturation correction for the text and icons";
value = mkOption {
type = types.nullOr (types.numbers.between 0 1);
default = null;
example = 0.5;
description = "The value to use for the saturation correction";
};
};
lightness = mkOption {
type = types.nullOr (types.numbers.between 0 1);
default = null;
example = 0.5;
description = "The value to use for the lightness correction";
};
};
};
shadow = {
enable = mkBoolOption "Whether to enable the shadow for the text and icons";
color = mkOption {
type = types.nullOr types.str;
default = null;
example = "#7f000000";
description = "The color to use for the shadow of the text and icons";
};
strength = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The strength to use for the shadow of the text and icons";
};
horizontalOffset = mkOption {
type = types.nullOr types.int;
default = null;
example = 5;
description = "The X offset to use for the shadow of the text and icons";
};
verticalOffset = mkOption {
type = types.nullOr types.int;
default = null;
example = 5;
description = "The Y offset to use for the shadow of the text and icons";
};
};
customBadges = {
fixCustomBadges = mkBoolOption "Whether to fix custom badges";
};
forceIconColor = {
widgets = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [ "org.kde.plasma.digitalclock" ];
description = "List of widgets to force icon color";
apply = convertWidgets;
};
};
};
panelBackground = {
originalBackground = {
hide = mkBoolOption "Whether to hide the original panel background";
opacity = mkOption {
type = types.nullOr (types.numbers.between 0 1);
default = null;
example = 0.5;
description = "The opacity to use for the original panel background";
};
fixedSizePadding = {
enable = mkBoolOption "Whether to enable fixed size padding";
value = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The value to use for the fixed size padding in pixels";
};
};
};
customBackground = {
enable = mkBoolOption "Whether to enable the custom panel background";
colorSource =
let
enumVals = [
"custom"
"system"
];
in
mkOption {
type = types.nullOr (types.enum enumVals);
default = null;
example = "custom";
description = "The source of the color to use for the custom panel background";
apply = getIndexFromEnum enumVals;
};
customColor = mkOption {
type = types.nullOr types.str;
default = null;
example = "#ff0000";
description = "The custom color to use for the custom panel background";
};
system = {
color = mkOption {
type = types.nullOr (types.enum systemColors);
default = null;
example = "text";
description = "The system color to use for the custom panel background";
apply = getIndexFromEnum systemColors;
};
colorSet = mkOption {
type = types.nullOr (types.enum systemColorSets);
default = null;
example = "view";
description = "The system color variant to use for the custom panel background";
apply = getIndexFromEnum systemColorSets;
};
};
opacity = mkOption {
type = types.nullOr (types.numbers.between 0 1);
default = null;
example = 0.5;
description = "The opacity to use for the custom panel background";
};
radius = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The radius to use for the custom panel background";
};
outline = {
colorSource =
let
enumVals = [
"custom"
"system"
];
in
mkOption {
type = types.nullOr (types.enum enumVals);
default = null;
example = "custom";
description = "The source of the color to use for the outline of the custom panel background";
apply = getIndexFromEnum enumVals;
};
customColor = mkOption {
type = types.nullOr types.str;
default = null;
example = "#ff0000";
description = "The custom color to use for the outline of the custom panel background";
};
system = {
color = mkOption {
type = types.nullOr (types.enum systemColors);
default = null;
example = "text";
description = "The system color to use for the outline of the custom panel background";
apply = getIndexFromEnum systemColors;
};
colorSet = mkOption {
type = types.nullOr (types.enum systemColorSets);
default = null;
example = "view";
description = "The system color variant to use for the outline of the custom panel background";
apply = getIndexFromEnum systemColorSets;
};
};
opacity = mkOption {
type = types.nullOr (types.numbers.between 0 1);
default = null;
example = 0.5;
description = "The opacity to use for the outline of the custom panel background";
};
width = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The width to use for the outline of the custom panel background";
};
};
shadow = {
color = mkOption {
type = types.nullOr types.str;
default = null;
example = "#7f000000";
description = "The color to use for the shadow of the custom panel background";
};
size = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The size to use for the shadow of the custom panel background";
};
horizontalOffset = mkOption {
type = types.nullOr types.int;
default = null;
example = 5;
description = "The X offset to use for the shadow of the custom panel background";
};
verticalOffset = mkOption {
type = types.nullOr types.int;
default = null;
example = 5;
description = "The Y offset to use for the shadow of the custom panel background";
};
};
};
};
blacklist = {
enable = mkBoolOption "Whether to enable the blacklist";
colorSource =
let
enumVals = [
"custom"
"system"
];
in
mkOption {
type = types.nullOr (types.enum enumVals);
default = null;
example = "custom";
description = "The source of the color to use for the blacklisted text and icons";
apply = getIndexFromEnum enumVals;
};
customColor = mkOption {
type = types.nullOr types.str;
default = null;
example = "#ff0000";
description = "The custom color to use for the blacklisted text and icons";
};
system = {
color = mkOption {
type = types.nullOr (types.enum systemColors);
default = null;
example = "text";
description = "The system color to use for the blacklisted text and icons";
apply = getIndexFromEnum systemColors;
};
colorSet = mkOption {
type = types.nullOr (types.enum systemColorSets);
default = null;
example = "view";
description = "The system color variant to use for the blacklisted text and icons";
apply = getIndexFromEnum systemColorSets;
};
};
widgets = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [ "org.kde.plasma.digitalclock" ];
description = "List of widgets to blacklist, blacklisted widgets will not be colorized";
apply = convertWidgets;
};
};
layout = {
enable = mkBoolOption "Whether to enable the layout configuration";
backgroundMargin = {
spacing = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The spacing to use for the background margin";
};
vertical = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The vertical margin to use for the background margin";
};
horizontal = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 5;
description = "The horizontal margin to use for the background margin";
};
};
widgetMarginRules = mkOption {
type = types.nullOr (types.listOf widgetMarginRuleType);
default = null;
example = [
{
widgetId = "org.kde.plasma.kickoff";
margin = {
vertical = 1;
horizontal = 2;
};
}
{
widgetId = "org.kde.plasma.digitalclock";
margin = {
vertical = 2;
horizontal = 1;
};
}
];
description = ''
List of rules to apply margins to specific widgets
Define every widget from the panel here.
'';
apply = convertWidgetMarginRules;
};
};
settings = mkOption {
type = configValueType;
default = null;
example = {
General = {
isEnabled = true;
};
};
description = ''
Extra configuration for the widget options.
See available options at https://github.com/luisbocanegra/plasma-panel-colorizer/blob/main/package/contents/config/main.xml
'';
apply = settings: if settings == null then { } else settings;
};
};
convert =
{
position,
size,
general,
presetAutoLoading,
widgetBackground,
textAndIcons,
panelBackground,
blacklist,
layout,
settings,
}:
{
name = "luisbocanegra.panel.colorizer";
config = lib.recursiveUpdate {
General = lib.filterAttrs (_: v: v != null) {
# General options
isEnabled = general.enable;
hideWidget = general.hideWidget;
# Preset autoloading
normalPreset = presetAutoLoading.normal;
floatingPreset = presetAutoLoading.floating;
touchingWindowPreset = presetAutoLoading.touchingWindow;
maximizedPreset = presetAutoLoading.maximized;
# Widget background options
widgetBgEnabled = widgetBackground.enable;
# Widget background options > Color mode
mode = widgetBackground.colorMode.mode; # Color mode
rainbowInterval = widgetBackground.colorMode.animationInterval;
rainbowTransition = widgetBackground.colorMode.animationSmoothing;
# Widget background options > Colors
colorMode = widgetBackground.colors.source;
singleColor = widgetBackground.colors.customColor; # Custom
colorModeTheme = widgetBackground.colors.system.color; # System
colorModeThemeVariant = widgetBackground.colors.system.colorSet; # System variant
customColors = widgetBackground.colors.customColorList; # Custom list
bgContrastFixEnabled = widgetBackground.colors.contrastCorrection.enable;
bgSaturationEnabled = widgetBackground.colors.contrastCorrection.saturation.enable;
bgSaturation = widgetBackground.colors.contrastCorrection.saturation.value;
bgLightness = widgetBackground.colors.contrastCorrection.lightness;
# Widget background options > Shape
opacity = widgetBackground.shape.opacity;
radius = widgetBackground.shape.radius;
bgLineModeEnabled = widgetBackground.shape.line.enable;
bgLinePosition = widgetBackground.shape.line.position;
bgLineWidth = widgetBackground.shape.line.width;
bgLineXOffset = widgetBackground.shape.line.horizontalOffset;
bgLineYOffset = widgetBackground.shape.line.verticalOffset;
# Widget background options > Shape > Outline
widgetOutlineColorMode = widgetBackground.shape.outline.colorSource;
widgetOutlineColor = widgetBackground.shape.outline.customColor;
widgetOutlineColorModeTheme = widgetBackground.shape.outline.system.color;
widgetOutlineColorModeThemeVariant = widgetBackground.shape.outline.system.colorSet;
widgetOutlineOpacity = widgetBackground.shape.outline.opacity;
widgetOutlineWidth = widgetBackground.shape.outline.width;
# Widget background options > Shape > Shadow
widgetShadowColor = widgetBackground.shape.shadow.color;
widgetShadowSize = widgetBackground.shape.shadow.size;
widgetShadowX = widgetBackground.shape.shadow.horizontalOffset;
widgetShadowY = widgetBackground.shape.shadow.verticalOffset;
# Text and icons options
fgColorEnabled = textAndIcons.enable;
# Text and icons options > Color mode
fgMode = textAndIcons.colorMode.mode;
fgRainbowInterval = textAndIcons.colorMode.interval;
# Text and icons options > Colors
fgColorMode = textAndIcons.colors.source;
fgSingleColor = textAndIcons.colors.customColor;
fgColorModeTheme = textAndIcons.colors.system.color;
fgColorModeThemeVariant = textAndIcons.colors.system.colorSet;
fgCustomColors = textAndIcons.colors.customColorList;
fgOpacity = textAndIcons.colors.opacity;
fgContrastFixEnabled = textAndIcons.colors.contrastCorrection.enable;
fgSaturationEnabled = textAndIcons.colors.contrastCorrection.saturation.enable;
fgSaturation = textAndIcons.colors.contrastCorrection.saturation.value;
fgLightness = textAndIcons.colors.contrastCorrection.lightness;
# Text and icons options > Shadow
fgShadowEnabled = textAndIcons.shadow.enable;
fgShadowColor = textAndIcons.shadow.color;
fgShadowRadius = textAndIcons.shadow.strength;
fgShadowX = textAndIcons.shadow.horizontalOffset;
fgShadowY = textAndIcons.shadow.verticalOffset;
# Text and icons options > Custom badges
fixCustomBadges = textAndIcons.customBadges.fixCustomBadges;
# Text and icons options > Force icon color
forceRecolor = textAndIcons.forceIconColor.widgets;
# Panel background options > Original background
hideRealPanelBg = panelBackground.originalBackground.hide;
panelRealBgOpacity = panelBackground.originalBackground.opacity;
enableCustomPadding = panelBackground.originalBackground.fixedSizePadding.enable;
panelPadding = panelBackground.originalBackground.fixedSizePadding.value;
# Panel background options > Custom background
panelBgEnabled = panelBackground.customBackground.enable;
panelBgColorMode = panelBackground.customBackground.colorSource;
panelBgColor = panelBackground.customBackground.customColor;
panelBgColorModeTheme = panelBackground.customBackground.system.color;
panelBgColorModeThemeVariant = panelBackground.customBackground.system.colorSet;
panelBgOpacity = panelBackground.customBackground.opacity;
panelBgRadius = panelBackground.customBackground.radius;
# Panel background options > Custom background > Outline
panelOutlineColorMode = panelBackground.customBackground.outline.colorSource;
panelOutlineColor = panelBackground.customBackground.outline.customColor;
panelOutlineColorModeTheme = panelBackground.customBackground.outline.system.color;
panelOutlineColorModeThemeVariant = panelBackground.customBackground.outline.system.colorSet;
panelOutlineOpacity = panelBackground.customBackground.outline.opacity;
panelOutlineWidth = panelBackground.customBackground.outline.width;
# Panel background options > Custom background > Shadow
panelShadowColor = panelBackground.customBackground.shadow.color;
panelShadowSize = panelBackground.customBackground.shadow.size;
panelShadowX = panelBackground.customBackground.shadow.horizontalOffset;
panelShadowY = panelBackground.customBackground.shadow.verticalOffset;
# Blacklist options
fgBlacklistedColorEnabled = blacklist.enable;
fgBlacklistedColorMode = blacklist.colorSource;
blacklistedFgColor = blacklist.customColor;
fgBlacklistedColorModeTheme = blacklist.system.color;
fgBlacklistedColorModeThemeVariant = blacklist.system.colorSet;
blacklist = blacklist.widgets;
# Layout options
layoutEnabled = layout.enable;
# Layout options > Background margin
panelSpacing = layout.backgroundMargin.spacing;
widgetBgHMargin = layout.backgroundMargin.horizontal;
widgetBgVMargin = layout.backgroundMargin.vertical;
# Layout options > Widget margin rules
marginRules = layout.widgetMarginRules;
};
} settings;
};
};
}

View File

@ -1,20 +1,25 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
qfont = import ../../lib/qfont.nix { inherit lib; };
mkBoolOption = description: lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
inherit description;
};
mkBoolOption =
description:
lib.mkOption {
type = with lib.types; nullOr bool;
default = null;
inherit description;
};
getIndexFromEnum = enum: value:
if value == null
then null
getIndexFromEnum =
enum: value:
if value == null then
null
else
lib.lists.findFirstIndex
(x: x == value)
lib.lists.findFirstIndex (x: x == value)
(throw "getIndexFromEnum (plasmusic-toolbar widget): Value ${value} isn't present in the enum. This is a bug")
enum;
@ -215,6 +220,22 @@ in
description = "KDE Plasma widget that shows currently playing song information and provide playback controls.";
opts = {
position = mkOption {
type = positionType;
example = {
horizontal = 250;
vertical = 100;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = mkOption {
type = sizeType;
example = {
width = 500;
height = 100;
};
description = "The size of the widget. (Only for desktop widget)";
};
panelIcon = {
icon = mkOption {
type = types.nullOr types.str;
@ -223,6 +244,7 @@ in
description = "Icon to show in the panel.";
};
albumCover = {
fallbackToIcon = mkBoolOption "Whether to fallback to icon if cover is not available.";
useAsIcon = mkBoolOption "Whether to use album cover as icon or not.";
radius = mkOption {
type = types.nullOr (types.ints.between 0 25);
@ -233,8 +255,14 @@ in
};
};
preferredSource =
let enumVals = [ "any" "spotify" "vlc" ];
in mkOption {
let
enumVals = [
"any"
"spotify"
"vlc"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "any";
@ -249,9 +277,14 @@ in
description = "Maximum width of the song text.";
};
scrolling = {
enable = mkBoolOption "Whether to enable scrolling text or not.";
behavior =
let
enumVals = [ "alwaysScroll" "scrollOnHover" "alwaysScrollExceptOnHover" ];
enumVals = [
"alwaysScroll"
"scrollOnHover"
"alwaysScrollExceptOnHover"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
@ -266,10 +299,19 @@ in
example = 3;
description = "Speed of the scrolling text.";
};
resetOnPause = mkBoolOption "Whether to reset the scrolling text when the song is paused or not.";
};
displayInSeparateLines = mkBoolOption "Whether to display song information (title and artist) in separate lines or not.";
};
showPlaybackControls = mkBoolOption "Whether to show playback controls or not.";
musicControls = {
showPlaybackControls = mkBoolOption "Whether to show playback controls or not.";
volumeStep = mkOption {
type = types.nullOr (types.ints.between 1 100);
default = null;
example = 5;
description = "Step size for volume control.";
};
};
font = mkOption {
type = types.nullOr fontType;
default = null;
@ -280,8 +322,46 @@ in
description = "Custom font to use for the widget.";
apply = font: if font == null then null else qfont.fontToString font;
};
background =
let
enumVals = [
"standard"
"transparent"
"transparentShadow"
];
in
mkOption {
type = with types; nullOr (enum enumVals);
default = null;
example = "transparent";
description = "Widget background type (only for desktop widget)";
apply =
background:
if background == null then
null
else
builtins.elemAt
[
1
0
4
]
(
lib.lists.findFirstIndex (
x: x == background
) (throw "plasmusic-toolbar: non-existent background ${background}. This is a bug!") enumVals
);
};
albumCover = {
albumPlaceholder = mkOption {
type = types.nullOr types.str;
default = null;
example = "file:///home/user/placeholder.png";
description = "Path to the album placeholder image.";
};
};
settings = mkOption {
type = with types; nullOr (attrsOf (attrsOf (either (oneOf [ bool float int str ]) (listOf (oneOf [ bool float int str ])))));
type = configValueType;
default = null;
example = {
General = {
@ -290,41 +370,55 @@ in
};
description = ''
Extra configuration for the widget options.
See available options at https://github.com/ccatterina/plasmusic-toolbar/blob/main/src/contents/config/main.xml
'';
apply = settings: if settings == null then {} else settings;
apply = settings: if settings == null then { } else settings;
};
};
convert =
{ panelIcon
, preferredSource
, songText
, showPlaybackControls
, font
, settings
}: {
{
position,
size,
panelIcon,
preferredSource,
songText,
musicControls,
font,
background,
albumCover,
settings,
}:
{
name = "plasmusic-toolbar";
config = lib.recursiveUpdate {
General = lib.filterAttrs (_: v: v != null) (
{
panelIcon = panelIcon.icon;
useAlbumCoverAsPanelIcon = panelIcon.albumCover.useAsIcon;
albumCoverRadius = panelIcon.albumCover.radius;
General = lib.filterAttrs (_: v: v != null) {
panelIcon = panelIcon.icon;
useAlbumCoverAsPanelIcon = panelIcon.albumCover.useAsIcon;
albumCoverRadius = panelIcon.albumCover.radius;
fallbackToIconWhenArtNotAvailable = panelIcon.albumCover.fallbackToIcon;
sourceIndex = preferredSource;
sourceIndex = preferredSource;
maxSongWidthInPanel = songText.maximumWidth;
textScrollingSpeed = songText.scrolling.speed;
separateText = songText.displayInSeparateLines;
textScrollingBehaviour = songText.scrolling.behavior;
maxSongWidthInPanel = songText.maximumWidth;
separateText = songText.displayInSeparateLines;
commandsInPanel = showPlaybackControls;
useCustomFont = (font != null);
customFont = font;
}
);
textScrollingEnabled = songText.scrolling.enable;
textScrollingBehaviour = songText.scrolling.behavior;
textScrollingSpeed = songText.scrolling.speed;
textScrollingResetOnPause = songText.scrolling.resetOnPause;
commandsInPanel = musicControls.showPlaybackControls;
volumeStep = musicControls.volumeStep;
useCustomFont = (font != null);
customFont = font;
desktopWidgetBg = background;
albumPlaceholder = albumCover.albumPlaceholder;
};
} settings;
};
};

View File

@ -1,6 +1,8 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
# KDE expects a key/value pair like this:
# ```ini
@ -15,10 +17,8 @@ let
# So, to satisfy the expected format we must quote the ENTIRE string as a valid JS string,
# which means constructing a string that looks like this in the source code:
# "[\"cpu/all/usage\", \"cpu/all/averageTemperature\"]"
toEscapedList = ids:
if ids != null
then "[${lib.concatMapStringsSep ", " (x: ''\"${x}\"'') ids}]"
else null;
toEscapedList =
ids: if ids != null then "[${lib.concatMapStringsSep ", " (x: ''\"${x}\"'') ids}]" else null;
mkListOption = mkOption {
type = with types; nullOr (listOf str);
@ -29,20 +29,22 @@ let
# {name, color} -> {name, value}
# Convert the sensor attrset into a name-value pair expected by listToAttrs
toColorKV =
{ name
, color
, label
,
}: {
{
name,
color,
label,
}:
{
inherit name;
value = color;
};
toLabelKV =
{ name
, color
, label
,
}: {
{
name,
color,
label,
}:
{
inherit name;
value = label;
};
@ -54,6 +56,22 @@ in
opts = {
# See https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/systemmonitor/systemmonitor/package/contents/config/main.xml for the accepted raw options
position = mkOption {
type = positionType;
example = {
horizontal = 250;
vertical = 50;
};
description = "The position of the widget. (Only for desktop widget)";
};
size = mkOption {
type = sizeType;
example = {
width = 500;
height = 500;
};
description = "The size of the widget. (Only for desktop widget)";
};
title = mkOption {
type = with types; nullOr str;
default = null;
@ -76,40 +94,47 @@ in
description = "The display style of the chart. Uses the internal plugin name.";
};
sensors = mkOption {
type = with types;
nullOr (listOf (submodule {
options = {
name = mkOption {
type = str;
example = "cpu/all/usage";
description = "The name of the sensor.";
type =
with types;
nullOr (
listOf (submodule {
options = {
name = mkOption {
type = str;
example = "cpu/all/usage";
description = "The name of the sensor.";
};
color = mkOption {
type = str; # TODO maybe use a better type
example = "255,255,255";
description = "The color of the sensor, as a string containing 8-bit integral RGB values separated by commas";
};
label = mkOption {
type = str;
example = "CPU %";
description = "The label of the sensor.";
};
};
color = mkOption {
type = str; # TODO maybe use a better type
example = "255,255,255";
description = "The color of the sensor, as a string containing 8-bit integral RGB values separated by commas";
};
label = mkOption {
type = str;
example = "CPU %";
description = "The label of the sensor.";
};
};
}));
})
);
default = null;
example = [{
name = "gpu/gpu1/usage";
color = "180,190,254";
label = "GPU %";
}];
example = [
{
name = "gpu/gpu1/usage";
color = "180,190,254";
label = "GPU %";
}
];
description = ''
The list of sensors displayed as a part of the graph/chart.
'';
apply = sensors: lib.optionalAttrs (sensors != null) {
SensorColors = builtins.listToAttrs (map toColorKV sensors);
SensorLabels = builtins.listToAttrs (map toLabelKV sensors);
Sensors.highPrioritySensorIds = toEscapedList (map (s: s.name) sensors);
};
apply =
sensors:
lib.optionalAttrs (sensors != null) {
SensorColors = builtins.listToAttrs (map toColorKV sensors);
SensorLabels = builtins.listToAttrs (map toLabelKV sensors);
Sensors.highPrioritySensorIds = toEscapedList (map (s: s.name) sensors);
};
};
totalSensors = mkListOption // {
@ -119,7 +144,10 @@ in
'';
};
textOnlySensors = mkListOption // {
example = [ "cpu/all/averageTemperature" "cpu/all/averageFrequency" ];
example = [
"cpu/all/averageTemperature"
"cpu/all/averageFrequency"
];
description = ''
The list of text-only sensors, displayed in the pop-up upon clicking the widget.
'';
@ -137,45 +165,48 @@ in
};
};
settings = mkOption {
type = with types; nullOr (attrsOf (attrsOf (either (oneOf [ bool float int str ]) (listOf (oneOf [ bool float int str ])))));
type = configValueType;
default = null;
description = "Extra configuration options for the widget.";
apply = settings: if settings == null then {} else settings;
apply = settings: if settings == null then { } else settings;
};
};
convert =
{ title
, showTitle
, showLegend
, displayStyle
, totalSensors
, sensors
, textOnlySensors
, range
, settings
}: {
{
position,
size,
title,
showTitle,
showLegend,
displayStyle,
totalSensors,
sensors,
textOnlySensors,
range,
settings,
}:
{
name = "org.kde.plasma.systemmonitor";
config = lib.filterAttrsRecursive (_: v: v != null)
(lib.recursiveUpdate
({
Appearance = {
inherit title;
inherit showTitle;
chartFace = displayStyle;
};
Sensors = {
lowPrioritySensorIds = textOnlySensors;
totalSensors = totalSensors;
};
"org.kde.ksysguard.piechart/General" = {
inherit showLegend;
rangeAuto = (range.from == null && range.to == null);
rangeFrom = range.from;
rangeTo = range.to;
};
})
(lib.recursiveUpdate sensors settings));
config = lib.filterAttrsRecursive (_: v: v != null) (
lib.recursiveUpdate ({
Appearance = {
inherit title;
inherit showTitle;
chartFace = displayStyle;
};
Sensors = {
lowPrioritySensorIds = textOnlySensors;
totalSensors = totalSensors;
};
"org.kde.ksysguard.piechart/General" = {
inherit showLegend;
rangeAuto = (range.from == null && range.to == null);
rangeFrom = range.from;
rangeTo = range.to;
};
}) (lib.recursiveUpdate sensors settings)
);
};
};
}

View File

@ -1,169 +1,206 @@
{ lib, widgets, ... }:
let
inherit (lib) mkOption types;
inherit (import ./lib.nix { inherit lib; }) configValueType;
inherit (import ./default.nix { inherit lib; }) positionType sizeType;
mkBoolOption = description: mkOption {
type = with types; nullOr bool;
default = null;
inherit description;
};
mkBoolOption =
description:
mkOption {
type = with types; nullOr bool;
default = null;
inherit description;
};
in
{
systemTray = {
description = "A system tray of other widgets/plasmoids";
opts = ({ options, ... }: {
# See https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/systemtray/package/contents/config/main.xml for the accepted raw options.
opts = (
{ options, ... }:
{
# See https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/systemtray/package/contents/config/main.xml for the accepted raw options.
pin = mkBoolOption "Whether the popup should remain open when another window is activated.";
icons = {
spacing =
let
enum = [ "small" "medium" "large" ];
in
mkOption {
type = types.nullOr (types.either (types.enum enum) types.ints.positive);
default = null;
description = ''
The spacing between icons.
Could be an integer unit, or "small" (1 unit), "medium" (2 units) or "large" (6 units).
'';
apply = spacing:
(if (spacing == null) then null
else
(if builtins.isInt spacing then
spacing
else
builtins.elemAt [ 1 2 6 ] (
lib.lists.findFirstIndex
(x: x == spacing)
(throw "systemTray: nonexistent spacing ${spacing}! This is a bug!")
enum
)));
};
scaleToFit = mkBoolOption ''
Whether to automatically scale System Tray icons to fix the available thickness of the panel.
If false, tray icons will be capped at the smallMedium size (22px) and become a two-row/column
layout when the panel is thick.
'';
};
items = {
showAll = mkBoolOption "If true, all system tray entries will always be in the main bar, outside the popup.";
hidden = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [
# Plasmoid plugin example
"org.kde.plasma.brightness"
# StatusNotifier example
"org.kde.plasma.addons.katesessions"
];
description = ''
List of widgets that should be hidden from the main bar, only visible in the popup.
Expects a list of plasmoid plugin IDs or StatusNotifier IDs.
'';
};
shown = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [
# Plasmoid plugin example
"org.kde.plasma.battery"
# StatusNotifier example
"org.kde.plasma.addons.katesessions"
];
description = ''
List of widgets that should be shown in the main bar.
Expects a list of plasmoid plugin IDs or StatusNotifier IDs.
'';
};
extra = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [ "org.kde.plasma.battery" ];
description = ''
List of extra widgets that are explicitly enabled in the system tray.
Expects a list of plasmoid plugin IDs.
'';
};
configs = mkOption {
# The type here is deliberately NOT modelled exactly correctly,
# to allow the apply function to provide better errors with the richer option and type system.
type = types.attrsOf (types.attrsOf types.anything);
default = { };
position = mkOption {
type = positionType;
example = {
# Example of a widget-specific config
battery.showPercentage = true;
# Example of raw config for an untyped widget
"org.kde.plasma.devicenotifier".config = {
removableDevices = false;
nonRemovableDevices = true;
};
horizontal = 250;
vertical = 50;
};
description = ''
Configurations for each widget in the tray.
description = "The position of the widget. (Only for desktop widget)";
};
size = mkOption {
type = sizeType;
example = {
width = 500;
height = 500;
};
description = "The size of the widget. (Only for desktop widget)";
};
pin = mkBoolOption "Whether the popup should remain open when another window is activated.";
Uses widget-specific configs if the key is a known widget type,
otherwise uses raw configs that's not specifically checked to be valid,
or even idiomatic in Nix!
icons = {
spacing =
let
enum = [
"small"
"medium"
"large"
];
in
mkOption {
type = types.nullOr (types.either (types.enum enum) types.ints.positive);
default = null;
description = ''
The spacing between icons.
Could be an integer unit, or "small" (1 unit), "medium" (2 units) or "large" (6 units).
'';
apply =
spacing:
(
if (spacing == null) then
null
else
(
if builtins.isInt spacing then
spacing
else
builtins.elemAt
[
1
2
6
]
(
lib.lists.findFirstIndex (
x: x == spacing
) (throw "systemTray: nonexistent spacing ${spacing}! This is a bug!") enum
)
)
);
};
scaleToFit = mkBoolOption ''
Whether to automatically scale System Tray icons to fix the available thickness of the panel.
If false, tray icons will be capped at the smallMedium size (22px) and become a two-row/column
layout when the panel is thick.
'';
};
# You might be asking yourself... WTH is this?
# Simply put, this thing allows us to apply the same defaults as defined by the options,
# Instead of forcing downstream converters to provide defaults to everything *again*.
# The way to do this is kind of cursed and honestly it might be easier if `lib.evalOptionValue`
# is not recommended for public use. Oh well.
apply = lib.mapAttrsToList
(name: config:
items = {
showAll = mkBoolOption "If true, all system tray entries will always be in the main bar, outside the popup.";
hidden = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [
# Plasmoid plugin example
"org.kde.plasma.brightness"
# StatusNotifier example
"org.kde.plasma.addons.katesessions"
];
description = ''
List of widgets that should be hidden from the main bar, only visible in the popup.
Expects a list of plasmoid plugin IDs or StatusNotifier IDs.
'';
};
shown = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [
# Plasmoid plugin example
"org.kde.plasma.battery"
# StatusNotifier example
"org.kde.plasma.addons.katesessions"
];
description = ''
List of widgets that should be shown in the main bar.
Expects a list of plasmoid plugin IDs or StatusNotifier IDs.
'';
};
extra = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [ "org.kde.plasma.battery" ];
description = ''
List of extra widgets that are explicitly enabled in the system tray.
Expects a list of plasmoid plugin IDs.
'';
};
configs = mkOption {
# The type here is deliberately NOT modelled exactly correctly,
# to allow the apply function to provide better errors with the richer option and type system.
type = types.attrsOf (types.attrsOf types.anything);
default = { };
example = {
# Example of a widget-specific config
battery.showPercentage = true;
keyboardLayout.displayStyle = "label";
# Example of raw config for an untyped widget
"org.kde.plasma.devicenotifier".config.General = {
removableDevices = false;
nonRemovableDevices = true;
};
};
description = ''
Configurations for each widget in the tray.
Uses widget-specific configs if the key is a known widget type,
otherwise uses raw configs that's not specifically checked to be valid,
or even idiomatic in Nix!
'';
# You might be asking yourself... WTH is this?
# Simply put, this thing allows us to apply the same defaults as defined by the options,
# Instead of forcing downstream converters to provide defaults to everything *again*.
# The way to do this is kind of cursed and honestly it might be easier if `lib.evalOptionValue`
# is not recommended for public use. Oh well.
apply = lib.mapAttrsToList (
name: config:
let
isKnownWidget = widgets.isKnownWidget name;
# Raw widgets aren't wrapped in an extra attrset layer, unlike known ones
# We wrap them back up to ensure the path is accurate
loc = options.items.configs.loc ++ lib.optional (!isKnownWidget) name;
in
widgets.convert (
lib.mergeDefinitions loc widgets.type [
widgets.convert
(lib.mergeDefinitions loc widgets.type [
{
file = builtins.head options.items.configs.files;
# Looks a bit funny, does the job just right.
value =
if isKnownWidget then
{ ${name} = config; }
else
config // { inherit name; };
value = if isKnownWidget then { ${name} = config; } else config // { inherit name; };
}
]
).mergedValue
]).mergedValue
);
};
};
};
settings = mkOption {
type = with types; nullOr (attrsOf (attrsOf (either (oneOf [ bool float int str ]) (listOf (oneOf [ bool float int str ])))));
default = null;
description = "Extra configuration options for the widget.";
apply = settings: if settings == null then {} else settings;
};
});
settings = mkOption {
type = configValueType;
default = null;
description = "Extra configuration options for the widget.";
apply = settings: if settings == null then { } else settings;
};
}
);
convert =
{ pin
, icons
, items
, settings
{
position,
size,
pin,
icons,
items,
settings,
}:
let
sets = {
@ -186,7 +223,7 @@ in
(widget) => {
const tray = desktopById(widget.readConfig("SystrayContainmentId"));
if (!tray) return; // if somehow the containment doesn't exist
${widgets.lib.setWidgetSettings "tray" mergedSettings}
${widgets.lib.addWidgetStmts "tray" "trayWidgets" items.configs}
}

View File

@ -1,9 +1,17 @@
{ lib
, config
, ...
}:
with lib.types; let
inherit (builtins) length listToAttrs foldl' toString attrNames getAttr concatStringsSep add isAttrs;
{ lib, config, ... }:
with lib.types;
let
inherit (builtins)
length
listToAttrs
foldl'
toString
attrNames
getAttr
concatStringsSep
add
isAttrs
;
inherit (lib) mkOption mkIf;
inherit (lib.trivial) mergeAttrs;
inherit (lib.lists) imap0;
@ -35,8 +43,10 @@ with lib.types; let
matchNameMap = {
"window-class" = "wmclass";
"window-types" = "types";
"window-role" = "windowrole";
};
matchOptionType = hasMatchWhole:
matchOptionType =
hasMatchWhole:
submodule {
options =
{
@ -58,7 +68,12 @@ with lib.types; let
};
};
};
basicValueType = oneOf [ bool float int str ];
basicValueType = oneOf [
bool
float
int
str
];
applyOptionType = submodule {
options = {
value = mkOption {
@ -72,25 +87,29 @@ with lib.types; let
};
};
};
mkMatchOption = name: hasMatchWhole:
mkMatchOption =
name: hasMatchWhole:
mkOption {
type = nullOr (coercedTo str (value: { inherit value; }) (matchOptionType hasMatchWhole));
default = null;
description = "${name} matching";
};
fixMatchName = name: matchNameMap.${name} or name;
buildMatchRule = name: rule: ({
"${fixMatchName name}" = rule.value;
"${fixMatchName name}match" = getAttr rule.type matchRules;
}
// optionalAttrs (rule ? match-whole) {
"${fixMatchName name}complete" = rule.match-whole;
});
buildMatchRule =
name: rule:
(
{
"${fixMatchName name}" = rule.value;
"${fixMatchName name}match" = getAttr rule.type matchRules;
}
// optionalAttrs (rule ? match-whole) { "${fixMatchName name}complete" = rule.match-whole; }
);
buildApplyRule = name: rule: {
"${name}" = rule.value;
"${name}rule" = getAttr rule.apply applyRules;
};
buildWindowRule = rule:
buildWindowRule =
rule:
let
matchOptions = filterAttrs (_name: isAttrs) rule.match;
matchRules = mapAttrsToList buildMatchRule matchOptions;
@ -100,16 +119,14 @@ with lib.types; let
{
Description = rule.description;
}
// optionalAttrs (rule.match.window-types != 0) {
types = rule.match.window-types;
}
// optionalAttrs (rule.match.window-types != 0) { types = rule.match.window-types; }
// combinedRules;
windowRules = listToAttrs (imap0
(i: rule: {
windowRules = listToAttrs (
imap0 (i: rule: {
name = toString (i + 1);
value = buildWindowRule rule;
})
cfg.window-rules);
}) cfg.window-rules
);
in
{
options.programs.plasma = {
@ -150,14 +167,12 @@ in
config = mkIf (length cfg.window-rules > 0) {
programs.plasma.configFile = {
kwinrulesrc =
{
General = {
count = length cfg.window-rules;
rules = concatStringsSep "," (attrNames windowRules);
};
}
// windowRules;
kwinrulesrc = {
General = {
count = length cfg.window-rules;
rules = concatStringsSep "," (attrNames windowRules);
};
} // windowRules;
};
};
}

View File

@ -16,12 +16,13 @@ in
};
};
config = (lib.mkIf (cfg.enable && cfg.windows.allowWindowsToRememberPositions != null) {
programs.plasma.configFile = {
kdeglobals = {
General.AllowKDEAppsToRememberWindowPositions = cfg.windows.allowWindowsToRememberPositions;
config = (
lib.mkIf (cfg.enable && cfg.windows.allowWindowsToRememberPositions != null) {
programs.plasma.configFile = {
kdeglobals = {
General.AllowKDEAppsToRememberWindowPositions = cfg.windows.allowWindowsToRememberPositions;
};
};
};
});
}
);
}

View File

@ -1,43 +1,76 @@
# General workspace behavior settings:
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.programs.plasma;
inherit (import ../lib/wallpapers.nix { inherit lib; }) wallpaperPictureOfTheDayType wallpaperSlideShowType;
cursorType = with lib.types; submodule {
options = {
theme = lib.mkOption {
type = nullOr str;
default = null;
example = "Breeze_Snow";
description = "The Plasma cursortheme. Run plasma-apply-cursortheme --list-themes for valid options.";
};
size = lib.mkOption {
type = nullOr ints.positive;
default = null;
example = 24;
description = "The size of the cursor. See the settings GUI for allowed sizes for each cursortheme.";
inherit (import ../lib/wallpapers.nix { inherit lib; })
wallpaperPictureOfTheDayType
wallpaperSlideShowType
wallpaperFillModeTypes
;
cursorType =
with lib.types;
submodule {
options = {
theme = lib.mkOption {
type = nullOr str;
default = null;
example = "Breeze_Snow";
description = "The Plasma cursortheme. Run plasma-apply-cursortheme --list-themes for valid options.";
};
size = lib.mkOption {
type = nullOr ints.positive;
default = null;
example = 24;
description = "The size of the cursor. See the settings GUI for allowed sizes for each cursortheme.";
};
};
};
};
anyThemeSet = (cfg.workspace.theme != null ||
cfg.workspace.colorScheme != null ||
(cfg.workspace.cursor != null && cfg.workspace.cursor.theme != null) ||
cfg.workspace.lookAndFeel != null ||
cfg.workspace.iconTheme != null);
anyThemeSet = (
cfg.workspace.theme != null
|| cfg.workspace.colorScheme != null
|| (cfg.workspace.cursor != null && cfg.workspace.cursor.theme != null)
|| cfg.workspace.lookAndFeel != null
|| cfg.workspace.iconTheme != null
);
splashScreenEngineDetect = theme: (if (theme == "None") then "none" else "KSplashQML");
in
{
imports = [
(lib.mkRenamedOptionModule [ "programs" "plasma" "workspace" "cursorTheme" ] [ "programs" "plasma" "workspace" "cursor" "theme" ])
(lib.mkRenamedOptionModule
[
"programs"
"plasma"
"workspace"
"cursorTheme"
]
[
"programs"
"plasma"
"workspace"
"cursor"
"theme"
]
)
];
options.programs.plasma.workspace = {
clickItemTo = lib.mkOption {
type = with lib.types; nullOr (enum [ "open" "select" ]);
type =
with lib.types;
nullOr (enum [
"open"
"select"
]);
default = null;
description = ''
Clicking files or folders should open or select them.
@ -54,7 +87,7 @@ in
};
theme = lib.mkOption {
type = lib.types.nullOr lib.types.str;
type = with lib.types; nullOr str;
default = null;
example = "breeze-dark";
description = ''
@ -63,7 +96,7 @@ in
};
colorScheme = lib.mkOption {
type = lib.types.nullOr lib.types.str;
type = with lib.types; nullOr str;
default = null;
example = "BreezeDark";
description = ''
@ -74,14 +107,17 @@ in
cursor = lib.mkOption {
type = lib.types.nullOr cursorType;
default = null;
example = { theme = "Breeze_Snow"; size = 24; };
example = {
theme = "Breeze_Snow";
size = 24;
};
description = ''
Allows to configure the cursor in plasma. Both the theme and size are configurable.
'';
};
lookAndFeel = lib.mkOption {
type = lib.types.nullOr lib.types.str;
type = with lib.types; nullOr str;
default = null;
example = "org.kde.breezedark.desktop";
description = ''
@ -90,7 +126,7 @@ in
};
iconTheme = lib.mkOption {
type = lib.types.nullOr lib.types.str;
type = with lib.types; nullOr str;
default = null;
example = "Papirus";
description = ''
@ -119,14 +155,16 @@ in
wallpaperPictureOfTheDay = lib.mkOption {
type = lib.types.nullOr wallpaperPictureOfTheDayType;
default = null;
example = { provider = "apod"; };
example = {
provider = "apod";
};
description = ''
Allows you to set wallpaper using the picture of the day plugin. Needs the provider.
'';
};
wallpaperPlainColor = lib.mkOption {
type = lib.types.nullOr lib.types.str;
type = with lib.types; nullOr str;
default = null;
example = "0,64,174,256";
description = ''
@ -134,6 +172,17 @@ in
'';
};
wallpaperFillMode = lib.mkOption {
type = with lib.types; nullOr (enum (builtins.attrNames wallpaperFillModeTypes));
default = null;
example = "stretch";
description = ''
Defines how the wallpaper should be displayed on the screen.
Applies only to wallpaperPictureOfTheDay or wallpaperSlideShow.
'';
apply = value: if value == null then null else (builtins.toString wallpaperFillModeTypes.${value});
};
soundTheme = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
@ -192,109 +241,247 @@ in
};
};
config = (lib.mkIf cfg.enable {
assertions = [
{
assertion =
let
wallpapers = with cfg.workspace; [ wallpaperSlideShow wallpaper wallpaperPictureOfTheDay wallpaperPlainColor ];
in
lib.count (x: x != null) wallpapers <= 1;
message = "Can set only one of wallpaper, wallpaperSlideShow, wallpaperPictureOfTheDay, and wallpaperPlainColor.";
}
{
assertion = (cfg.workspace.splashScreen.engine == null || cfg.workspace.splashScreen.theme != null);
message = ''
Cannot set plasma.workspace.splashScreen.engine without a
corresponding theme.
'';
}
{
assertion = !(lib.xor (cfg.workspace.windowDecorations.theme == null) (cfg.workspace.windowDecorations.library == null));
message = ''
Must set both plasma.workspace.windowDecorations.library and
plasma.workspace.windowDecorations.theme or none.
'';
}
];
warnings = (if
((cfg.workspace.lookAndFeel != null) &&
(cfg.workspace.splashScreen.theme != null ||
cfg.workspace.windowDecorations.theme != null)) then
[
''Setting lookAndFeel together with splashScreen or windowDecorations in
plasma-manager is not recommended since lookAndFeel themes often
override these settings. Consider setting each part in the lookAndFeel
theme manually.''
] else [ ]);
programs.plasma.configFile = (lib.mkMerge [
{
kdeglobals = {
KDE.SingleClick = (lib.mkIf (cfg.workspace.clickItemTo != null) (cfg.workspace.clickItemTo == "open"));
Sounds.Theme = (lib.mkIf (cfg.workspace.soundTheme != null) cfg.workspace.soundTheme);
};
plasmarc = (lib.mkIf (cfg.workspace.tooltipDelay != null) {
PlasmaToolTips.Delay = cfg.workspace.tooltipDelay;
});
kcminputrc = (lib.mkIf (cfg.workspace.cursor != null && cfg.workspace.cursor.size != null) {
Mouse.cursorSize = cfg.workspace.cursor.size;
});
ksplashrc.KSplash = (lib.mkIf (cfg.workspace.splashScreen.theme != null) {
Engine = (if (cfg.workspace.splashScreen.engine == null) then
(splashScreenEngineDetect cfg.workspace.splashScreen.theme)
else cfg.workspace.splashScreen.engine);
Theme = cfg.workspace.splashScreen.theme;
});
kwinrc = (lib.mkIf (cfg.workspace.windowDecorations.theme != null) {
"org.kde.kdecoration2".library = cfg.workspace.windowDecorations.library;
"org.kde.kdecoration2".theme = cfg.workspace.windowDecorations.theme;
});
}
# We add persistence to some keys in order to not reset the themes on
# each generation when we use overrideConfig.
(lib.mkIf (cfg.overrideConfig && anyThemeSet) (
let
colorSchemeIgnore =
if (cfg.workspace.colorScheme != null || cfg.workspace.lookAndFeel != null) then
(import ../lib/colorscheme.nix {
inherit lib;
}) else { };
in
(lib.mkMerge
config = (
lib.mkIf cfg.enable {
assertions = [
{
assertion =
let
wallpapers = with cfg.workspace; [
wallpaperSlideShow
wallpaper
wallpaperPictureOfTheDay
wallpaperPlainColor
];
in
lib.count (x: x != null) wallpapers <= 1;
message = "Can set only one of wallpaper, wallpaperSlideShow, wallpaperPictureOfTheDay, and wallpaperPlainColor.";
}
{
assertion = (cfg.workspace.splashScreen.engine == null || cfg.workspace.splashScreen.theme != null);
message = ''
Cannot set plasma.workspace.splashScreen.engine without a
corresponding theme.
'';
}
{
assertion =
!(lib.xor (cfg.workspace.windowDecorations.theme == null) (
cfg.workspace.windowDecorations.library == null
));
message = ''
Must set both plasma.workspace.windowDecorations.library and
plasma.workspace.windowDecorations.theme or none.
'';
}
];
warnings = (
if
(
(cfg.workspace.lookAndFeel != null)
&& (cfg.workspace.splashScreen.theme != null || cfg.workspace.windowDecorations.theme != null)
)
then
[
{
kcminputrc.Mouse.cursorTheme.persistent = lib.mkDefault (cfg.workspace.cursor != null && cfg.workspace.cursor.theme != null);
kdeglobals.General.ColorScheme.persistent = lib.mkDefault (cfg.workspace.colorScheme != null || cfg.workspace.lookAndFeel != null);
kdeglobals.Icons.Theme.persistent = lib.mkDefault (cfg.workspace.iconTheme != null);
kdeglobals.KDE.LookAndFeelPackage.persistent = lib.mkDefault (cfg.workspace.lookAndFeel != null);
plasmarc.Theme.name.persistent = lib.mkDefault (cfg.workspace.theme != null);
}
colorSchemeIgnore
])
))
]);
''
Setting lookAndFeel together with splashScreen or windowDecorations in
plasma-manager is not recommended since lookAndFeel themes often
override these settings. Consider setting each part in the lookAndFeel
theme manually.''
]
else
[ ]
);
# We create a script which applies the different theme settings using
# kde tools. We then run this using an autostart script, where this is
# run only on the first login (unless overrideConfig is enabled),
# granted all the commands succeed (until we change the settings again).
programs.plasma.startup.startupScript."apply_themes" = (lib.mkIf anyThemeSet {
text = ''
${if cfg.workspace.lookAndFeel != null then "plasma-apply-lookandfeel -a ${cfg.workspace.lookAndFeel}" else ""}
${if cfg.workspace.theme != null then "plasma-apply-desktoptheme ${cfg.workspace.theme}" else ""}
${if (cfg.workspace.cursor != null && cfg.workspace.cursor.theme != null) then
"plasma-apply-cursortheme ${cfg.workspace.cursor.theme}" +
(if cfg.workspace.cursor.size != null then " --size ${builtins.toString cfg.workspace.cursor.size}" else "")
else ""}
${if cfg.workspace.colorScheme != null then "plasma-apply-colorscheme ${cfg.workspace.colorScheme}" else ""}
${if cfg.workspace.iconTheme != null then "${pkgs.kdePackages.plasma-workspace}/libexec/plasma-changeicons ${cfg.workspace.iconTheme}" else ""}
'';
priority = 1;
});
programs.plasma.configFile = (
lib.mkMerge [
{
kdeglobals = {
KDE.SingleClick = (
lib.mkIf (cfg.workspace.clickItemTo != null) (cfg.workspace.clickItemTo == "open")
);
Sounds.Theme = (lib.mkIf (cfg.workspace.soundTheme != null) cfg.workspace.soundTheme);
};
plasmarc = (
lib.mkIf (cfg.workspace.tooltipDelay != null) { PlasmaToolTips.Delay = cfg.workspace.tooltipDelay; }
);
kcminputrc = (
lib.mkIf (cfg.workspace.cursor != null && cfg.workspace.cursor.size != null) {
Mouse.cursorSize = cfg.workspace.cursor.size;
}
);
ksplashrc.KSplash = (
lib.mkIf (cfg.workspace.splashScreen.theme != null) {
Engine = (
if (cfg.workspace.splashScreen.engine == null) then
(splashScreenEngineDetect cfg.workspace.splashScreen.theme)
else
cfg.workspace.splashScreen.engine
);
Theme = cfg.workspace.splashScreen.theme;
}
);
kwinrc = (
lib.mkIf (cfg.workspace.windowDecorations.theme != null) {
"org.kde.kdecoration2".library = cfg.workspace.windowDecorations.library;
"org.kde.kdecoration2".theme = cfg.workspace.windowDecorations.theme;
}
);
}
# We add persistence to some keys in order to not reset the themes on
# each generation when we use overrideConfig.
(lib.mkIf (cfg.overrideConfig && anyThemeSet) (
let
colorSchemeIgnore =
if (cfg.workspace.colorScheme != null || cfg.workspace.lookAndFeel != null) then
(import ../lib/colorscheme.nix { inherit lib; })
else
{ };
in
(lib.mkMerge [
{
kcminputrc.Mouse.cursorTheme.persistent = lib.mkDefault (
cfg.workspace.cursor != null && cfg.workspace.cursor.theme != null
);
kdeglobals.General.ColorScheme.persistent = lib.mkDefault (
cfg.workspace.colorScheme != null || cfg.workspace.lookAndFeel != null
);
kdeglobals.Icons.Theme.persistent = lib.mkDefault (cfg.workspace.iconTheme != null);
kdeglobals.KDE.LookAndFeelPackage.persistent = lib.mkDefault (cfg.workspace.lookAndFeel != null);
plasmarc.Theme.name.persistent = lib.mkDefault (cfg.workspace.theme != null);
}
colorSchemeIgnore
])
))
]
);
# The wallpaper configuration can be found in panels.nix due to wallpaper
# configuration and panel configuration being stored in the same file, and
# thus should be using the same desktop-script.
});
# We create a script which applies the different theme settings using
# kde tools. We then run this using an autostart script, where this is
# run only on the first login (unless overrideConfig is enabled),
# granted all the commands succeed (until we change the settings again).
programs.plasma.startup = {
startupScript."apply_themes" = (
lib.mkIf anyThemeSet {
text = ''
${
if cfg.workspace.lookAndFeel != null then
"plasma-apply-lookandfeel -a ${cfg.workspace.lookAndFeel}"
else
""
}
${if cfg.workspace.theme != null then "plasma-apply-desktoptheme ${cfg.workspace.theme}" else ""}
${
if (cfg.workspace.cursor != null && cfg.workspace.cursor.theme != null) then
"plasma-apply-cursortheme ${cfg.workspace.cursor.theme}"
+ (
if cfg.workspace.cursor.size != null then
" --size ${builtins.toString cfg.workspace.cursor.size}"
else
""
)
else
""
}
${
if cfg.workspace.colorScheme != null then
"plasma-apply-colorscheme ${cfg.workspace.colorScheme}"
else
""
}
${
if cfg.workspace.iconTheme != null then
"${pkgs.kdePackages.plasma-workspace}/libexec/plasma-changeicons ${cfg.workspace.iconTheme}"
else
""
}
'';
priority = 1;
}
);
desktopScript."wallpaper_picture" = (
lib.mkIf (cfg.workspace.wallpaper != null) {
# We just put this here as we need this script to be a desktop-script in
# order to link it together with the other desktop-script (namely
# panels). Adding a comment with the wallpaper makes it so that when the
# wallpaper changes, the sha256sum also changes for the js file, which
# gives us the correct behavior with last_run files.
text = "// Wallpaper to set later: ${cfg.workspace.wallpaper}";
postCommands = ''
plasma-apply-wallpaperimage ${cfg.workspace.wallpaper}
'';
priority = 3;
}
);
desktopScript."wallpaper_potd" = (
lib.mkIf (cfg.workspace.wallpaperPictureOfTheDay != null) {
text = ''
// Wallpaper POTD
let allDesktops = desktops();
for (const desktop of allDesktops) {
desktop.wallpaperPlugin = "org.kde.potd";
desktop.currentConfigGroup = ["Wallpaper", "org.kde.potd", "General"];
desktop.writeConfig("Provider", "${cfg.workspace.wallpaperPictureOfTheDay.provider}");
desktop.writeConfig("UpdateOverMeteredConnection", "${
if (cfg.workspace.wallpaperPictureOfTheDay.updateOverMeteredConnection) then "1" else "0"
}");
${
lib.optionalString (
cfg.workspace.wallpaperFillMode != null
) ''desktop.writeConfig("FillMode", "${cfg.workspace.wallpaperFillMode}");''
}
}
'';
priority = 3;
}
);
desktopScript."wallpaper_plaincolor" = (
lib.mkIf (cfg.workspace.wallpaperPlainColor != null) {
text = ''
// Wallpaper plain color
let allDesktops = desktops();
for (var desktopIndex = 0; desktopIndex < allDesktops.length; desktopIndex++) {
var desktop = allDesktops[desktopIndex];
desktop.wallpaperPlugin = "org.kde.color";
desktop.currentConfigGroup = Array("Wallpaper", "org.kde.color", "General");
desktop.writeConfig("Color", "${cfg.workspace.wallpaperPlainColor}");
}
'';
priority = 3;
}
);
desktopScript."wallpaper_slideshow" = (
lib.mkIf (cfg.workspace.wallpaperSlideShow != null) {
text = ''
// Wallpaper slideshow
let allDesktops = desktops();
for (var desktopIndex = 0; desktopIndex < allDesktops.length; desktopIndex++) {
var desktop = allDesktops[desktopIndex];
desktop.wallpaperPlugin = "org.kde.slideshow";
desktop.currentConfigGroup = Array("Wallpaper", "org.kde.slideshow", "General");
desktop.writeConfig("SlidePaths", ${
with cfg.workspace.wallpaperSlideShow;
if ((builtins.isPath path) || (builtins.isString path)) then
"\"" + (builtins.toString path) + "\""
else
"[" + (builtins.concatStringsSep "," (map (s: "\"" + s + "\"") path)) + "]"
});
desktop.writeConfig("SlideInterval", "${builtins.toString cfg.workspace.wallpaperSlideShow.interval}");
${
lib.optionalString (
cfg.workspace.wallpaperFillMode != null
) ''desktop.writeConfig("FillMode", "${cfg.workspace.wallpaperFillMode}");''
}
}
'';
priority = 3;
}
);
};
}
);
}

View File

@ -18,43 +18,60 @@ import os
import re
import sys
from pathlib import Path
from typing import List, Dict, Callable, Optional, Tuple
from typing import Callable, Dict, List, Optional, Tuple
# The root directory where configuration files are stored.
XDG_CONFIG_HOME: str = os.path.expanduser(os.getenv("XDG_CONFIG_HOME", "~/.config"))
XDG_DATA_HOME: str = os.path.expanduser(os.getenv("XDG_DATA_HOME", "~/.local/share"))
class Rc2Nix:
# Files that we'll scan by default.
KNOWN_FILES: List[str] = [os.path.join(XDG_CONFIG_HOME, f) for f in [
"kcminputrc",
"kglobalshortcutsrc",
"kactivitymanagerdrc",
"ksplashrc",
"kwin_rules_dialogrc",
"kmixrc",
"kwalletrc",
"kgammarc",
"krunnerrc",
"klaunchrc",
"plasmanotifyrc",
"systemsettingsrc",
"kscreenlockerrc",
"kwinrulesrc",
"khotkeysrc",
"ksmserverrc",
"kded5rc",
"plasmarc",
"kwinrc",
"kdeglobals",
"baloofilerc",
"dolphinrc",
"klipperrc",
"plasma-localerc",
"kxkbrc",
"ffmpegthumbsrc",
"kservicemenurc",
"kiorc",
]]
KNOWN_CONFIG_FILES: List[str] = [
os.path.join(XDG_CONFIG_HOME, f)
for f in [
"kcminputrc",
"kglobalshortcutsrc",
"kactivitymanagerdrc",
"ksplashrc",
"kwin_rules_dialogrc",
"kmixrc",
"kwalletrc",
"kgammarc",
"krunnerrc",
"klaunchrc",
"plasmanotifyrc",
"systemsettingsrc",
"kscreenlockerrc",
"kwinrulesrc",
"khotkeysrc",
"ksmserverrc",
"kded5rc",
"plasmarc",
"kwinrc",
"kdeglobals",
"baloofilerc",
"dolphinrc",
"klipperrc",
"plasma-localerc",
"kxkbrc",
"ffmpegthumbsrc",
"kservicemenurc",
"kiorc",
"ktrashrc",
"kuriikwsfilterrc",
"plasmaparc",
"spectaclerc",
"katerc",
]
]
KNOWN_DATA_FILES: List[str] = [
os.path.join(XDG_DATA_HOME, f)
for f in [
"kate/anonymous.katesession",
"dolphin/view_properties/global/.directory",
]
]
class RcFile:
# Any group that matches a listed regular expression is blocked
@ -69,6 +86,7 @@ class Rc2Nix:
r"^PlasmaViews",
r"^ScreenConnectors$",
r"^Session:",
r"^Recent (Files|URLs)",
]
# Similar to the GROUP_BLOCK_LIST but for setting keys.
@ -97,21 +115,23 @@ class Rc2Nix:
def parse(self):
def is_group_line(line: str) -> bool:
return re.match(r'^\s*(\[[^\]]+\]){1,}\s*$', line) is not None
return re.match(r"^\s*(\[[^\]]+\])+\s*$", line) is not None
def is_setting_line(line: str) -> bool:
return re.match(r'^\s*([^=]+)=?(.*)\s*$', line) is not None
return re.match(r"^\s*([^=]+)=?(.*)\s*$", line) is not None
def parse_group(line: str) -> str:
return re.sub(r'\s*\[([^\]]+)\]\s*', r'\1/', line.replace("/", "\\\\/")).rstrip("/")
return re.sub(
r"\s*\[([^\]]+)\]\s*", r"\1/", line.replace("/", "\\\\/")
).rstrip("/")
def parse_setting(line: str) -> Tuple[str, str]:
match = re.match(r'^\s*([^=]+)=?(.*)\s*$', line)
match = re.match(r"^\s*([^=]+)=?(.*)\s*$", line)
if match:
return match.groups() #type: ignore
return match.groups() # type: ignore
raise Exception(f"{self.file_name}: can't parse setting line: {line}")
with open(self.file_name, 'r') as file:
with open(self.file_name, "r") as file:
for line in file:
line = line.strip()
if not line:
@ -139,9 +159,15 @@ class Rc2Nix:
val = val.strip()
if self.last_group is None:
raise Exception(f"{self.file_name}: setting outside of group: {key}={val}")
raise Exception(
f"{self.file_name}: setting outside of group: {key}={val}"
)
if should_skip_group(self.last_group) or should_skip_key(key) or should_skip_by_lambda(self.last_group, key):
if (
should_skip_group(self.last_group)
or should_skip_key(key)
or should_skip_by_lambda(self.last_group, key)
):
return
if self.last_group not in self.settings:
@ -150,12 +176,13 @@ class Rc2Nix:
class App:
def __init__(self, args: List[str]):
self.files: List[str] = Rc2Nix.KNOWN_FILES.copy()
self.config_files: List[str] = Rc2Nix.KNOWN_CONFIG_FILES.copy()
self.data_files: List[str] = Rc2Nix.KNOWN_DATA_FILES.copy()
self.config_settings: Dict[str, Dict[str, Dict[str, str]]] = {}
self.data_settings: Dict[str, Dict[str, Dict[str, str]]] = {}
def run(self):
settings: Dict[str, Dict[str, Dict[str, str]]] = {}
for file in self.files:
for file in self.config_files:
if not os.path.exists(file):
continue
@ -163,62 +190,97 @@ class Rc2Nix:
rc.parse()
path = Path(file).relative_to(XDG_CONFIG_HOME)
settings[str(path)] = rc.settings
self.config_settings[str(path)] = rc.settings
self.print_output(settings)
for file in self.data_files:
if not os.path.exists(file):
continue
def print_output(self, settings: Dict[str, Dict[str, Dict[str, str]]]):
rc = Rc2Nix.RcFile(file)
rc.parse()
path = Path(file).relative_to(XDG_DATA_HOME)
self.data_settings[str(path)] = rc.settings
self.print_output()
def print_output(self):
print("{")
print(" programs.plasma = {")
print(" enable = true;")
print(" shortcuts = {")
print(self.pp_shortcuts(settings.get("kglobalshortcutsrc", {}), 6))
print(
self.pp_shortcuts(self.config_settings.get("kglobalshortcutsrc", {}), 6)
)
print(" };")
print(" configFile = {")
print(self.pp_settings(settings, 6))
print(self.pp_settings(self.config_settings, 6))
print(" };")
print(" dataFile = {")
print(self.pp_settings(self.data_settings, 6))
print(" };")
print(" };")
print("}")
def pp_settings(self, settings: Dict[str, Dict[str, Dict[str, str]]], indent: int) -> str:
result : List[str] = []
def pp_settings(
self, settings: Dict[str, Dict[str, Dict[str, str]]], indent: int
) -> str:
result: List[str] = []
for file in sorted(settings.keys()):
if file != "kglobalshortcutsrc":
for group in sorted(settings[file].keys()):
for key in sorted(settings[file][group].keys()):
if key != "_k_friendly_name":
result.append(f"{' ' * indent}\"{file}\".\"{group}\".\"{key}\" = {nix_val(settings[file][group][key])};")
result.append(
f"{' ' * indent}\"{file}\".\"{group}\".\"{key}\" = {nix_val(settings[file][group][key])};"
)
return "\n".join(result)
def pp_shortcuts(self, groups: Dict[str, Dict[str, str]], indent: int) -> str:
if not groups:
return ""
result : List[str] = []
result: List[str] = []
for group in sorted(groups.keys()):
for action in sorted(groups[group].keys()):
if action != "_k_friendly_name":
keys = groups[group][action].split(r'(?<!\\),')[0].replace(r'\?', ',').replace(r'\t', '\t').split('\t')
keys = (
groups[group][action]
.split(r"(?<!\\),")[0]
.replace(r"\?", ",")
.replace(r"\t", "\t")
.split("\t")
)
if not keys or keys[0] == "none":
keys_str = "[ ]"
elif len(keys) > 1:
keys_str = f"[{' '.join(nix_val(k.rstrip(',')) for k in keys)}]"
keys_str = (
f"[{' '.join(nix_val(k.rstrip(',')) for k in keys)}]"
)
else:
ks = keys[0].split(",")
k = ks[0] if len(ks) == 3 and ks[0] == ks[1] else keys[0]
keys_str = "[ ]" if k == "" or k == "none" else nix_val(k.rstrip(","))
keys_str = (
"[ ]"
if k == "" or k == "none"
else nix_val(k.rstrip(","))
)
result.append(f"{' ' * indent}\"{group}\".\"{action}\" = {keys_str};")
result.append(
f"{' ' * indent}\"{group}\".\"{action}\" = {keys_str};"
)
return "\n".join(result)
def nix_val(s: Optional[str]) -> str:
if s is None:
return "null"
if re.match(r'^true|false$', s, re.IGNORECASE):
if re.match(r"^(true|false)$", s, re.IGNORECASE):
return s.lower()
if re.match(r'^[0-9]+(\.[0-9]+)?$', s):
if re.match(r"^[0-9]+(\.[0-9]+)?$", s):
return s
return '"' + re.sub(r'(?<!\\)"', r'\\"', s) + '"'
Rc2Nix.App(sys.argv[1:]).run()

View File

@ -3,15 +3,14 @@ import json
import os
import re
import sys
import os
from dataclasses import dataclass
from typing import Dict, Optional, Self, Set
from typing import Any, Optional, Self
# KDE has a bespoke escape format:
# https://github.com/KDE/kconfig/blob/44f98ff5cb9008436ba5ba385cae03bbd0ab33e6/src/core/kconfigini.cpp#L882
def unescape(s: str) -> str:
out = []
out: list[str] = []
while s:
parts = s.split("\\", 1)
out.append(parts.pop(0))
@ -57,41 +56,28 @@ def escape_bytes(c: str) -> str:
def escape(s: str) -> str:
if not s:
return s
s = list(s)
for i, c in enumerate(s):
ls: list[str] = list(s)
for i, c in enumerate(ls):
match c:
case "\n":
s[i] = "\\n"
ls[i] = "\\n"
case "\t":
s[i] = "\\t"
ls[i] = "\\t"
case "\r":
s[i] = "\\r"
ls[i] = "\\r"
case "\\":
s[i] = "\\\\"
ls[i] = "\\\\"
case "=" | "[" | "]":
s[i] = escape_bytes(c)
ls[i] = escape_bytes(c)
case _ if ord(c) < 32:
s[i] = escape_bytes(c)
ls[i] = escape_bytes(c)
case _:
pass
for i in (0, -1):
if s[i] == " ":
s[i] = "\\s"
return "".join(s)
if ls[i] == " ":
ls[i] = "\\s"
return "".join(ls)
# Some values need to be "transformed", such as being converted from hex to decimal
def transformValues(d_orig):
d = d_orig.copy()
c_path = os.path.expanduser("~/.config/kcminputrc")
if c_path in d:
for item_str in list(d[c_path]):
if item_str.startswith("Libinput/"):
item = d[c_path].pop(item_str)
item_str_list = item_str.split("/")
item_str_list[1] = str(int(item_str_list[1],16))
item_str_list[2] = str(int(item_str_list[2],16))
item_str = "/".join(item_str_list)
d[c_path][item_str] = item
return d;
@dataclass
class ConfigValue:
@ -118,14 +104,14 @@ class ConfigValue:
return key, value
@classmethod
def from_json(cls, value: dict) -> Self:
def from_json(cls, value: dict[str, Any]) -> Self:
key_value = (
str(value["value"])
if not isinstance(value["value"], bool)
else str(value["value"]).lower()
)
return cls(
value=escape(key_value),
value=escape(key_value) if value["escapeValue"] else key_value,
immutable=value["immutable"],
shellExpand=value["shellExpand"],
)
@ -155,31 +141,34 @@ class ConfigValue:
class KConfManager:
def __init__(
self, filepath: str, json_dict: Dict, reset: bool, immutable_by_default: bool
self,
filepath: str,
json_dict: dict[str, Any],
reset: bool,
immutable_by_default: bool,
):
"""
filepath (str): The full path to the config-file to manage
json_dict (Dict): The nix-configuration presented in a dictionary (converted from json)
reset (bool): Whether to reset the file, i.e. remove all the lines not present in the configuration
"""
self.data = {}
self.json_dict = json_dict
self.data: dict[tuple[str, ...], dict[str, ConfigValue]] = {}
self.filepath = filepath
self.reset = reset
self.immutable_by_default = immutable_by_default
self._json_value_checks()
self._json_value_checks(json_dict)
# The nix expressions will have / to separate groups, and \/ to escape a /.
# This parses the groups into tuples of unescaped group names.
self.json_dict = {
self.json_dict: dict[tuple[str, ...], Any] = {
tuple(
g.replace("\\/", "/")
for g in re.findall(r"(/|(?:[^/\\]|\\.)+)", group)[::2]
): entry
for group, entry in self.json_dict.items()
for group, entry in json_dict.items()
}
def _json_value_checks(self):
for group, entry in self.json_dict.items():
def _json_value_checks(self, json_dict: dict[str, Any]):
for group, entry in json_dict.items():
for key, value in entry.items():
non_default_immutability = (
value["immutable"] != self.immutable_by_default
@ -209,7 +198,7 @@ class KConfManager:
f"{base_msg} with shell-expansion enabled. Persistency with shell-expansion enabled is not supported"
)
def key_is_persistent(self, group, key) -> bool:
def key_is_persistent(self, group: tuple[str, ...], key: str) -> bool:
"""
Checks if a key in a group in the nix config is persistent.
"""
@ -271,14 +260,14 @@ class KConfManager:
if not value["persistent"]:
self.set_value(group, key, ConfigValue.from_json(value))
def set_value(self, group, key, value):
def set_value(self, group: tuple[str, ...], key: str, value: ConfigValue):
"""Adds an entry to the config. Creates necessary groups if needed."""
if not group in self.data:
self.data[group] = {}
self.data[group][key] = value
def remove_value(self, group, key):
def remove_value(self, group: tuple[str, ...], key: str):
"""Removes an entry from the config. Does nothing if the entry isn't there."""
if group in self.data and key in self.data[group]:
del self.data[group][key]
@ -313,18 +302,18 @@ class KConfManager:
f.write(f"{value.to_line(key)}\n")
def remove_config_files(d: Dict, reset_files: Set):
def remove_config_files(d: dict[str, Any], reset_files: set[str]):
"""
Removes files which doesn't have any configuration entries in d and which is
in the list of files to be reset by overrideConfig.
"""
for del_path in reset_files - set(d.keys()):
for file_to_del in glob.glob(del_path):
for file_to_del in glob.glob(del_path, recursive=True):
if os.path.isfile(file_to_del):
os.remove(file_to_del)
def write_configs(d: Dict, reset_files: Set, immutable_by_default: bool):
def write_configs(d: dict[str, Any], reset_files: set[str], immutable_by_default: bool):
for filepath, c in d.items():
config = KConfManager(
filepath, c, filepath in reset_files, immutable_by_default
@ -343,10 +332,9 @@ def main():
with open(json_path, "r") as f:
json_str = f.read()
reset_files = set(sys.argv[2].split(" ")) if sys.argv[2] != "" else set()
reset_files: set[str] = set(sys.argv[2].split(" ")) if sys.argv[2] != "" else set()
immutable_by_default = bool(sys.argv[3])
d_raw = json.loads(json_str)
d = transformValues(d_raw)
d = json.loads(json_str)
remove_config_files(d, reset_files)
write_configs(d, reset_files, immutable_by_default)

View File

@ -1,4 +1,10 @@
{ testers, home-manager-module, plasma-module, writeShellScriptBin, kdePackages }:
{
testers,
home-manager-module,
plasma-module,
writeShellScriptBin,
kdePackages,
}:
let
script = writeShellScriptBin "plasma-basic-test" ''
set -eu
@ -48,39 +54,41 @@ testers.nixosTest {
isNormalUser = true;
};
home-manager.users.fake = { lib, ... }: {
home.stateVersion = "23.11";
imports = [ plasma-module ];
programs.plasma = {
enable = true;
workspace.clickItemTo = "select";
# Test a variety of weird keys and groups
configFile.kdeglobals = {
group = {
" leading space" = " leading space";
key1 = 1;
key2 = {
value = 2;
immutable = true;
home-manager.users.fake =
{ lib, ... }:
{
home.stateVersion = "23.11";
imports = [ plasma-module ];
programs.plasma = {
enable = true;
workspace.clickItemTo = "select";
# Test a variety of weird keys and groups
configFile.kdeglobals = {
group = {
" leading space" = " leading space";
key1 = 1;
key2 = {
value = 2;
immutable = true;
};
"escaped[$i]" = {
value = "\${HOME}";
shellExpand = true;
};
};
"escaped[$i]" = {
value = "\${HOME}";
shellExpand = true;
"escaped\\/nested/group" = {
key3 = 3;
};
};
"escaped\\/nested/group" = {
key3 = 3;
};
};
home.activation.preseed = lib.hm.dag.entryBefore [ "configure-plasma" ] ''
mkdir -p ~/.config
cat <<EOF >> ~/.config/kdeglobals
[escaped/nested][group]
untouched = \svalue
EOF
'';
};
home.activation.preseed = lib.hm.dag.entryBefore [ "configure-plasma" ] ''
mkdir -p ~/.config
cat <<EOF >> ~/.config/kdeglobals
[escaped/nested][group]
untouched = \svalue
EOF
'';
};
};
testScript = ''

View File

@ -1,6 +1,4 @@
{ home-manager-module
, plasma-module
}:
{ home-manager-module, plasma-module }:
{ modulesPath, ... }:
{
@ -28,11 +26,13 @@
];
};
virtualisation.forwardPorts = [{
from = "host";
host.port = 2222;
guest.port = 22;
}];
virtualisation.forwardPorts = [
{
from = "host";
host.port = 2222;
guest.port = 22;
}
];
services.xserver.enable = true;
services.displayManager = {

View File

@ -1,30 +1,47 @@
#!/usr/bin/env nix
#! nix shell nixpkgs#python3Packages.python nixpkgs#ruby -c python3
import unittest
import subprocess
import os
import subprocess
import unittest
def red(s: str) -> str:
return '\033[91m' + s + '\033[0m'
return "\033[91m" + s + "\033[0m"
def green(s: str) -> str:
return '\033[32m' + s + '\033[0m'
return "\033[32m" + s + "\033[0m"
def gray(s: str) -> str:
return '\033[90m' + s + '\033[0m'
return "\033[90m" + s + "\033[0m"
current_dir = os.path.dirname(os.path.abspath(__file__))
def path(relative_path: str) -> str:
return os.path.abspath(os.path.join(current_dir, relative_path))
rc2nix_py = path("../../script/rc2nix.py")
rc2nix_rb = path("../../script/rc2nix.rb")
class TestRc2nix (unittest.TestCase):
class TestRc2nix(unittest.TestCase):
def test(self):
def run_script(*command: str) -> str:
rst = subprocess.run(command, env={'XDG_CONFIG_HOME': path('./test_data'), 'PATH': os.environ["PATH"]}, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
rst = subprocess.run(
command,
env={
"XDG_CONFIG_HOME": path("./test_data"),
"PATH": os.environ["PATH"],
},
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
print(red(rst.stderr))
rst.check_returncode()
return rst.stdout
@ -35,5 +52,5 @@ class TestRc2nix (unittest.TestCase):
self.assertEqual(rst_py.splitlines(), rst_rb.splitlines())
if __name__ == '__main__': # pragma: no cover
if __name__ == "__main__": # pragma: no cover
_ = unittest.main()

11
treefmt.toml Normal file
View File

@ -0,0 +1,11 @@
[formatter.nixfmt-rfc-style]
command = "nixfmt"
includes = ["*.nix"]
[formatter.black]
command = "black"
includes = ["*.py", "*.pyi"]
[formatter.isort]
command = "isort"
includes = ["*.py", "*.pyi"]