Merge remote-tracking branch 'github/main' into ext-workspace-0.20
Some checks failed
Generate Nix Options Docs / update-docs (push) Has been cancelled

This commit is contained in:
DreamMaoMao 2026-06-19 12:32:28 +08:00
commit da13241f9f
90 changed files with 16353 additions and 5086 deletions

48
.github/scripts/generate-nix-options-docs.py vendored Executable file
View file

@ -0,0 +1,48 @@
#!/usr/bin/env python3
# Post-processes nixos-render-docs commonmark output into a single docs page.
# Input: two pre-rendered .md files (NixOS and HM), produced by nixos-render-docs.
# Steps: strip internal _module.args section, promote ## headings to ###.
import re
import sys
HEADER = (
"---\n"
"title: Nix Module Options\n"
"description: NixOS and Home Manager configuration options for mangowm.\n"
"---\n\n"
"> **Note:** This document is automatically generated from the Nix module source code.\n\n"
)
SECTIONS = [
("NixOS", "**System-level options via `programs.mango`.**"),
("Home Manager", "**Configure mangowm declaratively via `wayland.windowManager.mango`.**"),
]
def process(md):
# Remove internal _module.args option injected by the module system
md = re.sub(r'## _module\\\.args.*?(?=\n## |\Z)', '', md, flags=re.DOTALL)
# Promote option headings (##) to subheadings (###) under the section (##)
md = re.sub(r'^## ', '### ', md, flags=re.MULTILINE)
return md.strip()
def main():
if len(sys.argv) != 4:
sys.exit("Usage: generate-nix-options-docs.py <nixos.md> <hm.md> <output.md>")
nixos_md, hm_md, output_md = sys.argv[1:4]
with open(output_md, 'w', encoding='utf-8') as out:
out.write(HEADER)
for path, (title, subtitle) in zip([nixos_md, hm_md], SECTIONS):
with open(path, 'r', encoding='utf-8') as f:
md = f.read()
out.write(f"## {title}\n\n{subtitle}\n\n")
out.write(process(md))
out.write('\n\n')
print(f"Written {title} section.")
if __name__ == "__main__":
main()

61
.github/scripts/sync-wiki.py vendored Normal file
View file

@ -0,0 +1,61 @@
#!/usr/bin/env python3
import json
import re
from pathlib import Path
DOCS_DIR = Path("docs")
WIKI_DIR = Path("wiki-temp")
FRONTMATTER_RE = re.compile(r"\A---\s*\n.*?^---\s*\n", re.DOTALL | re.MULTILINE)
DOCS_LINK_RE = re.compile(r"\[([^\]]+)\]\(/docs/(?:[^/)]+/)*([^/)#]+)(#[^)]+)?\)")
def collect_all_files() -> list[tuple[Path, str]]:
files = []
def from_dir(directory: Path) -> list[Path]:
meta = directory / "meta.json"
if meta.exists():
data = json.loads(meta.read_text())
return [directory / f"{p}.md" for p in data.get("pages", []) if (directory / f"{p}.md").exists()]
return sorted(directory.glob("*.md"))
for src in from_dir(DOCS_DIR):
files.append((src, "Home" if src.stem == "index" else src.stem))
for subdir in sorted(DOCS_DIR.iterdir()):
if subdir.is_dir():
for src in from_dir(subdir):
files.append((src, src.stem))
return files
def main() -> None:
files = collect_all_files()
contents = {src: src.read_text() for src, _ in files}
for src, dest_name in files:
text = FRONTMATTER_RE.sub("", contents[src], count=1).lstrip("\n")
text = DOCS_LINK_RE.sub(lambda m: f"[{m.group(1)}]({m.group(2)}{m.group(3) or ''})", text)
(WIKI_DIR / f"{dest_name}.md").write_text(text)
lines: list[str] = []
current_section = None
for src, dest_name in files:
section = "General" if src.parent == DOCS_DIR else src.parent.name.replace("-", " ").title()
if section != current_section:
if current_section is not None:
lines.append("")
lines.append(f"## {section}\n")
current_section = section
if dest_name != "Home":
title = dest_name.replace("-", " ").replace("_", " ").title()
lines.append(f"- [[{dest_name}|{title}]]")
(WIKI_DIR / "_Sidebar.md").write_text("\n".join(lines))
if __name__ == "__main__":
main()

View file

@ -0,0 +1,63 @@
name: Generate Nix Options Docs
on:
push:
paths:
- 'nix/**-modules.nix'
- 'nix/generate-options.nix'
- 'flake.nix'
- '.github/scripts/generate-nix-options-docs.py'
pull_request:
paths:
- 'nix/**-modules.nix'
jobs:
update-docs:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Build Options JSON
run: |
nix build .#nixos-options-json --out-link result-nixos
nix build .#hm-options-json --out-link result-hm
- name: Render to CommonMark
run: |
echo '{}' > /tmp/manpage-urls.json
nix run nixpkgs#nixos-render-docs -- options commonmark \
--manpage-urls /tmp/manpage-urls.json \
--revision nightly \
result-nixos/share/doc/nixos/options.json \
/tmp/nixos-raw.md
nix run nixpkgs#nixos-render-docs -- options commonmark \
--manpage-urls /tmp/manpage-urls.json \
--revision nightly \
result-hm/share/doc/nixos/options.json \
/tmp/hm-raw.md
- name: Format to Markdown
run: |
python3 ./.github/scripts/generate-nix-options-docs.py \
/tmp/nixos-raw.md \
/tmp/hm-raw.md \
'docs/nix-options.md'
- name: Auto-commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "docs: auto-generate Nix module options"
file_pattern: 'docs/nix-options.md'
branch: ${{ github.head_ref }}

View file

@ -16,7 +16,7 @@ jobs:
days-before-issue-stale: -1
# 手动标记后14 天后关闭
days-before-issue-close: 7
# 使用的标签(必须和你手动添加的标签一致)
# 使用的标签
stale-issue-label: "stale"
# 自动关闭时自动加上的标签
close-issue-label: "automatic-closing"

74
.github/workflows/sync-website.yml vendored Normal file
View file

@ -0,0 +1,74 @@
name: Sync website
on:
push:
branches: [main]
paths:
- docs/**
concurrency:
group: sync-website
cancel-in-progress: true
jobs:
sync-website:
if: github.repository == 'mangowm/mango'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ github.token }}
- name: Fetch tags for versioned docs
run: git fetch origin --tags --depth=1
- name: Checkout website
uses: actions/checkout@v4
with:
repository: mangowm/mangowm.github.io
path: website
token: ${{ secrets.WEBSITE_SYNC_TOKEN }}
fetch-depth: 1
- name: Sync docs
run: |
TARGET=website/apps/web/content/docs
rm -rf "$TARGET"
mkdir -p "$TARGET"
# Copy current docs as "(git)"
cp -r docs "$TARGET/(git)"
# Generate versioned docs from last 2 semver tags
tags=$(git tag --sort=-v:refname | grep '^v\?[0-9]\+\.[0-9]\+\.[0-9]\+$' | head -2)
for tag in $tags; do
git worktree add /tmp/docs-"$tag" "$tag" || continue
if [ -d "/tmp/docs-$tag/docs" ]; then
name="v${tag#v}"
cp -r "/tmp/docs-$tag/docs" "$TARGET/$name"
jq --arg title "$name" --arg desc "$name release" \
'.title = $title | .description = $desc | .root = true' \
"$TARGET/$name/meta.json" > "$TARGET/$name/meta.json.tmp" \
&& mv "$TARGET/$name/meta.json.tmp" "$TARGET/$name/meta.json"
fi
git worktree remove /tmp/docs-"$tag"
done
# Generate root version index
{
echo "(git)"
for tag in $tags; do
echo "v${tag#v}"
done
} | jq -Rn '[inputs | select(length > 0)] | {pages: .}' > "$TARGET/meta.json"
- name: Commit and push
working-directory: website
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add apps/web/content/docs
git diff --staged --quiet || git commit \
-m "docs: content update from mangowm/mango" \
-m "${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}"
git push

40
.github/workflows/sync-wiki.yml vendored Normal file
View file

@ -0,0 +1,40 @@
name: Sync wiki
on:
push:
branches: [main]
paths:
- docs/**
concurrency:
group: sync-wiki
cancel-in-progress: true
permissions:
contents: write
jobs:
sync-wiki:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Clone wiki
run: |
git clone --depth 1 \
https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.wiki.git \
wiki-temp
- name: Sync docs to wiki
run: |
find wiki-temp -not -path 'wiki-temp/.git*' -type f -delete
python3 .github/scripts/sync-wiki.py
- name: Commit and push
working-directory: wiki-temp
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git diff --staged --quiet || git commit -m "sync from ${{ github.sha }}"
git push

354
README.md
View file

@ -1,299 +1,129 @@
# Mango Wayland Compositor
<div>
<img src="https://github.com/DreamMaoMao/mangowc/blob/main/assets/mango-transparency-256.png" alt="MangoWC Logo" width="120"/>
<div align="center">
<img src="https://github.com/mangowm/mango/blob/main/assets/mango-transparency-256.png" alt="Mango Logo" width="120"/>
<h1>Mango Wayland Compositor</h1>
<p>A fast, feature-rich Wayland compositor built on <a href="https://codeberg.org/dwl/dwl">dwl</a></p>
<a href="https://github.com/mangowm/mango/stargazers"><img src="https://img.shields.io/github/stars/mangowm/mango?style=flat&color=orange" alt="Stars"/></a>
<a href="https://github.com/mangowm/mango/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-GPL--3.0-blue?style=flat" alt="License"/></a>
<a href="https://repology.org/project/mangowm/versions"><img src="https://repology.org/badge/tiny-repos/mangowm.svg" alt="Packaged in"/></a>
<a href="https://discord.gg/CPjbDxesh5"><img src="https://img.shields.io/discord/1430889676264177687?style=flat&logo=discord&label=discord" alt="Discord"/></a>
</div>
This project's development is based on [dwl](https://codeberg.org/dwl/dwl/).
---
https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f
1. **Lightweight & Fast Build**
> See all layouts in action at [mangowm.github.io](https://mangowm.github.io/)
- _Mango_ is as lightweight as _dwl_, and can be built completely within a few seconds. Despite this, _Mango_ does not compromise on functionality.
## Why Mango?
2. **Feature Highlights**
- In addition to basic WM functionality, Mango provides:
- Excellent xwayland support.
- Base tags not workspaces (supports separate window layouts for each tag)
- Smooth and customizable complete animations (window open/move/close, tag enter/leave,layer open/close/move)
- Excellent input method support (text input v2/v3)
- Flexible window layouts with easy switching (scroller, master-stack, monocle,center-master, etc.)
- Rich window states (swallow, minimize, maximize, unglobal, global, fakefullscreen, overlay, etc.)
- Simple yet powerful external configuration(support shortcuts hot-reload)
- Sway-like scratchpad and named scratchpad
- Ipc support(get/send message from/to compositor by external program)
- Hycov-like overview
- Window effects from scenefx (blur, shadow, corner radius, opacity)
Mango starts where dwl ends. It keeps the lightweight, fast-build philosophy while adding the features that make a compositor actually usable day-to-day — without the bloat.
Master-Stack Layout
- **Lightweight & fast** — as lean as dwl, builds in seconds, no functionality compromised
- **Excellent xwayland support** — run X11 apps without friction
- **Tags, not workspaces** — each tag maintains its own independent window layout
- **Smooth animations** — window open/move/close, tag transitions, layer surfaces
- **Flexible layouts** — scroller, master-stack, monocle, dwindle, grid, and more
- **Rich window states** — swallow, minimize, maximize, global, overlay, fakefullscreen
- **Window effects** — blur, shadow, corner radius, opacity (via scenefx)
- **Excellent input method support** — text-input v2/v3
- **Sway-like scratchpad** — named scratchpad support included
- **Hycov-style overview** — see all windows at a glance
- **IPC** — send/receive messages from external programs
- **Hot-reload config** — no restart needed for keybinding changes
- **Zero flickering** — every frame is correct
https://github.com/user-attachments/assets/a9d4776e-b50b-48fb-94ce-651d8a749b8a
## Vision
Scroller Layout
**Stability first.** After months of testing, Mango is solid enough for daily use. Breaking changes will be minimal.
https://github.com/user-attachments/assets/c9bf9415-fad1-4400-bcdc-3ad2d76de85a
**Practicality over novelty.** Features get added when they genuinely improve daily workflows — not for the sake of completeness.
Layer animation
**Focused scope.** Niche requests are evaluated by community interest. Significant upvotes move things forward.
https://github.com/user-attachments/assets/014c893f-115c-4ae9-8342-f9ae3e9a0df0
## Installation
[![Packaging status](https://repology.org/badge/vertical-allrepos/mangowm.svg)](https://repology.org/project/mangowm/versions)
# Our discord
[mangowc](https://discord.gg/CPjbDxesh5)
### Arch Linux
# Supported layouts
- tile
- scroller
- monocle
- grid
- deck
- center_tile
- vertical_tile
- vertical_grid
- vertical_scroller
# Installation
## Dependencies
- glibc
- wayland
- wayland-protocols
- libinput
- libdrm
- libxkbcommon
- pixman
- git
- meson
- ninja
- libdisplay-info
- libliftoff
- hwdata
- seatd
- pcre2
- xorg-xwayland
- libxcb
## Arch Linux
The package is in the Arch User Repository and is availble for manual download [here](https://aur.archlinux.org/packages/mangowc-git) or through a AUR helper like yay:
```bash
yay -S mangowc-git
yay -S mangowm-git
```
## Gentoo Linux
The package is in the community-maintained repository called GURU.
First, add GURU repository:
```bash
emerge --ask --verbose eselect-repository
eselect repository enable guru
emerge --sync guru
#### use my config
- install dependencies
```
Then, add `gui-libs/scenefx` and `gui-wm/mangowc` to the `package.accept_keywords`.
Finally, install the package:
```bash
emerge --ask --verbose gui-wm/mangowc
```
## Fedora Linux
The package is in the third-party Terra repository.
First, add the [Terra Repository](https://terra.fyralabs.com/).
Then, install the package:
```bash
dnf install mangowc
```
## Other
```bash
git clone -b 0.19.2 https://gitlab.freedesktop.org/wlroots/wlroots.git
cd wlroots
meson build -Dprefix=/usr
sudo ninja -C build install
git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git
cd scenefx
meson build -Dprefix=/usr
sudo ninja -C build install
git clone https://github.com/DreamMaoMao/mangowc.git
cd mangowc
meson build -Dprefix=/usr
sudo ninja -C build install
```
## Suggested Tools
### Hybrid component
- [dms-shell](https://github.com/AvengeMedia/DankMaterialShell)
### Independent component
- Application launcher (rofi, bemenu, wmenu, fuzzel)
- Terminal emulator (foot, wezterm, alacritty, kitty, ghostty)
- Status bar (waybar, eww, quickshell, ags), waybar is preferred
- Wallpaper setup (swww, swaybg)
- Notification daemon (swaync, dunst,mako)
- Desktop portal (xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk)
- Clipboard (wl-clipboard, wl-clip-persist, cliphist)
- Gamma control/night light (wlsunset, gammastep)
- Miscellaneous (xfce-polkit, wlogout)
## Some Common Default Keybindings
- alt+return: open foot terminal
- alt+space: open rofi launcher
- alt+q: kill client
- alt+left/right/up/down: focus direction
- super+m: quit mango
## My Dotfiles
### Daily
- Dependencies
```bash
yay -S rofi foot xdg-desktop-portal-wlr swaybg waybar wl-clip-persist cliphist wl-clipboard wlsunset xfce-polkit swaync pamixer wlr-dpms sway-audio-idle-inhibit-git swayidle dimland-git brightnessctl swayosd wlr-randr grim slurp satty swaylock-effects-git wlogout sox
```
### Dms
- Dependencies
```bash
yay -S foot xdg-desktop-portal-wlr swaybg wl-clip-persist cliphist wl-clipboard sway-audio-idle-inhibit-git brightnessctl grim slurp satty matugen-bin dms-shell-git
- clone config
```
- use my dms config
```bash
git clone -b dms https://github.com/DreamMaoMao/mango-config.git ~/.config/mango
```
- use my daily config
```bash
git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango
```
### Other distributions
## Config Documentation
See the [Installation Guide](https://mangowm.github.io/docs/installation) for Fedora, Gentoo, Guix, NixOS, openSUSE, PikaOS, AerynOS, and building from source.
Refer to the repo wiki [wiki](https://github.com/DreamMaoMao/mango/wiki/)
## Documentation
or the website docs [docs](https://mangowc.vercel.app/docs)
- **[mangowm.github.io](https://mangowm.github.io/)** — website docs with configuration reference, keybindings, layouts, IPC, and more
- **[GitHub Wiki](https://github.com/mangowm/mango/wiki/)** — community-maintained wiki
# NixOS + Home-manager
## Community
The repo contains a flake that provides a NixOS module and a home-manager module for mango.
Use the NixOS module to install mango with other necessary components of a working Wayland environment.
Use the home-manager module to declare configuration and autostart for mango.
Join us on **[Discord](https://discord.gg/CPjbDxesh5)**
Here's an example of using the modules in a flake:
## Acknowledgements
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-parts.url = "github:hercules-ci/flake-parts";
mango = {
url = "github:DreamMaoMao/mango";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
inputs@{ self, flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
debug = true;
systems = [ "x86_64-linux" ];
flake = {
nixosConfigurations = {
hostname = inputs.nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
inputs.home-manager.nixosModules.home-manager
- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) — Wayland protocol implementation
- [dwl](https://codeberg.org/dwl/dwl) — the foundation Mango builds on
- [scenefx](https://github.com/wlrfx/scenefx) — window effects library
- [owl](https://github.com/dqrk0jeste/owl) — animation groundwork
- [sway](https://github.com/swaywm/sway) — protocol reference
# Add mango nixos module
inputs.mango.nixosModules.mango
{
programs.mango.enable = true;
}
{
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
backupFileExtension = "backup";
users."username".imports =
[
(
{ ... }:
{
wayland.windowManager.mango = {
enable = true;
settings = ''
# see config.conf
'';
autostart_sh = ''
# see autostart.sh
# Note: here no need to add shebang
'';
};
}
)
]
++ [
# Add mango hm module
inputs.mango.hmModules.mango
];
};
}
];
};
};
};
};
}
```
## Sponsor
# Packaging mango
If Mango makes your desktop better, consider supporting its development.
To package mango for other distributions, you can check the reference setup for:
Thanks to everyone who has sponsored this project:
- [nix](https://github.com/DreamMaoMao/mangowc/blob/main/nix/default.nix)
- [arch](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=mangowc-git).
- [gentoo](https://data.gpo.zugaina.org/guru/gui-wm/mangowc)
<table>
<tr>
<!-- add new sponsors here: copy the <td>...</td> block below -->
<td align="center">
<a href="https://github.com/dl09r">
<img src="https://unavatar.io/github/dl09r" width="48" style="border-radius:50%"/><br/>
<sub>dl09r</sub>
</a>
</td>
<td align="center">
<a href="https://github.com/tonybanters">
<img src="https://unavatar.io/github/tonybanters" width="48" style="border-radius:50%"/><br/>
<sub>tonybanters</sub>
</a>
</td>
<td align="center">
<a href="https://github.com/vinthara">
<img src="https://unavatar.io/github/vinthara" width="48" style="border-radius:50%"/><br/>
<sub>vinthara</sub>
</a>
</td>
</tr>
</table>
You might need to package `scenefx` for your distribution, check availability [here](https://github.com/wlrfx/scenefx.git).
Crypto donations accepted:
If you encounter build errors when packaging `mango`, feel free to create an issue and ask a question, but
Read The Friendly Manual on packaging software in your distribution first.
# Thanks to These Reference Repositories
- https://gitlab.freedesktop.org/wlroots/wlroots - Implementation of Wayland protocol
- https://github.com/dqrk0jeste/owl - Basal window animation
- https://codeberg.org/dwl/dwl - Basal dwl feature
- https://github.com/swaywm/sway - Sample of Wayland protocol
- https://github.com/wlrfx/scenefx - Make it simple to add window effect.
# Sponsor
At present, I can only accept sponsorship through an encrypted connection.
If you find this project helpful to you, you can offer sponsorship in the following ways.
<img width="650" height="870" alt="image" src="https://github.com/user-attachments/assets/8c860317-90d2-4071-971d-f1a92b674469" />
Thanks to the following friends for their sponsorship of this project
[@tonybanters](https://github.com/tonybanters)
<table>
<tr>
<td valign="middle">
<strong>Network:</strong> BEP20 (BSC)<br/>
<strong>Address:</strong> <code>0xf9cda472f2556671d2504afc4c35340ec5615da1</code>
</td>
<td valign="middle">
<img width="120" alt="sponsor QR" src="assets/crypto_sponserme_qrcode.png" />
</td>
</tr>
</table>

View file

@ -34,7 +34,7 @@ animation_type_close=slide
animation_fade_in=1
animation_fade_out=1
tag_animation_direction=1
zoom_initial_ratio=0.3
zoom_initial_ratio=0.4
zoom_end_ratio=0.8
fadein_begin_opacity=0.5
fadeout_begin_opacity=0.8
@ -57,6 +57,7 @@ scroller_default_proportion=0.8
scroller_focus_center=0
scroller_prefer_center=0
edge_scroller_pointer_focus=1
edge_scroller_focus_allow_speed=0.0
scroller_default_proportion_single=1.0
scroller_proportion_preset=0.5,0.8,1.0
@ -66,10 +67,19 @@ default_mfact=0.55
default_nmaster=1
smartgaps=0
# Dwindle Layout Setting
dwindle_smart_split=0
dwindle_drop_simple_split=1
dwindle_manual_split=0
dwindle_hsplit=1
dwindle_vsplit=1
dwindle_preserve_split=0
# Overview Setting
hotarea_size=10
enable_hotarea=1
ov_tab_mode=0
enable_hotarea=0
ov_tab_mode=1
ov_no_resize=1
overviewgappi=5
overviewgappo=30
@ -86,6 +96,7 @@ enable_floating_snap=0
snap_distance=30
cursor_size=24
drag_tile_to_tile=1
drag_tile_small=1
# keyboard
repeat_rate=25
@ -119,6 +130,8 @@ scratchpad_height_ratio=0.9
borderpx=4
rootcolor=0x201b14ff
bordercolor=0x444444ff
dropcolor=0x8FBA7C55
splitcolor=0xEB441EFF
focuscolor=0xc9b890ff
maximizescreencolor=0x89aa61ff
urgentcolor=0xad401fff
@ -181,6 +194,13 @@ bind=ALT,z,toggle_scratchpad
# scroller layout
bind=ALT,e,set_proportion,1.0
bind=ALT,x,switch_proportion_preset,
bind=alt+super+ctrl,Left,scroller_stack,left
bind=alt+super+ctrl,Right,scroller_stack,right
bind=alt+super+ctrl,Up,scroller_stack,up
bind=alt+super+ctrl,Down,scroller_stack,down
#dwindle layout(manual split mode)
bind=alt+shift,Return,dwindle_toggle_split_direction
# switch layout
bind=SUPER,n,switch_layout
@ -240,12 +260,11 @@ bind=CTRL+ALT,Left,resizewin,-50,+0
bind=CTRL+ALT,Right,resizewin,+50,+0
# Mouse Button Bindings
# NONE mode key only work in ov mode
# btn_left and btn_right can't bind none mod key
mousebind=SUPER,btn_left,moveresize,curmove
mousebind=NONE,btn_middle,togglemaximizescreen,0
mousebind=SUPER,btn_right,moveresize,curresize
mousebind=NONE,btn_left,toggleoverview,1
mousebind=NONE,btn_right,killclient,0
# Axis Bindings
axisbind=SUPER,UP,viewtoleft_have_client

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,5 @@
[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=wlr
org.freedesktop.impl.portal.Screenshot=wlr
org.freedesktop.impl.portal.Inhibit=none

View file

@ -1,6 +1,7 @@
[Desktop Entry]
Encoding=UTF-8
Name=Mango
DesktopNames=mango;wlroots
Comment=mango WM
Exec=mango
Icon=mango

15
docs/bindings/index.mdx Normal file
View file

@ -0,0 +1,15 @@
---
title: Bindings & Input
description: Keybindings, mouse gestures, and input devices.
icon: Keyboard
---
Configure how you interact with mangowm using flexible keybindings and input options.
<Cards>
<Card href="/docs/bindings/keys" title="Key Bindings" description="Keyboard shortcuts and modes" />
<Card href="/docs/bindings/mouse-gestures" title="Mouse Gestures" description="Touchpad and mouse gestures" />
</Cards>

222
docs/bindings/keys.md Normal file
View file

@ -0,0 +1,222 @@
---
title: Key Bindings
description: Define keyboard shortcuts and modes.
---
## Syntax
Key bindings follow this format:
```ini
bind[flags]=MODIFIERS,KEY,COMMAND,PARAMETERS
```
- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE` (combine with `+`, e.g. `SUPER+CTRL+ALT`).
- **Key**: Key name (from `xev` or `wev`) or keycode (e.g., `code:24` for `q`).
> **Info:** `bind` automatically converts keysym to keycode for comparison. This makes it compatible with all keyboard layouts, but the matching may not always be precise. If a key combination doesn't work on your keyboard layout, use a keycode instead (e.g., `code:24` instead of `q`).
### Flags
- `l`: Works even when screen is locked.
- `s`: Uses keysym instead of keycode to bind.
- `r`: Triggers on key release instead of press.
- `p`: Pass key event to client.
**Examples:**
```ini
bind=SUPER,Q,killclient
bindl=SUPER,L,spawn,swaylock
# Using keycode instead of key name
bind=ALT,code:24,killclient
# Combining keycodes for modifiers and keys
bind=code:64,code:24,killclient
bind=code:64+code:133,code:24,killclient
# Bind with no modifier
bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl set +5%
# Bind a modifier key itself as the trigger key
bind=alt,shift_l,switch_keyboard_layout
```
## Key Modes (Submaps)
You can divide key bindings into named modes. Rules:
1. Set `keymode=<name>` before a group of `bind` lines — those binds only apply in that mode.
2. If no `keymode` is set before a bind, it belongs to the `default` mode.
3. The special `common` keymode applies its binds **across all modes**.
Use `setkeymode` to switch modes, and `mmsg get keymode` to query the current mode.
```ini
# Binds in 'common' apply in every mode
keymode=common
bind=SUPER,r,reload_config
# Default mode bindings
keymode=default
bind=ALT,Return,spawn,foot
bind=SUPER,F,setkeymode,resize
# 'resize' mode bindings
keymode=resize
bind=NONE,Left,resizewin,-10,0
bind=NONE,Right,resizewin,+10,0
bind=NONE,Escape,setkeymode,default
```
### Single Modifier Key Binding
When binding a modifier key itself, use `NONE` for press and the modifier name for release:
```ini
# Trigger on press of Super key
bind=none,Super_L,spawn,rofi -show run
# Trigger on release of Super key
bindr=Super,Super_L,spawn,rofi -show run
```
## Dispatchers List
### Window Management
| Command | Param | Description |
| :--- | :--- | :--- |
| `killclient` | `force` | Close the focused window. If `force` is specified, sends `SIGKILL`. |
| `togglefloating` | - | Toggle floating state. |
| `toggle_all_floating` | - | Toggle all visible clients floating state. |
| `togglefullscreen` | - | Toggle fullscreen. |
| `togglefakefullscreen` | - | Toggle "fake" fullscreen (remains constrained). |
| `togglemaximizescreen` | - | Maximize window (keep decoration/bar). |
| `toggleglobal` | - | Pin window to all tags. |
| `toggle_render_border` | - | Toggle border rendering. |
| `centerwin` | - | Center the floating window. |
| `minimized` | - | Minimize window to scratchpad. |
| `restore_minimized` | - | Restore window from scratchpad. |
| `toggle_scratchpad` | - | Toggle scratchpad. |
| `toggle_named_scratchpad` | `appid,title,cmd` | Toggle named scratchpad. Launches app if not running, otherwise shows/hides it. |
### Focus & Movement
| Command | Param | Description |
| :--- | :--- | :--- |
| `focusid` | - | Focus window (can target any window via IPC: `mmsg dispatch focusid client,<id>`) |
| `focusdir` | `left/right/up/down` | Focus window in direction. |
| `focusstack` | `next/prev` | Cycle focus within the stack. |
| `focuslast` | - | Focus the previously active window. |
| `exchange_client` | `left/right/up/down` | Swap window with neighbor in direction. |
| `exchange_stack_client` | `next/prev` | Exchange window position in stack. |
| `zoom` | - | Swap focused window with Master. |
### Tags & Monitors
| Command | Param | Description |
| :--- | :--- | :--- |
| `view` | `-1/0/1-9` or `mask [,synctag]` | View tag. `-1` = previous tagset, `0` = all tags, `1-9` = specific tag, mask e.g. `1\|3\|5`. Optional `synctag` (0/1) syncs the action to all monitors. |
| `viewtoleft` | `[synctag]` | View previous tag. Optional `synctag` (0/1) syncs to all monitors. |
| `viewtoright` | `[synctag]` | View next tag. Optional `synctag` (0/1) syncs to all monitors. |
| `viewtoleft_have_client` | `[synctag]` | View left tag and focus client if present. Optional `synctag` (0/1). |
| `viewtoright_have_client` | `[synctag]` | View right tag and focus client if present. Optional `synctag` (0/1). |
| `viewcrossmon` | `tag,monitor_spec` | View specified tag on specified monitor. |
| `tag` | `1-9 [,synctag]` | Move window to tag. Optional `synctag` (0/1) syncs to all monitors. |
| `tagsilent` | `1-9` | Move window to tag without focusing it. |
| `tagtoleft` | `[synctag]` | Move window to left tag. Optional `synctag` (0/1). |
| `tagtoright` | `[synctag]` | Move window to right tag. Optional `synctag` (0/1). |
| `tagcrossmon` | `tag,monitor_spec` | Move window to specified tag on specified monitor. |
| `toggletag` | `0-9` | Toggle tag on window (0 means all tags). |
| `toggleview` | `1-9` | Toggle tag view. |
| `comboview` | `1-9` | View multi tags pressed simultaneously. |
| `focusmon` | `left/right/up/down/monitor_spec` | Focus monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). |
| `tagmon` | `left/right/up/down/monitor_spec,[keeptag]` | Move window to monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). `keeptag` is 0 or 1. |
### Layouts
| Command | Param | Description |
| :--- | :--- | :--- |
| `setlayout` | `name` | Switch to layout (e.g., `scroller`, `tile`). |
| `switch_layout` | - | Cycle through available layouts. |
| `incnmaster` | `+1/-1` | Increase/Decrease number of master windows. |
| `setmfact` | `+0.05` | Increase/Decrease master area size. |
| `set_proportion` | `float` | Set scroller window proportion (0.01.0). |
| `switch_proportion_preset` | - | Cycle proportion presets of scroller window. |
| `scroller_stack` | `left/right/up/down` | Move window inside/outside scroller stack by direction. |
| `incgaps` | `+/-value` | Adjust gap size. |
| `togglegaps` | - | Toggle gaps. |
| `dwindle_toggle_split_direction` | - | Toggle split direction in dwindle layout. |
| `dwindle_split_horizontal` | - | Set split window direction to horizontal in dwindle layout. |
| `dwindle_split_vertical` | - | Set split window direction to vertical in dwindle layout. |
### System
| Command | Param | Description |
| :--- | :--- | :--- |
| `spawn` | `cmd` | Execute a command. |
| `spawn_shell` | `cmd` | Execute shell command (supports pipes `\|`). |
| `spawn_on_empty` | `cmd,tagnumber` | Open command on empty tag. |
| `reload_config` | - | Hot-reload configuration. |
| `quit` | - | Exit mangowm. |
| `toggleoverview` | - | Toggle overview mode. |
| `togglejump` | - | Toggle overview with jump mode. |
| `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). |
| `destroy_all_virtual_output` | - | Destroy all virtual monitors. |
| `toggleoverlay` | - | Toggle overlay state for the focused window. |
| `toggle_trackpad_enable` | - | Toggle trackpad enable. |
| `setkeymode` | `mode` | Set keymode. |
| `switch_keyboard_layout` | `[index]` | Switch keyboard layout. Optional index (0, 1, 2...) to switch to specific layout. |
| `setoption` | `key,value` | Set config option temporarily. |
| `disable_monitor` | `monitor_spec` | Shutdown monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). |
| `enable_monitor` | `monitor_spec` | Power on monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). |
| `toggle_monitor` | `monitor_spec` | Toggle monitor power. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). |
| `chvt` | `1-9` | Change virtual terminal (tty, equivalent to using ctrl+alt+Fkeys) |
### Media Controls
> **Warning:** Some keyboards don't send standard media keys. Run `wev` and press your key to check the exact key name.
#### Brightness
Requires: `brightnessctl`
```ini
bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl s +2%
bind=SHIFT,XF86MonBrightnessUp,spawn,brightnessctl s 100%
bind=NONE,XF86MonBrightnessDown,spawn,brightnessctl s 2%-
bind=SHIFT,XF86MonBrightnessDown,spawn,brightnessctl s 1%
```
#### Volume
Requires: `wpctl` (WirePlumber)
```ini
bind=NONE,XF86AudioRaiseVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%+
bind=NONE,XF86AudioLowerVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%-
bind=NONE,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SINK@ toggle
bind=SHIFT,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SOURCE@ toggle
```
#### Playback
Requires: `playerctl`
```ini
bind=NONE,XF86AudioNext,spawn,playerctl next
bind=NONE,XF86AudioPrev,spawn,playerctl previous
bind=NONE,XF86AudioPlay,spawn,playerctl play-pause
```
### Floating Window Movement
| Command | Param | Description |
| :--- | :--- | :--- |
| `smartmovewin` | `left/right/up/down` | Move floating window by snap distance. |
| `smartresizewin` | `left/right/up/down` | Resize floating window by snap distance. |
| `movewin` | `(x,y)` | Move floating window. |
| `resizewin` | `(width,height)` | Resize window. |

4
docs/bindings/meta.json Normal file
View file

@ -0,0 +1,4 @@
{
"title": "Bindings & Input",
"pages": ["keys", "mouse-gestures"]
}

View file

@ -0,0 +1,116 @@
---
title: Mouse & Gestures
description: Configure mouse buttons, scrolling, gestures, and lid switches.
---
## Mouse Bindings
Assign actions to mouse button presses with optional modifier keys.
### Syntax
```ini
mousebind=MODIFIERS,BUTTON,COMMAND,PARAMETERS
```
- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE`. Combine with `+` (e.g., `SUPER+CTRL`).
- **Buttons**: Can be specified in one of the following ways:
- **Standard Names**: `btn_left`, `btn_right`, `btn_middle`, `btn_side`, `btn_extra`, `btn_forward`, `btn_back`, `btn_task`
- **Hardware Codes**: `code:NUMBER` (e.g., `code:272`, `code:273`, useful for binding non-standard or extra mouse buttons)
> **Warning:** When modifiers are set to `NONE`, only `btn_middle` works in normal mode. `btn_left` and `btn_right` only work in overview mode.
### Examples
```ini
# Window manipulation
mousebind=SUPER,btn_left,moveresize,curmove
mousebind=SUPER,btn_right,moveresize,curresize
mousebind=SUPER+CTRL,btn_right,killclient
mousebind=NONE,code:273,togglemaximizescreen,0
```
---
## Axis Bindings
Map scroll wheel movements to actions for workspace and window navigation.
### Syntax
```ini
axisbind=MODIFIERS,DIRECTION,COMMAND,PARAMETERS
```
- **Direction**: `UP`, `DOWN`, `LEFT`, `RIGHT`
### Examples
```ini
axisbind=SUPER,UP,viewtoleft_have_client
axisbind=SUPER,DOWN,viewtoright_have_client
```
---
## Gesture Bindings
Enable touchpad swipe gestures for navigation and window management.
### Syntax
```ini
gesturebind=MODIFIERS,DIRECTION,FINGERS,COMMAND,PARAMETERS
```
- **Direction**: `up`, `down`, `left`, `right`
- **Fingers**: `3` or `4`
> **Info:** Gestures require proper touchpad configuration. See [Input Devices](/docs/configuration/input) for touchpad settings like `tap_to_click` and `disable_while_typing`.
### Examples
```ini
# 3-finger: Window focus
gesturebind=none,left,3,focusdir,left
gesturebind=none,right,3,focusdir,right
gesturebind=none,up,3,focusdir,up
gesturebind=none,down,3,focusdir,down
# 4-finger: Workspace navigation
gesturebind=none,left,4,viewtoleft_have_client
gesturebind=none,right,4,viewtoright_have_client
gesturebind=none,up,4,toggleoverview
gesturebind=none,down,4,toggleoverview
```
---
## Switch Bindings
Trigger actions on hardware events like laptop lid open/close.
### Syntax
```ini
switchbind=FOLD_STATE,COMMAND,PARAMETERS
```
- **Fold State**: `fold` (lid closed), `unfold` (lid opened)
> **Warning:** Disable system lid handling in `/etc/systemd/logind.conf`:
>
> ```ini
> HandleLidSwitch=ignore
> HandleLidSwitchExternalPower=ignore
> HandleLidSwitchDocked=ignore
> ```
### Examples
```ini
switchbind=fold,spawn,swaylock -f -c 000000
switchbind=unfold,spawn,wlr-dpms on
```

View file

@ -0,0 +1,87 @@
---
title: Basic Configuration
description: Learn how to configure mangowm files, environment variables, and autostart scripts.
---
## Configuration File
mangowm uses a simple configuration file format. By default, it looks for a configuration file in `~/.config/mango/`.
1. **Locate Default Config**
A fallback configuration is provided at `/etc/mango/config.conf`. You can use this as a reference.
2. **Create User Config**
Copy the default config to your local config directory to start customizing.
```bash
mkdir -p ~/.config/mango
cp /etc/mango/config.conf ~/.config/mango/config.conf
```
3. **Launch with Custom Config (Optional)**
If you prefer to keep your config elsewhere, you can launch mango with the `-c` flag.
```bash
mango -c /path/to/your_config.conf
```
### Sub-Configuration
To keep your configuration organized, you can split it into multiple files and include them using the `source` keyword.
```ini
# Import keybindings from a separate file
source=~/.config/mango/bind.conf
# Relative paths work too
source=./theme.conf
# Optional: ignore if file doesn't exist (useful for shared configs)
source-optional=~/.config/mango/optional.conf
```
### Validate Configuration
You can check your configuration for errors without starting mangowm:
```bash
mango -c /path/to/config.conf -p
```
Use with `source-optional` for shared configs across different setups.
## Environment Variables
You can define environment variables directly within your config file. These are set before the window manager fully initializes.
> **Warning:** Environment variables defined here will be **reset** every time you reload the configuration.
```ini
env=QT_IM_MODULES,wayland;fcitx
env=XMODIFIERS,@im=fcitx
```
## Autostart
mangowm can automatically run commands or scripts upon startup. There are two modes for execution:
| Command | Behavior | Usage Case |
| :--- | :--- | :--- |
| `exec-once` | Runs **only once** when mangowm starts. | Status bars, Wallpapers, Notification daemons |
| `exec` | Runs **every time** the config is reloaded. | Scripts that need to refresh settings |
### Example Setup
```ini
# Start the status bar once
exec-once=waybar
# Set wallpaper
exec-once=swaybg -i ~/.config/mango/wallpaper/room.png
# Reload a custom script on config change
exec=bash ~/.config/mango/reload-settings.sh
```

View file

@ -0,0 +1,21 @@
---
title: Configuration
description: Configure mangowm with config files, environment variables, and autostart.
icon: Settings
---
Configure mangowm through config files, environment variables, and autostart.
<Cards>
<Card href="/docs/configuration/basics" title="Basics" description="Config files, env vars, exec-once, exec" />
<Card href="/docs/configuration/monitors" title="Monitors" description="Monitor settings and resolution" />
<Card href="/docs/configuration/input" title="Input" description="Keyboard, mouse, and touchpad" />
<Card href="/docs/configuration/xdg-portals" title="XDG Portals" description="File pickers and notifications" />
<Card href="/docs/configuration/miscellaneous" title="Miscellaneous" description="Additional options" />
</Cards>

161
docs/configuration/input.md Normal file
View file

@ -0,0 +1,161 @@
---
title: Input Devices
description: Configure keyboard layouts, mouse sensitivity, and touchpad gestures.
---
## Device Configuration
mangowm provides granular control over different input devices.
### Keyboard Settings
Control key repeat rates and layout rules.
| Setting | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `repeat_rate` | `int` | `25` | How many times a key repeats per second. |
| `repeat_delay` | `int` | `600` | Delay (ms) before a held key starts repeating. |
| `numlockon` | `0` or `1` | `0` | Enable NumLock on startup. |
| `xkb_rules_rules` | `string` | - | XKB rules file (e.g., `evdev`, `base`). Usually auto-detected. |
| `xkb_rules_model` | `string` | - | Keyboard model (e.g., `pc104`, `macbook`). |
| `xkb_rules_layout` | `string` | - | Keyboard layout code (e.g., `us`, `de`, `us,de`). |
| `xkb_rules_variant` | `string` | - | Layout variant (e.g., `dvorak`, `colemak`, `intl`). |
| `xkb_rules_options` | `string` | - | XKB options (e.g., `caps:escape`, `ctrl:nocaps`). |
**Example:**
```ini
repeat_rate=40
repeat_delay=300
numlockon=1
xkb_rules_layout=us,de
xkb_rules_variant=dvorak
xkb_rules_options=caps:escape,ctrl:nocaps
```
---
### Mouse Settings
Configuration for external mice.
| Setting | Default | Description |
| :--- | :--- | :--- |
| `mouse_natural_scrolling` | `0` | Invert scrolling direction. |
| `mouse_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). |
| `mouse_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). |
| `left_handed` | `0` | Swap left and right buttons. |
| `axis_scroll_factor` | `1.0` | Scroll factor for axis scroll speed (0.110.0). |
---
### Trackpad Settings
Specific settings for laptop touchpads. Some settings may require a relogin to take effect.
| Setting | Default | Description |
| :--- | :--- | :--- |
| `disable_trackpad` | `0` | Set to `1` to disable the trackpad entirely. |
| `tap_to_click` | `1` | Tap to trigger a left click. |
| `tap_and_drag` | `1` | Tap and hold to drag items. |
| `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). |
| `trackpad_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). |
| `trackpad_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). |
| `scroll_button` | `274` | The mouse button that use for scrolling(272 to 279).
| `scroll_method` | `1` | `1` (Two-finger), `2` (Edge), `4` (Button). |
| `click_method` | `1` | `1` (Button areas), `2` (Clickfinger). |
| `send_events_mode` | `0` | `0` (Enabled), `1` (Disabled), `2` (Disabled on external mouse). |
| `drag_lock` | `1` | Lock dragging after tapping. |
| `disable_while_typing` | `1` | Disable trackpad while typing. |
| `left_handed` | `0` | Swap left/right buttons. |
| `middle_button_emulation` | `0` | Emulate middle button. |
| `swipe_min_threshold` | `1` | Minimum swipe threshold when use gesture. |
| `button_map` | `0` | `0` (Left/right/middle), `1` (Left/middle/right). |
| `trackpad_scroll_factor` | `1.0` | Scroll factor for trackpad scroll speed (0.110.0). |
---
**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:
- `0` — Never send scroll events (no scrolling).
- `1` — Two-finger scrolling: send scroll events when two fingers are logically down on the device.
- `2` — Edge scrolling: send scroll events when a finger moves along the bottom or right edge.
- `4` — Button scrolling: send scroll events when a button is held and the device moves along a scroll axis.
- `click_method` values:
- `0` — No software click emulation.
- `1` — Button areas: use software-defined areas on the touchpad to generate button events.
- `2` — Clickfinger: the number of fingers determines which button is pressed.
- `mouse_accel_profile` or `trackpad_scroll_profile` values:
- `0` — No acceleration.
- `1` — Flat: no dynamic acceleration. Pointer speed = original input speed × (1 + `mouse_accel_speed`).
- `2` — Adaptive: slow movement results in less acceleration, fast movement results in more.
- `button_map` values:
- `0` — 1/2/3 finger tap maps to left / right / middle.
- `1` — 1/2/3 finger tap maps to left / middle / right.
- `send_events_mode` values:
- `0` — Send events from this device normally.
- `1` — Do not send events from this device.
- `2` — Disable this device when an external pointer device is plugged in.
---
---
## Keyboard Layout Switching
To bind multiple layouts and toggle between them, define the layouts in `xkb_rules_layout` and use `xkb_rules_options` to set a toggle key combination. Then bind `switch_keyboard_layout` to trigger a switch.
```ini
# Define two layouts: US QWERTY and US Dvorak
xkb_rules_layout=us,us
xkb_rules_variant=,dvorak
xkb_rules_options=grp:lalt_lshift_toggle
```
Or bind it manually to a key:
```ini
# Bind Alt+Shift_L to cycle keyboard layout
bind=alt,shift_l,switch_keyboard_layout
```
Use `mmsg get keyboardlayout` to query the current layout.
---
## Input Method Editor (IME)
To use Fcitx5 or IBus, set these environment variables in your config file.
> **Info:** These settings require a restart of the window manager to take effect.
**For Fcitx5:**
```ini
env=GTK_IM_MODULE,fcitx
env=QT_IM_MODULE,fcitx
env=QT_IM_MODULES,wayland;fcitx
env=SDL_IM_MODULE,fcitx
env=XMODIFIERS,@im=fcitx
env=GLFW_IM_MODULE,ibus
```
**For IBus:**
```ini
env=GTK_IM_MODULE,ibus
env=QT_IM_MODULE,ibus
env=XMODIFIERS,@im=ibus
```

View file

@ -0,0 +1,4 @@
{
"title": "Configuration",
"pages": ["basics", "monitors", "input", "xdg-portals", "miscellaneous"]
}

View file

@ -0,0 +1,52 @@
---
title: Miscellaneous
description: Advanced settings for XWayland, focus behavior, and system integration.
---
## System & Hardware
| Setting | Default | Description |
| :--- | :--- | :--- |
| `xwayland_persistence` | `1` | Keep XWayland running even when no X11 apps are open (reduces startup lag). |
| `syncobj_enable` | `0` | Enable `drm_syncobj` timeline support (helps with gaming stutter/lag). **Requires restart.** |
| `allow_lock_transparent` | `0` | Allow the lock screen to be transparent. |
| `allow_shortcuts_inhibit` | `1` | Allow shortcuts to be inhibited by clients. |
| `vrr` | - | Set via [monitor rule](/docs/configuration/monitors#monitor-rules). |
## Focus & Input
| Setting | Default | Description |
| :--- | :--- | :--- |
| `focus_on_activate` | `1` | Automatically focus windows when they request activation. |
| `sloppyfocus` | `1` | Focus follows the mouse cursor. |
| `warpcursor` | `1` | Warp the cursor to the center of the window when focus changes via keyboard. |
| `cursor_hide_timeout` | `0` | Hide the cursor after `N` seconds of inactivity (`0` to disable). |
| `cursor_hide_on_keypress` | `0` | Hide the cursor on keypress. |
| `drag_tile_to_tile` | `0` | Allow dragging a tiled window onto another to swap their positions. |
| `drag_tile_small` | `1` | Allow dragging a tiled window temporarily to small size.|
| `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 13: corners, 4: auto-detect). |
| `drag_warp_cursor` | `1` | Warp cursor when dragging windows to tile. |
| `axis_bind_apply_timeout` | `100` | Timeout (ms) for detecting consecutive scroll events for axis bindings. |
## Multi-Monitor & Tags
| Setting | Default | Description |
| :--- | :--- | :--- |
| `focus_cross_monitor` | `0` | Allow directional focus to cross monitor boundaries. |
| `exchange_cross_monitor` | `0` | Allow exchanging clients across monitor boundaries. |
| `focus_cross_tag` | `0` | Allow directional focus to cross into other tags. |
| `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. |
| `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. |
| `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. |
## Window Behavior
| Setting | Default | Description |
| :--- | :--- | :--- |
| `enable_floating_snap` | `0` | Snap floating windows to edges or other windows. |
| `snap_distance` | `30` | Max distance (pixels) to trigger floating snap. |
| `no_border_when_single` | `0` | Remove window borders when only one window is visible on the tag. |
| `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. |
| `tag_carousel` | `0` | Enable tag carousel (cycling through tags). |
| `drag_tile_refresh_interval` | `8.0` | Interval (1.016.0) to refresh tiled window resize during drag. Too small may cause application lag. |
| `drag_floating_refresh_interval` | `8.0` | Interval (1.016.0) to refresh floating window resize during drag. Too small may cause application lag. |

View file

@ -0,0 +1,278 @@
---
title: Monitors
description: Manage display outputs, resolution, scaling, and tearing.
---
## Monitor Rules
You can configure each display output individually using the `monitorrule` keyword.
**Syntax:**
```ini
monitorrule=name:Values,Parameter:Values,Parameter:Values
```
> **Info:** If any of the matching fields (`name`, `make`, `model`, `serial`) are set, **all** of the set ones must match to be considered a match. Use `wlr-randr` to get your monitor's name, make, model, and serial.
### Parameters
| Parameter | Type | Values | Description |
| :--- | :--- | :--- | :--- |
| `name` | string | Any | Match by monitor name (supports regex) |
| `make` | string | Any | Match by monitor manufacturer |
| `model` | string | Any | Match by monitor model |
| `serial` | string | Any | Match by monitor serial number |
| `width` | integer | 0-9999 | Monitor width |
| `height` | integer | 0-9999 | Monitor height |
| `refresh` | float | 0.001-9999.0 | Monitor refresh rate |
| `x` | integer | 0-99999 | X position |
| `y` | integer | 0-99999 | Y position |
| `scale` | float | 0.01-100.0 | Monitor scale |
| `vrr` | integer | 0, 1 | Enable variable refresh rate |
| `rr` | integer | 0-7 | Monitor transform |
| `custom` | integer | 0, 1 | Enable custom mode (not supported on all displays — may cause black screen) |
### Transform Values
| Value | Rotation |
| :--- | :--- |
| `0` | No transform |
| `1` | 90° counter-clockwise |
| `2` | 180° counter-clockwise |
| `3` | 270° counter-clockwise |
| `4` | 180° vertical flip |
| `5` | Flip + 90° counter-clockwise |
| `6` | Flip + 180° counter-clockwise |
| `7` | Flip + 270° counter-clockwise |
> **Critical:** If you use XWayland applications, **never use negative coordinates** for your monitor positions. This is a known XWayland bug that causes click events to malfunction. Always arrange your monitors starting from `0,0` and extend into positive coordinates.
> **Note:** that "name" is a regular expression. If you want an exact match, you need to add `^` and `$` to the beginning and end of the expression, for example, `^eDP-1$` matches exactly the string `eDP-1`.
### Examples
```ini
# Laptop display: 1080p, 60Hz, positioned at origin
monitorrule=name:^eDP-1$,width:1920,height:1080,refresh:60,x:0,y:10
# Match by make and model instead of name
monitorrule=make:Chimei Innolux Corporation,model:0x15F5,width:1920,height:1080,refresh:60,x:0,y:0
# Virtual monitor with pattern matching
monitorrule=name:HEADLESS-.*,width:1920,height:1080,refresh:60,x:1926,y:0,scale:1,rr:0,vrr:0
```
---
## Monitor Spec Format
Several commands (`focusmon`, `tagmon`, `disable_monitor`, `enable_monitor`, `toggle_monitor`, `viewcrossmon`, `tagcrossmon`) accept a **monitor_spec** string to identify a monitor.
**Format:**
```text
name:xxx&&make:xxx&&model:xxx&&serial:xxx
```
- Any field can be omitted and there is no order requirement.
- If all fields are omitted, the string is treated as the monitor name directly (e.g., `eDP-1`).
- Use `wlr-randr` to find your monitor's name, make, model, and serial.
**Examples:**
```bash
# By name (shorthand)
mmsg dispatch toggle_monitor,eDP-1
# By make and model
mmsg dispatch toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5
# By serial
mmsg dispatch toggle_monitor,serial:12345678
```
---
## Tearing (Game Mode)
Tearing allows games to bypass the compositor's VSync for lower latency.
| Setting | Default | Description |
| :--- | :--- | :--- |
| `allow_tearing` | `0` | Global tearing control: `0` (Disable), `1` (Enable), `2` (Fullscreen only). |
### Configuration
**Enable Globally:**
```ini
allow_tearing=1
```
**Enable per Window:**
Use a window rule to force tearing for specific games.
```ini
windowrule=force_tearing:1,title:vkcube
```
### Tearing Behavior Matrix
| `force_tearing` \ `allow_tearing` | DISABLED (0) | ENABLED (1) | FULLSCREEN_ONLY (2) |
| :--- | :--- | :--- | :--- |
| **UNSPECIFIED** (0) | Not Allowed | Follows tearing_hint | Only fullscreen follows tearing_hint |
| **ENABLED** (1) | Not Allowed | Allowed | Only fullscreen allowed |
| **DISABLED** (2) | Not Allowed | Not Allowed | Not Allowed |
### Graphics Card Compatibility
> **Warning:** Some graphics cards require setting the `WLR_DRM_NO_ATOMIC` environment variable before mango starts to successfully enable tearing.
Add this to `/etc/environment` and reboot:
```bash
WLR_DRM_NO_ATOMIC=1
```
Or run mango with the environment variable:
```bash
WLR_DRM_NO_ATOMIC=1 mango
```
---
## GPU Compatibility
If mango cannot display correctly or shows a black screen, try selecting a specific GPU:
```bash
# Use a single GPU
WLR_DRM_DEVICES=/dev/dri/card1 mango
# Use multiple GPUs
WLR_DRM_DEVICES=/dev/dri/card0:/dev/dri/card1 mango
```
Some GPUs have compatibility issues with `syncobj_enable=1` — it may crash apps like `kitty` that use syncobj. Set `WLR_DRM_NO_ATOMIC=1` in `/etc/environment` and reboot to resolve this.
---
## Power Management
You can control monitor power using the `mmsg` IPC tool.
> Notice: This command does not remove the monitor, it only turns it off.
> if you want completely remove monitor, just use `wlr-randr`
```bash
# Turn off
mmsg dispatch disable_monitor,eDP-1
# Turn on
mmsg dispatch enable_monitor,eDP-1
# Toggle
mmsg dispatch toggle_monitor,eDP-1
```
You can also use `wlr-randr` for monitor management:
```bash
# remove a monitor
wlr-randr --output eDP-1 --off
# add a monitor
wlr-randr --output eDP-1 --on
# Show all monitors spec
wlr-randr
```
---
## Screen Scale
### Without Global Scale (Recommended)
- If you do not use XWayland apps, you can use monitor rules or `wlr-randr` to set a global monitor scale.
- If you are using XWayland apps, it is not recommended to set a global monitor scale.
You can set scale like this, for example with a 1.4 factor.
**Dependencies:**
```bash
yay -S xorg-xrdb
yay -S xwayland-satellite
```
**In config file:**
```ini
env=QT_AUTO_SCREEN_SCALE_FACTOR,1
env=QT_WAYLAND_FORCE_DPI,140
```
**In autostart:**
```bash
echo "Xft.dpi: 140" | xrdb -merge
gsettings set org.gnome.desktop.interface text-scaling-factor 1.4
```
**Edit autostart for XWayland:**
```bash
# Start xwayland
/usr/sbin/xwayland-satellite :11 &
# Apply scale 1.4 for xwayland
sleep 0.5s && echo "Xft.dpi: 140" | xrdb -merge
```
### Using xwayland-satellite to Prevent Blurry XWayland Apps
If you use fractional scaling, you can use `xwayland-satellite` to automatically scale XWayland apps to prevent blurriness, for example with a scale of 1.4.
**Dependencies:**
```bash
yay -S xwayland-satellite
```
**In config file:**
```ini
env=DISPLAY,:2
exec-once=xwayland-satellite :2
monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:0,scale:1.4,vrr:0,rr:0
```
> **Warning:** Use a `DISPLAY` value other than `:1` to avoid conflicting with mangowm.
---
## Virtual Monitors
You can create and manage virtual displays through IPC commands:
```bash
# Create virtual output
mmsg dispatch create_virtual_output
# Destroy all virtual outputs
mmsg dispatch destroy_all_virtual_output
```
You can configure virtual monitors using `wlr-randr`:
```bash
# Show all monitors
wlr-randr
# Configure virtual monitor
wlr-randr --output HEADLESS-1 --pos 1921,0 --scale 1 --custom-mode 1920x1080@60Hz
```
Virtual monitors can be used for screen sharing with tools like [Sunshine](https://github.com/LizardByte/Sunshine) and [Moonlight](https://github.com/moonlight-stream/moonlight-android), allowing other devices to act as extended monitors.

View file

@ -0,0 +1,76 @@
---
title: XDG Portals
description: Set up screen sharing, clipboard, keyring, and file pickers using XDG portals.
---
## Portal Configuration
You can customize portal settings via the following paths:
- **User Configuration (Priority):** `~/.config/xdg-desktop-portal/mango-portals.conf`
- **System Fallback:** `/usr/share/xdg-desktop-portal/mango-portals.conf`
> **Warning:** If you previously added `dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=wlroots` to your config, remove it. Mango now handles this automatically.
## Screen Sharing
To enable screen sharing (OBS, Discord, WebRTC), you need `xdg-desktop-portal-wlr`.
1. **Install Dependencies**
`pipewire`, `pipewire-pulse`, `xdg-desktop-portal-wlr`
2. **Optional: Add to autostart**
In some situations the portal may not start automatically. You can add this to your autostart script to ensure it launches:
```bash
/usr/lib/xdg-desktop-portal-wlr &
```
3. **Restart your computer** to apply changes.
### Known Issues
- **Window screen sharing:** Some applications may have issues sharing individual windows. See [#184](https://github.com/mangowm/mango/pull/184) for workarounds.
- **Screen recording lag:** If you experience stuttering during screen recording, see [xdg-desktop-portal-wlr#351](https://github.com/emersion/xdg-desktop-portal-wlr/issues/351).
## Clipboard Manager
Use `cliphist` to manage clipboard history.
**Dependencies:** `wl-clipboard`, `cliphist`, `wl-clip-persist`
**Autostart Config:**
```bash
# Keep clipboard content after app closes
wl-clip-persist --clipboard regular --reconnect-tries 0 &
# Watch clipboard and store history
wl-paste --type text --watch cliphist store &
```
## GNOME Keyring
If you need to store passwords or secrets (e.g., for VS Code or Minecraft launchers), install `gnome-keyring`.
**Configuration:**
Add the following to `~/.config/xdg-desktop-portal/mango-portals.conf`:
```ini
[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=wlr
org.freedesktop.impl.portal.Screenshot=wlr
org.freedesktop.impl.portal.Secret=gnome-keyring
org.freedesktop.impl.portal.Inhibit=none
```
## File Picker (File Selector)
**Dependencies:** `xdg-desktop-portal`, `xdg-desktop-portal-gtk`
Reboot your computer once to apply.

100
docs/faq.md Normal file
View file

@ -0,0 +1,100 @@
---
title: FAQ
description: Frequently asked questions and troubleshooting.
---
### How do I arrange tiled windows with my mouse?
You can enable the `drag_tile_to_tile` option in your config. This allows you to drag a tiled window onto another to swap them.
```ini
drag_tile_to_tile=1
```
---
### Why is my background blurry or why does blur look wrong?
Blur applies to the transparent areas of windows. To disable it entirely, set `blur=0`.
If you are experiencing **performance issues with blur**, make sure `blur_optimized=1` (the default). This caches the wallpaper as the blur background, which is much cheaper on the GPU:
```ini
blur_optimized=1
```
---
### Blur shows my wallpaper instead of the real background content
This is expected behavior when `blur_optimized=1` (the default). The optimizer caches the wallpaper to reduce GPU load — windows will blur against the wallpaper rather than the actual content stacked beneath them.
If you want blur to composite against the true background (i.e., show whatever is actually behind the window), set:
```ini
blur_optimized=0
```
> **Warning:** Disabling `blur_optimized` significantly increases GPU consumption and may cause rendering lag, especially on lower-end hardware.
---
### My games are lagging or stuttering
Try enabling **SyncObj** timeline support.
```ini
syncobj_enable=1
```
---
### My games have high input latency
You can enable **Tearing** (similar to VSync off).
First, enable it globally:
```ini
allow_tearing=1
```
Then force it for your specific game:
```ini
windowrule=force_tearing:1,title:Counter-Strike 2
```
> **Warning:** Some graphics cards require setting `WLR_DRM_NO_ATOMIC=1` before mango starts for tearing to work. Add it to `/etc/environment` and reboot, or launch mango with `WLR_DRM_NO_ATOMIC=1 mango`. See [Monitors — Tearing](/docs/configuration/monitors#tearing-game-mode) for details.
---
### How do I use pipes `|` in spawn commands?
The standard `spawn` command does not support shell pipes directly. You must use `spawn_shell` instead.
```ini
bind=SUPER,P,spawn_shell,echo "hello" | rofi -dmenu
```
---
### Certain key combinations do not work on my keyboard layout.
`bind` automatically converts keysym to keycode, which is compatible with most layouts but can sometimes be imprecise. If a key combination is not triggering, use the **keycode** directly instead of the key name.
Run `wev` and press the key to find its keycode, then use it in your bind:
```ini
# Instead of:
bind=ALT,q,killclient
# Use the keycode (e.g., code:24 = q on most layouts):
bind=ALT,code:24,killclient
```
You can also use `binds` (the `s` flag) to match by keysym instead of keycode:
```ini
binds=ALT,q,killclient
```

42
docs/index.md Normal file
View file

@ -0,0 +1,42 @@
---
title: Introduction
description: A lightweight and feature-rich Wayland compositor based on dwl.
---
**mango** is a Wayland compositor based on [dwl](https://codeberg.org/dwl/dwl/). It aims to be as lightweight as `dwl` and can be built completely within a few seconds, without compromising on functionality.
> **Philosophy:** **Lightweight & Fast**: mango is designed to be minimal yet functional. It compiles in seconds and offers a robust set of features out of the box.
## Feature Highlights
Beyond basic window management, mangowm provides a rich set of features designed for a modern Wayland experience.
- **[Animations](/docs/visuals/animations)** — Smooth, customizable animations for opening, moving, closing windows and tag switching.
- **[Layouts](/docs/window-management/layouts)** — Supports Scroller, Master-Stack, Monocle, Grid, Deck, and more, with per-tag layouts.
- **[Visual Effects](/docs/visuals/effects)** — Built-in blur, shadows, corner radius, and opacity effects powered by scenefx.
- **[IPC & Scripting](/docs/ipc)** — Control the compositor externally with robust IPC support for custom scripts and widgets.
## Additional Features
- **XWayland Support** — Excellent compatibility for legacy X11 applications.
- **Tag System** — Uses tags instead of workspaces, allowing separate window layouts for each tag.
- **Input Methods** — Great support for text input v2/v3 (Fcitx5, IBus).
- **Window States** — Rich states including swallow, minimize, maximize, fullscreen, and overlay.
- **Hot-Reload Config** — Simple external configuration that supports hot-reloading without restarting.
- **Scratchpads** — Support for both Sway-like and named scratchpads.
## Community
- **[Join the mangowm Discord](https://discord.gg/CPjbDxesh5)** — Chat with the community, get support, share your setup, and stay updated with the latest mangowm news.
- **[Join the GitHub Discussions](https://github.com/mangowm/mango/discussions)** — Ask questions, request features, report issues, or share ideas directly with contributors and other users.
## Acknowledgements
This project is built upon the hard work of several open-source projects:
- **[wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)** — Implementation of the Wayland protocol.
- **[mwc](https://github.com/nikoloc/mwc)** — Basal window animation reference.
- **[dwl](https://codeberg.org/dwl/dwl)** — Basal dwl features.
- **[sway](https://github.com/swaywm/sway)** — Sample implementation of the Wayland protocol.
- **[scenefx](https://github.com/wlrfx/scenefx)** — Library to simplify adding window effects.

312
docs/installation.md Normal file
View file

@ -0,0 +1,312 @@
---
title: Installation
description: Install mangowm on AerynOS, Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source.
---
## Package Installation
mangowm is available as a pre-built package on several distributions. Choose your distribution below.
---
### AerynOS
mangowm is available in the **AerynOS package repository**.
You can install it using the `moss` package manager:
```bash
sudo moss install mangowm
```
* The Default config will be located at `/usr/share/defaults/mango`.
---
### Arch Linux
mangowm is available in the **Arch User Repository (AUR)**.
You can install it using an AUR helper like `yay` or `paru`:
```bash
yay -S mangowm-git
```
> **Tip:** This package pulls the latest git version, ensuring you have the newest features and fixes.
---
### Fedora
The package is in the third-party **Terra repository**. First, add the Terra Repository.
> **Warning:** Both commands require root privileges. Use `sudo` if needed.
```bash
dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release
```
Then, install the package:
```bash
dnf install mangowm
```
---
### Gentoo
The package is hosted in the community-maintained **GURU** repository.
1. **Add the GURU repository**
```bash
emerge --ask --verbose eselect-repository
eselect repository enable guru
emerge --sync guru
```
2. **Unmask packages**
Add the required packages to your `package.accept_keywords` file:
- `gui-libs/scenefx`
- `gui-wm/mangowm`
3. **Install mango**
```bash
emerge --ask --verbose gui-wm/mangowm
```
---
### Guix System
The package definition is described in the source repository.
1. **Add mango channel**
Add to `$HOME/.config/guix/channels.scm`:
```scheme
(cons (channel
(name 'mangowm)
(url "https://github.com/mangowm/mango.git")
(branch "main"))
%default-channels)
```
2. **Install**
After running `guix pull`, you can install mangowm:
```bash
guix install mangowm
```
Or add it to your system configuration using the mangowm module:
```scheme
(use-modules (mangowm))
(packages (cons*
mangowm-git
... ;; Other packages
%base-packages))
```
> **Tip:** For more information, see the [Guix System documentation](https://guix.gnu.org/manual/devel/en/html_node/Channels.html).
---
### NixOS
The repository provides a Flake with a NixOS module.
1. **Add flake input**
```nix
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
mangowm = {
url = "github:mangowm/mango";
inputs.nixpkgs.follows = "nixpkgs";
};
# other inputs ...
};
}
```
2. **Import the NixOS module**
**Option A — Import in `configuration.nix`:**
```nix
# configuration.nix (or any other file that you import)
{inputs, ...}: {
imports = [
inputs.mangowm.nixosModules.mango
# .. other imports ...
];
# ...
}
```
**Option B — Import directly in flake:**
```nix
# flake.nix
{
# ...
outputs = { self, nixpkgs, mangowm, ...}@inputs: let
inherit (nixpkgs) lib;
# ...
in {
nixosConfigurations.YourHostName = lib.nixosSystem {
modules = [
mangowm.nixosModules.mango # or inputs.mangowm.nixosModules.mango
# other imports ...
];
};
}
}
```
3. **Enable the module**
```nix
# configuration.nix (or any other file that you import)
{
programs.mango.enable = true;
}
```
4. **Start mango on login**
The following are common examples. Refer to the official NixOS documentation for full configuration options.
**Option A — greetd:** Autologin on first start; login screen after logout.
```nix
services.greetd = {
enable = true;
settings = {
initial_session = {
command = "mango";
user = "your-username"; # auto-login on first start, no password required
};
default_session = {
command = "${pkgs.greetd.tuigreet}/bin/tuigreet --cmd mango";
user = "greeter";
};
};
};
```
**Option B — Display manager autologin:** Autologin via an existing display manager (e.g. SDDM, GDM). [`addLoginEntry`](/docs/nix-options#addloginentry) (default: `true`) automatically registers mango as a session.
```nix
services.displayManager = {
defaultSession = "mango"; # derived from mango.desktop filename
autoLogin = {
enable = true;
user = "your-username";
};
};
```
**Option C — getty autologin:** No login screen, boots directly into mango on TTY1.
For bash/zsh:
```nix
services.getty.autologinUser = "your-username";
environment.loginShellInit = ''
[ "$(tty)" = /dev/tty1 ] && exec mango
'';
```
For fish:
```nix
services.getty.autologinUser = "your-username";
programs.fish.loginShellInit = ''
if test (tty) = /dev/tty1
exec mango
end
'';
```
5. **All available options**
See [Nix Module Options](/docs/nix-options) for the full list of NixOS and Home Manager options.
---
### PikaOS
mangowm is available in the **PikaOS package repository**.
You can install it using the `pikman` package manager:
```bash
pikman install mangowm
```
---
## Building from Source
If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source.
> **Info:** Ensure the following dependencies are installed before proceeding:
>
> - `wayland`
> - `wayland-protocols`
> - `libinput`
> - `libdrm`
> - `libxkbcommon`
> - `pixman`
> - `libdisplay-info`
> - `libliftoff`
> - `hwdata`
> - `seatd`
> - `pcre2`
> - `pango`
> - `cjson`
> - `pixman`
> - `xorg-xwayland`
> - `libxcb`
You will need to build `wlroots` and `scenefx` manually as well.
1. **Build wlroots**
Clone and install the specific version required (check README for latest version).
```bash
git clone -b 0.19.3 https://gitlab.freedesktop.org/wlroots/wlroots.git
cd wlroots
meson build -Dprefix=/usr
sudo ninja -C build install
```
2. **Build scenefx**
This library handles the visual effects.
```bash
git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git
cd scenefx
meson build -Dprefix=/usr
sudo ninja -C build install
```
3. **Build mangowm**
Finally, compile the compositor itself.
```bash
git clone https://github.com/mangowm/mango.git
cd mango
meson build -Dprefix=/usr
sudo ninja -C build install
```

78
docs/ipc.md Normal file
View file

@ -0,0 +1,78 @@
---
title: IPC
description: Control mangowm programmatically using mmsg.
---
# mmsg(1) - User Manual
`mmsg` is the command-line interface for the Mango compositor's Inter-Process Communication (IPC) system. It allows users and scripts to query the state of the compositor or subscribe to real-time events.
## SYNOPSIS
`mmsg <command> [arguments...]`
## DESCRIPTION
`mmsg` acts as a client that connects to the Mango compositor via a Unix domain socket defined by the `MANGO_INSTANCE_SIGNATURE` environment variable. It supports two primary modes of operation:
1. **One-shot Request (`get`)**: Sends a query to the compositor, receives a single JSON response, and terminates.
2. **Persistent Stream (`watch`)**: Subscribes to a specific state, receiving continuous JSON updates whenever that state changes.
## ENVIRONMENT VARIABLES
* **`MANGO_INSTANCE_SIGNATURE`**: Must be set to the path of the Unix socket created by the running Mango instance. This is typically handled automatically when running `mmsg` from within a terminal spawned by the compositor.
## COMMANDS
### GET (One-Shot Queries)
| Command | Description |
| :--- | :--- |
| `get version` | Returns the current version of the compositor. |
| `get keymode` | Returns the current active keyboard mode (e.g., normal, insert). |
| `get keyboardlayout` | Returns the active XKB layout (abbreviated). |
| `get monitor <name>` | Returns full JSON details for a specific monitor. |
| `get focusing-client` | Returns full JSON details for the client currently in focus. |
| `get client <id>` | Returns full JSON details for a client with the given ID. |
| `get tag <mon> <idx>` | Queries status of a specific tag on a monitor. |
| `get tags <mon>` | Returns a JSON object containing the status of all tags on a monitor. |
| `get all-clients` | Returns a JSON array of all active clients. |
| `get all-monitors` | Returns a JSON array of all connected monitors. |
| `get all-tags` | Returns a JSON object containing the status of all tags. |
| `get last_open_surface [<mon>]` | Returns the last focused surface name for a monitor,if the mon not set, it will get current monitor. |
*Example:*
```bash
mmsg get monitor eDP-1
mmsg get all-clients
mmsg get all-monitors
```
### WATCH (Event Subscription)
Subscribes the client to real-time updates. When the state changes, the server pushes a new JSON object to the output stream.
* `watch monitor <name>`
* `watch focusing-client`
* `watch client <id>`
* `watch tags <mon_name>`
* `watch all-monitors`
* `watch all-tags`
* `watch all-clients`
* `watch keymode`
* `watch keyboardlayout`
* `watch last_open_surface [<mon_name>]`
*Example:*
```bash
# watch all monitors
mmsg watch all-monitors
# watch all tags
mmsg watch all-tags
```
### DISPATCH
Allows sending commands to the compositor to alter its state.
* `dispatch <func_name>,[args...] [client,<id>]`
*Example:*
```bash
# operate specific client by id
mmsg dispatch exchange_client,left client,375
# operate current client
mmsg dispatch exchange_client,left
````

22
docs/meta.json Normal file
View file

@ -0,0 +1,22 @@
{
"title": "git (latest)",
"description": "Latest development",
"root": true,
"pages": [
"---Getting Started---",
"index",
"installation",
"quick-start",
"---Configuration---",
"configuration",
"visuals",
"window-management",
"bindings",
"---Examples---",
"screenshot",
"---Reference---",
"nix-options",
"ipc",
"faq"
]
}

519
docs/nix-options.md Normal file
View file

@ -0,0 +1,519 @@
---
title: Nix Module Options
description: NixOS and Home Manager configuration options for mangowm.
---
> **Note:** This document is automatically generated from the Nix module source code.
## NixOS
**System-level options via `programs.mango`.**
### enable
Whether to enable mango, a wayland compositor based on dwl\.
*Type:*
boolean
*Default:*
```nix
false
```
*Example:*
```nix
true
```
*Declared by:*
- [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix)
### package
The mango package to use
*Type:*
package
*Default:*
```nix
<derivation mango-nightly>
```
*Declared by:*
- [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix)
### addLoginEntry
Whether to add a login entry to the display manager for mango\. Only has effect if a display manager is configured (e\.g\. SDDM, GDM via ` services.displayManager `)\.
*Type:*
boolean
*Default:*
```nix
true
```
*Declared by:*
- [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix)
## Home Manager
**Configure mangowm declaratively via `wayland.windowManager.mango`.**
### enable
Whether to enable mangowm, a Wayland compositor based on dwl\.
*Type:*
boolean
*Default:*
```nix
false
```
*Declared by:*
- [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix)
### package
The mango package to use
*Type:*
package
*Default:*
```nix
<derivation mango-nightly>
```
*Declared by:*
- [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix)
### autostart_sh
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:*
strings concatenated with “\\n”
*Default:*
```nix
""
```
*Example:*
```nix
''
waybar &
dunst &
''
```
*Declared by:*
- [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix)
### bottomPrefixes
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\.
*Type:*
list of string
*Default:*
```nix
[ ]
```
*Example:*
```nix
[
"source"
]
```
*Declared by:*
- [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix)
### extraConfig
Extra configuration lines to add to ` ~/.config/mango/config.conf `\.
This is useful for advanced configurations that dont fit the structured
settings format, or for options that arent yet supported by the module\.
*Type:*
strings concatenated with “\\n”
*Default:*
```nix
""
```
*Example:*
```nix
''
# Advanced config that doesn't fit structured format
special_option = 1
''
```
*Declared by:*
- [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix)
### settings
Mango configuration written in Nix\. Entries with the same key
should be written as lists\. Variables and colors names should be
quoted\. See [https://mangowm\.github\.io/docs](https://mangowm\.github\.io/docs) for more examples\.
**Note:** This option uses a structured format that is converted to Mangos
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\.
*Type:*
Mango configuration value
*Default:*
```nix
{ }
```
*Example:*
```nix
{
# 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"
];
};
};
}
```
*Declared by:*
- [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix)
### systemd\.enable
Whether to enable ` mango-session.target ` on
mango startup\. This links to
` graphical-session.target `\.
Some important environment variables will be imported to systemd
and dbus user environment before reaching the target, including
- ` DISPLAY `
- ` WAYLAND_DISPLAY `
- ` XDG_CURRENT_DESKTOP `
- ` XDG_SESSION_TYPE `
- ` NIXOS_OZONE_WL `
You can extend this list using the ` systemd.variables ` option\.
*Type:*
boolean
*Default:*
```nix
true
```
*Example:*
```nix
false
```
*Declared by:*
- [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix)
### systemd\.extraCommands
Extra commands to run after D-Bus activation\.
*Type:*
list of string
*Default:*
```nix
[
"systemctl --user reset-failed"
"systemctl --user start mango-session.target"
]
```
*Declared by:*
- [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix)
### systemd\.variables
Environment variables imported into the systemd and D-Bus user environment\.
*Type:*
list of string
*Default:*
```nix
[
"DISPLAY"
"WAYLAND_DISPLAY"
"XDG_CURRENT_DESKTOP"
"XDG_SESSION_TYPE"
"NIXOS_OZONE_WL"
"XCURSOR_THEME"
"XCURSOR_SIZE"
]
```
*Example:*
```nix
[
"--all"
]
```
*Declared by:*
- [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix)
### systemd\.xdgAutostart
Whether to enable autostart of applications using
` systemd-xdg-autostart-generator(8) `
\.
*Type:*
boolean
*Default:*
```nix
false
```
*Example:*
```nix
true
```
*Declared by:*
- [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix)
### topPrefixes
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\.
*Type:*
list of string
*Default:*
```nix
[ ]
```
*Example:*
```nix
[
"source"
]
```
*Declared by:*
- [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix)

88
docs/quick-start.md Normal file
View file

@ -0,0 +1,88 @@
---
title: Quick Start
description: Basic configuration and first steps with mangowm.
---
Now that you have mangowm installed, let's get your environment set up.
## Initial Setup
1. **Create Configuration Directory**
mangowm looks for configuration files in `~/.config/mango/`.
```bash
mkdir -p ~/.config/mango
```
2. **Copy Default Config**
A default configuration file is provided at `/etc/mango/config.conf`. Copy it to your local directory to start customizing.
```bash
cp /etc/mango/config.conf ~/.config/mango/config.conf
```
3. **Launch mangowm**
You can now start the compositor from your TTY.
```bash
mango
```
Optional: To specify a custom config file path:
```bash
mango -c /path/to/your/config.conf
```
## Essential Keybindings
mangowm uses the following keybinds by default:
| Key Combination | Action |
| :--- | :--- |
| `Alt` + `Return` | Open Terminal (defaults to `foot`) |
| `Alt` + `Space` | Open Launcher (defaults to `rofi`) |
| `Alt` + `Q` | Close (Kill) the active window |
| `Super` + `M` | Quit mangowm |
| `Super` + `F` | Toggle Fullscreen |
| `Alt` + `Arrow Keys` | Move focus (Left, Right, Up, Down) |
| `Ctrl` + `1-9` | Switch to Tag 1-9 |
| `Alt` + `1-9` | Move window to Tag 1-9 |
> **Warning:** Some default bindings rely on specific tools like `foot` (terminal) and `rofi` (launcher). Ensure you have them installed or update your `config.conf` to use your preferred alternatives.
## Recommended Tools
To get a fully functional desktop experience, we recommend installing the following components:
| Category | Recommended Tools |
| :--- | :--- |
| Application Launcher | rofi, bemenu, wmenu, fuzzel |
| Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty |
| Status Bar | waybar, eww, quickshell, ags |
| Desktop Shell | Noctalia, DankMaterialShell |
| Wallpaper Setup | awww(swww), swaybg |
| Notification Daemon | swaync, dunst, mako |
| Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk |
| Clipboard | wl-clipboard, wl-clip-persist, cliphist |
| Gamma Control / Night Light | wlsunset, gammastep |
| Miscellaneous | xfce-polkit, wlogout |
## Example Configuration
Check out the [example configuration](https://github.com/DreamMaoMao/mango-config) by the creator of mangowm, including complete setups for mangowm, Waybar, Rofi, and more.
```bash
git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango
```
## Next Steps
Now that you are up and running, dive deeper into customizing mangowm:
- [Configure Monitors](/docs/configuration/monitors) — Set up resolution, scaling, and multi-monitor layouts.
- [Window Rules](/docs/window-management/rules#window-rules) — Define how specific applications should behave.
- [Appearance](/docs/visuals/theming) — Customize colors, borders, gaps, and effects.

213
docs/screenshot.md Normal file
View file

@ -0,0 +1,213 @@
---
title: Screenshots
description: Example screenshot keybindings and capture workflows for mangowm.
---
mangowm does not include a built-in screenshot tool. This keeps the compositor lean.
Instead, compose your own workflow from small Wayland utilities and bind them to keys;
| Tool | Purpose |
| :--- | :--- |
| [`grim`](https://github.com/emersion/grim) | Capture the screen or a region to a file |
| [`slurp`](https://github.com/emersion/slurp) | Interactively select a region for `grim` |
| [`wl-copy`](https://github.com/bugaevc/wl-clipboard) | Copy screenshots directly to the clipboard |
| [`satty`](https://github.com/gabm/Satty) | Annotate screenshots before saving |
| [`wayfreeze`](https://github.com/Jappie3/wayfreeze) | Freeze the screen before capture |
Install the required with your package manager or from source.
`grim` writes to the file path you give it, but **will not create missing directories**. Create one first:
```bash
mkdir -p ~/Pictures/Screenshots
```
Any directory works. `~/Pictures/Screenshots/` is just a convention.
## Quick Binds
Short, single-step commands can be placed directly in `config.conf` with `spawn_shell`.
No script file needed.
### Fullscreen
Captures the entire display.
```ini
bind=NONE,Print,spawn_shell,grim $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png
```
### Region
Select an area with `slurp` before capturing.
```ini
bind=SHIFT,Print,spawn_shell,g=$(slurp -d) && [ -n "$g" ] && grim -g "$g" $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png
```
### Pointer
Captures the full screen including the cursor.
```ini
bind=ALT,Print,spawn_shell,grim -c $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png
```
### Clipboard
Captures to a temporary file and copies the image to the clipboard; no file is saved.
```ini
bind=CTRL,Print,spawn_shell,f=$(mktemp -t screenshot-XXXXXX.png) && grim "$f" && wl-copy < "$f" && rm -f "$f"
```
### Annotate
Captures and opens `satty` for drawing before saving.
```ini
bind=SUPER,Print,spawn_shell,f=$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png && grim "$f" && satty --filename "$f" --output-filename "$f" --actions-on-enter save-to-file --early-exit
```
## Script Binds
When a command involves multi-step logic, geometry parsing, FIFOs, or screen freezing,
move it into a script and invoke it with `spawn` instead of `spawn_shell`.
Create the scripts directory first:
```bash
mkdir -p ~/.config/mango/scripts/screenshot
```
### Window
Uses `mmsg` (ships with mango) to capture the focused window.
`~/.config/mango/scripts/screenshot/window.sh`:
```bash
#!/usr/bin/env bash
geometry=$(mmsg get focusing-client | jq -r '"\(.x),\(.y) \(.width)x\(.height)"')
[ -z "$geometry" ] && exit 1
grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png"
```
```ini
bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/window.sh
```
### Freeze
Freezes the screen with `wayfreeze` before capturing.
`~/.config/mango/scripts/screenshot/freeze.sh`:
```bash
#!/usr/bin/env bash
pipe=$(mktemp -u).fifo
mkfifo "$pipe"
wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" &
wayfreeze_pid=$!
read -r < "$pipe"
grim "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png"
kill "$wayfreeze_pid" 2>/dev/null
rm -f "$pipe"
```
```ini
bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze.sh
```
### Freeze + Region
Freeze, then select a region with `slurp`. Cleans up on cancel.
`~/.config/mango/scripts/screenshot/freeze-region.sh`:
```bash
#!/usr/bin/env bash
pipe=$(mktemp -u).fifo
mkfifo "$pipe"
wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" &
wayfreeze_pid=$!
read -r < "$pipe"
geometry=$(slurp -d)
if [[ -z "$geometry" ]]; then
kill "$wayfreeze_pid" 2>/dev/null
rm -f "$pipe"
exit 1
fi
grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png"
kill "$wayfreeze_pid" 2>/dev/null
rm -f "$pipe"
```
```ini
bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze-region.sh
```
Make all three scripts executable:
```bash
chmod +x ~/.config/mango/scripts/screenshot/*.sh
```
## All-in-One Script
Prefer fewer files? A single script with subcommands covers every mode above.
Place it in the same directory and use it in place of the individual scripts.
`~/.config/mango/scripts/screenshot/screenshot.sh`:
```bash
#!/usr/bin/env bash
set -euo pipefail
mkdir -p "$HOME/Pictures/Screenshots"
filepath="$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png"
case "${1:-fullscreen}" in
region)
g=$(slurp -d); [ -z "$g" ] && exit 1
grim -g "$g" "$filepath" ;;
window)
g=$(mmsg get focusing-client | jq -r '"\(.x),\(.y) \(.width)x\(.height)"')
[ -z "$g" ] && exit 1
grim -g "$g" "$filepath" ;;
freeze)
p=$(mktemp -u).fifo; mkfifo "$p"
wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$!
read -r < "$p"; grim "$filepath"
kill "$wp" 2>/dev/null; rm -f "$p" ;;
freeze-region)
p=$(mktemp -u).fifo; mkfifo "$p"
wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$!
read -r < "$p"; g=$(slurp -d)
if [ -z "$g" ]; then kill "$wp" 2>/dev/null; rm -f "$p"; exit 1; fi
grim -g "$g" "$filepath"
kill "$wp" 2>/dev/null; rm -f "$p" ;;
annotate)
grim "$filepath"; satty --filename "$filepath" --output-filename "$filepath" --actions-on-enter save-to-file --early-exit ;;
*) grim "$filepath" ;;
esac
```
Make the script executable:
```bash
chmod +x ~/.config/mango/scripts/screenshot/screenshot.sh
```
Then add the binds to `config.conf`:
```ini
bind=NONE,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh fullscreen
bind=SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh region
bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh window
bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze
bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze-region
bind=SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh annotate
```

108
docs/visuals/animations.md Normal file
View file

@ -0,0 +1,108 @@
---
title: Animations
description: Configure smooth transitions for windows and layers.
---
## Enabling Animations
mangowm supports animations for both standard windows and layer shell surfaces (like bars and notifications).
```ini
animations=1
layer_animations=1
```
## Animation Types
You can define different animation styles for opening and closing windows and layer surfaces.
Available types: `slide`, `zoom`, `fade`, `none`.
```ini
animation_type_open=zoom
animation_type_close=slide
layer_animation_type_open=slide
layer_animation_type_close=slide
```
## Fade Settings
Control the fade-in and fade-out effects for animations.
```ini
animation_fade_in=1
animation_fade_out=1
fadein_begin_opacity=0.5
fadeout_begin_opacity=0.5
```
- `animation_fade_in` — Enable fade-in effect (0: disable, 1: enable)
- `animation_fade_out` — Enable fade-out effect (0: disable, 1: enable)
- `fadein_begin_opacity` — Starting opacity for fade-in animations (0.01.0)
- `fadeout_begin_opacity` — Starting opacity for fade-out animations (0.01.0)
## Zoom Settings
Adjust the zoom ratios for zoom animations.
```ini
zoom_initial_ratio=0.4
zoom_end_ratio=0.8
```
- `zoom_initial_ratio` — Initial zoom ratio
- `zoom_end_ratio` — End zoom ratio
## Durations
Control the speed of animations (in milliseconds).
| Setting | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `animation_duration_move` | integer | `500` | Move animation duration (ms) |
| `animation_duration_open` | integer | `400` | Open animation duration (ms) |
| `animation_duration_tag` | integer | `300` | Tag animation duration (ms) |
| `animation_duration_close` | integer | `300` | Close animation duration (ms) |
| `animation_duration_focus` | integer | `0` | Focus change (opacity transition) animation duration (ms) |
```ini
animation_duration_move=500
animation_duration_open=400
animation_duration_tag=300
animation_duration_close=300
animation_duration_focus=0
```
## Custom Bezier Curves
Bezier curves determine the "feel" of an animation (e.g., linear vs. bouncy). The format is `x1,y1,x2,y2`.
You can visualize and generate curve values using online tools like [cssportal.com](https://www.cssportal.com/css-cubic-bezier-generator/) or [easings.net](https://easings.net).
| Setting | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `animation_curve_open` | string | `0.46,1.0,0.29,0.99` | Open animation bezier curve |
| `animation_curve_move` | string | `0.46,1.0,0.29,0.99` | Move animation bezier curve |
| `animation_curve_tag` | string | `0.46,1.0,0.29,0.99` | Tag animation bezier curve |
| `animation_curve_close` | string | `0.46,1.0,0.29,0.99` | Close animation bezier curve |
| `animation_curve_focus` | string | `0.46,1.0,0.29,0.99` | Focus change (opacity transition) animation bezier curve |
| `animation_curve_opafadein` | string | `0.46,1.0,0.29,0.99` | Open opacity animation bezier curve |
| `animation_curve_opafadeout` | string | `0.5,0.5,0.5,0.5` | Close opacity animation bezier curve |
```ini
animation_curve_open=0.46,1.0,0.29,0.99
animation_curve_move=0.46,1.0,0.29,0.99
animation_curve_tag=0.46,1.0,0.29,0.99
animation_curve_close=0.46,1.0,0.29,0.99
animation_curve_focus=0.46,1.0,0.29,0.99
animation_curve_opafadein=0.46,1.0,0.29,0.99
animation_curve_opafadeout=0.5,0.5,0.5,0.5
```
## Tag Animation Direction
Control the direction of tag switch animations.
| Setting | Default | Description |
| :--- | :--- | :--- |
| `tag_animation_direction` | `1` | Tag animation direction (1: horizontal, 0: vertical) |

82
docs/visuals/effects.md Normal file
View file

@ -0,0 +1,82 @@
---
title: Window Effects
description: Add visual polish with blur, shadows, and opacity.
---
## Blur
Blur creates a frosted glass effect for transparent windows.
| Setting | Default | Description |
| :--- | :--- | :--- |
| `blur` | `0` | Enable blur for windows. |
| `blur_layer` | `0` | Enable blur for layer surfaces (like bars/docks). |
| `blur_optimized` | `1` | Caches the wallpaper and blur background, significantly reducing GPU usage. Disabling it will significantly increase GPU consumption and may cause rendering lag. **Highly recommended.** |
| `blur_params_radius` | `5` | The strength (radius) of the blur. |
| `blur_params_num_passes` | `1` | Number of passes. Higher = smoother but more expensive. |
| `blur_params_noise` | `0.02` | Blur noise level. |
| `blur_params_brightness` | `0.9` | Blur brightness adjustment. |
| `blur_params_contrast` | `0.9` | Blur contrast adjustment. |
| `blur_params_saturation` | `1.2` | Blur saturation adjustment. |
> **Warning:** Blur has a relatively high impact on performance. If your hardware is limited, it is not recommended to enable it. If you experience lag with blur on, ensure `blur_optimized=1` — disabling it will significantly increase GPU consumption and may cause rendering lag. To disable blur entirely, set `blur=0`.
---
## Shadows
Drop shadows help distinguish floating windows from the background.
| Setting | Default | Description |
| :--- | :--- | :--- |
| `shadows` | `0` | Enable shadows. |
| `layer_shadows` | `0` | Enable shadows for layer surfaces. |
| `shadow_only_floating` | `1` | Only draw shadows for floating windows (saves performance). |
| `shadows_size` | `10` | Size of the shadow. |
| `shadows_blur` | `15` | Shadow blur amount. |
| `shadows_position_x` | `0` | Shadow X offset. |
| `shadows_position_y` | `0` | Shadow Y offset. |
| `shadowscolor` | `0x000000ff` | Color of the shadow. |
```ini
# Example shadows configuration
shadows=1
layer_shadows=1
shadow_only_floating=1
shadows_size=12
shadows_blur=15
shadows_position_x=0
shadows_position_y=0
shadowscolor=0x000000ff
```
---
## Opacity & Corner Radius
Control the transparency and roundness of your windows.
| Setting | Default | Description |
| :--- | :--- | :--- |
| `border_radius` | `0` | Window corner radius in pixels. |
| `border_radius_location_default` | `0` | Corner radius location: `0` (all), `1` (top-left), `2` (top-right), `3` (bottom-left), `4` (bottom-right), `5` (closest corner). |
| `no_radius_when_single` | `0` | Disable radius if only one window is visible. |
| `focused_opacity` | `1.0` | Opacity for the active window (0.0 - 1.0). |
| `unfocused_opacity` | `1.0` | Opacity for inactive windows (0.0 - 1.0). |
```ini
# Window corner radius in pixels
border_radius=0
# Corner radius location (0=all, 1=top-left, 2=top-right, 3=bottom-left, 4=bottom-right)
border_radius_location_default=0
# Disable radius if only one window is visible
no_radius_when_single=0
# Opacity for the active window (0.0 - 1.0)
focused_opacity=1.0
# Opacity for inactive windows
unfocused_opacity=1.0
```

19
docs/visuals/index.mdx Normal file
View file

@ -0,0 +1,19 @@
---
title: Visuals
description: Customize borders, colors, effects, and animations.
icon: Palette
---
Customize the look of your desktop.
<Cards>
<Card href="/docs/visuals/theming" title="Theming" description="Borders, colors, and cursor" />
<Card href="/docs/visuals/status-bar" title="Status Bar" description="Built-in status bar" />
<Card href="/docs/visuals/effects" title="Effects" description="Blur, shadows, rounded corners" />
<Card href="/docs/visuals/animations" title="Animations" description="Window and tag animations" />
</Cards>

4
docs/visuals/meta.json Normal file
View file

@ -0,0 +1,4 @@
{
"title": "Visuals",
"pages": ["theming", "status-bar", "effects", "animations"]
}

141
docs/visuals/status-bar.md Normal file
View file

@ -0,0 +1,141 @@
---
title: Status Bar
description: Configure Waybar for mangowm.
---
## Module Configuration
mangowm is compatible with Waybar's `ext/workspaces` module (Wayland standard) or the `dwl/tags` module. We recommend `ext/workspaces` for the best experience.
> **Tip:** You can also use the `dwl/tags` module, but `ext/workspaces` provides better integration with mangowm's features. The `ext/workspaces` module requires **Waybar > 0.14.0**.
### `config.jsonc`
Add the following to your Waybar configuration:
```jsonc
{
"modules-left": [
"ext/workspaces",
"dwl/window"
],
"ext/workspaces": {
"format": "{icon}",
"ignore-hidden": true,
"on-click": "activate",
"on-click-right": "deactivate",
"sort-by-id": true
},
"dwl/window": {
"format": "[{layout}] {title}"
}
}
```
## Styling
You can style the tags using standard CSS in `style.css`.
### `style.css`
```css
#workspaces {
border-radius: 4px;
border-width: 2px;
border-style: solid;
border-color: #c9b890;
margin-left: 4px;
padding-left: 10px;
padding-right: 6px;
background: rgba(40, 40, 40, 0.76);
}
#workspaces button {
border: none;
background: none;
box-shadow: inherit;
text-shadow: inherit;
color: #ddca9e;
padding: 1px;
padding-left: 1px;
padding-right: 1px;
margin-right: 2px;
margin-left: 2px;
}
#workspaces button.hidden {
color: #9e906f;
background-color: transparent;
}
#workspaces button.visible {
color: #ddca9e;
}
#workspaces button:hover {
color: #d79921;
}
#workspaces button.active {
background-color: #ddca9e;
color: #282828;
margin-top: 5px;
margin-bottom: 5px;
padding-top: 1px;
padding-bottom: 0px;
border-radius: 3px;
}
#workspaces button.urgent {
background-color: #ef5e5e;
color: #282828;
margin-top: 5px;
margin-bottom: 5px;
padding-top: 1px;
padding-bottom: 0px;
border-radius: 3px;
}
#tags {
background-color: transparent;
}
#tags button {
background-color: #fff;
color: #a585cd;
}
#tags button:not(.occupied):not(.focused) {
font-size: 0;
min-width: 0;
min-height: 0;
margin: -17px;
padding: 0;
color: transparent;
background-color: transparent;
}
#tags button.occupied {
background-color: #fff;
color: #cdc885;
}
#tags button.focused {
background-color: rgb(186, 142, 213);
color: #fff;
}
#tags button.urgent {
background: rgb(171, 101, 101);
color: #fff;
}
#window {
background-color: rgb(237, 196, 147);
color: rgb(63, 37, 5);
}
```
## Complete Configuration Example
> **Tip:** You can find a complete Waybar configuration for mangowm at [waybar-config](https://github.com/DreamMaoMao/waybar-config).

95
docs/visuals/theming.md Normal file
View file

@ -0,0 +1,95 @@
---
title: Theming
description: Customize the visual appearance of borders, colors, and the cursor.
---
## Dimensions
Control the sizing of window borders and gaps.
| Setting | Default | Description |
| :--- | :--- | :--- |
| `borderpx` | `4` | Border width in pixels. |
| `gappih` | `5` | Horizontal inner gap (between windows). |
| `gappiv` | `5` | Vertical inner gap. |
| `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). |
| `gappov` | `10` | Vertical outer gap. |
## Colors
Colors are defined in `0xRRGGBBAA` hex format.
```ini
# Background color of the root window
rootcolor=0x323232ff
# Inactive window border
bordercolor=0x444444ff
# Drop shadow when dragging windows
dropcolor=0x8FBA7C55
# Split window border color in manual dwindle layout
splitcolor=0xEB441EFF
# Active window border
focuscolor=0xc66b25ff
# Urgent window border (alerts)
urgentcolor=0xad401fff
```
### State-Specific Colors
You can also color-code windows based on their state:
| State | Config Key | Default Color |
| :--- | :--- | :--- |
| Maximized | `maximizescreencolor` | `0x89aa61ff` |
| Scratchpad | `scratchpadcolor` | `0x516c93ff` |
| Global | `globalcolor` | `0xb153a7ff` |
| Overlay | `overlaycolor` | `0x14a57cff` |
> **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration.
### Overview Jump Mode
| Setting | Default | Description |
| :--- | :--- | :--- |
| `jump_label_decorate_fg_color` | `0xc4939dff` | text color. |
| `jump_label_decorate_bg_color` | `0x201b14ff` | background color.|
| `jump_label_decorate_focus_fg_color` | `0x201b14ff` | text color for focus. |
| `jump_label_decorate_focus_bg_color` | `0xc4939dff` | background color for focus.|
| `jump_label_decorate_border_color` | `0x8BAA9Bff` | border color.|
| `jump_label_decorate_border_width` | `4` | border width.|
| `jump_label_decorate_corner_radius` | `5` | corner radius.|
| `jump_label_decorate_padding_x` | `10` | horizontal padding.|
| `jump_label_decorate_padding_y` | `10` | vertical padding.|
| `jump_label_decorate_font_desc` | `monospace Bold 16` | font set.|
### Tab Bar For Monocle Layout
| Setting | Default | Description |
| :--- | :--- | :--- |
| `tab_bar_height` | `50` | Height of the tab bar for monocle layout. |
| `tab_bar_decorate_fg_color` | `0xc4939dff` | text color.
| `tab_bar_decorate_bg_color` | `0x201b14ff` | background color.|
| `tab_bar_decorate_focus_fg_color` | `0x201b14ff` | text color for focus. |
| `tab_bar_decorate_focus_bg_color` | `0xc4939dff` | background color for focus.|
| `tab_bar_decorate_border_color` | `0x8BAA9Bff` | border color.|
| `tab_bar_decorate_border_width` | `4` | border width.|
| `tab_bar_decorate_corner_radius` | `5` | corner radius.|
| `tab_bar_decorate_padding_x` | `0` | horizontal padding.|
| `tab_bar_decorate_padding_y` | `0` | vertical padding.|
| `tab_bar_decorate_font_desc` | `monospace Bold 16` | font set.|
## Borders
Control the appearance of window borders.
## Cursor Theme
Set the size and theme of your mouse cursor.
```ini
cursor_size=24
cursor_theme=Adwaita
```

View file

@ -0,0 +1,19 @@
---
title: Window Management
description: Layouts, rules, and window behavior.
icon: LayoutGrid
---
Window management with layouts, rules, and scratchpad support.
<Cards>
<Card href="/docs/window-management/layouts" title="Layouts" description="Tile, scroller, monocle, grid, deck" />
<Card href="/docs/window-management/rules" title="Rules" description="Window rules and conditions" />
<Card href="/docs/window-management/overview" title="Overview" description="Window states and properties" />
<Card href="/docs/window-management/scratchpad" title="Scratchpad" description="Quick access to applications" />
</Cards>

View file

@ -0,0 +1,137 @@
---
title: Layouts
description: Configure and switch between different window layouts.
---
## Supported Layouts
mangowm supports a variety of layouts that can be assigned per tag.
- `tile`
- `scroller`
- `monocle`
- `grid`
- `deck`
- `center_tile`
- `vertical_tile`
- `right_tile`
- `vertical_scroller`
- `vertical_grid`
- `vertical_deck`
- `dwindle`
- `fair`
- `vertical_fair`
---
## Scroller Layout
The Scroller layout positions windows in a scrollable strip, similar to PaperWM.
### Configuration
| Setting | Default | Description |
| :--- | :--- | :--- |
| `scroller_structs` | `20` | Width reserved on sides when window ratio is 1. |
| `scroller_default_proportion` | `0.9` | Default width proportion for new windows. |
| `scroller_focus_center` | `0` | Always center the focused window (1 = enable). |
| `scroller_prefer_center` | `0` | Center focused window only if it was outside the view. |
| `scroller_prefer_overspread` | `1` | Allow windows to overspread when there's extra space. |
| `edge_scroller_pointer_focus` | `1` | Focus windows even if partially off-screen. |
| `edge_scroller_focus_allow_speed` | `0.0` | Allow pointer focus to happen if the pointer moves at a speed greater than this value. |
| `scroller_proportion_preset` | `0.5,0.8,1.0` | Presets for cycling window widths. |
| `scroller_ignore_proportion_single` | `1` | Ignore proportion adjustments for single windows. |
| `scroller_default_proportion_single` | `1.0` | Default proportion for single windows in scroller. **Requires `scroller_ignore_proportion_single=0` to take effect.** |
> **Warning:** `scroller_prefer_overspread`, `scroller_focus_center`, and `scroller_prefer_center` interact with each other. Their priority order is:
>
> **scroller_prefer_overspread > scroller_focus_center > scroller_prefer_center**
>
> To ensure a lower-priority setting takes effect, you must set all higher-priority options to `0`.
```ini
# Example scroller configuration
scroller_structs=20
scroller_default_proportion=0.9
scroller_focus_center=0
scroller_prefer_center=0
scroller_prefer_overspread=1
edge_scroller_pointer_focus=1
edge_scroller_focus_allow_speed=0.0
scroller_default_proportion_single=1.0
scroller_proportion_preset=0.5,0.8,1.0
```
---
## Master-Stack Layouts
These settings apply to layouts like `tile` and `center_tile`.
| Setting | Default | Description |
| :--- | :--- | :--- |
| `new_is_master` | `1` | New windows become the master window. |
| `default_mfact` | `0.55` | The split ratio between master and stack areas. |
| `default_nmaster` | `1` | Number of allowed master windows. |
| `smartgaps` | `0` | Disable gaps when only one window is present. |
| `center_master_overspread` | `0` | (Center Tile) Master spreads across screen if no stack exists. |
| `center_when_single_stack` | `1` | (Center Tile) Center master when only one stack window exists. |
```ini
# Example master-stack configuration
new_is_master=1
smartgaps=0
default_mfact=0.55
default_nmaster=1
```
---
## Dwindle Layout
The Dwindle layout arranges windows as a binary tree of recursive splits. Each new window splits the focused window's container, producing a spiral-like tiling.
### Configuration
| Setting | Default | Description |
| :--- | :--- | :--- |
| `dwindle_split_ratio` | `0.5` | Ratio used for new splits (`0.05``0.95`). |
| `dwindle_smart_split` | `0` | Pick the split axis from the cursor's position inside the focused window. The new window appears on the cursor's side. |
| `dwindle_hsplit` | `1` | Side-by-side splits: where the new window goes. `0` = follow cursor, `1` = right, `2` = left. |
| `dwindle_vsplit` | `1` | Top/bottom splits: where the new window goes. `0` = follow cursor, `1` = below, `2` = above. |
| `dwindle_preserve_split` | `0` | Keep the sibling's split orientation when a window is closed. |
| `dwindle_smart_resize` | `0` | When dragging to resize, move the split toward the cursor regardless of which side was grabbed. |
| `dwindle_drop_simple_split` | `1` | Drag-to-tile drop preview. `1` = 2-zone preview matching `dwindle_split_ratio`, `0` = 4-quadrant preview. |
| `dwindle_manual_split` | `0` | Manually split windows mode. |
```ini
# Example dwindle configuration
dwindle_split_ratio=0.5
dwindle_smart_split=0
dwindle_hsplit=0
dwindle_vsplit=0
dwindle_preserve_split=0
dwindle_smart_resize=0
dwindle_drop_simple_split=1
```
---
## Switching Layouts
| Setting | Default | Description |
| :--- | :--- | :--- |
| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through,the value sample:`tile,scroller`. |
You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules).
**Keybinding Examples:**
```ini
# Cycle through layouts
circle_layout=grid,scroller,tile
bind=SUPER,n,switch_layout
# Set specific layout
bind=SUPER,t,setlayout,tile
bind=SUPER,s,setlayout,scroller
```

View file

@ -0,0 +1,4 @@
{
"title": "Window Management",
"pages": ["layouts", "rules", "overview", "scratchpad"]
}

View file

@ -0,0 +1,31 @@
---
title: Overview
description: Configure the overview mode for window navigation.
---
## Overview Settings
| Setting | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `hotarea_size` | integer | `10` | Hot area size in pixels. |
| `enable_hotarea` | integer | `0` | Enable hot areas (0: disable, 1: enable). |
| `hotarea_corner` | integer | `2` | Hot area corner (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). |
| `ov_tab_mode` | integer | `1` | Overview tab mode (0: disable, 1: enable). |
| `overviewgappi` | integer | `5` | Inner gap in overview mode. |
| `overviewgappo` | integer | `30` | Outer gap in overview mode. |
| `ov_no_resize` | integer | `1` | Disable window resizing in overview mode. |
### Setting Descriptions
- `enable_hotarea` — Toggles overview when the cursor enters the configured corner.
- `hotarea_size` — Size of the hot area trigger zone in pixels.
- `hotarea_corner` — Corner that triggers the hot area (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right).
- `ov_tab_mode` — Circles focus through windows in overview; releasing the mod key exits overview.
- `ov_no_resize` — Disables resizing of windows in overview mode(use snap to display).
### Mouse Interaction in Overview
When in overview mode:
- **Left mouse button** — Jump to (focus) a window.
- **Right mouse button** — Close a window.

View file

@ -0,0 +1,257 @@
---
title: Rules
description: Define behavior for specific windows, tags, and layers.
---
## Window Rules
Window rules allow you to set specific properties (floating, opacity, size, animations, etc.) for applications based on their `appid` or `title`. You can set all parameters in one line, and if you both set appid and title, the window will only follow the rules when appid and title both match.
**Format:**
```ini
windowrule=Parameter:Values,title:Values
windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values
```
### State & Behavior Parameters
| Parameter | Type | Values | Description |
| :--- | :--- | :--- | :--- |
| `appid` | string | Any | Match by application ID, supports regex |
| `title` | string | Any | Match by window title, supports regex |
| `isfloating` | integer | `0` / `1` | Force floating state |
| `isfullscreen` | integer | `0` / `1` | Force fullscreen state |
| `isfakefullscreen` | integer | `0` / `1` | Force fake-fullscreen state (window stays constrained) |
| `isglobal` | integer | `0` / `1` | Open as global window (sticky across tags) |
| `isoverlay` | integer | `0` / `1` | Make it always in top layer |
| `isopensilent` | integer | `0` / `1` | Open without focus |
| `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag |
| `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_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 |
| `noopenmaximized` | integer | `0` / `1` | Window does not open as maximized mode |
| `single_scratchpad` | integer | `0` / `1` (default 1) | Only show one out of named scratchpads or the normal scratchpad |
| `allow_shortcuts_inhibit` | integer | `0` / `1` (default 1) | Allow shortcuts to be inhibited by clients |
| `idleinhibit_when_focus` | integer | `0` / `1` (default 0) | Automatically keep idle inhibit active when this window is focused |
### Geometry & Position
| Parameter | Type | Values | Description |
| :--- | :--- | :--- | :--- |
| `width` | float | 0-9999 | Window width when it becomes a floating window,if the value below 1, it will be the percentage of the screen width,otherwise it will be the pixel value |
| `height` | float | 0-9999 | Window height when it becomes a floating window,if the value below 1, it will be the percentage of the screen height,otherwise it will be the pixel value |
| `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap |
| `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap |
| `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) |
| `tags` | integer | 1-9 | Assign to specific tag |
| `no_force_center` | integer | `0` / `1` | Window does not force center |
| `isnosizehint` | integer | `0` / `1` | Don't use min size and max size for size hints |
### Visuals & Decoration
| Parameter | Type | Values | Description |
| :--- | :--- | :--- | :--- |
| `noblur` | integer | `0` / `1` | Window does not have blur effect |
| `isnoborder` | integer | `0` / `1` | Remove window border |
| `isnoshadow` | integer | `0` / `1` | Not apply shadow |
| `isnoradius` | integer | `0` / `1` | Not apply corner radius |
| `isnoanimation` | integer | `0` / `1` | Not apply animation |
| `focused_opacity` | integer | `0` / `1` | Window focused opacity |
| `unfocused_opacity` | integer | `0` / `1` | Window unfocused opacity |
| `allow_csd` | integer | `0` / `1` | Allow client side decoration |
> **Tip:** For detailed visual effects configuration, see the [Window Effects](/docs/visuals/effects) page for blur, shadows, and opacity settings.
### Layout & Scroller
| Parameter | Type | Values | Description |
| :--- | :--- | :--- | :--- |
| `scroller_proportion` | float | 0.1-1.0 | Set scroller proportion |
| `scroller_proportion_single` | float | 0.1-1.0 | Set scroller auto adjust proportion when it is single window |
> **Tip:** For comprehensive layout configuration, see the [Layouts](/docs/window-management/layouts) page for all layout options and detailed settings.
### Animation
| Parameter | Type | Values | Description |
| :--- | :--- | :--- | :--- |
| `animation_type_open` | string | zoom, slide, fade, none | Set open animation |
| `animation_type_close` | string | zoom, slide, fade, none | Set close animation |
| `nofadein` | integer | `0` / `1` | Window ignores fade-in animation |
| `nofadeout` | integer | `0` / `1` | Window ignores fade-out animation |
> **Tip:** For detailed animation configuration, see the [Animations](/docs/visuals/animations) page for available types and settings.
### Terminal & Swallowing
| Parameter | Type | Values | Description |
| :--- | :--- | :--- | :--- |
| `isterm` | integer | `0` / `1` | A new GUI window will replace the isterm window when it is opened |
| `noswallow` | integer | `0` / `1` | The window will not replace the isterm window |
### Global & Special Windows
| Parameter | Type | Values | Description |
| :--- | :--- | :--- | :--- |
| `globalkeybinding` | string | `[mod combination][-][key]` | Global keybinding (only works for Wayland apps) |
| `isunglobal` | integer | `0` / `1` | Open as unmanaged global window (for desktop pets or camera windows) |
| `isnamedscratchpad` | integer | `0` / `1` | 0: disable, 1: named scratchpad |
> **Tip:** For scratchpad usage, see the [Scratchpad](/docs/window-management/scratchpad) page for detailed configuration examples.
### Performance & Tearing
| Parameter | Type | Values | Description |
| :--- | :--- | :--- | :--- |
| `force_tearing` | integer | `0` / `1` | Set window to tearing state, refer to [Tearing](/docs/configuration/monitors#tearing-game-mode) |
### Examples
```ini
# Set specific window size and position
windowrule=width:1000,height:900,appid:yesplaymusic,title:Demons
# Global keybindings for OBS Studio
windowrule=globalkeybinding:ctrl+alt-o,appid:com.obsproject.Studio
windowrule=globalkeybinding:ctrl+alt-n,appid:com.obsproject.Studio
windowrule=isopensilent:1,appid:com.obsproject.Studio
# Force tearing for games
windowrule=force_tearing:1,title:vkcube
windowrule=force_tearing:1,title:Counter-Strike 2
# Named scratchpad for file manager
windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi
# Custom opacity for specific apps
windowrule=focused_opacity:0.8,appid:firefox
windowrule=unfocused_opacity:0.6,appid:foot
# Disable blur for selection tools
windowrule=noblur:1,appid:slurp
# Position windows relative to screen center
windowrule=offsetx:20,offsety:-30,width:800,height:600,appid:alacritty
# Send to specific tag and monitor
windowrule=tags:9,monitor:HDMI-A-1,appid:discord
# Terminal swallowing setup
windowrule=isterm:1,appid:st
windowrule=noswallow:1,appid:foot
# Disable client-side decorations
windowrule=allow_csd:1,appid:firefox
# Unmanaged global window (desktop pets, camera)
windowrule=isunglobal:1,appid:cheese
# Named scratchpad toggle
bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi
```
---
## Tag Rules
You can set all parameters in one line. If only `id` is set, the rule is followed when the id matches. If any of `monitor_name`, `monitor_make`, `monitor_model`, or `monitor_serial` are set, the rule is followed only if **all** of the set monitor fields match.
> **Warning:** Layouts set in tag rules have a higher priority than monitor rule layouts.
**Format:**
```ini
tagrule=id:Values,Parameter:Values,Parameter:Values
tagrule=id:Values,monitor_name:eDP-1,Parameter:Values,Parameter:Values
tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values
```
> **Tip:** See [Layouts](/docs/window-management/layouts#supported-layouts) for detailed descriptions of each layout type.
| Parameter | Type | Values | Description |
| :--- | :--- | :--- | :--- |
| `id` | integer | 0-9 | Match by tag id, 0 means the ~0 tag |
| `monitor_name` | string | monitor name | Match by monitor name |
| `monitor_make` | string | monitor make | Match by monitor manufacturer |
| `monitor_model` | string | monitor model | Match by monitor model |
| `monitor_serial` | string | monitor serial | Match by monitor serial number |
| `layout_name` | string | layout name | Layout name to set |
| `no_render_border` | integer | `0` / `1` | Disable render border |
| `open_as_floating` | integer | `0` / `1` | New open window will be floating|
| `no_hide` | integer | `0` / `1` | Not hide even if the tag is empty |
| `nmaster` | integer | 0, 99 | Number of master windows |
| `mfact` | float | 0.10.9 | Master area factor |
| `scroller_default_proportion` | float | 0.1-1.0 | Set scroller default proportion. |
| `scroller_default_proportion_single` | float | 0.1-1.0 | Set scroller auto adjust proportion when it is single window(only apply when set `scroller_ignore_proportion_single` to `0`) |
| `scroller_ignore_proportion_single` | integer | `0` / `1` | Ignore scroller single proportion setting. |
### Examples
```ini
# Set layout for specific tags
tagrule=id:1,layout_name:scroller
tagrule=id:2,layout_name:scroller
# Limit to specific monitor
tagrule=id:1,monitor_name:eDP-1,layout_name:scroller
tagrule=id:2,monitor_name:eDP-1,layout_name:scroller
# Persistent tags (1-4) with layout assignment
tagrule=id:1,no_hide:1,layout_name:scroller
tagrule=id:2,no_hide:1,layout_name:scroller
tagrule=id:3,monitor_name:eDP-1,no_hide:1,layout_name:scroller
tagrule=id:4,monitor_name:eDP-1,no_hide:1,layout_name:scroller
# Advanced tag configuration with master layout settings
tagrule=id:5,layout_name:tile,nmaster:2,mfact:0.6
tagrule=id:6,monitor_name:HDMI-A-1,layout_name:monocle,no_render_border:1
# set scroller proportion for specific tag
tagrule=id:1,layout_name:scroller,scroller_default_proportion_single:0.5,scroller_ignore_proportion_single:0,scroller_default_proportion:0.9,monitor_name:HDMI-A-1
```
> **Tip:** For Waybar configuration with persistent tags, see [Status Bar](/docs/visuals/status-bar) documentation.
---
## Layer Rules
You can set all parameters in one line. Target "layer shell" surfaces like status bars (`waybar`), launchers (`rofi`), or notification daemons.
**Format:**
```ini
layerrule=layer_name:Values,Parameter:Values,Parameter:Values
```
> **Tip:** You can use `mmsg get last_open_surface` to get the last open layer name for debugging.
| Parameter | Type | Values | Description |
| :--- | :--- | :--- | :--- |
| `layer_name` | string | layer name | Match name of layer, supports regex |
| `animation_type_open` | string | slide, zoom, fade, none | Set open animation |
| `animation_type_close` | string | slide, zoom, fade, none | Set close animation |
| `noblur` | integer | `0` / `1` | Disable blur |
| `noanim` | integer | `0` / `1` | Disable layer animation |
| `noshadow` | integer | `0` / `1` | Disable layer shadow |
> **Tip:** For animation types, see [Animations](/docs/visuals/animations#animation-types). For visual effects, see [Window Effects](/docs/visuals/effects).
### Examples
```ini
# No blur or animation for slurp selection layer (avoids occlusion and ghosting in screenshots)
layerrule=noanim:1,noblur:1,layer_name:selection
# Zoom animation for Rofi with multiple parameters
layerrule=animation_type_open:zoom,noanim:0,layer_name:rofi
# Disable animations and shadows for notification daemon
layerrule=noanim:1,noshadow:1,layer_name:swaync
# Multiple effects for launcher
layerrule=animation_type_open:slide,animation_type_close:fade,noblur:1,layer_name:wofi
```

View file

@ -0,0 +1,73 @@
---
title: Scratchpad
description: Manage hidden "scratchpad" windows for quick access.
---
mangowm supports two types of scratchpads: the standard pool (Sway-like) and named scratchpads.
## Standard Scratchpad
Any window can be sent to the "scratchpad" pile, which hides it. You can then cycle through them.
**Keybindings:**
```ini
# Send current window to scratchpad
bind=SUPER,i,minimized
# Toggle (show/hide) the scratchpad
bind=ALT,z,toggle_scratchpad
# Retrieve window from scratchpad (restore)
bind=SUPER+SHIFT,i,restore_minimized
```
---
## Named Scratchpad
Named scratchpads are bound to specific keys and applications. When triggered, mangowm will either launch the app (if not running) or toggle its visibility.
**1. Define the Window Rule**
You must identify the app using a unique `appid` or `title` and mark it as a named scratchpad. The application must support setting a custom appid or title at launch. Common examples:
- `st -c my-appid` — sets the appid
- `kitty -T my-title` — sets the window title
- `foot --app-id my-appid` — sets the appid
Use `none` as a placeholder when you only want to match by one field.
```ini
# Match by appid
windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi
# Match by title
windowrule=isnamedscratchpad:1,width:1000,height:700,title:kitty-scratch
```
**2. Bind the Toggle Key**
Format: `bind=MOD,KEY,toggle_named_scratchpad,appid,title,command`
Use `none` for whichever field you are not matching on.
```ini
# Match by appid: launch 'st' with class 'st-yazi' running 'yazi'
bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi
# Match by title: launch 'kitty' with window title 'kitty-scratch'
bind=alt,k,toggle_named_scratchpad,none,kitty-scratch,kitty -T kitty-scratch
```
---
## Appearance
You can customize the size of scratchpad windows relative to the screen.
```ini
scratchpad_width_ratio=0.8
scratchpad_height_ratio=0.9
scratchpadcolor=0x516c93ff
```

6
flake.lock generated
View file

@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1750386251,
"narHash": "sha256-1ovgdmuDYVo5OUC5NzdF+V4zx2uT8RtsgZahxidBTyw=",
"lastModified": 1778274207,
"narHash": "sha256-I4puXmX1iovcCHZlRmztO3vW0mAbbRvq4F8wgIMQ1MM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "076e8c6678d8c54204abcb4b1b14c366835a58bb",
"rev": "b3da656039dc7a6240f27b2ef8cc6a3ef3bccae7",
"type": "github"
},
"original": {

View file

@ -43,6 +43,14 @@
};
packages = {
inherit mango;
hm-options-json = pkgs.callPackage (import ./nix/generate-options.nix self) {
module = ./nix/hm-modules.nix;
optionPrefix = "wayland.windowManager.mango.";
};
nixos-options-json = pkgs.callPackage (import ./nix/generate-options.nix self) {
module = ./nix/nixos-modules.nix;
optionPrefix = "programs.mango.";
};
};
devShells.default = mango.overrideAttrs shellOverride;
formatter = pkgs.alejandra;

View file

@ -1,3 +1,3 @@
#!/usr/bin/bash
clang-format -i src/*/*.h -i src/*/*.c -i src/mango.c -i mmsg/mmsg.c -i mmsg/arg.h -i mmsg/dynarr.h
clang-format -i src/*/*.h -i src/*/*.c -i src/mango.c -i mmsg/mmsg.c

View file

@ -1,4 +1,4 @@
(define-module (mangowc)
(define-module (mangowm)
#:use-module (guix download)
#:use-module (guix git-download)
#:use-module (guix gexp)
@ -10,19 +10,20 @@
#:use-module (gnu packages pciutils)
#:use-module (gnu packages admin)
#:use-module (gnu packages pcre)
#:use-module (gnu packages javascript)
#:use-module (gnu packages xorg)
#:use-module (gnu packages build-tools)
#:use-module (gnu packages ninja)
#:use-module (gnu packages pkg-config)
#:use-module (guix build-system meson)
#:use-module (guix licenses))
#:use-module ((guix licenses) #:prefix license:))
(define-public mangowc-git
(define-public mangowm-git
(package
(name "mangowc")
(name "mangowm")
(version "git")
(source (local-file "." "mangowc-checkout"
(source (local-file "." "mangowm-checkout"
#:recursive? #t
#:select? (or (git-predicate (current-source-directory))
(const #t))))
@ -36,10 +37,13 @@
(add-before 'configure 'patch-meson
(lambda _
(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(sysconfdir)")
(("sysconfdir = sysconfdir.substring\\(prefix.length\\(\\)\\)")
"")))))))
"'-DSYSCONFDIR=\"@0@\"'.format(sysconfdir)")))))))
(inputs (list wayland
libinput
libdrm
@ -50,15 +54,24 @@
hwdata
seatd
pcre2
pango
cjson
libxcb
pixman
xcb-util-wm
wlroots
wlroots-0.19
scenefx))
(native-inputs (list pkg-config wayland-protocols))
(home-page "https://github.com/DreamMaoMao/mangowc")
(home-page "https://github.com/mangowm/mango")
(synopsis "Wayland compositor based on wlroots and scenefx")
(description "A Wayland compositor based on wlroots and scenefx,
inspired by dwl but aiming to be more feature-rich.")
(license gpl3)))
(description
"MangoWM is a modern, lightweight, high-performance Wayland compositor
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
mangowc-git
(define-deprecated-package mangowc
mangowm-git)
mangowm-git

View file

@ -1,5 +1,5 @@
project('mango', ['c', 'cpp'],
version : '0.11.0',
project('mango', ['c'],
version : '0.14.4',
)
subdir('protocols')
@ -24,12 +24,9 @@ if sysconfdir.startswith(prefix) and not is_nixos
endif
endif
# 打印调试信息,确认 sysconfdir 的值
# message('prefix: ' + prefix)
# message('sysconfdir: ' + sysconfdir)
cc = meson.get_compiler('c')
libm = cc.find_library('m')
libdrm = dependency('libdrm')
xcb = dependency('xcb', required : get_option('xwayland'))
xlibs = dependency('xcb-icccm', required : get_option('xwayland'))
wayland_server_dep = dependency('wayland-server',version: '>=1.23.1')
@ -39,13 +36,15 @@ libinput_dep = dependency('libinput',version: '>=1.27.1')
libwayland_client_dep = dependency('wayland-client')
pcre2_dep = dependency('libpcre2-8')
libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1')
pixman_dep = dependency('pixman-1')
cjson_dep = dependency('libcjson')
pangocairo_dep = dependency('pangocairo')
# 获取版本信息
# version info
git = find_program('git', required : false)
is_git_repo = false
# 检查当前目录是否是 Git 仓库
# check if current directory is a git repo
if git.found()
git_status = run_command(git, 'rev-parse', '--is-inside-work-tree', check : false)
if git_status.returncode() == 0 and git_status.stdout().strip() == 'true'
@ -54,18 +53,18 @@ if git.found()
endif
if is_git_repo
# 如果是 Git 目录,获取 Commit Hash 和最新的 tag
# if current directory is a git repo, get commit hash and latest tag
commit_hash = run_command(git, 'rev-parse', '--short', 'HEAD', check : false).stdout().strip()
latest_tag = run_command(git, 'describe', '--tags', '--abbrev=0', check : false).stdout().strip()
latest_tag = meson.project_version()
version_with_hash = '@0@(@1@)'.format(latest_tag, commit_hash)
else
# 如果不是 Git 目录,使用项目版本号和 "release" 字符串
# if not a git repo, use project version and "release" string
commit_hash = 'release'
latest_tag = meson.project_version()
version_with_hash = '@0@(@1@)'.format(latest_tag, commit_hash)
endif
# 定义编译参数
# define compilation args
c_args = [
'-g',
'-Wno-unused-function',
@ -75,7 +74,7 @@ c_args = [
'-DSYSCONFDIR="@0@"'.format('/etc'),
]
# 仅在 debug 选项启用时添加调试参数
# add debug args only when debug option is enabled
if get_option('asan')
c_args += [
'-fsanitize=address',
@ -88,7 +87,7 @@ if xcb.found() and xlibs.found()
c_args += '-DXWAYLAND'
endif
# 链接参数(根据 debug 状态添加 ASAN
# define link args
link_args = []
if get_option('asan')
link_args += '-fsanitize=address'
@ -97,9 +96,11 @@ endif
executable('mango',
'src/mango.c',
'src/common/util.c',
'src/draw/text-node.c',
wayland_sources,
dependencies : [
libm,
libdrm,
xcb,
xlibs,
libscenefx_dep,
@ -109,6 +110,9 @@ executable('mango',
libinput_dep,
libwayland_client_dep,
pcre2_dep,
pixman_dep,
cjson_dep,
pangocairo_dep,
],
install : true,
c_args : c_args,
@ -130,7 +134,7 @@ wayland_scanner_private_code = generator(
arguments: ['private-code', '@INPUT@', '@OUTPUT@']
)
# 在 mmsg 目标中使用生成器
# use generator in mmsg target
executable('mmsg',
'mmsg/mmsg.c',
wayland_scanner_private_code.process(dwl_ipc_protocol),
@ -145,6 +149,10 @@ executable('mmsg',
],
)
mandir = get_option('mandir')
desktop_install_dir = join_paths(prefix, 'share/wayland-sessions')
install_data('mango.desktop', install_dir : desktop_install_dir)
install_data('config.conf', install_dir : join_paths(sysconfdir, 'mango'))
portal_install_dir = join_paths(prefix, 'share/xdg-desktop-portal')
install_data('assets/mango.desktop', install_dir : desktop_install_dir)
install_data('assets/mango-portals.conf', install_dir : portal_install_dir)
install_data('assets/config.conf', install_dir : join_paths(sysconfdir, 'mango'))
install_data('mmsg/mmsg.1', install_dir : join_paths(mandir, 'man1'))

View file

@ -1,65 +0,0 @@
/*
* Copy me if you can.
* by 20h
*/
#ifndef ARG_H__
#define ARG_H__
extern char *argv0;
/* use main(int32_t argc, char *argv[]) */
#define ARGBEGIN \
for (argv0 = *argv, argv++, argc--; \
argv[0] && argv[0][0] == '-' && argv[0][1]; argc--, argv++) { \
char argc_; \
char **argv_; \
int32_t brk_; \
if (argv[0][1] == '-' && argv[0][2] == '\0') { \
argv++; \
argc--; \
break; \
} \
for (brk_ = 0, argv[0]++, argv_ = argv; argv[0][0] && !brk_; \
argv[0]++) { \
if (argv_ != argv) \
break; \
argc_ = argv[0][0]; \
switch (argc_)
/* Handles obsolete -NUM syntax */
#define ARGNUM \
case '0': \
case '1': \
case '2': \
case '3': \
case '4': \
case '5': \
case '6': \
case '7': \
case '8': \
case '9'
#define ARGEND \
} \
}
#define ARGC() argc_
#define ARGNUMF() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX))
#define EARGF(x) \
((argv[0][1] == '\0' && argv[1] == NULL) \
? ((x), abort(), (char *)0) \
: (brk_ = 1, \
(argv[0][1] != '\0') ? (&argv[0][1]) : (argc--, argv++, argv[0])))
#define ARGF() \
((argv[0][1] == '\0' && argv[1] == NULL) \
? (char *)0 \
: (brk_ = 1, \
(argv[0][1] != '\0') ? (&argv[0][1]) : (argc--, argv++, argv[0])))
#define LNGARG() &argv[0][0]
#endif

View file

@ -1,30 +0,0 @@
#ifndef DYNARR_H__
#define DYNARR_H__
#define DYNARR_DEF(t) \
struct { \
t *arr; \
size_t len, cap, size; \
}
#define DYNARR_INIT(p) \
((p)->arr = reallocarray((p)->arr, ((p)->cap = 1), \
((p)->size = sizeof(((p)->arr[0])))))
#define DYNARR_FINI(p) free((p)->arr)
#define DYNARR_PUSH(p, v) \
do { \
if ((p)->len >= (p)->cap) { \
while ((p)->len >= ((p)->cap *= 2)) \
; \
(p)->arr = reallocarray((p)->arr, (p)->cap, (p)->size); \
} \
(p)->arr[(p)->len++] = (v); \
} while (0)
#define DYNARR_POP(p) ((p)->arr[(p)->len--])
#define DYNARR_CLR(p) ((p)->len = 0)
#endif

122
mmsg/mmsg.1 Normal file
View file

@ -0,0 +1,122 @@
.TH "mmsg" "1" "2026-05-31" "mmsg 0.14.0" "mmsg manual"
.SH NAME
mmsg \- send commands to the mango Wayland compositor and receive JSON responses
.SH SYNOPSIS
\fBmmsg\fR <command> [\fIargs\fR...]
.br
\fBmmsg\fR \-\-help | \-h | help
.SH DESCRIPTION
\fBmmsg\fR connects to a running \fBmango\fR(1) compositor via the UNIX domain socket
specified by the \fIMANGO_INSTANCE_SIGNATURE\fR environment variable. It sends
the given command and prints the JSON reply to standard output. In \fBwatch\fR
mode it keeps the connection open and prints a stream of updates as they
occur.
.PP
If \fB\-\-help\fR, \fB\-h\fR or \fBhelp\fR is given, a complete usage summary is printed and
the program exits with success.
.SH COMMANDS
.SS "One\-shot queries (get)"
All \fBget\fR commands print a single JSON object and then close the connection.
.TP
\fBget version\fR
Return compositor version.
.TP
\fBget keymode\fR
Return current keymode.
.TP
\fBget keyboardlayout\fR
Return current keyboard layout.
.TP
\fBget last_open_surface\fR [\fImonitor\fR]
Return the last open surface (application) for the given monitor.
If \fImonitor\fR is omitted the focused monitor is used.
.TP
\fBget monitor\fR <name>
Return details of the named monitor.
.TP
\fBget focusing\-client\fR
Return details of the currently focused client.
.TP
\fBget client\fR <id>
Return details of the client with the given numeric ID.
.TP
\fBget tag\fR <monitor> <index>
Return details of a specific tag (1\-based index) on the named monitor.
.TP
\fBget all\-clients\fR
List all clients.
.TP
\fBget all\-monitors\fR
List all monitors.
.TP
\fBget all\-tags\fR
List tags for all monitors.
.TP
\fBget tags\fR <monitor>
List tags for a specific monitor.
.TP
\fBdispatch\fR <func>[,arg...] [client,<id>]
Invoke an internal compositor function.
The function name and its arguments are separated by commas.
Optionally add \fBclient,<id>\fR (before or after the function) to target a
specific client.
.br
Examples:
.RS
.IP \(bu 2
\fBmmsg dispatch togglefloating\fR
.IP \(bu 2
\fBmmsg dispatch movewin,10,100\fR
.IP \(bu 2
\fBmmsg dispatch movewin,10,100 client,4\fR
.RE
.SS "Persistent streams (watch)"
\fBWatch\fR commands keep the connection open and continuously output JSON
updates. The initial state is sent immediately, followed by updates whenever
relevant changes occur.
.TP
\fBwatch monitor\fR <name>
Stream updates for the named monitor.
.TP
\fBwatch focusing\-client\fR
Stream updates for the focused client.
.TP
\fBwatch client\fR <id>
Stream updates for the client with the given ID.
.TP
\fBwatch tags\fR <monitor>
Stream tag updates for the named monitor.
.TP
\fBwatch all\-monitors\fR
Stream updates for all monitors.
.TP
\fBwatch all\-tags\fR
Stream updates for all tags.
.TP
\fBwatch all\-clients\fR
Stream updates for all clients.
.TP
\fBwatch keymode\fR
Stream keymode changes.
.TP
\fBwatch keyboardlayout\fR
Stream keyboard layout changes.
.TP
\fBwatch last_open_surface\fR [\fImonitor\fR]
Stream last open surface changes for a specific monitor (or the focused one).
.SH ENVIRONMENT
.TP
\fIMANGO_INSTANCE_SIGNATURE\fR
Path to the compositor's IPC socket. Set automatically by \fBmango\fR(1).
If unset, \fBmmsg\fR prints an error and exits.
.SH EXIT STATUS
.TP
\fB0\fR
Success (or help message printed).
.TP
\fBEXIT_FAILURE\fR
An error occurred (connection refused, send/receive error, etc.).
.SH SEE ALSO
\fBmango\fR(1)
.SH BUGS
Report issues at the project's issue tracker.

View file

@ -1,789 +1,156 @@
#include "arg.h"
#include "dwl-ipc-unstable-v2-protocol.h"
#include "dynarr.h"
#include <ctype.h>
#include <poll.h>
#define _GNU_SOURCE
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland-util.h>
#define die(fmt, ...) \
do { \
fprintf(stderr, fmt "\n", ##__VA_ARGS__); \
exit(EXIT_FAILURE); \
} while (0)
char *argv0;
static enum {
NONE = 0,
SET = 1 << 0,
GET = 1 << 1,
WATCH = 1 << 2 | GET,
} mode = NONE;
static int32_t Oflag;
static int32_t Tflag;
static int32_t Lflag;
static int32_t oflag;
static int32_t tflag;
static int32_t lflag;
static int32_t cflag;
static int32_t vflag;
static int32_t mflag;
static int32_t fflag;
static int32_t qflag;
static int32_t dflag;
static int32_t xflag;
static int32_t eflag;
static int32_t kflag;
static int32_t bflag;
static int32_t Aflag;
static uint32_t occ, seltags, total_clients, urg;
static char *output_name;
static int32_t tagcount;
static char *tagset;
static char *layout_name;
static int32_t layoutcount, layout_idx;
static char *client_tags;
static char *dispatch_cmd;
static char *dispatch_arg1;
static char *dispatch_arg2;
static char *dispatch_arg3;
static char *dispatch_arg4;
static char *dispatch_arg5;
struct output {
char *output_name;
uint32_t name;
};
static DYNARR_DEF(struct output) outputs;
static struct wl_display *display;
static struct zdwl_ipc_manager_v2 *dwl_ipc_manager;
// 为每个回调定义专用的空函数
static void noop_geometry(void *data, struct wl_output *wl_output, int32_t x,
int32_t y, int32_t physical_width,
int32_t physical_height, int32_t subpixel,
const char *make, const char *model,
int32_t transform) {}
static void noop_mode(void *data, struct wl_output *wl_output, uint32_t flags,
int32_t width, int32_t height, int32_t refresh) {}
static void noop_done(void *data, struct wl_output *wl_output) {}
static void noop_scale(void *data, struct wl_output *wl_output,
int32_t factor) {}
static void noop_description(void *data, struct wl_output *wl_output,
const char *description) {}
// 将 n 转换为 9 位二进制字符串,结果存入 buf至少长度 10
void bin_str_9bits(char *buf, uint32_t n) {
for (int32_t i = 8; i >= 0; i--) {
*buf++ = ((n >> i) & 1) ? '1' : '0';
}
*buf = '\0'; // 字符串结尾
}
static void dwl_ipc_tags(void *data,
struct zdwl_ipc_manager_v2 *dwl_ipc_manager,
uint32_t count) {
tagcount = count;
if (Tflag && mode & GET)
printf("%d\n", tagcount);
}
static void dwl_ipc_layout(void *data,
struct zdwl_ipc_manager_v2 *dwl_ipc_manager,
const char *name) {
if (lflag && mode & SET && strcmp(layout_name, name) == 0)
layout_idx = layoutcount;
if (Lflag && mode & GET)
printf("%s\n", name);
layoutcount++;
}
static const struct zdwl_ipc_manager_v2_listener dwl_ipc_listener = {
.tags = dwl_ipc_tags, .layout = dwl_ipc_layout};
static void
dwl_ipc_output_toggle_visibility(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output) {
if (!vflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("toggle\n");
}
static void dwl_ipc_output_active(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
uint32_t active) {
if (!oflag) {
if (mode & SET && !output_name && active)
output_name = strdup(data);
return;
}
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("selmon %u\n", active ? 1 : 0);
}
static void dwl_ipc_output_tag(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
uint32_t tag, uint32_t state, uint32_t clients,
uint32_t focused) {
if (!tflag)
return;
if (state == ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE)
seltags |= 1 << tag;
if (state == ZDWL_IPC_OUTPUT_V2_TAG_STATE_URGENT)
urg |= 1 << tag;
if (clients > 0)
occ |= 1 << tag;
// 累计所有 tag 的 clients 总数
total_clients += clients;
if (!(mode & GET))
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("tag %u %u %u %u\n", tag + 1, state, clients, focused);
}
static void dwl_ipc_output_layout(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
uint32_t layout) {}
static void dwl_ipc_output_layout_symbol(
void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output, const char *layout) {
if (!(lflag && mode & GET))
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("layout %s\n", layout);
}
static void dwl_ipc_output_title(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
const char *title) {
if (!(cflag && mode & GET))
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("title %s\n", title);
}
static void dwl_ipc_output_appid(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
const char *appid) {
if (!(cflag && mode & GET))
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("appid %s\n", appid);
}
static void dwl_ipc_output_x(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
int32_t x) {
if (!xflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("x %d\n", x);
}
static void dwl_ipc_output_y(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
int32_t y) {
if (!xflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("y %d\n", y);
}
static void dwl_ipc_output_width(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
int32_t width) {
if (!xflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("width %d\n", width);
}
static void dwl_ipc_output_height(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
int32_t height) {
if (!xflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("height %d\n", height);
}
static void dwl_ipc_output_last_layer(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
const char *last_layer) {
if (!eflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("last_layer %s\n", last_layer);
}
static void dwl_ipc_output_kb_layout(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
const char *kb_layout) {
if (!kflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("kb_layout %s\n", kb_layout);
}
static void
dwl_ipc_output_scalefactor(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
const uint32_t scalefactor) {
if (!Aflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("scale_factor %f\n", scalefactor / 100.0f);
}
static void dwl_ipc_output_keymode(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
const char *keymode) {
if (!bflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("keymode %s\n", keymode);
}
static void dwl_ipc_output_fullscreen(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
uint32_t is_fullscreen) {
if (!mflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("fullscreen %u\n", is_fullscreen);
}
static void dwl_ipc_output_floating(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output,
uint32_t is_floating) {
if (!fflag)
return;
char *output_name = data;
if (output_name)
printf("%s ", output_name);
printf("floating %u\n", is_floating);
}
static void dwl_ipc_output_frame(void *data,
struct zdwl_ipc_output_v2 *dwl_ipc_output) {
if (mode & SET) {
if (data && (!output_name || strcmp(output_name, (char *)data)))
return;
if (qflag) {
zdwl_ipc_output_v2_quit(dwl_ipc_output);
}
if (lflag) {
zdwl_ipc_output_v2_set_layout(dwl_ipc_output, layout_idx);
}
if (tflag) {
uint32_t mask = seltags;
char *t = tagset;
int32_t i = 0;
for (; *t && *t >= '0' && *t <= '9'; t++)
i = *t - '0' + i * 10;
if (!*t)
mask = 1 << (i - 1);
for (; *t; t++, i++) {
switch (*t) {
case '-':
mask &= ~(1 << (i - 1));
break;
case '+':
mask |= 1 << (i - 1);
break;
case '^':
mask ^= 1 << (i - 1);
break;
}
}
if ((i - 1) > tagcount)
die("bad tagset %s", tagset);
zdwl_ipc_output_v2_set_tags(dwl_ipc_output, mask, 0);
}
if (cflag) {
uint32_t and = ~0, xor = 0;
char *t = client_tags;
int32_t i = 0;
for (; *t && *t >= '0' && *t <= '9'; t++)
i = *t - '0' + i * 10;
if (!*t)
t = "+";
for (; *t; t++, i++) {
switch (*t) {
case '-':
and &= ~(1 << (i - 1));
break;
case '+':
and &= ~(1 << (i - 1));
xor |= 1 << (i - 1);
break;
case '^':
xor |= 1 << (i - 1);
break;
}
}
if ((i - 1) > tagcount)
die("bad client tagset %s", client_tags);
zdwl_ipc_output_v2_set_client_tags(dwl_ipc_output, and, xor);
}
if (dflag) {
zdwl_ipc_output_v2_dispatch(
dwl_ipc_output, dispatch_cmd, dispatch_arg1, dispatch_arg2,
dispatch_arg3, dispatch_arg4, dispatch_arg5);
}
wl_display_flush(display);
usleep(1000);
exit(0);
} else {
if (tflag) {
char *output_name = data;
printf("%s clients %u\n", output_name, total_clients);
char occ_str[10], seltags_str[10], urg_str[10];
bin_str_9bits(occ_str, occ);
bin_str_9bits(seltags_str, seltags);
bin_str_9bits(urg_str, urg);
printf("%s tags %u %u %u\n", output_name, occ, seltags, urg);
printf("%s tags %s %s %s\n", output_name, occ_str, seltags_str,
urg_str);
occ = seltags = total_clients = urg = 0;
}
}
fflush(stdout);
}
static const struct zdwl_ipc_output_v2_listener dwl_ipc_output_listener = {
.toggle_visibility = dwl_ipc_output_toggle_visibility,
.active = dwl_ipc_output_active,
.tag = dwl_ipc_output_tag,
.layout = dwl_ipc_output_layout,
.title = dwl_ipc_output_title,
.appid = dwl_ipc_output_appid,
.layout_symbol = dwl_ipc_output_layout_symbol,
.fullscreen = dwl_ipc_output_fullscreen,
.floating = dwl_ipc_output_floating,
.x = dwl_ipc_output_x,
.y = dwl_ipc_output_y,
.width = dwl_ipc_output_width,
.height = dwl_ipc_output_height,
.last_layer = dwl_ipc_output_last_layer,
.kb_layout = dwl_ipc_output_kb_layout,
.keymode = dwl_ipc_output_keymode,
.scalefactor = dwl_ipc_output_scalefactor,
.frame = dwl_ipc_output_frame,
};
static void wl_output_name(void *data, struct wl_output *output,
const char *name) {
if (outputs.arr) {
struct output *o = (struct output *)data;
o->output_name = strdup(name);
printf("+ ");
}
if (Oflag)
printf("%s\n", name);
if (output_name && strcmp(output_name, name) != 0) {
wl_output_release(output);
return;
}
struct zdwl_ipc_output_v2 *dwl_ipc_output =
zdwl_ipc_manager_v2_get_output(dwl_ipc_manager, output);
zdwl_ipc_output_v2_add_listener(dwl_ipc_output, &dwl_ipc_output_listener,
output_name ? NULL : strdup(name));
}
static const struct wl_output_listener output_listener = {
.geometry = noop_geometry,
.mode = noop_mode,
.done = noop_done,
.scale = noop_scale,
.name = wl_output_name,
.description = noop_description,
};
static void global_add(void *data, struct wl_registry *wl_registry,
uint32_t name, const char *interface, uint32_t version) {
if (strcmp(interface, wl_output_interface.name) == 0) {
struct wl_output *o =
wl_registry_bind(wl_registry, name, &wl_output_interface,
WL_OUTPUT_NAME_SINCE_VERSION);
if (!outputs.arr) {
wl_output_add_listener(o, &output_listener, NULL);
} else {
DYNARR_PUSH(&outputs, (struct output){.name = name});
wl_output_add_listener(o, &output_listener,
&outputs.arr[outputs.len - 1]);
}
} else if (strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
dwl_ipc_manager = wl_registry_bind(wl_registry, name,
&zdwl_ipc_manager_v2_interface, 2);
zdwl_ipc_manager_v2_add_listener(dwl_ipc_manager, &dwl_ipc_listener,
NULL);
}
}
static void global_remove(void *data, struct wl_registry *wl_registry,
uint32_t name) {
if (!outputs.arr)
return;
struct output *o = outputs.arr;
for (size_t i = 0; i < outputs.len; i++, o++) {
if (o->name == name) {
printf("- %s\n", o->output_name);
free(o->output_name);
*o = DYNARR_POP(&outputs);
}
}
}
static const struct wl_registry_listener registry_listener = {
.global = global_add,
.global_remove = global_remove,
};
static void usage(void) {
fprintf(stderr,
"usage:"
"\t%s [-OTLq]\n"
"\t%s [-o <output>] -s [-t <tags>] [-l <layout>] [-c <tags>] [-d "
"<cmd>,<arg1>,<arg2>,<arg3>,<arg4>,<arg5>]\n"
"\t%s [-o <output>] (-g | -w) [-OotlcvmfxekbA]\n",
argv0, argv0, argv0);
exit(2);
printf("Usage: mmsg <command> [args...]\n\n");
printf("One-shot queries (get):\n");
printf(
" get version Show compositor version\n");
printf(" get keymode Show current keymode\n");
printf(" get keyboardlayout Show current keyboard "
"layout\n");
printf(" get last_open_surface [monitor] Show last open surface "
"(default focused monitor)\n");
printf(" get monitor <name> Show monitor details\n");
printf(" get focusing-client Show focused client "
"details\n");
printf(" get client <id> Show client details by "
"ID\n");
printf(" get tag <monitor> <index> Show tag details "
"(1based index)\n");
printf(" get all-clients List all clients\n");
printf(" get all-monitors List all monitors\n");
printf(" get all-tags List all tags (all "
"monitors)\n");
printf(
" get tags <monitor> List tags for a monitor\n");
printf(" dispatch <func>[,arg...] [client,<id>] Call a compositor "
"function\n");
printf(" <func> and arguments are separated by commas.\n");
printf(" Add 'client,<id>' at the beginning or end to target a "
"specific client.\n");
printf(" Examples:\n");
printf(" dispatch togglefloating\n");
printf(" dispatch movewin,10,100\n");
printf(" dispatch movewin,10,100 client,4\n");
printf("Persistent streams (watch):\n");
printf(
" watch monitor <name> Stream monitor changes\n");
printf(" watch focusing-client Stream focused client "
"changes\n");
printf(
" watch client <id> Stream client changes\n");
printf(" watch tags <monitor> Stream tag changes for "
"a monitor\n");
printf(" watch all-monitors Stream all monitors "
"changes\n");
printf(
" watch all-tags Stream all tags changes\n");
printf(" watch all-clients Stream all clients "
"changes\n");
printf(
" watch keymode Stream keymode changes\n");
printf(" watch keyboardlayout Stream keyboard layout "
"changes\n");
printf(" watch last_open_surface [monitor] Stream last open "
"surface changes\n\n");
printf("Environment:\n");
printf(" MANGO_INSTANCE_SIGNATURE IPC socket path (set by the "
"compositor)\n\n");
printf("Run 'mmsg --help', '-h' or 'help' to see this message.\n");
}
int32_t main(int32_t argc, char *argv[]) {
ARGBEGIN {
case 'q':
qflag = 1;
if (!(mode & GET)) {
mode = SET;
}
break;
case 's':
if (mode != NONE)
usage();
mode = SET;
break;
case 'g':
if (mode != NONE)
usage();
mode = GET;
break;
case 'w':
if (mode != NONE)
usage();
mode = WATCH;
break;
case 'o':
if (mode == SET)
output_name = EARGF(usage());
else
output_name = ARGF();
if (!output_name)
oflag = 1;
break;
case 't':
tflag = 1;
if (!(mode & GET)) {
mode = SET;
tagset = EARGF(usage());
}
break;
case 'l':
lflag = 1;
if (!(mode & GET)) {
mode = SET;
layout_name = EARGF(usage());
}
break;
case 'c':
cflag = 1;
if (!(mode & GET)) {
mode = SET;
client_tags = EARGF(usage());
}
break;
case 'd':
dflag = 1;
if (!(mode & GET)) {
mode = SET;
char *arg = EARGF(usage());
// Trim leading and trailing whitespace from entire argument first
while (isspace(*arg))
arg++;
char *end = arg + strlen(arg) - 1;
while (end > arg && isspace(*end))
end--;
*(end + 1) = '\0';
dispatch_cmd = arg;
char *comma1 = strchr(arg, ',');
if (comma1) {
*comma1 = '\0';
// Trim trailing whitespace from command
end = dispatch_cmd + strlen(dispatch_cmd) - 1;
while (end > dispatch_cmd && isspace(*end))
end--;
*(end + 1) = '\0';
dispatch_arg1 = comma1 + 1;
// Trim leading whitespace from arg1
while (isspace(*dispatch_arg1))
dispatch_arg1++;
// Trim trailing whitespace from arg1 before looking for next
// comma
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 + 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 {
dispatch_arg1 = "";
dispatch_arg2 = "";
dispatch_arg3 = "";
dispatch_arg4 = "";
dispatch_arg5 = "";
}
}
break;
case 'O':
Oflag = 1;
if (mode && !(mode & GET))
usage();
if (mode & WATCH)
DYNARR_INIT(&outputs);
else
mode = GET;
break;
case 'T':
Tflag = 1;
if (mode && mode != GET)
usage();
mode = GET;
break;
case 'L':
Lflag = 1;
if (mode && mode != GET)
usage();
mode = GET;
break;
case 'v':
vflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'm':
mflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'f':
fflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'x':
xflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'e':
eflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'k':
kflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'b':
bflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
case 'A':
Aflag = 1;
if (mode == SET)
usage();
mode |= GET;
break;
default:
fprintf(stderr, "bad option %c\n", ARGC());
int main(int argc, char *argv[]) {
if (argc >= 2 &&
(strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0 ||
strcmp(argv[1], "help") == 0)) {
usage();
return EXIT_SUCCESS;
}
ARGEND
if (mode == NONE)
usage();
if (mode & GET && !output_name &&
!(oflag || tflag || lflag || Oflag || Tflag || Lflag || cflag ||
vflag || mflag || fflag || xflag || eflag || kflag || bflag ||
Aflag || dflag))
oflag = tflag = lflag = cflag = vflag = mflag = fflag = xflag = eflag =
kflag = bflag = Aflag = 1;
display = wl_display_connect(NULL);
if (!display)
die("bad display");
if (argc < 2) {
fprintf(stderr, "Usage: mmsg <command> [args...]\n");
fprintf(stderr, " get <type> ... one-shot request\n");
fprintf(stderr, " watch <type> ... persistent stream\n");
return EXIT_FAILURE;
}
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, NULL);
const char *socket_path = getenv("MANGO_INSTANCE_SIGNATURE");
if (!socket_path) {
fprintf(stderr, "Error: MANGO_INSTANCE_SIGNATURE is not set. Did you "
"run 'mmsg' in mango?\n");
return EXIT_FAILURE;
}
wl_display_dispatch(display);
wl_display_roundtrip(display);
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return EXIT_FAILURE;
}
if (!dwl_ipc_manager)
die("bad dwl-ipc protocol");
struct sockaddr_un addr = {.sun_family = AF_UNIX};
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
wl_display_roundtrip(display);
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect");
close(sock);
return EXIT_FAILURE;
}
if (mode == WATCH)
while (wl_display_dispatch(display) != -1)
;
char cmd[4096] = {0};
int offset = 0;
for (int i = 1; i < argc; i++) {
int n = snprintf(cmd + offset, sizeof(cmd) - offset, "%s%s", argv[i],
(i == argc - 1) ? "" : " ");
if (n < 0 || n >= (int)(sizeof(cmd) - offset)) {
fprintf(stderr, "Error: command too long.\n");
close(sock);
return EXIT_FAILURE;
}
offset += n;
}
return 0;
}
int n = snprintf(cmd + offset, sizeof(cmd) - offset, "\n");
if (n < 0 || n >= (int)(sizeof(cmd) - offset)) {
fprintf(stderr, "Error: command too long to append newline.\n");
close(sock);
return EXIT_FAILURE;
}
if (send(sock, cmd, strlen(cmd), MSG_NOSIGNAL) < 0) {
perror("send");
close(sock);
return EXIT_FAILURE;
}
FILE *stream = fdopen(sock, "r");
if (!stream) {
perror("fdopen");
close(sock);
return EXIT_FAILURE;
}
char *line = NULL;
size_t len = 0;
while (getline(&line, &len, stream) != -1) {
printf("%s", line);
fflush(stdout);
}
if (ferror(stream)) {
perror("recv");
free(line);
fclose(stream);
return EXIT_FAILURE;
}
free(line);
fclose(stream);
return EXIT_SUCCESS;
}

View file

@ -3,15 +3,18 @@
libX11,
libinput,
libxcb,
libdrm,
libxkbcommon,
pcre2,
pango,
cjson,
pixman,
pkg-config,
stdenv,
wayland,
wayland-protocols,
wayland-scanner,
xcbutilwm,
libxcb-wm,
xwayland,
meson,
ninja,
@ -48,16 +51,19 @@ stdenv.mkDerivation {
libxcb
libxkbcommon
pcre2
pango
cjson
pixman
wayland
wayland-protocols
wlroots_0_19
scenefx
libGL
libdrm
]
++ lib.optionals enableXWayland [
libX11
xcbutilwm
libxcb-wm
xwayland
];
@ -67,8 +73,8 @@ stdenv.mkDerivation {
meta = {
mainProgram = "mango";
description = "A streamlined but feature-rich Wayland compositor";
homepage = "https://github.com/DreamMaoMao/mango";
description = "Practical and Powerful wayland compositor (dwm but wayland)";
homepage = "https://github.com/mangowm/mango";
license = lib.licenses.gpl3Plus;
maintainers = [];
platforms = lib.platforms.unix;

42
nix/generate-options.nix Normal file
View file

@ -0,0 +1,42 @@
self:
{
pkgs,
lib ? pkgs.lib,
module,
optionPrefix,
}:
let
# Absolute store path of the flake root, used to compute relative subpaths
repoPath = toString self;
eval = lib.evalModules {
modules = [
(import module self)
{ _module.check = false; }
];
specialArgs = { inherit pkgs; };
};
# Relative path of the module file within the repo (e.g. "nix/hm-modules.nix")
moduleSubpath = lib.removePrefix "/" (lib.removePrefix repoPath (toString module));
# Declaration entry linking each option back to its source file on GitHub
moduleDeclaration = {
url = "https://github.com/mangowm/mango/blob/main/${moduleSubpath}";
name = "<mango/${moduleSubpath}>";
};
optionsDoc = pkgs.nixosOptionsDoc {
options = eval.options;
transformOptions =
opt:
opt
// {
visible = opt.visible && !opt.internal;
# Strip the option prefix so docs show "enable" instead of "programs.mango.enable"
name = lib.removePrefix optionPrefix opt.name;
declarations = [ moduleDeclaration ];
};
};
in
optionsDoc.optionsJSON

View file

@ -1,23 +1,28 @@
self: {
self:
{
lib,
config,
pkgs,
...
}: let
}:
let
cfg = config.wayland.windowManager.mango;
selflib = import ./lib.nix lib;
variables = lib.concatStringsSep " " cfg.systemd.variables;
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" ''
${lib.optionalString cfg.systemd.enable systemdActivation}
${cfg.autostart_sh}
'';
in {
in
{
options = {
wayland.windowManager.mango = with lib; {
enable = mkOption {
type = types.bool;
default = false;
description = "Whether to enable mangowm, a Wayland compositor based on dwl.";
};
package = lib.mkOption {
type = lib.types.package;
@ -54,7 +59,7 @@ in {
"XCURSOR_THEME"
"XCURSOR_SIZE"
];
example = ["--all"];
example = [ "--all" ];
description = ''
Environment variables imported into the systemd and D-Bus user environment.
'';
@ -75,50 +80,195 @@ in {
'';
};
settings = mkOption {
description = "mango config content";
type = types.lines;
default = "";
example = ''
# menu and terminal
bind=Alt,space,spawn,rofi -show drun
bind=Alt,Return,spawn,foot
type =
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://mangowm.github.io/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 {
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;
default = "";
example = ''
waybar &
dunst &
'';
};
};
};
config = lib.mkIf cfg.enable {
home.packages = [cfg.package];
xdg.configFile = {
"mango/config.conf" = lib.mkIf (cfg.settings != "") {
text = cfg.settings;
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 ];
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 != "") {
source = autostart_sh;
executable = true;
};
};
systemd.user.targets.mango-session = lib.mkIf cfg.systemd.enable {
Unit = {
Description = "mango compositor session";
Documentation = ["man:systemd.special(7)"];
BindsTo = ["graphical-session.target"];
Wants =
[
systemd.user.targets.mango-session = lib.mkIf cfg.systemd.enable {
Unit = {
Description = "mango compositor session";
Documentation = [ "man:systemd.special(7)" ];
BindsTo = [ "graphical-session.target" ];
Wants = [
"graphical-session-pre.target"
]
++ lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target";
After = ["graphical-session-pre.target"];
Before = lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target";
After = [ "graphical-session-pre.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

@ -9,6 +9,11 @@ in {
options = {
programs.mango = {
enable = lib.mkEnableOption "mango, a wayland compositor based on dwl";
addLoginEntry = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to add a login entry to the display manager for mango. Only has effect if a display manager is configured (e.g. SDDM, GDM via `services.displayManager`).";
};
package = lib.mkOption {
type = lib.types.package;
default = self.packages.${pkgs.stdenv.hostPlatform.system}.mango;
@ -55,7 +60,7 @@ in {
programs.xwayland.enable = lib.mkDefault true;
services = {
displayManager.sessionPackages = [cfg.package];
displayManager.sessionPackages = lib.mkIf cfg.addLoginEntry [ cfg.package ];
graphical-desktop.enable = lib.mkDefault true;
};

View file

@ -1,6 +1,6 @@
wayland_scanner = find_program('wayland-scanner')
wayland_protos_dep = dependency('wayland-protocols')
wl_protocol_dir = wayland_protos_dep.get_pkgconfig_variable('pkgdatadir')
wl_protocol_dir = wayland_protos_dep.get_variable(pkgconfig:'pkgdatadir')
wayland_scanner_code = generator(
wayland_scanner,
output: '@BASENAME@-protocol.c',

122
src/action/client.h Normal file
View file

@ -0,0 +1,122 @@
static void client_swap_layout_properties(Client *c1, Client *c2) {
// Grid 属性交换
double grid_col_per = c1->grid_col_per;
double grid_row_per = c1->grid_row_per;
int32_t grid_col_idx = c1->grid_col_idx;
int32_t grid_row_idx = c1->grid_row_idx;
c1->grid_col_per = c2->grid_col_per;
c1->grid_row_per = c2->grid_row_per;
c1->grid_col_idx = c2->grid_col_idx;
c1->grid_row_idx = c2->grid_row_idx;
c2->grid_col_per = grid_col_per;
c2->grid_row_per = grid_row_per;
c2->grid_col_idx = grid_col_idx;
c2->grid_row_idx = grid_row_idx;
// Master / Stack 属性交换
double master_inner_per = c1->master_inner_per;
double master_mfact_per = c1->master_mfact_per;
double stack_inner_per = c1->stack_inner_per;
c1->master_inner_per = c2->master_inner_per;
c1->master_mfact_per = c2->master_mfact_per;
c1->stack_inner_per = c2->stack_inner_per;
c2->master_inner_per = master_inner_per;
c2->master_mfact_per = master_mfact_per;
c2->stack_inner_per = stack_inner_per;
}
static void client_swap_monitors_and_tags(Client *c1, Client *c2) {
Monitor *tmp_mon = c2->mon;
uint32_t tmp_tags = c2->tags;
c2->mon = c1->mon;
c1->mon = tmp_mon;
c2->tags = c1->tags;
c1->tags = tmp_tags;
}
static void finish_exchange_arrange_and_focus(Client *c1, Client *c2,
Monitor *m1, Monitor *m2) {
if (m1 != m2) {
arrange(c1->mon, false, false);
arrange(c2->mon, false, false);
} else {
arrange(c1->mon, false, false);
}
wl_list_remove(&c2->flink);
wl_list_insert(&c1->flink, &c2->flink);
if (config.warpcursor)
warp_cursor(c1);
}
void client_tile_resize(Client *c, struct wlr_box geo, int32_t interact) {
if (!ISFAKETILED(c))
return;
if (!c->isfullscreen && !c->ismaximizescreen) {
resize(c, geo, interact);
}
}
static uint32_t next_client_id = 0;
uint32_t generate_client_id(void) { return ++next_client_id; }
void client_active(Client *c) {
uint32_t target;
if (client_is_unmanaged(c)) {
focusclient(c, 1);
return;
}
if (c->swallowing || !c->mon)
return;
if (c->isminimized) {
c->is_in_scratchpad = 0;
c->isnamedscratchpad = 0;
c->is_scratchpad_show = 0;
setborder_color(c);
show_hide_client(c);
arrange(c->mon, true, false);
return;
}
target = get_tags_first_tag(c->tags);
view_in_mon(&(Arg){.ui = target}, true, c->mon, true);
focusclient(c, 1);
}
void client_pending_force_kill(Client *c) {
if (!c)
return;
kill(c->pid, SIGKILL);
}
void client_add_jump_label_node(Client *c) {
c->jump_label_node =
mango_jump_label_node_create(c->scene, config.jumplabeldata);
wlr_scene_node_lower_to_bottom(&c->jump_label_node->scene_buffer->node);
wlr_scene_node_set_enabled(&c->jump_label_node->scene_buffer->node, false);
}
void client_add_tab_bar_node(Client *c) {
if (config.tab_bar_height <= 0) {
return;
}
MangoNodeData *mangonodedata = ecalloc(1, sizeof(MangoNodeData));
mangonodedata->node_data = c;
mangonodedata->type = MANGO_TITLE_NODE;
c->tab_bar_node = mango_tab_bar_node_create(
mangonodedata, layers[LyrDecorate], config.tabdata, 0, 0);
wlr_scene_node_lower_to_bottom(&c->tab_bar_node->scene_buffer->node);
wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, false);
mango_tab_bar_node_update(c->tab_bar_node, client_get_title(c), 1.0);
}

File diff suppressed because it is too large Load diff

View file

@ -2,21 +2,21 @@ struct dvec2 calculate_animation_curve_at(double t, int32_t type) {
struct dvec2 point;
double *animation_curve;
if (type == MOVE) {
animation_curve = animation_curve_move;
animation_curve = config.animation_curve_move;
} else if (type == OPEN) {
animation_curve = animation_curve_open;
animation_curve = config.animation_curve_open;
} else if (type == TAG) {
animation_curve = animation_curve_tag;
animation_curve = config.animation_curve_tag;
} else if (type == CLOSE) {
animation_curve = animation_curve_close;
animation_curve = config.animation_curve_close;
} else if (type == FOCUS) {
animation_curve = animation_curve_focus;
animation_curve = config.animation_curve_focus;
} else if (type == OPAFADEIN) {
animation_curve = animation_curve_opafadein;
animation_curve = config.animation_curve_opafadein;
} else if (type == OPAFADEOUT) {
animation_curve = animation_curve_opafadeout;
animation_curve = config.animation_curve_opafadeout;
} else {
animation_curve = animation_curve_move;
animation_curve = config.animation_curve_move;
}
point.x = 3 * t * (1 - t) * (1 - t) * animation_curve[0] +
@ -28,6 +28,12 @@ struct dvec2 calculate_animation_curve_at(double t, int32_t type) {
return point;
}
void handle_snapshot_meta_destroy(struct wl_listener *listener, void *data) {
SnapshotMetadata *meta = wl_container_of(listener, meta, destroy);
wl_list_remove(&meta->destroy.link); // 安全移除监听器
free(meta);
}
void init_baked_points(void) {
baked_points_move = calloc(BAKED_POINTS_COUNT, sizeof(*baked_points_move));
baked_points_open = calloc(BAKED_POINTS_COUNT, sizeof(*baked_points_open));
@ -154,12 +160,43 @@ static bool scene_node_snapshot(struct wlr_scene_node *node, int32_t lx,
struct wlr_scene_buffer *scene_buffer =
wlr_scene_buffer_from_node(node);
struct wlr_scene_buffer *snapshot_buffer =
wlr_scene_buffer_create(snapshot_tree, NULL);
if (snapshot_buffer == NULL) {
// 创建中间包装树节点
struct wlr_scene_tree *wrapper = wlr_scene_tree_create(snapshot_tree);
if (wrapper == NULL) {
return false;
}
snapshot_node = &snapshot_buffer->node;
snapshot_node = &wrapper->node; // 坐标位移应用在外层包装盒上
// 收集表面状态并保存为元数据
SnapshotMetadata *meta = calloc(1, sizeof(SnapshotMetadata));
if (meta == NULL) {
wlr_scene_node_destroy(&wrapper->node);
return false;
}
meta->orig_width = scene_buffer->dst_width;
meta->orig_height = scene_buffer->dst_height;
struct wlr_scene_surface *scene_surface =
wlr_scene_surface_try_from_buffer(scene_buffer);
if (scene_surface != NULL) {
meta->is_subsurface =
!!wlr_subsurface_try_from_wlr_surface(scene_surface->surface);
}
// 绑定销毁回调监听,随包装节点销毁而释放内存
meta->destroy.notify = handle_snapshot_meta_destroy;
wl_signal_add(&wrapper->node.events.destroy, &meta->destroy);
wrapper->node.data = meta;
// 将真正的 buffer 挂靠在 wrapper 下面相对坐标0,0
struct wlr_scene_buffer *snapshot_buffer =
wlr_scene_buffer_create(wrapper, NULL);
if (snapshot_buffer == NULL) {
wlr_scene_node_destroy(&wrapper->node);
return false;
}
// 保留原生的 data 指针(如 Client*),防止事件派发/焦点获取失效
snapshot_buffer->node.data = scene_buffer->node.data;
wlr_scene_buffer_set_dest_size(snapshot_buffer, scene_buffer->dst_width,
@ -179,16 +216,8 @@ static bool scene_node_snapshot(struct wlr_scene_node *node, int32_t lx,
scene_buffer->corner_radius,
scene_buffer->corners);
// wlr_scene_buffer_set_backdrop_blur_optimized(
// snapshot_buffer, scene_buffer->backdrop_blur_optimized);
// wlr_scene_buffer_set_backdrop_blur_ignore_transparent(
// snapshot_buffer, scene_buffer->backdrop_blur_ignore_transparent);
wlr_scene_buffer_set_backdrop_blur(snapshot_buffer, false);
snapshot_buffer->node.data = scene_buffer->node.data;
struct wlr_scene_surface *scene_surface =
wlr_scene_surface_try_from_buffer(scene_buffer);
if (scene_surface != NULL && scene_surface->surface->buffer != NULL) {
wlr_scene_buffer_set_buffer(snapshot_buffer,
&scene_surface->surface->buffer->base);

View file

@ -156,7 +156,7 @@ void layer_draw_shadow(LayerSurface *l) {
if (!l->mapped || !l->shadow)
return;
if (!shadows || !layer_shadows || l->noshadow) {
if (!config.shadows || !config.layer_shadows || l->noshadow) {
wlr_scene_shadow_set_size(l->shadow, 0, 0);
return;
}
@ -164,9 +164,8 @@ void layer_draw_shadow(LayerSurface *l) {
int32_t width, height;
layer_actual_size(l, &width, &height);
int32_t delta = shadows_size;
int32_t delta = config.shadows_size;
/* we calculate where to clip the shadow */
struct wlr_box layer_box = {
.x = 0,
.y = 0,
@ -175,23 +174,21 @@ void layer_draw_shadow(LayerSurface *l) {
};
struct wlr_box shadow_box = {
.x = shadows_position_x,
.y = shadows_position_y,
.x = config.shadows_position_x,
.y = config.shadows_position_y,
.width = width + 2 * delta,
.height = height + 2 * delta,
};
struct wlr_box intersection_box;
wlr_box_intersection(&intersection_box, &layer_box, &shadow_box);
/* clipped region takes shadow relative coords, so we translate everything
* by its position */
intersection_box.x -= shadows_position_x;
intersection_box.y -= shadows_position_y;
intersection_box.x -= config.shadows_position_x;
intersection_box.y -= config.shadows_position_y;
struct clipped_region clipped_region = {
.area = intersection_box,
.corner_radius = border_radius,
.corners = border_radius_location_default,
.corner_radius = config.border_radius,
.corners = config.border_radius_location_default,
};
wlr_scene_node_set_position(&l->shadow->node, shadow_box.x, shadow_box.y);
@ -261,7 +258,7 @@ void fadeout_layer_animation_next_tick(LayerSurface *l) {
buffer_data.height = height;
if ((!l->animation_type_close &&
strcmp(layer_animation_type_close, "zoom") == 0) ||
strcmp(config.layer_animation_type_close, "zoom") == 0) ||
(l->animation_type_close &&
strcmp(l->animation_type_close, "zoom") == 0)) {
wlr_scene_node_for_each_buffer(&l->scene->node,
@ -279,12 +276,12 @@ void fadeout_layer_animation_next_tick(LayerSurface *l) {
double opacity_eased_progress =
find_animation_curve_at(animation_passed, OPAFADEOUT);
double percent = fadeout_begin_opacity -
(opacity_eased_progress * fadeout_begin_opacity);
double percent = config.fadeout_begin_opacity -
(opacity_eased_progress * config.fadeout_begin_opacity);
double opacity = MAX(percent, 0.0f);
double opacity = MANGO_MAX(percent, 0.0f);
if (animation_fade_out)
if (config.animation_fade_out)
wlr_scene_node_for_each_buffer(&l->scene->node,
scene_buffer_apply_opacity, &opacity);
@ -326,12 +323,12 @@ void layer_animation_next_tick(LayerSurface *l) {
double opacity_eased_progress =
find_animation_curve_at(animation_passed, OPAFADEIN);
double opacity =
MIN(fadein_begin_opacity +
opacity_eased_progress * (1.0 - fadein_begin_opacity),
1.0f);
double opacity = MANGO_MIN(config.fadein_begin_opacity +
opacity_eased_progress *
(1.0 - config.fadein_begin_opacity),
1.0f);
if (animation_fade_in)
if (config.animation_fade_in)
wlr_scene_node_for_each_buffer(&l->scene->node,
scene_buffer_apply_opacity, &opacity);
@ -347,7 +344,7 @@ void layer_animation_next_tick(LayerSurface *l) {
}
if ((!l->animation_type_open &&
strcmp(layer_animation_type_open, "zoom") == 0) ||
strcmp(config.layer_animation_type_open, "zoom") == 0) ||
(l->animation_type_open &&
strcmp(l->animation_type_open, "zoom") == 0)) {
wlr_scene_node_for_each_buffer(
@ -370,7 +367,7 @@ void layer_animation_next_tick(LayerSurface *l) {
void init_fadeout_layers(LayerSurface *l) {
if (!animations || !layer_animations || l->noanim) {
if (!config.animations || !config.layer_animations || l->noanim) {
return;
}
@ -380,7 +377,7 @@ void init_fadeout_layers(LayerSurface *l) {
if ((l->animation_type_close &&
strcmp(l->animation_type_close, "none") == 0) ||
(!l->animation_type_close &&
strcmp(layer_animation_type_close, "none") == 0)) {
strcmp(config.layer_animation_type_close, "none") == 0)) {
return;
}
@ -403,7 +400,7 @@ void init_fadeout_layers(LayerSurface *l) {
return;
}
fadeout_layer->animation.duration = animation_duration_close;
fadeout_layer->animation.duration = config.animation_duration_close;
fadeout_layer->geom = fadeout_layer->current =
fadeout_layer->animainit_geom = fadeout_layer->animation.initial =
l->animation.current;
@ -419,14 +416,14 @@ void init_fadeout_layers(LayerSurface *l) {
fadeout_layer->animation.initial.y = 0;
if ((!l->animation_type_close &&
strcmp(layer_animation_type_close, "zoom") == 0) ||
strcmp(config.layer_animation_type_close, "zoom") == 0) ||
(l->animation_type_close &&
strcmp(l->animation_type_close, "zoom") == 0)) {
// 算出要设置的绝对坐标和大小
fadeout_layer->current.width =
(float)l->animation.current.width * zoom_end_ratio;
(float)l->animation.current.width * config.zoom_end_ratio;
fadeout_layer->current.height =
(float)l->animation.current.height * zoom_end_ratio;
(float)l->animation.current.height * config.zoom_end_ratio;
fadeout_layer->current.x = usable_area.x + usable_area.width / 2 -
fadeout_layer->current.width / 2;
fadeout_layer->current.y = usable_area.y + usable_area.height / 2 -
@ -438,7 +435,7 @@ void init_fadeout_layers(LayerSurface *l) {
fadeout_layer->current.y - l->animation.current.y;
} else if ((!l->animation_type_close &&
strcmp(layer_animation_type_close, "slide") == 0) ||
strcmp(config.layer_animation_type_close, "slide") == 0) ||
(l->animation_type_close &&
strcmp(l->animation_type_close, "slide") == 0)) {
// 获取slide动画的结束绝对坐标和大小
@ -483,17 +480,18 @@ void layer_set_pending_state(LayerSurface *l) {
if (l->animation.action == OPEN && !l->animation.running) {
if ((!l->animation_type_open &&
strcmp(layer_animation_type_open, "zoom") == 0) ||
strcmp(config.layer_animation_type_open, "zoom") == 0) ||
(l->animation_type_open &&
strcmp(l->animation_type_open, "zoom") == 0)) {
l->animainit_geom.width = l->geom.width * zoom_initial_ratio;
l->animainit_geom.height = l->geom.height * zoom_initial_ratio;
l->animainit_geom.width = l->geom.width * config.zoom_initial_ratio;
l->animainit_geom.height =
l->geom.height * config.zoom_initial_ratio;
l->animainit_geom.x = usable_area.x + usable_area.width / 2 -
l->animainit_geom.width / 2;
l->animainit_geom.y = usable_area.y + usable_area.height / 2 -
l->animainit_geom.height / 2;
} else if ((!l->animation_type_open &&
strcmp(layer_animation_type_open, "slide") == 0) ||
strcmp(config.layer_animation_type_open, "slide") == 0) ||
(l->animation_type_open &&
strcmp(l->animation_type_open, "slide") == 0)) {
@ -507,8 +505,7 @@ void layer_set_pending_state(LayerSurface *l) {
} else {
l->animainit_geom = l->animation.current;
}
// 判断是否需要动画
if (!animations || !layer_animations || l->noanim ||
if (!config.animations || !config.layer_animations || l->noanim ||
l->layer_surface->current.layer ==
ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND ||
l->layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) {
@ -520,7 +517,7 @@ void layer_set_pending_state(LayerSurface *l) {
if (((l->animation_type_open &&
strcmp(l->animation_type_open, "none") == 0) ||
(!l->animation_type_open &&
strcmp(layer_animation_type_open, "none") == 0)) &&
strcmp(config.layer_animation_type_open, "none") == 0)) &&
l->animation.action == OPEN) {
l->animation.should_animate = false;
}
@ -567,7 +564,8 @@ bool layer_draw_frame(LayerSurface *l) {
return false;
}
if (animations && layer_animations && l->animation.running && !l->noanim) {
if (config.animations && config.layer_animations && l->animation.running &&
!l->noanim) {
layer_animation_next_tick(l);
layer_draw_shadow(l);
} else {

View file

@ -5,41 +5,57 @@ void set_tagin_animation(Monitor *m, Client *c) {
return;
}
if (m->pertag->curtag > m->pertag->prevtag) {
if ((c->isglobal || c->isunglobal) ||
(c->tags & (1 << (m->pertag->prevtag - 1)) &&
c->tags & (1 << (m->pertag->curtag - 1)))) {
c->animation.tagouting = false;
c->animation.tagouted = false;
c->animation.tagining = false;
c->animation.action = MOVE;
return;
}
c->animainit_geom.x = tag_animation_direction == VERTICAL
bool going_forward = m->carousel_anim_dir
? m->carousel_anim_dir > 0
: m->pertag->curtag > m->pertag->prevtag;
if (going_forward) {
c->animainit_geom.x = config.tag_animation_direction == VERTICAL
? c->animation.current.x
: MAX(c->mon->m.x + c->mon->m.width,
c->geom.x + c->mon->m.width);
c->animainit_geom.y = tag_animation_direction == VERTICAL
? MAX(c->mon->m.y + c->mon->m.height,
c->geom.y + c->mon->m.height)
: MANGO_MAX(c->mon->m.x + c->mon->m.width,
c->geom.x + c->mon->m.width);
c->animainit_geom.y = config.tag_animation_direction == VERTICAL
? MANGO_MAX(c->mon->m.y + c->mon->m.height,
c->geom.y + c->mon->m.height)
: c->animation.current.y;
} else {
c->animainit_geom.x =
tag_animation_direction == VERTICAL
? c->animation.current.x
: MIN(m->m.x - c->geom.width, c->geom.x - c->mon->m.width);
c->animainit_geom.y =
tag_animation_direction == VERTICAL
? MIN(m->m.y - c->geom.height, c->geom.y - c->mon->m.height)
: c->animation.current.y;
c->animainit_geom.x = config.tag_animation_direction == VERTICAL
? c->animation.current.x
: MANGO_MIN(m->m.x - c->geom.width,
c->geom.x - c->mon->m.width);
c->animainit_geom.y = config.tag_animation_direction == VERTICAL
? MANGO_MIN(m->m.y - c->geom.height,
c->geom.y - c->mon->m.height)
: c->animation.current.y;
}
}
void set_arrange_visible(Monitor *m, Client *c, bool want_animation) {
if (!c->is_clip_to_hide || !ISTILED(c) || !is_scroller_layout(c->mon)) {
if (!ISTILED(c) || ((!c->is_clip_to_hide || !is_scroller_layout(c->mon)) &&
(!c->is_monocle_hide || !is_monocle_layout(c->mon)))) {
c->is_clip_to_hide = false;
c->is_monocle_hide = false;
wlr_scene_node_set_enabled(&c->scene->node, true);
wlr_scene_node_set_enabled(&c->scene_surface->node, true);
}
client_set_suspended(c, false);
if (!c->animation.tag_from_rule && want_animation &&
m->pertag->prevtag != 0 && m->pertag->curtag != 0 && animations) {
m->pertag->prevtag != 0 && m->pertag->curtag != 0 &&
config.animations) {
c->animation.tagining = true;
set_tagin_animation(m, c);
} else {
@ -54,40 +70,58 @@ void set_arrange_visible(Monitor *m, Client *c, bool want_animation) {
}
void set_tagout_animation(Monitor *m, Client *c) {
if (m->pertag->curtag > m->pertag->prevtag) {
if ((c->isglobal || c->isunglobal) ||
(c->tags & (1 << (m->pertag->prevtag - 1)) &&
c->tags & (1 << (m->pertag->curtag - 1)))) {
c->animation.tagouting = false;
c->animation.tagouted = false;
c->animation.tagining = false;
c->animation.action = MOVE;
return;
}
bool going_forward = m->carousel_anim_dir
? m->carousel_anim_dir > 0
: m->pertag->curtag > m->pertag->prevtag;
if (going_forward) {
c->pending = c->geom;
c->pending.x =
tag_animation_direction == VERTICAL
? c->animation.current.x
: MIN(c->mon->m.x - c->geom.width, c->geom.x - c->mon->m.width);
c->pending.y = tag_animation_direction == VERTICAL
? MIN(c->mon->m.y - c->geom.height,
c->geom.y - c->mon->m.height)
c->pending.x = config.tag_animation_direction == VERTICAL
? c->animation.current.x
: MANGO_MIN(c->mon->m.x - c->geom.width,
c->geom.x - c->mon->m.width);
c->pending.y = config.tag_animation_direction == VERTICAL
? MANGO_MIN(c->mon->m.y - c->geom.height,
c->geom.y - c->mon->m.height)
: c->animation.current.y;
resize(c, c->geom, 0);
} else {
c->pending = c->geom;
c->pending.x = tag_animation_direction == VERTICAL
c->pending.x = config.tag_animation_direction == VERTICAL
? c->animation.current.x
: MAX(c->mon->m.x + c->mon->m.width,
c->geom.x + c->mon->m.width);
c->pending.y = tag_animation_direction == VERTICAL
? MAX(c->mon->m.y + c->mon->m.height,
c->geom.y + c->mon->m.height)
: MANGO_MAX(c->mon->m.x + c->mon->m.width,
c->geom.x + c->mon->m.width);
c->pending.y = config.tag_animation_direction == VERTICAL
? MANGO_MAX(c->mon->m.y + c->mon->m.height,
c->geom.y + c->mon->m.height)
: c->animation.current.y;
resize(c, c->geom, 0);
}
}
void set_arrange_hidden(Monitor *m, Client *c, bool want_animation) {
if ((c->tags & (1 << (m->pertag->prevtag - 1))) &&
m->pertag->prevtag != 0 && m->pertag->curtag != 0 && animations) {
m->pertag->prevtag != 0 && m->pertag->curtag != 0 &&
config.animations) {
c->animation.tagouting = true;
c->animation.tagining = false;
set_tagout_animation(m, c);
} else {
c->animation.running = false;
wlr_scene_node_set_enabled(&c->scene->node, false);
client_set_suspended(c, true);
c->animainit_geom = c->current = c->pending = c->animation.current =
c->geom;
}
}

View file

@ -243,31 +243,6 @@ static inline int32_t client_is_rendered_on_mon(Client *c, Monitor *m) {
return 0;
}
static inline int32_t client_is_stopped(Client *c) {
int32_t pid;
siginfo_t in = {0};
#ifdef XWAYLAND
if (client_is_x11(c))
return 0;
#endif
wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL);
if (waitid(P_PID, pid, &in, WNOHANG | WCONTINUED | WSTOPPED | WNOWAIT) <
0) {
/* This process is not our child process, while is very unluckely that
* it is stopped, in order to do not skip frames assume that it is. */
if (errno == ECHILD)
return 1;
} else if (in.si_pid) {
if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED)
return 1;
if (in.si_code == CLD_CONTINUED)
return 0;
}
return 0;
}
static inline int32_t client_is_unmanaged(Client *c) {
#ifdef XWAYLAND
if (client_is_x11(c))
@ -319,9 +294,34 @@ static inline uint32_t client_set_size(Client *c, uint32_t width,
uint32_t height) {
#ifdef XWAYLAND
if (client_is_x11(c)) {
struct wlr_xwayland_surface *surface = c->surface.xwayland;
struct wlr_surface_state *state = &surface->surface->current;
if ((int32_t)c->geom.width - 2 * (int32_t)c->bw ==
(int32_t)state->width &&
(int32_t)c->geom.height - 2 * (int32_t)c->bw ==
(int32_t)state->height &&
(int32_t)c->surface.xwayland->x ==
(int32_t)c->geom.x + (int32_t)c->bw &&
(int32_t)c->surface.xwayland->y ==
(int32_t)c->geom.y + (int32_t)c->bw) {
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 (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,
c->geom.y + c->bw, width, height);
return 0;
return 1;
}
#endif
if ((int32_t)width == c->surface.xdg->toplevel->current.width &&
@ -360,7 +360,7 @@ static inline void client_set_maximized(Client *c, bool maximized) {
static inline void client_set_tiled(Client *c, uint32_t edges) {
struct wlr_xdg_toplevel *toplevel;
#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,
edges != WLR_EDGE_NONE,
edges != WLR_EDGE_NONE);
@ -375,20 +375,11 @@ static inline void client_set_tiled(Client *c, uint32_t 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);
}
}
static inline void client_set_suspended(Client *c, int32_t suspended) {
#ifdef XWAYLAND
if (client_is_x11(c))
return;
#endif
wlr_xdg_toplevel_set_suspended(c->surface.xdg->toplevel, suspended);
}
static inline int32_t client_should_ignore_focus(Client *c) {
#ifdef XWAYLAND
@ -547,4 +538,4 @@ static inline void client_set_size_bound(Client *c) {
state.max_height > 0) {
c->geom.height = state.max_height + 2 * c->bw;
}
}
}

View file

@ -92,3 +92,117 @@ uint32_t get_now_in_ms(void) {
uint32_t timespec_to_ms(struct timespec *ts) {
return (uint32_t)ts->tv_sec * 1000 + (uint32_t)ts->tv_nsec / 1000000;
}
char *join_strings(char *arr[], const char *sep) {
if (!arr || !arr[0]) {
char *empty = malloc(1);
if (empty)
empty[0] = '\0';
return empty;
}
size_t total_len = 0;
int count = 0;
for (int i = 0; arr[i] != NULL; i++) {
total_len += strlen(arr[i]);
count++;
}
if (count > 0) {
total_len += strlen(sep) * (count - 1);
}
char *result = malloc(total_len + 1);
if (!result)
return NULL;
result[0] = '\0';
for (int i = 0; arr[i] != NULL; i++) {
if (i > 0)
strcat(result, sep);
strcat(result, arr[i]);
}
return result;
}
char *join_strings_with_suffix(char *arr[], const char *suffix,
const char *sep) {
if (!arr || !arr[0]) {
char *empty = malloc(1);
if (empty)
empty[0] = '\0';
return empty;
}
size_t total_len = 0;
int count = 0;
for (int i = 0; arr[i] != NULL; i++) {
total_len += strlen(arr[i]) + strlen(suffix);
count++;
}
if (count > 0) {
total_len += strlen(sep) * (count - 1);
}
char *result = malloc(total_len + 1);
if (!result)
return NULL;
result[0] = '\0';
for (int i = 0; arr[i] != NULL; i++) {
if (i > 0)
strcat(result, sep);
strcat(result, arr[i]);
strcat(result, suffix);
}
return result;
}
char *string_printf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int len = vsnprintf(NULL, 0, fmt, args);
va_end(args);
if (len < 0)
return NULL;
char *str = malloc(len + 1);
if (!str)
return NULL;
va_start(args, fmt);
vsnprintf(str, len + 1, fmt, args);
va_end(args);
return str;
}
void wl_list_swap(struct wl_list *l1, struct wl_list *l2) {
struct wl_list *tmp1_prev = l1->prev;
struct wl_list *tmp2_prev = l2->prev;
struct wl_list *tmp1_next = l1->next;
struct wl_list *tmp2_next = l2->next;
if (l1->next == l2) { /* l1 -> l2 相邻 */
l1->next = l2->next;
l1->prev = l2;
l2->next = l1;
l2->prev = tmp1_prev;
tmp1_prev->next = l2;
tmp2_next->prev = l1;
} else if (l2->next == l1) { /* l2 -> l1 相邻 */
l2->next = l1->next;
l2->prev = l1;
l1->next = l2;
l1->prev = tmp2_prev;
tmp2_prev->next = l1;
tmp1_next->prev = l2;
} else { /* 不相邻 */
l2->next = tmp1_next;
l2->prev = tmp1_prev;
l1->next = tmp2_next;
l1->prev = tmp2_prev;
tmp1_prev->next = l2;
tmp1_next->prev = l2;
tmp2_prev->next = l1;
tmp2_next->prev = l1;
}
}

View file

@ -7,4 +7,9 @@ int32_t fd_set_nonblock(int32_t fd);
int32_t regex_match(const char *pattern_mb, const char *str_mb);
void wl_list_append(struct wl_list *list, struct wl_list *object);
uint32_t get_now_in_ms(void);
uint32_t timespec_to_ms(struct timespec *ts);
uint32_t timespec_to_ms(struct timespec *ts);
char *join_strings(char *arr[], const char *sep);
char *join_strings_with_suffix(char *arr[], const char *suffix,
const char *sep);
char *string_printf(const char *fmt, ...);
void wl_list_swap(struct wl_list *l1, struct wl_list *l2);

File diff suppressed because it is too large Load diff

View file

@ -1,130 +1,9 @@
// TODO: remove this file in the future, replace all global variables with
// config.xxx
#define MODKEY WLR_MODIFIER_ALT
/* speedie's mango config */
static const char *tags[] = {
"1", "2", "3", "4", "5", "6", "7", "8", "9",
};
#define COLOR(hex) \
{((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f, \
((hex >> 8) & 0xFF) / 255.0f, (hex & 0xFF) / 255.0f}
/* animaion */
char *animation_type_open = "slide"; // 是否启用动画 //slide,zoom
char *animation_type_close = "slide"; // 是否启用动画 //slide,zoom
char *layer_animation_type_open = "slide"; // 是否启用layer动画 //slide,zoom
char *layer_animation_type_close = "slide"; // 是否启用layer动画 //slide,zoom
int32_t animations = 1; // 是否启用动画
int32_t layer_animations = 0; // 是否启用layer动画
int32_t tag_animation_direction = HORIZONTAL; // 标签动画方向
int32_t animation_fade_in = 1; // Enable animation fade in
int32_t animation_fade_out = 1; // Enable animation fade out
float zoom_initial_ratio = 0.3; // 动画起始窗口比例
float zoom_end_ratio = 0.8; // 动画结束窗口比例
float fadein_begin_opacity = 0.5; // Begin opac window ratio for animations
float fadeout_begin_opacity = 0.5; // Begin opac window ratio for animations
uint32_t animation_duration_move = 500; // Animation move speed
uint32_t animation_duration_open = 400; // Animation open speed
uint32_t animation_duration_tag = 300; // Animation tag speed
uint32_t animation_duration_close = 300; // Animation close speed
uint32_t animation_duration_focus = 0; // Animation focus opacity speed
double animation_curve_move[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线
double animation_curve_open[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线
double animation_curve_tag[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线
double animation_curve_close[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线
double animation_curve_focus[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线
double animation_curve_opafadein[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线
double animation_curve_opafadeout[4] = {0.5, 0.5, 0.5, 0.5}; // 动画曲线
/* appearance */
uint32_t axis_bind_apply_timeout = 100; // 滚轮绑定动作的触发的时间间隔
uint32_t focus_on_activate = 1; // 收到窗口激活请求是否自动跳转聚焦
uint32_t new_is_master = 1; // 新窗口是否插在头部
double default_mfact = 0.55f; // master 窗口比例
uint32_t default_nmaster = 1; // 默认master数量
int32_t center_master_overspread = 0; // 中心master时是否铺满
int32_t center_when_single_stack = 1; // 单个stack时是否居中
/* logging */
int32_t log_level = WLR_ERROR;
uint32_t numlockon = 0; // 是否打开右边小键盘
uint32_t capslock = 0; // 是否启用快捷键
uint32_t ov_tab_mode = 0; // alt tab切换模式
uint32_t hotarea_size = 10; // 热区大小,10x10
uint32_t hotarea_corner = BOTTOM_LEFT;
uint32_t enable_hotarea = 1; // 是否启用鼠标热区
int32_t smartgaps = 0; /* 1 means no outer gap when there is only one window */
int32_t sloppyfocus = 1; /* focus follows mouse */
uint32_t gappih = 5; /* horiz inner gap between windows */
uint32_t gappiv = 5; /* vert inner gap between windows */
uint32_t gappoh = 10; /* horiz outer gap between windows and screen edge */
uint32_t gappov = 10; /* vert outer gap between windows and screen edge */
float scratchpad_width_ratio = 0.8;
float scratchpad_height_ratio = 0.9;
int32_t scroller_structs = 20;
float scroller_default_proportion = 0.9;
float scroller_default_proportion_single = 1.0;
int32_t scroller_ignore_proportion_single = 1;
int32_t scroller_focus_center = 0;
int32_t scroller_prefer_center = 0;
int32_t focus_cross_monitor = 0;
int32_t focus_cross_tag = 0;
int32_t exchange_cross_monitor = 0;
int32_t scratchpad_cross_monitor = 0;
int32_t view_current_to_back = 0;
int32_t no_border_when_single = 0;
int32_t no_radius_when_single = 0;
int32_t snap_distance = 30;
int32_t enable_floating_snap = 0;
int32_t drag_tile_to_tile = 0;
uint32_t cursor_size = 24;
uint32_t cursor_hide_timeout = 0;
uint32_t swipe_min_threshold = 1;
int32_t idleinhibit_ignore_visible =
0; /* 1 means idle inhibitors will disable idle tracking even if it's
surface isn't visible */
uint32_t borderpx = 4; /* border pixel of windows */
float rootcolor[] = COLOR(0x323232ff);
float bordercolor[] = COLOR(0x444444ff);
float focuscolor[] = COLOR(0xc66b25ff);
float maximizescreencolor[] = COLOR(0x89aa61ff);
float urgentcolor[] = COLOR(0xad401fff);
float scratchpadcolor[] = COLOR(0x516c93ff);
float globalcolor[] = COLOR(0xb153a7ff);
float overlaycolor[] = COLOR(0x14a57cff);
// char *cursor_theme = "Bibata-Modern-Ice";
int32_t overviewgappi = 5; /* overview时 窗口与边缘 缝隙大小 */
int32_t overviewgappo = 30; /* overview时 窗口与窗口 缝隙大小 */
/* To conform the xdg-protocol, set the alpha to zero to restore the old
* behavior */
float fullscreen_bg[] = {0.1, 0.1, 0.1, 1.0};
int32_t warpcursor = 1;
int32_t drag_corner = 3;
int32_t drag_warp_cursor = 1;
int32_t xwayland_persistence = 1; /* xwayland persistence */
int32_t syncobj_enable = 0;
int32_t allow_lock_transparent = 0;
double drag_refresh_interval = 16.0;
int32_t allow_tearing = TEARING_DISABLED;
int32_t allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE;
/* keyboard */
/*
only layout can modify after fisrt init
other fields change will be ignored.
*/
char xkb_rules_rules[256];
char xkb_rules_model[256];
char xkb_rules_layout[256];
char xkb_rules_variant[256];
char xkb_rules_options[256];
/* keyboard */
static const struct xkb_rule_names xkb_fallback_rules = {
.layout = "us",
.variant = NULL,
@ -132,106 +11,3 @@ static const struct xkb_rule_names xkb_fallback_rules = {
.rules = NULL,
.options = NULL,
};
static const struct xkb_rule_names xkb_default_rules = {
.options = NULL,
};
struct xkb_rule_names xkb_rules = {
/* can specify fields: rules, model, layout, variant, options */
/* example:
.options = "ctrl:nocaps",
*/
.rules = xkb_rules_rules, .model = xkb_rules_model,
.layout = xkb_rules_layout, .variant = xkb_rules_variant,
.options = xkb_rules_options,
};
int32_t repeat_rate = 25;
int32_t repeat_delay = 600;
/* Trackpad */
int32_t disable_trackpad = 0;
int32_t tap_to_click = 1;
int32_t tap_and_drag = 1;
int32_t drag_lock = 1;
int32_t mouse_natural_scrolling = 0;
int32_t trackpad_natural_scrolling = 0;
int32_t disable_while_typing = 1;
int32_t left_handed = 0;
int32_t middle_button_emulation = 0;
int32_t single_scratchpad = 1;
int32_t edge_scroller_pointer_focus = 1;
/* You can choose between:
LIBINPUT_CONFIG_SCROLL_NO_SCROLL
LIBINPUT_CONFIG_SCROLL_2FG
LIBINPUT_CONFIG_SCROLL_EDGE
LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN
*/
enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG;
uint32_t scroll_button = 274;
/* You can choose between:
LIBINPUT_CONFIG_CLICK_METHOD_NONE
LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS
LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER
*/
enum libinput_config_click_method click_method =
LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS;
double axis_scroll_factor = 1.0;
/* You can choose between:
LIBINPUT_CONFIG_SEND_EVENTS_ENABLED
LIBINPUT_CONFIG_SEND_EVENTS_DISABLED
LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE
*/
uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
/* You can choose between:
LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT
LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE
*/
enum libinput_config_accel_profile accel_profile =
LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE;
double accel_speed = 0.0;
/* You can choose between:
LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle
LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right
*/
enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM;
/* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */
#define MODKEY WLR_MODIFIER_ALT
static const char *tags[] = {
"1", "2", "3", "4", "5", "6", "7", "8", "9",
};
float focused_opacity = 1.0;
float unfocused_opacity = 1.0;
int32_t border_radius = 0;
int32_t border_radius_location_default = CORNER_LOCATION_ALL;
int32_t blur = 0;
int32_t blur_layer = 0;
int32_t blur_optimized = 1;
struct blur_data blur_params;
int32_t blur_params_num_passes = 1;
int32_t blur_params_radius = 5;
float blur_params_noise = 0.02;
float blur_params_brightness = 0.9;
float blur_params_contrast = 0.9;
float blur_params_saturation = 1.2;
int32_t shadows = 0;
int32_t shadow_only_floating = 1;
int32_t layer_shadows = 0;
uint32_t shadows_size = 10;
double shadows_blur = 15;
int32_t shadows_position_x = 0;
int32_t shadows_position_y = 0;
float shadowscolor[] = COLOR(0x000000ff);

View file

@ -3,6 +3,7 @@ int32_t restore_minimized(const Arg *arg);
int32_t toggle_scratchpad(const Arg *arg);
int32_t focusdir(const Arg *arg);
int32_t toggleoverview(const Arg *arg);
int32_t togglejump(const Arg *arg);
int32_t set_proportion(const Arg *arg);
int32_t switch_proportion_preset(const Arg *arg);
int32_t zoom(const Arg *arg);
@ -69,4 +70,9 @@ int32_t setoption(const Arg *arg);
int32_t disable_monitor(const Arg *arg);
int32_t enable_monitor(const Arg *arg);
int32_t toggle_monitor(const Arg *arg);
int32_t scroller_stack(const Arg *arg);
int32_t scroller_stack(const Arg *arg);
int32_t toggle_all_floating(const Arg *arg);
int32_t dwindle_toggle_split_direction(const Arg *arg);
int32_t dwindle_split_horizontal(const Arg *arg);
int32_t dwindle_split_vertical(const Arg *arg);
int32_t focusid(const Arg *arg);

File diff suppressed because it is too large Load diff

845
src/draw/text-node.c Normal file
View file

@ -0,0 +1,845 @@
#include "text-node.h"
#include <cairo.h>
#include <drm_fourcc.h>
#include <glib.h>
#include <pango/pangocairo.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wlr/interfaces/wlr_buffer.h>
static GHashTable *font_desc_cache = NULL;
static PangoFontDescription *get_cached_font_desc(const char *font_desc) {
if (!font_desc_cache) {
font_desc_cache =
g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
(GDestroyNotify)pango_font_description_free);
}
PangoFontDescription *desc =
g_hash_table_lookup(font_desc_cache, font_desc);
if (!desc) {
desc = pango_font_description_from_string(font_desc);
g_hash_table_insert(font_desc_cache, g_strdup(font_desc), desc);
}
return desc;
}
void mango_text_global_finish(void) {
if (font_desc_cache) {
g_hash_table_destroy(font_desc_cache);
font_desc_cache = NULL;
}
}
static void text_buffer_destroy(struct wlr_buffer *wlr_buffer) {
struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base);
free(buf);
}
static bool text_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
uint32_t flags, void **data,
uint32_t *format,
size_t *stride) {
(void)flags;
struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base);
*data = cairo_image_surface_get_data(buf->surface);
*format = DRM_FORMAT_ARGB8888;
*stride = cairo_image_surface_get_stride(buf->surface);
return true;
}
static void text_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) {}
static const struct wlr_buffer_impl text_buffer_impl = {
.destroy = text_buffer_destroy,
.begin_data_ptr_access = text_buffer_begin_data_ptr_access,
.end_data_ptr_access = text_buffer_end_data_ptr_access,
};
struct mango_jump_label_node *
mango_jump_label_node_create(struct wlr_scene_tree *parent,
DecorateDrawData data) {
struct mango_jump_label_node *node = calloc(1, sizeof(*node));
if (!node)
return NULL;
node->scene_buffer = wlr_scene_buffer_create(parent, NULL);
if (!node->scene_buffer) {
free(node);
return NULL;
}
memcpy(node->fg_color, data.fg_color, sizeof(node->fg_color));
memcpy(node->bg_color, data.bg_color, sizeof(node->bg_color));
memcpy(node->focus_fg_color, data.focus_fg_color,
sizeof(node->focus_fg_color));
memcpy(node->focus_bg_color, data.focus_bg_color,
sizeof(node->focus_bg_color));
memcpy(node->border_color, data.border_color, sizeof(node->border_color));
node->border_width = data.border_width;
node->corner_radius = data.corner_radius;
node->padding_x = data.padding_x;
node->padding_y = data.padding_y;
node->font_desc =
g_strdup(data.font_desc ? data.font_desc : "monospace Bold 16");
node->cached_text = NULL;
node->cached_scale = -1.0f;
node->cached_font_desc = NULL;
node->focused = false;
node->cached_focused = false;
node->measure_surface =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
node->measure_cr = cairo_create(node->measure_surface);
node->measure_context = pango_cairo_create_context(node->measure_cr);
node->measure_layout = pango_layout_new(node->measure_context);
node->measure_scale = 1.0f;
node->scene_buffer->node.data = NULL;
return node;
}
void mango_jump_label_node_destroy(struct mango_jump_label_node *node) {
if (!node)
return;
if (node->buffer) {
wlr_buffer_drop(&node->buffer->base);
node->buffer = NULL;
}
if (node->surface) {
cairo_surface_destroy(node->surface);
node->surface = NULL;
}
if (node->measure_layout)
g_object_unref(node->measure_layout);
if (node->measure_context)
g_object_unref(node->measure_context);
if (node->measure_cr)
cairo_destroy(node->measure_cr);
if (node->measure_surface)
cairo_surface_destroy(node->measure_surface);
wlr_scene_node_destroy(&node->scene_buffer->node);
g_free(node->font_desc);
g_free(node->cached_text);
g_free(node->cached_font_desc);
free(node);
}
void mango_jump_label_node_set_background(struct mango_jump_label_node *node,
float r, float g, float b, float a) {
if (!node)
return;
node->bg_color[0] = r;
node->bg_color[1] = g;
node->bg_color[2] = b;
node->bg_color[3] = a;
}
void mango_jump_label_node_set_border(struct mango_jump_label_node *node,
float r, float g, float b, float a,
int32_t width, int32_t radius) {
if (!node)
return;
node->border_color[0] = r;
node->border_color[1] = g;
node->border_color[2] = b;
node->border_color[3] = a;
node->border_width = width > 0 ? width : 0;
node->corner_radius = radius;
}
void mango_jump_label_node_set_padding(struct mango_jump_label_node *node,
int32_t pad_x, int32_t pad_y) {
if (!node)
return;
node->padding_x = pad_x >= 0 ? pad_x : 0;
node->padding_y = pad_y >= 0 ? pad_y : 0;
}
static void get_text_pixel_size(struct mango_jump_label_node *node,
const char *text, float scale, int32_t *out_w,
int32_t *out_h) {
if (node->measure_scale != scale) {
pango_cairo_context_set_resolution(node->measure_context, 96.0 * scale);
node->measure_scale = scale;
}
PangoFontDescription *desc = get_cached_font_desc(node->font_desc);
pango_layout_set_font_description(node->measure_layout, desc);
pango_layout_set_text(node->measure_layout, text, -1);
pango_layout_get_pixel_size(node->measure_layout, out_w, out_h);
}
static void draw_rounded_rect(cairo_t *cr, double x, double y, double w,
double h, double r) {
double degrees = G_PI / 180.0;
cairo_new_sub_path(cr);
cairo_arc(cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);
cairo_arc(cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);
cairo_arc(cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);
cairo_arc(cr, x + r, y + r, r, 180 * degrees, 270 * degrees);
cairo_close_path(cr);
}
void mango_jump_label_node_update(struct mango_jump_label_node *node,
const char *text, float scale) {
if (!node || !text)
return;
if (scale <= 0.0f)
scale = 1.0f;
/* 脏检查,加入 focused 状态 */
if (node->cached_scale == scale && node->cached_font_desc &&
strcmp(node->cached_font_desc, node->font_desc) == 0 &&
node->cached_text && strcmp(node->cached_text, text) == 0 &&
memcmp(node->cached_fg_color, node->fg_color, sizeof(node->fg_color)) ==
0 &&
memcmp(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)) ==
0 &&
memcmp(node->cached_focus_fg_color, node->focus_fg_color,
sizeof(node->focus_fg_color)) == 0 &&
memcmp(node->cached_focus_bg_color, node->focus_bg_color,
sizeof(node->focus_bg_color)) == 0 &&
memcmp(node->cached_border_color, node->border_color,
sizeof(node->border_color)) == 0 &&
node->cached_border_width == node->border_width &&
node->cached_corner_radius == node->corner_radius &&
node->cached_padding_x == node->padding_x &&
node->cached_padding_y == node->padding_y &&
node->cached_focused == node->focused) {
return;
}
/* 更新缓存 */
g_free(node->cached_text);
node->cached_text = g_strdup(text);
g_free(node->cached_font_desc);
node->cached_font_desc = g_strdup(node->font_desc);
node->cached_scale = scale;
memcpy(node->cached_fg_color, node->fg_color, sizeof(node->fg_color));
memcpy(node->cached_bg_color, node->bg_color, sizeof(node->bg_color));
memcpy(node->cached_focus_fg_color, node->focus_fg_color,
sizeof(node->focus_fg_color));
memcpy(node->cached_focus_bg_color, node->focus_bg_color,
sizeof(node->focus_bg_color));
memcpy(node->cached_border_color, node->border_color,
sizeof(node->border_color));
node->cached_border_width = node->border_width;
node->cached_corner_radius = node->corner_radius;
node->cached_padding_x = node->padding_x;
node->cached_padding_y = node->padding_y;
node->cached_focused = node->focused;
int32_t text_pixel_w, text_pixel_h;
get_text_pixel_size(node, text, scale, &text_pixel_w, &text_pixel_h);
if (text_pixel_w <= 0 || text_pixel_h <= 0) {
wlr_scene_buffer_set_buffer(node->scene_buffer, NULL);
if (node->buffer) {
wlr_buffer_drop(&node->buffer->base);
node->buffer = NULL;
}
if (node->surface) {
cairo_surface_destroy(node->surface);
node->surface = NULL;
}
node->logical_width = 0;
node->logical_height = 0;
wlr_scene_buffer_set_dest_size(node->scene_buffer, 0, 0);
return;
}
int32_t logical_text_w = (int32_t)(text_pixel_w / scale + 0.5f);
int32_t logical_text_h = (int32_t)(text_pixel_h / scale + 0.5f);
int32_t box_logical_w = logical_text_w + 2 * node->padding_x;
int32_t box_logical_h = logical_text_h + 2 * node->padding_y;
int32_t required_pixel_w =
(int32_t)((box_logical_w + 2 * node->border_width) * scale + 0.5f);
int32_t required_pixel_h =
(int32_t)((box_logical_h + 2 * node->border_width) * scale + 0.5f);
if (required_pixel_w < 1)
required_pixel_w = 1;
if (required_pixel_h < 1)
required_pixel_h = 1;
bool surface_size_changed = (!node->surface) ||
(node->surface_pixel_w != required_pixel_w) ||
(node->surface_pixel_h != required_pixel_h);
if (surface_size_changed) {
if (node->buffer) {
wlr_buffer_drop(&node->buffer->base);
node->buffer = NULL;
}
if (node->surface) {
cairo_surface_destroy(node->surface);
node->surface = NULL;
}
node->surface = cairo_image_surface_create(
CAIRO_FORMAT_ARGB32, required_pixel_w, required_pixel_h);
node->surface_pixel_w = required_pixel_w;
node->surface_pixel_h = required_pixel_h;
}
cairo_t *cr = cairo_create(node->surface);
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
double border = node->border_width * scale;
double bg_x = border;
double bg_y = border;
double bg_w = box_logical_w * scale;
double bg_h = box_logical_h * scale;
double radius;
if (node->corner_radius < 0) {
radius = (bg_w < bg_h ? bg_w : bg_h) / 2.0;
} else {
radius = node->corner_radius * scale;
}
if (radius > bg_w / 2.0)
radius = bg_w / 2.0;
if (radius > bg_h / 2.0)
radius = bg_h / 2.0;
const float *active_bg =
node->focused ? node->focus_bg_color : node->bg_color;
const float *active_fg =
node->focused ? node->focus_fg_color : node->fg_color;
bool draw_bg = (active_bg[3] > 0.0f); // 使用 active_bg
bool draw_border =
(node->border_width > 0) && (node->border_color[3] > 0.0f);
if (draw_bg) {
cairo_set_source_rgba(cr, active_bg[0], active_bg[1], active_bg[2],
active_bg[3]);
if (radius > 0.0) {
draw_rounded_rect(cr, bg_x, bg_y, bg_w, bg_h, radius);
cairo_fill(cr);
} else {
cairo_rectangle(cr, bg_x, bg_y, bg_w, bg_h);
cairo_fill(cr);
}
}
cairo_save(cr);
double text_x = (node->border_width + node->padding_x) * scale;
double text_y = (node->border_width + node->padding_y) * scale;
cairo_translate(cr, text_x, text_y);
PangoContext *ctx = pango_cairo_create_context(cr);
pango_cairo_context_set_resolution(ctx, 96.0 * scale);
PangoLayout *layout = pango_layout_new(ctx);
PangoFontDescription *desc = get_cached_font_desc(node->font_desc);
pango_layout_set_font_description(layout, desc);
pango_layout_set_text(layout, text, -1);
cairo_set_source_rgba(cr, active_fg[0], active_fg[1], active_fg[2],
active_fg[3]);
pango_cairo_show_layout(cr, layout);
g_object_unref(layout);
g_object_unref(ctx);
cairo_restore(cr);
if (draw_border) {
cairo_set_source_rgba(cr, node->border_color[0], node->border_color[1],
node->border_color[2], node->border_color[3]);
cairo_set_line_width(cr, border);
double half_lw = border * 0.5;
double bx = bg_x - half_lw;
double by = bg_y - half_lw;
double bw = bg_w + border;
double bh = bg_h + border;
if (radius > 0.0) {
double outer_radius = radius + half_lw;
if (outer_radius < 0.0)
outer_radius = 0.0;
draw_rounded_rect(cr, bx, by, bw, bh, outer_radius);
} else {
cairo_rectangle(cr, bx, by, bw, bh);
}
cairo_stroke(cr);
}
cairo_surface_flush(node->surface);
cairo_destroy(cr);
if (node->buffer) {
wlr_buffer_drop(&node->buffer->base);
node->buffer = NULL;
}
struct mango_text_buffer *buf = calloc(1, sizeof(*buf));
if (!buf) {
return;
}
wlr_buffer_init(&buf->base, &text_buffer_impl, node->surface_pixel_w,
node->surface_pixel_h);
buf->surface = node->surface;
node->buffer = buf;
wlr_scene_buffer_set_buffer(node->scene_buffer, &buf->base);
node->logical_width = box_logical_w + 2 * node->border_width;
node->logical_height = box_logical_h + 2 * node->border_width;
wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width,
node->logical_height);
}
void mango_jump_label_node_set_focus(struct mango_jump_label_node *node,
bool focused) {
if (!node || node->focused == focused)
return;
node->focused = focused;
// 使用缓存的文本和缩放触发重绘(如果无文本则不重绘)
if (node->cached_text && node->cached_scale > 0.0f) {
mango_jump_label_node_update(node, node->cached_text,
node->cached_scale);
}
}
struct mango_tab_bar_node *
mango_tab_bar_node_create(void *mango_node_data, struct wlr_scene_tree *parent,
DecorateDrawData data, int32_t width,
int32_t height) {
struct mango_tab_bar_node *node = calloc(1, sizeof(*node));
if (!node)
return NULL;
node->scene_buffer = wlr_scene_buffer_create(parent, NULL);
if (!node->scene_buffer) {
free(node);
return NULL;
}
memcpy(node->fg_color, data.fg_color, sizeof(node->fg_color));
memcpy(node->bg_color, data.bg_color, sizeof(node->bg_color));
memcpy(node->focus_fg_color, data.focus_fg_color,
sizeof(node->focus_fg_color));
memcpy(node->focus_bg_color, data.focus_bg_color,
sizeof(node->focus_bg_color));
memcpy(node->border_color, data.border_color, sizeof(node->border_color));
node->border_width = data.border_width;
node->corner_radius = data.corner_radius;
node->padding_x = data.padding_x;
node->padding_y = data.padding_y;
node->font_desc =
g_strdup(data.font_desc ? data.font_desc : "monospace Bold 16");
node->target_width = width;
node->target_height = height;
node->focused = false;
node->cached_focused = false;
node->measure_surface =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
node->measure_cr = cairo_create(node->measure_surface);
node->measure_context = pango_cairo_create_context(node->measure_cr);
node->measure_layout = pango_layout_new(node->measure_context);
node->measure_scale = 1.0f;
node->cached_scale = -1.0f;
node->last_text = NULL;
node->last_scale = 0.0f;
node->scene_buffer->node.data = mango_node_data;
return node;
}
void mango_tab_bar_node_destroy(struct mango_tab_bar_node *node) {
if (!node)
return;
if (node->buffer) {
wlr_buffer_drop(&node->buffer->base);
node->buffer = NULL;
}
if (node->surface) {
cairo_surface_destroy(node->surface);
node->surface = NULL;
}
if (node->measure_surface) {
cairo_surface_destroy(node->measure_surface);
node->measure_surface = NULL;
}
if (node->measure_layout)
g_object_unref(node->measure_layout);
if (node->measure_context)
g_object_unref(node->measure_context);
if (node->measure_cr)
cairo_destroy(node->measure_cr);
void *data = node->scene_buffer->node.data;
wlr_scene_node_destroy(&node->scene_buffer->node);
g_free(node->font_desc);
g_free(node->cached_text);
g_free(node->cached_font_desc);
g_free(node->last_text);
free(data);
free(node);
}
void mango_tab_bar_node_set_size(struct mango_tab_bar_node *node, int32_t width,
int32_t height) {
if (!node)
return;
if (width < 0)
width = 0;
if (height < 0)
height = 0;
if (node->target_width == width && node->target_height == height)
return;
node->target_width = width;
node->target_height = height;
const char *redraw_text = node->last_text ? node->last_text : "";
float redraw_scale = node->last_scale > 0.0f ? node->last_scale : 1.0f;
mango_tab_bar_node_update(node, redraw_text, redraw_scale);
}
void mango_tab_bar_node_update(struct mango_tab_bar_node *node,
const char *text, float scale) {
if (!node || !text)
return;
if (scale <= 0.0f)
scale = 1.0f;
char *safe_text = g_strdup(text);
g_free(node->last_text);
node->last_text = safe_text; // 所有权转移
node->last_scale = scale;
// 脏检查加入 focused
if (node->cached_scale == scale && node->cached_font_desc &&
strcmp(node->cached_font_desc, node->font_desc) == 0 &&
node->cached_text && strcmp(node->cached_text, safe_text) == 0 &&
memcmp(node->cached_fg_color, node->fg_color, sizeof(node->fg_color)) ==
0 &&
memcmp(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)) ==
0 &&
memcmp(node->cached_focus_fg_color, node->focus_fg_color,
sizeof(node->focus_fg_color)) == 0 &&
memcmp(node->cached_focus_bg_color, node->focus_bg_color,
sizeof(node->focus_bg_color)) == 0 &&
memcmp(node->cached_border_color, node->border_color,
sizeof(node->border_color)) == 0 &&
node->cached_border_width == node->border_width &&
node->cached_corner_radius == node->corner_radius &&
node->cached_padding_x == node->padding_x &&
node->cached_padding_y == node->padding_y &&
node->cached_target_width == node->target_width &&
node->cached_target_height == node->target_height &&
node->cached_focused == node->focused) {
return;
}
// 更新缓存
g_free(node->cached_text);
node->cached_text = g_strdup(safe_text);
g_free(node->cached_font_desc);
node->cached_font_desc = g_strdup(node->font_desc);
node->cached_scale = scale;
memcpy(node->cached_fg_color, node->fg_color, sizeof(node->fg_color));
memcpy(node->cached_bg_color, node->bg_color, sizeof(node->bg_color));
memcpy(node->cached_focus_fg_color, node->focus_fg_color,
sizeof(node->focus_fg_color));
memcpy(node->cached_focus_bg_color, node->focus_bg_color,
sizeof(node->focus_bg_color));
memcpy(node->cached_border_color, node->border_color,
sizeof(node->border_color));
node->cached_border_width = node->border_width;
node->cached_corner_radius = node->corner_radius;
node->cached_padding_x = node->padding_x;
node->cached_padding_y = node->padding_y;
node->cached_target_width = node->target_width;
node->cached_target_height = node->target_height;
node->cached_focused = node->focused;
if (node->target_width <= 0 || node->target_height <= 0) {
wlr_scene_buffer_set_buffer(node->scene_buffer, NULL);
if (node->buffer) {
wlr_buffer_drop(&node->buffer->base);
node->buffer = NULL;
}
if (node->surface) {
cairo_surface_destroy(node->surface);
node->surface = NULL;
}
node->logical_width = 0;
node->logical_height = 0;
wlr_scene_buffer_set_dest_size(node->scene_buffer, 0, 0);
return;
}
int32_t box_logical_w = node->target_width - 2 * node->border_width;
int32_t box_logical_h = node->target_height - 2 * node->border_width;
if (box_logical_w < 0)
box_logical_w = 0;
if (box_logical_h < 0)
box_logical_h = 0;
int32_t required_pixel_w = (int32_t)(node->target_width * scale + 0.5f);
int32_t required_pixel_h = (int32_t)(node->target_height * scale + 0.5f);
if (required_pixel_w < 1)
required_pixel_w = 1;
if (required_pixel_h < 1)
required_pixel_h = 1;
bool surface_size_changed = (!node->surface) ||
(node->surface_pixel_w != required_pixel_w) ||
(node->surface_pixel_h != required_pixel_h);
if (surface_size_changed) {
if (node->buffer) {
wlr_buffer_drop(&node->buffer->base);
node->buffer = NULL;
}
if (node->surface) {
cairo_surface_destroy(node->surface);
node->surface = NULL;
}
node->surface = cairo_image_surface_create(
CAIRO_FORMAT_ARGB32, required_pixel_w, required_pixel_h);
node->surface_pixel_w = required_pixel_w;
node->surface_pixel_h = required_pixel_h;
}
cairo_t *cr = cairo_create(node->surface);
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
double border_phys = node->border_width * scale;
double bg_x = border_phys;
double bg_y = border_phys;
double bg_w = box_logical_w * scale;
double bg_h = box_logical_h * scale;
double radius;
if (node->corner_radius < 0) {
radius = (bg_w < bg_h ? bg_w : bg_h) / 2.0;
} else {
radius = node->corner_radius * scale;
}
if (radius > bg_w / 2.0)
radius = bg_w / 2.0;
if (radius > bg_h / 2.0)
radius = bg_h / 2.0;
const float *active_bg =
node->focused ? node->focus_bg_color : node->bg_color;
const float *active_fg =
node->focused ? node->focus_fg_color : node->fg_color;
bool draw_bg = (active_bg[3] > 0.0f);
bool draw_border =
(node->border_width > 0) && (node->border_color[3] > 0.0f);
if (draw_bg) {
cairo_set_source_rgba(cr, active_bg[0], active_bg[1], active_bg[2],
active_bg[3]);
if (radius > 0.0) {
draw_rounded_rect(cr, bg_x, bg_y, bg_w, bg_h, radius);
cairo_fill(cr);
} else {
cairo_rectangle(cr, bg_x, bg_y, bg_w, bg_h);
cairo_fill(cr);
}
}
int32_t text_area_logical_w = box_logical_w - 2 * node->padding_x;
int32_t text_area_logical_h = box_logical_h - 2 * node->padding_y;
if (text_area_logical_w > 0 && text_area_logical_h > 0) {
cairo_save(cr);
double text_x = (node->border_width + node->padding_x) * scale;
double text_y = (node->border_width + node->padding_y) * scale;
double text_area_w = text_area_logical_w * scale;
double text_area_h = text_area_logical_h * scale;
PangoContext *ctx = pango_cairo_create_context(cr);
pango_cairo_context_set_resolution(ctx, 96.0 * scale);
PangoLayout *layout = pango_layout_new(ctx);
PangoFontDescription *desc = get_cached_font_desc(node->font_desc);
pango_layout_set_font_description(layout, desc);
pango_layout_set_text(layout, safe_text, -1);
pango_layout_set_wrap(layout, PANGO_WRAP_NONE);
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
pango_layout_set_width(layout, (int)(text_area_w * PANGO_SCALE));
int text_pixel_w, text_pixel_h;
pango_layout_get_pixel_size(layout, &text_pixel_w, &text_pixel_h);
double y_offset = (text_area_h - text_pixel_h) / 2.0;
if (y_offset < 0)
y_offset = 0;
cairo_translate(cr, text_x, text_y + y_offset);
cairo_set_source_rgba(cr, active_fg[0], active_fg[1], active_fg[2],
active_fg[3]);
pango_cairo_show_layout(cr, layout);
g_object_unref(layout);
g_object_unref(ctx);
cairo_restore(cr);
}
if (draw_border) {
cairo_set_source_rgba(cr, node->border_color[0], node->border_color[1],
node->border_color[2], node->border_color[3]);
cairo_set_line_width(cr, border_phys);
double half_lw = border_phys * 0.5;
double bx = bg_x - half_lw;
double by = bg_y - half_lw;
double bw = bg_w + border_phys;
double bh = bg_h + border_phys;
if (radius > 0.0) {
double outer_radius = radius + half_lw;
if (outer_radius < 0.0)
outer_radius = 0.0;
draw_rounded_rect(cr, bx, by, bw, bh, outer_radius);
} else {
cairo_rectangle(cr, bx, by, bw, bh);
}
cairo_stroke(cr);
}
cairo_surface_flush(node->surface);
cairo_destroy(cr);
if (node->buffer) {
wlr_buffer_drop(&node->buffer->base);
node->buffer = NULL;
}
struct mango_text_buffer *buf = calloc(1, sizeof(*buf));
if (!buf)
return;
wlr_buffer_init(&buf->base, &text_buffer_impl, node->surface_pixel_w,
node->surface_pixel_h);
buf->surface = node->surface;
node->buffer = buf;
wlr_scene_buffer_set_buffer(node->scene_buffer, &buf->base);
node->logical_width = node->target_width;
node->logical_height = node->target_height;
wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width,
node->logical_height);
}
void mango_tab_bar_node_set_focus(struct mango_tab_bar_node *node,
bool focused) {
if (!node || node->focused == focused)
return;
node->focused = focused;
if (node->last_text) {
float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f;
mango_tab_bar_node_update(node, node->last_text, scale);
}
}
void mango_tab_bar_node_set_colors(struct mango_tab_bar_node *node,
const float fg[4], const float bg[4]) {
if (!node)
return;
memcpy(node->fg_color, fg, sizeof(node->fg_color));
memcpy(node->bg_color, bg, sizeof(node->bg_color));
if (!node->focused && node->last_text) {
float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f;
mango_tab_bar_node_update(node, node->last_text, scale);
}
}
void mango_jump_label_node_apply_config(struct mango_jump_label_node *node,
const DecorateDrawData *data) {
if (!node || !data)
return;
memcpy(node->fg_color, data->fg_color, sizeof(node->fg_color));
memcpy(node->bg_color, data->bg_color, sizeof(node->bg_color));
memcpy(node->focus_fg_color, data->focus_fg_color,
sizeof(node->focus_fg_color));
memcpy(node->focus_bg_color, data->focus_bg_color,
sizeof(node->focus_bg_color));
memcpy(node->border_color, data->border_color, sizeof(node->border_color));
node->border_width = data->border_width;
node->corner_radius = data->corner_radius;
node->padding_x = data->padding_x;
node->padding_y = data->padding_y;
g_free(node->font_desc);
node->font_desc =
g_strdup(data->font_desc ? data->font_desc : "monospace Bold 16");
if (node->cached_text && node->cached_scale > 0.0f) {
mango_jump_label_node_update(node, node->cached_text,
node->cached_scale);
}
}
void mango_tab_bar_node_apply_config(struct mango_tab_bar_node *node,
const DecorateDrawData *data) {
if (!node || !data)
return;
memcpy(node->fg_color, data->fg_color, sizeof(node->fg_color));
memcpy(node->bg_color, data->bg_color, sizeof(node->bg_color));
memcpy(node->focus_fg_color, data->focus_fg_color,
sizeof(node->focus_fg_color));
memcpy(node->focus_bg_color, data->focus_bg_color,
sizeof(node->focus_bg_color));
memcpy(node->border_color, data->border_color, sizeof(node->border_color));
node->border_width = data->border_width;
node->corner_radius = data->corner_radius;
node->padding_x = data->padding_x;
node->padding_y = data->padding_y;
g_free(node->font_desc);
node->font_desc =
g_strdup(data->font_desc ? data->font_desc : "monospace Bold 16");
if (node->last_text) {
float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f;
mango_tab_bar_node_update(node, node->last_text, scale);
}
}

166
src/draw/text-node.h Normal file
View file

@ -0,0 +1,166 @@
#ifndef jump_label_node_H
#define jump_label_node_H
#include <cairo.h>
#include <pango/pango.h>
#include <pango/pangocairo.h>
#include <stdbool.h>
#include <stdint.h>
#include <wlr/types/wlr_scene.h>
// 原有结构体,假设已存在
typedef struct {
float fg_color[4];
float bg_color[4];
float focus_fg_color[4];
float focus_bg_color[4];
float border_color[4];
int32_t border_width;
int32_t corner_radius;
int32_t padding_x;
int32_t padding_y;
const char *font_desc;
} DecorateDrawData;
struct mango_text_buffer {
struct wlr_buffer base;
cairo_surface_t *surface;
};
struct mango_jump_label_node {
struct wlr_scene_buffer *scene_buffer;
struct mango_text_buffer *buffer;
cairo_surface_t *surface;
int surface_pixel_w, surface_pixel_h;
float fg_color[4];
float bg_color[4];
float focus_fg_color[4];
float focus_bg_color[4];
float border_color[4];
int32_t border_width;
int32_t corner_radius;
int32_t padding_x;
int32_t padding_y;
char *font_desc;
// 缓存
char *cached_text;
char *cached_font_desc;
float cached_scale;
float cached_fg_color[4];
float cached_bg_color[4];
float cached_focus_fg_color[4];
float cached_focus_bg_color[4];
float cached_border_color[4];
int32_t cached_border_width;
int32_t cached_corner_radius;
int32_t cached_padding_x;
int32_t cached_padding_y;
bool cached_focused;
bool focused;
// 测量
cairo_surface_t *measure_surface;
cairo_t *measure_cr;
PangoContext *measure_context;
PangoLayout *measure_layout;
float measure_scale;
int32_t logical_width;
int32_t logical_height;
};
struct mango_tab_bar_node {
struct wlr_scene_buffer *scene_buffer;
struct mango_text_buffer *buffer;
cairo_surface_t *surface;
int surface_pixel_w, surface_pixel_h;
// 初始配置
float fg_color[4];
float bg_color[4];
float focus_fg_color[4];
float focus_bg_color[4];
float border_color[4];
int32_t border_width;
int32_t corner_radius;
int32_t padding_x;
int32_t padding_y;
char *font_desc;
// 尺寸
int32_t target_width;
int32_t target_height;
// 缓存
char *cached_text;
char *cached_font_desc;
float cached_scale;
float cached_fg_color[4];
float cached_bg_color[4];
float cached_focus_fg_color[4];
float cached_focus_bg_color[4];
float cached_border_color[4];
int32_t cached_border_width;
int32_t cached_corner_radius;
int32_t cached_padding_x;
int32_t cached_padding_y;
int32_t cached_target_width;
int32_t cached_target_height;
bool cached_focused;
bool focused;
// 上次绘制参数(用于尺寸变化重绘)
char *last_text;
float last_scale;
// 测量
cairo_surface_t *measure_surface;
cairo_t *measure_cr;
PangoContext *measure_context;
PangoLayout *measure_layout;
float measure_scale;
int32_t logical_width;
int32_t logical_height;
};
void mango_text_global_finish(void);
struct mango_jump_label_node *
mango_jump_label_node_create(struct wlr_scene_tree *parent,
DecorateDrawData data);
void mango_jump_label_node_destroy(struct mango_jump_label_node *node);
void mango_jump_label_node_set_background(struct mango_jump_label_node *node,
float r, float g, float b, float a);
void mango_jump_label_node_set_border(struct mango_jump_label_node *node,
float r, float g, float b, float a,
int32_t width, int32_t radius);
void mango_jump_label_node_set_padding(struct mango_jump_label_node *node,
int32_t pad_x, int32_t pad_y);
void mango_jump_label_node_update(struct mango_jump_label_node *node,
const char *text, float scale);
struct mango_tab_bar_node *
mango_tab_bar_node_create(void *mango_node_data, struct wlr_scene_tree *parent,
DecorateDrawData data, int32_t width, int32_t height);
void mango_tab_bar_node_destroy(struct mango_tab_bar_node *node);
void mango_tab_bar_node_set_size(struct mango_tab_bar_node *node, int32_t width,
int32_t height);
void mango_tab_bar_node_update(struct mango_tab_bar_node *node,
const char *text, float scale);
void mango_jump_label_node_set_focus(struct mango_jump_label_node *node,
bool focused);
void mango_tab_bar_node_set_focus(struct mango_tab_bar_node *node,
bool focused);
void mango_tab_bar_node_set_colors(struct mango_tab_bar_node *node,
const float fg[4], const float bg[4]);
void mango_jump_label_node_apply_config(struct mango_jump_label_node *node,
const DecorateDrawData *data);
void mango_tab_bar_node_apply_config(struct mango_tab_bar_node *node,
const DecorateDrawData *data);
#endif // jump_label_node_H

View file

@ -1,5 +1,6 @@
#include "dwl-ipc.h"
#include "ext-workspace.h"
#include "foreign-toplevel.h"
#include "tablet.h"
#include "tearing.h"
#include "text-input.h"
#include "text-input.h"

View file

@ -192,7 +192,7 @@ void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) {
if (wl_resource_get_version(ipc_output->resource) >=
ZDWL_IPC_OUTPUT_V2_LAST_LAYER_SINCE_VERSION) {
zdwl_ipc_output_v2_send_last_layer(ipc_output->resource,
monitor->last_surface_ws_name);
monitor->last_open_surface);
}
if (wl_resource_get_version(ipc_output->resource) >=
@ -239,7 +239,7 @@ void dwl_ipc_output_set_client_tags(struct wl_client *client,
if (selmon == monitor)
focusclient(focustop(monitor), 1);
arrange(selmon, false, false);
printstatus();
printstatus(IPC_WATCH_ARRANGGE);
}
void dwl_ipc_output_set_layout(struct wl_client *client,
@ -258,7 +258,7 @@ void dwl_ipc_output_set_layout(struct wl_client *client,
monitor->pertag->ltidxs[monitor->pertag->curtag] = &layouts[index];
clear_fullscreen_and_maximized_state(monitor);
arrange(monitor, false, false);
printstatus();
printstatus(IPC_WATCH_ARRANGGE);
}
void dwl_ipc_output_set_tags(struct wl_client *client,

View file

@ -17,6 +17,10 @@ struct workspace {
struct wlr_ext_workspace_manager_v1 *ext_manager;
struct wl_list workspaces;
static void handle_ext_commit(struct wl_listener *listener, void *data);
static struct wl_listener ext_manager_commit_listener = {.notify =
handle_ext_commit};
void goto_workspace(struct workspace *target) {
uint32_t tag;
tag = 1 << (target->tag - 1);
@ -184,10 +188,10 @@ void refresh_monitors_workspaces_status(Monitor *m) {
int32_t i;
if (m->isoverview) {
add_workspace_by_tag(0, m);
for (i = 1; i <= LENGTH(tags); i++) {
remove_workspace_by_tag(i, m);
}
add_workspace_by_tag(0, m);
} else {
remove_workspace_by_tag(0, m);
for (i = 1; i <= LENGTH(tags); i++) {
@ -203,7 +207,5 @@ void workspaces_init() {
wl_list_init(&workspaces);
static struct wl_listener commit_listener;
commit_listener.notify = handle_ext_commit;
wl_signal_add(&ext_manager->events.commit, &commit_listener);
wl_signal_add(&ext_manager->events.commit, &ext_manager_commit_listener);
}

View file

@ -4,40 +4,24 @@ static struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager;
void handle_foreign_activate_request(struct wl_listener *listener, void *data) {
Client *c = wl_container_of(listener, c, foreign_activate_request);
uint32_t target;
if (c->swallowing)
return;
if (c->isminimized) {
c->is_in_scratchpad = 0;
c->isnamedscratchpad = 0;
c->is_scratchpad_show = 0;
setborder_color(c);
show_hide_client(c);
arrange(c->mon, true, false);
return;
}
target = get_tags_first_tag(c->tags);
view_in_mon(&(Arg){.ui = target}, true, c->mon, true);
focusclient(c, 1);
client_active(c);
}
void handle_foreign_maximize_request(struct wl_listener *listener, void *data) {
Client *c = wl_container_of(listener, c, foreign_maximize_request);
struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data;
if (c->swallowing)
if (c->swallowing || !c->mon)
return;
if (c->ismaximizescreen && !event->maximized) {
setmaximizescreen(c, 0);
setmaximizescreen(c, 0, true);
return;
}
if (!c->ismaximizescreen && event->maximized) {
setmaximizescreen(c, 1);
setmaximizescreen(c, 1, true);
return;
}
}
@ -46,7 +30,7 @@ void handle_foreign_minimize_request(struct wl_listener *listener, void *data) {
Client *c = wl_container_of(listener, c, foreign_minimize_request);
struct wlr_foreign_toplevel_handle_v1_minimized_event *event = data;
if (c->swallowing)
if (c->swallowing || !c->mon)
return;
if (!c->isminimized && event->minimized) {
@ -71,16 +55,16 @@ void handle_foreign_fullscreen_request(struct wl_listener *listener,
Client *c = wl_container_of(listener, c, foreign_fullscreen_request);
struct wlr_foreign_toplevel_handle_v1_fullscreen_event *event = data;
if (c->swallowing)
if (c->swallowing || !c->mon)
return;
if (c->isfullscreen && !event->fullscreen) {
setfullscreen(c, 0);
setfullscreen(c, 0, true);
return;
}
if (!c->isfullscreen && event->fullscreen) {
setfullscreen(c, 1);
setfullscreen(c, 1, true);
return;
}
}
@ -98,10 +82,6 @@ void handle_foreign_destroy(struct wl_listener *listener, void *data) {
wl_list_remove(&c->foreign_fullscreen_request.link);
wl_list_remove(&c->foreign_close_request.link);
wl_list_remove(&c->foreign_destroy.link);
}
void remove_foreign_topleve(Client *c) {
wlr_foreign_toplevel_handle_v1_destroy(c->foreign_toplevel);
c->foreign_toplevel = NULL;
}
@ -144,7 +124,23 @@ void add_foreign_toplevel(Client *c) {
}
}
void reset_foreign_tolevel(Client *c) {
remove_foreign_topleve(c);
add_foreign_toplevel(c);
void reset_foreign_tolevel(Client *c, Monitor *oldmon, Monitor *newmon) {
if (!c)
return;
if (!c->foreign_toplevel) {
add_foreign_toplevel(c);
return;
}
if (oldmon == newmon)
return;
if (oldmon)
wlr_foreign_toplevel_handle_v1_output_leave(c->foreign_toplevel,
oldmon->wlr_output);
if (newmon)
wlr_foreign_toplevel_handle_v1_output_enter(c->foreign_toplevel,
newmon->wlr_output);
}

458
src/ext-protocol/tablet.h Normal file
View file

@ -0,0 +1,458 @@
#include <wlr/types/wlr_tablet_pad.h>
#include <wlr/types/wlr_tablet_tool.h>
#include <wlr/types/wlr_tablet_v2.h>
static void createtablet(struct wlr_input_device *device);
static void destroytablet(struct wl_listener *listener, void *data);
static void createtabletpad(struct wlr_input_device *device);
static void destroytabletpad(struct wl_listener *listener, void *data);
static void tabletpadtabletdestroy(struct wl_listener *listener, void *data);
static void tabletpadattach(struct wl_listener *listener, void *data);
static void destroytabletsurfacenotify(struct wl_listener *listener,
void *data);
static void destroytablettool(struct wl_listener *listener, void *data);
static void tablettoolsetcursor(struct wl_listener *listener, void *data);
static void tablettoolproximity(struct wl_listener *listener, void *data);
static void tablettoolaxis(struct wl_listener *listener, void *data);
static void tablettoolbutton(struct wl_listener *listener, void *data);
static void tablettooltip(struct wl_listener *listener, void *data);
static struct wlr_tablet_manager_v2 *tablet_mgr;
struct Tablet {
struct wlr_tablet_v2_tablet *tablet_v2;
struct wl_listener destroy;
struct wlr_input_device *device;
struct wl_list link;
};
static struct wl_list tablets;
struct TabletTool {
struct wlr_tablet_v2_tablet_tool *tool_v2;
struct Tablet *tablet;
struct wlr_surface *curr_surface;
struct wl_listener destroy;
struct wl_listener surface_destroy;
struct wl_listener set_cursor;
double tilt_x, tilt_y;
};
struct TabletPad {
struct wlr_tablet_v2_tablet_pad *pad_v2;
struct wlr_input_device *device;
struct Tablet *tablet;
struct wl_listener tablet_destroy;
struct wl_listener attach;
struct wl_listener destroy;
struct wl_list link;
};
static struct wl_list tablet_pads;
static void attach_tablet_pad(struct TabletPad *tablet_pad,
struct Tablet *tablet);
static void tablettoolmotion(struct TabletTool *tool, bool change_x,
bool change_y, double x, double y, double dx,
double dy);
static struct wl_listener tablet_tool_axis = {.notify = tablettoolaxis};
static struct wl_listener tablet_tool_button = {.notify = tablettoolbutton};
static struct wl_listener tablet_tool_proximity = {.notify =
tablettoolproximity};
static struct wl_listener tablet_tool_tip = {.notify = tablettooltip};
void createtablet(struct wlr_input_device *device) {
struct Tablet *tablet = calloc(1, sizeof(struct Tablet));
if (!tablet) {
wlr_log(WLR_ERROR, "could not allocate tablet");
return;
}
struct libinput_device *device_handle = NULL;
if (!wlr_input_device_is_libinput(device) ||
!(device_handle = wlr_libinput_get_device_handle(device))) {
free(tablet);
return;
}
tablet->device = device;
tablet->tablet_v2 = wlr_tablet_create(tablet_mgr, seat, device);
if (!tablet->tablet_v2) {
free(tablet);
return;
}
tablet->tablet_v2->wlr_tablet->data = tablet;
tablet->destroy.notify = destroytablet;
wl_signal_add(&tablet->device->events.destroy, &tablet->destroy);
if (libinput_device_config_send_events_get_modes(device_handle)) {
libinput_device_config_send_events_set_mode(device_handle,
config.send_events_mode);
wlr_cursor_attach_input_device(cursor, device);
}
wl_list_insert(&tablets, &tablet->link);
/* Search for a sibling tablet pad */
struct libinput_device_group *group = libinput_device_get_device_group(
wlr_libinput_get_device_handle(device));
struct TabletPad *tablet_pad;
wl_list_for_each(tablet_pad, &tablet_pads, link) {
struct wlr_input_device *pad_device = tablet_pad->device;
if (!wlr_input_device_is_libinput(pad_device)) {
continue;
}
struct libinput_device_group *pad_group =
libinput_device_get_device_group(
wlr_libinput_get_device_handle(pad_device));
if (pad_group == group) {
attach_tablet_pad(tablet_pad, tablet);
break;
}
}
}
void destroytablet(struct wl_listener *listener, void *data) {
struct Tablet *tablet = wl_container_of(listener, tablet, destroy);
wl_list_remove(&listener->link);
wl_list_remove(&tablet->link);
free(tablet);
}
void tabletpadtabletdestroy(struct wl_listener *listener, void *data) {
struct TabletPad *tablet_pad =
wl_container_of(listener, tablet_pad, tablet_destroy);
tablet_pad->tablet = NULL;
wl_list_remove(&tablet_pad->tablet_destroy.link);
wl_list_init(&tablet_pad->tablet_destroy.link);
}
void attach_tablet_pad(struct TabletPad *tablet_pad, struct Tablet *tablet) {
tablet_pad->tablet = tablet;
wl_list_remove(&tablet_pad->tablet_destroy.link);
tablet_pad->tablet_destroy.notify = tabletpadtabletdestroy;
wl_signal_add(&tablet->device->events.destroy, &tablet_pad->tablet_destroy);
}
void tabletpadattach(struct wl_listener *listener, void *data) {
struct TabletPad *tablet_pad =
wl_container_of(listener, tablet_pad, attach);
struct wlr_tablet_tool *wlr_tool = data;
struct TabletTool *tool = wlr_tool->data;
if (!tool) {
return;
}
attach_tablet_pad(tablet_pad, tool->tablet);
}
void createtabletpad(struct wlr_input_device *device) {
struct TabletPad *tablet_pad = calloc(1, sizeof(struct TabletPad));
if (!tablet_pad) {
wlr_log(WLR_ERROR, "could not allocate tablet_pad");
return;
}
tablet_pad->device = device;
tablet_pad->pad_v2 = wlr_tablet_pad_create(tablet_mgr, seat, device);
if (!tablet_pad->pad_v2) {
wlr_log(WLR_ERROR, "could not create tablet_pad_v2 wrapper");
free(tablet_pad);
return;
}
tablet_pad->destroy.notify = destroytabletpad;
tablet_pad->attach.notify = tabletpadattach;
wl_list_init(&tablet_pad->tablet_destroy.link);
wl_signal_add(&device->events.destroy, &tablet_pad->destroy);
wl_signal_add(&tablet_pad->pad_v2->wlr_pad->events.attach_tablet,
&tablet_pad->attach);
wl_list_insert(&tablet_pads, &tablet_pad->link);
/* Search for a sibling tablet */
if (!wlr_input_device_is_libinput(device)) {
/* We can only do this on libinput devices */
return;
}
struct libinput_device_group *group = libinput_device_get_device_group(
wlr_libinput_get_device_handle(device));
struct Tablet *tablet;
wl_list_for_each(tablet, &tablets, link) {
struct wlr_input_device *tablet_device = tablet->device;
if (!wlr_input_device_is_libinput(tablet_device)) {
continue;
}
struct libinput_device_group *tablet_group =
libinput_device_get_device_group(
wlr_libinput_get_device_handle(tablet_device));
if (tablet_group == group) {
attach_tablet_pad(tablet_pad, tablet);
break;
}
}
}
void destroytabletpad(struct wl_listener *listener, void *data) {
struct TabletPad *tablet_pad =
wl_container_of(listener, tablet_pad, destroy);
wl_list_remove(&listener->link);
wl_list_remove(&tablet_pad->link);
wl_list_remove(&tablet_pad->tablet_destroy.link);
wl_list_remove(&tablet_pad->attach.link);
free(tablet_pad);
}
void destroytabletsurfacenotify(struct wl_listener *listener, void *data) {
struct TabletTool *tool = wl_container_of(listener, tool, surface_destroy);
wl_list_remove(&tool->surface_destroy.link);
tool->curr_surface = NULL;
}
void destroytablettool(struct wl_listener *listener, void *data) {
struct TabletTool *tool = wl_container_of(listener, tool, destroy);
if (tool->curr_surface)
wl_list_remove(&tool->surface_destroy.link);
wl_list_remove(&tool->set_cursor.link);
wl_list_remove(&listener->link);
free(tool);
}
static void tablettoolsetcursor(struct wl_listener *listener, void *data) {
struct TabletTool *tool = wl_container_of(listener, tool, set_cursor);
struct wlr_tablet_v2_event_cursor *event = data;
struct wlr_seat_client *focused_client = NULL;
if (tool->tool_v2->focused_surface) {
focused_client = wlr_seat_client_for_wl_client(
seat,
wl_resource_get_client(tool->tool_v2->focused_surface->resource));
}
if (focused_client != event->seat_client)
return;
wlr_cursor_set_surface(cursor, event->surface, event->hotspot_x,
event->hotspot_y);
}
void tablettoolmotion(struct TabletTool *tool, bool change_x, bool change_y,
double x, double y, double dx, double dy) {
struct wlr_surface *surface = NULL;
Client *c = NULL, *w = NULL;
LayerSurface *l = NULL;
struct Tablet *tablet = tool->tablet;
struct TabletPad *tablet_pad;
double sx, sy;
if (!change_x && !change_y)
return;
// TODO: apply constraints
switch (tool->tool_v2->wlr_tool->type) {
case WLR_TABLET_TOOL_TYPE_LENS:
case WLR_TABLET_TOOL_TYPE_MOUSE:
wlr_cursor_move(cursor, tablet->device, dx, dy);
break;
default:
wlr_cursor_warp_absolute(cursor, tablet->device, change_x ? x : NAN,
change_y ? y : NAN);
break;
}
motionnotify(0, NULL, 0, 0, 0, 0);
if (config.sloppyfocus)
selmon = xytomon(cursor->x, cursor->y);
xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy);
if (cursor_mode == CurPressed && !seat->drag &&
surface != seat->pointer_state.focused_surface &&
toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w,
&l) >= 0) {
c = w;
surface = seat->pointer_state.focused_surface;
sx = cursor->x - (l ? l->scene->node.x : w->geom.x);
sy = cursor->y - (l ? l->scene->node.y : w->geom.y);
}
if (config.sloppyfocus && c && c->scene->node.enabled &&
(surface != seat->pointer_state.focused_surface ||
(selmon && selmon->sel && c != selmon->sel)) &&
!client_is_unmanaged(c))
focusclient(c, 0);
if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet->tablet_v2))
surface = NULL;
if (surface != tool->curr_surface) {
if (tool->curr_surface) {
// TODO: wait until all buttons released before leaving
wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tool_v2);
wl_list_for_each(tablet_pad, &tablet_pads, link) {
if (tablet_pad->tablet && tablet_pad->tablet == tablet)
wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad->pad_v2,
tool->curr_surface);
}
wl_list_remove(&tool->surface_destroy.link);
}
if (surface) {
wl_list_for_each(tablet_pad, &tablet_pads, link) {
if (tablet_pad->tablet && tablet_pad->tablet == tablet)
wlr_tablet_v2_tablet_pad_notify_enter(
tablet_pad->pad_v2, tablet->tablet_v2, surface);
}
wlr_tablet_v2_tablet_tool_notify_proximity_in(
tool->tool_v2, tablet->tablet_v2, surface);
wl_signal_add(&surface->events.destroy, &tool->surface_destroy);
}
tool->curr_surface = surface;
}
if (surface)
wlr_tablet_v2_tablet_tool_notify_motion(tool->tool_v2, sx, sy);
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
handlecursoractivity();
}
void tablettoolproximity(struct wl_listener *listener, void *data) {
struct wlr_tablet_tool_proximity_event *event = data;
struct wlr_tablet_tool *wlr_tool = event->tool;
struct TabletTool *tool = wlr_tool->data;
Monitor *m_iter;
if (!tool) {
tool = calloc(1, sizeof(struct TabletTool));
if (!tool) {
wlr_log(WLR_ERROR, "could not allocate tablet_tool");
return;
}
tool->tool_v2 = wlr_tablet_tool_create(tablet_mgr, seat, wlr_tool);
tool->surface_destroy.notify = destroytabletsurfacenotify;
tool->destroy.notify = destroytablettool;
tool->set_cursor.notify = tablettoolsetcursor;
tool->tablet = event->tablet->data;
wlr_tool->data = tool;
wl_signal_add(&tool->tool_v2->wlr_tool->events.destroy, &tool->destroy);
wl_signal_add(&tool->tool_v2->events.set_cursor, &tool->set_cursor);
if (config.tablet_map_to_mon) {
wl_list_for_each(m_iter, &mons, link) {
if (match_monitor_spec(config.tablet_map_to_mon, m_iter)) {
wlr_log(WLR_DEBUG, "Mapping tablet %s to output %s",
event->tablet->base.name, config.tablet_map_to_mon);
wlr_cursor_map_input_to_output(cursor, &event->tablet->base,
m_iter->wlr_output);
break;
}
}
}
}
switch (event->state) {
case WLR_TABLET_TOOL_PROXIMITY_OUT:
wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tool_v2);
if (tool->curr_surface)
wl_list_remove(&tool->surface_destroy.link);
tool->curr_surface = NULL;
break;
case WLR_TABLET_TOOL_PROXIMITY_IN:
tablettoolmotion(tool, true, true, event->x, event->y, 0, 0);
break;
}
}
void tablettoolaxis(struct wl_listener *listener, void *data) {
struct wlr_tablet_tool_axis_event *event = data;
struct TabletTool *tool = event->tool->data;
if (!tool)
return;
tablettoolmotion(tool, event->updated_axes & WLR_TABLET_TOOL_AXIS_X,
event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, event->x,
event->y, event->dx, event->dy);
if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE)
wlr_tablet_v2_tablet_tool_notify_pressure(tool->tool_v2,
event->pressure);
if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE)
wlr_tablet_v2_tablet_tool_notify_distance(tool->tool_v2,
event->distance);
if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X)
tool->tilt_x = event->tilt_x;
if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y)
tool->tilt_y = event->tilt_y;
if (event->updated_axes &
(WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) {
wlr_tablet_v2_tablet_tool_notify_tilt(tool->tool_v2, tool->tilt_x,
tool->tilt_y);
}
if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION)
wlr_tablet_v2_tablet_tool_notify_rotation(tool->tool_v2,
event->rotation);
if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER)
wlr_tablet_v2_tablet_tool_notify_slider(tool->tool_v2, event->slider);
if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL)
wlr_tablet_v2_tablet_tool_notify_wheel(tool->tool_v2,
event->wheel_delta, 0);
}
void tablettoolbutton(struct wl_listener *listener, void *data) {
struct wlr_tablet_tool_button_event *event = data;
struct TabletTool *tool = event->tool->data;
if (!tool)
return;
wlr_tablet_v2_tablet_tool_notify_button(
tool->tool_v2, event->button,
(enum zwp_tablet_pad_v2_button_state)event->state);
}
void tablettooltip(struct wl_listener *listener, void *data) {
struct wlr_tablet_tool_tip_event *event = data;
struct TabletTool *tool = event->tool->data;
if (!tool)
return;
struct wlr_pointer_button_event fakeptrbtnevent = {
.button = BTN_LEFT,
.state = event->state == WLR_TABLET_TOOL_TIP_UP
? WL_POINTER_BUTTON_STATE_RELEASED
: WL_POINTER_BUTTON_STATE_PRESSED,
.time_msec = event->time_msec,
};
if (handle_buttonpress(&fakeptrbtnevent))
return;
if (!tool->curr_surface) {
wlr_seat_pointer_notify_button(seat, fakeptrbtnevent.time_msec,
fakeptrbtnevent.button,
fakeptrbtnevent.state);
return;
}
if (event->state == WLR_TABLET_TOOL_TIP_UP) {
wlr_tablet_v2_tablet_tool_notify_up(tool->tool_v2);
return;
}
wlr_tablet_v2_tablet_tool_notify_down(tool->tool_v2);
wlr_tablet_tool_v2_start_implicit_grab(tool->tool_v2);
}

View file

@ -60,7 +60,7 @@ void handle_tearing_new_object(struct wl_listener *listener, void *data) {
bool check_tearing_frame_allow(Monitor *m) {
/* never allow tearing when disabled */
if (!allow_tearing) {
if (!config.allow_tearing) {
return false;
}
@ -72,7 +72,7 @@ bool check_tearing_frame_allow(Monitor *m) {
}
/* allow tearing for any window when requested or forced */
if (allow_tearing == TEARING_ENABLED) {
if (config.allow_tearing == TEARING_ENABLED) {
if (c->force_tearing == STATE_UNSPECIFIED) {
return c->tearing_hint;
} else {
@ -87,7 +87,8 @@ bool check_tearing_frame_allow(Monitor *m) {
if (c->force_tearing == STATE_UNSPECIFIED) {
/* honor the tearing hint or the fullscreen-force preference */
return c->tearing_hint || allow_tearing == TEARING_FULLSCREEN_ONLY;
return c->tearing_hint ||
config.allow_tearing == TEARING_FULLSCREEN_ONLY;
}
/* honor tearing as requested by action */

View file

@ -77,15 +77,6 @@ Monitor *output_from_wlr_output(struct wlr_output *wlr_output) {
return NULL;
}
Monitor *output_nearest_to(int32_t lx, int32_t ly) {
double closest_x, closest_y;
wlr_output_layout_closest_point(output_layout, NULL, lx, ly, &closest_x,
&closest_y);
return output_from_wlr_output(
wlr_output_layout_output_at(output_layout, closest_x, closest_y));
}
bool output_is_usable(Monitor *m) { return m && m->wlr_output->enabled; }
static bool
@ -255,7 +246,7 @@ static void update_popup_position(struct dwl_input_method_popup *popup) {
cursor_rect = (struct wlr_box){0};
}
output = output_nearest_to(cursor_rect.x, cursor_rect.y);
output = get_monitor_nearest_to(cursor_rect.x, cursor_rect.y);
if (!output_is_usable(output)) {
return;
}

View file

@ -1,24 +1,29 @@
bool check_hit_no_border(Client *c) {
int32_t i;
bool hit_no_border = false;
if (!c->mon)
return false;
if (c->tags <= 0)
return false;
if (!render_border) {
hit_no_border = true;
}
for (i = 0; i < config.tag_rules_count; i++) {
if (c->tags & (1 << (config.tag_rules[i].id - 1)) &&
config.tag_rules[i].no_render_border) {
hit_no_border = true;
}
if (c->mon && !c->mon->isoverview &&
c->mon->pertag->no_render_border[get_tags_first_tag_num(c->tags)]) {
hit_no_border = true;
}
if (no_border_when_single && c && c->mon &&
if (config.no_border_when_single && c && c->mon &&
((ISSCROLLTILED(c) && c->mon->visible_scroll_tiling_clients == 1) ||
c->mon->visible_clients == 1)) {
hit_no_border = true;
}
return hit_no_border;
}
Client *termforwin(Client *w) {
Client *c = NULL;
@ -39,7 +44,7 @@ Client *get_client_by_id_or_title(const char *arg_id, const char *arg_title) {
const char *appid, *title;
Client *c = NULL;
wl_list_for_each(c, &clients, link) {
if (!scratchpad_cross_monitor && c->mon != selmon) {
if (!config.scratchpad_cross_monitor && c->mon != selmon) {
continue;
}
@ -83,9 +88,12 @@ setclient_coordinate_center(Client *c, Monitor *tm, struct wlr_box geom,
int32_t len = 0;
Monitor *m = tm ? tm : selmon;
uint32_t cbw = check_hit_no_border(c) ? c->bw : 0;
if (!m)
return geom;
if (!c->no_force_center && m) {
uint32_t cbw = c && check_hit_no_border(c) ? c->bw : 0;
if ((!c || !c->no_force_center) && m) {
tempbox.x = m->w.x + (m->w.width - geom.width) / 2;
tempbox.y = m->w.y + (m->w.height - geom.height) / 2;
} else {
@ -155,296 +163,137 @@ Client *center_tiled_select(Monitor *m) {
}
return target_c;
}
Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating,
bool ignore_align) {
Client *find_client_by_direction(Client *tc, const Arg *arg,
bool findfloating) {
Client *c = NULL;
Client **tempClients = NULL; // 初始化为 NULL
int32_t last = -1;
// 第一次遍历,计算客户端数量
wl_list_for_each(c, &clients, link) {
if (c && (findfloating || !c->isfloating) && !c->isunglobal &&
(focus_cross_monitor || c->mon == tc->mon) &&
(c->tags & c->mon->tagset[c->mon->seltags])) {
last++;
}
}
if (last < 0) {
return NULL; // 没有符合条件的客户端
}
// 动态分配内存
tempClients = malloc((last + 1) * sizeof(Client *));
if (!tempClients) {
// 处理内存分配失败的情况
return NULL;
}
// 第二次遍历,填充 tempClients
last = -1;
wl_list_for_each(c, &clients, link) {
if (c && (findfloating || !c->isfloating) && !c->isunglobal &&
(focus_cross_monitor || c->mon == tc->mon) &&
(c->tags & c->mon->tagset[c->mon->seltags])) {
last++;
tempClients[last] = c;
}
}
int32_t sel_x = tc->geom.x;
int32_t sel_y = tc->geom.y;
int64_t distance = LLONG_MAX;
int64_t same_monitor_distance = LLONG_MAX;
Client *tempFocusClients = NULL;
Client *tempSameMonitorFocusClients = NULL;
int64_t distance = LLONG_MAX;
int64_t same_monitor_distance = LLONG_MAX;
switch (arg->i) {
case UP:
if (!ignore_align) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.y < sel_y &&
tempClients[_i]->geom.x == sel_x &&
tempClients[_i]->mon == tc->mon) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
int32_t tc_l = tc->geom.x;
int32_t tc_r = tc->geom.x + tc->geom.width;
int32_t tc_t = tc->geom.y;
int32_t tc_b = tc->geom.y + tc->geom.height;
int32_t tc_cx = tc_l + tc->geom.width / 2;
int32_t tc_cy = tc_t + tc->geom.height / 2;
for (int32_t step = 0; step < 2; step++) {
if (step == 1 && tempFocusClients)
break;
wl_list_for_each(c, &clients, link) {
if (!c || c == tc)
continue;
if (!findfloating && c->isfloating)
continue;
if (c->is_monocle_hide)
continue;
if (c->isunglobal)
continue;
if (!config.focus_cross_monitor && c->mon != tc->mon)
continue;
if (!(c->tags & c->mon->tagset[c->mon->seltags]))
continue;
if (step == 0 && ((!tc->mon->isoverview &&
!client_is_in_same_stack(tc, c, NULL)) ||
c->mon != tc->mon))
continue;
int32_t c_l = c->geom.x;
int32_t c_r = c->geom.x + c->geom.width;
int32_t c_t = c->geom.y;
int32_t c_b = c->geom.y + c->geom.height;
int32_t c_cx = c_l + c->geom.width / 2;
int32_t c_cy = c_t + c->geom.height / 2;
int64_t main_dist = 0;
int64_t orth_dist = 0;
bool match_dir = false;
switch (arg->i) {
case LEFT:
if (c_cx < tc_cx || (c_cx == tc_cx && c_l < tc_l)) {
match_dir = true;
main_dist = tc_l - c_r;
orth_dist = (c_b < tc_t)
? (tc_t - c_b)
: ((c_t > tc_b) ? (c_t - tc_b) : 0);
}
break;
case RIGHT:
if (c_cx > tc_cx || (c_cx == tc_cx && c_l > tc_l)) {
match_dir = true;
main_dist = c_l - tc_r;
orth_dist = (c_b < tc_t)
? (tc_t - c_b)
: ((c_t > tc_b) ? (c_t - tc_b) : 0);
}
break;
case UP:
if (c_cy < tc_cy || (c_cy == tc_cy && c_t < tc_t)) {
match_dir = true;
main_dist = tc_t - c_b;
orth_dist = (c_r < tc_l)
? (tc_l - c_r)
: ((c_l > tc_r) ? (c_l - tc_r) : 0);
}
break;
case DOWN:
if (c_cy > tc_cy || (c_cy == tc_cy && c_t > tc_t)) {
match_dir = true;
main_dist = c_t - tc_b;
orth_dist = (c_r < tc_l)
? (tc_l - c_r)
: ((c_l > tc_r) ? (c_l - tc_r) : 0);
}
break;
default:
continue;
}
if (!match_dir)
continue;
int64_t penalty = 0;
if (main_dist < 0) {
penalty = 10000000000LL; // 主方向重叠(反方向)的极大惩罚
main_dist = -main_dist;
}
// 正交方向无重叠惩罚,优先选择在同一行/列的窗口
int64_t no_overlap_penalty = 0;
if (orth_dist > 0) {
// LEFT/RIGHT 时 orth_dist 是垂直间距,>0 表示垂直无重叠
// UP/DOWN 时 orth_dist 是水平间距,>0 表示水平无重叠
no_overlap_penalty = 10000000LL;
}
int64_t tmp_distance = penalty + no_overlap_penalty +
(main_dist * main_dist) +
(orth_dist * orth_dist);
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = c;
}
if (c->mon == tc->mon && tmp_distance < same_monitor_distance) {
same_monitor_distance = tmp_distance;
tempSameMonitorFocusClients = c;
}
}
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.y < sel_y &&
tempClients[_i]->mon == tc->mon &&
client_is_in_same_stack(tc, tempClients[_i], NULL)) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
if (tempClients[_i]->mon == tc->mon &&
tmp_distance < same_monitor_distance) {
same_monitor_distance = tmp_distance;
tempSameMonitorFocusClients = tempClients[_i];
}
}
}
}
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.y < sel_y) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
if (tempClients[_i]->mon == tc->mon &&
tmp_distance < same_monitor_distance) {
same_monitor_distance = tmp_distance;
tempSameMonitorFocusClients = tempClients[_i];
}
}
}
}
break;
case DOWN:
if (!ignore_align) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.y > sel_y &&
tempClients[_i]->geom.x == sel_x &&
tempClients[_i]->mon == tc->mon) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
}
}
}
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.y > sel_y &&
tempClients[_i]->mon == tc->mon &&
client_is_in_same_stack(tc, tempClients[_i], NULL)) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
if (tempClients[_i]->mon == tc->mon &&
tmp_distance < same_monitor_distance) {
same_monitor_distance = tmp_distance;
tempSameMonitorFocusClients = tempClients[_i];
}
}
}
}
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.y > sel_y) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
if (tempClients[_i]->mon == tc->mon &&
tmp_distance < same_monitor_distance) {
same_monitor_distance = tmp_distance;
tempSameMonitorFocusClients = tempClients[_i];
}
}
}
}
break;
case LEFT:
if (!ignore_align) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.x < sel_x &&
tempClients[_i]->geom.y == sel_y &&
tempClients[_i]->mon == tc->mon) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
}
}
}
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.x < sel_x &&
tempClients[_i]->mon == tc->mon &&
client_is_in_same_stack(tc, tempClients[_i], NULL)) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
if (tempClients[_i]->mon == tc->mon &&
tmp_distance < same_monitor_distance) {
same_monitor_distance = tmp_distance;
tempSameMonitorFocusClients = tempClients[_i];
}
}
}
}
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.x < sel_x) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
if (tempClients[_i]->mon == tc->mon &&
tmp_distance < same_monitor_distance) {
same_monitor_distance = tmp_distance;
tempSameMonitorFocusClients = tempClients[_i];
}
}
}
}
break;
case RIGHT:
if (!ignore_align) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.x > sel_x &&
tempClients[_i]->geom.y == sel_y &&
tempClients[_i]->mon == tc->mon) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
}
}
}
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.x > sel_x &&
tempClients[_i]->mon == tc->mon &&
client_is_in_same_stack(tc, tempClients[_i], NULL)) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
if (tempClients[_i]->mon == tc->mon &&
tmp_distance < same_monitor_distance) {
same_monitor_distance = tmp_distance;
tempSameMonitorFocusClients = tempClients[_i];
}
}
}
}
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.x > sel_x) {
int32_t dis_x = tempClients[_i]->geom.x - sel_x;
int32_t dis_y = tempClients[_i]->geom.y - sel_y;
int64_t tmp_distance =
dis_x * dis_x + dis_y * dis_y; // 计算距离
if (tmp_distance < distance) {
distance = tmp_distance;
tempFocusClients = tempClients[_i];
}
if (tempClients[_i]->mon == tc->mon &&
tmp_distance < same_monitor_distance) {
same_monitor_distance = tmp_distance;
tempSameMonitorFocusClients = tempClients[_i];
}
}
}
}
break;
}
free(tempClients); // 释放内存
if (tempSameMonitorFocusClients) {
if (tempSameMonitorFocusClients)
return tempSameMonitorFocusClients;
} else {
return tempFocusClients;
}
return tempFocusClients;
}
Client *direction_select(const Arg *arg) {
Client *tc = selmon->sel;
Client *tc = arg->tc ? arg->tc : selmon->sel;
if (!tc)
return NULL;
@ -454,10 +303,7 @@ Client *direction_select(const Arg *arg) {
return NULL;
}
return find_client_by_direction(
tc, arg, true,
(is_scroller_layout(selmon) || is_centertile_layout(selmon)) &&
!selmon->isoverview);
return find_client_by_direction(tc, arg, true);
}
/* We probably should change the name of this, it sounds like
@ -508,21 +354,21 @@ Client *get_next_stack_client(Client *c, bool reverse) {
float *get_border_color(Client *c) {
if (c->mon != selmon) {
return bordercolor;
return config.bordercolor;
} else if (c->isurgent) {
return urgentcolor;
return config.urgentcolor;
} else if (c->is_in_scratchpad && selmon && c == selmon->sel) {
return scratchpadcolor;
return config.scratchpadcolor;
} else if (c->isglobal && selmon && c == selmon->sel) {
return globalcolor;
return config.globalcolor;
} else if (c->isoverlay && selmon && c == selmon->sel) {
return overlaycolor;
return config.overlaycolor;
} else if (c->ismaximizescreen && selmon && c == selmon->sel) {
return maximizescreencolor;
return config.maximizescreencolor;
} else if (selmon && c == selmon->sel) {
return focuscolor;
return config.focuscolor;
} else {
return bordercolor;
return config.bordercolor;
}
}
@ -537,18 +383,6 @@ bool client_only_in_one_tag(Client *c) {
}
}
Client *get_scroll_stack_head(Client *c) {
Client *scroller_stack_head = c;
if (!scroller_stack_head)
return NULL;
while (scroller_stack_head->prev_in_stack) {
scroller_stack_head = scroller_stack_head->prev_in_stack;
}
return scroller_stack_head;
}
bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) {
if (!sc || !tc)
return false;
@ -557,14 +391,15 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) {
if (id != SCROLLER && id != VERTICAL_SCROLLER && id != TILE &&
id != VERTICAL_TILE && id != DECK && id != VERTICAL_DECK &&
id != CENTER_TILE && id != RIGHT_TILE && id != TGMIX)
id != CENTER_TILE && id != RIGHT_TILE)
return false;
if (id == SCROLLER || id == VERTICAL_SCROLLER) {
Client *source_stack_head = get_scroll_stack_head(sc);
Client *target_stack_head = get_scroll_stack_head(tc);
Client *fc_head = fc ? get_scroll_stack_head(fc) : NULL;
if (fc && fc->prev_in_stack && fc_head == source_stack_head)
Client *source_stack_head = scroll_get_stack_head_client(sc);
Client *target_stack_head = scroll_get_stack_head_client(tc);
Client *fc_head = fc ? scroll_get_stack_head_client(fc) : NULL;
if (fc && fc_head == source_stack_head)
return false;
if (source_stack_head == target_stack_head)
return true;
@ -574,23 +409,20 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) {
if (id == TILE || id == VERTICAL_TILE || id == DECK ||
id == VERTICAL_DECK || id == RIGHT_TILE) {
if (fc && !fc->ismaster)
if (tc->ismaster ^ sc->ismaster)
return false;
else if (!sc->ismaster)
return true;
}
if (id == TGMIX) {
if (fc && !fc->ismaster)
if (fc && !(fc->ismaster ^ sc->ismaster))
return false;
if (!sc->ismaster && sc->mon->visible_tiling_clients <= 3)
else
return true;
}
if (id == CENTER_TILE) {
if (fc && !fc->ismaster)
if (tc->ismaster ^ sc->ismaster)
return false;
if (!sc->ismaster && sc->geom.x == tc->geom.x)
if (fc && !(fc->ismaster ^ sc->ismaster))
return false;
if (sc->geom.x == tc->geom.x)
return true;
else
return false;
@ -599,18 +431,18 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) {
return false;
}
Client *get_focused_stack_client(Client *sc) {
Client *get_focused_stack_client(Client *sc, Client *custom_focus_client) {
if (!sc || sc->isfloating)
return sc;
Client *tc = NULL;
Client *fc = focustop(sc->mon);
Client *fc = custom_focus_client ? custom_focus_client : focustop(sc->mon);
if (fc->isfloating || sc->isfloating)
return sc;
wl_list_for_each(tc, &fstack, flink) {
if (tc->iskilling || tc->isunglobal)
if (tc->iskilling || tc->isunglobal || tc->is_monocle_hide)
continue;
if (!VISIBLEON(tc, sc->mon))
continue;
@ -622,4 +454,4 @@ Client *get_focused_stack_client(Client *sc) {
}
}
return sc;
}
}

View file

@ -77,6 +77,28 @@ void get_layout_abbr(char *abbr, const char *full_name) {
}
}
Client *xytoclient(double x, double y) {
Client *c = NULL, *tmp = NULL;
wl_list_for_each_safe(c, tmp, &clients, link) {
if (VISIBLEON(c, c->mon) && c->animation.current.x <= x &&
c->animation.current.y <= y &&
c->animation.current.x + c->animation.current.width >= x &&
c->animation.current.y + c->animation.current.height >= y) {
return c;
}
}
return NULL;
}
static bool layer_ignores_focus(LayerSurface *l) {
if (!l || !l->layer_surface)
return true;
struct wlr_surface *s = l->layer_surface->surface;
return !pixman_region32_not_empty(&s->input_region) ||
l->layer_surface->current.keyboard_interactive ==
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE;
}
void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc,
LayerSurface **pl, double *nx, double *ny) {
struct wlr_scene_node *node, *pnode;
@ -84,6 +106,7 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc,
Client *c = NULL;
LayerSurface *l = NULL;
int32_t layer;
Client *ovc = NULL;
for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) {
@ -96,13 +119,15 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc,
if (!node->enabled)
continue;
if (node->type == WLR_SCENE_NODE_BUFFER)
surface = wlr_scene_surface_try_from_buffer(
wlr_scene_buffer_from_node(node))
->surface;
else if (node->type == WLR_SCENE_NODE_RECT) {
surface = NULL;
break;
if (node->type == WLR_SCENE_NODE_BUFFER) {
struct wlr_scene_surface *scene_surface =
wlr_scene_surface_try_from_buffer(
wlr_scene_buffer_from_node(node));
if (scene_surface) {
surface = scene_surface->surface;
} else {
continue;
}
}
/* start from the topmost layer,
@ -115,10 +140,17 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc,
for (pnode = node; pnode && !c; pnode = &pnode->parent->node)
c = pnode->data;
if (c && c->type == LayerShell) {
l = (LayerSurface *)c;
c = NULL;
l = pnode->data;
}
}
if (node->type == WLR_SCENE_NODE_RECT) {
if (c) {
surface = client_surface(c);
}
break;
}
}
if (psurface)
@ -127,4 +159,27 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc,
*pc = c;
if (pl)
*pl = l;
if (selmon && selmon->isoverview && config.ov_no_resize) {
ovc = xytoclient(x, y);
bool is_below = false;
if (l && l->layer_surface) {
is_below = (l->layer_surface->current.layer ==
ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND ||
l->layer_surface->current.layer ==
ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM);
}
if (ovc && (!l || layer_ignores_focus(l) || is_below)) {
if (pc)
*pc = ovc;
if (psurface)
*psurface = ovc ? client_surface(ovc) : NULL;
if (pl && ovc)
*pl = NULL;
}
}
}

View file

@ -26,6 +26,14 @@ bool is_scroller_layout(Monitor *m) {
return false;
}
bool is_monocle_layout(Monitor *m) {
if (m->pertag->ltidxs[m->pertag->curtag]->id == MONOCLE)
return true;
return false;
}
bool is_centertile_layout(Monitor *m) {
if (m->pertag->ltidxs[m->pertag->curtag]->id == CENTER_TILE)
@ -54,7 +62,7 @@ uint32_t get_tags_first_tag_num(uint32_t source_tags) {
tag = 0;
if (!source_tags) {
return selmon->pertag->curtag;
return 0;
}
for (i = 0; !(tag & 1) && source_tags != 0 && i < LENGTH(tags); i++) {
@ -96,3 +104,81 @@ Monitor *xytomon(double x, double y) {
struct wlr_output *o = wlr_output_layout_output_at(output_layout, x, y);
return o ? o->data : NULL;
}
Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly) {
double closest_x, closest_y;
wlr_output_layout_closest_point(output_layout, NULL, lx, ly, &closest_x,
&closest_y);
return output_from_wlr_output(
wlr_output_layout_output_at(output_layout, closest_x, closest_y));
}
bool match_monitor_spec(char *spec, Monitor *m) {
if (!spec || !m)
return false;
// if the spec does not contain a colon, treat it as a match on the monitor
// name
if (strchr(spec, ':') == NULL) {
return regex_match(spec, m->wlr_output->name);
}
char *spec_copy = strdup(spec);
if (!spec_copy)
return false;
char *name_rule = NULL;
char *make_rule = NULL;
char *model_rule = NULL;
char *serial_rule = NULL;
char *token = strtok(spec_copy, "&&");
while (token) {
char *colon = strchr(token, ':');
if (colon) {
*colon = '\0';
char *key = token;
char *value = colon + 1;
if (strcmp(key, "name") == 0)
name_rule = strdup(value);
else if (strcmp(key, "make") == 0)
make_rule = strdup(value);
else if (strcmp(key, "model") == 0)
model_rule = strdup(value);
else if (strcmp(key, "serial") == 0)
serial_rule = strdup(value);
}
token = strtok(NULL, "&&");
}
bool match = true;
if (name_rule) {
if (!regex_match(name_rule, m->wlr_output->name))
match = false;
}
if (make_rule) {
if (!m->wlr_output->make || strcmp(make_rule, m->wlr_output->make) != 0)
match = false;
}
if (model_rule) {
if (!m->wlr_output->model ||
strcmp(model_rule, m->wlr_output->model) != 0)
match = false;
}
if (serial_rule) {
if (!m->wlr_output->serial ||
strcmp(serial_rule, m->wlr_output->serial) != 0)
match = false;
}
free(spec_copy);
free(name_rule);
free(make_rule);
free(model_rule);
free(serial_rule);
return match;
}

1053
src/ipc/ipc.h Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

635
src/layout/dwindle.h Normal file
View file

@ -0,0 +1,635 @@
static DwindleNode *dwindle_locked_h_node = NULL;
static DwindleNode *dwindle_locked_v_node = NULL;
static DwindleNode *dwindle_new_leaf(Client *c) {
DwindleNode *n = calloc(1, sizeof(DwindleNode));
n->client = c;
return n;
}
// 统计同方向上的节点总和 (N_old)
static int count_block_items(DwindleNode *node, bool split_h) {
if (!node)
return 0;
if (!node->is_split || node->split_h != split_h)
return 1;
return count_block_items(node->first, split_h) +
count_block_items(node->second, split_h);
}
// 向上查找方向块路径,并计算每个祖先节点的绝对占比
static int get_block_path_and_ratios(DwindleNode *target, bool split_h,
DwindleNode **path, float *p) {
int path_len = 0;
path[path_len++] = target;
DwindleNode *curr = target->parent;
while (curr && curr->split_h == split_h) {
path[path_len++] = curr;
curr = curr->parent;
}
p[path_len - 1] = 1.0f; // 方向块根节点占比为 100%
for (int i = path_len - 1; i > 0; i--) {
DwindleNode *S = path[i];
DwindleNode *child = path[i - 1];
if (S->first == child)
p[i - 1] = p[i] * S->ratio;
else
p[i - 1] = p[i] * (1.0f - S->ratio);
}
return path_len;
}
static DwindleNode *dwindle_find_leaf(DwindleNode *node, Client *c) {
if (!node)
return NULL;
if (!node->is_split)
return node->client == c ? node : NULL;
DwindleNode *r = dwindle_find_leaf(node->first, c);
return r ? r : dwindle_find_leaf(node->second, c);
}
static DwindleNode *dwindle_first_leaf(DwindleNode *node) {
if (!node)
return NULL;
while (node->is_split)
node = node->first;
return node;
}
static void dwindle_free_tree(DwindleNode *node) {
if (!node)
return;
dwindle_free_tree(node->first);
dwindle_free_tree(node->second);
free(node);
}
static void dwindle_insert(DwindleNode **root, Client *new_c, Client *focused,
float ratio, bool as_first, bool split_h,
bool lock) {
DwindleNode *new_leaf = dwindle_new_leaf(new_c);
if (!*root) {
new_leaf->custom_leaf_split_h = true;
*root = new_leaf;
return;
}
DwindleNode *target = focused ? dwindle_find_leaf(*root, focused) : NULL;
if (!target)
target = dwindle_first_leaf(*root);
// ================= 保持其他窗口比例缩减逻辑 =================
if (config.dwindle_manual_split) {
DwindleNode *path[512];
float p[512];
int path_len = get_block_path_and_ratios(target, split_h, path, p);
int n_old = 1;
if (path_len > 1) {
n_old = count_block_items(path[path_len - 1], split_h);
}
float N = (float)(n_old + 1);
for (int i = path_len - 1; i > 0; i--) {
DwindleNode *S = path[i];
DwindleNode *child = path[i - 1];
float p_S = p[i];
float p_first = p_S * S->ratio;
if (S->first == child) {
float p_first_new = p_first * (N - 1.0f) / N + 1.0f / N;
float p_S_new = p_S * (N - 1.0f) / N + 1.0f / N;
S->ratio = p_first_new / p_S_new;
} else {
float p_first_new = p_first * (N - 1.0f) / N;
float p_S_new = p_S * (N - 1.0f) / N + 1.0f / N;
S->ratio = p_first_new / p_S_new;
}
if (S->ratio < 0.001f)
S->ratio = 0.001f;
if (S->ratio > 0.999f)
S->ratio = 0.999f;
}
}
// ============================================================
DwindleNode *split = calloc(1, sizeof(DwindleNode));
split->is_split = true;
split->split_h = split_h;
split->split_locked = lock;
split->custom_leaf_split_h = target->custom_leaf_split_h;
new_leaf->custom_leaf_split_h = target->custom_leaf_split_h;
if (as_first) {
split->first = new_leaf;
split->second = target;
} else {
split->first = target;
split->second = new_leaf;
}
// 通用逻辑
split->ratio = ratio;
split->parent = target->parent;
target->parent = split;
new_leaf->parent = split;
if (!split->parent) {
*root = split;
} else {
if (split->parent->first == target)
split->parent->first = split;
else
split->parent->second = split;
}
}
static void dwindle_remove(DwindleNode **root, Client *c) {
DwindleNode *leaf = dwindle_find_leaf(*root, c);
if (!leaf)
return;
DwindleNode *parent = leaf->parent;
if (dwindle_locked_h_node == leaf || dwindle_locked_h_node == parent)
dwindle_locked_h_node = NULL;
if (dwindle_locked_v_node == leaf || dwindle_locked_v_node == parent)
dwindle_locked_v_node = NULL;
if (!parent) {
free(leaf);
*root = NULL;
return;
}
// 开始删除空间的比例回退逻辑
// 查找连续的同方向块路径
if (config.dwindle_manual_split) {
bool split_h = parent->split_h;
DwindleNode *path[512];
int path_len = 0;
path[path_len++] = parent;
DwindleNode *curr = parent->parent;
while (curr && curr->split_h == split_h) {
path[path_len++] = curr;
curr = curr->parent;
}
// 计算各祖先的旧绝对占比
float p[512];
p[path_len - 1] = 1.0f;
for (int i = path_len - 1; i > 0; i--) {
DwindleNode *S = path[i];
DwindleNode *child = path[i - 1];
if (S->first == child)
p[i - 1] = p[i] * S->ratio;
else
p[i - 1] = p[i] * (1.0f - S->ratio);
}
// 计算即将被删除的叶子节点,在该方向块中所占的绝对面积比例 (P_del)
float p_del = p[0] * (parent->first == leaf ? parent->ratio
: (1.0f - parent->ratio));
if (p_del > 0.999f)
p_del = 0.999f; // 兜底
// 重算祖先比例:将 P_del 空出来的空间,按原定比例无缝分配给其他窗口
for (int i = path_len - 1; i > 0; i--) {
DwindleNode *S = path[i];
DwindleNode *child = path[i - 1];
float p_S = p[i];
float p_first = p_S * S->ratio;
float denom = p_S - p_del;
if (denom < 0.0001f)
denom = 0.0001f;
if (S->first == child) {
S->ratio = (p_first - p_del) / denom;
} else {
S->ratio = p_first / denom;
}
if (S->ratio < 0.001f)
S->ratio = 0.001f;
if (S->ratio > 0.999f)
S->ratio = 0.999f;
}
}
// 比例重算结束
// 基础的二叉树摘除节点逻辑
DwindleNode *sibling =
(parent->first == leaf) ? parent->second : parent->first;
DwindleNode *grandparent = parent->parent;
sibling->parent = grandparent;
if (!sibling->is_split ||
(!config.dwindle_preserve_split && !config.dwindle_smart_split)) {
sibling->container_w = 0;
sibling->container_h = 0;
}
if (!grandparent) {
*root = sibling;
} else {
if (grandparent->first == parent)
grandparent->first = sibling;
else
grandparent->second = sibling;
}
free(leaf);
free(parent);
}
static void dwindle_assign(DwindleNode *node, int32_t ax, int32_t ay,
int32_t aw, int32_t ah, int32_t gap_h,
int32_t gap_v) {
if (!node)
return;
if (!node->is_split) {
if (node->client) {
if (!node->client->isfullscreen &&
!node->client->ismaximizescreen) {
struct wlr_box box = {ax, ay, MANGO_MAX(1, aw),
MANGO_MAX(1, ah)};
resize(node->client, box, 0);
}
}
return;
}
if (!node->split_locked && node->container_w == 0 && node->container_h == 0)
node->split_h = (aw >= ah);
node->container_x = ax;
node->container_y = ay;
node->container_w = aw;
node->container_h = ah;
if (node->split_h) {
int32_t w1 = MANGO_MAX(1, (int32_t)(aw * node->ratio) - gap_h / 2);
dwindle_assign(node->first, ax, ay, w1, ah, gap_h, gap_v);
dwindle_assign(node->second, ax + w1 + gap_h, ay, aw - w1 - gap_h, ah,
gap_h, gap_v);
} else {
int32_t h1 = MANGO_MAX(1, (int32_t)(ah * node->ratio) - gap_v / 2);
dwindle_assign(node->first, ax, ay, aw, h1, gap_h, gap_v);
dwindle_assign(node->second, ax, ay + h1 + gap_v, aw, ah - h1 - gap_v,
gap_h, gap_v);
}
}
static void dwindle_move_client(DwindleNode **root, Client *c, Client *target,
float ratio, int32_t dir) {
if (!c || !target || c == target)
return;
if (!dwindle_find_leaf(*root, c) || !dwindle_find_leaf(*root, target))
return;
dwindle_remove(root, c);
bool as_first = (dir == UP || dir == LEFT);
bool split_h = (dir == LEFT || dir == RIGHT);
dwindle_insert(root, c, target, ratio, as_first, split_h, true);
}
static void dwindle_swap_clients(Client *c1, Client *c2) {
if (!c1 || !c2 || !c1->mon || !c2->mon || c1 == c2)
return;
Monitor *m1 = c1->mon;
Monitor *m2 = c2->mon;
DwindleNode **c1_root = &m1->pertag->dwindle_root[m1->pertag->curtag];
DwindleNode *c1node = dwindle_find_leaf(*c1_root, c1);
DwindleNode **c2_root = &m2->pertag->dwindle_root[m2->pertag->curtag];
DwindleNode *c2node = dwindle_find_leaf(*c2_root, c2);
client_swap_layout_properties(c1, c2);
if (c1node)
c1node->client = c2;
if (c2node)
c2node->client = c1;
if (m1 != m2) {
client_swap_monitors_and_tags(c1, c2);
}
wl_list_swap(&c1->link, &c2->link);
finish_exchange_arrange_and_focus(c1, c2, m1, m2);
}
static void dwindle_resize_client(Monitor *m, Client *c) {
uint32_t tag = m->pertag->curtag;
DwindleNode *leaf = dwindle_find_leaf(m->pertag->dwindle_root[tag], c);
if (!leaf)
return;
if (!start_drag_window) {
start_drag_window = true;
dwindle_locked_h_node = NULL;
dwindle_locked_v_node = NULL;
drag_begin_cursorx = cursor->x;
drag_begin_cursory = cursor->y;
DwindleNode *node = leaf->parent;
while (node) {
if (node->split_h && !dwindle_locked_h_node) {
dwindle_locked_h_node = node;
node->drag_init_ratio = node->ratio;
}
if (!node->split_h && !dwindle_locked_v_node) {
dwindle_locked_v_node = node;
node->drag_init_ratio = node->ratio;
}
if (dwindle_locked_h_node && dwindle_locked_v_node)
break;
node = node->parent;
}
}
if (!dwindle_locked_h_node && !dwindle_locked_v_node)
return;
if (dwindle_locked_h_node) {
float cw = (float)MANGO_MAX(1, dwindle_locked_h_node->container_w);
float ox = (float)(cursor->x - drag_begin_cursorx);
if (config.dwindle_smart_resize) {
/* Move the boundary toward the cursor: invert direction when
* the drag started on the right side of the split line. */
float split_x = dwindle_locked_h_node->container_x +
cw * dwindle_locked_h_node->drag_init_ratio;
if (drag_begin_cursorx >= split_x)
ox = -ox;
}
dwindle_locked_h_node->ratio =
dwindle_locked_h_node->drag_init_ratio + ox / cw;
dwindle_locked_h_node->ratio =
CLAMP_FLOAT(dwindle_locked_h_node->ratio, 0.05f, 0.95f);
}
if (dwindle_locked_v_node) {
float ch = (float)MANGO_MAX(1, dwindle_locked_v_node->container_h);
float oy = (float)(cursor->y - drag_begin_cursory);
if (config.dwindle_smart_resize) {
/* Same logic for the vertical split line. */
float split_y = dwindle_locked_v_node->container_y +
ch * dwindle_locked_v_node->drag_init_ratio;
if (drag_begin_cursory >= split_y)
oy = -oy;
}
dwindle_locked_v_node->ratio =
dwindle_locked_v_node->drag_init_ratio + oy / ch;
dwindle_locked_v_node->ratio =
CLAMP_FLOAT(dwindle_locked_v_node->ratio, 0.05f, 0.95f);
}
int32_t n = m->visible_tiling_clients;
int32_t gap_ih = enablegaps ? m->gappih : 0;
int32_t gap_iv = enablegaps ? m->gappiv : 0;
int32_t gap_oh = enablegaps ? m->gappoh : 0;
int32_t gap_ov = enablegaps ? m->gappov : 0;
if (config.smartgaps && n == 1)
gap_ih = gap_iv = gap_oh = gap_ov = 0;
dwindle_assign(m->pertag->dwindle_root[tag], m->w.x + gap_oh,
m->w.y + gap_ov, m->w.width - 2 * gap_oh,
m->w.height - 2 * gap_ov, gap_ih, gap_iv);
}
static void dwindle_resize_client_step(Monitor *m, Client *c, int32_t dx,
int32_t dy) {
uint32_t tag = m->pertag->curtag;
DwindleNode *leaf = dwindle_find_leaf(m->pertag->dwindle_root[tag], c);
if (!leaf)
return;
DwindleNode *h_node = NULL;
DwindleNode *v_node = NULL;
DwindleNode *node = leaf->parent;
while (node) {
if (node->split_h && !h_node)
h_node = node;
if (!node->split_h && !v_node)
v_node = node;
if (h_node && v_node)
break;
node = node->parent;
}
if (!h_node && !v_node)
return;
if (h_node && dx) {
float cw = (float)MANGO_MAX(1, h_node->container_w);
float delta = (float)dx / cw;
h_node->ratio = CLAMP_FLOAT(h_node->ratio + delta, 0.05f, 0.95f);
}
if (v_node && dy) {
float ch = (float)MANGO_MAX(1, v_node->container_h);
float delta = (float)dy / ch;
v_node->ratio = CLAMP_FLOAT(v_node->ratio + delta, 0.05f, 0.95f);
}
int32_t n_clients = m->visible_tiling_clients;
int32_t gap_ih = enablegaps ? m->gappih : 0;
int32_t gap_iv = enablegaps ? m->gappiv : 0;
int32_t gap_oh = enablegaps ? m->gappoh : 0;
int32_t gap_ov = enablegaps ? m->gappov : 0;
if (config.smartgaps && n_clients == 1)
gap_ih = gap_iv = gap_oh = gap_ov = 0;
dwindle_assign(m->pertag->dwindle_root[tag], m->w.x + gap_oh,
m->w.y + gap_ov, m->w.width - 2 * gap_oh,
m->w.height - 2 * gap_ov, gap_ih, gap_iv);
}
static void dwindle_remove_client(Client *c) {
Monitor *m;
wl_list_for_each(m, &mons, link) {
for (uint32_t t = 0; t < LENGTH(tags) + 1; t++)
dwindle_remove(&m->pertag->dwindle_root[t], c);
}
}
/* Insert a new client respecting dwindle_vsplit, dwindle_hsplit, and
* dwindle_smart_split config options. */
static void dwindle_insert_with_config(DwindleNode **root, Client *new_c,
Client *focused, float ratio) {
if (!new_c || !focused)
return;
bool as_first = false;
bool split_h = false;
bool lock = false;
struct wlr_box *fg = &focused->geom;
double fcx = fg->x + fg->width * 0.5;
double fcy = fg->y + fg->height * 0.5;
if (config.dwindle_smart_split) {
double nx = (cursor->x - fcx) / (fg->width * 0.5);
double ny = (cursor->y - fcy) / (fg->height * 0.5);
if (fabs(ny) > fabs(nx)) {
split_h = false; // vertical split
as_first = (ny < 0); // top → new window on top
} else {
split_h = true; // horizontal split
as_first = (nx < 0); // left → new window on left
}
lock = true; // lock split direction
} else {
// normal mode, auto split
bool likely_h = (fg->width >= fg->height);
split_h = likely_h;
if (likely_h) {
if (config.dwindle_hsplit == 0)
as_first = (cursor->x < fcx);
else
as_first = (config.dwindle_hsplit == 2);
} else {
if (config.dwindle_vsplit == 0)
as_first = (cursor->y < fcy);
else
as_first = (config.dwindle_vsplit == 2);
}
}
DwindleNode *target = focused ? dwindle_find_leaf(*root, focused) : NULL;
if (!target && *root)
target = dwindle_first_leaf(*root);
// 当且仅当 manual_split=1 时,计算精确的 1/N 新节点比例
if (config.dwindle_manual_split && target) {
split_h = target->custom_leaf_split_h;
lock = true;
as_first = false;
// ================= 计算新节点的 1/N 比例 =================
DwindleNode *path[512];
float p[512];
int path_len = get_block_path_and_ratios(target, split_h, path, p);
int n_old = 1;
if (path_len > 1) {
n_old = count_block_items(path[path_len - 1], split_h);
}
float N = (float)(n_old + 1);
float p_target_old = p[0];
float p_split_new = p_target_old * (N - 1.0f) / N + 1.0f / N;
if (as_first) {
ratio = (1.0f / N) / p_split_new;
} else {
ratio = (p_target_old * (N - 1.0f) / N) / p_split_new;
}
if (ratio < 0.001f)
ratio = 0.001f;
if (ratio > 0.999f)
ratio = 0.999f;
// =========================================================
}
// 调用通用插入函数
dwindle_insert(root, new_c, focused, ratio, as_first, split_h, lock);
}
void dwindle(Monitor *m) {
int32_t n = m->visible_tiling_clients;
if (n == 0)
return;
uint32_t tag = m->pertag->curtag;
DwindleNode **root = &m->pertag->dwindle_root[tag];
float ratio = config.dwindle_split_ratio;
Client *vis[512];
int32_t count = 0;
Client *c;
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, m) && ISTILED(c))
vis[count++] = c;
if (count >= 512)
break;
}
// 清理树中已不存在的客户端
{
DwindleNode *leaves[512];
int32_t lc = 0;
DwindleNode *stack[1024];
int32_t sp = 0;
if (*root)
stack[sp++] = *root;
while (sp > 0) {
DwindleNode *nd = stack[--sp];
if (!nd->is_split) {
leaves[lc++] = nd;
} else {
if (nd->second)
stack[sp++] = nd->second;
if (nd->first)
stack[sp++] = nd->first;
}
}
for (int32_t i = 0; i < lc; i++) {
bool found = false;
for (int32_t j = 0; j < count; j++)
if (vis[j] == leaves[i]->client) {
found = true;
break;
}
if (!found) {
if (VISIBLEON(leaves[i]->client, m) &&
(leaves[i]->client->isfullscreen ||
leaves[i]->client->ismaximizescreen))
continue;
dwindle_remove(root, leaves[i]->client);
}
}
}
// 获得焦点客户端,若为空则用第一个可见平铺客户端兜底
Client *focused = focustop(m);
if (focused && !dwindle_find_leaf(*root, focused))
focused = m->sel;
if (!focused && count > 0)
focused = vis[0];
for (int32_t i = 0; i < count; i++) {
if (!dwindle_find_leaf(*root, vis[i]))
dwindle_insert_with_config(root, vis[i], focused, ratio);
}
int32_t gap_ih = enablegaps ? m->gappih : 0;
int32_t gap_iv = enablegaps ? m->gappiv : 0;
int32_t gap_oh = enablegaps ? m->gappoh : 0;
int32_t gap_ov = enablegaps ? m->gappov : 0;
if (config.smartgaps && n == 1)
gap_ih = gap_iv = gap_oh = gap_ov = 0;
dwindle_assign(*root, m->w.x + gap_oh, m->w.y + gap_ov,
m->w.width - 2 * gap_oh, m->w.height - 2 * gap_ov, gap_ih,
gap_iv);
}
void cleanup_monitor_dwindle(Monitor *m) {
for (uint32_t t = 0; t < LENGTH(tags) + 1; t++)
dwindle_free_tree(m->pertag->dwindle_root[t]);
}

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,9 @@ static void vertical_overview(Monitor *m);
static void vertical_grid(Monitor *m);
static void vertical_scroller(Monitor *m);
static void vertical_deck(Monitor *mon);
static void tgmix(Monitor *m);
static void dwindle(Monitor *m);
static void fair(Monitor *m);
static void vertical_fair(Monitor *m);
/* layout(s) */
Layout overviewlayout = {"󰃇", overview, "overview"};
@ -28,7 +30,9 @@ enum {
VERTICAL_GRID,
VERTICAL_DECK,
RIGHT_TILE,
TGMIX,
DWINDLE,
FAIR,
VERTICAL_FAIR,
};
Layout layouts[] = {
@ -46,5 +50,7 @@ Layout layouts[] = {
{"VT", vertical_tile, "vertical_tile", VERTICAL_TILE}, // 垂直平铺布局
{"VG", vertical_grid, "vertical_grid", VERTICAL_GRID}, // 垂直格子布局
{"VK", vertical_deck, "vertical_deck", VERTICAL_DECK}, // 垂直卡片布局
{"TG", tgmix, "tgmix", TGMIX}, // 混合布局
{"DW", dwindle, "dwindle", DWINDLE},
{"F", fair, "fair", FAIR},
{"VF", vertical_fair, "vertical_fair", VERTICAL_FAIR},
};

418
src/layout/overview.h Normal file
View file

@ -0,0 +1,418 @@
typedef struct {
float x, y, w, h;
} OvPlacedRect;
typedef struct {
float x, y;
} OvPoint;
typedef struct {
Client *c;
float orig_w;
float orig_h;
float area;
} OvLayoutItem;
static int compare_layout_items(const void *a, const void *b) {
float area_a = ((const OvLayoutItem *)a)->area;
float area_b = ((const OvLayoutItem *)b)->area;
if (area_a < area_b)
return 1;
if (area_a > area_b)
return -1;
return 0;
}
static bool try_place(OvPlacedRect *placed, int placed_cnt, float w, float h,
float gap, float avail_w, float avail_h,
OvPlacedRect *out, OvPoint *cands, OvPoint *feas) {
int cand_cnt = 0;
cands[cand_cnt++] = (OvPoint){0.0f, 0.0f};
for (int i = 0; i < placed_cnt; i++) {
OvPlacedRect p = placed[i];
cands[cand_cnt++] = (OvPoint){p.x + p.w + gap, p.y};
cands[cand_cnt++] = (OvPoint){p.x, p.y + p.h + gap};
cands[cand_cnt++] = (OvPoint){p.x + p.w + gap, p.y + p.h + gap};
}
int unique_cnt = 0;
for (int i = 0; i < cand_cnt; i++) {
bool dup = false;
for (int j = 0; j < unique_cnt; j++) {
if (fabs(cands[i].x - cands[j].x) < 0.5f &&
fabs(cands[i].y - cands[j].y) < 0.5f) {
dup = true;
break;
}
}
if (!dup)
cands[unique_cnt++] = cands[i];
}
cand_cnt = unique_cnt;
int feas_cnt = 0;
for (int i = 0; i < cand_cnt; i++) {
float cx = cands[i].x;
float cy = cands[i].y;
if (cx < 0 || cy < 0 || cx + w > avail_w || cy + h > avail_h)
continue;
bool overlap = false;
for (int j = 0; j < placed_cnt; j++) {
OvPlacedRect p = placed[j];
if (!(cx + w + gap <= p.x || cx >= p.x + p.w + gap ||
cy + h + gap <= p.y || cy >= p.y + p.h + gap)) {
overlap = true;
break;
}
}
if (!overlap) {
feas[feas_cnt++] = (OvPoint){cx, cy};
}
}
if (feas_cnt == 0)
return false;
int best = 0;
for (int i = 1; i < feas_cnt; i++) {
if (feas[i].y < feas[best].y ||
(fabs(feas[i].y - feas[best].y) < 0.5f &&
feas[i].x < feas[best].x)) {
best = i;
}
}
out->x = feas[best].x;
out->y = feas[best].y;
out->w = w;
out->h = h;
return true;
}
void overview_scale(Monitor *m) {
int32_t target_gappo = config.overviewgappo;
int32_t target_gappi = config.overviewgappi;
int orig_n = m->visible_clients;
if (orig_n == 0)
return;
OvLayoutItem *items = calloc(orig_n, sizeof(OvLayoutItem));
if (!items)
return;
int n = 0;
Client *c;
wl_list_for_each(c, &clients, link) {
if (c->mon != m)
continue;
if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) {
items[n].c = c;
float w = c->overview_backup_geom.width;
float h = c->overview_backup_geom.height;
if (w <= 0 || h <= 0) {
w = 100.0f;
h = 100.0f;
}
items[n].orig_w = w;
items[n].orig_h = h;
items[n].area = w * h;
n++;
}
}
if (n == 0) {
free(items);
return;
}
qsort(items, n, sizeof(OvLayoutItem), compare_layout_items);
float max_avail_w = fmaxf(1.0f, m->w.width - 2 * target_gappo);
float max_avail_h = fmaxf(1.0f, m->w.height - 2 * target_gappo);
int max_points = 1 + 3 * n;
OvPlacedRect *placed = calloc(n, sizeof(OvPlacedRect));
OvPoint *cands = calloc(max_points, sizeof(OvPoint));
OvPoint *feas = calloc(max_points, sizeof(OvPoint));
if (!placed || !cands || !feas) {
free(items);
free(placed);
free(cands);
free(feas);
return;
}
float low = 0.0f, high = 1.0f, best_s = 0.0f;
for (int iter = 0; iter < 50; iter++) {
float mid = (low + high) / 2.0f;
bool ok = true;
int placed_cnt = 0;
for (int k = 0; k < n; k++) {
float w = items[k].orig_w * mid;
float h = items[k].orig_h * mid;
OvPlacedRect out;
if (!try_place(placed, placed_cnt, w, h, (float)target_gappi,
max_avail_w, max_avail_h, &out, cands, feas)) {
ok = false;
break;
}
placed[placed_cnt++] = out;
}
if (ok) {
best_s = mid;
low = mid;
} else {
high = mid;
}
}
if (best_s > 0.0f) {
int placed_cnt = 0;
for (int k = 0; k < n; k++) {
float w = items[k].orig_w * best_s;
float h = items[k].orig_h * best_s;
OvPlacedRect out;
try_place(placed, placed_cnt, w, h, (float)target_gappi,
max_avail_w, max_avail_h, &out, cands, feas);
placed[placed_cnt++] = out;
}
if (n > 1) {
float grid_box_w = 0;
for (int k = 0; k < n - 1; k++) {
float r = placed[k].x + placed[k].w;
if (r > grid_box_w)
grid_box_w = r;
}
OvPlacedRect *last = &placed[n - 1];
float max_x = grid_box_w - last->w;
if (max_x > last->x) {
for (int k = 0; k < n - 1; k++) {
OvPlacedRect p = placed[k];
if (!(last->y + last->h + target_gappi <= p.y ||
last->y >= p.y + p.h + target_gappi)) {
if (p.x > last->x) {
float limit = p.x - target_gappi - last->w;
if (limit < max_x) {
max_x = limit;
}
}
}
}
if (max_x > last->x) {
last->x += (max_x - last->x) / 2.0f;
}
}
}
float box_w = 0, box_h = 0;
for (int k = 0; k < n; k++) {
float r = placed[k].x + placed[k].w;
float b = placed[k].y + placed[k].h;
if (r > box_w)
box_w = r;
if (b > box_h)
box_h = b;
}
float dx = (max_avail_w - box_w) / 2.0f;
float dy = (max_avail_h - box_h) / 2.0f;
float base_x = m->w.x + target_gappo + dx;
float base_y = m->w.y + target_gappo + dy;
for (int k = 0; k < n; k++) {
Client *cl = items[k].c;
struct wlr_box geom;
geom.x = (int)(base_x + placed[k].x + 0.5f);
geom.y = (int)(base_y + placed[k].y + 0.5f);
float w = items[k].orig_w * best_s;
float h = items[k].orig_h * best_s;
geom.width = (int)(geom.x + w + 0.5f) - geom.x;
geom.height = (int)(geom.y + h + 0.5f) - geom.y;
resize(cl, geom, 0);
}
}
free(items);
free(placed);
free(cands);
free(feas);
}
void overview_resize(Monitor *m) {
int32_t target_gappo = config.overviewgappo;
int32_t target_gappi = config.overviewgappi;
float single_width_ratio = 0.7f;
float single_height_ratio = 0.8f;
int orig_n = m->visible_clients;
if (orig_n == 0)
return;
Client **c_arr = malloc(orig_n * sizeof(Client *));
if (!c_arr)
return;
int n = 0;
Client *c;
wl_list_for_each(c, &clients, link) {
if (c->mon != m)
continue;
if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) {
c_arr[n++] = c;
}
}
if (n == 0) {
free(c_arr);
return;
}
if (n == 1) {
int32_t cw = (m->w.width - 2 * target_gappo) * single_width_ratio;
int32_t ch = (m->w.height - 2 * target_gappo) * single_height_ratio;
c_arr[0]->geom.x = m->w.x + (m->w.width - cw) / 2;
c_arr[0]->geom.y = m->w.y + (m->w.height - ch) / 2;
c_arr[0]->geom.width = cw;
c_arr[0]->geom.height = ch;
resize(c_arr[0], c_arr[0]->geom, 0);
free(c_arr);
return;
}
if (n == 2) {
int32_t cw = (m->w.width - 2 * target_gappo - target_gappi) / 2;
int32_t ch = (m->w.height - 2 * target_gappo) * 0.65f;
c_arr[0]->geom.x = m->w.x + target_gappo;
c_arr[0]->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo;
c_arr[0]->geom.width = cw;
c_arr[0]->geom.height = ch;
resize(c_arr[0], c_arr[0]->geom, 0);
c_arr[1]->geom.x = m->w.x + cw + target_gappo + target_gappi;
c_arr[1]->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo;
c_arr[1]->geom.width = cw;
c_arr[1]->geom.height = ch;
resize(c_arr[1], c_arr[1]->geom, 0);
free(c_arr);
return;
}
int32_t cols = 1;
while (cols * cols < n) {
cols++;
}
int32_t rows = (n + cols - 1) / cols;
int32_t ch =
(m->w.height - 2 * target_gappo - (rows - 1) * target_gappi) / rows;
int32_t cw =
(m->w.width - 2 * target_gappo - (cols - 1) * target_gappi) / cols;
if (ch < 1)
ch = 1;
if (cw < 1)
cw = 1;
int32_t overcols = n % cols;
int32_t dx = 0;
if (overcols) {
dx = (m->w.width - overcols * cw - (overcols - 1) * target_gappi) / 2 -
target_gappo;
}
for (int i = 0; i < n; i++) {
int32_t cx = m->w.x + (i % cols) * (cw + target_gappi);
int32_t cy = m->w.y + (i / cols) * (ch + target_gappi);
if (overcols && i >= n - overcols) {
cx += dx;
}
c_arr[i]->geom.x = cx + target_gappo;
c_arr[i]->geom.y = cy + target_gappo;
c_arr[i]->geom.width = cw;
c_arr[i]->geom.height = ch;
resize(c_arr[i], c_arr[i]->geom, 0);
}
free(c_arr);
}
void create_jump_hints(Monitor *m) {
const char jump_labels[] = "HJKLASDFGQWERTYUIOPZXCVBNM";
int label_idx = 0;
Client *c;
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) {
if (label_idx >= 26)
break;
char c_char = jump_labels[label_idx];
c->jump_char = c_char;
// 把字符变成字符串
char label_text[2] = {c_char, '\0'};
mango_jump_label_node_update(c->jump_label_node, label_text, 1.0f);
wlr_scene_node_set_enabled(&c->jump_label_node->scene_buffer->node,
true);
wlr_scene_node_raise_to_top(
&c->jump_label_node->scene_buffer->node);
wlr_scene_node_set_position(
&c->jump_label_node->scene_buffer->node,
c->geom.width / 2 - c->jump_label_node->logical_width / 2,
c->geom.height / 2 - c->jump_label_node->logical_height / 2);
label_idx++;
}
}
}
void begin_jump_mode(Monitor *m) { m->is_jump_mode = 1; }
void finish_jump_mode(Monitor *m) {
Client *c = NULL;
if (!m->is_jump_mode) {
return;
}
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, m)) {
if (c->jump_label_node->scene_buffer->node.enabled) {
c->jump_char = '\0';
wlr_scene_node_set_enabled(
&c->jump_label_node->scene_buffer->node, false);
}
}
}
m->is_jump_mode = 0;
}
void overview(Monitor *m) {
if (config.ov_no_resize) {
overview_scale(m);
} else {
overview_resize(m);
}
if (m->is_jump_mode) {
create_jump_hints(m);
}
}

1069
src/layout/scroll.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@ void vertical_tile(Monitor *m) {
int32_t master_num = 0;
int32_t stack_num = 0;
n = m->visible_tiling_clients;
n = m->visible_fake_tiling_clients;
master_num = m->pertag->nmasters[m->pertag->curtag];
master_num = n > master_num ? master_num : n;
stack_num = n - master_num;
@ -19,13 +19,17 @@ void vertical_tile(Monitor *m) {
int32_t cur_gapoh = enablegaps ? m->gappoh : 0;
int32_t cur_gapov = enablegaps ? m->gappov : 0;
cur_gapih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapih;
cur_gapiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapiv;
cur_gapoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapoh;
cur_gapov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapov;
cur_gapih =
config.smartgaps && m->visible_fake_tiling_clients == 1 ? 0 : cur_gapih;
cur_gapiv =
config.smartgaps && m->visible_fake_tiling_clients == 1 ? 0 : cur_gapiv;
cur_gapoh =
config.smartgaps && m->visible_fake_tiling_clients == 1 ? 0 : cur_gapoh;
cur_gapov =
config.smartgaps && m->visible_fake_tiling_clients == 1 ? 0 : cur_gapov;
wl_list_for_each(fc, &clients, link) {
if (VISIBLEON(fc, m) && ISTILED(fc))
if (VISIBLEON(fc, m) && ISFAKETILED(fc))
break;
}
@ -51,10 +55,10 @@ void vertical_tile(Monitor *m) {
float slave_surplus_ratio = 1.0;
wl_list_for_each(c, &clients, link) {
if (!VISIBLEON(c, m) || !ISTILED(c))
if (!VISIBLEON(c, m) || !ISFAKETILED(c))
continue;
if (i < m->pertag->nmasters[m->pertag->curtag]) {
r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i;
r = MANGO_MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i;
if (c->master_inner_per > 0.0f) {
w = master_surplus_width * c->master_inner_per /
master_surplus_ratio;
@ -69,13 +73,13 @@ void vertical_tile(Monitor *m) {
cur_gapih * ie * (r - 1));
c->master_mfact_per = mfact;
}
resize(c,
(struct wlr_box){.x = m->w.x + mx,
.y = m->w.y + cur_gapov,
.width = w,
.height = mh - cur_gapiv * ie},
0);
mx += c->geom.width + cur_gapih * ie;
client_tile_resize(c,
(struct wlr_box){.x = m->w.x + mx,
.y = m->w.y + cur_gapov,
.width = w,
.height = mh - cur_gapiv * ie},
0);
mx += w + cur_gapih * ie; // 使用理论宽度累加
} else {
r = n - i;
if (c->stack_inner_per > 0.0f) {
@ -92,13 +96,14 @@ void vertical_tile(Monitor *m) {
c->master_mfact_per = mfact;
}
resize(c,
(struct wlr_box){.x = m->w.x + tx,
.y = m->w.y + mh + cur_gapov,
.width = w,
.height = m->w.height - mh - 2 * cur_gapov},
0);
tx += c->geom.width + cur_gapih * ie;
client_tile_resize(
c,
(struct wlr_box){.x = m->w.x + tx,
.y = m->w.y + mh + cur_gapov,
.width = w,
.height = m->w.height - mh - 2 * cur_gapov},
0);
tx += w + cur_gapih * ie; // 使用理论宽度累加
}
i++;
}
@ -116,22 +121,26 @@ void vertical_deck(Monitor *m) {
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv;
cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh;
cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov;
cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1
? 0
: cur_gappiv;
cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1
? 0
: cur_gappoh;
cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1
? 0
: cur_gappov;
n = m->visible_tiling_clients;
n = m->visible_fake_tiling_clients;
if (n == 0)
return;
wl_list_for_each(fc, &clients, link) {
if (VISIBLEON(fc, m) && ISTILED(fc))
if (VISIBLEON(fc, m) && ISFAKETILED(fc))
break;
}
// Calculate master width using mfact from pertag
mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per
: m->pertag->mfacts[m->pertag->curtag];
@ -142,26 +151,29 @@ void vertical_deck(Monitor *m) {
i = mx = 0;
wl_list_for_each(c, &clients, link) {
if (!VISIBLEON(c, m) || !ISTILED(c))
if (!VISIBLEON(c, m) || !ISFAKETILED(c))
continue;
if (i < nmasters) {
resize(
c,
(struct wlr_box){.x = m->w.x + cur_gappoh + mx,
.y = m->w.y + cur_gappov,
.width = (m->w.width - 2 * cur_gappoh - mx) /
(MIN(n, nmasters) - i),
.height = mh},
0);
mx += c->geom.width;
c->master_mfact_per = mfact;
int32_t w = (m->w.width - 2 * cur_gappoh - mx) /
(MANGO_MIN(n, nmasters) - i);
client_tile_resize(c,
(struct wlr_box){.x = m->w.x + cur_gappoh + mx,
.y = m->w.y + cur_gappov,
.width = w,
.height = mh},
0);
mx += w;
} else {
resize(c,
(struct wlr_box){.x = m->w.x + cur_gappoh,
.y = m->w.y + mh + cur_gappov + cur_gappiv,
.width = m->w.width - 2 * cur_gappoh,
.height = m->w.height - mh -
2 * cur_gappov - cur_gappiv},
0);
c->master_mfact_per = mfact;
client_tile_resize(
c,
(struct wlr_box){.x = m->w.x + cur_gappoh,
.y = m->w.y + mh + cur_gappov + cur_gappiv,
.width = m->w.width - 2 * cur_gappoh,
.height = m->w.height - mh - 2 * cur_gappov -
cur_gappiv},
0);
if (c == focustop(m))
wlr_scene_node_raise_to_top(&c->scene->node);
}
@ -169,372 +181,345 @@ void vertical_deck(Monitor *m) {
}
}
void vertical_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) {
Monitor *m = c->mon;
int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
cur_gappiv =
smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappiv;
cur_gappov =
smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov;
cur_gappoh =
smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh;
if (c->isfullscreen) {
target_geom->width = m->m.width;
target_geom->height = m->m.height;
target_geom->x = m->m.x;
return;
}
if (c->ismaximizescreen) {
target_geom->width = m->w.width - 2 * cur_gappoh;
target_geom->height = m->w.height - 2 * cur_gappov;
target_geom->x = m->w.x + cur_gappoh;
return;
}
target_geom->width = m->w.width - 2 * cur_gappoh;
target_geom->x = m->w.x + (m->w.width - target_geom->width) / 2;
}
void arrange_stack_vertical(Client *scroller_stack_head,
struct wlr_box geometry, int32_t gappih) {
int32_t stack_size = 0;
Client *iter = scroller_stack_head;
while (iter) {
stack_size++;
iter = iter->next_in_stack;
}
if (stack_size == 0)
return;
float total_proportion = 0.0f;
iter = scroller_stack_head;
while (iter) {
if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f) {
iter->stack_proportion =
stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1);
}
total_proportion += iter->stack_proportion;
iter = iter->next_in_stack;
}
iter = scroller_stack_head;
while (iter) {
iter->stack_proportion = iter->stack_proportion / total_proportion;
iter = iter->next_in_stack;
}
int32_t client_width;
int32_t current_x = geometry.x;
int32_t remain_client_width = geometry.width - (stack_size - 1) * gappih;
float remain_proportion = 1.0f;
iter = scroller_stack_head;
while (iter) {
client_width =
remain_client_width * (iter->stack_proportion / remain_proportion);
struct wlr_box client_geom = {.y = geometry.y,
.x = current_x,
.height = geometry.height,
.width = client_width};
resize(iter, client_geom, 0);
remain_proportion -= iter->stack_proportion;
remain_client_width -= client_width;
current_x += client_width + gappih;
iter = iter->next_in_stack;
}
}
void vertical_check_scroller_root_inside_mon(Client *c,
struct wlr_box *geometry) {
if (!GEOMINSIDEMON(geometry, c->mon)) {
geometry->y = c->mon->w.y + (c->mon->w.height - geometry->height) / 2;
}
}
// 竖屏滚动布局
void vertical_scroller(Monitor *m) {
int32_t i, n, j;
float single_proportion = 1.0;
Client *c = NULL, *root_client = NULL;
Client **tempClients = NULL;
struct wlr_box target_geom;
int32_t focus_client_index = 0;
bool need_scroller = false;
int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
int32_t cur_gappih = enablegaps ? m->gappih : 0;
cur_gappiv =
smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappiv;
cur_gappov =
smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov;
cur_gappoh =
smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh;
int32_t max_client_height = m->w.height - 2 * scroller_structs - cur_gappiv;
n = m->visible_scroll_tiling_clients;
if (n == 0) {
return;
}
tempClients = malloc(n * sizeof(Client *));
if (!tempClients) {
return;
}
j = 0;
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, m) && ISSCROLLTILED(c) && !c->prev_in_stack) {
tempClients[j] = c;
j++;
}
}
if (n == 1 && !scroller_ignore_proportion_single &&
!tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) {
c = tempClients[0];
single_proportion = c->scroller_proportion_single > 0.0f
? c->scroller_proportion_single
: scroller_default_proportion_single;
target_geom.width = m->w.width - 2 * cur_gappoh;
target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion;
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
vertical_check_scroller_root_inside_mon(c, &target_geom);
arrange_stack_vertical(c, target_geom, cur_gappih);
free(tempClients);
return;
}
if (m->sel && !client_is_unmanaged(m->sel) && ISSCROLLTILED(m->sel)) {
root_client = m->sel;
} else if (m->prevsel && ISSCROLLTILED(m->prevsel) &&
VISIBLEON(m->prevsel, m) && !client_is_unmanaged(m->prevsel)) {
root_client = m->prevsel;
} else {
root_client = center_tiled_select(m);
}
// root_client might be in a stack, find the stack head
if (root_client) {
root_client = get_scroll_stack_head(root_client);
}
if (!root_client) {
free(tempClients);
return;
}
for (i = 0; i < n; i++) {
c = tempClients[i];
if (root_client == c) {
if (c->geom.y >= m->w.y + scroller_structs &&
c->geom.y + c->geom.height <=
m->w.y + m->w.height - scroller_structs) {
need_scroller = false;
} else {
need_scroller = true;
}
focus_client_index = i;
break;
}
}
if (n == 1 && scroller_ignore_proportion_single) {
need_scroller = true;
}
if (start_drag_window)
need_scroller = false;
target_geom.width = m->w.width - 2 * cur_gappoh;
target_geom.height = max_client_height * c->scroller_proportion;
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
vertical_scroll_adjust_fullandmax(tempClients[focus_client_index],
&target_geom);
if (tempClients[focus_client_index]->isfullscreen) {
target_geom.y = m->m.y;
vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
&target_geom);
arrange_stack_vertical(tempClients[focus_client_index], target_geom,
cur_gappih);
} else if (tempClients[focus_client_index]->ismaximizescreen) {
target_geom.y = m->w.y + cur_gappov;
vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
&target_geom);
arrange_stack_vertical(tempClients[focus_client_index], target_geom,
cur_gappih);
} else if (need_scroller) {
if (scroller_focus_center ||
((!m->prevsel ||
(ISSCROLLTILED(m->prevsel) &&
(m->prevsel->scroller_proportion * max_client_height) +
(root_client->scroller_proportion * max_client_height) >
m->w.height - 2 * scroller_structs - cur_gappiv)) &&
scroller_prefer_center)) {
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
} else {
target_geom.y = root_client->geom.y > m->w.y + (m->w.height) / 2
? m->w.y + (m->w.height -
root_client->scroller_proportion *
max_client_height -
scroller_structs)
: m->w.y + scroller_structs;
}
vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
&target_geom);
arrange_stack_vertical(tempClients[focus_client_index], target_geom,
cur_gappih);
} else {
target_geom.y = c->geom.y;
vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
&target_geom);
arrange_stack_vertical(tempClients[focus_client_index], target_geom,
cur_gappih);
}
for (i = 1; i <= focus_client_index; i++) {
c = tempClients[focus_client_index - i];
target_geom.height = max_client_height * c->scroller_proportion;
vertical_scroll_adjust_fullandmax(c, &target_geom);
target_geom.y = tempClients[focus_client_index - i + 1]->geom.y -
cur_gappiv - target_geom.height;
arrange_stack_vertical(c, target_geom, cur_gappih);
}
for (i = 1; i < n - focus_client_index; i++) {
c = tempClients[focus_client_index + i];
target_geom.height = max_client_height * c->scroller_proportion;
vertical_scroll_adjust_fullandmax(c, &target_geom);
target_geom.y = tempClients[focus_client_index + i - 1]->geom.y +
cur_gappiv +
tempClients[focus_client_index + i - 1]->geom.height;
arrange_stack_vertical(c, target_geom, cur_gappih);
}
free(tempClients);
}
void vertical_grid(Monitor *m) {
int32_t i, n;
int32_t cx, cy, cw, ch;
int32_t dy;
int32_t cw, ch;
int32_t rows, cols, overrows;
Client *c = NULL;
int32_t target_gappo =
enablegaps ? m->isoverview ? overviewgappo : gappov : 0;
int32_t target_gappi =
enablegaps ? m->isoverview ? overviewgappi : gappiv : 0;
float single_width_ratio = m->isoverview ? 0.7 : 0.9;
float single_height_ratio = m->isoverview ? 0.8 : 0.9;
int32_t target_gappo = enablegaps ? config.gappov : 0;
int32_t target_gappi = enablegaps ? config.gappiv : 0;
float single_width_ratio = 0.9;
float single_height_ratio = 0.9;
struct wlr_box target_geom;
n = m->isoverview ? m->visible_clients : m->visible_tiling_clients;
if (n == 0) {
n = m->visible_fake_tiling_clients;
if (n == 0)
return;
}
if (n == 1) {
wl_list_for_each(c, &clients, link) {
if (c->mon != m)
continue;
if (VISIBLEON(c, m) && !c->isunglobal &&
((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) {
(!client_is_x11_popup(c) || ISFAKETILED(c))) {
ch = (m->w.height - 2 * target_gappo) * single_height_ratio;
cw = (m->w.width - 2 * target_gappo) * single_width_ratio;
c->geom.x = m->w.x + (m->w.width - cw) / 2;
c->geom.y = m->w.y + (m->w.height - ch) / 2;
c->geom.width = cw;
c->geom.height = ch;
resize(c, c->geom, 0);
target_geom.x = m->w.x + (m->w.width - cw) / 2;
target_geom.y = m->w.y + (m->w.height - ch) / 2;
target_geom.width = cw;
target_geom.height = ch;
client_tile_resize(c, target_geom, 0);
return;
}
}
}
if (n == 2) {
ch = (m->w.height - 2 * target_gappo - target_gappi) / 2;
cw = (m->w.width - 2 * target_gappo) * 0.65;
float row_pers[2] = {1.0f, 1.0f};
// 先提取这两个窗口现有的行比例
i = 0;
wl_list_for_each(c, &clients, link) {
if (c->mon != m)
continue;
if (VISIBLEON(c, m) && !c->isunglobal &&
((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) {
(!client_is_x11_popup(c) || ISFAKETILED(c))) {
if (i < 2)
row_pers[i] =
(c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f;
i++;
}
}
float sum_row = row_pers[0] + row_pers[1];
float avail_h = m->w.height - 2 * target_gappo - target_gappi;
cw = (m->w.width - 2 * target_gappo) * 0.65; // 依然保持 0.65 的美观宽度
i = 0;
wl_list_for_each(c, &clients, link) {
if (c->mon != m)
continue;
if (VISIBLEON(c, m) && !c->isunglobal &&
(!client_is_x11_popup(c) || ISFAKETILED(c))) {
c->grid_col_idx = 0;
c->grid_row_idx = i;
c->grid_col_per = 1.0f;
c->grid_row_per = row_pers[i];
// 根据分配的权重动态计算当前窗口的高度
ch = avail_h * (row_pers[i] / sum_row);
target_geom.x = m->w.x + (m->w.width - cw) / 2 + target_gappo;
if (i == 0) {
c->geom.x = m->w.x + (m->w.width - cw) / 2 + target_gappo;
c->geom.y = m->w.y + target_gappo;
c->geom.width = cw;
c->geom.height = ch;
resize(c, c->geom, 0);
target_geom.y = m->w.y + target_gappo;
} else if (i == 1) {
c->geom.x = m->w.x + (m->w.width - cw) / 2 + target_gappo;
c->geom.y = m->w.y + ch + target_gappo + target_gappi;
c->geom.width = cw;
c->geom.height = ch;
resize(c, c->geom, 0);
// 第二个窗口的 Y 坐标紧跟第一个窗口下面
float ch0 = avail_h * (row_pers[0] / sum_row);
target_geom.y = m->w.y + target_gappo + ch0 + target_gappi;
}
target_geom.width = cw;
target_geom.height = ch;
client_tile_resize(c, target_geom, 0);
i++;
}
}
return;
}
for (rows = 0; rows <= n / 2; rows++) {
if (rows * rows >= n) {
if (rows * rows >= n)
break;
}
}
cols = (rows && (rows - 1) * rows >= n) ? rows - 1 : rows;
cw = (m->w.width - 2 * target_gappo - (cols - 1) * target_gappi) / cols;
ch = (m->w.height - 2 * target_gappo - (rows - 1) * target_gappi) / rows;
overrows = n % rows;
if (overrows) {
dy = (m->w.height - overrows * ch - (overrows - 1) * target_gappi) / 2 -
target_gappo;
}
float col_pers[cols];
float row_pers[rows];
for (i = 0; i < cols; i++)
col_pers[i] = 1.0f;
for (i = 0; i < rows; i++)
row_pers[i] = 1.0f;
i = 0;
wl_list_for_each(c, &clients, link) {
if (c->mon != m)
continue;
if (VISIBLEON(c, m) && !c->isunglobal &&
((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) {
cx = m->w.x + (i / rows) * (cw + target_gappi);
cy = m->w.y + (i % rows) * (ch + target_gappi);
if (overrows && i >= n - overrows) {
cy += dy;
}
c->geom.x = cx + target_gappo;
c->geom.y = cy + target_gappo;
c->geom.width = cw;
c->geom.height = ch;
resize(c, c->geom, 0);
(!client_is_x11_popup(c) || ISFAKETILED(c))) {
int32_t c_idx = i / rows;
int32_t r_idx = i % rows;
if (r_idx == 0)
col_pers[c_idx] =
(c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f;
if (c_idx == 0)
row_pers[r_idx] =
(c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f;
i++;
}
}
float sum_col = 0.0f, sum_row = 0.0f;
for (i = 0; i < cols; i++)
sum_col += col_pers[i];
for (i = 0; i < rows; i++)
sum_row += row_pers[i];
float avail_w = m->w.width - 2 * target_gappo - (cols - 1) * target_gappi;
float avail_h = m->w.height - 2 * target_gappo - (rows - 1) * target_gappi;
i = 0;
wl_list_for_each(c, &clients, link) {
if (c->mon != m)
continue;
if (VISIBLEON(c, m) && !c->isunglobal &&
(!client_is_x11_popup(c) || ISFAKETILED(c))) {
int32_t c_idx = i / rows;
int32_t r_idx = i % rows;
c->grid_col_per = col_pers[c_idx];
c->grid_row_per = row_pers[r_idx];
c->grid_col_idx = c_idx;
c->grid_row_idx = r_idx;
float fl_cy = m->w.y + target_gappo;
float fl_ch = 0.0f;
if (overrows && i >= n - overrows) {
float over_h = 0.0f;
for (int j = 0; j < overrows; j++)
over_h += avail_h * (row_pers[j] / sum_row);
over_h += (overrows - 1) * target_gappi;
float dy = (m->w.height - over_h) / 2.0f - target_gappo;
fl_cy += dy;
for (int j = 0; j < r_idx; j++)
fl_cy += avail_h * (row_pers[j] / sum_row) + target_gappi;
fl_ch = avail_h * (row_pers[r_idx] / sum_row);
} else {
for (int j = 0; j < r_idx; j++)
fl_cy += avail_h * (row_pers[j] / sum_row) + target_gappi;
fl_ch = (r_idx == rows - 1)
? (m->w.y + m->w.height - target_gappo - fl_cy)
: avail_h * (row_pers[r_idx] / sum_row);
}
float fl_cx = m->w.x + target_gappo;
for (int j = 0; j < c_idx; j++)
fl_cx += avail_w * (col_pers[j] / sum_col) + target_gappi;
float fl_cw = (c_idx == cols - 1)
? (m->w.x + m->w.width - target_gappo - fl_cx)
: avail_w * (col_pers[c_idx] / sum_col);
target_geom.x = (int32_t)fl_cx;
target_geom.y = (int32_t)fl_cy;
target_geom.width = (int32_t)fl_cw;
target_geom.height = (int32_t)fl_ch;
client_tile_resize(c, target_geom, 0);
i++;
}
}
}
void vertical_fair(Monitor *m) {
int32_t i, n = 0;
Client *c = NULL;
n = m->visible_fake_tiling_clients;
if (n == 0)
return;
int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
int32_t cur_gappih = enablegaps ? m->gappih : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
if (config.smartgaps && n == 1) {
cur_gappiv = cur_gappih = cur_gappov = cur_gappoh = 0;
}
int32_t rows;
for (rows = 0; rows <= n; rows++) {
if (rows * rows >= n)
break;
}
int32_t base_cols = n / rows;
int32_t remainder = n % rows;
int32_t first_group_rows = rows - remainder;
int32_t first_group_count = first_group_rows * base_cols;
int32_t max_cols = base_cols + (remainder > 0 ? 1 : 0);
Client *arr[n];
int32_t arr_idx = 0;
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, m) && ISFAKETILED(c)) {
arr[arr_idx++] = c;
if (arr_idx >= n)
break;
}
}
float row_pers[rows];
float col_pers[max_cols];
for (i = 0; i < rows; i++)
row_pers[i] = 0.0f;
for (i = 0; i < max_cols; i++)
col_pers[i] = 0.0f;
for (i = 0; i < n; i++) {
c = arr[i];
int32_t row_idx =
(i < first_group_count)
? (i / base_cols)
: (first_group_rows + (i - first_group_count) / max_cols);
int32_t col_idx = (i < first_group_count)
? (i % base_cols)
: ((i - first_group_count) % max_cols);
if (c->grid_row_idx == row_idx && c->grid_row_per > 0.0f)
row_pers[row_idx] = c->grid_row_per;
if (c->grid_col_idx == col_idx && c->grid_col_per > 0.0f)
col_pers[col_idx] = c->grid_col_per;
}
for (i = 0; i < n; i++) {
c = arr[i];
int32_t row_idx =
(i < first_group_count)
? (i / base_cols)
: (first_group_rows + (i - first_group_count) / max_cols);
int32_t col_idx = (i < first_group_count)
? (i % base_cols)
: ((i - first_group_count) % max_cols);
if (row_pers[row_idx] == 0.0f && c->grid_row_per > 0.0f)
row_pers[row_idx] = c->grid_row_per;
if (col_pers[col_idx] == 0.0f && c->grid_col_per > 0.0f)
col_pers[col_idx] = c->grid_col_per;
}
float sum_row = 0.0f;
for (i = 0; i < rows; i++) {
if (row_pers[i] == 0.0f)
row_pers[i] = 1.0f;
sum_row += row_pers[i];
}
for (i = 0; i < max_cols; i++) {
if (col_pers[i] == 0.0f)
col_pers[i] = 1.0f;
}
float row_y[rows], row_h[rows];
float avail_h = m->w.height - 2 * cur_gappov - (rows - 1) * cur_gappiv;
float next_y = m->w.y + cur_gappov;
for (i = 0; i < rows; i++) {
row_y[i] = next_y;
row_h[i] = (i == rows - 1)
? (m->w.y + m->w.height - cur_gappov - next_y)
: (avail_h * (row_pers[i] / sum_row));
next_y += row_h[i] + cur_gappiv;
}
float col_x_base[base_cols], col_w_base[base_cols];
float sum_col_base = 0.0f;
for (i = 0; i < base_cols; i++)
sum_col_base += col_pers[i];
float avail_w_base =
m->w.width - 2 * cur_gappoh - (base_cols - 1) * cur_gappih;
float next_x = m->w.x + cur_gappoh;
for (i = 0; i < base_cols; i++) {
col_x_base[i] = next_x;
col_w_base[i] = (i == base_cols - 1)
? (m->w.x + m->w.width - cur_gappoh - next_x)
: (avail_w_base * (col_pers[i] / sum_col_base));
next_x += col_w_base[i] + cur_gappih;
}
float col_x_max[max_cols], col_w_max[max_cols];
if (remainder > 0) {
float sum_col_max = 0.0f;
for (i = 0; i < max_cols; i++)
sum_col_max += col_pers[i];
float avail_w_max =
m->w.width - 2 * cur_gappoh - (max_cols - 1) * cur_gappih;
next_x = m->w.x + cur_gappoh;
for (i = 0; i < max_cols; i++) {
col_x_max[i] = next_x;
col_w_max[i] = (i == max_cols - 1)
? (m->w.x + m->w.width - cur_gappoh - next_x)
: (avail_w_max * (col_pers[i] / sum_col_max));
next_x += col_w_max[i] + cur_gappih;
}
}
for (i = 0; i < n; i++) {
c = arr[i];
int32_t row_idx, col_idx;
float fl_cx, fl_cy, fl_cw, fl_ch;
if (i < first_group_count) {
row_idx = i / base_cols;
col_idx = i % base_cols;
fl_cx = col_x_base[col_idx];
fl_cw = col_w_base[col_idx];
} else {
int32_t offset = i - first_group_count;
row_idx = first_group_rows + (offset / max_cols);
col_idx = offset % max_cols;
fl_cx = col_x_max[col_idx];
fl_cw = col_w_max[col_idx];
}
c->grid_row_per = row_pers[row_idx];
c->grid_col_per = col_pers[col_idx];
c->grid_row_idx = row_idx;
c->grid_col_idx = col_idx;
fl_cy = row_y[row_idx];
fl_ch = row_h[row_idx];
client_tile_resize(c,
(struct wlr_box){.x = (int32_t)fl_cx,
.y = (int32_t)fl_cy,
.width = (int32_t)fl_cw,
.height = (int32_t)fl_ch},
0);
}
}

File diff suppressed because it is too large Load diff