Merge branch 'mangowm:main' into main

This commit is contained in:
Davide Greco 2026-04-06 14:23:16 +02:00 committed by GitHub
commit 76ad12e51c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 621 additions and 117 deletions

View file

@ -34,7 +34,7 @@ animation_type_close=slide
animation_fade_in=1 animation_fade_in=1
animation_fade_out=1 animation_fade_out=1
tag_animation_direction=1 tag_animation_direction=1
zoom_initial_ratio=0.3 zoom_initial_ratio=0.4
zoom_end_ratio=0.8 zoom_end_ratio=0.8
fadein_begin_opacity=0.5 fadein_begin_opacity=0.5
fadeout_begin_opacity=0.8 fadeout_begin_opacity=0.8

View file

@ -45,6 +45,7 @@ Specific settings for laptop touchpads. Some settings may require a relogin to t
| `tap_to_click` | `1` | Tap to trigger a left click. | | `tap_to_click` | `1` | Tap to trigger a left click. |
| `tap_and_drag` | `1` | Tap and hold to drag items. | | `tap_and_drag` | `1` | Tap and hold to drag items. |
| `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). | | `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). |
| `scroll_button` | `274` | The mouse button that use for scrolling(272 to 279).
| `scroll_method` | `1` | `1` (Two-finger), `2` (Edge), `4` (Button). | | `scroll_method` | `1` | `1` (Two-finger), `2` (Edge), `4` (Button). |
| `click_method` | `1` | `1` (Button areas), `2` (Clickfinger). | | `click_method` | `1` | `1` (Button areas), `2` (Clickfinger). |
| `drag_lock` | `1` | Lock dragging after tapping. | | `drag_lock` | `1` | Lock dragging after tapping. |
@ -57,6 +58,16 @@ Specific settings for laptop touchpads. Some settings may require a relogin to t
**Detailed descriptions:** **Detailed descriptions:**
- `scroll_button` values:
- `272` — Left button.
- `273` — Right button.
- `274` — Middle button.
- `275` — Side button.
- `276` — Extra button.
- `277` — Forward button.
- `278` — Back button.
- `279` — Task button.
- `scroll_method` values: - `scroll_method` values:
- `0` — Never send scroll events (no scrolling). - `0` — Never send scroll events (no scrolling).
- `1` — Two-finger scrolling: send scroll events when two fingers are logically down on the device. - `1` — Two-finger scrolling: send scroll events when two fingers are logically down on the device.

View file

@ -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 mangowc pikman install mangowm
``` ```
--- ---

View file

@ -46,7 +46,7 @@ fadeout_begin_opacity=0.5
Adjust the zoom ratios for zoom animations. Adjust the zoom ratios for zoom animations.
```ini ```ini
zoom_initial_ratio=0.3 zoom_initial_ratio=0.4
zoom_end_ratio=0.8 zoom_end_ratio=0.8
``` ```

View file

@ -27,7 +27,7 @@ windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values
| `isoverlay` | integer | `0` / `1` | Make it always in top layer | | `isoverlay` | integer | `0` / `1` | Make it always in top layer |
| `isopensilent` | integer | `0` / `1` | Open without focus | | `isopensilent` | integer | `0` / `1` | Open without focus |
| `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag | | `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag |
| `force_maximize` | integer | `0` / `1` (default 1) | The state of client default to maximized | | `force_fakemaximize` | integer | `0` / `1` (default 1) | The state of client set to fake maximized |
| `ignore_maximize` | integer | `0` / `1` (default 1) | Don't handle maximize request from client | | `ignore_maximize` | integer | `0` / `1` (default 1) | Don't handle maximize request from client |
| `ignore_minimize` | integer | `0` / `1` (default 1) | Don't handle minimize request from client | | `ignore_minimize` | integer | `0` / `1` (default 1) | Don't handle minimize request from client |
| `force_tiled_state` | integer | `0` / `1` | Deceive the window into thinking it is tiling, so it better adheres to assigned dimensions | | `force_tiled_state` | integer | `0` / `1` | Deceive the window into thinking it is tiling, so it better adheres to assigned dimensions |

View file

@ -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)

View file

@ -1,5 +1,5 @@
project('mango', ['c', 'cpp'], project('mango', ['c', 'cpp'],
version : '0.12.7', version : '0.12.8',
) )
subdir('protocols') subdir('protocols')

View file

@ -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
View 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;
}

View file

@ -294,9 +294,8 @@ static inline uint32_t client_set_size(Client *c, uint32_t width,
uint32_t height) { uint32_t height) {
#ifdef XWAYLAND #ifdef XWAYLAND
if (client_is_x11(c)) { if (client_is_x11(c)) {
struct wlr_xwayland_surface *surface = c->surface.xwayland;
struct wlr_surface_state *state = struct wlr_surface_state *state = &surface->surface->current;
&c->surface.xwayland->surface->current;
if ((int32_t)c->geom.width - 2 * (int32_t)c->bw == if ((int32_t)c->geom.width - 2 * (int32_t)c->bw ==
(int32_t)state->width && (int32_t)state->width &&
@ -309,6 +308,22 @@ static inline uint32_t client_set_size(Client *c, uint32_t width,
return 0; return 0;
} }
xcb_size_hints_t *size_hints = surface->size_hints;
int32_t width = c->geom.width - 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 &&
c->geom.width - 2 * (int32_t)c->bw < size_hints->min_width)
width = size_hints->min_width;
if (size_hints &&
c->geom.height - 2 * (int32_t)c->bw < size_hints->min_height)
height = size_hints->min_height;
wlr_xwayland_surface_configure(c->surface.xwayland, c->geom.x + c->bw, wlr_xwayland_surface_configure(c->surface.xwayland, c->geom.x + c->bw,
c->geom.y + c->bw, width, height); c->geom.y + c->bw, width, height);
return 1; return 1;
@ -350,7 +365,7 @@ static inline void client_set_maximized(Client *c, bool maximized) {
static inline void client_set_tiled(Client *c, uint32_t edges) { static inline void client_set_tiled(Client *c, uint32_t edges) {
struct wlr_xdg_toplevel *toplevel; struct wlr_xdg_toplevel *toplevel;
#ifdef XWAYLAND #ifdef XWAYLAND
if (client_is_x11(c) && c->force_maximize) { if (client_is_x11(c) && c->force_fakemaximize) {
wlr_xwayland_surface_set_maximized(c->surface.xwayland, wlr_xwayland_surface_set_maximized(c->surface.xwayland,
edges != WLR_EDGE_NONE, edges != WLR_EDGE_NONE,
edges != WLR_EDGE_NONE); edges != WLR_EDGE_NONE);
@ -365,7 +380,7 @@ static inline void client_set_tiled(Client *c, uint32_t edges) {
wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges);
} }
if (c->force_maximize) { if (c->force_fakemaximize) {
wlr_xdg_toplevel_set_maximized(toplevel, edges != WLR_EDGE_NONE); wlr_xdg_toplevel_set_maximized(toplevel, edges != WLR_EDGE_NONE);
} }
} }

View file

@ -90,7 +90,7 @@ typedef struct {
int32_t no_force_center; int32_t no_force_center;
int32_t isterm; int32_t isterm;
int32_t allow_csd; int32_t allow_csd;
int32_t force_maximize; int32_t force_fakemaximize;
int32_t force_tiled_state; int32_t force_tiled_state;
int32_t force_tearing; int32_t force_tearing;
int32_t noswallow; int32_t noswallow;
@ -2059,7 +2059,7 @@ bool parse_option(Config *config, char *key, char *value) {
rule->indleinhibit_when_focus = -1; rule->indleinhibit_when_focus = -1;
rule->isterm = -1; rule->isterm = -1;
rule->allow_csd = -1; rule->allow_csd = -1;
rule->force_maximize = -1; rule->force_fakemaximize = -1;
rule->force_tiled_state = -1; rule->force_tiled_state = -1;
rule->force_tearing = -1; rule->force_tearing = -1;
rule->noswallow = -1; rule->noswallow = -1;
@ -2173,8 +2173,8 @@ bool parse_option(Config *config, char *key, char *value) {
rule->isterm = atoi(val); rule->isterm = atoi(val);
} else if (strcmp(key, "allow_csd") == 0) { } else if (strcmp(key, "allow_csd") == 0) {
rule->allow_csd = atoi(val); rule->allow_csd = atoi(val);
} else if (strcmp(key, "force_maximize") == 0) { } else if (strcmp(key, "force_fakemaximize") == 0) {
rule->force_maximize = atoi(val); rule->force_fakemaximize = atoi(val);
} else if (strcmp(key, "force_tiled_state") == 0) { } else if (strcmp(key, "force_tiled_state") == 0) {
rule->force_tiled_state = atoi(val); rule->force_tiled_state = atoi(val);
} else if (strcmp(key, "force_tearing") == 0) { } else if (strcmp(key, "force_tearing") == 0) {
@ -3214,7 +3214,7 @@ void override_config(void) {
config.accel_profile = CLAMP_INT(config.accel_profile, 0, 2); config.accel_profile = CLAMP_INT(config.accel_profile, 0, 2);
config.accel_speed = CLAMP_FLOAT(config.accel_speed, -1.0f, 1.0f); config.accel_speed = CLAMP_FLOAT(config.accel_speed, -1.0f, 1.0f);
config.scroll_method = CLAMP_INT(config.scroll_method, 0, 4); config.scroll_method = CLAMP_INT(config.scroll_method, 0, 4);
config.scroll_button = CLAMP_INT(config.scroll_button, 272, 276); config.scroll_button = CLAMP_INT(config.scroll_button, 272, 279);
config.click_method = CLAMP_INT(config.click_method, 0, 2); config.click_method = CLAMP_INT(config.click_method, 0, 2);
config.send_events_mode = CLAMP_INT(config.send_events_mode, 0, 2); config.send_events_mode = CLAMP_INT(config.send_events_mode, 0, 2);
config.button_map = CLAMP_INT(config.button_map, 0, 1); config.button_map = CLAMP_INT(config.button_map, 0, 1);
@ -3264,7 +3264,7 @@ void set_value_default() {
config.animation_fade_in = 1; config.animation_fade_in = 1;
config.animation_fade_out = 1; config.animation_fade_out = 1;
config.tag_animation_direction = HORIZONTAL; config.tag_animation_direction = HORIZONTAL;
config.zoom_initial_ratio = 0.3f; config.zoom_initial_ratio = 0.4f;
config.zoom_end_ratio = 0.8f; config.zoom_end_ratio = 0.8f;
config.fadein_begin_opacity = 0.5f; config.fadein_begin_opacity = 0.5f;
config.fadeout_begin_opacity = 0.5f; config.fadeout_begin_opacity = 0.5f;

View file

@ -380,7 +380,11 @@ int32_t moveresize(const Arg *arg) {
/* Float the window and tell motionnotify to grab it */ /* Float the window and tell motionnotify to grab it */
if (grabc->isfloating == 0 && arg->ui == CurMove) { if (grabc->isfloating == 0 && arg->ui == CurMove) {
grabc->drag_to_tile = true; grabc->drag_to_tile = true;
exit_scroller_stack(grabc);
setfloating(grabc, 1); setfloating(grabc, 1);
grabc->old_stack_inner_per = 0.0f;
grabc->old_master_inner_per = 0.0f;
set_size_per(grabc->mon, grabc);
} }
switch (cursor_mode = arg->ui) { switch (cursor_mode = arg->ui) {
@ -552,7 +556,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) {
selmon->sel->isminimized = 0; client_pending_minimized_state(selmon->sel, 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,7 +867,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 +879,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;
} }

View file

@ -391,7 +391,7 @@ struct Client {
struct dwl_opacity_animation opacity_animation; struct dwl_opacity_animation opacity_animation;
int32_t isterm, noswallow; int32_t isterm, noswallow;
int32_t allow_csd; int32_t allow_csd;
int32_t force_maximize; int32_t force_fakemaximize;
int32_t force_tiled_state; int32_t force_tiled_state;
pid_t pid; pid_t pid;
Client *swallowing, *swallowedby; Client *swallowing, *swallowedby;
@ -807,6 +807,10 @@ 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"
@ -1064,11 +1068,33 @@ 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) {
c->isfullscreen = 0; // 清除窗口全屏标志 client_pending_fullscreen_state(c, 0);
c->ismaximizescreen = 0; client_pending_maximized_state(c, 0);
c->bw = c->isnoborder ? 0 : config.borderpx; c->bw = c->isnoborder ? 0 : config.borderpx;
} }
@ -1107,9 +1133,6 @@ 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;
@ -1121,6 +1144,7 @@ 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)
@ -1139,11 +1163,9 @@ 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);
if (c->isminimized && c->foreign_toplevel) { client_pending_fullscreen_state(c, w->isfullscreen);
wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, client_pending_maximized_state(c, w->ismaximizescreen);
false); client_pending_minimized_state(c, w->isminimized);
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) {
@ -1326,7 +1348,7 @@ void toggle_hotarea(int32_t x_root, int32_t y_root) {
static void apply_rule_properties(Client *c, const ConfigWinRule *r) { static void apply_rule_properties(Client *c, const ConfigWinRule *r) {
APPLY_INT_PROP(c, r, isterm); APPLY_INT_PROP(c, r, isterm);
APPLY_INT_PROP(c, r, allow_csd); APPLY_INT_PROP(c, r, allow_csd);
APPLY_INT_PROP(c, r, force_maximize); APPLY_INT_PROP(c, r, force_fakemaximize);
APPLY_INT_PROP(c, r, force_tiled_state); APPLY_INT_PROP(c, r, force_tiled_state);
APPLY_INT_PROP(c, r, force_tearing); APPLY_INT_PROP(c, r, force_tearing);
APPLY_INT_PROP(c, r, noswallow); APPLY_INT_PROP(c, r, noswallow);
@ -4081,7 +4103,7 @@ void init_client_properties(Client *c) {
c->old_master_mfact_per = 0.0f; c->old_master_mfact_per = 0.0f;
c->isterm = 0; c->isterm = 0;
c->allow_csd = 0; c->allow_csd = 0;
c->force_maximize = 0; c->force_fakemaximize = 0;
c->force_tiled_state = 1; c->force_tiled_state = 1;
c->force_tearing = 0; c->force_tearing = 0;
c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE;
@ -4239,7 +4261,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) {
c->isminimized = 0; client_pending_minimized_state(c, 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;
@ -4267,13 +4289,12 @@ 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;
c->isminimized = 1; client_pending_minimized_state(c, 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); // 插入尾部
} }
@ -5050,11 +5071,11 @@ setfloating(Client *c, int32_t floating) {
if (floating == 1 && c != grabc) { if (floating == 1 && c != grabc) {
if (c->isfullscreen) { if (c->isfullscreen) {
c->isfullscreen = 0; client_pending_fullscreen_state(c, 0);
client_set_fullscreen(c, 0); client_set_fullscreen(c, 0);
} }
c->ismaximizescreen = 0; client_pending_maximized_state(c, 0);
exit_scroller_stack(c); exit_scroller_stack(c);
// 重新计算居中的坐标 // 重新计算居中的坐标
@ -5120,7 +5141,7 @@ setfloating(Client *c, int32_t floating) {
save_old_size_per(c->mon); save_old_size_per(c->mon);
} }
if (!c->force_maximize) if (!c->force_fakemaximize)
client_set_maximized(c, false); client_set_maximized(c, false);
if (!c->isfloating || c->force_tiled_state) { if (!c->isfloating || c->force_tiled_state) {
@ -5179,12 +5200,12 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) {
return; return;
int32_t old_maximizescreen_state = c->ismaximizescreen; int32_t old_maximizescreen_state = c->ismaximizescreen;
c->ismaximizescreen = maximizescreen; client_pending_maximized_state(c, maximizescreen);
if (maximizescreen) { if (maximizescreen) {
if (c->isfullscreen) { if (c->isfullscreen) {
c->isfullscreen = 0; client_pending_fullscreen_state(c, 0);
client_set_fullscreen(c, 0); client_set_fullscreen(c, 0);
} }
@ -5197,10 +5218,8 @@ 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);
} }
@ -5215,9 +5234,9 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) {
save_old_size_per(c->mon); save_old_size_per(c->mon);
} }
if (!c->force_maximize && !c->ismaximizescreen) { if (!c->force_fakemaximize && !c->ismaximizescreen) {
client_set_maximized(c, false); client_set_maximized(c, false);
} else if (!c->force_maximize && c->ismaximizescreen) { } else if (!c->force_fakemaximize && c->ismaximizescreen) {
client_set_maximized(c, true); client_set_maximized(c, true);
} }
@ -5248,14 +5267,15 @@ 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) {
if (c->ismaximizescreen && !c->force_maximize) { if (c->ismaximizescreen && !c->force_fakemaximize) {
client_set_maximized(c, false); client_set_maximized(c, false);
} }
c->ismaximizescreen = 0; client_pending_maximized_state(c, 0);
exit_scroller_stack(c); exit_scroller_stack(c);
c->isfakefullscreen = 0; c->isfakefullscreen = 0;
@ -5264,10 +5284,8 @@ 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);
} }
@ -5459,8 +5477,7 @@ void show_hide_client(Client *c) {
c->tags = c->oldtags; c->tags = c->oldtags;
arrange(c->mon, false, false); arrange(c->mon, false, false);
} }
c->isminimized = 0; client_pending_minimized_state(c, 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);
} }
@ -5884,8 +5901,8 @@ void overview_backup(Client *c) {
c->isfloating = 0; c->isfloating = 0;
} }
if (c->isfullscreen || c->ismaximizescreen) { if (c->isfullscreen || c->ismaximizescreen) {
c->isfullscreen = 0; // 清除窗口全屏标志 client_pending_fullscreen_state(c, 0); // 清除窗口全屏标志
c->ismaximizescreen = 0; client_pending_maximized_state(c, 0);
} }
c->bw = c->isnoborder ? 0 : config.borderpx; c->bw = c->isnoborder ? 0 : config.borderpx;
@ -5915,8 +5932,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 {
c->isfullscreen = 0; client_pending_fullscreen_state(c, 0);
c->ismaximizescreen = 0; client_pending_maximized_state(c, 0);
setfullscreen(c, false); setfullscreen(c, false);
} }
} else { } else {
@ -6489,13 +6506,11 @@ void activatex11(struct wl_listener *listener, void *data) {
return; return;
if (c->isminimized) { if (c->isminimized) {
c->isminimized = 0; client_pending_minimized_state(c, 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;