Add widget-specific options for panels (#161)

This commit is contained in:
Leah Amelia Chen 2024-05-04 23:09:22 +02:00 committed by GitHub
parent 4b56c71c9c
commit 1554e19ede
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 474 additions and 50 deletions

View File

@ -61,7 +61,19 @@
# widget will add them with the default configuration.
"org.kde.plasma.marginsseparator"
"org.kde.plasma.systemtray"
"org.kde.plasma.digitalclock"
# If you need configuration for your widget, instead of specifying the
# the keys and values directly using the config attribute as shown
# above, plasma-manager also provides some higher-level interfaces for
# configuring the widgets. See modules/widgets for supported widgets
# and options for these widgets. The widget below shows an example of
# usage, where we add a digital clock, setting 12h time and first day
# of the week to sunday.
{
digitalClock = {
calendar.firstDayOfWeek = "sunday";
time.format = "12h";
};
}
];
hiding = "autohide";
}

View File

@ -1,27 +1,13 @@
{ config, lib, ... }:
{ config
, lib
, ...
} @ args:
let
cfg = config.programs.plasma;
# Widget types
widgetType = lib.types.submodule {
options = {
name = lib.mkOption {
type = lib.types.str;
example = "org.kde.plasma.kickoff";
description = "The name of the widget to add.";
};
config = lib.mkOption {
type = with lib.types; nullOr (attrsOf (attrsOf (either str (listOf str))));
default = null;
example = {
General.icon = "nix-snowflake-white";
};
description = "Extra configuration-options for the widget.";
};
};
};
widgets = import ./widgets args;
panelType = lib.types.submodule ({config, ...}: {
panelType = lib.types.submodule ({ config, ... }: {
options = {
height = lib.mkOption {
type = lib.types.int;
@ -47,8 +33,8 @@ let
description = "The maximum allowed length/width of the panel.";
};
lengthMode = lib.mkOption {
type = lib.types.nullOr (lib.types.enum ["fit" "fill" "custom"]);
default =
type = lib.types.nullOr (lib.types.enum [ "fit" "fill" "custom" ]);
default =
if config.minLength != null || config.maxLength != null then
"custom"
else
@ -90,7 +76,7 @@ let
};
floating = lib.mkEnableOption "Enable or disable floating style (plasma 6 only).";
widgets = lib.mkOption {
type = with lib.types; listOf (either str widgetType);
type = with lib.types; listOf (either str widgets.type);
default = [
"org.kde.plasma.kickoff"
"org.kde.plasma.pager"
@ -179,38 +165,42 @@ let
}
'';
panelAddWidgetStr = widget: let
createWidget = name: ''panelWidgets["${name}"] = panel.addWidget("${name}");'';
in
if builtins.isString widget then
createWidget widget
panelAddWidgetStr = widget:
let
widget' = widgets.convert widget;
createWidget = name: ''panelWidgets["${name}"] = panel.addWidget("${name}");'';
in
if builtins.isString widget
then createWidget widget
else
''
${createWidget widget.name}
${stringIfNotNull widget.config (widgetConfigsToStr widget.name widget.config)}
${createWidget widget'.name}
${stringIfNotNull widget'.config (widgetConfigsToStr widget'.name widget'.config)}
'';
panelToLayout = panel: let
inherit (lib) boolToString optionalString;
inherit (builtins) toString;
in ''
var panel = new Panel;
panel.height = ${toString panel.height};
panel.floating = ${boolToString panel.floating};
${stringIfNotNull panel.alignment ''panel.alignment = "${panel.alignment}";''}
${stringIfNotNull panel.hiding ''panel.hiding = "${panel.hiding}";''}
${stringIfNotNull panel.location ''panel.location = "${panel.location}";''}
${stringIfNotNull panel.lengthMode (plasma6OnlyCmd ''panel.lengthMode = "${panel.lengthMode}";'')}
${stringIfNotNull panel.maxLength "panel.maximumLength = ${toString panel.maxLength};"}
${stringIfNotNull panel.minLength "panel.minimumLength = ${toString panel.minLength};"}
${stringIfNotNull panel.offset "panel.offset = ${toString panel.offset};"}
${optionalString (panel.screen != 0) ''panel.writeConfig("lastScreen[$i]", ${toString panel.screen});''}
panelToLayout = panel:
let
inherit (lib) boolToString optionalString;
inherit (builtins) toString;
in
''
var panel = new Panel;
panel.height = ${toString panel.height};
panel.floating = ${boolToString panel.floating};
${stringIfNotNull panel.alignment ''panel.alignment = "${panel.alignment}";''}
${stringIfNotNull panel.hiding ''panel.hiding = "${panel.hiding}";''}
${stringIfNotNull panel.location ''panel.location = "${panel.location}";''}
${stringIfNotNull panel.lengthMode (plasma6OnlyCmd ''panel.lengthMode = "${panel.lengthMode}";'')}
${stringIfNotNull panel.maxLength "panel.maximumLength = ${toString panel.maxLength};"}
${stringIfNotNull panel.minLength "panel.minimumLength = ${toString panel.minLength};"}
${stringIfNotNull panel.offset "panel.offset = ${toString panel.offset};"}
${optionalString (panel.screen != 0) ''panel.writeConfig("lastScreen[$i]", ${toString panel.screen});''}
var panelWidgets = {};
${lib.concatMapStringsSep "\n" panelAddWidgetStr panel.widgets}
var panelWidgets = {};
${lib.concatMapStringsSep "\n" panelAddWidgetStr panel.widgets}
${stringIfNotNull panel.extraSettings panel.extraSettings}
'';
${stringIfNotNull panel.extraSettings panel.extraSettings}
'';
in
{
options.programs.plasma.panels = lib.mkOption {

View File

@ -0,0 +1,51 @@
{ lib, ... } @ args:
let
sources = lib.mergeAttrsList (map (s: import s args) [
./digital-clock.nix
./system-monitor.nix
]);
compositeWidgetType = lib.pipe sources [
(builtins.mapAttrs (_: s:
lib.mkOption {
inherit (s) description;
type = lib.types.submodule {
options = s.opts;
};
}))
lib.types.attrTag
];
simpleWidgetType = lib.types.submodule {
options = {
name = lib.mkOption {
type = lib.types.str;
example = "org.kde.plasma.kickoff";
description = "The name of the widget to add.";
};
config = lib.mkOption {
type = with lib.types; nullOr (attrsOf (attrsOf (either str (listOf str))));
default = null;
example = {
General.icon = "nix-snowflake-white";
};
description = "Extra configuration-options for the widget.";
};
};
};
in
{
type = lib.types.either compositeWidgetType simpleWidgetType;
convert = composite:
let
inherit (builtins) length head attrNames hasAttr mapAttrs isAttrs;
keys = attrNames composite;
type = head keys;
converters = mapAttrs (_: s: s.convert) sources;
in
if isAttrs composite && length keys == 1 && hasAttr type converters
then converters.${type} composite.${type}
else composite; # not a known composite type
}

View File

@ -0,0 +1,245 @@
{ lib, ... }:
let
inherit (lib) mkEnableOption mkOption types;
fontType = types.submodule {
options = {
family = mkOption {
type = types.str;
example = "Noto Sans";
description = "The family of the font.";
};
bold = mkEnableOption "bold text";
italic = mkEnableOption "italic text";
weight = mkOption {
type = types.ints.between 1 1000;
default = 50;
description = "The weight of the font.";
};
style = mkOption {
type = types.nullOr types.str;
default = null;
description = "The custom style of the font.";
};
size = mkOption {
type = types.ints.positive;
default = 10;
description = "The size of the font.";
};
};
};
enums = {
date = {
format = [ "shortDate" "longDate" "isoDate" ];
position = [ "adaptive" "besideTime" "belowTime" ];
};
time = {
showSeconds = [ "never" "onlyInTooltip" "always" ];
format = [ "12h" "default" "24h" ];
};
timeZone.format = [ "code" "city" "offset" ];
calendar.weekdays = [ "sunday" "monday" "tuesday" "wednesday" "thursday" "friday" "saturday" ];
};
in
{
digitalClock = {
description = "A digital clock widget.";
opts = {
date = {
enable = mkEnableOption "showing the current date" // { default = true; };
format = mkOption {
type = types.nullOr (types.either (types.enum enums.date.format) (types.submodule {
options.custom = mkOption {
type = types.str;
example = "ddd d";
description = "The custom date format to use.";
};
}));
default = null;
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.
'';
};
position = mkOption {
type = types.nullOr (types.enum enums.date.position);
default = null;
description = ''
The position where the date is displayed.
Could be adaptive, always beside the displayed time, or below the displayed time.
'';
};
};
time = {
showSeconds = mkOption {
type = types.nullOr (types.enum enums.time.showSeconds);
default = null;
description = ''
When and where the seconds should be shown on the clock.
Could be never, only in the tooltip on hover, or always.
'';
};
format = mkOption {
type = types.nullOr (types.enum enums.time.format);
default = null;
description = ''
The time format used for this clock.
Could be 12-hour, the default for your locale, or 24-hour.
'';
};
};
timeZone = {
selected = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [ "Europe/Berlin" "Asia/Shanghai" ];
description = ''
The timezones that are configured for this clock.
The special value "Local" indicates the system's current timezone.
'';
};
lastSelected = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
The timezone to show upon widget restore.
The special value "Local" indicates the system's current timezone.
'';
};
changeOnScroll = mkOption {
type = types.bool;
default = false;
description = "Allow changing the displayed timezone by scrolling on the widget with the mouse wheel.";
};
format = mkOption {
type = types.nullOr (types.enum enums.timeZone.format);
default = null;
example = "code";
description = ''
The format of the timezone displayed, whether as a
code, full name of the city that the timezone belongs to,
or as an UTC offset.
For example, for the timezone Asia/Shanghai, the three formats
listed above would display "CST", "Shanghai" and "+8" respectively.
'';
};
alwaysShow = mkEnableOption "always showing the selected timezone, when it's the same with the system timezone";
};
calendar = {
firstDayOfWeek = mkOption {
type = types.nullOr (types.enum enums.calendar.weekdays);
default = null;
example = "monday";
description = ''
The first day of the week that the calendar uses.
If null, then the default for the user locale is used.
'';
};
plugins = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = "List of enabled calendar plugins, where additional event data can be sourced from.";
};
showWeekNumbers = mkEnableOption "showing week numbers in the calendar";
};
font = mkOption {
type = types.nullOr fontType;
default = null;
example = {
family = "Noto Sans";
bold = true;
size = 16;
};
description = ''
The font used for this clock.
If null, then it will use the system font and automatically expand to fill available space.
'';
};
};
convert =
{ date
, time
, timeZone
, calendar
, font
,
}:
let
inherit (builtins) toString;
inherit (lib) boolToString;
getEnum = es: e:
if e == null
then null
else
toString (
lib.lists.findFirstIndex
(x: x == e)
(throw "getEnum: nonexistent key ${e}! This is a bug!")
es
);
in
{
name = "org.kde.plasma.digitalclock";
config.Appearance = lib.filterAttrs (_: v: v != null) (
{
showDate = boolToString date.enable;
dateDisplayFormat = getEnum enums.date.position date.position;
dateFormat =
if date.format ? custom
then "custom"
else date.format;
customDateFormat =
if date.format ? custom
then date.format.custom
else null;
showSeconds = getEnum enums.time.showSeconds time.showSeconds;
use24hFormat = getEnum enums.time.format time.format;
selectedTimeZones = timeZone.selected;
lastSelectedTimezone = timeZone.lastSelected;
wheelChangesTimezone = boolToString timeZone.changeOnScroll;
displayTimezoneFormat = getEnum enums.timeZone.format timeZone.format;
showLocalTimezone = boolToString timeZone.alwaysShow;
firstDayOfWeek =
if calendar.firstDayOfWeek != null
then getEnum enums.calendar.weekdays calendar.firstDayOfWeek
else null;
enabledCalendarPlugins = calendar.plugins;
autoFontAndSize = boolToString (font == null);
}
// lib.optionalAttrs (font != null) {
fontFamily = font.family;
boldText = boolToString font.bold;
italicText = boolToString font.italic;
fontWeight = toString font.weight;
fontStyleName = font.styleName;
fontSize = toString font.size;
}
);
};
};
}

View File

@ -0,0 +1,126 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
in
{
systemMonitor = {
description = "A system monitor widget.";
opts = {
title = mkOption {
type = types.nullOr types.str;
default = null;
description = "The title of this system monitor.";
};
displayStyle = mkOption {
type = types.nullOr types.str;
default = null;
example = "org.kde.ksysguard.barchart";
description = "The display style of the chart. Uses the internal plugin name.";
};
sensors = mkOption {
type = types.nullOr (types.listOf (types.submodule {
options = {
name = mkOption {
type = types.str;
example = "cpu/all/usage";
description = "The name of the sensor.";
};
color = mkOption {
type = types.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";
};
};
}));
default = null;
example = [
{
name = "gpu/gpu1/usage";
color = "180,190,254";
}
];
description = ''
The list of sensors displayed as a part of the graph/chart.
'';
};
totalSensors = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [ "cpu/all/usage" ];
description = ''
The list of "total sensors" displayed on top of the graph/chart.
'';
};
textOnlySensors = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [ "cpu/all/averageTemperature" "cpu/all/averageFrequency" ];
description = ''
The list of text-only sensors, displayed in the pop-up upon clicking the widget.
'';
};
};
convert =
{ title
, displayStyle
, totalSensors
, sensors
, textOnlySensors
,
}:
let
# KDE expects a key/value pair like this:
# ```ini
# highPrioritySensorIds=["cpu/all/usage", "cpu/all/averageTemperature"]
# ```
#
# Which is **different** to what would happen if you pass a list of strings to the JS script:
# ```ini
# highPrioritySensorIds=cpu/all/usage,cpu/all/averageTemperature
# ```
#
# 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;
# {name, color} -> {name, value}
# Convert the sensor attrset into a name-value pair expected by listToAttrs
toColorKV =
{ name
, color
,
}: {
inherit name;
value = color;
};
in
{
name = "org.kde.plasma.systemmonitor";
config = lib.filterAttrsRecursive (_: v: v != null) {
Appearance = {
inherit title;
chartFace = displayStyle;
};
SensorColors =
if sensors != null
then builtins.listToAttrs (map toColorKV sensors)
else null;
Sensors = {
highPrioritySensorIds =
if sensors != null
then toEscapedList (map (s: s.name) sensors)
else null;
lowPrioritySensorIds = toEscapedList textOnlySensors;
totalSensors = toEscapedList totalSensors;
};
};
};
};
}