mirror of
https://github.com/DreamMaoMao/maomaowm.git
synced 2026-04-06 07:15:53 -04:00
Compare commits
No commits in common. "main" and "0.12.8" have entirely different histories.
6 changed files with 92 additions and 566 deletions
|
|
@ -185,7 +185,7 @@ mangowm is available in the **PikaOS package repository**.
|
||||||
You can install it using the `pikman` package manager:
|
You can install it using the `pikman` package manager:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pikman install mangowm
|
pikman install mangowc
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
24
mangowm.scm
24
mangowm.scm
|
|
@ -15,7 +15,7 @@
|
||||||
#:use-module (gnu packages ninja)
|
#:use-module (gnu packages ninja)
|
||||||
#:use-module (gnu packages pkg-config)
|
#:use-module (gnu packages pkg-config)
|
||||||
#:use-module (guix build-system meson)
|
#:use-module (guix build-system meson)
|
||||||
#:use-module ((guix licenses) #:prefix license:))
|
#:use-module (guix licenses))
|
||||||
|
|
||||||
|
|
||||||
(define-public mangowm-git
|
(define-public mangowm-git
|
||||||
|
|
@ -36,13 +36,10 @@
|
||||||
(add-before 'configure 'patch-meson
|
(add-before 'configure 'patch-meson
|
||||||
(lambda _
|
(lambda _
|
||||||
(substitute* "meson.build"
|
(substitute* "meson.build"
|
||||||
;; MangoWM ignores sysconfdir handling for NixOS.
|
|
||||||
;; We also need to skip that sysconfdir edits.
|
|
||||||
(("is_nixos = false")
|
|
||||||
"is_nixos = true")
|
|
||||||
;; Unhardcode path. Fixes loading default config.
|
|
||||||
(("'-DSYSCONFDIR=\\\"@0@\\\"'.format\\('/etc'\\)")
|
(("'-DSYSCONFDIR=\\\"@0@\\\"'.format\\('/etc'\\)")
|
||||||
"'-DSYSCONFDIR=\"@0@\"'.format(sysconfdir)")))))))
|
"'-DSYSCONFDIR=\"@0@\"'.format(sysconfdir)")
|
||||||
|
(("sysconfdir = sysconfdir.substring\\(prefix.length\\(\\)\\)")
|
||||||
|
"")))))))
|
||||||
(inputs (list wayland
|
(inputs (list wayland
|
||||||
libinput
|
libinput
|
||||||
libdrm
|
libdrm
|
||||||
|
|
@ -55,17 +52,14 @@
|
||||||
pcre2
|
pcre2
|
||||||
libxcb
|
libxcb
|
||||||
xcb-util-wm
|
xcb-util-wm
|
||||||
wlroots-0.19
|
wlroots
|
||||||
scenefx))
|
scenefx))
|
||||||
(native-inputs (list pkg-config wayland-protocols))
|
(native-inputs (list pkg-config wayland-protocols))
|
||||||
(home-page "https://github.com/mangowm/mango")
|
(home-page "https://github.com/DreamMaoMao/mangowm")
|
||||||
(synopsis "Wayland compositor based on wlroots and scenefx")
|
(synopsis "Wayland compositor based on wlroots and scenefx")
|
||||||
(description
|
(description "A Wayland compositor based on wlroots and scenefx,
|
||||||
"MangoWM is a modern, lightweight, high-performance Wayland compositor
|
inspired by dwl but aiming to be more feature-rich.")
|
||||||
built on dwl — crafted for speed, flexibility, and a customizable desktop experience.")
|
(license gpl3)))
|
||||||
(license (list license:gpl3 ;mangowm itself, dwl
|
|
||||||
license:expat ;dwm, sway, wlroots
|
|
||||||
license:cc0)))) ;tinywl
|
|
||||||
|
|
||||||
(define-deprecated-package mangowc
|
(define-deprecated-package mangowc
|
||||||
mangowm-git)
|
mangowm-git)
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,18 @@
|
||||||
self:
|
self: {
|
||||||
{
|
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}:
|
}: let
|
||||||
let
|
|
||||||
cfg = config.wayland.windowManager.mango;
|
cfg = config.wayland.windowManager.mango;
|
||||||
selflib = import ./lib.nix lib;
|
|
||||||
variables = lib.concatStringsSep " " cfg.systemd.variables;
|
variables = lib.concatStringsSep " " cfg.systemd.variables;
|
||||||
extraCommands = lib.concatStringsSep " && " cfg.systemd.extraCommands;
|
extraCommands = lib.concatStringsSep " && " cfg.systemd.extraCommands;
|
||||||
systemdActivation = "${pkgs.dbus}/bin/dbus-update-activation-environment --systemd ${variables}; ${extraCommands}";
|
systemdActivation = ''${pkgs.dbus}/bin/dbus-update-activation-environment --systemd ${variables}; ${extraCommands}'';
|
||||||
autostart_sh = pkgs.writeShellScript "autostart.sh" ''
|
autostart_sh = pkgs.writeShellScript "autostart.sh" ''
|
||||||
${lib.optionalString cfg.systemd.enable systemdActivation}
|
${lib.optionalString cfg.systemd.enable systemdActivation}
|
||||||
${cfg.autostart_sh}
|
${cfg.autostart_sh}
|
||||||
'';
|
'';
|
||||||
in
|
in {
|
||||||
{
|
|
||||||
options = {
|
options = {
|
||||||
wayland.windowManager.mango = with lib; {
|
wayland.windowManager.mango = with lib; {
|
||||||
enable = mkOption {
|
enable = mkOption {
|
||||||
|
|
@ -79,176 +75,31 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
settings = mkOption {
|
settings = mkOption {
|
||||||
type =
|
description = "mango config content";
|
||||||
with lib.types;
|
|
||||||
let
|
|
||||||
valueType =
|
|
||||||
nullOr (oneOf [
|
|
||||||
bool
|
|
||||||
int
|
|
||||||
float
|
|
||||||
str
|
|
||||||
path
|
|
||||||
(attrsOf valueType)
|
|
||||||
(listOf valueType)
|
|
||||||
])
|
|
||||||
// {
|
|
||||||
description = "Mango configuration value";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
valueType;
|
|
||||||
default = { };
|
|
||||||
description = ''
|
|
||||||
Mango configuration written in Nix. Entries with the same key
|
|
||||||
should be written as lists. Variables and colors names should be
|
|
||||||
quoted. See <https://mangowc.vercel.app/docs> for more examples.
|
|
||||||
|
|
||||||
::: {.note}
|
|
||||||
This option uses a structured format that is converted to Mango's
|
|
||||||
configuration syntax. Nested attributes are flattened with underscore separators.
|
|
||||||
For example: `animation.duration_open = 400` becomes `animation_duration_open = 400`
|
|
||||||
|
|
||||||
Keymodes (submaps) are supported via the special `keymode` attribute. Each keymode
|
|
||||||
is a nested attribute set under `keymode` that contains its own bindings.
|
|
||||||
:::
|
|
||||||
'';
|
|
||||||
example = lib.literalExpression ''
|
|
||||||
{
|
|
||||||
# Window effects
|
|
||||||
blur = 1;
|
|
||||||
blur_optimized = 1;
|
|
||||||
blur_params = {
|
|
||||||
radius = 5;
|
|
||||||
num_passes = 2;
|
|
||||||
};
|
|
||||||
border_radius = 6;
|
|
||||||
focused_opacity = 1.0;
|
|
||||||
|
|
||||||
# Animations - use underscores for multi-part keys
|
|
||||||
animations = 1;
|
|
||||||
animation_type_open = "slide";
|
|
||||||
animation_type_close = "slide";
|
|
||||||
animation_duration_open = 400;
|
|
||||||
animation_duration_close = 800;
|
|
||||||
|
|
||||||
# Or use nested attrs (will be flattened with underscores)
|
|
||||||
animation_curve = {
|
|
||||||
open = "0.46,1.0,0.29,1";
|
|
||||||
close = "0.08,0.92,0,1";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Use lists for duplicate keys like bind and tagrule
|
|
||||||
bind = [
|
|
||||||
"SUPER,r,reload_config"
|
|
||||||
"Alt,space,spawn,rofi -show drun"
|
|
||||||
"Alt,Return,spawn,foot"
|
|
||||||
"ALT,R,setkeymode,resize" # Enter resize mode
|
|
||||||
];
|
|
||||||
|
|
||||||
tagrule = [
|
|
||||||
"id:1,layout_name:tile"
|
|
||||||
"id:2,layout_name:scroller"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Keymodes (submaps) for modal keybindings
|
|
||||||
keymode = {
|
|
||||||
resize = {
|
|
||||||
bind = [
|
|
||||||
"NONE,Left,resizewin,-10,0"
|
|
||||||
"NONE,Escape,setkeymode,default"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
extraConfig = mkOption {
|
|
||||||
type = types.lines;
|
type = types.lines;
|
||||||
default = "";
|
default = "";
|
||||||
description = ''
|
|
||||||
Extra configuration lines to add to `~/.config/mango/config.conf`.
|
|
||||||
This is useful for advanced configurations that don't fit the structured
|
|
||||||
settings format, or for options that aren't yet supported by the module.
|
|
||||||
'';
|
|
||||||
example = ''
|
example = ''
|
||||||
# Advanced config that doesn't fit structured format
|
# menu and terminal
|
||||||
special_option = 1
|
bind=Alt,space,spawn,rofi -show drun
|
||||||
|
bind=Alt,Return,spawn,foot
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
topPrefixes = mkOption {
|
|
||||||
type = with lib.types; listOf str;
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
List of prefixes for attributes that should appear at the top of the config file.
|
|
||||||
Attributes starting with these prefixes will be sorted to the beginning.
|
|
||||||
'';
|
|
||||||
example = [ "source" ];
|
|
||||||
};
|
|
||||||
bottomPrefixes = mkOption {
|
|
||||||
type = with lib.types; listOf str;
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
List of prefixes for attributes that should appear at the bottom of the config file.
|
|
||||||
Attributes starting with these prefixes will be sorted to the end.
|
|
||||||
'';
|
|
||||||
example = [ "source" ];
|
|
||||||
};
|
|
||||||
autostart_sh = mkOption {
|
autostart_sh = mkOption {
|
||||||
description = ''
|
description = "WARRNING: This is a shell script, but no need to add shebang";
|
||||||
Shell script to run on mango startup. No shebang needed.
|
|
||||||
|
|
||||||
When this option is set, the script will be written to
|
|
||||||
`~/.config/mango/autostart.sh` and an `exec-once` line
|
|
||||||
will be automatically added to the config to execute it.
|
|
||||||
'';
|
|
||||||
type = types.lines;
|
type = types.lines;
|
||||||
default = "";
|
default = "";
|
||||||
example = ''
|
example = ''
|
||||||
waybar &
|
waybar &
|
||||||
dunst &
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable (
|
config = lib.mkIf cfg.enable {
|
||||||
let
|
|
||||||
finalConfigText =
|
|
||||||
# Support old string-based config during transition period
|
|
||||||
(
|
|
||||||
if builtins.isString cfg.settings then
|
|
||||||
cfg.settings
|
|
||||||
else
|
|
||||||
lib.optionalString (cfg.settings != { }) (
|
|
||||||
selflib.toMango {
|
|
||||||
topCommandsPrefixes = cfg.topPrefixes;
|
|
||||||
bottomCommandsPrefixes = cfg.bottomPrefixes;
|
|
||||||
} cfg.settings
|
|
||||||
)
|
|
||||||
)
|
|
||||||
+ lib.optionalString (cfg.extraConfig != "") cfg.extraConfig
|
|
||||||
+ lib.optionalString (cfg.autostart_sh != "") "\nexec-once=~/.config/mango/autostart.sh\n";
|
|
||||||
|
|
||||||
validatedConfig = pkgs.runCommand "mango-config.conf" { } ''
|
|
||||||
cp ${pkgs.writeText "mango-config.conf" finalConfigText} "$out"
|
|
||||||
${cfg.package}/bin/mango -c "$out" -p || exit 1
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
# Backwards compatibility warning for old string-based config
|
|
||||||
warnings = lib.optional (builtins.isString cfg.settings) ''
|
|
||||||
wayland.windowManager.mango.settings: Using a string for settings is deprecated.
|
|
||||||
Please migrate to the new structured attribute set format.
|
|
||||||
See the module documentation for examples, or use the 'extraConfig' option for raw config strings.
|
|
||||||
The old string format will be removed in a future release.
|
|
||||||
'';
|
|
||||||
|
|
||||||
home.packages = [cfg.package];
|
home.packages = [cfg.package];
|
||||||
xdg.configFile = {
|
xdg.configFile = {
|
||||||
"mango/config.conf" =
|
"mango/config.conf" = lib.mkIf (cfg.settings != "") {
|
||||||
lib.mkIf (cfg.settings != { } || cfg.extraConfig != "" || cfg.autostart_sh != "")
|
text = cfg.settings;
|
||||||
{
|
|
||||||
source = validatedConfig;
|
|
||||||
};
|
};
|
||||||
"mango/autostart.sh" = lib.mkIf (cfg.autostart_sh != "") {
|
"mango/autostart.sh" = lib.mkIf (cfg.autostart_sh != "") {
|
||||||
source = autostart_sh;
|
source = autostart_sh;
|
||||||
|
|
@ -260,7 +111,8 @@ in
|
||||||
Description = "mango compositor session";
|
Description = "mango compositor session";
|
||||||
Documentation = ["man:systemd.special(7)"];
|
Documentation = ["man:systemd.special(7)"];
|
||||||
BindsTo = ["graphical-session.target"];
|
BindsTo = ["graphical-session.target"];
|
||||||
Wants = [
|
Wants =
|
||||||
|
[
|
||||||
"graphical-session-pre.target"
|
"graphical-session-pre.target"
|
||||||
]
|
]
|
||||||
++ lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target";
|
++ lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target";
|
||||||
|
|
@ -268,6 +120,5 @@ in
|
||||||
Before = lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target";
|
Before = lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
312
nix/lib.nix
312
nix/lib.nix
|
|
@ -1,312 +0,0 @@
|
||||||
lib:
|
|
||||||
let
|
|
||||||
inherit (lib)
|
|
||||||
attrNames
|
|
||||||
filterAttrs
|
|
||||||
foldl
|
|
||||||
generators
|
|
||||||
partition
|
|
||||||
removeAttrs
|
|
||||||
;
|
|
||||||
|
|
||||||
inherit (lib.strings)
|
|
||||||
concatMapStrings
|
|
||||||
hasPrefix
|
|
||||||
;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Convert a structured Nix attribute set into Mango's configuration format.
|
|
||||||
|
|
||||||
This function takes a nested attribute set and converts it into Mango-compatible
|
|
||||||
configuration syntax, supporting top, bottom, and regular command sections.
|
|
||||||
|
|
||||||
Commands are flattened using the `flattenAttrs` function, and attributes are formatted as
|
|
||||||
`key = value` pairs. Lists are expanded as duplicate keys to match Mango's expected format.
|
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
* `topCommandsPrefixes` - A list of prefixes to define **top** commands (default: `[]`).
|
|
||||||
* `bottomCommandsPrefixes` - A list of prefixes to define **bottom** commands (default: `[]`).
|
|
||||||
|
|
||||||
Attention:
|
|
||||||
|
|
||||||
- The function ensures top commands appear **first** and bottom commands **last**.
|
|
||||||
- The generated configuration is a **single string**, suitable for writing to a config file.
|
|
||||||
- Lists are converted into multiple entries, ensuring compatibility with Mango.
|
|
||||||
|
|
||||||
# Inputs
|
|
||||||
|
|
||||||
Structured function argument:
|
|
||||||
|
|
||||||
: topCommandsPrefixes (optional, default: `[]`)
|
|
||||||
: A list of prefixes that define **top** commands. Any key starting with one of these
|
|
||||||
prefixes will be placed at the beginning of the configuration.
|
|
||||||
: bottomCommandsPrefixes (optional, default: `[]`)
|
|
||||||
: A list of prefixes that define **bottom** commands. Any key starting with one of these
|
|
||||||
prefixes will be placed at the end of the configuration.
|
|
||||||
|
|
||||||
Value:
|
|
||||||
|
|
||||||
: The attribute set to be converted to Hyprland configuration format.
|
|
||||||
|
|
||||||
# Type
|
|
||||||
|
|
||||||
```
|
|
||||||
toMango :: AttrSet -> AttrSet -> String
|
|
||||||
```
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
:::{.example}
|
|
||||||
|
|
||||||
## Basic mangowc configuration
|
|
||||||
|
|
||||||
```nix
|
|
||||||
let
|
|
||||||
config = {
|
|
||||||
blur = 1;
|
|
||||||
blur_params_radius = 5;
|
|
||||||
border_radius = 6;
|
|
||||||
animations = 1;
|
|
||||||
animation_duration_open = 400;
|
|
||||||
};
|
|
||||||
in lib.toMango {} config
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output:**
|
|
||||||
```
|
|
||||||
animations = 1
|
|
||||||
animation_duration_open = 400
|
|
||||||
blur = 1
|
|
||||||
blur_params_radius = 5
|
|
||||||
border_radius = 6
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using nested attributes
|
|
||||||
|
|
||||||
```nix
|
|
||||||
let
|
|
||||||
config = {
|
|
||||||
blur = 1;
|
|
||||||
blur_params = {
|
|
||||||
radius = 5;
|
|
||||||
num_passes = 2;
|
|
||||||
noise = 0.02;
|
|
||||||
};
|
|
||||||
animation_curve = {
|
|
||||||
open = "0.46,1.0,0.29,1";
|
|
||||||
close = "0.08,0.92,0,1";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in lib.toMango {} config
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output:**
|
|
||||||
```
|
|
||||||
animation_curve_close = 0.08,0.92,0,1
|
|
||||||
animation_curve_open = 0.46,1.0,0.29,1
|
|
||||||
blur = 1
|
|
||||||
blur_params_noise = 0.02
|
|
||||||
blur_params_num_passes = 2
|
|
||||||
blur_params_radius = 5
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using lists for duplicate keys
|
|
||||||
|
|
||||||
```nix
|
|
||||||
let
|
|
||||||
config = {
|
|
||||||
bind = [
|
|
||||||
"SUPER,r,reload_config"
|
|
||||||
"Alt,space,spawn,rofi -show drun"
|
|
||||||
"Alt,Return,spawn,foot"
|
|
||||||
];
|
|
||||||
tagrule = [
|
|
||||||
"id:1,layout_name:tile"
|
|
||||||
"id:2,layout_name:scroller"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in lib.toMango {} config
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output:**
|
|
||||||
```
|
|
||||||
bind = SUPER,r,reload_config
|
|
||||||
bind = Alt,space,spawn,rofi -show drun
|
|
||||||
bind = Alt,Return,spawn,foot
|
|
||||||
tagrule = id:1,layout_name:tile
|
|
||||||
tagrule = id:2,layout_name:scroller
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using keymodes (submaps)
|
|
||||||
|
|
||||||
```nix
|
|
||||||
let
|
|
||||||
config = {
|
|
||||||
bind = [
|
|
||||||
"SUPER,Q,killclient"
|
|
||||||
"ALT,R,setkeymode,resize"
|
|
||||||
];
|
|
||||||
keymode = {
|
|
||||||
resize = {
|
|
||||||
bind = [
|
|
||||||
"NONE,Left,resizewin,-10,0"
|
|
||||||
"NONE,Right,resizewin,10,0"
|
|
||||||
"NONE,Escape,setkeymode,default"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in lib.toMango {} config
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output:**
|
|
||||||
```
|
|
||||||
bind = SUPER,Q,killclient
|
|
||||||
bind = ALT,R,setkeymode,resize
|
|
||||||
|
|
||||||
keymode = resize
|
|
||||||
bind = NONE,Left,resizewin,-10,0
|
|
||||||
bind = NONE,Right,resizewin,10,0
|
|
||||||
bind = NONE,Escape,setkeymode,default
|
|
||||||
```
|
|
||||||
|
|
||||||
:::
|
|
||||||
*/
|
|
||||||
toMango =
|
|
||||||
{
|
|
||||||
topCommandsPrefixes ? [ ],
|
|
||||||
bottomCommandsPrefixes ? [ ],
|
|
||||||
}:
|
|
||||||
attrs:
|
|
||||||
let
|
|
||||||
toMango' =
|
|
||||||
attrs:
|
|
||||||
let
|
|
||||||
# Specially configured `toKeyValue` generator with support for duplicate keys
|
|
||||||
# and a legible key-value separator.
|
|
||||||
mkCommands = generators.toKeyValue {
|
|
||||||
mkKeyValue = generators.mkKeyValueDefault { } " = ";
|
|
||||||
listsAsDuplicateKeys = true;
|
|
||||||
indent = ""; # No indent, since we don't have nesting
|
|
||||||
};
|
|
||||||
|
|
||||||
# Extract keymode definitions if they exist
|
|
||||||
keymodes = attrs.keymode or { };
|
|
||||||
attrsWithoutKeymodes = removeAttrs attrs [ "keymode" ];
|
|
||||||
|
|
||||||
# Generate keymode blocks
|
|
||||||
# Format: keymode=name\nbind=...\nbind=...\n
|
|
||||||
mkKeymodeBlock =
|
|
||||||
name: modeAttrs:
|
|
||||||
let
|
|
||||||
modeCommands = flattenAttrs (p: k: "${p}_${k}") modeAttrs;
|
|
||||||
in
|
|
||||||
"keymode = ${name}\n${mkCommands modeCommands}";
|
|
||||||
|
|
||||||
keymodeBlocks =
|
|
||||||
if keymodes == { } then
|
|
||||||
""
|
|
||||||
else
|
|
||||||
"\n" + concatMapStrings (name: mkKeymodeBlock name keymodes.${name} + "\n") (attrNames keymodes);
|
|
||||||
|
|
||||||
# Flatten the attrset, combining keys in a "path" like `"a_b_c" = "x"`.
|
|
||||||
# Uses `flattenAttrs` with an underscore separator.
|
|
||||||
commands = flattenAttrs (p: k: "${p}_${k}") attrsWithoutKeymodes;
|
|
||||||
|
|
||||||
# General filtering function to check if a key starts with any prefix in a given list.
|
|
||||||
filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list;
|
|
||||||
|
|
||||||
# Partition keys into top commands and the rest
|
|
||||||
result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
|
|
||||||
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
|
|
||||||
remainingCommands = removeAttrs commands result.right;
|
|
||||||
|
|
||||||
# Partition remaining commands into bottom commands and regular commands
|
|
||||||
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
|
|
||||||
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
|
|
||||||
regularCommands = removeAttrs remainingCommands result2.right;
|
|
||||||
in
|
|
||||||
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
|
|
||||||
# Keymodes are appended at the end.
|
|
||||||
concatMapStrings mkCommands [
|
|
||||||
topCommands
|
|
||||||
regularCommands
|
|
||||||
bottomCommands
|
|
||||||
]
|
|
||||||
+ keymodeBlocks;
|
|
||||||
in
|
|
||||||
toMango' attrs;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Flatten a nested attribute set into a flat attribute set, using a custom key separator function.
|
|
||||||
|
|
||||||
This function recursively traverses a nested attribute set and produces a flat attribute set
|
|
||||||
where keys are joined using a user-defined function (`pred`). It allows transforming deeply
|
|
||||||
nested structures into a single-level attribute set while preserving key-value relationships.
|
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
* `pred` - A function `(string -> string -> string)` defining how keys should be concatenated.
|
|
||||||
|
|
||||||
# Inputs
|
|
||||||
|
|
||||||
Structured function argument:
|
|
||||||
|
|
||||||
: pred (required)
|
|
||||||
: A function that determines how parent and child keys should be combined into a single key.
|
|
||||||
It takes a `prefix` (parent key) and `key` (current key) and returns the joined key.
|
|
||||||
|
|
||||||
Value:
|
|
||||||
|
|
||||||
: The nested attribute set to be flattened.
|
|
||||||
|
|
||||||
# Type
|
|
||||||
|
|
||||||
```
|
|
||||||
flattenAttrs :: (String -> String -> String) -> AttrSet -> AttrSet
|
|
||||||
```
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
:::{.example}
|
|
||||||
|
|
||||||
```nix
|
|
||||||
let
|
|
||||||
nested = {
|
|
||||||
a = "3";
|
|
||||||
b = { c = "4"; d = "5"; };
|
|
||||||
};
|
|
||||||
|
|
||||||
separator = (prefix: key: "${prefix}.${key}"); # Use dot notation
|
|
||||||
in lib.flattenAttrs separator nested
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output:**
|
|
||||||
```nix
|
|
||||||
{
|
|
||||||
"a" = "3";
|
|
||||||
"b.c" = "4";
|
|
||||||
"b.d" = "5";
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
:::
|
|
||||||
*/
|
|
||||||
flattenAttrs =
|
|
||||||
pred: attrs:
|
|
||||||
let
|
|
||||||
flattenAttrs' =
|
|
||||||
prefix: attrs:
|
|
||||||
builtins.foldl' (
|
|
||||||
acc: key:
|
|
||||||
let
|
|
||||||
value = attrs.${key};
|
|
||||||
newKey = if prefix == "" then key else pred prefix key;
|
|
||||||
in
|
|
||||||
acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; })
|
|
||||||
) { } (builtins.attrNames attrs);
|
|
||||||
in
|
|
||||||
flattenAttrs' "" attrs;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit flattenAttrs toMango;
|
|
||||||
}
|
|
||||||
|
|
@ -552,7 +552,7 @@ int32_t restore_minimized(const Arg *arg) {
|
||||||
|
|
||||||
if (selmon && selmon->sel && selmon->sel->is_in_scratchpad &&
|
if (selmon && selmon->sel && selmon->sel->is_in_scratchpad &&
|
||||||
selmon->sel->is_scratchpad_show) {
|
selmon->sel->is_scratchpad_show) {
|
||||||
client_pending_minimized_state(selmon->sel, 0);
|
selmon->sel->isminimized = 0;
|
||||||
selmon->sel->is_scratchpad_show = 0;
|
selmon->sel->is_scratchpad_show = 0;
|
||||||
selmon->sel->is_in_scratchpad = 0;
|
selmon->sel->is_in_scratchpad = 0;
|
||||||
selmon->sel->isnamedscratchpad = 0;
|
selmon->sel->isnamedscratchpad = 0;
|
||||||
|
|
@ -863,6 +863,7 @@ int32_t spawn_shell(const Arg *arg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t spawn(const Arg *arg) {
|
int32_t spawn(const Arg *arg) {
|
||||||
|
|
||||||
if (!arg->v)
|
if (!arg->v)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
@ -875,21 +876,28 @@ int32_t spawn(const Arg *arg) {
|
||||||
dup2(STDERR_FILENO, STDOUT_FILENO);
|
dup2(STDERR_FILENO, STDOUT_FILENO);
|
||||||
setsid();
|
setsid();
|
||||||
|
|
||||||
// 2. 对整个参数字符串进行单词展开
|
// 2. 解析参数
|
||||||
|
char *argv[64];
|
||||||
|
int32_t argc = 0;
|
||||||
|
char *token = strtok((char *)arg->v, " ");
|
||||||
|
while (token != NULL && argc < 63) {
|
||||||
wordexp_t p;
|
wordexp_t p;
|
||||||
if (wordexp(arg->v, &p, 0) != 0) {
|
if (wordexp(token, &p, 0) == 0) {
|
||||||
wlr_log(WLR_DEBUG, "mango: wordexp failed for '%s'\n", arg->v);
|
argv[argc++] = p.we_wordv[0];
|
||||||
_exit(EXIT_FAILURE);
|
} else {
|
||||||
|
argv[argc++] = token;
|
||||||
}
|
}
|
||||||
|
token = strtok(NULL, " ");
|
||||||
|
}
|
||||||
|
argv[argc] = NULL;
|
||||||
|
|
||||||
// 3. 执行命令(p.we_wordv 已经是 argv 数组)
|
// 3. 执行命令
|
||||||
execvp(p.we_wordv[0], p.we_wordv);
|
execvp(argv[0], argv);
|
||||||
|
|
||||||
// 4. execvp 失败时:打印错误,释放 wordexp 资源,然后退出
|
// 4. execvp 失败时:打印错误并直接退出(避免 coredump)
|
||||||
wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", p.we_wordv[0],
|
wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", argv[0],
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
wordfree(&p); // 释放 wordexp 分配的内存
|
_exit(EXIT_FAILURE); // 使用 _exit 避免缓冲区刷新等操作
|
||||||
_exit(EXIT_FAILURE);
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
77
src/mango.c
77
src/mango.c
|
|
@ -806,10 +806,6 @@ static int32_t keep_idle_inhibit(void *data);
|
||||||
static void check_keep_idle_inhibit(Client *c);
|
static void check_keep_idle_inhibit(Client *c);
|
||||||
static void pre_caculate_before_arrange(Monitor *m, bool want_animation,
|
static void pre_caculate_before_arrange(Monitor *m, bool want_animation,
|
||||||
bool from_view, bool only_caculate);
|
bool from_view, bool only_caculate);
|
||||||
static void client_pending_fullscreen_state(Client *c, int32_t isfullscreen);
|
|
||||||
static void client_pending_maximized_state(Client *c, int32_t ismaximized);
|
|
||||||
static void client_pending_minimized_state(Client *c, int32_t isminimized);
|
|
||||||
|
|
||||||
#include "data/static_keymap.h"
|
#include "data/static_keymap.h"
|
||||||
#include "dispatch/bind_declare.h"
|
#include "dispatch/bind_declare.h"
|
||||||
#include "layout/layout.h"
|
#include "layout/layout.h"
|
||||||
|
|
@ -1067,33 +1063,11 @@ void clear_fullscreen_flag(Client *c) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void client_pending_fullscreen_state(Client *c, int32_t isfullscreen) {
|
|
||||||
c->isfullscreen = isfullscreen;
|
|
||||||
|
|
||||||
if (c->foreign_toplevel && !c->iskilling)
|
|
||||||
wlr_foreign_toplevel_handle_v1_set_fullscreen(c->foreign_toplevel,
|
|
||||||
isfullscreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_pending_maximized_state(Client *c, int32_t ismaximized) {
|
|
||||||
c->ismaximizescreen = ismaximized;
|
|
||||||
if (c->foreign_toplevel && !c->iskilling)
|
|
||||||
wlr_foreign_toplevel_handle_v1_set_maximized(c->foreign_toplevel,
|
|
||||||
ismaximized);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_pending_minimized_state(Client *c, int32_t isminimized) {
|
|
||||||
c->isminimized = isminimized;
|
|
||||||
if (c->foreign_toplevel && !c->iskilling)
|
|
||||||
wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel,
|
|
||||||
isminimized);
|
|
||||||
}
|
|
||||||
|
|
||||||
void show_scratchpad(Client *c) {
|
void show_scratchpad(Client *c) {
|
||||||
c->is_scratchpad_show = 1;
|
c->is_scratchpad_show = 1;
|
||||||
if (c->isfullscreen || c->ismaximizescreen) {
|
if (c->isfullscreen || c->ismaximizescreen) {
|
||||||
client_pending_fullscreen_state(c, 0);
|
c->isfullscreen = 0; // 清除窗口全屏标志
|
||||||
client_pending_maximized_state(c, 0);
|
c->ismaximizescreen = 0;
|
||||||
c->bw = c->isnoborder ? 0 : config.borderpx;
|
c->bw = c->isnoborder ? 0 : config.borderpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1132,6 +1106,9 @@ void swallow(Client *c, Client *w) {
|
||||||
c->bw = w->bw;
|
c->bw = w->bw;
|
||||||
c->isfloating = w->isfloating;
|
c->isfloating = w->isfloating;
|
||||||
c->isurgent = w->isurgent;
|
c->isurgent = w->isurgent;
|
||||||
|
c->isfullscreen = w->isfullscreen;
|
||||||
|
c->ismaximizescreen = w->ismaximizescreen;
|
||||||
|
c->isminimized = w->isminimized;
|
||||||
c->is_in_scratchpad = w->is_in_scratchpad;
|
c->is_in_scratchpad = w->is_in_scratchpad;
|
||||||
c->is_scratchpad_show = w->is_scratchpad_show;
|
c->is_scratchpad_show = w->is_scratchpad_show;
|
||||||
c->tags = w->tags;
|
c->tags = w->tags;
|
||||||
|
|
@ -1143,7 +1120,6 @@ void swallow(Client *c, Client *w) {
|
||||||
c->scroller_proportion = w->scroller_proportion;
|
c->scroller_proportion = w->scroller_proportion;
|
||||||
c->next_in_stack = w->next_in_stack;
|
c->next_in_stack = w->next_in_stack;
|
||||||
c->prev_in_stack = w->prev_in_stack;
|
c->prev_in_stack = w->prev_in_stack;
|
||||||
|
|
||||||
if (w->next_in_stack)
|
if (w->next_in_stack)
|
||||||
w->next_in_stack->prev_in_stack = c;
|
w->next_in_stack->prev_in_stack = c;
|
||||||
if (w->prev_in_stack)
|
if (w->prev_in_stack)
|
||||||
|
|
@ -1162,9 +1138,11 @@ void swallow(Client *c, Client *w) {
|
||||||
if (!c->foreign_toplevel && c->mon)
|
if (!c->foreign_toplevel && c->mon)
|
||||||
add_foreign_toplevel(c);
|
add_foreign_toplevel(c);
|
||||||
|
|
||||||
client_pending_fullscreen_state(c, w->isfullscreen);
|
if (c->isminimized && c->foreign_toplevel) {
|
||||||
client_pending_maximized_state(c, w->ismaximizescreen);
|
wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel,
|
||||||
client_pending_minimized_state(c, w->isminimized);
|
false);
|
||||||
|
wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool switch_scratchpad_client_state(Client *c) {
|
bool switch_scratchpad_client_state(Client *c) {
|
||||||
|
|
@ -4253,7 +4231,7 @@ void maximizenotify(struct wl_listener *listener, void *data) {
|
||||||
|
|
||||||
void unminimize(Client *c) {
|
void unminimize(Client *c) {
|
||||||
if (c && c->is_in_scratchpad && c->is_scratchpad_show) {
|
if (c && c->is_in_scratchpad && c->is_scratchpad_show) {
|
||||||
client_pending_minimized_state(c, 0);
|
c->isminimized = 0;
|
||||||
c->is_scratchpad_show = 0;
|
c->is_scratchpad_show = 0;
|
||||||
c->is_in_scratchpad = 0;
|
c->is_in_scratchpad = 0;
|
||||||
c->isnamedscratchpad = 0;
|
c->isnamedscratchpad = 0;
|
||||||
|
|
@ -4281,12 +4259,13 @@ void set_minimized(Client *c) {
|
||||||
c->oldtags = c->mon->tagset[c->mon->seltags];
|
c->oldtags = c->mon->tagset[c->mon->seltags];
|
||||||
c->mini_restore_tag = c->tags;
|
c->mini_restore_tag = c->tags;
|
||||||
c->tags = 0;
|
c->tags = 0;
|
||||||
client_pending_minimized_state(c, 1);
|
c->isminimized = 1;
|
||||||
c->is_in_scratchpad = 1;
|
c->is_in_scratchpad = 1;
|
||||||
c->is_scratchpad_show = 0;
|
c->is_scratchpad_show = 0;
|
||||||
focusclient(focustop(selmon), 1);
|
focusclient(focustop(selmon), 1);
|
||||||
arrange(c->mon, false, false);
|
arrange(c->mon, false, false);
|
||||||
wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, false);
|
wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, false);
|
||||||
|
wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, true);
|
||||||
wl_list_remove(&c->link); // 从原来位置移除
|
wl_list_remove(&c->link); // 从原来位置移除
|
||||||
wl_list_insert(clients.prev, &c->link); // 插入尾部
|
wl_list_insert(clients.prev, &c->link); // 插入尾部
|
||||||
}
|
}
|
||||||
|
|
@ -5063,11 +5042,11 @@ setfloating(Client *c, int32_t floating) {
|
||||||
if (floating == 1 && c != grabc) {
|
if (floating == 1 && c != grabc) {
|
||||||
|
|
||||||
if (c->isfullscreen) {
|
if (c->isfullscreen) {
|
||||||
client_pending_fullscreen_state(c, 0);
|
c->isfullscreen = 0;
|
||||||
client_set_fullscreen(c, 0);
|
client_set_fullscreen(c, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
client_pending_maximized_state(c, 0);
|
c->ismaximizescreen = 0;
|
||||||
exit_scroller_stack(c);
|
exit_scroller_stack(c);
|
||||||
|
|
||||||
// 重新计算居中的坐标
|
// 重新计算居中的坐标
|
||||||
|
|
@ -5192,12 +5171,12 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int32_t old_maximizescreen_state = c->ismaximizescreen;
|
int32_t old_maximizescreen_state = c->ismaximizescreen;
|
||||||
client_pending_maximized_state(c, maximizescreen);
|
c->ismaximizescreen = maximizescreen;
|
||||||
|
|
||||||
if (maximizescreen) {
|
if (maximizescreen) {
|
||||||
|
|
||||||
if (c->isfullscreen) {
|
if (c->isfullscreen) {
|
||||||
client_pending_fullscreen_state(c, 0);
|
c->isfullscreen = 0;
|
||||||
client_set_fullscreen(c, 0);
|
client_set_fullscreen(c, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5210,8 +5189,10 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) {
|
||||||
wlr_scene_node_raise_to_top(&c->scene->node);
|
wlr_scene_node_raise_to_top(&c->scene->node);
|
||||||
if (!is_scroller_layout(c->mon) || c->isfloating)
|
if (!is_scroller_layout(c->mon) || c->isfloating)
|
||||||
resize(c, maximizescreen_box, 0);
|
resize(c, maximizescreen_box, 0);
|
||||||
|
c->ismaximizescreen = 1;
|
||||||
} else {
|
} else {
|
||||||
c->bw = c->isnoborder ? 0 : config.borderpx;
|
c->bw = c->isnoborder ? 0 : config.borderpx;
|
||||||
|
c->ismaximizescreen = 0;
|
||||||
if (c->isfloating)
|
if (c->isfloating)
|
||||||
setfloating(c, 1);
|
setfloating(c, 1);
|
||||||
}
|
}
|
||||||
|
|
@ -5259,7 +5240,6 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自
|
||||||
c->isfullscreen = fullscreen;
|
c->isfullscreen = fullscreen;
|
||||||
|
|
||||||
client_set_fullscreen(c, fullscreen);
|
client_set_fullscreen(c, fullscreen);
|
||||||
client_pending_fullscreen_state(c, fullscreen);
|
|
||||||
|
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
|
|
||||||
|
|
@ -5267,7 +5247,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自
|
||||||
client_set_maximized(c, false);
|
client_set_maximized(c, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
client_pending_maximized_state(c, 0);
|
c->ismaximizescreen = 0;
|
||||||
|
|
||||||
exit_scroller_stack(c);
|
exit_scroller_stack(c);
|
||||||
c->isfakefullscreen = 0;
|
c->isfakefullscreen = 0;
|
||||||
|
|
@ -5276,8 +5256,10 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自
|
||||||
wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层
|
wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层
|
||||||
if (!is_scroller_layout(c->mon) || c->isfloating)
|
if (!is_scroller_layout(c->mon) || c->isfloating)
|
||||||
resize(c, c->mon->m, 1);
|
resize(c, c->mon->m, 1);
|
||||||
|
c->isfullscreen = 1;
|
||||||
} else {
|
} else {
|
||||||
c->bw = c->isnoborder ? 0 : config.borderpx;
|
c->bw = c->isnoborder ? 0 : config.borderpx;
|
||||||
|
c->isfullscreen = 0;
|
||||||
if (c->isfloating)
|
if (c->isfloating)
|
||||||
setfloating(c, 1);
|
setfloating(c, 1);
|
||||||
}
|
}
|
||||||
|
|
@ -5469,7 +5451,8 @@ void show_hide_client(Client *c) {
|
||||||
c->tags = c->oldtags;
|
c->tags = c->oldtags;
|
||||||
arrange(c->mon, false, false);
|
arrange(c->mon, false, false);
|
||||||
}
|
}
|
||||||
client_pending_minimized_state(c, 0);
|
c->isminimized = 0;
|
||||||
|
wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, false);
|
||||||
focusclient(c, 1);
|
focusclient(c, 1);
|
||||||
wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, true);
|
wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, true);
|
||||||
}
|
}
|
||||||
|
|
@ -5893,8 +5876,8 @@ void overview_backup(Client *c) {
|
||||||
c->isfloating = 0;
|
c->isfloating = 0;
|
||||||
}
|
}
|
||||||
if (c->isfullscreen || c->ismaximizescreen) {
|
if (c->isfullscreen || c->ismaximizescreen) {
|
||||||
client_pending_fullscreen_state(c, 0); // 清除窗口全屏标志
|
c->isfullscreen = 0; // 清除窗口全屏标志
|
||||||
client_pending_maximized_state(c, 0);
|
c->ismaximizescreen = 0;
|
||||||
}
|
}
|
||||||
c->bw = c->isnoborder ? 0 : config.borderpx;
|
c->bw = c->isnoborder ? 0 : config.borderpx;
|
||||||
|
|
||||||
|
|
@ -5924,8 +5907,8 @@ void overview_restore(Client *c, const Arg *arg) {
|
||||||
} else if (want_restore_fullscreen(c) && c->isfullscreen) {
|
} else if (want_restore_fullscreen(c) && c->isfullscreen) {
|
||||||
setfullscreen(c, 1);
|
setfullscreen(c, 1);
|
||||||
} else {
|
} else {
|
||||||
client_pending_fullscreen_state(c, 0);
|
c->isfullscreen = 0;
|
||||||
client_pending_maximized_state(c, 0);
|
c->ismaximizescreen = 0;
|
||||||
setfullscreen(c, false);
|
setfullscreen(c, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -6498,11 +6481,13 @@ void activatex11(struct wl_listener *listener, void *data) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (c->isminimized) {
|
if (c->isminimized) {
|
||||||
client_pending_minimized_state(c, 0);
|
c->isminimized = 0;
|
||||||
c->tags = c->mini_restore_tag;
|
c->tags = c->mini_restore_tag;
|
||||||
c->is_scratchpad_show = 0;
|
c->is_scratchpad_show = 0;
|
||||||
c->is_in_scratchpad = 0;
|
c->is_in_scratchpad = 0;
|
||||||
c->isnamedscratchpad = 0;
|
c->isnamedscratchpad = 0;
|
||||||
|
wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel,
|
||||||
|
false);
|
||||||
setborder_color(c);
|
setborder_color(c);
|
||||||
if (VISIBLEON(c, c->mon)) {
|
if (VISIBLEON(c, c->mon)) {
|
||||||
need_arrange = true;
|
need_arrange = true;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue