├── .envrc ├── docs ├── src │ ├── options │ │ └── .gitkeep │ ├── SUMMARY.md │ ├── getting-started │ │ ├── README.md │ │ ├── flakes.md │ │ └── stable-nix.md │ └── README.md ├── book.toml ├── generate-website.nix └── options.nix ├── .github ├── FUNDING.yml └── workflows │ ├── website.yml │ └── update.yml ├── .gitignore ├── lib ├── extend-lib.nix ├── import-modules.nix ├── utils.nix ├── default.nix ├── modules.nix ├── applications.nix ├── applets.nix ├── ron.nix └── options.nix ├── .editorconfig ├── tests ├── default.nix ├── to-ron.nix └── from-ron.nix ├── modules ├── misc │ ├── deprecation.nix │ ├── info.nix │ └── meta.nix ├── applets │ └── by-name │ │ ├── audio │ │ └── default.nix │ │ ├── time │ │ └── default.nix │ │ ├── app-list │ │ └── default.nix │ │ └── panel-button │ │ └── default.nix ├── applications │ └── by-name │ │ ├── cosmic-ext-tweaks │ │ └── default.nix │ │ ├── tasks │ │ └── default.nix │ │ ├── cosmic-store │ │ └── default.nix │ │ ├── cosmic-player │ │ └── default.nix │ │ ├── cosmic-ext-calculator │ │ └── default.nix │ │ ├── cosmic-ext-ctl │ │ └── default.nix │ │ ├── cosmic-edit │ │ └── default.nix │ │ ├── forecast │ │ └── default.nix │ │ ├── cosmic-applibrary │ │ └── default.nix │ │ ├── cosmic-files │ │ └── default.nix │ │ └── cosmic-term │ │ └── default.nix ├── all-modules.nix ├── default.nix ├── system-actions.nix ├── idle.nix ├── shortcuts.nix ├── wallpapers.nix ├── panels.nix ├── files.nix └── compositor.nix ├── .treefmt.toml ├── generators ├── actions-for-shortcuts │ ├── default.nix │ └── main.py ├── default.nix └── generate.nix ├── shell.nix ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix └── generated └── actions-for-shortcuts.json /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /docs/src/options/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: HeitorAugustoLN 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/debug/ 2 | **/target/ 3 | **/*.rs.bk 4 | result* 5 | .direnv/ 6 | -------------------------------------------------------------------------------- /lib/extend-lib.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.extend ( 3 | final: prev: { 4 | cosmic = import ./. { lib = final; }; 5 | types = prev.types // import ./types.nix { lib = final; }; 6 | } 7 | ) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.rs] 12 | indent_size = 4 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](README.md) 4 | 5 | - [Getting started](getting-started/README.md) 6 | - [Stable Nix](getting-started/stable-nix.md) 7 | - [Flakes](getting-started/flakes.md) 8 | 9 | - [Options](options/README.md) 10 | -------------------------------------------------------------------------------- /tests/default.nix: -------------------------------------------------------------------------------- 1 | { lib, pkgs }: 2 | let 3 | args = pkgs // { 4 | lib = import ../lib/extend-lib.nix { inherit lib; }; 5 | 6 | inherit callTest; 7 | }; 8 | 9 | callTest = lib.callPackageWith args; 10 | in 11 | { 12 | from-ron = callTest ./from-ron.nix { }; 13 | to-ron = callTest ./to-ron.nix { }; 14 | } 15 | -------------------------------------------------------------------------------- /lib/import-modules.nix: -------------------------------------------------------------------------------- 1 | { 2 | modules, 3 | config, 4 | lib, 5 | pkgs, 6 | ... 7 | }: 8 | let 9 | toModule = file: { 10 | _file = file; 11 | imports = [ 12 | (import file { 13 | inherit config pkgs; 14 | lib = import ./extend-lib.nix { inherit lib; }; 15 | }) 16 | ]; 17 | }; 18 | in 19 | map toModule modules 20 | -------------------------------------------------------------------------------- /docs/src/getting-started/README.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | `cosmic-manager` supports both stable Nix and [Flakes](https://wiki.nixos.org/wiki/Flakes). So you 4 | can choose the one that best fits your needs. Follow the steps below to get started with managing 5 | your COSMIC desktop declaratively using `cosmic-manager`. 6 | 7 | - [Stable Nix](stable-nix.md) 8 | - [Flakes](flakes.md) 9 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "cosmic-manager" 3 | authors = ["HeitorAugustoLN"] 4 | description = "Manage COSMIC desktop declaratively using home-manager" 5 | language = "en" 6 | multilingual = false 7 | src = "src" 8 | 9 | [output.html] 10 | no-section-label = true 11 | git-repository-url = "https://github.com/HeitorAugustoLN/cosmic-manager" 12 | git-repository-icon = "fa-github" 13 | -------------------------------------------------------------------------------- /modules/misc/deprecation.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { 3 | imports = [ 4 | (lib.mkRemovedOptionModule [ 5 | "programs" 6 | "cosmic-manager" 7 | "enable" 8 | ] "The cosmic-manager CLI is no longer available. Please remove this option.") 9 | (lib.mkRemovedOptionModule [ 10 | "programs" 11 | "cosmic-manager" 12 | "package" 13 | ] "The cosmic-manager CLI is no longer available. Please remove this option.") 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /.treefmt.toml: -------------------------------------------------------------------------------- 1 | [formatter.deadnix] 2 | command = "deadnix" 3 | options = ["--edit"] 4 | includes = ["*.nix"] 5 | 6 | [formatter.nixfmt] 7 | command = "nixfmt" 8 | includes = ["*.nix"] 9 | 10 | [formatter.rustfmt] 11 | command = "rustfmt" 12 | options = ["--config", "skip_children=true", "--edition", "2021"] 13 | includes = ["*.rs"] 14 | 15 | [formatter.statix] 16 | command = "statix-fix" 17 | includes = ["*.nix"] 18 | 19 | [formatter.taplo] 20 | command = "taplo" 21 | options = ["format"] 22 | includes = ["*.toml"] 23 | -------------------------------------------------------------------------------- /generators/actions-for-shortcuts/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | fetchFromGitHub, 3 | runCommand, 4 | python3, 5 | }: 6 | let 7 | cosmic-settings-daemon = fetchFromGitHub { 8 | owner = "pop-os"; 9 | repo = "cosmic-settings-daemon"; 10 | tag = "epoch-1.0.0-beta.3"; 11 | hash = "sha256-CtHy8qy7CatbErNZKu1pLFC9aUWLj0r87+lvRB16oSE="; 12 | }; 13 | in 14 | runCommand "actions-for-shortcuts.json" { buildInputs = [ python3 ]; } '' 15 | python3 ${./main.py} ${cosmic-settings-daemon}/config/src/shortcuts/action.rs actions-for-shortcuts.json 16 | cp actions-for-shortcuts.json $out 17 | '' 18 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import { }, 3 | ... 4 | }: 5 | let 6 | inherit (pkgs) lib; 7 | in 8 | pkgs.mkShell { 9 | strictDeps = true; 10 | 11 | nativeBuildInputs = 12 | let 13 | statix-fix = pkgs.writeShellScriptBin "statix-fix" '' 14 | for file in "''$@"; do 15 | ${lib.getExe pkgs.statix} fix "$file" 16 | done 17 | ''; 18 | in 19 | with pkgs; 20 | [ 21 | cargo 22 | clippy 23 | deadnix 24 | nixfmt-rfc-style 25 | rustc 26 | rustfmt 27 | statix-fix 28 | taplo 29 | ]; 30 | } 31 | -------------------------------------------------------------------------------- /lib/utils.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { 3 | cleanNullsExceptOptional = 4 | let 5 | cleanNullsExceptOptional' = 6 | attrset: 7 | lib.filterAttrs (_: value: value != null) ( 8 | builtins.mapAttrs ( 9 | _: value: 10 | if 11 | builtins.isAttrs value && !(value ? __type && value.__type == "optional" && value.value == null) 12 | then 13 | cleanNullsExceptOptional' value 14 | else 15 | value 16 | ) attrset 17 | ); 18 | in 19 | cleanNullsExceptOptional'; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /generators/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? 3 | let 4 | lock = (builtins.fromJSON (builtins.readFile ../flake.lock)).nodes.nixpkgs.locked; 5 | nixpkgs = fetchTarball { 6 | url = 7 | assert lock.type == "github"; 8 | "https://github.com/${lock.owner}/${lock.repo}/archive/${lock.rev}.tar.gz"; 9 | sha256 = lock.narHash; 10 | }; 11 | in 12 | import nixpkgs { }, 13 | lib ? pkgs.lib, 14 | ... 15 | }: 16 | lib.fix (self: { 17 | actions-for-shortcuts = pkgs.callPackage ./actions-for-shortcuts { }; 18 | default = self.generate; 19 | generate = lib.callPackageWith (pkgs // self) ./generate.nix { }; 20 | }) 21 | -------------------------------------------------------------------------------- /modules/applets/by-name/audio/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.cosmic.applets.mkCosmicApplet { 3 | name = "audio"; 4 | originalName = "Sound"; 5 | identifier = "com.system76.CosmicAppletAudio"; 6 | configurationVersion = 1; 7 | 8 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 9 | 10 | settingsOptions = 11 | let 12 | inherit (lib.cosmic) defaultNullOpts; 13 | in 14 | { 15 | show_media_controls_in_top_panel = defaultNullOpts.mkBool true '' 16 | Whether to show media controls in the top panel. 17 | ''; 18 | }; 19 | 20 | settingsExample = { 21 | show_media_controls_in_top_panel = true; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /modules/applications/by-name/cosmic-ext-tweaks/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.cosmic.applications.mkCosmicApplication { 3 | name = "cosmic-ext-tweaks"; 4 | originalName = "COSMIC Calculator"; 5 | identifier = "dev.edfloreshz.CosmicTweaks"; 6 | configurationVersion = 1; 7 | 8 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 9 | 10 | settingsOptions.app_theme = 11 | lib.cosmic.defaultNullOpts.mkRonEnum [ "Dark" "Light" "System" ] 12 | { 13 | __type = "enum"; 14 | variant = "System"; 15 | } 16 | '' 17 | The theme of the application. 18 | ''; 19 | 20 | settingsExample.app_theme = { 21 | __type = "enum"; 22 | variant = "System"; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /modules/all-modules.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (builtins) readDir; 4 | inherit (lib) foldlAttrs optional; 5 | inherit (lib.filesystem) listFilesRecursive; 6 | 7 | appletsByName = ./applets/by-name; 8 | applicationsByName = ./applications/by-name; 9 | misc = ./misc; 10 | in 11 | [ 12 | ./appearance.nix 13 | ./compositor.nix 14 | ./files.nix 15 | ./idle.nix 16 | ./panels.nix 17 | ./shortcuts.nix 18 | ./system-actions.nix 19 | ./wallpapers.nix 20 | ] 21 | ++ foldlAttrs ( 22 | prev: name: type: 23 | prev ++ optional (type == "directory") (applicationsByName + "/${name}") 24 | ) [ ] (readDir applicationsByName) 25 | ++ lib.foldlAttrs ( 26 | prev: name: type: 27 | prev ++ optional (type == "directory") (appletsByName + "/${name}") 28 | ) [ ] (readDir appletsByName) 29 | ++ listFilesRecursive misc 30 | -------------------------------------------------------------------------------- /modules/applications/by-name/tasks/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.cosmic.applications.mkCosmicApplication { 3 | name = "tasks"; 4 | originalName = "Tasks"; 5 | identifier = "dev.edfloreshz.Tasks"; 6 | configurationVersion = 1; 7 | 8 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 9 | 10 | settingsOptions = 11 | let 12 | inherit (lib.cosmic) defaultNullOpts; 13 | in 14 | { 15 | app_theme = 16 | defaultNullOpts.mkRonEnum [ "Dark" "Light" "System" ] 17 | { 18 | __type = "enum"; 19 | variant = "System"; 20 | } 21 | '' 22 | The theme of the application. 23 | ''; 24 | }; 25 | 26 | settingsExample = { 27 | app_theme = { 28 | __type = "enum"; 29 | variant = "System"; 30 | }; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /modules/applications/by-name/cosmic-store/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.cosmic.applications.mkCosmicApplication { 3 | name = "cosmic-store"; 4 | originalName = "COSMIC Store"; 5 | identifier = "com.system76.CosmicStore"; 6 | configurationVersion = 1; 7 | 8 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 9 | 10 | settingsOptions = 11 | let 12 | inherit (lib.cosmic) defaultNullOpts; 13 | in 14 | { 15 | app_theme = 16 | defaultNullOpts.mkRonEnum [ "Dark" "Light" "System" ] 17 | { 18 | __type = "enum"; 19 | variant = "System"; 20 | } 21 | '' 22 | The theme of the application. 23 | ''; 24 | }; 25 | 26 | settingsExample = { 27 | app_theme = { 28 | __type = "enum"; 29 | variant = "System"; 30 | }; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /modules/applications/by-name/cosmic-player/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.cosmic.applications.mkCosmicApplication { 3 | name = "cosmic-player"; 4 | originalName = "COSMIC Media Player"; 5 | identifier = "com.system76.CosmicPlayer"; 6 | configurationVersion = 1; 7 | 8 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 9 | 10 | settingsOptions = 11 | let 12 | inherit (lib.cosmic) defaultNullOpts; 13 | in 14 | { 15 | app_theme = 16 | defaultNullOpts.mkRonEnum [ "Dark" "Light" "System" ] 17 | { 18 | __type = "enum"; 19 | variant = "System"; 20 | } 21 | '' 22 | The theme of the application. 23 | ''; 24 | }; 25 | 26 | settingsExample = { 27 | app_theme = { 28 | __type = "enum"; 29 | variant = "System"; 30 | }; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /modules/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | { 8 | imports = import ../lib/import-modules.nix { 9 | inherit config lib pkgs; 10 | modules = import ./all-modules.nix { inherit lib; }; 11 | }; 12 | 13 | options.wayland.desktopManager.cosmic.enable = lib.mkEnableOption "" // { 14 | description = '' 15 | Whether to enable declarative configuration management for the COSMIC Desktop environment. 16 | 17 | When enabled, this module allows you to manage your COSMIC Desktop settings through 18 | `home-manager`. 19 | ''; 20 | }; 21 | 22 | config = 23 | let 24 | cfg = config.wayland.desktopManager.cosmic; 25 | in 26 | lib.mkIf cfg.enable { 27 | _module.args.cosmicLib = lib.mkDefault (import ../lib/extend-lib.nix { inherit lib; }); 28 | lib.cosmic = lib.mkDefault config._module.args.cosmicLib.cosmic; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /modules/applications/by-name/cosmic-ext-calculator/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.cosmic.applications.mkCosmicApplication { 3 | name = "cosmic-ext-calculator"; 4 | originalName = "COSMIC Calculator"; 5 | identifier = "dev.edfloreshz.Calculator"; 6 | configurationVersion = 1; 7 | 8 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 9 | 10 | # NOTE: There is also a history configuration entry, but I will not add it here, since I don't think it belong as a setting, but rather an state entry 11 | # It will probably open a PR there, fixing it. 12 | settingsOptions.app_theme = 13 | lib.cosmic.defaultNullOpts.mkRonEnum [ "Dark" "Light" "System" ] 14 | { 15 | __type = "enum"; 16 | variant = "System"; 17 | } 18 | '' 19 | The theme of the application. 20 | ''; 21 | 22 | settingsExample.app_theme = { 23 | __type = "enum"; 24 | variant = "System"; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /docs/generate-website.nix: -------------------------------------------------------------------------------- 1 | { 2 | stdenvNoCC, 3 | writeShellApplication, 4 | mdbook, 5 | python3, 6 | }: 7 | { options, ... }@args: 8 | stdenvNoCC.mkDerivation ( 9 | finalAttrs: 10 | args 11 | // { 12 | nativeBuildInputs = [ mdbook ]; 13 | 14 | dontPatch = true; 15 | dontConfigure = true; 16 | doCheck = false; 17 | 18 | buildPhase = '' 19 | runHook preBuild 20 | 21 | ln -s ${options} src/options/README.md 22 | mdbook build 23 | 24 | runHook postBuild 25 | ''; 26 | 27 | installPhase = '' 28 | runHook preInstall 29 | 30 | mv book $out 31 | 32 | runHook postInstall 33 | ''; 34 | 35 | passthru = { 36 | serve = writeShellApplication { 37 | name = "serve"; 38 | 39 | runtimeInputs = [ python3 ]; 40 | 41 | text = '' 42 | python -m http.server --bind 127.0.0.1 --directory ${finalAttrs.finalPackage} 43 | ''; 44 | }; 45 | }; 46 | } 47 | ) 48 | -------------------------------------------------------------------------------- /modules/applications/by-name/cosmic-ext-ctl/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | { 8 | imports = [ 9 | # TODO: Remove after COSMIC stable release. 10 | (lib.mkRenamedOptionModule 11 | [ "wayland" "desktopManager" "cosmic" "installCosmicCtl" ] 12 | [ "programs" "cosmic-ext-ctl" "enable" ] 13 | ) 14 | ]; 15 | 16 | options.programs.cosmic-ext-ctl = { 17 | enable = lib.mkEnableOption "" // { 18 | default = config.wayland.desktopManager.cosmic.enable; 19 | defaultText = lib.literalMD "`config.wayland.desktopManager.cosmic.enable`"; 20 | description = '' 21 | Whether to enable `cosmic-ctl`. 22 | 23 | When disabled, `cosmic-ctl` will not be installed. 24 | But it will still be used by `cosmic-manager` for managing COSMIC Desktop configurations. 25 | ''; 26 | }; 27 | 28 | package = lib.mkPackageOption pkgs "cosmic-ext-ctl" { }; 29 | }; 30 | 31 | config = 32 | let 33 | cfg = config.programs.cosmic-ext-ctl; 34 | in 35 | lib.mkIf cfg.enable { 36 | home.packages = [ cfg.package ]; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Heitor Augusto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /modules/system-actions.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | inherit (lib) mkIf types; 4 | inherit (lib.cosmic) defaultNullOpts; 5 | in 6 | { 7 | options.wayland.desktopManager.cosmic.systemActions = 8 | defaultNullOpts.mkRonMapOf types.str 9 | { 10 | __type = "map"; 11 | value = [ 12 | { 13 | key = { 14 | __type = "enum"; 15 | variant = "Terminal"; 16 | }; 17 | 18 | value = "ghostty"; 19 | } 20 | { 21 | key = { 22 | __type = "enum"; 23 | variant = "Launcher"; 24 | }; 25 | 26 | value = "krunner"; 27 | } 28 | ]; 29 | } 30 | '' 31 | Overrides for COSMIC Desktop system actions. 32 | ''; 33 | 34 | config = 35 | let 36 | cfg = config.wayland.desktopManager.cosmic; 37 | in 38 | mkIf (cfg.systemActions != null) { 39 | wayland.desktopManager.cosmic.configFile."com.system76.CosmicSettings.Shortcuts" = { 40 | entries.system_actions = cfg.systemActions; 41 | version = 1; 42 | }; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /docs/src/README.md: -------------------------------------------------------------------------------- 1 | # cosmic-manager 2 | 3 | Manage COSMIC desktop declaratively using [`home-manager`](https://github.com/nix-community/home-manager). 4 | 5 | ## What is cosmic-manager? 6 | 7 | `cosmic-manager` is a declarative way to manage your COSMIC desktop using `home-manager`. 8 | It provides a set of modules that can be used to configure your favorite desktop environment. 9 | This means your settings are easy to backup, share, replicate and restore. It also provides a 10 | command-line interface with some utilities to use in conjunction with `cosmic-manager`. 11 | 12 | ## Getting started 13 | 14 | Ready to give it a try? Check out the [Getting started](./getting-started/index.md) guide to get started with `cosmic-manager`. 15 | Once you're done, you can take a look at all of our available options in the [Options](./options/index.md) section. 16 | 17 | ## Found a bug? 18 | 19 | If you found a bug, please open an issue on the [issue tracker](https://github.com/HeitorAugustoLN/cosmic-manager/issues/new). 20 | 21 | ## Licenses 22 | 23 | - **GPL-3.0-only**: Applies to the `cosmic-manager` command-line interface located in the `cosmic-manager` subdirectory. 24 | - **MIT**: Covers Nix packages, `home-manager` modules, `cosmic-manager` library, documentation, and basically everything else. 25 | -------------------------------------------------------------------------------- /modules/misc/info.nix: -------------------------------------------------------------------------------- 1 | # Based on: https://github.com/nix-community/nixvim/blob/35d6c12626f9895cd5d8ccf5d19c3d00de394334/modules/misc/nixvim-info.nix 2 | { lib, ... }: 3 | { 4 | options.meta.cosmicInfo = lib.mkOption { 5 | # This will create an attrset of the form: 6 | # 7 | # { path.to.plugin.name = ; } 8 | # 9 | # 10 | # Where is an attrset of the form: 11 | # { 12 | # file = "path"; 13 | # description = null or ""; 14 | # url = null or ""; 15 | # } 16 | type = (with lib.types; nullOr attrs) // { 17 | merge = 18 | _: defs: 19 | builtins.foldl' 20 | ( 21 | acc: def: 22 | lib.recursiveUpdate acc ( 23 | lib.setAttrByPath def.value.path { 24 | inherit (def) file; 25 | url = def.value.url or null; 26 | description = def.value.description or null; 27 | } 28 | ) 29 | ) 30 | { 31 | wayland.desktopManager.cosmic.applets = { }; 32 | programs = { }; 33 | } 34 | defs; 35 | }; 36 | internal = true; 37 | default = null; 38 | description = '' 39 | cosmic-manager related information for each module. 40 | ''; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /modules/applets/by-name/time/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.cosmic.applets.mkCosmicApplet { 3 | name = "time"; 4 | originalName = "Date, Time & Calendar"; 5 | identifier = "com.system76.CosmicAppletTime"; 6 | configurationVersion = 1; 7 | 8 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 9 | 10 | settingsOptions = 11 | let 12 | inherit (lib.cosmic) defaultNullOpts; 13 | in 14 | { 15 | first_day_of_week = defaultNullOpts.mkU8 6 '' 16 | Which day of the week should be considered the first day. 17 | 18 | `0` for Monday, `1` for Tuesday, and so on. 19 | ''; 20 | 21 | military_time = defaultNullOpts.mkBool false '' 22 | Whether to use the 24-hour format for the clock. 23 | ''; 24 | 25 | show_date_in_top_panel = defaultNullOpts.mkBool true '' 26 | Whether to show the current date in the top panel. 27 | ''; 28 | 29 | show_seconds = defaultNullOpts.mkBool false '' 30 | Whether to show the seconds in the clock. 31 | ''; 32 | 33 | show_weekday = defaultNullOpts.mkBool true '' 34 | Whether to show the current weekday in the clock. 35 | ''; 36 | }; 37 | 38 | settingsExample = { 39 | first_day_of_week = 6; 40 | military_time = false; 41 | show_date_in_top_panel = true; 42 | show_seconds = false; 43 | show_weekday = true; 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cosmic-manager 2 | 3 | Manage COSMIC desktop declaratively using [`home-manager`](https://github.com/nix-community/home-manager). 4 | 5 | ## What is cosmic-manager? 6 | 7 | `cosmic-manager` is a declarative way to manage your COSMIC desktop using `home-manager`. 8 | It provides a set of modules that can be used to configure your favorite desktop environment. 9 | This means your settings are easy to backup, share, replicate and restore. It also provides a 10 | command-line interface with some utilities to use in conjunction with `cosmic-manager`. 11 | 12 | ## Getting started 13 | 14 | Ready to give it a try? Check out the [Getting started](https://heitoraugustoln.github.io/cosmic-manager/getting-started/index.html) guide to get started with `cosmic-manager`. 15 | Once you're done, you can take a look at all of our available options in the [Options](https://heitoraugustoln.github.io/cosmic-manager/options/index.html) section. 16 | 17 | ## Found a bug? 18 | 19 | If you found a bug, please open an issue on the [issue tracker](https://github.com/HeitorAugustoLN/cosmic-manager/issues/new). 20 | 21 | ## Licenses 22 | 23 | - [GPL-3.0-only](./LICENSE-GPL): Applies to the `cosmic-manager` command-line interface located in the `cosmic-manager` subdirectory. 24 | - [MIT](./LICENSE-MIT): Covers Nix packages, `home-manager` modules, `cosmic-manager` library, documentation, and basically everything else. 25 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Build & deploy website 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: 7 | - 'docs/**' 8 | - '**.nix' 9 | pull_request: 10 | paths: 11 | - 'docs/**' 12 | - '**.nix' 13 | 14 | jobs: 15 | build: 16 | name: Build site 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | - name: Setup Nix 22 | uses: cachix/install-nix-action@v30 23 | - name: Run build 24 | run: | 25 | nix build \ 26 | --print-build-logs \ 27 | --show-trace \ 28 | '.#site' 29 | - name: Get artifact directory 30 | id: find-path 31 | run: | 32 | # exit if no `result` from `nix build` 33 | [ ! -L result ] && exit 1 34 | echo "path=$(readlink -f result)" >> "$GITHUB_OUTPUT" 35 | - name: Upload artifact 36 | uses: actions/upload-pages-artifact@v3 37 | with: 38 | path: ${{ steps.find-path.outputs.path }} 39 | deploy: 40 | name: Deploy site 41 | if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' 42 | needs: build 43 | runs-on: ubuntu-latest 44 | environment: 45 | name: github-pages 46 | url: ${{ steps.deploy.outputs.page_url }} 47 | permissions: 48 | id-token: write 49 | pages: write 50 | steps: 51 | - name: Deploy to GitHub pages 52 | id: deploy 53 | uses: actions/deploy-pages@v4 54 | -------------------------------------------------------------------------------- /modules/misc/meta.nix: -------------------------------------------------------------------------------- 1 | # Based on nixpkgs meta.nix with a few modifications 2 | { lib, ... }: 3 | let 4 | maintainer = lib.mkOptionType { 5 | name = "maintainer"; 6 | check = maintainer: builtins.elem maintainer (builtins.attrValues lib.maintainers); 7 | merge = 8 | _: defs: 9 | let 10 | def = lib.last defs; 11 | in 12 | { 13 | ${def.file} = def.value; 14 | }; 15 | }; 16 | 17 | listOfMaintainers = lib.types.listOf maintainer // { 18 | # Returns attrset of 19 | # { "module-file" = [ 20 | # "maintainer1 " 21 | # "maintainer2 " ]; 22 | # } 23 | merge = 24 | loc: defs: 25 | lib.pipe defs [ 26 | (lib.imap1 ( 27 | n: def: 28 | lib.imap1 ( 29 | m: value: 30 | maintainer.merge (loc ++ [ "[${toString n}-${toString m}]" ]) [ 31 | { 32 | inherit (def) file; 33 | inherit value; 34 | } 35 | ] 36 | ) def.value 37 | )) 38 | lib.flatten 39 | lib.zipAttrs 40 | ]; 41 | }; 42 | in 43 | { 44 | options.meta = { 45 | maintainers = lib.mkOption { 46 | type = listOfMaintainers; 47 | internal = true; 48 | default = [ ]; 49 | example = lib.literalExpression "[ lib.maintainers.HeitorAugustoLN ]"; 50 | description = '' 51 | List of maintainers of each module. This option should be defined at 52 | most once per module. 53 | ''; 54 | }; 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /docs/options.nix: -------------------------------------------------------------------------------- 1 | { lib, nixosOptionsDoc }: 2 | { 3 | version, 4 | modules ? [ moduleRoot ], 5 | moduleRoot, 6 | }: 7 | let 8 | baseDeclarationUrl = "https://github.com/HeitorAugustoLN/cosmic-manager/blob/main"; 9 | declarationIsOurs = declaration: lib.hasPrefix (toString moduleRoot) (toString declaration); 10 | declarationSubpath = declaration: lib.removePrefix (toString ../. + "/") (toString declaration); 11 | 12 | toGithubDeclaration = 13 | declaration: 14 | let 15 | subpath = declarationSubpath declaration; 16 | in 17 | { 18 | url = "${baseDeclarationUrl}/${subpath}"; 19 | name = ""; 20 | }; 21 | 22 | evaluatedModules = lib.evalModules { 23 | modules = modules ++ [ 24 | { 25 | options.system.nixos.release = lib.mkOption { 26 | type = lib.types.str; 27 | default = lib.trivial.release; 28 | readOnly = true; 29 | }; 30 | 31 | config = { 32 | _module.check = false; 33 | }; 34 | } 35 | ]; 36 | }; 37 | 38 | optionsDoc = nixosOptionsDoc { 39 | options = builtins.removeAttrs evaluatedModules.options [ 40 | "_module" 41 | "system" 42 | ]; 43 | 44 | transformOptions = 45 | option: 46 | option 47 | // { 48 | declarations = map ( 49 | declaration: if declarationIsOurs declaration then toGithubDeclaration declaration else declaration 50 | ) option.declarations; 51 | }; 52 | 53 | documentType = "none"; 54 | revision = version; 55 | warningsAreErrors = false; 56 | }; 57 | in 58 | optionsDoc.optionsCommonMark 59 | -------------------------------------------------------------------------------- /lib/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (lib) genAttrs makeExtensible; 4 | in 5 | makeExtensible (self: { 6 | applets = import ./applets.nix { inherit lib; }; 7 | applications = import ./applications.nix { inherit lib; }; 8 | modules = import ./modules.nix { inherit lib; }; 9 | options = import ./options.nix { inherit lib; }; 10 | ron = import ./ron.nix { inherit lib; }; 11 | utils = import ./utils.nix { inherit lib; }; 12 | 13 | inherit (self.applets) mkCosmicApplet; 14 | inherit (self.applications) mkCosmicApplication; 15 | inherit (self.modules) 16 | applyExtraConfig 17 | messagePrefix 18 | mkAssertion 19 | mkAssertions 20 | mkThrow 21 | mkWarning 22 | mkWarnings 23 | ; 24 | inherit (self.ron) 25 | fromRON 26 | importRON 27 | isRONType 28 | mkRON 29 | toRON 30 | ; 31 | inherit (self.options) 32 | defaultNullOpts 33 | literalRON 34 | mkNullOrOption 35 | mkNullOrOption' 36 | mkRONExpression 37 | mkSettingsOption 38 | nestedLiteral 39 | nestedLiteralRON 40 | nestedRONExpression 41 | RONExpression 42 | ; 43 | inherit (self.utils) cleanNullsExceptOptional; 44 | 45 | # TODO: Remove after COSMIC stable release 46 | generators = 47 | genAttrs 48 | [ 49 | "fromRON" 50 | "toRON" 51 | ] 52 | ( 53 | name: 54 | self.mkWarning "lib" 55 | "`cosmicLib.cosmic.generators.${name}` has been renamed to `cosmicLib.cosmic.ron.${name}`." 56 | self.ron.${name} 57 | ); 58 | 59 | mkRon = 60 | self.mkWarning "lib" "`cosmicLib.cosmic.mkRon` has been renamed to `cosmicLib.cosmic.mkRON`" 61 | self.ron.mkRON; 62 | }) 63 | -------------------------------------------------------------------------------- /lib/modules.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (builtins) isFunction; 4 | inherit (lib) 5 | assertMsg 6 | mkIf 7 | optionalString 8 | pipe 9 | trim 10 | warn 11 | ; 12 | 13 | messagePrefix = scope: "cosmic-manager${optionalString (scope != null) "(${scope})"}:"; 14 | 15 | mkThrow = 16 | scope: error: 17 | let 18 | prefix = messagePrefix scope; 19 | in 20 | throw "${prefix} ${trim error}"; 21 | in 22 | { 23 | inherit messagePrefix mkThrow; 24 | 25 | applyExtraConfig = 26 | { 27 | extraConfig, 28 | cfg, 29 | opts, 30 | enabled ? 31 | cfg.enable 32 | or (mkThrow "applyExtraConfig" "`enabled` argument was not provided and `cfg.enable` option was found."), 33 | }: 34 | let 35 | maybeApply = 36 | variable: maybeFunction: if isFunction maybeFunction then maybeFunction variable else maybeFunction; 37 | in 38 | pipe extraConfig [ 39 | (maybeApply cfg) 40 | (maybeApply opts) 41 | (mkIf enabled) 42 | ]; 43 | 44 | mkAssertion = 45 | scope: assertion: message: 46 | let 47 | prefix = messagePrefix scope; 48 | in 49 | assertMsg assertion "${prefix} ${trim message}"; 50 | 51 | mkAssertions = 52 | scope: assertions: 53 | let 54 | prefix = messagePrefix scope; 55 | process = assertion: { 56 | inherit (assertion) assertion; 57 | message = "${prefix} ${trim assertion.message}"; 58 | }; 59 | in 60 | map process assertions; 61 | 62 | mkWarning = 63 | scope: message: value: 64 | let 65 | prefix = messagePrefix scope; 66 | in 67 | warn "${prefix} ${trim message}" value; 68 | 69 | mkWarnings = 70 | scope: warnings: 71 | let 72 | prefix = messagePrefix scope; 73 | process = warning: [ "${prefix} ${trim warning}" ]; 74 | in 75 | map process warnings; 76 | } 77 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": [ 6 | "nixpkgs" 7 | ] 8 | }, 9 | "locked": { 10 | "lastModified": 1759362264, 11 | "narHash": "sha256-wfG0S7pltlYyZTM+qqlhJ7GMw2fTF4mLKCIVhLii/4M=", 12 | "owner": "hercules-ci", 13 | "repo": "flake-parts", 14 | "rev": "758cf7296bee11f1706a574c77d072b8a7baa881", 15 | "type": "github" 16 | }, 17 | "original": { 18 | "owner": "hercules-ci", 19 | "repo": "flake-parts", 20 | "type": "github" 21 | } 22 | }, 23 | "home-manager": { 24 | "inputs": { 25 | "nixpkgs": [ 26 | "nixpkgs" 27 | ] 28 | }, 29 | "locked": { 30 | "lastModified": 1760103600, 31 | "narHash": "sha256-R4cltQFceN3POiPhBu7aTKsrwqTiwo6zjzmitrHD80E=", 32 | "owner": "nix-community", 33 | "repo": "home-manager", 34 | "rev": "bcccb01d0a353c028cc8cb3254cac7ebae32929e", 35 | "type": "github" 36 | }, 37 | "original": { 38 | "owner": "nix-community", 39 | "repo": "home-manager", 40 | "type": "github" 41 | } 42 | }, 43 | "nixpkgs": { 44 | "locked": { 45 | "lastModified": 1759831965, 46 | "narHash": "sha256-vgPm2xjOmKdZ0xKA6yLXPJpjOtQPHfaZDRtH+47XEBo=", 47 | "owner": "NixOS", 48 | "repo": "nixpkgs", 49 | "rev": "c9b6fb798541223bbb396d287d16f43520250518", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "NixOS", 54 | "ref": "nixos-unstable", 55 | "repo": "nixpkgs", 56 | "type": "github" 57 | } 58 | }, 59 | "root": { 60 | "inputs": { 61 | "flake-parts": "flake-parts", 62 | "home-manager": "home-manager", 63 | "nixpkgs": "nixpkgs" 64 | } 65 | } 66 | }, 67 | "root": "root", 68 | "version": 7 69 | } 70 | -------------------------------------------------------------------------------- /modules/applets/by-name/app-list/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.cosmic.applets.mkCosmicApplet { 3 | name = "app-list"; 4 | originalName = "App Tray"; 5 | identifier = "com.system76.CosmicAppList"; 6 | configurationVersion = 1; 7 | 8 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 9 | 10 | settingsOptions = 11 | let 12 | inherit (lib.cosmic) defaultNullOpts; 13 | in 14 | { 15 | enable_drag_source = defaultNullOpts.mkBool true '' 16 | Whether to enable dragging applications from the application list. 17 | ''; 18 | 19 | favorites = 20 | defaultNullOpts.mkListOf lib.types.str 21 | [ 22 | "firefox" 23 | "com.system76.CosmicFiles" 24 | "com.system76.CosmicEdit" 25 | "com.system76.CosmicTerm" 26 | "com.system76.CosmicSettings" 27 | ] 28 | '' 29 | A list of applications to always be shown in the application list. 30 | ''; 31 | 32 | filter_top_levels = 33 | defaultNullOpts.mkRonOptionalOf 34 | (lib.types.ronEnum [ 35 | "ActiveWorkspace" 36 | "ConfiguredOutput" 37 | ]) 38 | { 39 | __type = "optional"; 40 | value = { 41 | __type = "enum"; 42 | variant = "ActiveWorkspace"; 43 | }; 44 | } 45 | '' 46 | The top level filter to use for the application list. 47 | ''; 48 | }; 49 | 50 | settingsExample = { 51 | enable_drag_source = true; 52 | favorites = [ 53 | "firefox" 54 | "com.system76.CosmicFiles" 55 | "com.system76.CosmicEdit" 56 | "com.system76.CosmicTerm" 57 | "com.system76.CosmicSettings" 58 | ]; 59 | filter_top_levels = { 60 | __type = "optional"; 61 | value = { 62 | __type = "enum"; 63 | variant = "ActiveWorkspace"; 64 | }; 65 | }; 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /generators/generate.nix: -------------------------------------------------------------------------------- 1 | { 2 | writeShellApplication, 3 | deno, 4 | actions-for-shortcuts, 5 | }: 6 | writeShellApplication { 7 | name = "generate"; 8 | 9 | runtimeInputs = [ deno ]; 10 | 11 | # Based of nixvim generate script 12 | text = '' 13 | repo_root=$(git rev-parse --show-toplevel) 14 | generated_dir=$repo_root/generated 15 | 16 | commit= 17 | while [ $# -gt 0 ]; do 18 | case "$1" in 19 | --commit) commit=1 20 | ;; 21 | --*) echo "unknown option $1" 22 | ;; 23 | *) echo "unexpected argument $1" 24 | ;; 25 | esac 26 | shift 27 | done 28 | 29 | echo "Generating actions for shortcuts..." 30 | install -Dm644 "${actions-for-shortcuts}" "$generated_dir/actions-for-shortcuts.json" 31 | deno fmt "$generated_dir/actions-for-shortcuts.json" 32 | 33 | if [ -n "$commit" ]; then 34 | cd "$generated_dir" 35 | git add . 36 | 37 | # Construct a msg body from `git status -- .` 38 | body=$( 39 | git status \ 40 | --short \ 41 | --ignored=no \ 42 | --untracked-files=no \ 43 | --no-ahead-behind \ 44 | -- . \ 45 | | sed \ 46 | -e 's/^\s*\([A-Z]\)\s*/\1 /' \ 47 | -e 's/^A/Added/' \ 48 | -e 's/^M/Updated/' \ 49 | -e 's/^R/Renamed/' \ 50 | -e 's/^D/Removed/' \ 51 | -e 's/^/- /' 52 | ) 53 | 54 | # Construct the commit message based on the body 55 | # NOTE: Can't use `wc -l` due to how `echo` pipes its output 56 | count=$(echo -n "$body" | awk 'END {print NR}') 57 | if [ "$count" -gt 1 ] || [ ''${#body} -gt 50 ]; then 58 | msg=$(echo -e "generated: Update\n\n$body") 59 | else 60 | msg="generated:''${body:1}" 61 | fi 62 | 63 | # Commit if there are changes 64 | if [ "$count" -gt 0 ]; then 65 | echo "Committing $count changes..." 66 | echo "$msg" 67 | git commit -m "$msg" --no-verify 68 | fi 69 | fi 70 | ''; 71 | } 72 | -------------------------------------------------------------------------------- /modules/idle.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | { 3 | options.wayland.desktopManager.cosmic.idle = 4 | let 5 | inherit (lib.cosmic) defaultNullOpts; 6 | 7 | idleSubmodule = lib.types.submodule { 8 | freeformType = with lib.types; attrsOf anything; 9 | options = { 10 | screen_off_time = 11 | defaultNullOpts.mkRonOptionalOf lib.types.ints.u32 12 | { 13 | __type = "optional"; 14 | value = 90000; 15 | } 16 | '' 17 | The time in milliseconds before the screen turns off. 18 | ''; 19 | 20 | suspend_on_ac_time = 21 | defaultNullOpts.mkRonOptionalOf lib.types.ints.u32 22 | { 23 | __type = "optional"; 24 | value = 180000; 25 | } 26 | '' 27 | The time in milliseconds before the system suspends when on AC power. 28 | ''; 29 | 30 | suspend_on_battery_time = 31 | defaultNullOpts.mkRonOptionalOf lib.types.ints.u32 32 | { 33 | __type = "optional"; 34 | value = 90000; 35 | } 36 | '' 37 | The time in milliseconds before the system suspends when on battery power. 38 | ''; 39 | }; 40 | }; 41 | in 42 | defaultNullOpts.mkNullable idleSubmodule 43 | { 44 | screen_off_time = { 45 | __type = "optional"; 46 | value = 90000; 47 | }; 48 | suspend_on_ac_time = { 49 | __type = "optional"; 50 | value = 180000; 51 | }; 52 | suspend_on_battery_time = { 53 | __type = "optional"; 54 | value = 90000; 55 | }; 56 | } 57 | '' 58 | Settings for the COSMIC idle manager. 59 | ''; 60 | 61 | config.wayland.desktopManager.cosmic.configFile."com.system76.CosmicIdle" = 62 | let 63 | cfg = config.wayland.desktopManager.cosmic; 64 | in 65 | lib.mkIf (cfg.idle != null) { 66 | entries = cfg.idle; 67 | version = 1; 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Manage COSMIC desktop declaratively using home-manager"; 3 | 4 | inputs = { 5 | flake-parts = { 6 | url = "github:hercules-ci/flake-parts"; 7 | inputs.nixpkgs-lib.follows = "nixpkgs"; 8 | }; 9 | 10 | home-manager = { 11 | url = "github:nix-community/home-manager"; 12 | inputs.nixpkgs.follows = "nixpkgs"; 13 | }; 14 | 15 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 16 | }; 17 | 18 | outputs = 19 | inputs: 20 | inputs.flake-parts.lib.mkFlake { inherit inputs; } { 21 | systems = [ 22 | "aarch64-linux" 23 | "i686-linux" 24 | "x86_64-linux" 25 | ]; 26 | 27 | flake.homeManagerModules = { 28 | default = inputs.self.homeManagerModules.cosmic-manager; 29 | cosmic-manager = ./modules; 30 | }; 31 | 32 | perSystem = 33 | { pkgs, self', ... }: 34 | let 35 | version = inputs.self.shortRev or inputs.self.dirtyShortRev or "unknown"; 36 | 37 | mkOptionsDoc = pkgs.callPackage ./docs/options.nix { }; 38 | mkSite = pkgs.callPackage ./docs/generate-website.nix { }; 39 | in 40 | { 41 | checks = pkgs.callPackages ./tests { }; 42 | 43 | devShells.default = import ./shell.nix { 44 | inherit pkgs; 45 | inherit (self'.packages) cosmic-manager; 46 | }; 47 | 48 | formatter = pkgs.treefmt; 49 | 50 | packages = { 51 | default = self'.packages.cosmic-manager; 52 | 53 | home-manager-options = mkOptionsDoc { 54 | inherit version; 55 | moduleRoot = ./modules; 56 | }; 57 | 58 | site = 59 | let 60 | src = 61 | let 62 | inherit (pkgs) lib; 63 | in 64 | lib.fileset.toSource { 65 | root = ./.; 66 | fileset = lib.fileset.unions [ 67 | ./docs/book.toml 68 | ./docs/src 69 | ]; 70 | }; 71 | in 72 | mkSite { 73 | pname = "cosmic-manager-website"; 74 | inherit version src; 75 | 76 | sourceRoot = "${src.name}/docs"; 77 | options = self'.packages.home-manager-options; 78 | }; 79 | }; 80 | }; 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /docs/src/getting-started/flakes.md: -------------------------------------------------------------------------------- 1 | # Flakes 2 | 3 | Flakes are the preferred way to use `cosmic-manager`, offering a modern and reproducible way to manage your COSMIC desktop. 4 | 5 | If you have Flakes enabled, this will be the easiest and most flexible method to get started. 6 | 7 | ## Adding `cosmic-manager` as an Input 8 | 9 | First, add `cosmic-manager` to the inputs in your `flake.nix` file. Here’s an example: 10 | 11 | ```nix 12 | { 13 | inputs = { 14 | nixpkgs.url = "nixpkgs/nixos-unstable"; 15 | cosmic-manager = { 16 | url = "github:HeitorAugustoLN/cosmic-manager"; 17 | inputs = { 18 | nixpkgs.follows = "nixpkgs"; 19 | home-manager.follows = "home-manager"; 20 | }; 21 | }; 22 | home-manager = { 23 | url = "github:nix-community/home-manager"; 24 | inputs.nixpkgs.follows = "nixpkgs"; 25 | }; 26 | }; 27 | 28 | outputs = { nixpkgs, home-manager, cosmic-manager, ... }: { 29 | # Outputs will depend on your setup (home-manager as NixOS module or standalone home-manager). 30 | }; 31 | } 32 | ``` 33 | 34 | ## Using `cosmic-manager` with a `home-manager` as NixOS module installation 35 | 36 | If you’re using `home-manager` as a NixOS module, your `flake.nix` file might look like this: 37 | 38 | ```nix 39 | { 40 | outputs = { nixpkgs, home-manager, cosmic-manager, ... }: { 41 | nixosConfigurations.my-computer = nixpkgs.lib.nixosSystem { 42 | system = "x86_64-linux"; 43 | modules = [ 44 | home-manager.nixosModules.home-manager 45 | { 46 | home-manager.users.cosmic-user = { 47 | imports = [ 48 | ./home.nix 49 | cosmic-manager.homeManagerModules.cosmic-manager 50 | ]; 51 | }; 52 | } 53 | ]; 54 | }; 55 | }; 56 | } 57 | ``` 58 | 59 | ## Using `cosmic-manager` with a standalone `home-manager` installation 60 | 61 | If you’re using `home-manager` as a standalone tool, your `flake.nix` file might look like this: 62 | 63 | ```nix 64 | { 65 | outputs = { nixpkgs, home-manager, cosmic-manager, ... }: { 66 | homeConfigurations."cosmic-user@my-computer" = home-manager.lib.homeManagerConfiguration { 67 | pkgs = nixpkgs.legacyPackages.x86_64-linux; 68 | modules = [ 69 | ./home.nix 70 | cosmic-manager.homeManagerModules.cosmic-manager 71 | ]; 72 | }; 73 | }; 74 | } 75 | ``` 76 | 77 | Follow this guide, and you’ll have `cosmic-manager` up and running with Flakes in no time! 78 | -------------------------------------------------------------------------------- /modules/applications/by-name/cosmic-edit/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.cosmic.applications.mkCosmicApplication { 3 | name = "cosmic-edit"; 4 | originalName = "COSMIC Text Editor"; 5 | identifier = "com.system76.CosmicEdit"; 6 | configurationVersion = 1; 7 | 8 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 9 | 10 | settingsOptions = 11 | let 12 | inherit (lib.cosmic) defaultNullOpts; 13 | in 14 | { 15 | app_theme = 16 | defaultNullOpts.mkRonEnum [ "Dark" "Light" "System" ] 17 | { 18 | __type = "enum"; 19 | variant = "System"; 20 | } 21 | '' 22 | The theme of the application. 23 | ''; 24 | 25 | auto_indent = defaultNullOpts.mkBool true '' 26 | Whether to automatically indent the code. 27 | ''; 28 | 29 | find_case_sensitive = defaultNullOpts.mkBool false '' 30 | Whether to make the search case sensitive. 31 | ''; 32 | 33 | find_use_regex = defaultNullOpts.mkBool false '' 34 | Whether to use regular expressions in the search. 35 | ''; 36 | 37 | find_wrap_around = defaultNullOpts.mkBool true '' 38 | Whether to wrap around the search. 39 | ''; 40 | 41 | font_name = defaultNullOpts.mkStr "Fira Mono" '' 42 | The name of the font to be used. 43 | ''; 44 | 45 | font_size = defaultNullOpts.mkU16 14 '' 46 | The size of the font to be used. 47 | ''; 48 | 49 | highlight_current_line = defaultNullOpts.mkBool true '' 50 | Whether to highlight the current line. 51 | ''; 52 | 53 | line_numbers = defaultNullOpts.mkBool true '' 54 | Whether to show line numbers. 55 | ''; 56 | 57 | syntax_theme_dark = defaultNullOpts.mkStr "COSMIC Dark" '' 58 | The name of the dark syntax theme to be used. 59 | ''; 60 | 61 | syntax_theme_light = defaultNullOpts.mkStr "COSMIC Light" '' 62 | The name of the light syntax theme to be used. 63 | ''; 64 | 65 | tab_width = defaultNullOpts.mkU16 4 '' 66 | The width of the tab. 67 | ''; 68 | 69 | vim_bindings = defaultNullOpts.mkBool false '' 70 | Whether to enable Vim bindings. 71 | ''; 72 | 73 | word_wrap = defaultNullOpts.mkBool true '' 74 | Whether to wrap the words. 75 | ''; 76 | }; 77 | 78 | settingsExample = { 79 | app_theme = { 80 | __type = "enum"; 81 | variant = "System"; 82 | }; 83 | auto_indent = true; 84 | find_case_sensitive = false; 85 | find_use_regex = false; 86 | find_wrap_around = true; 87 | font_name = "Fira Mono"; 88 | font_size = 16; 89 | highlight_current_line = true; 90 | line_numbers = true; 91 | syntax_theme_dark = "COSMIC Dark"; 92 | syntax_theme_light = "COSMIC Light"; 93 | tab_width = 2; 94 | vim_bindings = true; 95 | word_wrap = true; 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /docs/src/getting-started/stable-nix.md: -------------------------------------------------------------------------------- 1 | # Stable Nix 2 | 3 | When using stable Nix, you have several options to install `cosmic-manager` based on your preferences and setup. Choose one of the methods below to get started. 4 | 5 | ## Option 1: Using `npins` 6 | 7 | [`npins`](https://github.com/andir/npins) simplifies the process of ["pinning"](https://nix.dev/tutorials/first-steps/towards-reproducibility-pinning-nixpkgs) external dependencies for your configuration. 8 | 9 | ### Steps: 10 | 11 | 1. Ensure you have followed [the `npins` getting started guide](https://github.com/andir/npins#getting-started). 12 | 2. Add `cosmic-manager` to your configuration: 13 | 14 | ```sh 15 | npins add --name cosmic-manager github HeitorAugustoLN cosmic-manager 16 | ``` 17 | 18 | 3. Update your Nix configuration: 19 | 20 | **With `home-manager` integrated into NixOS:** 21 | 22 | ```nix 23 | let 24 | sources = import ./npins; 25 | in 26 | { 27 | home-manager.users.cosmic-user = { 28 | imports = [ 29 | (sources.cosmic-manager + "/modules") 30 | ]; 31 | 32 | wayland.desktopManager.cosmic.enable = true; 33 | }; 34 | } 35 | ``` 36 | 37 | **With standalone `home-manager`:** 38 | 39 | ```nix 40 | let 41 | sources = import ./npins.nix; 42 | in 43 | { 44 | imports = [ 45 | (sources.cosmic-manager + "/modules") 46 | ]; 47 | 48 | home.username = "cosmic-user"; 49 | programs.home-manager.enable = true; 50 | 51 | wayland.desktopManager.cosmic.enable = true; 52 | } 53 | ``` 54 | 55 | ## Option 2: Using Channels 56 | 57 | [Nix channels](https://nixos.org/manual/nix/stable/command-ref/nix-channel.html) offer a simple way to download, update, and use `cosmic-manager` modules. However, this approach sacrifices reproducibility across different machines. 58 | 59 | ### Steps: 60 | 61 | 1. Add the `cosmic-manager` channel: 62 | 63 | ```sh 64 | sudo nix-channel --add https://github.com/HeitorAugustoLN/cosmic-manager/archive/main.tar.gz cosmic-manager 65 | sudo nix-channel --update 66 | ``` 67 | 68 | 2. Update your Nix configuration: 69 | 70 | **With `home-manager` integrated into NixOS:** 71 | 72 | ```nix 73 | { 74 | home-manager.users.cosmic-user = { 75 | imports = [ 76 | 77 | ]; 78 | 79 | wayland.desktopManager.cosmic.enable = true; 80 | }; 81 | } 82 | ``` 83 | 84 | **With standalone `home-manager`:** 85 | 86 | ```nix 87 | { 88 | imports = [ 89 | 90 | ]; 91 | 92 | home.username = "cosmic-user"; 93 | programs.home-manager.enable = true; 94 | 95 | wayland.desktopManager.cosmic.enable = true; 96 | } 97 | ``` 98 | 99 | ## Which Option Should I Choose? 100 | 101 | - **Use `npins`**: If you want better reproducibility and a cleaner way to manage external dependencies. 102 | - **Use Channels**: If you prefer a simpler setup and are okay with sacrificing strict reproducibility. 103 | 104 | With either method, you’re set to manage your COSMIC desktop declaratively! 105 | -------------------------------------------------------------------------------- /modules/applets/by-name/panel-button/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (lib) mkOption types; 4 | inherit (lib.cosmic) defaultNullOpts mkCosmicApplet mkRONExpression; 5 | in 6 | mkCosmicApplet { 7 | name = "panel-button"; 8 | identifier = "com.system76.CosmicPanelButton"; 9 | configurationVersion = 1; 10 | settingsDescription = "Configuration entries for all panel buttons."; 11 | 12 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 13 | 14 | settingsOptions = { 15 | configs = 16 | let 17 | configsSubmodule = types.submodule { 18 | freeformType = with types; attrsOf anything; 19 | options.force_presentation = mkOption { 20 | type = 21 | with types; 22 | maybeRonRaw ( 23 | ronOptionalOf ( 24 | maybeRonRaw (ronEnum [ 25 | "Icon" 26 | "Text" 27 | ]) 28 | ) 29 | ); 30 | example = mkRONExpression 0 { 31 | __type = "optional"; 32 | value = { 33 | __type = "enum"; 34 | variant = "Icon"; 35 | }; 36 | } null; 37 | description = '' 38 | Force the presentation of the buttons on the panel. 39 | ''; 40 | }; 41 | }; 42 | in 43 | defaultNullOpts.mkNullable (types.ronMapOf configsSubmodule) 44 | { 45 | __type = "map"; 46 | value = [ 47 | { 48 | key = "Panel"; 49 | value = { 50 | force_presentation = { 51 | __type = "optional"; 52 | value = { 53 | __type = "enum"; 54 | variant = "Icon"; 55 | }; 56 | }; 57 | }; 58 | } 59 | { 60 | key = "Dock"; 61 | value = { 62 | force_presentation = { 63 | __type = "optional"; 64 | value = { 65 | __type = "enum"; 66 | variant = "Text"; 67 | }; 68 | }; 69 | }; 70 | } 71 | ]; 72 | } 73 | '' 74 | Configurations for the panel buttons. 75 | ''; 76 | }; 77 | 78 | settingsExample = { 79 | configs = { 80 | __type = "map"; 81 | value = [ 82 | { 83 | key = "Panel"; 84 | value = { 85 | force_presentation = { 86 | __type = "optional"; 87 | value = { 88 | __type = "enum"; 89 | variant = "Icon"; 90 | }; 91 | }; 92 | }; 93 | } 94 | { 95 | key = "Dock"; 96 | value = { 97 | force_presentation = { 98 | __type = "optional"; 99 | value = { 100 | __type = "enum"; 101 | variant = "Text"; 102 | }; 103 | }; 104 | }; 105 | } 106 | ]; 107 | }; 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /lib/applications.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (lib) 4 | getAttrFromPath 5 | mkEnableOption 6 | mkIf 7 | mkMerge 8 | mkPackageOption 9 | optionalAttrs 10 | optionals 11 | setAttrByPath 12 | ; 13 | inherit (lib.cosmic) applyExtraConfig mkAssertions mkSettingsOption; 14 | in 15 | { 16 | mkCosmicApplication = 17 | { 18 | configurationVersion, 19 | description ? null, 20 | extraConfig ? _: { }, 21 | extraOptions ? { }, 22 | hasSettings ? true, 23 | identifier, 24 | imports ? [ ], 25 | maintainers, 26 | name, 27 | originalName ? name, 28 | package ? name, 29 | settingsDescription ? "Configuration entries for ${originalName}.", 30 | settingsOptions ? { }, 31 | settingsExample ? null, 32 | }@args: 33 | let 34 | loc = [ 35 | "programs" 36 | name 37 | ]; 38 | 39 | module = 40 | { 41 | config, 42 | options, 43 | pkgs, 44 | ... 45 | }: 46 | let 47 | cfg = getAttrFromPath loc config; 48 | opts = getAttrFromPath loc options; 49 | in 50 | { 51 | options = setAttrByPath loc ( 52 | { 53 | enable = mkEnableOption originalName; 54 | package = mkPackageOption pkgs package { 55 | extraDescription = "Set to `null` if you don't want to install the package."; 56 | nullable = true; 57 | }; 58 | } 59 | // optionalAttrs hasSettings { 60 | settings = mkSettingsOption { 61 | description = settingsDescription; 62 | example = settingsExample; 63 | options = settingsOptions; 64 | }; 65 | } 66 | // extraOptions 67 | ); 68 | 69 | config = mkIf cfg.enable (mkMerge [ 70 | { 71 | assertions = mkAssertions name [ 72 | { 73 | assertion = cfg.enable -> config.wayland.desktopManager.cosmic.enable; 74 | message = "COSMIC Desktop declarative configuration must be enabled to use ${originalName} module."; 75 | } 76 | ]; 77 | 78 | home.packages = optionals (cfg.package != null) [ cfg.package ]; 79 | } 80 | 81 | (mkIf hasSettings { 82 | wayland.desktopManager.cosmic = { 83 | configFile.${identifier} = { 84 | entries = cfg.settings; 85 | version = configurationVersion; 86 | }; 87 | }; 88 | }) 89 | 90 | (mkIf (args ? extraConfig) (applyExtraConfig { 91 | inherit cfg extraConfig opts; 92 | })) 93 | ]); 94 | 95 | meta = { 96 | inherit maintainers; 97 | cosmicInfo = { 98 | inherit description; 99 | url = args.url or opts.package.default.meta.homepage; 100 | path = loc; 101 | }; 102 | }; 103 | }; 104 | in 105 | { 106 | imports = imports ++ [ module ]; 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /generators/actions-for-shortcuts/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | import sys 4 | from typing import Any, Dict, List, Set 5 | 6 | 7 | class EnumParser: 8 | def __init__(self, content: str): 9 | self.content = content 10 | self.parsed_enums: Dict[str, List[Dict[str, Any]]] = {} 11 | self.enum_dependencies: Set[str] = set() 12 | self.processing_enums: Set[str] = set() 13 | 14 | def find_enum_definition(self, enum_name: str) -> str: 15 | enum_pattern = rf"enum {enum_name} \{{([\s\S]*?)\}}" 16 | enum_match = re.search(enum_pattern, self.content) 17 | 18 | return enum_match.group(1) if enum_match else "" 19 | 20 | def clean_enum_content(self, content: str) -> str: 21 | content = re.sub(r"/\*[\s\S]*?\*/", "", content) 22 | content = re.sub(r"//[^\n]*\n", "\n", content) 23 | 24 | return content 25 | 26 | def extract_enum_types(self, variant_type: str) -> Set[str]: 27 | enum_types = set(re.findall(r"([A-Z][a-zA-Z]+)", variant_type)) 28 | return { 29 | enum_type 30 | for enum_type in enum_types 31 | if self.find_enum_definition(enum_type) 32 | } 33 | 34 | def parse_enum_variants(self, enum_name: str) -> List[Dict[str, Any]]: 35 | if enum_name in self.parsed_enums: 36 | return self.parsed_enums[enum_name] 37 | if enum_name in self.processing_enums: 38 | return [] 39 | 40 | enum_content = self.find_enum_definition(enum_name) 41 | if not enum_content: 42 | return [] 43 | 44 | self.processing_enums.add(enum_name) 45 | enum_content = self.clean_enum_content(enum_content) 46 | variants = [] 47 | 48 | variant_pattern = r"([A-Za-z]+)(?:\((.*?)\))?," 49 | matches = re.finditer(variant_pattern, enum_content) 50 | 51 | for match in matches: 52 | variant_name = match.group(1) 53 | variant_type = match.group(2) 54 | 55 | variant_info = {"name": variant_name} 56 | 57 | if variant_type: 58 | variant_info["type"] = variant_type.strip() 59 | 60 | nested_enums = self.extract_enum_types(variant_type) 61 | for nested_enum in nested_enums: 62 | self.enum_dependencies.add(nested_enum) 63 | self.parse_enum_variants(nested_enum) 64 | 65 | variants.append(variant_info) 66 | 67 | self.processing_enums.remove(enum_name) 68 | self.parsed_enums[enum_name] = variants 69 | return variants 70 | 71 | def parse_all_actions(self) -> Dict[str, Any]: 72 | self.parse_enum_variants("Action") 73 | 74 | processed_deps = set() 75 | to_process = self.enum_dependencies.copy() 76 | 77 | while to_process: 78 | current_dep = to_process.pop() 79 | if current_dep not in processed_deps: 80 | self.parse_enum_variants(current_dep) 81 | processed_deps.add(current_dep) 82 | to_process.update(self.enum_dependencies - processed_deps) 83 | 84 | result = { 85 | "Actions": self.parsed_enums["Action"], 86 | "Dependencies": { 87 | enum_name: self.parsed_enums[enum_name] 88 | for enum_name in self.enum_dependencies 89 | }, 90 | } 91 | 92 | return result 93 | 94 | 95 | def main(): 96 | input = sys.argv[1] 97 | output = sys.argv[2] 98 | 99 | with open(input, "r") as file: 100 | content = file.read() 101 | 102 | parser = EnumParser(content) 103 | result = parser.parse_all_actions() 104 | 105 | with open(output, "w") as file: 106 | json.dump(result, file, indent=4) 107 | 108 | 109 | if __name__ == "__main__": 110 | main() 111 | -------------------------------------------------------------------------------- /modules/applications/by-name/forecast/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.cosmic.applications.mkCosmicApplication { 3 | name = "forecast"; 4 | originalName = "Forecast"; 5 | identifier = "com.jwestall.Forecast"; 6 | configurationVersion = 1; 7 | 8 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 9 | 10 | settingsOptions = 11 | let 12 | inherit (lib.cosmic) defaultNullOpts; 13 | in 14 | { 15 | api_key = defaultNullOpts.mkStr "" '' 16 | The API key for Geocoding API. 17 | ''; 18 | 19 | app_theme = 20 | defaultNullOpts.mkRonEnum [ "Dark" "Light" "System" ] 21 | { 22 | __type = "enum"; 23 | variant = "System"; 24 | } 25 | '' 26 | The theme of the application. 27 | ''; 28 | 29 | default_page = 30 | defaultNullOpts.mkRonEnum [ "DailyView" "Details" "HourlyView" ] 31 | { 32 | __type = "enum"; 33 | variant = "HourlyView"; 34 | } 35 | '' 36 | The default page of the application. 37 | ''; 38 | 39 | latitude = 40 | defaultNullOpts.mkRonOptionalOf lib.types.str 41 | { 42 | __type = "optional"; 43 | value = "-28.971476"; 44 | } 45 | '' 46 | The latitude of the location. 47 | ''; 48 | 49 | location = 50 | defaultNullOpts.mkRonOptionalOf lib.types.str 51 | { 52 | __type = "optional"; 53 | value = "Anta Gorda - RS, Brazil"; 54 | } 55 | '' 56 | The name of the location. 57 | ''; 58 | 59 | longitude = 60 | defaultNullOpts.mkRonOptionalOf lib.types.str 61 | { 62 | __type = "optional"; 63 | value = "-412.005691"; 64 | } 65 | '' 66 | The longitude of the location. 67 | ''; 68 | 69 | pressure_units = 70 | defaultNullOpts.mkRonEnum [ "Bar" "Hectopascal" "Kilopascal" "Psi" ] 71 | { 72 | __type = "enum"; 73 | variant = "Hectopascal"; 74 | } 75 | '' 76 | The units of the pressure. 77 | ''; 78 | 79 | speed_units = 80 | defaultNullOpts.mkRonEnum [ "KilometresPerHour" "MetersPerSecond" "MilesPerHour" ] 81 | { 82 | __type = "enum"; 83 | variant = "KilometresPerHour"; 84 | } 85 | '' 86 | The units of the speed. 87 | ''; 88 | 89 | timefmt = 90 | defaultNullOpts.mkRonEnum [ "TwelveHr" "TwentyFourHr" ] 91 | { 92 | __type = "enum"; 93 | variant = "TwelveHr"; 94 | } 95 | '' 96 | The time format. 97 | ''; 98 | 99 | units = 100 | defaultNullOpts.mkRonEnum [ "Celsius" "Fahrenheit" ] 101 | { 102 | __type = "enum"; 103 | variant = "Fahrenheit"; 104 | } 105 | '' 106 | The units of the temperature. 107 | ''; 108 | }; 109 | 110 | settingsExample = { 111 | api_key = ""; 112 | 113 | app_theme = { 114 | __type = "enum"; 115 | variant = "System"; 116 | }; 117 | 118 | default_page = { 119 | __type = "enum"; 120 | variant = "HourlyView"; 121 | }; 122 | 123 | latitude = { 124 | __type = "optional"; 125 | value = "-28.971476"; 126 | }; 127 | 128 | location = { 129 | __type = "optional"; 130 | value = "Anta Gorda - RS, Brazil"; 131 | }; 132 | 133 | longitude = { 134 | __type = "optional"; 135 | value = "-412.005691"; 136 | }; 137 | 138 | pressure_units = { 139 | __type = "enum"; 140 | variant = "Hectopascal"; 141 | }; 142 | 143 | speed_units = { 144 | __type = "enum"; 145 | variant = "KilometresPerHour"; 146 | }; 147 | 148 | timefmt = { 149 | __type = "enum"; 150 | variant = "TwelveHr"; 151 | }; 152 | 153 | units = { 154 | __type = "enum"; 155 | variant = "Fahrenheit"; 156 | }; 157 | }; 158 | } 159 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: Update 2 | on: 3 | schedule: 4 | - cron: '0 0 * * 0' # runs weekly on Sunday at 00:00 5 | workflow_dispatch: 6 | inputs: 7 | lock: 8 | type: boolean 9 | default: true 10 | description: Update flake.lock 11 | generate: 12 | type: boolean 13 | default: true 14 | description: Generate flake.nix 15 | 16 | concurrency: 17 | group: 'update-${{ github.ref_name }}' 18 | cancel-in-progress: true 19 | 20 | permissions: 21 | actions: write 22 | contents: write 23 | pull-requests: write 24 | 25 | jobs: 26 | update: 27 | name: Update flake inputs 28 | runs-on: ubuntu-latest 29 | if: github.event_name != 'schedule' || github.repository == 'HeitorAugustoLN/cosmic-manager' 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v4 34 | - name: Install Nix 35 | uses: cachix/install-nix-action@v30 36 | with: 37 | nix_path: nixpkgs=channel:nixos-unstable 38 | github_acess_token: ${{ secrets.GITHUB_TOKEN }} 39 | - name: Configure git 40 | run: | 41 | git config user.name "github-actions[bot]" 42 | git config user.email "github-actions[bot]@users.noreply.github.com" 43 | - name: Update flake.lock 44 | id: flake_lock 45 | if: inputs.lock || github.event_name == 'schedule' 46 | run: | 47 | old=$(git show --no-patch --format=%h) 48 | nix flake update --commit-lock-file 49 | new=$(git show --no-patch --format=%h) 50 | if [ "$old" != "$new" ]; then 51 | echo "body<> "$GITHUB_OUTPUT" 52 | git show --no-patch --format=%b >> "$GITHUB_OUTPUT" 53 | echo "EOF" >> "$GITHUB_OUTPUT" 54 | fi 55 | - name: Update generated files 56 | id: generate 57 | if: inputs.generate || github.event_name == 'schedule' 58 | run: | 59 | old=$(git show --no-patch --format=%h) 60 | nix-build ./generators -A generate 61 | result/bin/generate --commit 62 | new=$(git show --no-patch --format=%h) 63 | if [ "$old" != "$new" ]; then 64 | body=$(git show --no-patch --format=%b) 65 | echo "body<> "$GITHUB_OUTPUT" 66 | if [ -n "$body" ]; then 67 | echo "$body" >> "$GITHUB_OUTPUT" 68 | else 69 | git show --no-patch --format=%s | \ 70 | sed -e 's/^generated:/-/' >> "$GITHUB_OUTPUT" 71 | fi 72 | echo "EOF" >> "$GITHUB_OUTPUT" 73 | - name: Create pull request 74 | id: pr 75 | uses: peter-evans/create-pull-request@v6 76 | with: 77 | add-paths: "!**" 78 | branch: update/${{ github.ref_name }} 79 | delete-branch: true 80 | title: | 81 | [${{ github.ref_name }}] Update flake.lock and generated files 82 | body: | 83 | ## Flake lockfile 84 | ``` 85 | ${{ steps.flake_lock.outputs.body || 'No changes' }} 86 | ``` 87 | 88 | ## Generate 89 | ${{ steps.generate.outputs.body || 'No changes' }} 90 | - name: Print summary 91 | if: ${{ steps.pr.outputs.pull-request-number }} 92 | run: | 93 | num="${{ steps.pr.outputs.pull-request-number }}" 94 | pr_url="${{ steps.pr.outputs.pull-request-url }}" 95 | pr_branch="${{ steps.pr.outputs.pull-request-branch }}" 96 | head="${{ steps.pr.outputs.pull-request-head-sha }}" 97 | operation="${{ steps.pr.outputs.pull-request-operation }}" 98 | 99 | # stdout 100 | echo "${head:0:6} pushed to ${pr_branch}" 101 | echo "${pr} was ${operation}." 102 | 103 | # markdown summary 104 | echo "## ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY 105 | echo >> $GITHUB_STEP_SUMMARY 106 | echo "\`${head:0:6}\` pushed to \`${pr_branch}\`" >> $GITHUB_STEP_SUMMARY 107 | echo >> $GITHUB_STEP_SUMMARY 108 | echo "[#${num}](${pr_url}) was ${operation}." >> $GITHUB_STEP_SUMMARY 109 | echo >> $GITHUB_STEP_SUMMARY 110 | -------------------------------------------------------------------------------- /lib/applets.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (builtins) attrValues filter length; 4 | inherit (lib) 5 | getAttrFromPath 6 | mkEnableOption 7 | mkIf 8 | mkMerge 9 | mkPackageOption 10 | optionalAttrs 11 | optionals 12 | pipe 13 | setAttrByPath 14 | ; 15 | inherit (lib.cosmic) 16 | applyExtraConfig 17 | mkAssertion 18 | mkAssertions 19 | mkSettingsOption 20 | ; 21 | in 22 | { 23 | mkCosmicApplet = 24 | { 25 | configurationVersion, 26 | description ? null, 27 | extraConfig ? _: { }, 28 | extraOptions ? { }, 29 | hasSettings ? true, 30 | identifier, 31 | imports ? [ ], 32 | isBuiltin ? true, 33 | maintainers, 34 | name, 35 | originalName ? name, 36 | package ? if isBuiltin then null else name, 37 | settingsDescription ? "Configuration entries for ${originalName} applet.", 38 | settingsOptions ? { }, 39 | settingsExample ? null, 40 | }@args: 41 | let 42 | loc = [ 43 | "wayland" 44 | "desktopManager" 45 | "cosmic" 46 | "applets" 47 | name 48 | ]; 49 | 50 | module = 51 | { 52 | config, 53 | options, 54 | pkgs, 55 | ... 56 | }: 57 | let 58 | cfg = getAttrFromPath loc config; 59 | opts = getAttrFromPath loc options; 60 | in 61 | { 62 | options = setAttrByPath loc ( 63 | optionalAttrs (!isBuiltin) { 64 | enable = mkEnableOption "${originalName} applet"; 65 | package = mkPackageOption pkgs package { 66 | extraDescription = "Set to `null` if you don't want to install the package."; 67 | nullable = true; 68 | }; 69 | } 70 | // optionalAttrs hasSettings { 71 | settings = mkSettingsOption { 72 | description = settingsDescription; 73 | example = settingsExample; 74 | options = settingsOptions; 75 | }; 76 | } 77 | // extraOptions 78 | ); 79 | 80 | config = 81 | let 82 | anySettingsSet = 83 | (pipe cfg.settings [ 84 | attrValues 85 | (filter (x: x != null)) 86 | length 87 | ]) > 0; 88 | 89 | enabled = if isBuiltin then anySettingsSet else cfg.enable; 90 | in 91 | assert mkAssertion name ( 92 | isBuiltin -> hasSettings 93 | ) "Applet module must have settings if it is a built-in applet."; 94 | mkIf enabled (mkMerge [ 95 | { 96 | assertions = mkAssertions name [ 97 | { 98 | assertion = enabled -> config.wayland.desktopManager.cosmic.enable; 99 | message = "COSMIC Desktop declarative configuration must be enabled to use ${originalName} applet module."; 100 | } 101 | ]; 102 | } 103 | 104 | (mkIf (!isBuiltin) { 105 | home.packages = optionals (cfg.package != null) [ cfg.package ]; 106 | }) 107 | 108 | (mkIf hasSettings { 109 | wayland.desktopManager.cosmic = { 110 | configFile.${identifier} = { 111 | entries = cfg.settings; 112 | version = configurationVersion; 113 | }; 114 | }; 115 | }) 116 | 117 | (mkIf (args ? extraConfig) (applyExtraConfig { 118 | inherit 119 | cfg 120 | enabled 121 | extraConfig 122 | opts 123 | ; 124 | })) 125 | ]); 126 | 127 | meta = { 128 | inherit maintainers; 129 | cosmicInfo = { 130 | inherit description; 131 | url = 132 | if isBuiltin then 133 | "https://github.com/pop-os/cosmic-applets" 134 | else 135 | args.url or opts.package.default.meta.homepage; 136 | path = loc; 137 | }; 138 | }; 139 | }; 140 | in 141 | { 142 | imports = imports ++ [ module ]; 143 | }; 144 | } 145 | -------------------------------------------------------------------------------- /modules/applications/by-name/cosmic-applibrary/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (lib) mkOption types; 4 | inherit (lib.cosmic) defaultNullOpts mkCosmicApplication mkRONExpression; 5 | in 6 | mkCosmicApplication { 7 | name = "cosmic-applibrary"; 8 | originalName = "COSMIC Application Library"; 9 | identifier = "com.system76.CosmicAppLibrary"; 10 | configurationVersion = 1; 11 | 12 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 13 | 14 | settingsOptions = { 15 | groups = 16 | let 17 | groupsSubmodule = types.submodule { 18 | freeformType = with types; attrsOf anything; 19 | options = { 20 | filter = mkOption { 21 | type = 22 | let 23 | categorySubmodule = types.submodule { 24 | freeformType = with types; attrsOf anything; 25 | options = { 26 | categories = mkOption { 27 | type = with types; maybeRonRaw (listOf str); 28 | example = [ "Office" ]; 29 | description = '' 30 | The categories of the group. 31 | ''; 32 | }; 33 | 34 | exclude = mkOption { 35 | type = with types; maybeRonRaw (listOf str); 36 | example = [ "com.system76.CosmicStore" ]; 37 | description = '' 38 | The applications to exclude from the group. 39 | ''; 40 | }; 41 | 42 | include = mkOption { 43 | type = with types; maybeRonRaw (listOf str); 44 | example = [ "com.system76.CosmicStore" ]; 45 | description = '' 46 | The applications to include in the group. 47 | ''; 48 | }; 49 | }; 50 | }; 51 | in 52 | with types; 53 | maybeRonRaw (oneOf [ 54 | (ronEnum [ "None" ]) 55 | (ronTupleEnumOf (listOf str) [ "AppIds" ] 1) 56 | (ronNamedStructOf categorySubmodule) 57 | ]); 58 | example = mkRONExpression 0 { 59 | __type = "namedStruct"; 60 | name = "Categories"; 61 | value = { 62 | categories = [ "Office" ]; 63 | exclude = [ ]; 64 | include = [ 65 | "org.gnome.Totem" 66 | "org.gnome.eog" 67 | "simple-scan" 68 | "thunderbird" 69 | ]; 70 | }; 71 | } null; 72 | description = '' 73 | The filter of the group. 74 | ''; 75 | }; 76 | 77 | icon = mkOption { 78 | type = with types; maybeRonRaw str; 79 | example = "folder-symbolic"; 80 | description = '' 81 | The icon of the group. 82 | ''; 83 | }; 84 | 85 | name = mkOption { 86 | type = with types; maybeRonRaw str; 87 | example = "cosmic-office"; 88 | description = '' 89 | The name of the group. 90 | ''; 91 | }; 92 | }; 93 | }; 94 | in 95 | defaultNullOpts.mkNullable (types.listOf groupsSubmodule) 96 | [ 97 | { 98 | name = "cosmic-office"; 99 | icon = "folder-symbolic"; 100 | filter = { 101 | __type = "namedStruct"; 102 | name = "Categories"; 103 | value = { 104 | categories = [ "Office" ]; 105 | exclude = [ ]; 106 | include = [ 107 | "org.gnome.Totem" 108 | "org.gnome.eog" 109 | "simple-scan" 110 | "thunderbird" 111 | ]; 112 | }; 113 | }; 114 | } 115 | { 116 | name = "Games"; 117 | icon = "folder-symbolic"; 118 | filter = { 119 | __type = "enum"; 120 | variant = "AppIds"; 121 | value = [ 122 | "Counter-Strike 2" 123 | ]; 124 | }; 125 | } 126 | ] 127 | '' 128 | The groups of applications to display. 129 | ''; 130 | }; 131 | 132 | settingsExample = { 133 | groups = [ 134 | { 135 | name = "cosmic-office"; 136 | icon = "folder-symbolic"; 137 | filter = { 138 | __type = "namedStruct"; 139 | name = "Categories"; 140 | value = { 141 | categories = [ "Office" ]; 142 | exclude = [ ]; 143 | include = [ 144 | "org.gnome.Totem" 145 | "org.gnome.eog" 146 | "simple-scan" 147 | "thunderbird" 148 | ]; 149 | }; 150 | }; 151 | } 152 | { 153 | name = "Games"; 154 | icon = "folder-symbolic"; 155 | filter = { 156 | __type = "enum"; 157 | variant = "AppIds"; 158 | value = [ 159 | "Counter-Strike 2" 160 | ]; 161 | }; 162 | } 163 | ]; 164 | }; 165 | } 166 | -------------------------------------------------------------------------------- /generated/actions-for-shortcuts.json: -------------------------------------------------------------------------------- 1 | { 2 | "Actions": [ 3 | { 4 | "name": "Close" 5 | }, 6 | { 7 | "name": "Debug" 8 | }, 9 | { 10 | "name": "Disable" 11 | }, 12 | { 13 | "name": "Focus", 14 | "type": "FocusDirection" 15 | }, 16 | { 17 | "name": "LastWorkspace" 18 | }, 19 | { 20 | "name": "Maximize" 21 | }, 22 | { 23 | "name": "Fullscreen" 24 | }, 25 | { 26 | "name": "MigrateWorkspaceToNextOutput" 27 | }, 28 | { 29 | "name": "MigrateWorkspaceToOutput", 30 | "type": "Direction" 31 | }, 32 | { 33 | "name": "MigrateWorkspaceToPreviousOutput" 34 | }, 35 | { 36 | "name": "Minimize" 37 | }, 38 | { 39 | "name": "Move", 40 | "type": "Direction" 41 | }, 42 | { 43 | "name": "MoveToLastWorkspace" 44 | }, 45 | { 46 | "name": "MoveToNextOutput" 47 | }, 48 | { 49 | "name": "MoveToNextWorkspace" 50 | }, 51 | { 52 | "name": "MoveToOutput", 53 | "type": "Direction" 54 | }, 55 | { 56 | "name": "MoveToPreviousOutput" 57 | }, 58 | { 59 | "name": "MoveToPreviousWorkspace" 60 | }, 61 | { 62 | "name": "MoveToWorkspace", 63 | "type": "u8" 64 | }, 65 | { 66 | "name": "NextOutput" 67 | }, 68 | { 69 | "name": "NextWorkspace" 70 | }, 71 | { 72 | "name": "Orientation", 73 | "type": "Orientation" 74 | }, 75 | { 76 | "name": "PreviousOutput" 77 | }, 78 | { 79 | "name": "PreviousWorkspace" 80 | }, 81 | { 82 | "name": "Resizing", 83 | "type": "ResizeDirection" 84 | }, 85 | { 86 | "name": "SendToLastWorkspace" 87 | }, 88 | { 89 | "name": "SendToNextOutput" 90 | }, 91 | { 92 | "name": "SendToNextWorkspace" 93 | }, 94 | { 95 | "name": "SendToOutput", 96 | "type": "Direction" 97 | }, 98 | { 99 | "name": "SendToPreviousOutput" 100 | }, 101 | { 102 | "name": "SendToPreviousWorkspace" 103 | }, 104 | { 105 | "name": "SendToWorkspace", 106 | "type": "u8" 107 | }, 108 | { 109 | "name": "SwapWindow" 110 | }, 111 | { 112 | "name": "SwitchOutput", 113 | "type": "Direction" 114 | }, 115 | { 116 | "name": "System", 117 | "type": "System" 118 | }, 119 | { 120 | "name": "Spawn", 121 | "type": "String" 122 | }, 123 | { 124 | "name": "Terminate" 125 | }, 126 | { 127 | "name": "ToggleOrientation" 128 | }, 129 | { 130 | "name": "ToggleStacking" 131 | }, 132 | { 133 | "name": "ToggleSticky" 134 | }, 135 | { 136 | "name": "ToggleTiling" 137 | }, 138 | { 139 | "name": "ToggleWindowFloating" 140 | }, 141 | { 142 | "name": "Workspace", 143 | "type": "u8" 144 | }, 145 | { 146 | "name": "ZoomIn" 147 | }, 148 | { 149 | "name": "ZoomOut" 150 | } 151 | ], 152 | "Dependencies": { 153 | "Direction": [ 154 | { 155 | "name": "Left" 156 | }, 157 | { 158 | "name": "Right" 159 | }, 160 | { 161 | "name": "Up" 162 | }, 163 | { 164 | "name": "Down" 165 | } 166 | ], 167 | "Orientation": [ 168 | { 169 | "name": "Horizontal" 170 | }, 171 | { 172 | "name": "Vertical" 173 | } 174 | ], 175 | "System": [ 176 | { 177 | "name": "AppLibrary" 178 | }, 179 | { 180 | "name": "BrightnessDown" 181 | }, 182 | { 183 | "name": "BrightnessUp" 184 | }, 185 | { 186 | "name": "DisplayToggle" 187 | }, 188 | { 189 | "name": "HomeFolder" 190 | }, 191 | { 192 | "name": "InputSourceSwitch" 193 | }, 194 | { 195 | "name": "KeyboardBrightnessDown" 196 | }, 197 | { 198 | "name": "KeyboardBrightnessUp" 199 | }, 200 | { 201 | "name": "Launcher" 202 | }, 203 | { 204 | "name": "LockScreen" 205 | }, 206 | { 207 | "name": "LogOut" 208 | }, 209 | { 210 | "name": "Mute" 211 | }, 212 | { 213 | "name": "MuteMic" 214 | }, 215 | { 216 | "name": "PlayPause" 217 | }, 218 | { 219 | "name": "PlayNext" 220 | }, 221 | { 222 | "name": "PlayPrev" 223 | }, 224 | { 225 | "name": "PowerOff" 226 | }, 227 | { 228 | "name": "Screenshot" 229 | }, 230 | { 231 | "name": "Terminal" 232 | }, 233 | { 234 | "name": "TouchpadToggle" 235 | }, 236 | { 237 | "name": "VolumeLower" 238 | }, 239 | { 240 | "name": "VolumeRaise" 241 | }, 242 | { 243 | "name": "WebBrowser" 244 | }, 245 | { 246 | "name": "WindowSwitcher" 247 | }, 248 | { 249 | "name": "WindowSwitcherPrevious" 250 | }, 251 | { 252 | "name": "WorkspaceOverview" 253 | } 254 | ], 255 | "FocusDirection": [ 256 | { 257 | "name": "Left" 258 | }, 259 | { 260 | "name": "Right" 261 | }, 262 | { 263 | "name": "Up" 264 | }, 265 | { 266 | "name": "Down" 267 | }, 268 | { 269 | "name": "In" 270 | }, 271 | { 272 | "name": "Out" 273 | } 274 | ], 275 | "ResizeDirection": [ 276 | { 277 | "name": "Inwards" 278 | }, 279 | { 280 | "name": "Outwards" 281 | } 282 | ] 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /tests/to-ron.nix: -------------------------------------------------------------------------------- 1 | { lib, runCommandLocal }: 2 | let 3 | results = lib.runTests { 4 | testToRonAll = { 5 | expr = lib.cosmic.ron.toRON 0 { 6 | bool = true; 7 | char = { 8 | __type = "char"; 9 | value = "a"; 10 | }; 11 | enum = { 12 | __type = "enum"; 13 | variant = "FooBar"; 14 | }; 15 | float = 3.14; 16 | int = 333; 17 | list = [ 18 | "foo" 19 | "bar" 20 | "baz" 21 | ]; 22 | map = { 23 | __type = "map"; 24 | value = [ 25 | { 26 | key = "foo"; 27 | value = "bar"; 28 | } 29 | ]; 30 | }; 31 | namedStruct = { 32 | __type = "namedStruct"; 33 | name = "foo"; 34 | value = { 35 | bar = "baz"; 36 | }; 37 | }; 38 | optional = { 39 | __type = "optional"; 40 | value = "foo"; 41 | }; 42 | raw = { 43 | __type = "raw"; 44 | value = "foo"; 45 | }; 46 | string = "foo"; 47 | struct = { 48 | foo = "bar"; 49 | }; 50 | tuple = { 51 | __type = "tuple"; 52 | value = [ 53 | "foo" 54 | "bar" 55 | "baz" 56 | ]; 57 | }; 58 | tupleEnum = { 59 | __type = "enum"; 60 | variant = "FooBar"; 61 | value = [ "baz" ]; 62 | }; 63 | }; 64 | expected = '' 65 | ( 66 | bool: true, 67 | char: 'a', 68 | enum: FooBar, 69 | float: 3.14, 70 | int: 333, 71 | list: [ 72 | "foo", 73 | "bar", 74 | "baz", 75 | ], 76 | map: { 77 | "foo": "bar", 78 | }, 79 | namedStruct: foo( 80 | bar: "baz", 81 | ), 82 | optional: Some("foo"), 83 | raw: foo, 84 | string: "foo", 85 | struct: ( 86 | foo: "bar", 87 | ), 88 | tuple: ( 89 | "foo", 90 | "bar", 91 | "baz", 92 | ), 93 | tupleEnum: FooBar( 94 | "baz", 95 | ), 96 | )''; 97 | }; 98 | testToRonBool = { 99 | expr = lib.cosmic.ron.toRON 0 true; 100 | expected = "true"; 101 | }; 102 | testToRonChar = { 103 | expr = lib.cosmic.ron.toRON 0 { 104 | __type = "char"; 105 | value = "a"; 106 | }; 107 | expected = "'a'"; 108 | }; 109 | testToRonEnum = { 110 | expr = lib.cosmic.ron.toRON 0 { 111 | __type = "enum"; 112 | variant = "FooBar"; 113 | }; 114 | expected = "FooBar"; 115 | }; 116 | testToRonFloat = { 117 | expr = lib.cosmic.ron.toRON 0 3.14; 118 | expected = "3.14"; 119 | }; 120 | testToRonInt = { 121 | expr = lib.cosmic.ron.toRON 0 333; 122 | expected = "333"; 123 | }; 124 | testToRonList = { 125 | expr = lib.cosmic.ron.toRON 0 [ 126 | "foo" 127 | "bar" 128 | "baz" 129 | ]; 130 | expected = '' 131 | [ 132 | "foo", 133 | "bar", 134 | "baz", 135 | ]''; 136 | }; 137 | testToRonMap = { 138 | expr = lib.cosmic.ron.toRON 0 { 139 | __type = "map"; 140 | value = [ 141 | { 142 | key = "foo"; 143 | value = "bar"; 144 | } 145 | ]; 146 | }; 147 | expected = '' 148 | { 149 | "foo": "bar", 150 | }''; 151 | }; 152 | testToRonNamedStruct = { 153 | expr = lib.cosmic.ron.toRON 0 { 154 | __type = "namedStruct"; 155 | name = "foo"; 156 | value = { 157 | bar = "baz"; 158 | }; 159 | }; 160 | expected = '' 161 | foo( 162 | bar: "baz", 163 | )''; 164 | }; 165 | testToRonOptional = { 166 | expr = lib.cosmic.ron.toRON 0 { 167 | __type = "optional"; 168 | value = "foo"; 169 | }; 170 | expected = ''Some("foo")''; 171 | }; 172 | testToRonRaw = { 173 | expr = lib.cosmic.ron.toRON 0 { 174 | __type = "raw"; 175 | value = "foo"; 176 | }; 177 | expected = "foo"; 178 | }; 179 | testToRonString = { 180 | expr = lib.cosmic.ron.toRON 0 "foo"; 181 | expected = ''"foo"''; 182 | }; 183 | testToRonStruct = { 184 | expr = lib.cosmic.ron.toRON 0 { 185 | foo = "bar"; 186 | }; 187 | expected = '' 188 | ( 189 | foo: "bar", 190 | )''; 191 | }; 192 | testToRonTuple = { 193 | expr = lib.cosmic.ron.toRON 0 { 194 | __type = "tuple"; 195 | value = [ 196 | "foo" 197 | "bar" 198 | "baz" 199 | ]; 200 | }; 201 | expected = '' 202 | ( 203 | "foo", 204 | "bar", 205 | "baz", 206 | )''; 207 | }; 208 | testToRonTupleEnum = { 209 | expr = lib.cosmic.ron.toRON 0 { 210 | __type = "enum"; 211 | variant = "FooBar"; 212 | value = [ "baz" ]; 213 | }; 214 | expected = '' 215 | FooBar( 216 | "baz", 217 | )''; 218 | }; 219 | }; 220 | in 221 | if results == [ ] then 222 | runCommandLocal "to-ron-success" { } "touch $out" 223 | else 224 | runCommandLocal "to-ron-failure" 225 | { 226 | results = builtins.concatStringsSep "\n" ( 227 | map (result: '' 228 | ${result.name}: 229 | expected: ${lib.generators.toPretty { } result.expected} 230 | actual: ${lib.generators.toPretty { } result.result} 231 | '') results 232 | ); 233 | } 234 | '' 235 | echo -e "Tests failed:\\n\\n$results" >&2 236 | exit 1 237 | '' 238 | -------------------------------------------------------------------------------- /tests/from-ron.nix: -------------------------------------------------------------------------------- 1 | { lib, runCommandLocal }: 2 | let 3 | results = lib.runTests { 4 | testFromRonAll = { 5 | expr = lib.cosmic.ron.fromRON '' 6 | AllTests( 7 | bool: true, 8 | char: 'a', 9 | int: 1, 10 | float: 1.1, 11 | string: "abc", 12 | list: [ 13 | 1, 14 | 2, 15 | 3 16 | ], 17 | map: { 18 | "a": 1, 19 | 2: 2 20 | }, 21 | tuple: ( 22 | 1, 23 | 2, 24 | 3 25 | ), 26 | struct: ( 27 | a: 1, 28 | b: 2 29 | ), 30 | namedStruct: NamedStruct( 31 | a: 1, 32 | b: 2 33 | ), 34 | enum: All, 35 | option: Some(Some(123)), 36 | tupleEnum: All(123), 37 | none: None, 38 | raw: foo 39 | ) 40 | ''; 41 | 42 | expected = { 43 | __type = "namedStruct"; 44 | name = "AllTests"; 45 | value = { 46 | bool = true; 47 | 48 | char = { 49 | __type = "char"; 50 | value = "a"; 51 | }; 52 | 53 | int = 1; 54 | float = 1.1; 55 | string = "abc"; 56 | 57 | list = [ 58 | 1 59 | 2 60 | 3 61 | ]; 62 | 63 | map = { 64 | __type = "map"; 65 | value = [ 66 | { 67 | key = "a"; 68 | value = 1; 69 | } 70 | { 71 | key = 2; 72 | value = 2; 73 | } 74 | ]; 75 | }; 76 | 77 | tuple = { 78 | __type = "tuple"; 79 | value = [ 80 | 1 81 | 2 82 | 3 83 | ]; 84 | }; 85 | 86 | struct = { 87 | a = 1; 88 | b = 2; 89 | }; 90 | 91 | namedStruct = { 92 | __type = "namedStruct"; 93 | name = "NamedStruct"; 94 | value = { 95 | a = 1; 96 | b = 2; 97 | }; 98 | }; 99 | 100 | enum = { 101 | __type = "raw"; 102 | value = "All"; 103 | }; 104 | 105 | option = { 106 | __type = "optional"; 107 | value = { 108 | __type = "optional"; 109 | value = 123; 110 | }; 111 | }; 112 | 113 | tupleEnum = { 114 | __type = "enum"; 115 | variant = "All"; 116 | value = [ 117 | 123 118 | ]; 119 | }; 120 | 121 | none = { 122 | __type = "optional"; 123 | value = null; 124 | }; 125 | 126 | raw = { 127 | __type = "raw"; 128 | value = "foo"; 129 | }; 130 | }; 131 | }; 132 | }; 133 | 134 | testFromRonBool = { 135 | expr = lib.cosmic.ron.fromRON "true"; 136 | expected = true; 137 | }; 138 | 139 | testFromRonChar = { 140 | expr = lib.cosmic.ron.fromRON "'a'"; 141 | 142 | expected = { 143 | __type = "char"; 144 | value = "a"; 145 | }; 146 | }; 147 | 148 | testFromRonEnum = { 149 | expr = lib.cosmic.ron.fromRON "FooBar"; 150 | # Simple enum variants are basically the same as raw values. So I didn't 151 | # bother to add a special case for them in the fromRON function. 152 | expected = { 153 | __type = "raw"; 154 | value = "FooBar"; 155 | }; 156 | }; 157 | 158 | testFromRonFloat = { 159 | expr = lib.cosmic.ron.fromRON "3.14"; 160 | expected = 3.14; 161 | }; 162 | 163 | testFromRonInt = { 164 | expr = lib.cosmic.ron.fromRON "333"; 165 | expected = 333; 166 | }; 167 | 168 | testFromRonList = { 169 | expr = lib.cosmic.ron.fromRON '' 170 | [ 171 | "foo", 172 | "bar", 173 | "baz" 174 | ] 175 | ''; 176 | 177 | expected = [ 178 | "foo" 179 | "bar" 180 | "baz" 181 | ]; 182 | }; 183 | 184 | testFromRonMap = { 185 | expr = lib.cosmic.ron.fromRON '' 186 | { 187 | "foo": (bar: "baz") 188 | } 189 | ''; 190 | 191 | expected = { 192 | __type = "map"; 193 | value = [ 194 | { 195 | key = "foo"; 196 | value = { 197 | bar = "baz"; 198 | }; 199 | } 200 | ]; 201 | }; 202 | }; 203 | 204 | testFromRonNamedStruct = { 205 | expr = lib.cosmic.ron.fromRON '' 206 | foo( 207 | bar: "baz" 208 | ) 209 | ''; 210 | 211 | expected = { 212 | __type = "namedStruct"; 213 | name = "foo"; 214 | value = { 215 | bar = "baz"; 216 | }; 217 | }; 218 | }; 219 | 220 | testFromRonOptional = { 221 | expr = lib.cosmic.ron.fromRON ''Some("foo")''; 222 | 223 | expected = { 224 | __type = "optional"; 225 | value = "foo"; 226 | }; 227 | }; 228 | 229 | testFromRonRaw = { 230 | expr = lib.cosmic.ron.fromRON "foo"; 231 | expected = { 232 | __type = "raw"; 233 | value = "foo"; 234 | }; 235 | }; 236 | 237 | testFromRonString = { 238 | expr = lib.cosmic.ron.fromRON ''"foo"''; 239 | expected = "foo"; 240 | }; 241 | 242 | testFromRonStruct = { 243 | expr = lib.cosmic.ron.fromRON ''( a: (b: (c: "foo")))''; 244 | expected.a.b.c = "foo"; 245 | }; 246 | 247 | testFromRonTuple = { 248 | expr = lib.cosmic.ron.fromRON ''("foo", "bar", "baz", 3.14, 333)''; 249 | expected = { 250 | __type = "tuple"; 251 | value = [ 252 | "foo" 253 | "bar" 254 | "baz" 255 | 3.14 256 | 333 257 | ]; 258 | }; 259 | }; 260 | 261 | testToRonTupleEnum = { 262 | expr = lib.cosmic.ron.fromRON ''Path("/some/path", "/another/path")''; 263 | expected = { 264 | __type = "enum"; 265 | variant = "Path"; 266 | value = [ 267 | "/some/path" 268 | "/another/path" 269 | ]; 270 | }; 271 | }; 272 | }; 273 | in 274 | if results == [ ] then 275 | runCommandLocal "to-ron-success" { } "touch $out" 276 | else 277 | runCommandLocal "to-ron-failure" 278 | { 279 | results = builtins.concatStringsSep "\n" ( 280 | map (result: '' 281 | ${result.name}: 282 | expected: ${lib.generators.toPretty { } result.expected} 283 | actual: ${lib.generators.toPretty { } result.result} 284 | '') results 285 | ); 286 | } 287 | '' 288 | echo -e "Tests failed:\\n\\n$results" >&2 289 | exit 1 290 | '' 291 | -------------------------------------------------------------------------------- /modules/shortcuts.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | inherit (builtins) 4 | all 5 | elem 6 | filter 7 | getAttr 8 | groupBy 9 | hasAttr 10 | mapAttrs 11 | stringLength 12 | ; 13 | inherit (lib) 14 | importJSON 15 | init 16 | last 17 | mapAttrsToList 18 | mkIf 19 | mkOption 20 | pipe 21 | splitString 22 | toLower 23 | types 24 | unique 25 | ; 26 | inherit (lib.cosmic) cleanNullsExceptOptional defaultNullOpts mkRONExpression; 27 | inherit (lib.types) rustToNixType; 28 | in 29 | { 30 | options.wayland.desktopManager.cosmic.shortcuts = 31 | let 32 | shortcutSubmodule = 33 | let 34 | generatedActions = importJSON ../generated/actions-for-shortcuts.json; 35 | in 36 | types.submodule { 37 | options = { 38 | description = 39 | defaultNullOpts.mkRonOptionalOf types.str 40 | { 41 | __type = "optional"; 42 | value = "Open Terminal"; 43 | } 44 | '' 45 | A description for the shortcut. 46 | Used by COSMIC Settings to display the name of a custom shortcut. 47 | This field is optional, and should only be used when defining custom shortcuts. 48 | ''; 49 | key = mkOption { 50 | type = types.str; 51 | example = "Super+Q"; 52 | description = '' 53 | The key combination that triggers the action. 54 | For example, "Super+Q" would trigger the action when the Super and Q keys are pressed together. 55 | ''; 56 | }; 57 | action = mkOption { 58 | type = 59 | with types; 60 | maybeRonRaw ( 61 | oneOf ( 62 | [ 63 | (ronEnum ( 64 | pipe generatedActions [ 65 | (getAttr "Actions") 66 | (filter (action: !(hasAttr "type" action))) 67 | (map (action: action.name)) 68 | # Remove deprecated actions from the list 69 | # TODO: Remove it when it gets removed from actions 70 | (filter ( 71 | action: 72 | !(elem action [ 73 | "MigrateWorkspaceToNextOutput" 74 | "MigrateWorkspaceToPreviousOutput" 75 | "MoveToNextOutput" 76 | "MoveToPreviousOutput" 77 | "NextOutput" 78 | "PreviousOutput" 79 | "SendToNextOutput" 80 | "SendToPreviousOutput" 81 | ]) 82 | )) 83 | ] 84 | )) 85 | ] 86 | ++ 87 | mapAttrsToList 88 | ( 89 | type: names: 90 | let 91 | actionDependencies = generatedActions.Dependencies; 92 | 93 | elemType = 94 | if hasAttr type actionDependencies then 95 | ronEnum (map (action: action.name) actionDependencies.${type}) 96 | else 97 | rustToNixType type; 98 | in 99 | ronTupleEnumOf elemType names 1 100 | ) 101 | ( 102 | pipe generatedActions [ 103 | (getAttr "Actions") 104 | (filter (action: hasAttr "type" action)) 105 | (groupBy (action: action.type)) 106 | (mapAttrs (_: actions: map (action: action.name) actions)) 107 | ] 108 | ) 109 | ) 110 | ); 111 | example = mkRONExpression 0 { 112 | __type = "enum"; 113 | variant = "Spawn"; 114 | value = [ "firefox" ]; 115 | } null; 116 | description = '' 117 | The action triggered by the shortcut. 118 | Actions can include running a command, moving windows, system actions, and more. 119 | ''; 120 | }; 121 | }; 122 | }; 123 | in 124 | defaultNullOpts.mkNullable (types.listOf shortcutSubmodule) 125 | [ 126 | { 127 | description = { 128 | __type = "optional"; 129 | value = "Open Firefox"; 130 | }; 131 | key = "Super+B"; 132 | action = { 133 | __type = "enum"; 134 | variant = "Spawn"; 135 | value = [ "firefox" ]; 136 | }; 137 | } 138 | { 139 | key = "Super+Q"; 140 | action = { 141 | __type = "enum"; 142 | variant = "Close"; 143 | }; 144 | } 145 | { 146 | key = "Super+M"; 147 | action = { 148 | __type = "enum"; 149 | variant = "Disable"; 150 | }; 151 | } 152 | { 153 | key = "XF86MonBrightnessDown"; 154 | action = { 155 | __type = "enum"; 156 | variant = "System"; 157 | value = [ 158 | { 159 | __type = "enum"; 160 | variant = "BrightnessDown"; 161 | } 162 | ]; 163 | }; 164 | } 165 | { 166 | key = "Super"; 167 | action = { 168 | __type = "enum"; 169 | variant = "System"; 170 | value = [ 171 | { 172 | __type = "enum"; 173 | variant = "Launcher"; 174 | } 175 | ]; 176 | }; 177 | } 178 | ] 179 | '' 180 | Defines a list of custom shortcuts for the COSMIC desktop environment. 181 | Each shortcut specifies a key combination, the action to be performed, and optionally a description for a custom shortcut. 182 | ''; 183 | 184 | config = 185 | let 186 | cfg = config.wayland.desktopManager.cosmic; 187 | 188 | parseShortcuts = 189 | key: 190 | let 191 | validModifiers = [ 192 | "Alt" 193 | "Ctrl" 194 | "Shift" 195 | "Super" 196 | ]; 197 | 198 | isModifier = part: elem part validModifiers; 199 | 200 | parts = pipe key [ 201 | (splitString "+") 202 | (filter (x: x != "")) 203 | ]; 204 | in 205 | { 206 | key = 207 | if all isModifier parts then 208 | null 209 | else if stringLength (last parts) == 1 then 210 | toLower (last parts) 211 | else 212 | last parts; 213 | 214 | modifiers = map (modifier: { 215 | __type = "enum"; 216 | variant = modifier; 217 | }) (unique (if all isModifier parts then parts else init parts)); 218 | }; 219 | in 220 | mkIf (cfg.shortcuts != null) { 221 | wayland.desktopManager.cosmic.configFile."com.system76.CosmicSettings.Shortcuts" = { 222 | entries.custom = { 223 | __type = "map"; 224 | value = map (shortcut: { 225 | key = pipe shortcut.key [ 226 | parseShortcuts 227 | (parsed: parsed // { inherit (shortcut) description; }) 228 | cleanNullsExceptOptional 229 | ]; 230 | value = shortcut.action; 231 | }) cfg.shortcuts; 232 | }; 233 | version = 1; 234 | }; 235 | }; 236 | } 237 | -------------------------------------------------------------------------------- /modules/applications/by-name/cosmic-files/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | lib.cosmic.applications.mkCosmicApplication { 3 | name = "cosmic-files"; 4 | originalName = "COSMIC Files"; 5 | identifier = "com.system76.CosmicFiles"; 6 | configurationVersion = 1; 7 | 8 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 9 | 10 | settingsOptions = 11 | let 12 | inherit (lib.cosmic) defaultNullOpts; 13 | in 14 | { 15 | app_theme = 16 | defaultNullOpts.mkRonEnum [ "Dark" "Light" "System" ] 17 | { 18 | __type = "enum"; 19 | variant = "System"; 20 | } 21 | '' 22 | The theme of the application. 23 | ''; 24 | 25 | desktop = 26 | defaultNullOpts.mkNullable 27 | (lib.types.submodule { 28 | freeformType = with lib.types; attrsOf anything; 29 | options = { 30 | show_content = lib.mkOption { 31 | type = with lib.types; maybeRonRaw bool; 32 | example = true; 33 | description = '' 34 | Whether to show the content of the Desktop folder. 35 | ''; 36 | }; 37 | 38 | show_mounted_drives = lib.mkOption { 39 | type = with lib.types; maybeRonRaw bool; 40 | example = false; 41 | description = '' 42 | Whether to show mounted drives on the Desktop. 43 | ''; 44 | }; 45 | 46 | show_trash = lib.mkOption { 47 | type = with lib.types; maybeRonRaw bool; 48 | example = false; 49 | description = '' 50 | Whether to show the Trash on the Desktop. 51 | ''; 52 | }; 53 | }; 54 | }) 55 | { 56 | show_content = true; 57 | show_mounted_drives = false; 58 | show_trash = false; 59 | } 60 | '' 61 | The desktop icons settings. 62 | ''; 63 | 64 | favorites = 65 | defaultNullOpts.mkListOf 66 | ( 67 | with lib.types; 68 | either (ronEnum [ 69 | "Documents" 70 | "Downloads" 71 | "Home" 72 | "Music" 73 | "Pictures" 74 | "Videos" 75 | ]) (ronTupleEnumOf lib.types.str [ "Path" ] 1) 76 | ) 77 | [ 78 | { 79 | __type = "enum"; 80 | variant = "Home"; 81 | } 82 | { 83 | __type = "enum"; 84 | variant = "Documents"; 85 | } 86 | { 87 | __type = "enum"; 88 | variant = "Downloads"; 89 | } 90 | { 91 | __type = "enum"; 92 | variant = "Music"; 93 | } 94 | { 95 | __type = "enum"; 96 | variant = "Pictures"; 97 | } 98 | { 99 | __type = "enum"; 100 | variant = "Videos"; 101 | } 102 | { 103 | __type = "enum"; 104 | variant = "Path"; 105 | value = [ 106 | "/home/user/Projects" 107 | ]; 108 | } 109 | ] 110 | '' 111 | The list of favorite folders. 112 | ''; 113 | 114 | show_details = defaultNullOpts.mkBool false '' 115 | Whether to show file details. 116 | ''; 117 | 118 | tab = 119 | defaultNullOpts.mkNullable 120 | (lib.types.submodule { 121 | freeformType = with lib.types; attrsOf anything; 122 | options = { 123 | folders_first = lib.mkOption { 124 | type = with lib.types; maybeRonRaw bool; 125 | example = true; 126 | description = '' 127 | Whether to show folders before files. 128 | ''; 129 | }; 130 | 131 | icon_sizes = lib.mkOption { 132 | type = lib.types.submodule { 133 | freeformType = with lib.types; attrsOf anything; 134 | options = { 135 | grid = lib.mkOption { 136 | type = 137 | with lib.types; 138 | maybeRonRaw ( 139 | addCheck ints.u16 (x: x > 0) 140 | // { 141 | description = "Non-zero unsigned 16-bit integer"; 142 | } 143 | ); 144 | example = 100; 145 | description = '' 146 | The size of the icons in the grid view. 147 | ''; 148 | }; 149 | 150 | list = lib.mkOption { 151 | type = 152 | with lib.types; 153 | maybeRonRaw ( 154 | addCheck ints.u16 (x: x > 0) 155 | // { 156 | description = "Non-zero unsigned 16-bit integer"; 157 | } 158 | ); 159 | example = 100; 160 | description = '' 161 | The size of the icons in the list view. 162 | ''; 163 | }; 164 | }; 165 | }; 166 | example = { 167 | grid = 100; 168 | list = 100; 169 | }; 170 | description = '' 171 | The icon sizes of the grid and list views. 172 | ''; 173 | }; 174 | 175 | show_hidden = lib.mkOption { 176 | type = with lib.types; maybeRonRaw bool; 177 | example = false; 178 | description = '' 179 | Whether to show hidden files. 180 | ''; 181 | }; 182 | 183 | view = lib.mkOption { 184 | type = 185 | with lib.types; 186 | maybeRonRaw (ronEnum [ 187 | "Grid" 188 | "List" 189 | ]); 190 | example = 191 | let 192 | inherit (lib.cosmic) mkRONExpression; 193 | in 194 | mkRONExpression 0 { 195 | __type = "enum"; 196 | variant = "List"; 197 | } null; 198 | description = '' 199 | The default view of the tab. 200 | ''; 201 | }; 202 | }; 203 | }) 204 | { 205 | folders_first = true; 206 | icon_sizes = { 207 | grid = 100; 208 | list = 100; 209 | }; 210 | show_hidden = false; 211 | view = { 212 | __type = "enum"; 213 | variant = "List"; 214 | }; 215 | } 216 | '' 217 | The tab settings. 218 | ''; 219 | }; 220 | 221 | settingsExample = { 222 | app_theme = { 223 | __type = "enum"; 224 | variant = "System"; 225 | }; 226 | 227 | desktop = { 228 | show_content = true; 229 | show_mounted_drives = false; 230 | show_trash = false; 231 | }; 232 | 233 | favorites = [ 234 | { 235 | __type = "enum"; 236 | variant = "Home"; 237 | } 238 | { 239 | __type = "enum"; 240 | variant = "Documents"; 241 | } 242 | { 243 | __type = "enum"; 244 | variant = "Downloads"; 245 | } 246 | { 247 | __type = "enum"; 248 | variant = "Music"; 249 | } 250 | { 251 | __type = "enum"; 252 | variant = "Pictures"; 253 | } 254 | { 255 | __type = "enum"; 256 | variant = "Videos"; 257 | } 258 | { 259 | __type = "enum"; 260 | variant = "Path"; 261 | value = [ 262 | "/home/user/Projects" 263 | ]; 264 | } 265 | ]; 266 | 267 | show_details = false; 268 | 269 | tab = { 270 | folders_first = true; 271 | icon_sizes = { 272 | grid = 100; 273 | list = 100; 274 | }; 275 | show_hidden = false; 276 | view = { 277 | __type = "enum"; 278 | variant = "List"; 279 | }; 280 | }; 281 | }; 282 | } 283 | -------------------------------------------------------------------------------- /modules/wallpapers.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | inherit (builtins) 4 | filter 5 | head 6 | length 7 | listToAttrs 8 | ; 9 | inherit (lib) 10 | mkIf 11 | mkMerge 12 | mkOption 13 | pipe 14 | types 15 | unique 16 | ; 17 | inherit (lib.cosmic) 18 | defaultNullOpts 19 | mkAssertions 20 | mkRONExpression 21 | ; 22 | in 23 | { 24 | options.wayland.desktopManager.cosmic.wallpapers = 25 | let 26 | wallpapersSubmodule = types.submodule { 27 | freeformType = with types; attrsOf anything; 28 | options = { 29 | filter_by_theme = mkOption { 30 | type = with types; maybeRonRaw bool; 31 | example = true; 32 | description = '' 33 | Whether to filter the wallpapers by the active theme. 34 | ''; 35 | }; 36 | 37 | filter_method = mkOption { 38 | type = 39 | with types; 40 | maybeRonRaw (ronEnum [ 41 | "Lanczos" 42 | "Linear" 43 | "Nearest" 44 | ]); 45 | example = mkRONExpression 0 { 46 | __type = "enum"; 47 | variant = "Lanczos"; 48 | } null; 49 | }; 50 | 51 | output = mkOption { 52 | type = with types; maybeRonRaw (either (enum [ "all" ]) str); 53 | example = "all"; 54 | description = '' 55 | The output(s) to show the wallpaper. 56 | ''; 57 | }; 58 | 59 | rotation_frequency = mkOption { 60 | type = with types; maybeRonRaw ints.unsigned; 61 | example = 600; 62 | description = '' 63 | The frequency at which the wallpaper should change in seconds. 64 | ''; 65 | }; 66 | 67 | sampling_method = mkOption { 68 | type = 69 | with types; 70 | maybeRonRaw (ronEnum [ 71 | "Alphanumeric" 72 | "Random" 73 | ]); 74 | example = mkRONExpression 0 { 75 | __type = "enum"; 76 | variant = "Alphanumeric"; 77 | } null; 78 | description = '' 79 | The method to use for sampling the wallpapers. 80 | ''; 81 | }; 82 | 83 | scaling_mode = mkOption { 84 | type = 85 | with types; 86 | maybeRonRaw ( 87 | either (ronEnum [ 88 | "Stretch" 89 | "Zoom" 90 | ]) (ronTupleEnumOf (ronTupleOf (maybeRonRaw (numbers.between 0.0 1.0)) 3) [ "Fit" ] 1) 91 | ); 92 | example = mkRONExpression 0 { 93 | __type = "enum"; 94 | variant = "Fit"; 95 | value = [ 96 | { 97 | __type = "tuple"; 98 | value = [ 99 | 0.5 100 | 1.0 101 | { 102 | __type = "raw"; 103 | value = "0.345354352"; 104 | } 105 | ]; 106 | } 107 | ]; 108 | } null; 109 | }; 110 | 111 | source = 112 | let 113 | gradientSubmodule = types.submodule { 114 | freeformType = with types; attrsOf anything; 115 | options = { 116 | colors = mkOption { 117 | type = 118 | with types; 119 | maybeRonRaw (listOf (maybeRonRaw (ronTupleOf (maybeRonRaw (numbers.between 0.0 1.0)) 3))); 120 | example = mkRONExpression 0 [ 121 | { 122 | __type = "tuple"; 123 | value = [ 124 | 0.0 125 | 0.0 126 | 0.0 127 | ]; 128 | } 129 | { 130 | __type = "tuple"; 131 | value = [ 132 | 1.0 133 | 1.0 134 | 1.0 135 | ]; 136 | } 137 | ] null; 138 | }; 139 | 140 | radius = mkOption { 141 | type = with types; maybeRonRaw float; 142 | example = 0.0; 143 | description = '' 144 | The radius of the gradient. 145 | ''; 146 | }; 147 | }; 148 | }; 149 | in 150 | mkOption { 151 | type = 152 | with types; 153 | maybeRonRaw ( 154 | either (ronTupleEnumOf (maybeRonRaw (either str path)) [ "Path" ] 1) ( 155 | ronTupleEnumOf (either (ronTupleEnumOf gradientSubmodule [ "Gradient" ] 1) ( 156 | ronTupleEnumOf (maybeRonRaw (ronTupleOf (maybeRonRaw float) 3)) [ "Single" ] 1 157 | )) [ "Color" ] 1 158 | ) 159 | ); 160 | example = mkRONExpression 0 { 161 | __type = "enum"; 162 | variant = "Color"; 163 | value = [ 164 | { 165 | __type = "enum"; 166 | variant = "Gradient"; 167 | value = [ 168 | { 169 | colors = [ 170 | { 171 | __type = "tuple"; 172 | value = [ 173 | 0.0 174 | 0.0 175 | 0.0 176 | ]; 177 | } 178 | { 179 | __type = "tuple"; 180 | value = [ 181 | 1.0 182 | 1.0 183 | 1.0 184 | ]; 185 | } 186 | ]; 187 | radius = 180.0; 188 | } 189 | ]; 190 | } 191 | ]; 192 | } null; 193 | description = '' 194 | The source of the wallpaper. 195 | ''; 196 | }; 197 | }; 198 | }; 199 | in 200 | defaultNullOpts.mkNullable (types.listOf wallpapersSubmodule) 201 | [ 202 | { 203 | output = "all"; 204 | source = { 205 | __type = "enum"; 206 | variant = "Path"; 207 | value = [ "/path/to/wallpaper.png" ]; 208 | }; 209 | filter_by_theme = true; 210 | filter_method = { 211 | __type = "enum"; 212 | variant = "Lanczos"; 213 | }; 214 | scaling_mode = { 215 | __type = "enum"; 216 | variant = "Fit"; 217 | value = [ 218 | { 219 | __type = "tuple"; 220 | value = [ 221 | 0.5 222 | 1.0 223 | { 224 | __type = "raw"; 225 | value = "0.345354352"; 226 | } 227 | ]; 228 | } 229 | ]; 230 | }; 231 | sampling_method = { 232 | __type = "enum"; 233 | variant = "Alphanumeric"; 234 | }; 235 | rotation_frequency = 600; 236 | } 237 | ] 238 | '' 239 | List of wallpapers to be used in COSMIC. 240 | ''; 241 | 242 | config = 243 | let 244 | cfg = config.wayland.desktopManager.cosmic; 245 | version = 1; 246 | 247 | hasAllWallpaper = 248 | pipe cfg.wallpapers [ 249 | (filter (wallpaper: wallpaper.output == "all")) 250 | length 251 | ] > 0; 252 | 253 | outputs = map (wallpaper: wallpaper.output) cfg.wallpapers; 254 | in 255 | mkIf (cfg.wallpapers != null) (mkMerge [ 256 | { 257 | assertions = mkAssertions "wallpapers" [ 258 | { 259 | assertion = hasAllWallpaper -> length cfg.wallpapers == 1; 260 | message = "Only one wallpaper can be set if the output is set to 'all'."; 261 | } 262 | 263 | { 264 | assertion = length outputs == length (unique outputs); 265 | message = "Each output can only have one wallpaper configuration."; 266 | } 267 | ]; 268 | 269 | wayland.desktopManager.cosmic.configFile."com.system76.CosmicBackground" = { 270 | entries = 271 | if hasAllWallpaper then 272 | { 273 | all = head cfg.wallpapers; 274 | same-on-all = true; 275 | } 276 | else 277 | { 278 | backgrounds = outputs; 279 | same-on-all = false; 280 | } 281 | // listToAttrs ( 282 | map (wallpaper: { 283 | name = "output.${wallpaper.output}"; 284 | value = wallpaper; 285 | }) cfg.wallpapers 286 | ); 287 | 288 | inherit version; 289 | }; 290 | } 291 | 292 | (mkIf (!hasAllWallpaper) { 293 | wayland.desktopManager.cosmic.stateFile."com.system76.CosmicBackground" = { 294 | entries.wallpapers = map (wallpaper: { 295 | __type = "tuple"; 296 | value = [ 297 | wallpaper.output 298 | wallpaper.source 299 | ]; 300 | }) cfg.wallpapers; 301 | 302 | inherit version; 303 | }; 304 | }) 305 | ]); 306 | } 307 | -------------------------------------------------------------------------------- /modules/panels.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | { 8 | options.wayland.desktopManager.cosmic.panels = 9 | let 10 | inherit (lib.cosmic) defaultNullOpts; 11 | 12 | panelSubmodule = lib.types.submodule { 13 | freeformType = with lib.types; attrsOf anything; 14 | options = { 15 | anchor = 16 | defaultNullOpts.mkRonEnum [ "Bottom" "Left" "Right" "Top" ] 17 | { 18 | __type = "enum"; 19 | variant = "Bottom"; 20 | } 21 | '' 22 | The position of the panel on the screen. 23 | ''; 24 | 25 | anchor_gap = defaultNullOpts.mkBool true '' 26 | Whether there should be a gap between the panel and the screen edge. 27 | ''; 28 | 29 | # HACK: Submodule options won't show up if maybeRonRaw comes before it. 30 | autohide = 31 | defaultNullOpts.mkNullable 32 | (lib.types.ronOptionalOf ( 33 | lib.types.submodule { 34 | freeformType = with lib.types; attrsOf anything; 35 | options = { 36 | handle_size = lib.mkOption { 37 | type = 38 | with lib.types; 39 | maybeRonRaw ( 40 | addCheck ints.u32 (x: x > 0) 41 | // { 42 | description = "Non-zero 32-bit unsigned integer"; 43 | } 44 | ); 45 | example = 4; 46 | description = '' 47 | The size of the handle in pixels. 48 | ''; 49 | }; 50 | transition_time = lib.mkOption { 51 | type = with lib.types; maybeRonRaw ints.u32; 52 | example = 200; 53 | description = '' 54 | The time in milliseconds it should take to transition the panel hiding. 55 | ''; 56 | }; 57 | wait_time = lib.mkOption { 58 | type = with lib.types; maybeRonRaw ints.u32; 59 | example = 1000; 60 | description = '' 61 | The time in milliseconds without pointer focus before the panel hides. 62 | ''; 63 | }; 64 | }; 65 | } 66 | )) 67 | { 68 | __type = "optional"; 69 | value = { 70 | handle_size = 4; 71 | transition_time = 200; 72 | wait_time = 1000; 73 | }; 74 | } 75 | '' 76 | Whether the panel should autohide and the settings for autohide. 77 | If set the `value` is set to `null`, the panel will not autohide. 78 | ''; 79 | 80 | background = 81 | defaultNullOpts.mkNullableWithRaw 82 | ( 83 | with lib.types; 84 | either (ronEnum [ 85 | "Dark" 86 | "Light" 87 | "ThemeDefault" 88 | ]) (ronTupleEnumOf (ronTupleOf float 3) [ "Color" ] 1) 89 | ) 90 | { 91 | __type = "enum"; 92 | variant = "Dark"; 93 | } 94 | '' 95 | The appearance of the panel. 96 | ''; 97 | 98 | expand_to_edges = defaultNullOpts.mkBool true '' 99 | Whether the panel should expand to the edges of the screen. 100 | ''; 101 | 102 | name = lib.mkOption { 103 | type = lib.types.str; 104 | example = "Panel"; 105 | description = '' 106 | The name of the panel. 107 | ''; 108 | }; 109 | 110 | margin = lib.mkOption { 111 | type = with lib.types; maybeRonRaw ints.u32; 112 | example = 4; 113 | description = '' 114 | The margin between the panel and anchored edge. Needs to have a value for anchor_gap to take effect. 115 | If anchor_gap is false, then set this to 0. 116 | ''; 117 | }; 118 | 119 | opacity = defaultNullOpts.mkNullableWithRaw (lib.types.numbers.between 0.0 1.0) 1.0 '' 120 | The opacity of the panel. 121 | ''; 122 | 123 | output = 124 | defaultNullOpts.mkNullableWithRaw 125 | ( 126 | with lib.types; 127 | either (ronEnum [ 128 | "Active" 129 | "All" 130 | ]) (ronTupleEnumOf str [ "Name" ] 1) 131 | ) 132 | { 133 | __type = "enum"; 134 | variant = "Name"; 135 | value = [ "Virtual-1" ]; 136 | } 137 | '' 138 | The output(s) the panel should be displayed on. 139 | ''; 140 | 141 | plugins_center = 142 | defaultNullOpts.mkRonOptionalOf (with lib.types; listOf str) 143 | { 144 | __type = "optional"; 145 | value = [ "com.system76.CosmicAppletTime" ]; 146 | } 147 | '' 148 | The center applets of the panel. 149 | ''; 150 | 151 | plugins_wings = 152 | defaultNullOpts.mkRonOptionalOf (with lib.types; ronTupleOf (listOf str) 2) 153 | { 154 | __type = "optional"; 155 | value = { 156 | __type = "tuple"; 157 | value = [ 158 | [ 159 | "com.system76.CosmicPanelWorkspacesButton" 160 | "com.system76.CosmicPanelAppButton" 161 | "com.system76.CosmicAppletWorkspaces" 162 | ] 163 | [ 164 | "com.system76.CosmicAppletInputSources" 165 | "com.system76.CosmicAppletStatusArea" 166 | "com.system76.CosmicAppletTiling" 167 | "com.system76.CosmicAppletAudio" 168 | "com.system76.CosmicAppletNetwork" 169 | "com.system76.CosmicAppletBattery" 170 | "com.system76.CosmicAppletNotifications" 171 | "com.system76.CosmicAppletBluetooth" 172 | "com.system76.CosmicAppletPower" 173 | ] 174 | ]; 175 | }; 176 | } 177 | '' 178 | The plugins that will be displayed on the right and left sides of the panel, respectively. 179 | ''; 180 | 181 | size = 182 | defaultNullOpts.mkRonEnum [ "XS" "S" "M" "L" "XL" ] 183 | { 184 | __type = "enum"; 185 | variant = "M"; 186 | } 187 | '' 188 | The size of the panel. 189 | ''; 190 | }; 191 | }; 192 | in 193 | defaultNullOpts.mkNullable (lib.types.listOf panelSubmodule) 194 | [ 195 | { 196 | anchor = { 197 | __type = "enum"; 198 | variant = "Bottom"; 199 | }; 200 | anchor_gap = true; 201 | autohide = { 202 | __type = "optional"; 203 | value = { 204 | handle_size = 4; 205 | transition_time = 200; 206 | wait_time = 1000; 207 | }; 208 | }; 209 | background = { 210 | __type = "enum"; 211 | variant = "Dark"; 212 | }; 213 | expand_to_edges = true; 214 | name = "Panel"; 215 | margin = 4; 216 | opacity = 1.0; 217 | output = { 218 | __type = "enum"; 219 | variant = "Name"; 220 | value = [ "Virtual-1" ]; 221 | }; 222 | plugins_center = { 223 | __type = "optional"; 224 | value = [ "com.system76.CosmicAppletTime" ]; 225 | }; 226 | plugins_wings = { 227 | __type = "optional"; 228 | value = { 229 | __type = "tuple"; 230 | value = [ 231 | [ 232 | "com.system76.CosmicPanelWorkspacesButton" 233 | "com.system76.CosmicPanelAppButton" 234 | "com.system76.CosmicAppletWorkspaces" 235 | ] 236 | [ 237 | "com.system76.CosmicAppletInputSources" 238 | "com.system76.CosmicAppletStatusArea" 239 | "com.system76.CosmicAppletTiling" 240 | "com.system76.CosmicAppletAudio" 241 | "com.system76.CosmicAppletNetwork" 242 | "com.system76.CosmicAppletBattery" 243 | "com.system76.CosmicAppletNotifications" 244 | "com.system76.CosmicAppletBluetooth" 245 | "com.system76.CosmicAppletPower" 246 | ] 247 | ]; 248 | }; 249 | }; 250 | size = { 251 | __type = "enum"; 252 | variant = "M"; 253 | }; 254 | } 255 | ] 256 | '' 257 | The panels that will be displayed on the desktop. 258 | ''; 259 | 260 | config = 261 | let 262 | cfg = config.wayland.desktopManager.cosmic; 263 | 264 | version = 1; 265 | in 266 | lib.mkIf (cfg.panels != null) { 267 | wayland.desktopManager.cosmic.configFile = lib.mkMerge ( 268 | [ 269 | { 270 | "com.system76.CosmicPanel" = { 271 | entries.entries = map (panel: panel.name) cfg.panels; 272 | inherit version; 273 | }; 274 | } 275 | ] 276 | ++ (map (panel: { 277 | "com.system76.CosmicPanel.${panel.name}" = { 278 | entries = panel; 279 | inherit version; 280 | }; 281 | }) cfg.panels) 282 | ); 283 | 284 | home.activation.restartCosmicPanel = lib.mkIf (cfg.panels != null) ( 285 | lib.hm.dag.entryAfter [ 286 | "configureCosmic" 287 | ] "run ${lib.getExe pkgs.killall} .cosmic-panel-wrapped || true" 288 | ); 289 | }; 290 | } 291 | -------------------------------------------------------------------------------- /modules/files.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | inherit (builtins) concatStringsSep length mapAttrs; 9 | inherit (lib) 10 | flatten 11 | getExe 12 | mapAttrsToList 13 | mkEnableOption 14 | mkIf 15 | mkOption 16 | optionalString 17 | types 18 | ; 19 | inherit (lib.cosmic) 20 | cleanNullsExceptOptional 21 | mkAssertions 22 | mkRONExpression 23 | toRON 24 | ; 25 | inherit (lib.hm.dag) entryAfter entryBefore; 26 | in 27 | { 28 | options.wayland.desktopManager.cosmic = { 29 | configFile = mkOption { 30 | type = with types; attrsOf cosmicComponent; 31 | default = { }; 32 | example = mkRONExpression 0 { 33 | "com.system76.CosmicComp" = { 34 | version = 1; 35 | entries = { 36 | autotile = true; 37 | 38 | autotile_behavior = { 39 | __type = "enum"; 40 | variant = "PerWorkspace"; 41 | }; 42 | 43 | xkb_config = { 44 | rules = ""; 45 | model = ""; 46 | layout = "br"; 47 | variant = ""; 48 | options = { 49 | __type = "optional"; 50 | value = null; 51 | }; 52 | repeat_delay = 600; 53 | repeat_rate = 25; 54 | }; 55 | }; 56 | }; 57 | 58 | "com.system76.CosmicSettings" = { 59 | version = 1; 60 | entries = { 61 | active-page = "wallpaper"; 62 | }; 63 | }; 64 | 65 | "com.system76.CosmicTerm" = { 66 | version = 1; 67 | entries = { 68 | font_name = "JetBrains Mono"; 69 | font_size = 16; 70 | }; 71 | }; 72 | } null; 73 | description = '' 74 | Configuration files for COSMIC components stored in `$XDG_CONFIG_HOME`. 75 | 76 | Each component is identified by its unique identifier (e.g., `com.system76.CosmicComp`). 77 | 78 | Structure for each component: 79 | - `version`: Schema version number for the component configuration. 80 | - `entries`: Component-specific settings as key-value pairs. 81 | 82 | Entry values can be: 83 | - Simple types: booleans, integers, floating point numbers, strings. 84 | - Complex types: lists, and attribute sets (RON structs). 85 | - Special types: 86 | - `raw`: The value is stored as-is. 87 | - `optional`: The value is stored as an optional value. (e.g. `Some(value)` or `None`). 88 | - `char`: The value is stored as a single character. (e.g. `'a'`). 89 | - `map`: The value is stored as a map. (e.g. `{ "key" = "value" }`). 90 | - `tuple`: The value is stored as a tuple. (e.g. `(1, 2, 3)`). 91 | - `enum`: The value is stored as an enum variant. 92 | - Named structs: A structured entry with a name identifier. 93 | 94 | All values are serialized to RON format using `lib.cosmic.ron.toRON`. 95 | ''; 96 | }; 97 | 98 | dataFile = mkOption { 99 | type = with types; attrsOf cosmicComponent; 100 | default = { }; 101 | description = '' 102 | Data files for COSMIC components stored in `$XDG_DATA_HOME`. 103 | 104 | Each component is identified by its unique identifier (e.g., `com.system76.CosmicComp`). 105 | 106 | Structure for each component: 107 | - `version`: Schema version number for the component configuration. 108 | - `entries`: Component-specific settings as key-value pairs. 109 | 110 | Entry values can be: 111 | - Simple types: booleans, integers, floating point numbers, strings. 112 | - Complex types: lists, and attribute sets (RON structs). 113 | - Special types: 114 | - `raw`: The value is stored as-is. 115 | - `optional`: The value is stored as an optional value. (e.g. `Some(value)` or `None`). 116 | - `char`: The value is stored as a single character. (e.g. `'a'`). 117 | - `map`: The value is stored as a map. (e.g. `{ "key" = "value" }`). 118 | - `tuple`: The value is stored as a tuple. (e.g. `(1, 2, 3)`). 119 | - `enum`: The value is stored as an enum variant. 120 | - Named structs: A structured entry with a name identifier. 121 | 122 | All values are serialized to RON format using `lib.cosmic.ron.toRON`. 123 | ''; 124 | }; 125 | 126 | stateFile = mkOption { 127 | type = with types; attrsOf cosmicComponent; 128 | default = { }; 129 | example = mkRONExpression 0 { 130 | "com.system76.CosmicBackground" = { 131 | version = 1; 132 | entries = { 133 | wallpapers = [ 134 | { 135 | __type = "tuple"; 136 | value = [ 137 | "Virtual-1" 138 | { 139 | __type = "enum"; 140 | variant = "Path"; 141 | value = [ "/usr/share/backgrounds/cosmic/webb-inspired-wallpaper-system76.jpg" ]; 142 | } 143 | ]; 144 | } 145 | ]; 146 | }; 147 | }; 148 | } null; 149 | description = '' 150 | State files for COSMIC components stored in `$XDG_STATE_HOME`. 151 | 152 | Each component is identified by its unique identifier (e.g., `com.system76.CosmicComp`). 153 | 154 | Structure for each component: 155 | - `version`: Schema version number for the component configuration. 156 | - `entries`: Component-specific settings as key-value pairs. 157 | 158 | Entry values can be: 159 | - Simple types: booleans, integers, floating point numbers, strings. 160 | - Complex types: lists, and attribute sets (RON structs). 161 | - Special types: 162 | - `raw`: The value is stored as-is. 163 | - `optional`: The value is stored as an optional value. (e.g. `Some(value)` or `None`). 164 | - `char`: The value is stored as a single character. (e.g. `'a'`). 165 | - `map`: The value is stored as a map. (e.g. `{ "key" = "value" }`). 166 | - `tuple`: The value is stored as a tuple. (e.g. `(1, 2, 3)`). 167 | - `enum`: The value is stored as an enum variant. 168 | - Named structs: A structured entry with a name identifier. 169 | 170 | All values are serialized to RON format using `lib.cosmic.ron.toRON`. 171 | ''; 172 | }; 173 | 174 | resetFiles = mkEnableOption "COSMIC configuration files reset" // { 175 | description = '' 176 | Whether to enable COSMIC configuration files reset. 177 | 178 | When enabled, this option will delete any COSMIC-related files in the specified 179 | XDG directories that were not explicitly declared in your configuration. This 180 | ensures that your COSMIC desktop environment remains in a clean, known state 181 | as defined by your `home-manager` configuration. 182 | ''; 183 | }; 184 | 185 | resetFilesDirectories = mkOption { 186 | type = 187 | with types; 188 | listOf (enum [ 189 | "config" 190 | "data" 191 | "state" 192 | "cache" 193 | "runtime" 194 | ]); 195 | default = [ 196 | "config" 197 | "state" 198 | ]; 199 | example = [ 200 | "config" 201 | "data" 202 | "state" 203 | ]; 204 | description = '' 205 | XDG base directories to reset when `resetFiles` is enabled. 206 | 207 | Available directories: 208 | - `config`: User configuration (`$XDG_CONFIG_HOME`) 209 | - `data`: Application data (`$XDG_DATA_HOME`) 210 | - `state`: Runtime state (`$XDG_STATE_HOME`) 211 | - `cache`: Cached data (`$XDG_CACHE_HOME`) 212 | - `runtime`: Runtime files (`$XDG_RUNTIME_DIR`) 213 | ''; 214 | }; 215 | 216 | resetFilesExclude = mkOption { 217 | type = with types; listOf str; 218 | default = [ ]; 219 | example = [ 220 | "com.system76.CosmicComp" 221 | "dev.edfloreshz.CosmicTweaks/v1" 222 | "com.system76.CosmicSettings/v1/active-page" 223 | "com.system76.CosmicTerm/v1/{font_size,font_family}" 224 | "com.system76.{CosmicComp,CosmicPanel.Dock}/v1" 225 | ]; 226 | description = '' 227 | Patterns to exclude from the reset operation when `resetFiles` is enabled. 228 | Supports glob patterns and brace expansion for matching files and directories. 229 | 230 | Use this option to preserve specific files or directories from being reset. 231 | ''; 232 | }; 233 | }; 234 | 235 | config = 236 | let 237 | cfg = config.wayland.desktopManager.cosmic; 238 | 239 | makeOperations = 240 | xdg_directory: components: 241 | flatten ( 242 | mapAttrsToList (component: details: { 243 | inherit component xdg_directory; 244 | inherit (details) version; 245 | operation = "write"; 246 | 247 | entries = mapAttrs (_: value: toRON 0 value) (cleanNullsExceptOptional details.entries); 248 | }) components 249 | ); 250 | 251 | configurations = { 252 | "$schema" = "https://raw.githubusercontent.com/cosmic-utils/cosmic-ctl/refs/heads/main/schema.json"; 253 | operations = 254 | (makeOperations "config" cfg.configFile) 255 | ++ (makeOperations "data" cfg.dataFile) 256 | ++ (makeOperations "state" cfg.stateFile); 257 | }; 258 | 259 | json = (pkgs.formats.json { }).generate "configurations.json" configurations; 260 | in 261 | { 262 | assertions = mkAssertions "files" [ 263 | { 264 | assertion = cfg.resetFiles -> length cfg.resetFilesDirectories > 0; 265 | message = "At least one XDG directory must be selected to reset COSMIC files."; 266 | } 267 | ]; 268 | 269 | home.activation = 270 | let 271 | cosmic-ctl = getExe config.programs.cosmic-ext-ctl.package; 272 | in 273 | mkIf cfg.enable { 274 | configureCosmic = entryAfter [ "writeBoundary" ] "run ${cosmic-ctl} apply ${json}"; 275 | 276 | resetCosmic = mkIf cfg.resetFiles ( 277 | entryBefore [ "configureCosmic" ] 278 | "run ${cosmic-ctl} reset --force --xdg-dirs ${concatStringsSep "," cfg.resetFilesDirectories} ${ 279 | optionalString ( 280 | length cfg.resetFilesExclude > 0 281 | ) "--exclude ${concatStringsSep "," cfg.resetFilesExclude}" 282 | }" 283 | ); 284 | }; 285 | }; 286 | } 287 | -------------------------------------------------------------------------------- /modules/applications/by-name/cosmic-term/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (builtins) filter length; 4 | inherit (lib) 5 | imap0 6 | mkIf 7 | mkMerge 8 | mkOption 9 | types 10 | ; 11 | inherit (lib.cosmic) defaultNullOpts mkCosmicApplication mkThrow; 12 | inherit (lib.lists) findFirstIndex; 13 | in 14 | mkCosmicApplication { 15 | name = "cosmic-term"; 16 | originalName = "COSMIC Terminal Emulator"; 17 | identifier = "com.system76.CosmicTerm"; 18 | configurationVersion = 1; 19 | 20 | maintainers = [ lib.maintainers.HeitorAugustoLN ]; 21 | 22 | settingsOptions = { 23 | app_theme = 24 | defaultNullOpts.mkRonEnum [ "Dark" "Light" "System" ] 25 | { 26 | __type = "enum"; 27 | variant = "Dark"; 28 | } 29 | '' 30 | Controls the theme of the terminal. 31 | 32 | - `Dark`: Use the dark theme. 33 | - `Light`: Use the light theme. 34 | - `System`: Follows the system theme. 35 | ''; 36 | 37 | bold_font_weight = defaultNullOpts.mkU16 700 '' 38 | Specifies the weight of bold text characters in the terminal. 39 | ''; 40 | 41 | dim_font_weight = defaultNullOpts.mkU16 300 '' 42 | Specifies the weight of dim text characters in the terminal. 43 | ''; 44 | 45 | focus_follows_mouse = defaultNullOpts.mkBool true '' 46 | Whether to enable focus follows mouse in the terminal. 47 | 48 | When enabled, the terminal split will automatically receive focus 49 | when the mouse cursor hovers over it, without needing to click. 50 | ''; 51 | 52 | font_name = defaultNullOpts.mkStr "JetBrains Mono" '' 53 | Specificies the font family to use in the terminal. 54 | ''; 55 | 56 | font_size = defaultNullOpts.mkU16 12 '' 57 | Specifies the font size to use in the terminal. 58 | ''; 59 | 60 | font_size_zoom_step_mul_100 = defaultNullOpts.mkU16 100 '' 61 | Controls the granularity of font size changes when zooming. 62 | 63 | Value is multiplied by 0.01 to determine the zoom step (e.g. 100 = 1px). 64 | ''; 65 | 66 | font_stretch = defaultNullOpts.mkU16 100 '' 67 | Controls the horizontal font spacing of characters in the terminal. 68 | ''; 69 | 70 | font_weight = defaultNullOpts.mkU16 400 '' 71 | Specifies the weight of normal text characters in the terminal. 72 | ''; 73 | 74 | opacity = defaultNullOpts.mkNullableWithRaw (types.ints.between 0 100) 100 '' 75 | Specifies the opacity of the terminal background. 76 | ''; 77 | 78 | show_headerbar = defaultNullOpts.mkBool true '' 79 | Whether to show the terminal window title bar and menu. 80 | ''; 81 | 82 | syntax_theme_dark = defaultNullOpts.mkStr "COSMIC Dark" '' 83 | Specifies the color scheme used for syntax highlighting in dark mode. 84 | ''; 85 | 86 | syntax_theme_light = defaultNullOpts.mkStr "COSMIC Light" '' 87 | Specifies the color scheme used for syntax highlighting in light mode. 88 | ''; 89 | 90 | use_bright_bold = defaultNullOpts.mkBool true '' 91 | Whether the terminal should use bright bold text. 92 | ''; 93 | }; 94 | 95 | settingsExample = { 96 | app_theme = { 97 | __type = "enum"; 98 | variant = "Dark"; 99 | }; 100 | bold_font_weight = 700; 101 | dim_font_weight = 300; 102 | focus_follows_mouse = true; 103 | font_name = "JetBrains Mono"; 104 | font_size = 12; 105 | font_size_zoom_step_mul_100 = 100; 106 | font_stretch = 100; 107 | font_weight = 400; 108 | opacity = 100; 109 | show_headerbar = true; 110 | use_bright_bold = true; 111 | }; 112 | 113 | extraOptions = { 114 | profiles = 115 | let 116 | profileSubmodule = types.submodule { 117 | freeformType = with types; attrsOf anything; 118 | options = { 119 | command = defaultNullOpts.mkStr' { 120 | example = "bash"; 121 | description = '' 122 | The shell or program to execute when opening a new terminal instance with this profile. 123 | If it is not specified, it will default to your system shell. 124 | ''; 125 | apply = toString; 126 | }; 127 | hold = mkOption { 128 | type = types.bool; 129 | example = true; 130 | description = '' 131 | Whether the terminal should continue running after the command exits. 132 | ''; 133 | }; 134 | is_default = mkOption { 135 | type = types.bool; 136 | default = false; 137 | example = true; 138 | description = '' 139 | Whether the profile is the default. 140 | ''; 141 | }; 142 | name = mkOption { 143 | type = types.str; 144 | description = '' 145 | The name of the profile. 146 | ''; 147 | example = "Default"; 148 | }; 149 | syntax_theme_dark = mkOption { 150 | type = types.str; 151 | description = '' 152 | Specifies the color scheme used for syntax highlighting in dark mode for this profile. 153 | ''; 154 | example = "COSMIC Dark"; 155 | }; 156 | syntax_theme_light = mkOption { 157 | type = types.str; 158 | description = '' 159 | Specifies the color scheme used for syntax highlighting in light mode for this profile. 160 | ''; 161 | example = "COSMIC Light"; 162 | }; 163 | tab_title = defaultNullOpts.mkStr' { 164 | example = "Default"; 165 | description = '' 166 | Overrides the title of the terminal tab. 167 | If it is not specified, it will not override the title. 168 | ''; 169 | apply = toString; 170 | }; 171 | working_directory = defaultNullOpts.mkStr' { 172 | example = "/home/user"; 173 | description = '' 174 | The working directory to use when opening a new terminal instance with this profile. 175 | If it is not specified, it will continue using the current working directory. 176 | ''; 177 | apply = toString; 178 | }; 179 | }; 180 | }; 181 | in 182 | defaultNullOpts.mkNullable' { 183 | type = types.listOf profileSubmodule; 184 | example = [ 185 | { 186 | command = "bash"; 187 | hold = false; 188 | is_default = true; 189 | name = "Default"; 190 | syntax_theme_dark = "COSMIC Dark"; 191 | syntax_theme_light = "COSMIC Light"; 192 | tab_title = "Default"; 193 | working_directory = "/home/user"; 194 | } 195 | { 196 | command = "bash"; 197 | hold = false; 198 | is_default = false; 199 | name = "New Profile"; 200 | syntax_theme_dark = "Catppuccin Mocha"; 201 | syntax_theme_light = "Catppuccin Latte"; 202 | tab_title = "New Profile"; 203 | } 204 | ]; 205 | description = '' 206 | The profiles to use in the terminal. 207 | ''; 208 | apply = 209 | profiles: 210 | let 211 | defaultProfiles = filter (profile: profile.is_default) profiles; 212 | in 213 | if length defaultProfiles > 1 then 214 | mkThrow "cosmic-term" "Only one profile can be the default." 215 | else if length defaultProfiles < 1 then 216 | mkThrow "cosmic-term" "At least one profile must be the default." 217 | else 218 | profiles; 219 | }; 220 | 221 | colorSchemes = 222 | let 223 | mkColorsSubmodule = 224 | scope: 225 | types.submodule { 226 | freeformType = with types; attrsOf anything; 227 | options = { 228 | black = mkOption { 229 | type = types.hexColor; 230 | description = '' 231 | The black color of ${scope} colors. 232 | ''; 233 | example = "#000000"; 234 | }; 235 | blue = mkOption { 236 | type = types.hexColor; 237 | description = '' 238 | The blue color of ${scope} colors. 239 | ''; 240 | example = "#0000FF"; 241 | }; 242 | cyan = mkOption { 243 | type = types.hexColor; 244 | description = '' 245 | The cyan color of ${scope} colors. 246 | ''; 247 | example = "#00FFFF"; 248 | }; 249 | green = mkOption { 250 | type = types.hexColor; 251 | description = '' 252 | The green color of ${scope} colors. 253 | ''; 254 | example = "#00FF00"; 255 | }; 256 | magenta = mkOption { 257 | type = types.hexColor; 258 | description = '' 259 | The magenta color of ${scope} colors. 260 | ''; 261 | example = "#FF00FF"; 262 | }; 263 | red = mkOption { 264 | type = types.hexColor; 265 | description = '' 266 | The red color of ${scope} colors. 267 | ''; 268 | example = "#FF0000"; 269 | }; 270 | white = mkOption { 271 | type = types.hexColor; 272 | description = '' 273 | The white color of ${scope} colors. 274 | ''; 275 | example = "#FFFFFF"; 276 | }; 277 | yellow = mkOption { 278 | type = types.hexColor; 279 | description = '' 280 | The yellow color of ${scope} colors. 281 | ''; 282 | example = "#FFFF00"; 283 | }; 284 | }; 285 | }; 286 | colorSchemeSubmodule = types.submodule { 287 | freeformType = with types; attrsOf anything; 288 | options = { 289 | bright = mkOption { 290 | type = mkColorsSubmodule "bright"; 291 | description = '' 292 | The bright colors of the terminal. 293 | ''; 294 | }; 295 | bright_foreground = mkOption { 296 | type = types.hexColor; 297 | description = '' 298 | The bright foreground color of the terminal. 299 | ''; 300 | example = "#FFFFFF"; 301 | }; 302 | cursor = mkOption { 303 | type = types.hexColor; 304 | description = '' 305 | The color of the terminal cursor. 306 | ''; 307 | example = "#FFFFFF"; 308 | }; 309 | dim = mkOption { 310 | type = mkColorsSubmodule "dim"; 311 | description = '' 312 | The dim colors of the terminal. 313 | ''; 314 | }; 315 | dim_foreground = mkOption { 316 | type = types.hexColor; 317 | description = '' 318 | The dim foreground color of the terminal. 319 | ''; 320 | example = "#FFFFFF"; 321 | }; 322 | foreground = mkOption { 323 | type = types.hexColor; 324 | description = '' 325 | The foreground color of the terminal. 326 | ''; 327 | example = "#FFFFFF"; 328 | }; 329 | mode = mkOption { 330 | type = types.enum [ 331 | "dark" 332 | "light" 333 | ]; 334 | description = '' 335 | The mode of the colorscheme. 336 | ''; 337 | example = "dark"; 338 | }; 339 | name = mkOption { 340 | type = types.str; 341 | description = '' 342 | The name of the colorscheme. 343 | ''; 344 | example = "Catppuccin Mocha"; 345 | }; 346 | normal = mkOption { 347 | type = mkColorsSubmodule "normal"; 348 | description = '' 349 | The normal colors of the terminal. 350 | ''; 351 | }; 352 | }; 353 | }; 354 | in 355 | defaultNullOpts.mkNullable' { 356 | type = types.listOf colorSchemeSubmodule; 357 | example = [ 358 | { 359 | mode = "dark"; 360 | name = "Catppuccin Mocha"; 361 | foreground = "#CDD6F4"; 362 | cursor = "#F5E0DC"; 363 | bright_foreground = "#CDD6F4"; 364 | dim_foreground = "#6C7086"; 365 | normal = { 366 | black = "#45475A"; 367 | red = "#F38BA8"; 368 | green = "#A6E3A1"; 369 | yellow = "#F9E2AF"; 370 | blue = "#89B4FA"; 371 | magenta = "#F5C2E7"; 372 | cyan = "#94E2D5"; 373 | white = "#BAC2DE"; 374 | }; 375 | bright = { 376 | black = "#585B70"; 377 | red = "#F38BA8"; 378 | green = "#A6E3A1"; 379 | yellow = "#F9E2AF"; 380 | blue = "#89B4FA"; 381 | magenta = "#F5C2E7"; 382 | cyan = "#94E2D5"; 383 | white = "#A6ADC8"; 384 | }; 385 | dim = { 386 | black = "#45475A"; 387 | red = "#F38BA8"; 388 | green = "#A6E3A1"; 389 | yellow = "#F9E2AF"; 390 | blue = "#89B4FA"; 391 | magenta = "#F5C2E7"; 392 | cyan = "#94E2D5"; 393 | white = "#BAC2DE"; 394 | }; 395 | } 396 | ]; 397 | description = '' 398 | The color schemes to include in the terminal. 399 | ''; 400 | }; 401 | }; 402 | 403 | extraConfig = cfg: { 404 | wayland.desktopManager.cosmic.configFile."com.system76.CosmicTerm".entries = mkMerge [ 405 | (mkIf (cfg.profiles != null) { 406 | default_profile = { 407 | __type = "optional"; 408 | value = findFirstIndex (profile: profile.is_default) null cfg.profiles; 409 | }; 410 | 411 | profiles = { 412 | __type = "map"; 413 | value = imap0 (index: profile: { 414 | key = index; 415 | value = removeAttrs profile [ "is_default" ]; 416 | }) cfg.profiles; 417 | }; 418 | }) 419 | 420 | (mkIf (cfg.colorSchemes != null) { 421 | color_schemes_dark = { 422 | __type = "map"; 423 | value = imap0 (index: colorscheme: { 424 | key = index; 425 | value = removeAttrs colorscheme [ "mode" ]; 426 | }) (filter (colorscheme: colorscheme.mode == "dark") cfg.colorSchemes); 427 | }; 428 | 429 | color_schemes_light = { 430 | __type = "map"; 431 | value = imap0 (index: colorscheme: { 432 | key = index; 433 | value = removeAttrs colorscheme [ "mode" ]; 434 | }) (filter (colorscheme: colorscheme.mode == "light") cfg.colorSchemes); 435 | }; 436 | }) 437 | ]; 438 | }; 439 | } 440 | -------------------------------------------------------------------------------- /lib/ron.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (builtins) 4 | all 5 | attrNames 6 | fromJSON 7 | head 8 | isAttrs 9 | isList 10 | isString 11 | length 12 | listToAttrs 13 | match 14 | readFile 15 | substring 16 | stringLength 17 | typeOf 18 | ; 19 | inherit (lib) 20 | boolToString 21 | concatImapStringsSep 22 | foldl 23 | hasInfix 24 | last 25 | optionalString 26 | pipe 27 | splitString 28 | stringToCharacters 29 | toInt 30 | trim 31 | ; 32 | inherit (lib.cosmic) mkAssertion mkThrow mkWarning; 33 | inherit (lib.strings) escapeNixString floatToString replicate; 34 | 35 | fromRON = 36 | let 37 | fromRON' = 38 | str: 39 | let 40 | trimmed = trim str; 41 | 42 | firstChar = substring 0 1 trimmed; 43 | lastChar = substring (stringLength trimmed - 1) 1 trimmed; 44 | 45 | splitItems = 46 | str: 47 | let 48 | content = pipe str [ 49 | trim 50 | (substring 1 (stringLength str - 2)) 51 | trim 52 | ]; 53 | 54 | split = 55 | acc: current: depth: rest: 56 | if rest == "" then 57 | if current != "" then acc ++ [ (trim current) ] else acc 58 | else 59 | let 60 | char = substring 0 1 rest; 61 | remaining = substring 1 (-1) rest; 62 | in 63 | if char == "(" || char == "[" || char == "{" then 64 | split acc (current + char) (depth + 1) remaining 65 | else if char == ")" || char == "]" || char == "}" then 66 | split acc (current + char) (depth - 1) remaining 67 | else if char == "," && depth == 0 then 68 | split (acc ++ [ (trim current) ]) "" depth remaining 69 | else 70 | split acc (current + char) depth remaining; 71 | in 72 | split [ ] "" 0 content; 73 | 74 | findFirstColon = 75 | str: 76 | let 77 | helper = 78 | pos: depth: 79 | if pos >= stringLength str then 80 | null 81 | else 82 | let 83 | char = substring pos 1 str; 84 | in 85 | if char == "(" || char == "[" || char == "{" then 86 | helper (pos + 1) (depth + 1) 87 | else if char == ")" || char == "]" || char == "}" then 88 | helper (pos + 1) (depth - 1) 89 | else if char == ":" && depth == 0 then 90 | pos 91 | else 92 | helper (pos + 1) depth; 93 | in 94 | helper 0 0; 95 | 96 | isStruct = 97 | str: 98 | let 99 | content = pipe str [ 100 | trim 101 | (substring 1 (stringLength str - 2)) 102 | trim 103 | ]; 104 | colonPos = findFirstColon content; 105 | 106 | beforeColon = substring 0 colonPos content; 107 | depth = foldl ( 108 | acc: char: 109 | if char == "(" || char == "[" || char == "{" then 110 | acc + 1 111 | else if char == ")" || char == "]" || char == "}" then 112 | acc - 1 113 | else 114 | acc 115 | ) 0 (stringToCharacters beforeColon); 116 | in 117 | colonPos != null && depth == 0; 118 | in 119 | if firstChar == "[" && lastChar == "]" then 120 | map fromRON' (splitItems trimmed) 121 | else if firstChar == "{" && lastChar == "}" then 122 | { 123 | __type = "map"; 124 | value = map ( 125 | item: 126 | let 127 | colonPos = findFirstColon item; 128 | key = trim (substring 0 colonPos item); 129 | value = trim (substring (colonPos + 1) (-1) item); 130 | in 131 | { 132 | key = fromRON' key; 133 | value = fromRON' value; 134 | } 135 | ) (splitItems trimmed); 136 | } 137 | else if trimmed == "None" then 138 | { 139 | __type = "optional"; 140 | value = null; 141 | } 142 | else if match "Some\\(.*\\)" trimmed != null then 143 | let 144 | value = head (match "Some\\((.*)\\)" trimmed); 145 | in 146 | { 147 | __type = "optional"; 148 | value = fromRON' value; 149 | } 150 | else if trimmed == "true" then 151 | true 152 | else if trimmed == "false" then 153 | false 154 | else if firstChar == "(" && lastChar == ")" then 155 | if isStruct trimmed then 156 | listToAttrs ( 157 | map ( 158 | item: 159 | let 160 | colonPos = findFirstColon item; 161 | name = trim (substring 0 colonPos item); 162 | value = trim (substring (colonPos + 1) (-1) item); 163 | in 164 | { 165 | inherit name; 166 | value = fromRON' value; 167 | } 168 | ) (splitItems trimmed) 169 | ) 170 | else 171 | { 172 | __type = "tuple"; 173 | value = map fromRON' (splitItems trimmed); 174 | } 175 | else if match "[A-Za-z_][A-Za-z0-9_]*\\(.*\\)" trimmed != null then 176 | let 177 | matches = match "([A-Za-z_][A-Za-z0-9_]*)(\\(.*\\))" trimmed; 178 | name = trim (head matches); 179 | value = trim (last matches); 180 | in 181 | if isStruct value then 182 | { 183 | __type = "namedStruct"; 184 | inherit name; 185 | value = fromRON' value; 186 | } 187 | else 188 | { 189 | __type = "enum"; 190 | variant = name; 191 | value = map fromRON' (splitItems value); 192 | } 193 | else if match "-?[0-9]+[.][0-9]+" trimmed != null then 194 | let 195 | decimals = pipe trimmed [ 196 | (splitString ".") 197 | last 198 | stringLength 199 | ]; 200 | in 201 | if decimals > 5 then 202 | { 203 | __type = "raw"; 204 | value = trimmed; 205 | } 206 | else 207 | fromJSON trimmed 208 | else if match "-?[0-9]+" trimmed != null then 209 | toInt trimmed 210 | else if match "'.'" trimmed != null then 211 | { 212 | __type = "char"; 213 | value = substring 1 1 trimmed; 214 | } 215 | else if match ''".*"'' trimmed != null then 216 | fromJSON trimmed 217 | else 218 | { 219 | __type = "raw"; 220 | value = trimmed; 221 | }; 222 | in 223 | mkWarning "fromRON" 224 | "This function is experimental, from my testing it works well, but it may not work in all cases. Please report any issues you find." 225 | fromRON'; 226 | in 227 | { 228 | inherit fromRON; 229 | 230 | importRON = 231 | path: 232 | pipe path [ 233 | readFile 234 | fromRON 235 | ]; 236 | 237 | isRONType = 238 | v: 239 | v ? __type 240 | && ( 241 | ( 242 | ( 243 | v.__type == "char" 244 | || v.__type == "map" 245 | || v.__type == "optional" 246 | || v.__type == "raw" 247 | || v.__type == "tuple" 248 | ) 249 | && v ? value 250 | ) 251 | || (v.__type == "namedStruct" && v ? name && v ? value) 252 | || (v.__type == "enum" && (v ? variant || (v ? variant && v ? value))) 253 | ); 254 | 255 | mkRON = 256 | type: value: 257 | { 258 | char = { 259 | __type = "char"; 260 | inherit value; 261 | }; 262 | 263 | enum = 264 | if isAttrs value then 265 | assert mkAssertion "mkRON" ( 266 | attrNames value == [ 267 | "value" 268 | "variant" 269 | ] 270 | ) "enum type must receive a string or an attribute set with value and variant keys value"; 271 | { 272 | __type = "enum"; 273 | inherit (value) value variant; 274 | } 275 | else 276 | { 277 | __type = "enum"; 278 | variant = value; 279 | }; 280 | 281 | map = { 282 | __type = "map"; 283 | inherit value; 284 | }; 285 | 286 | namedStruct = 287 | assert mkAssertion "mkRON" ( 288 | isAttrs value 289 | && 290 | attrNames value == [ 291 | "name" 292 | "value" 293 | ] 294 | ) "namedStruct type must receive an attribute set with name and value keys."; 295 | { 296 | __type = "namedStruct"; 297 | inherit (value) name value; 298 | }; 299 | 300 | optional = { 301 | __type = "optional"; 302 | inherit value; 303 | }; 304 | 305 | raw = { 306 | __type = "raw"; 307 | inherit value; 308 | }; 309 | 310 | tuple = { 311 | __type = "tuple"; 312 | inherit value; 313 | }; 314 | } 315 | .${type} or (mkThrow "mkRON" "${type} is not supported."); 316 | 317 | toRON = 318 | let 319 | toRON' = 320 | startIndent: value: 321 | let 322 | type = typeOf value; 323 | nextIndent = startIndent + 1; 324 | 325 | indent = level: replicate level " "; 326 | in 327 | { 328 | bool = boolToString value; 329 | 330 | float = 331 | let 332 | trimFloatString = 333 | float: 334 | let 335 | string = floatToString float; 336 | in 337 | if hasInfix "." string then head (match "([0-9]+[.][0-9]*[1-9]|[0-9]+[.]0)0*" string) else string; 338 | in 339 | trimFloatString value; 340 | 341 | int = toString value; 342 | lambda = mkThrow "toRON" "Functions are not supported in RON"; 343 | 344 | list = 345 | let 346 | count = length value; 347 | in 348 | if count == 0 then 349 | "[]" 350 | else 351 | "[\n${ 352 | concatImapStringsSep "\n" ( 353 | index: element: 354 | "${indent nextIndent}${toRON' nextIndent element}${optionalString (index != count) ","}" 355 | ) value 356 | },\n${indent startIndent}]"; 357 | 358 | null = mkThrow "toRON" '' 359 | Null values are cleaned up by lib.cosmic.utils.cleanNullsExceptOptional. 360 | If you are seeing this message, please report this, as it should not happen. 361 | 362 | If you want to represent a null value in RON, you can use the `optional` type. 363 | ''; 364 | 365 | path = pipe value [ 366 | toString 367 | escapeNixString 368 | ]; 369 | 370 | set = 371 | if value ? __type then 372 | if value.__type == "raw" then 373 | assert mkAssertion "toRON" (value ? value) "raw type must have a value."; 374 | assert mkAssertion "toRON" (isString value.value) "raw type value must be a string."; 375 | 376 | value.value 377 | else if value.__type == "optional" then 378 | assert mkAssertion "toRON" (value ? value) "optional type must have a value."; 379 | 380 | if value.value == null then "None" else "Some(${toRON' startIndent value.value})" 381 | else if value.__type == "char" then 382 | assert mkAssertion "toRON" (value ? value) "char type must have a value."; 383 | assert mkAssertion "toRON" (isString value.value) "char type value must be a string."; 384 | assert mkAssertion "toRON" ( 385 | stringLength value.value == 1 386 | ) "char type value must be a single character string."; 387 | 388 | "'${value.value}'" 389 | else if value.__type == "enum" then 390 | assert mkAssertion "toRON" (value ? variant) "enum type must have a variant."; 391 | assert mkAssertion "toRON" (isString value.variant) "enum type variant must be a string value."; 392 | 393 | if value ? value then 394 | assert mkAssertion "toRON" (isList value.value) "enum type must have a list of values."; 395 | 396 | let 397 | count = length value.value; 398 | in 399 | if count == 0 then 400 | "${value.variant}()" 401 | else 402 | "${value.variant}(\n${ 403 | concatImapStringsSep "\n" ( 404 | index: element: 405 | "${indent nextIndent}${toRON' nextIndent element}${optionalString (index != count) ","}" 406 | ) value.value 407 | },\n${indent startIndent})" 408 | else 409 | value.variant 410 | else if value.__type == "map" then 411 | assert mkAssertion "toRON" (value ? value) "map type must have a value."; 412 | assert mkAssertion "toRON" (isList value.value) "map type value must be a list."; 413 | assert mkAssertion "toRON" (all isAttrs value.value) 414 | "map type value must be a list of attribute sets."; 415 | 416 | let 417 | count = length value.value; 418 | in 419 | if count == 0 then 420 | "{}" 421 | else 422 | "{\n${ 423 | concatImapStringsSep "\n" ( 424 | index: entry: 425 | assert mkAssertion "toRON" ( 426 | let 427 | keys = attrNames entry; 428 | in 429 | keys == [ 430 | "key" 431 | "value" 432 | ] 433 | ) "map type entry must have only 'key' and 'value' attributes."; 434 | 435 | "${indent nextIndent}${toRON' nextIndent entry.key}: ${toRON' nextIndent entry.value}${ 436 | optionalString (index != count) "," 437 | }" 438 | ) value.value 439 | },\n${indent startIndent}}" 440 | else if value.__type == "tuple" then 441 | assert mkAssertion "toRON" (value ? value) "tuple type must have a value."; 442 | assert mkAssertion "toRON" (isList value.value) "tuple type value must be a list."; 443 | 444 | let 445 | count = length value.value; 446 | in 447 | if count == 0 then 448 | "()" 449 | else 450 | "(\n${ 451 | concatImapStringsSep "\n" ( 452 | index: element: 453 | "${indent nextIndent}${toRON' nextIndent element}${optionalString (index != count) ","}" 454 | ) value.value 455 | },\n${indent startIndent})" 456 | else if value.__type == "namedStruct" then 457 | assert mkAssertion "toRON" (value ? name) "namedStruct type must have a name."; 458 | assert mkAssertion "toRON" (isString value.name) "namedStruct type name must be a string."; 459 | assert mkAssertion "toRON" (value ? value) "namedStruct type must have a value."; 460 | assert mkAssertion "toRON" (isAttrs value.value) "namedStruct type value must be a attribute set."; 461 | 462 | let 463 | keys = attrNames value.value; 464 | count = length keys; 465 | in 466 | if count == 0 then 467 | "${value.name}()" 468 | else 469 | "${value.name}(\n${ 470 | concatImapStringsSep "\n" ( 471 | index: key: 472 | "${indent nextIndent}${key}: ${toRON' nextIndent value.value.${key}}${ 473 | optionalString (index != count) "," 474 | }" 475 | ) keys 476 | },\n${indent startIndent})" 477 | else 478 | mkThrow "toRON" "set type ${toString value.__type} is not supported." 479 | else 480 | let 481 | keys = attrNames value; 482 | count = length keys; 483 | in 484 | if count == 0 then 485 | "()" 486 | else 487 | "(\n${ 488 | concatImapStringsSep "\n" ( 489 | index: key: 490 | "${indent nextIndent}${key}: ${toRON' nextIndent value.${key}}${ 491 | optionalString (index != count) "," 492 | }" 493 | ) keys 494 | },\n${indent startIndent})"; 495 | 496 | string = escapeNixString value; 497 | } 498 | .${type} or (mkThrow "toRON" "${type} is not supported."); 499 | in 500 | toRON'; 501 | } 502 | -------------------------------------------------------------------------------- /lib/options.nix: -------------------------------------------------------------------------------- 1 | # Heavily inspired by nixvim 2 | { lib, ... }: 3 | let 4 | inherit (builtins) 5 | getAttr 6 | isAttrs 7 | isInt 8 | isList 9 | isString 10 | mapAttrs 11 | removeAttrs 12 | toJSON 13 | ; 14 | inherit (lib) 15 | literalExpression 16 | mkOption 17 | optionalAttrs 18 | types 19 | ; 20 | inherit (lib.cosmic) 21 | isRONType 22 | mkAssertion 23 | mkRON 24 | mkThrow 25 | ; 26 | inherit (lib.generators) toPretty; 27 | inherit (lib.strings) replicate; 28 | 29 | literalRON = 30 | r: 31 | let 32 | raw = mkRON "raw" r; 33 | expression = ''cosmicLib.cosmic.mkRON "raw" ${toJSON raw.value}''; 34 | in 35 | literalExpression expression; 36 | 37 | mkNullOrOption' = 38 | { 39 | type, 40 | default ? null, 41 | ... 42 | }@args: 43 | mkOption ( 44 | args 45 | // { 46 | type = types.nullOr type; 47 | inherit default; 48 | } 49 | ); 50 | 51 | mkRONExpression = 52 | let 53 | mkRONExpression' = 54 | startIndent: value: previousType: 55 | let 56 | nextIndent = startIndent + 1; 57 | 58 | indent = level: replicate level " "; 59 | 60 | toRONExpression = 61 | type: value: 62 | let 63 | v = nestedRONExpression type value (indent startIndent); 64 | in 65 | if previousType == null || previousType == "namedStruct" then 66 | v 67 | else 68 | nestedLiteral "(${v.__pretty v.val})"; 69 | in 70 | if isRONType value then 71 | if value.__type == "enum" then 72 | if value ? variant then 73 | if value ? value then 74 | toRONExpression "enum" { 75 | inherit (value) variant; 76 | value = map (v: mkRONExpression' (nextIndent + 1) v "enum") value.value; 77 | } 78 | else 79 | toRONExpression "enum" value.variant 80 | else 81 | mkThrow "mkRONExpression" "enum type must have at least a variant key." 82 | else if value.__type == "namedStruct" then 83 | if value ? name && value ? value then 84 | toRONExpression "namedStruct" { 85 | inherit (value) name; 86 | value = mapAttrs (_: v: mkRONExpression' nextIndent v "namedStruct") value.value; 87 | } 88 | else 89 | mkThrow "mkRONExpression" "namedStruct type must have name and value keys." 90 | else if isRONType value.value then 91 | toRONExpression value.__type (mkRONExpression' startIndent value.value value.__type) 92 | else if isList value.value then 93 | toRONExpression value.__type (map (v: mkRONExpression' nextIndent v value.__type) value.value) 94 | else if isAttrs value.value then 95 | toRONExpression value.__type ( 96 | mapAttrs (_: v: mkRONExpression' nextIndent v value.__type) value.value 97 | ) 98 | else 99 | toRONExpression value.__type value.value 100 | else if isList value then 101 | map (v: mkRONExpression' nextIndent v "list") value 102 | else if isAttrs value then 103 | mapAttrs (_: v: mkRONExpression' nextIndent v null) value 104 | else 105 | value; 106 | in 107 | mkRONExpression'; 108 | 109 | nestedLiteral = val: { 110 | __pretty = getAttr "text"; 111 | val = if val._type or null == "literalExpression" then val else literalExpression val; 112 | }; 113 | 114 | nestedRONExpression = 115 | type: value: indent: 116 | nestedLiteral (RONExpression type value indent); 117 | 118 | RONExpression = 119 | type: value: indent: 120 | literalExpression ''cosmicLib.cosmic.mkRON "${type}" ${ 121 | toPretty { 122 | allowPrettyValues = true; 123 | inherit indent; 124 | } value 125 | }''; 126 | in 127 | { 128 | inherit 129 | literalRON 130 | mkNullOrOption' 131 | mkRONExpression 132 | nestedLiteral 133 | nestedRONExpression 134 | RONExpression 135 | ; 136 | 137 | defaultNullOpts = 138 | let 139 | processDefaultNullArgs = 140 | args: 141 | assert mkAssertion "defaultNullOpts" (!(args ? default)) "unexpected argument `default`."; 142 | assert mkAssertion "defaultNullOpts" (!(args ? defaultText)) "unexpected argument `defaultText`."; 143 | args // { default = null; }; 144 | 145 | mkAttrs' = args: mkNullableWithRaw' (args // { type = types.attrs; }); 146 | 147 | mkAttrsOf' = 148 | { type, ... }@args: mkNullableWithRaw' (args // { type = with types; attrsOf (maybeRonRaw type); }); 149 | 150 | mkBool' = args: mkNullableWithRaw' (args // { type = types.bool; }); 151 | 152 | mkEnum' = 153 | { variants, ... }@args: 154 | assert mkAssertion "defaultNullOpts.mkEnum'" (isList variants) "`variants` must be a list"; 155 | mkNullableWithRaw' (removeAttrs args [ "variants" ] // { type = types.enum variants; }); 156 | 157 | mkFloat' = args: mkNullableWithRaw' (args // { type = types.float; }); 158 | 159 | mkHexColor' = args: mkNullableWithRaw' (args // { type = types.hexColor; }); 160 | 161 | mkI8' = args: mkNullableWithRaw' (args // { type = types.ints.s8; }); 162 | 163 | mkI16' = args: mkNullableWithRaw' (args // { type = types.ints.s16; }); 164 | 165 | mkI32' = args: mkNullableWithRaw' (args // { type = types.ints.s32; }); 166 | 167 | mkInt' = args: mkNullableWithRaw' (args // { type = types.int; }); 168 | 169 | mkListOf' = 170 | { type, ... }@args: mkNullableWithRaw' (args // { type = with types; listOf (maybeRonRaw type); }); 171 | 172 | mkNullable' = 173 | args: 174 | mkNullOrOption' ( 175 | processDefaultNullArgs args 176 | // optionalAttrs (args ? example) { 177 | example = mkRONExpression 0 args.example null; 178 | } 179 | ); 180 | 181 | mkNullableWithRaw' = { type, ... }@args: mkNullable' (args // { type = types.maybeRonRaw type; }); 182 | 183 | mkNumber' = args: mkNullableWithRaw' (args // { type = types.number; }); 184 | 185 | mkRaw' = 186 | args: 187 | mkNullable' ( 188 | args 189 | // { 190 | type = types.ronRaw; 191 | } 192 | // optionalAttrs (args ? example) { 193 | example = 194 | if isString args.example then literalRON args.example else mkRONExpression 0 args.example null; 195 | } 196 | ); 197 | 198 | mkRonArrayOf' = 199 | { size, type, ... }@args: 200 | assert mkAssertion "defaultNullOpts.mkRonArrayOf'" (isInt size) "`size` must be an integer"; 201 | mkNullableWithRaw' ( 202 | removeAttrs args [ "size" ] 203 | // { 204 | type = with types; ronArrayOf (maybeRonRaw type) size; 205 | } 206 | ); 207 | 208 | mkRonChar' = args: mkNullableWithRaw' (args // { type = types.ronChar; }); 209 | 210 | mkRonEnum' = 211 | { variants, ... }@args: 212 | assert mkAssertion "defaultNullOpts.mkRonEnum'" (isList variants) "`variants` must be a list"; 213 | mkNullableWithRaw' (removeAttrs args [ "variants" ] // { type = types.ronEnum variants; }); 214 | 215 | mkRonMap' = args: mkNullableWithRaw' (args // { type = types.ronMap; }); 216 | 217 | mkRonMapOf' = 218 | { type, ... }@args: 219 | mkNullableWithRaw' (args // { type = with types; ronMapOf (maybeRonRaw type); }); 220 | 221 | mkRonNamedStruct' = args: mkNullableWithRaw' (args // { type = types.ronNamedStruct; }); 222 | 223 | mkRonNamedStructOf' = 224 | { type, ... }@args: 225 | mkNullableWithRaw' (args // { type = with types; ronNamedStructOf (maybeRonRaw type); }); 226 | 227 | mkRonOptional' = args: mkNullableWithRaw' (args // { type = types.ronOptional; }); 228 | 229 | mkRonOptionalOf' = 230 | { type, ... }@args: 231 | mkNullableWithRaw' (args // { type = with types; ronOptionalOf (maybeRonRaw type); }); 232 | 233 | mkRonTuple' = 234 | { size, ... }@args: 235 | assert mkAssertion "defaultNullOpts.mkRonTuple'" (isInt size) "`size` must be an integer"; 236 | mkNullableWithRaw' (removeAttrs args [ "size" ] // { type = types.ronTuple size; }); 237 | 238 | mkRonTupleEnum' = 239 | { size, variants, ... }@args: 240 | assert mkAssertion "defaultNullOpts.mkRonTupleEnum'" (isList variants) "`variants` must be a list"; 241 | assert mkAssertion "defaultNullOpts.mkRonTupleEnum'" (isInt size) "`size` must be an integer"; 242 | mkNullableWithRaw' ( 243 | removeAttrs args [ 244 | "size" 245 | "variants" 246 | ] 247 | // { 248 | type = types.ronTupleEnum variants size; 249 | } 250 | ); 251 | 252 | mkRonTupleEnumOf' = 253 | { 254 | size, 255 | type, 256 | variants, 257 | ... 258 | }@args: 259 | assert mkAssertion "defaultNullOpts.mkRonTupleEnumOf'" (isList variants) 260 | "`variants` must be a list"; 261 | assert mkAssertion "defaultNullOpts.mkRonTupleEnumOf'" (isInt size) "`size` must be an integer"; 262 | mkNullableWithRaw' ( 263 | removeAttrs args [ 264 | "size" 265 | "variants" 266 | ] 267 | // { 268 | type = with types; ronTupleEnumOf (maybeRonRaw type) variants size; 269 | } 270 | ); 271 | 272 | mkRonTupleOf' = 273 | { size, type, ... }@args: 274 | assert mkAssertion "defaultNullOpts.mkRonTupleOf'" (isInt size) "`size` must be an integer"; 275 | mkNullableWithRaw' ( 276 | removeAttrs args [ "size" ] 277 | // { 278 | type = with types; ronTupleOf (maybeRonRaw type) size; 279 | } 280 | ); 281 | 282 | mkPositiveInt' = args: mkNullableWithRaw' (args // { type = types.ints.positive; }); 283 | 284 | mkStr' = args: mkNullableWithRaw' (args // { type = types.str; }); 285 | 286 | mkU8' = args: mkNullableWithRaw' (args // { type = types.ints.u8; }); 287 | 288 | mkU16' = args: mkNullableWithRaw' (args // { type = types.ints.u16; }); 289 | 290 | mkU32' = args: mkNullableWithRaw' (args // { type = types.ints.u32; }); 291 | 292 | mkUnsignedInt' = args: mkNullableWithRaw' (args // { type = types.ints.unsigned; }); 293 | in 294 | { 295 | inherit 296 | mkAttrs' 297 | mkAttrsOf' 298 | mkBool' 299 | mkEnum' 300 | mkFloat' 301 | mkHexColor' 302 | mkI8' 303 | mkI16' 304 | mkI32' 305 | mkInt' 306 | mkListOf' 307 | mkNullable' 308 | mkNullableWithRaw' 309 | mkNumber' 310 | mkRaw' 311 | mkRonArrayOf' 312 | mkRonChar' 313 | mkRonEnum' 314 | mkRonMap' 315 | mkRonMapOf' 316 | mkRonNamedStruct' 317 | mkRonNamedStructOf' 318 | mkRonOptional' 319 | mkRonOptionalOf' 320 | mkRonTuple' 321 | mkRonTupleEnum' 322 | mkRonTupleEnumOf' 323 | mkRonTupleOf' 324 | mkPositiveInt' 325 | mkStr' 326 | mkU8' 327 | mkU16' 328 | mkU32' 329 | mkUnsignedInt' 330 | ; 331 | 332 | mkAttrs = example: description: mkAttrs' { inherit description example; }; 333 | 334 | mkAttrsOf = 335 | type: example: description: 336 | mkAttrsOf' { inherit description example type; }; 337 | 338 | mkBool = example: description: mkBool' { inherit description example; }; 339 | 340 | mkEnum = 341 | variants: example: description: 342 | mkEnum' { inherit description example variants; }; 343 | 344 | mkFloat = example: description: mkFloat' { inherit description example; }; 345 | 346 | mkHexColor = example: description: mkHexColor' { inherit description example; }; 347 | 348 | mkI8 = example: description: mkI8' { inherit description example; }; 349 | 350 | mkI16 = example: description: mkI16' { inherit description example; }; 351 | 352 | mkI32 = example: description: mkI32' { inherit description example; }; 353 | 354 | mkInt = example: description: mkInt' { inherit description example; }; 355 | 356 | mkListOf = 357 | type: example: description: 358 | mkListOf' { inherit description example type; }; 359 | 360 | mkNullable = 361 | type: example: description: 362 | mkNullable' { inherit description example type; }; 363 | 364 | mkNullableWithRaw = 365 | type: example: description: 366 | mkNullableWithRaw' { inherit description example type; }; 367 | 368 | mkNumber = example: description: mkNumber' { inherit description example; }; 369 | 370 | mkRaw = example: description: mkRaw' { inherit description example; }; 371 | 372 | mkRonArrayOf = 373 | type: size: example: description: 374 | mkRonArrayOf' { 375 | inherit 376 | description 377 | example 378 | size 379 | type 380 | ; 381 | }; 382 | 383 | mkRonChar = example: description: mkRonChar' { inherit description example; }; 384 | 385 | mkRonEnum = 386 | variants: example: description: 387 | mkRonEnum' { inherit description example variants; }; 388 | 389 | mkRonMap = example: description: mkRonMap' { inherit description example; }; 390 | 391 | mkRonMapOf = 392 | type: example: description: 393 | mkRonMapOf' { inherit description example type; }; 394 | 395 | mkRonNamedStruct = example: description: mkRonNamedStruct' { inherit description example; }; 396 | 397 | mkRonNamedStructOf = 398 | type: example: description: 399 | mkRonNamedStructOf' { inherit description example type; }; 400 | 401 | mkRonOptional = example: description: mkRonOptional' { inherit description example; }; 402 | 403 | mkRonOptionalOf = 404 | type: example: description: 405 | mkRonOptionalOf' { inherit description example type; }; 406 | 407 | mkRonTuple = 408 | size: example: description: 409 | mkRonTuple' { inherit description example size; }; 410 | 411 | mkRonTupleEnum = 412 | variants: size: example: description: 413 | mkRonTupleEnum' { 414 | inherit 415 | description 416 | example 417 | size 418 | variants 419 | ; 420 | }; 421 | 422 | mkRonTupleEnumOf = 423 | type: variants: size: example: description: 424 | mkRonTupleEnumOf' { 425 | inherit 426 | description 427 | example 428 | size 429 | type 430 | variants 431 | ; 432 | }; 433 | 434 | mkRonTupleOf = 435 | type: size: example: description: 436 | mkRonTupleOf' { 437 | inherit 438 | description 439 | example 440 | size 441 | type 442 | ; 443 | }; 444 | 445 | mkPositiveInt = example: description: mkPositiveInt' { inherit description example; }; 446 | 447 | mkStr = example: description: mkStr' { inherit description example; }; 448 | 449 | mkU8 = example: description: mkU8' { inherit description example; }; 450 | 451 | mkU16 = example: description: mkU16' { inherit description example; }; 452 | 453 | mkU32 = example: description: mkU32' { inherit description example; }; 454 | 455 | mkUnsignedInt = example: description: mkUnsignedInt' { inherit description example; }; 456 | }; 457 | 458 | mkNullOrOption = type: description: mkNullOrOption' { inherit description type; }; 459 | 460 | mkSettingsOption = 461 | { 462 | description, 463 | example ? null, 464 | options ? { }, 465 | }: 466 | mkOption { 467 | type = 468 | with types; 469 | submodule { 470 | freeformType = attrsOf anything; 471 | inherit options; 472 | }; 473 | default = { }; 474 | example = 475 | if example == null then 476 | let 477 | ex = { 478 | bool = true; 479 | char = { 480 | __type = "char"; 481 | value = "a"; 482 | }; 483 | enum = { 484 | __type = "enum"; 485 | variant = "FooBar"; 486 | }; 487 | float = 3.14; 488 | int = 333; 489 | list = [ 490 | "foo" 491 | "bar" 492 | "baz" 493 | ]; 494 | map = { 495 | __type = "map"; 496 | value = [ 497 | { 498 | key = "foo"; 499 | value = "bar"; 500 | } 501 | ]; 502 | }; 503 | namedStruct = { 504 | __type = "namedStruct"; 505 | name = "foo"; 506 | value = { 507 | bar = "baz"; 508 | }; 509 | }; 510 | optional = { 511 | __type = "optional"; 512 | value = "foo"; 513 | }; 514 | raw = { 515 | __type = "raw"; 516 | value = "foo"; 517 | }; 518 | string = "hello"; 519 | struct = { 520 | foo = "bar"; 521 | }; 522 | tuple = { 523 | __type = "tuple"; 524 | value = [ 525 | "foo" 526 | "bar" 527 | "baz" 528 | ]; 529 | }; 530 | tupleEnum = { 531 | __type = "enum"; 532 | variant = "FooBar"; 533 | value = [ "baz" ]; 534 | }; 535 | }; 536 | in 537 | mkRONExpression 0 ex null 538 | else 539 | mkRONExpression 0 example null; 540 | inherit description; 541 | }; 542 | 543 | nestedLiteralRON = r: nestedLiteral (literalRON r); 544 | } 545 | -------------------------------------------------------------------------------- /modules/compositor.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | inherit (lib) mkIf mkOption types; 4 | inherit (lib.cosmic) defaultNullOpts mkRONExpression; 5 | in 6 | { 7 | options.wayland.desktopManager.cosmic.compositor = 8 | let 9 | compositorSubmodule = 10 | let 11 | inputSubmodule = types.submodule { 12 | freeformType = with types; attrsOf anything; 13 | options = { 14 | acceleration = 15 | let 16 | accelerationSubmodule = types.submodule { 17 | freeformType = with types; attrsOf anything; 18 | options = { 19 | profile = mkOption { 20 | type = 21 | with types; 22 | maybeRonRaw ( 23 | ronOptionalOf ( 24 | maybeRonRaw (ronEnum [ 25 | "Adaptive" 26 | "Flat" 27 | ]) 28 | ) 29 | ); 30 | example = mkRONExpression 0 { 31 | __type = "optional"; 32 | value = { 33 | __type = "enum"; 34 | variant = "Flat"; 35 | }; 36 | } null; 37 | }; 38 | 39 | speed = mkOption { 40 | type = with types; maybeRonRaw float; 41 | example = 0.0; 42 | description = '' 43 | The speed of the input device. 44 | ''; 45 | }; 46 | }; 47 | }; 48 | in 49 | defaultNullOpts.mkNullable (types.ronOptionalOf accelerationSubmodule) 50 | { 51 | __type = "optional"; 52 | value = { 53 | profile = { 54 | __type = "optional"; 55 | value = { 56 | __type = "enum"; 57 | variant = "Flat"; 58 | }; 59 | }; 60 | speed = 0.0; 61 | }; 62 | } 63 | '' 64 | The acceleration configuration for the input device. 65 | ''; 66 | 67 | calibration = 68 | defaultNullOpts.mkRonOptionalOf (with types; ronTupleOf float 6) 69 | { 70 | __type = "optional"; 71 | value = { 72 | __type = "tuple"; 73 | value = [ 74 | 1.0 75 | 0.0 76 | 0.0 77 | 0.0 78 | 1.0 79 | 0.0 80 | ]; 81 | }; 82 | } 83 | '' 84 | The calibration matrix for the input device. 85 | ''; 86 | 87 | click_method = 88 | defaultNullOpts.mkRonOptionalOf 89 | (types.ronEnum [ 90 | "ButtonAreas" 91 | "Clickfinger" 92 | ]) 93 | { 94 | __type = "optional"; 95 | value = { 96 | __type = "enum"; 97 | variant = "ButtonAreas"; 98 | }; 99 | } 100 | '' 101 | The click method for the input device. 102 | ''; 103 | 104 | disable_while_typing = 105 | defaultNullOpts.mkRonOptionalOf types.bool 106 | { 107 | __type = "optional"; 108 | value = true; 109 | } 110 | '' 111 | Whether to disable the input device while typing. 112 | ''; 113 | 114 | left_handed = 115 | defaultNullOpts.mkRonOptionalOf types.bool 116 | { 117 | __type = "optional"; 118 | value = false; 119 | } 120 | '' 121 | Whether the input device is left-handed. 122 | ''; 123 | 124 | map_to_output = 125 | defaultNullOpts.mkRonOptionalOf types.str 126 | { 127 | __type = "optional"; 128 | value = "HDMI-A-1"; 129 | } 130 | '' 131 | The output to map the input device to. 132 | ''; 133 | 134 | middle_button_emulation = 135 | defaultNullOpts.mkRonOptionalOf types.bool 136 | { 137 | __type = "optional"; 138 | value = false; 139 | } 140 | '' 141 | Whether to emulate the middle button. 142 | ''; 143 | 144 | rotation_angle = 145 | defaultNullOpts.mkRonOptionalOf types.ints.u32 146 | { 147 | __type = "optional"; 148 | value = 0; 149 | } 150 | '' 151 | The rotation angle of the input device. 152 | ''; 153 | 154 | scroll_config = 155 | let 156 | scrollConfigSubmodule = types.submodule { 157 | freeformType = with types; attrsOf anything; 158 | options = { 159 | method = mkOption { 160 | type = 161 | with types; 162 | maybeRonRaw ( 163 | ronOptionalOf ( 164 | maybeRonRaw (ronEnum [ 165 | "Edge" 166 | "NoScroll" 167 | "OnButtonDown" 168 | "TwoFinger" 169 | ]) 170 | ) 171 | ); 172 | example = mkRONExpression 0 { 173 | __type = "optional"; 174 | value = { 175 | __type = "enum"; 176 | variant = "Edge"; 177 | }; 178 | } null; 179 | description = '' 180 | The scroll method of the input device. 181 | ''; 182 | }; 183 | 184 | natural_scroll = mkOption { 185 | type = with types; maybeRonRaw (ronOptionalOf (maybeRonRaw bool)); 186 | example = true; 187 | description = '' 188 | Whether to enable natural scrolling. 189 | ''; 190 | }; 191 | 192 | scroll_button = mkOption { 193 | type = with types; maybeRonRaw (ronOptionalOf (maybeRonRaw ints.u32)); 194 | example = 2; 195 | description = '' 196 | The scroll button of the input device. 197 | ''; 198 | }; 199 | 200 | scroll_factor = mkOption { 201 | type = with types; maybeRonRaw (ronOptionalOf (maybeRonRaw float)); 202 | example = 1.0; 203 | description = '' 204 | The scroll factor of the input device. 205 | ''; 206 | }; 207 | }; 208 | }; 209 | in 210 | defaultNullOpts.mkNullable (types.ronOptionalOf scrollConfigSubmodule) 211 | { 212 | method = { 213 | __type = "optional"; 214 | value = { 215 | __type = "enum"; 216 | variant = "Edge"; 217 | }; 218 | }; 219 | natural_scroll = { 220 | __type = "optional"; 221 | value = true; 222 | }; 223 | scroll_button = { 224 | __type = "optional"; 225 | value = 2; 226 | }; 227 | scroll_factor = { 228 | __type = "optional"; 229 | value = 1.0; 230 | }; 231 | } 232 | '' 233 | The scroll configuration for the input device. 234 | ''; 235 | 236 | state = 237 | defaultNullOpts.mkRonEnum [ "Disabled" "DisabledOnExternalMouse" "Enabled" ] 238 | { 239 | __type = "enum"; 240 | variant = "Enabled"; 241 | } 242 | '' 243 | The state of the input module. 244 | 245 | If set to Disabled, the input module is disabled. 246 | If set to DisabledOnExternalMouse, the input module is disabled when an external mouse is connected. 247 | If set to Enabled, the input module is enabled. 248 | ''; 249 | 250 | tap_config = 251 | let 252 | tapConfigSubmodule = types.submodule { 253 | freeformType = with types; attrsOf anything; 254 | options = { 255 | button_map = mkOption { 256 | type = 257 | with types; 258 | maybeRonRaw ( 259 | ronOptionalOf ( 260 | maybeRonRaw (ronEnum [ 261 | "LeftMiddleRight" 262 | "LeftRightMiddle" 263 | ]) 264 | ) 265 | ); 266 | example = mkRONExpression 0 { 267 | __type = "optional"; 268 | value = { 269 | __type = "enum"; 270 | variant = "LeftMiddleRight"; 271 | }; 272 | } null; 273 | }; 274 | 275 | drag = mkOption { 276 | type = with types; maybeRonRaw bool; 277 | example = true; 278 | description = '' 279 | Whether to enable drag. 280 | ''; 281 | }; 282 | 283 | drag_lock = mkOption { 284 | type = with types; maybeRonRaw bool; 285 | example = true; 286 | description = '' 287 | Whether to enable drag lock. 288 | ''; 289 | }; 290 | 291 | enabled = mkOption { 292 | type = with types; maybeRonRaw bool; 293 | example = true; 294 | description = '' 295 | Whether to enable tap. 296 | ''; 297 | }; 298 | }; 299 | }; 300 | in 301 | defaultNullOpts.mkNullable (types.ronOptionalOf tapConfigSubmodule) 302 | { 303 | button_map = { 304 | __type = "optional"; 305 | value = { 306 | __type = "enum"; 307 | variant = "LeftMiddleRight"; 308 | }; 309 | }; 310 | drag = { 311 | __type = "optional"; 312 | value = true; 313 | }; 314 | drag_lock = { 315 | __type = "optional"; 316 | value = true; 317 | }; 318 | enabled = { 319 | __type = "optional"; 320 | value = true; 321 | }; 322 | } 323 | '' 324 | The tap configuration for the input device. 325 | ''; 326 | }; 327 | }; 328 | in 329 | types.submodule { 330 | freeformType = with types; attrsOf anything; 331 | options = { 332 | accessibility_zoom = 333 | let 334 | accessibilityZoomSubmodule = types.submodule { 335 | freeformType = with types; attrsOf anything; 336 | options = { 337 | increment = mkOption { 338 | type = with types; maybeRonRaw ints.u32; 339 | example = 50; 340 | description = '' 341 | The zoom increment. 342 | ''; 343 | }; 344 | 345 | start_on_login = mkOption { 346 | type = with types; maybeRonRaw bool; 347 | example = false; 348 | description = '' 349 | Whether to start the accessibility zoom on login. 350 | ''; 351 | }; 352 | 353 | view_moves = mkOption { 354 | type = 355 | with types; 356 | maybeRonRaw (ronEnum [ 357 | "Centered" 358 | "Continuously" 359 | "OnEdge" 360 | ]); 361 | example = mkRONExpression 0 { 362 | __type = "enum"; 363 | variant = "Continuously"; 364 | } null; 365 | description = '' 366 | The view moves of the accessibility zoom. 367 | ''; 368 | }; 369 | }; 370 | }; 371 | in 372 | defaultNullOpts.mkNullable accessibilityZoomSubmodule 373 | { 374 | increment = 50; 375 | start_on_login = false; 376 | view_moves = { 377 | __type = "enum"; 378 | variant = "Continuously"; 379 | }; 380 | } 381 | '' 382 | The accessibility zoom configuration. 383 | ''; 384 | 385 | active_hint = defaultNullOpts.mkBool true '' 386 | Whether to show the active window hint. 387 | ''; 388 | 389 | autotile = defaultNullOpts.mkBool false '' 390 | Whether to automatically tile windows. 391 | ''; 392 | 393 | autotile_behavior = 394 | defaultNullOpts.mkRonEnum [ "Global" "PerWorkspace" ] 395 | { 396 | __type = "enum"; 397 | variant = "PerWorkspace"; 398 | } 399 | '' 400 | Automatic tiling behavior. 401 | 402 | If set to Global, autotile applies to all windows in all workspaces. 403 | If set to PerWorkspace, autotile only applies to new windows, and new workspaces. 404 | ''; 405 | 406 | cursor_follows_focus = defaultNullOpts.mkBool false '' 407 | Whether the cursor should follow the focused window. 408 | ''; 409 | 410 | descale_xwayland = defaultNullOpts.mkBool false '' 411 | Whether to let XWayland windows be scaled by themselves. 412 | ''; 413 | 414 | edge_snap_threshold = defaultNullOpts.mkU32 0 '' 415 | The edge snap threshold. 416 | ''; 417 | 418 | focus_follows_cursor = defaultNullOpts.mkBool false '' 419 | Whether the focused window should follow the cursor. 420 | ''; 421 | 422 | focus_follows_cursor_delay = defaultNullOpts.mkUnsignedInt 250 '' 423 | The delay in milliseconds before the focused window follows the cursor. 424 | ''; 425 | 426 | input_default = 427 | defaultNullOpts.mkNullable inputSubmodule 428 | { 429 | acceleration = { 430 | profile = { 431 | __type = "optional"; 432 | value = { 433 | __type = "enum"; 434 | variant = "Flat"; 435 | }; 436 | }; 437 | speed = 0.0; 438 | }; 439 | state = { 440 | __type = "enum"; 441 | variant = "Enabled"; 442 | }; 443 | } 444 | '' 445 | The input configuration for mice. 446 | ''; 447 | 448 | input_touchpad = 449 | defaultNullOpts.mkNullable inputSubmodule 450 | { 451 | acceleration = { 452 | profile = { 453 | __type = "optional"; 454 | value = { 455 | __type = "enum"; 456 | variant = "Flat"; 457 | }; 458 | }; 459 | speed = 0.0; 460 | }; 461 | click_method = { 462 | __type = "optional"; 463 | value = { 464 | __type = "enum"; 465 | variant = "Clickfinger"; 466 | }; 467 | }; 468 | disable_while_typing = { 469 | __type = "optional"; 470 | value = true; 471 | }; 472 | state = { 473 | __type = "enum"; 474 | variant = "Enabled"; 475 | }; 476 | tap_config = { 477 | button_map = { 478 | __type = "optional"; 479 | value = { 480 | __type = "enum"; 481 | variant = "LeftMiddleRight"; 482 | }; 483 | }; 484 | drag = { 485 | __type = "optional"; 486 | value = true; 487 | }; 488 | drag_lock = { 489 | __type = "optional"; 490 | value = true; 491 | }; 492 | enabled = { 493 | __type = "optional"; 494 | value = true; 495 | }; 496 | }; 497 | } 498 | '' 499 | The input configuration for touchpad. 500 | ''; 501 | 502 | keyboard_config = 503 | let 504 | keyboardConfigSubmodule = types.submodule { 505 | freeformType = with types; attrsOf anything; 506 | options.numlock_state = mkOption { 507 | type = 508 | with types; 509 | maybeRonRaw (ronEnum [ 510 | "BootOff" 511 | "BootOn" 512 | "LastBoot" 513 | ]); 514 | example = mkRONExpression 0 { 515 | __type = "enum"; 516 | variant = "BootOff"; 517 | } null; 518 | description = '' 519 | The numlock state of the keyboard. 520 | ''; 521 | }; 522 | }; 523 | in 524 | defaultNullOpts.mkNullable keyboardConfigSubmodule 525 | { 526 | numlock_state = { 527 | __type = "enum"; 528 | variant = "BootOff"; 529 | }; 530 | } 531 | '' 532 | The keyboard configuration. 533 | ''; 534 | 535 | workspaces = 536 | let 537 | workspacesSubmodule = types.submodule { 538 | freeformType = with types; attrsOf anything; 539 | options = { 540 | workspace_layout = mkOption { 541 | type = 542 | with types; 543 | maybeRonRaw (ronEnum [ 544 | "Horizontal" 545 | "Vertical" 546 | ]); 547 | example = mkRONExpression 0 { 548 | __type = "enum"; 549 | variant = "Vertical"; 550 | } null; 551 | description = '' 552 | The layout of the workspaces. 553 | 554 | If set to Horizontal, workspaces are arranged horizontally. 555 | If set to Vertical, workspaces are arranged vertically. 556 | ''; 557 | }; 558 | 559 | workspace_mode = mkOption { 560 | type = 561 | with types; 562 | maybeRonRaw (ronEnum [ 563 | "Global" 564 | "OutputBound" 565 | ]); 566 | example = mkRONExpression 0 { 567 | __type = "enum"; 568 | variant = "OutputBound"; 569 | } null; 570 | description = '' 571 | The mode of the workspaces. 572 | 573 | If set to Global, workspaces are shared across all outputs. 574 | If set to OutputBound, workspaces are bound to the output they are created on. 575 | ''; 576 | }; 577 | }; 578 | }; 579 | in 580 | defaultNullOpts.mkNullable workspacesSubmodule 581 | { 582 | workspace_layout = { 583 | __type = "enum"; 584 | variant = "Vertical"; 585 | }; 586 | workspace_mode = { 587 | __type = "enum"; 588 | variant = "OutputBound"; 589 | }; 590 | } 591 | '' 592 | The workspaces configuration for the COSMIC compositor. 593 | ''; 594 | 595 | xkb_config = 596 | let 597 | xkbConfigSubmodule = types.submodule { 598 | freeformType = with types; attrsOf anything; 599 | options = { 600 | layout = mkOption { 601 | type = with types; maybeRonRaw str; 602 | example = "br"; 603 | description = '' 604 | The keyboard layout. 605 | ''; 606 | }; 607 | 608 | model = mkOption { 609 | type = with types; maybeRonRaw str; 610 | example = "pc104"; 611 | description = '' 612 | The keyboard model. 613 | ''; 614 | }; 615 | 616 | options = mkOption { 617 | type = with types; maybeRonRaw (ronOptionalOf (maybeRonRaw str)); 618 | example = "terminate:ctrl_alt_bksp"; 619 | description = '' 620 | The keyboard options. 621 | ''; 622 | }; 623 | 624 | repeat_delay = mkOption { 625 | type = with types; maybeRonRaw ints.u32; 626 | example = 600; 627 | description = '' 628 | The keyboard repeat delay. 629 | ''; 630 | }; 631 | 632 | repeat_rate = mkOption { 633 | type = with types; maybeRonRaw ints.u32; 634 | example = 25; 635 | description = '' 636 | The keyboard repeat rate. 637 | ''; 638 | }; 639 | 640 | rules = mkOption { 641 | type = with types; maybeRonRaw str; 642 | description = '' 643 | The keyboard rules. 644 | ''; 645 | }; 646 | 647 | variant = mkOption { 648 | type = with types; maybeRonRaw str; 649 | example = "dvorak"; 650 | description = '' 651 | The keyboard variant. 652 | ''; 653 | }; 654 | }; 655 | }; 656 | in 657 | defaultNullOpts.mkNullable xkbConfigSubmodule 658 | { 659 | layout = "br"; 660 | model = "pc104"; 661 | options = { 662 | __type = "optional"; 663 | value = "terminate:ctrl_alt_bksp"; 664 | }; 665 | repeat_delay = 600; 666 | repeat_rate = 25; 667 | rules = ""; 668 | variant = "dvorak"; 669 | } 670 | '' 671 | The keyboard configuration for the COSMIC compositor. 672 | ''; 673 | }; 674 | }; 675 | in 676 | defaultNullOpts.mkNullable compositorSubmodule 677 | { 678 | active_hint = true; 679 | autotile = false; 680 | 681 | autotile_behavior = { 682 | __type = "enum"; 683 | variant = "PerWorkspace"; 684 | }; 685 | 686 | cursor_follows_focus = false; 687 | descale_xwayland = false; 688 | edge_snap_threshold = 0; 689 | focus_follows_cursor = false; 690 | focus_follows_cursor_delay = 250; 691 | 692 | workspaces = { 693 | workspace_layout = { 694 | __type = "enum"; 695 | variant = "Vertical"; 696 | }; 697 | 698 | workspace_mode = { 699 | __type = "enum"; 700 | variant = "OutputBound"; 701 | }; 702 | }; 703 | 704 | xkb_config = { 705 | layout = "br"; 706 | model = "pc104"; 707 | options = { 708 | __type = "optional"; 709 | value = "terminate:ctrl_alt_bksp"; 710 | }; 711 | repeat_delay = 600; 712 | repeat_rate = 25; 713 | rules = ""; 714 | variant = "dvorak"; 715 | }; 716 | } 717 | '' 718 | The COSMIC compositor configuration. 719 | ''; 720 | 721 | config.wayland.desktopManager.cosmic.configFile."com.system76.CosmicComp" = 722 | let 723 | cfg = config.wayland.desktopManager.cosmic; 724 | in 725 | mkIf (cfg.compositor != null) { 726 | entries = cfg.compositor; 727 | version = 1; 728 | }; 729 | } 730 | --------------------------------------------------------------------------------