mirror of
https://github.com/DreamMaoMao/maomaowm.git
synced 2026-07-06 00:06:43 -04:00
Merge branch 'main' into feat/readiness-notification
This commit is contained in:
commit
31136fdb66
11 changed files with 600 additions and 199 deletions
|
|
@ -95,6 +95,12 @@ Finally, install the package:
|
||||||
emerge --ask --verbose gui-wm/mangowm
|
emerge --ask --verbose gui-wm/mangowm
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## openSUSE
|
||||||
|
The package is in the community-maintained repository called obs.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo opi in mangowm
|
||||||
|
```
|
||||||
## Fedora Linux
|
## Fedora Linux
|
||||||
The package is in the third-party Terra repository.
|
The package is in the third-party Terra repository.
|
||||||
First, add the [Terra Repository](https://terra.fyralabs.com/).
|
First, add the [Terra Repository](https://terra.fyralabs.com/).
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,10 @@ blur_optimized=0
|
||||||
|
|
||||||
### My games are lagging or stuttering
|
### My games are lagging or stuttering
|
||||||
|
|
||||||
Try enabling **SyncObj** timeline support and **Adaptive Sync** (VRR) if your monitor supports it.
|
Try enabling **SyncObj** timeline support.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
syncobj_enable=1
|
syncobj_enable=1
|
||||||
adaptive_sync=1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ To get a fully functional desktop experience, we recommend installing the follow
|
||||||
| Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty |
|
| Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty |
|
||||||
| Status Bar | waybar, eww, quickshell, ags |
|
| Status Bar | waybar, eww, quickshell, ags |
|
||||||
| Desktop Shell | Noctalia, DankMaterialShell |
|
| Desktop Shell | Noctalia, DankMaterialShell |
|
||||||
| Wallpaper Setup | swww, swaybg |
|
| Wallpaper Setup | awww(swww), swaybg |
|
||||||
| Notification Daemon | swaync, dunst, mako |
|
| Notification Daemon | swaync, dunst, mako |
|
||||||
| Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk |
|
| Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk |
|
||||||
| Clipboard | wl-clipboard, wl-clip-persist, cliphist |
|
| Clipboard | wl-clipboard, wl-clip-persist, cliphist |
|
||||||
|
|
|
||||||
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))
|
#:use-module ((guix licenses) #:prefix license:))
|
||||||
|
|
||||||
|
|
||||||
(define-public mangowm-git
|
(define-public mangowm-git
|
||||||
|
|
@ -36,10 +36,13 @@
|
||||||
(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
|
||||||
|
|
@ -52,14 +55,17 @@
|
||||||
pcre2
|
pcre2
|
||||||
libxcb
|
libxcb
|
||||||
xcb-util-wm
|
xcb-util-wm
|
||||||
wlroots
|
wlroots-0.19
|
||||||
scenefx))
|
scenefx))
|
||||||
(native-inputs (list pkg-config wayland-protocols))
|
(native-inputs (list pkg-config wayland-protocols))
|
||||||
(home-page "https://github.com/DreamMaoMao/mangowm")
|
(home-page "https://github.com/mangowm/mango")
|
||||||
(synopsis "Wayland compositor based on wlroots and scenefx")
|
(synopsis "Wayland compositor based on wlroots and scenefx")
|
||||||
(description "A Wayland compositor based on wlroots and scenefx,
|
(description
|
||||||
inspired by dwl but aiming to be more feature-rich.")
|
"MangoWM is a modern, lightweight, high-performance Wayland compositor
|
||||||
(license gpl3)))
|
built on dwl — crafted for speed, flexibility, and a customizable desktop experience.")
|
||||||
|
(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,5 +1,5 @@
|
||||||
project('mango', ['c', 'cpp'],
|
project('mango', ['c', 'cpp'],
|
||||||
version : '0.12.8',
|
version : '0.12.9',
|
||||||
)
|
)
|
||||||
|
|
||||||
subdir('protocols')
|
subdir('protocols')
|
||||||
|
|
|
||||||
143
mmsg/mmsg.c
143
mmsg/mmsg.c
|
|
@ -603,120 +603,49 @@ int32_t main(int32_t argc, char *argv[]) {
|
||||||
mode = SET;
|
mode = SET;
|
||||||
char *arg = EARGF(usage());
|
char *arg = EARGF(usage());
|
||||||
|
|
||||||
// Trim leading and trailing whitespace from entire argument first
|
dispatch_cmd = dispatch_arg1 = dispatch_arg2 = dispatch_arg3 =
|
||||||
while (isspace(*arg))
|
dispatch_arg4 = dispatch_arg5 = "";
|
||||||
arg++;
|
|
||||||
char *end = arg + strlen(arg) - 1;
|
|
||||||
while (end > arg && isspace(*end))
|
|
||||||
end--;
|
|
||||||
*(end + 1) = '\0';
|
|
||||||
|
|
||||||
dispatch_cmd = arg;
|
char *tokens[6] = {0};
|
||||||
char *comma1 = strchr(arg, ',');
|
int count = 0;
|
||||||
if (comma1) {
|
|
||||||
*comma1 = '\0';
|
|
||||||
|
|
||||||
// Trim trailing whitespace from command
|
while (arg && count < 6) {
|
||||||
end = dispatch_cmd + strlen(dispatch_cmd) - 1;
|
char *comma = (count < 5) ? strchr(arg, ',') : NULL;
|
||||||
while (end > dispatch_cmd && isspace(*end))
|
if (comma) {
|
||||||
end--;
|
*comma = '\0';
|
||||||
*(end + 1) = '\0';
|
tokens[count++] = arg;
|
||||||
|
arg = comma + 1;
|
||||||
|
} else {
|
||||||
|
tokens[count++] = arg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dispatch_arg1 = comma1 + 1;
|
for (int i = 0; i < count; i++) {
|
||||||
// Trim leading whitespace from arg1
|
char *str = tokens[i];
|
||||||
while (isspace(*dispatch_arg1))
|
while (isspace((unsigned char)*str))
|
||||||
dispatch_arg1++;
|
str++;
|
||||||
|
if (*str) {
|
||||||
// Trim trailing whitespace from arg1 before looking for next
|
char *end = str + strlen(str) - 1;
|
||||||
// comma
|
while (end > str && isspace((unsigned char)*end))
|
||||||
end = dispatch_arg1 + strlen(dispatch_arg1) - 1;
|
|
||||||
while (end > dispatch_arg1 && isspace(*end))
|
|
||||||
end--;
|
|
||||||
*(end + 1) = '\0';
|
|
||||||
|
|
||||||
char *comma2 = strchr(dispatch_arg1, ',');
|
|
||||||
if (comma2) {
|
|
||||||
*comma2 = '\0';
|
|
||||||
dispatch_arg2 = comma2 + 1;
|
|
||||||
// Trim leading whitespace from arg2
|
|
||||||
while (isspace(*dispatch_arg2))
|
|
||||||
dispatch_arg2++;
|
|
||||||
|
|
||||||
// Trim trailing whitespace from arg2 before looking for
|
|
||||||
// next comma
|
|
||||||
end = dispatch_arg2 + strlen(dispatch_arg2) - 1;
|
|
||||||
while (end > dispatch_arg2 && isspace(*end))
|
|
||||||
end--;
|
end--;
|
||||||
*(end + 1) = '\0';
|
*(end + 1) = '\0';
|
||||||
|
|
||||||
char *comma3 = strchr(dispatch_arg2, ',');
|
|
||||||
if (comma3) {
|
|
||||||
*comma3 = '\0';
|
|
||||||
dispatch_arg3 = comma3 + 1;
|
|
||||||
// Trim leading whitespace from arg3
|
|
||||||
while (isspace(*dispatch_arg3))
|
|
||||||
dispatch_arg3++;
|
|
||||||
|
|
||||||
// Trim trailing whitespace from arg3 before looking for
|
|
||||||
// next comma
|
|
||||||
end = dispatch_arg3 + strlen(dispatch_arg3) - 1;
|
|
||||||
while (end > dispatch_arg3 && isspace(*end))
|
|
||||||
end--;
|
|
||||||
*(end + 1) = '\0';
|
|
||||||
|
|
||||||
char *comma4 = strchr(dispatch_arg3, ',');
|
|
||||||
if (comma4) {
|
|
||||||
*comma4 = '\0';
|
|
||||||
dispatch_arg4 = comma4 + 1;
|
|
||||||
// Trim leading whitespace from arg4
|
|
||||||
while (isspace(*dispatch_arg4))
|
|
||||||
dispatch_arg4++;
|
|
||||||
|
|
||||||
// Trim trailing whitespace from arg4 before looking
|
|
||||||
// for next comma
|
|
||||||
end = dispatch_arg4 + strlen(dispatch_arg4) - 1;
|
|
||||||
while (end > dispatch_arg4 && isspace(*end))
|
|
||||||
end--;
|
|
||||||
*(end + 1) = '\0';
|
|
||||||
|
|
||||||
char *comma5 = strchr(dispatch_arg4, ',');
|
|
||||||
if (comma5) {
|
|
||||||
*comma5 = '\0';
|
|
||||||
dispatch_arg5 = comma5 + 1;
|
|
||||||
// Trim leading whitespace from arg5
|
|
||||||
while (isspace(*dispatch_arg5))
|
|
||||||
dispatch_arg5++;
|
|
||||||
|
|
||||||
// Trim trailing whitespace from arg5
|
|
||||||
end = dispatch_arg5 + strlen(dispatch_arg5) - 1;
|
|
||||||
while (end > dispatch_arg5 && isspace(*end))
|
|
||||||
end--;
|
|
||||||
*(end + 1) = '\0';
|
|
||||||
} else {
|
|
||||||
dispatch_arg5 = "";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dispatch_arg4 = "";
|
|
||||||
dispatch_arg5 = "";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dispatch_arg3 = "";
|
|
||||||
dispatch_arg4 = "";
|
|
||||||
dispatch_arg5 = "";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dispatch_arg2 = "";
|
|
||||||
dispatch_arg3 = "";
|
|
||||||
dispatch_arg4 = "";
|
|
||||||
dispatch_arg5 = "";
|
|
||||||
}
|
}
|
||||||
} else {
|
tokens[i] = str;
|
||||||
dispatch_arg1 = "";
|
|
||||||
dispatch_arg2 = "";
|
|
||||||
dispatch_arg3 = "";
|
|
||||||
dispatch_arg4 = "";
|
|
||||||
dispatch_arg5 = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (count > 0)
|
||||||
|
dispatch_cmd = tokens[0];
|
||||||
|
if (count > 1)
|
||||||
|
dispatch_arg1 = tokens[1];
|
||||||
|
if (count > 2)
|
||||||
|
dispatch_arg2 = tokens[2];
|
||||||
|
if (count > 3)
|
||||||
|
dispatch_arg3 = tokens[3];
|
||||||
|
if (count > 4)
|
||||||
|
dispatch_arg4 = tokens[4];
|
||||||
|
if (count > 5)
|
||||||
|
dispatch_arg5 = tokens[5];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'O':
|
case 'O':
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
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 {
|
||||||
|
|
@ -54,7 +58,7 @@ in {
|
||||||
"XCURSOR_THEME"
|
"XCURSOR_THEME"
|
||||||
"XCURSOR_SIZE"
|
"XCURSOR_SIZE"
|
||||||
];
|
];
|
||||||
example = ["--all"];
|
example = [ "--all" ];
|
||||||
description = ''
|
description = ''
|
||||||
Environment variables imported into the systemd and D-Bus user environment.
|
Environment variables imported into the systemd and D-Bus user environment.
|
||||||
'';
|
'';
|
||||||
|
|
@ -75,50 +79,195 @@ in {
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
settings = mkOption {
|
settings = mkOption {
|
||||||
description = "mango config content";
|
type =
|
||||||
type = types.lines;
|
with lib.types;
|
||||||
default = "";
|
let
|
||||||
example = ''
|
valueType =
|
||||||
# menu and terminal
|
nullOr (oneOf [
|
||||||
bind=Alt,space,spawn,rofi -show drun
|
bool
|
||||||
bind=Alt,Return,spawn,foot
|
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;
|
||||||
|
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 = ''
|
||||||
|
# Advanced config that doesn't fit structured format
|
||||||
|
special_option = 1
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
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 = "WARRNING: This is a shell script, but no need to add shebang";
|
description = ''
|
||||||
|
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 (
|
||||||
home.packages = [cfg.package];
|
let
|
||||||
xdg.configFile = {
|
finalConfigText =
|
||||||
"mango/config.conf" = lib.mkIf (cfg.settings != "") {
|
# Support old string-based config during transition period
|
||||||
text = cfg.settings;
|
(
|
||||||
|
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 ];
|
||||||
|
xdg.configFile = {
|
||||||
|
"mango/config.conf" =
|
||||||
|
lib.mkIf (cfg.settings != { } || cfg.extraConfig != "" || cfg.autostart_sh != "")
|
||||||
|
{
|
||||||
|
source = validatedConfig;
|
||||||
|
};
|
||||||
|
"mango/autostart.sh" = lib.mkIf (cfg.autostart_sh != "") {
|
||||||
|
source = autostart_sh;
|
||||||
|
executable = true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
"mango/autostart.sh" = lib.mkIf (cfg.autostart_sh != "") {
|
systemd.user.targets.mango-session = lib.mkIf cfg.systemd.enable {
|
||||||
source = autostart_sh;
|
Unit = {
|
||||||
executable = true;
|
Description = "mango compositor session";
|
||||||
};
|
Documentation = [ "man:systemd.special(7)" ];
|
||||||
};
|
BindsTo = [ "graphical-session.target" ];
|
||||||
systemd.user.targets.mango-session = lib.mkIf cfg.systemd.enable {
|
Wants = [
|
||||||
Unit = {
|
|
||||||
Description = "mango compositor session";
|
|
||||||
Documentation = ["man:systemd.special(7)"];
|
|
||||||
BindsTo = ["graphical-session.target"];
|
|
||||||
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";
|
||||||
After = ["graphical-session-pre.target"];
|
After = [ "graphical-session-pre.target" ];
|
||||||
Before = lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target";
|
Before = lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
312
nix/lib.nix
Normal file
312
nix/lib.nix
Normal file
|
|
@ -0,0 +1,312 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -312,11 +312,6 @@ static inline uint32_t client_set_size(Client *c, uint32_t width,
|
||||||
int32_t width = c->geom.width - 2 * c->bw;
|
int32_t width = c->geom.width - 2 * c->bw;
|
||||||
int32_t height = c->geom.height - 2 * c->bw;
|
int32_t height = c->geom.height - 2 * c->bw;
|
||||||
|
|
||||||
if (c->mon && c->mon->isoverview && size_hints &&
|
|
||||||
c->geom.width - 2 * (int32_t)c->bw < size_hints->min_width &&
|
|
||||||
c->geom.height - 2 * (int32_t)c->bw < size_hints->min_height)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (size_hints &&
|
if (size_hints &&
|
||||||
c->geom.width - 2 * (int32_t)c->bw < size_hints->min_width)
|
c->geom.width - 2 * (int32_t)c->bw < size_hints->min_width)
|
||||||
width = size_hints->min_width;
|
width = size_hints->min_width;
|
||||||
|
|
|
||||||
|
|
@ -863,7 +863,6 @@ 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;
|
||||||
|
|
||||||
|
|
@ -876,28 +875,21 @@ int32_t spawn(const Arg *arg) {
|
||||||
dup2(STDERR_FILENO, STDOUT_FILENO);
|
dup2(STDERR_FILENO, STDOUT_FILENO);
|
||||||
setsid();
|
setsid();
|
||||||
|
|
||||||
// 2. 解析参数
|
// 2. 对整个参数字符串进行单词展开
|
||||||
char *argv[64];
|
wordexp_t p;
|
||||||
int32_t argc = 0;
|
if (wordexp(arg->v, &p, 0) != 0) {
|
||||||
char *token = strtok((char *)arg->v, " ");
|
wlr_log(WLR_DEBUG, "mango: wordexp failed for '%s'\n", arg->v);
|
||||||
while (token != NULL && argc < 63) {
|
_exit(EXIT_FAILURE);
|
||||||
wordexp_t p;
|
|
||||||
if (wordexp(token, &p, 0) == 0) {
|
|
||||||
argv[argc++] = p.we_wordv[0];
|
|
||||||
} else {
|
|
||||||
argv[argc++] = token;
|
|
||||||
}
|
|
||||||
token = strtok(NULL, " ");
|
|
||||||
}
|
}
|
||||||
argv[argc] = NULL;
|
|
||||||
|
|
||||||
// 3. 执行命令
|
// 3. 执行命令(p.we_wordv 已经是 argv 数组)
|
||||||
execvp(argv[0], argv);
|
execvp(p.we_wordv[0], p.we_wordv);
|
||||||
|
|
||||||
// 4. execvp 失败时:打印错误并直接退出(避免 coredump)
|
// 4. execvp 失败时:打印错误,释放 wordexp 资源,然后退出
|
||||||
wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", argv[0],
|
wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", p.we_wordv[0],
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
_exit(EXIT_FAILURE); // 使用 _exit 避免缓冲区刷新等操作
|
wordfree(&p); // 释放 wordexp 分配的内存
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
55
src/mango.c
55
src/mango.c
|
|
@ -1569,12 +1569,17 @@ void applyrules(Client *c) {
|
||||||
int32_t fullscreen_state_backup =
|
int32_t fullscreen_state_backup =
|
||||||
c->isfullscreen || client_wants_fullscreen(c);
|
c->isfullscreen || client_wants_fullscreen(c);
|
||||||
|
|
||||||
setmon(c, mon, newtags,
|
bool should_init_get_focus =
|
||||||
!c->isopensilent &&
|
!c->isopensilent &&
|
||||||
!(client_is_x11_popup(c) && client_should_ignore_focus(c)) &&
|
!(client_is_x11_popup(c) && client_should_ignore_focus(c)) && mon &&
|
||||||
mon &&
|
(!c->istagsilent || !newtags || newtags & mon->tagset[mon->seltags]);
|
||||||
(!c->istagsilent || !newtags ||
|
|
||||||
newtags & mon->tagset[mon->seltags]));
|
if (!should_init_get_focus) {
|
||||||
|
wl_list_remove(&c->flink);
|
||||||
|
wl_list_insert(fstack.prev, &c->flink);
|
||||||
|
}
|
||||||
|
|
||||||
|
setmon(c, mon, newtags, should_init_get_focus);
|
||||||
|
|
||||||
if (!c->isfloating) {
|
if (!c->isfloating) {
|
||||||
c->old_stack_inner_per = c->stack_inner_per;
|
c->old_stack_inner_per = c->stack_inner_per;
|
||||||
|
|
@ -4211,6 +4216,7 @@ mapnotify(struct wl_listener *listener, void *data) {
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
wl_list_insert(clients.prev, &c->link); // 尾部入栈
|
wl_list_insert(clients.prev, &c->link); // 尾部入栈
|
||||||
|
|
||||||
wl_list_insert(&fstack, &c->flink);
|
wl_list_insert(&fstack, &c->flink);
|
||||||
|
|
||||||
applyrules(c);
|
applyrules(c);
|
||||||
|
|
@ -4378,20 +4384,23 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx,
|
||||||
|
|
||||||
if (active_constraint && cursor_mode != CurResize &&
|
if (active_constraint && cursor_mode != CurResize &&
|
||||||
cursor_mode != CurMove) {
|
cursor_mode != CurMove) {
|
||||||
toplevel_from_wlr_surface(active_constraint->surface, &c, NULL);
|
if (active_constraint->surface ==
|
||||||
if (c && active_constraint->surface ==
|
seat->pointer_state.focused_surface) {
|
||||||
seat->pointer_state.focused_surface) {
|
|
||||||
sx = cursor->x - c->geom.x - c->bw;
|
|
||||||
sy = cursor->y - c->geom.y - c->bw;
|
|
||||||
if (wlr_region_confine(&active_constraint->region, sx, sy,
|
|
||||||
sx + dx, sy + dy, &sx_confined,
|
|
||||||
&sy_confined)) {
|
|
||||||
dx = sx_confined - sx;
|
|
||||||
dy = sy_confined - sy;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED)
|
if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
toplevel_from_wlr_surface(active_constraint->surface, &c, NULL);
|
||||||
|
if (c) {
|
||||||
|
sx = cursor->x - c->geom.x - c->bw;
|
||||||
|
sy = cursor->y - c->geom.y - c->bw;
|
||||||
|
if (wlr_region_confine(&active_constraint->region, sx, sy,
|
||||||
|
sx + dx, sy + dy, &sx_confined,
|
||||||
|
&sy_confined)) {
|
||||||
|
dx = sx_confined - sx;
|
||||||
|
dy = sy_confined - sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6326,10 +6335,14 @@ void view_in_mon(const Arg *arg, bool want_animation, Monitor *m,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg->ui == UINT32_MAX) {
|
if (arg->ui == UINT32_MAX) {
|
||||||
m->pertag->prevtag = get_tags_first_tag_num(m->tagset[m->seltags]);
|
if (m->tagset[0] != m->tagset[1]) {
|
||||||
m->seltags ^= 1; /* toggle sel tagset */
|
m->pertag->prevtag = get_tags_first_tag_num(m->tagset[m->seltags]);
|
||||||
m->pertag->curtag = get_tags_first_tag_num(m->tagset[m->seltags]);
|
m->seltags ^= 1; /* toggle sel tagset */
|
||||||
goto toggleseltags;
|
m->pertag->curtag = get_tags_first_tag_num(m->tagset[m->seltags]);
|
||||||
|
goto toggleseltags;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((m->tagset[m->seltags] & arg->ui & TAGMASK) != 0) {
|
if ((m->tagset[m->seltags] & arg->ui & TAGMASK) != 0) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue