diff --git a/.github/scripts/generate-nix-options-docs.py b/.github/scripts/generate-nix-options-docs.py new file mode 100755 index 00000000..268edb5b --- /dev/null +++ b/.github/scripts/generate-nix-options-docs.py @@ -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 = 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() diff --git a/.github/scripts/sync-wiki.py b/.github/scripts/sync-wiki.py new file mode 100644 index 00000000..ebf543c7 --- /dev/null +++ b/.github/scripts/sync-wiki.py @@ -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() diff --git a/.github/workflows/generate-nix-options-docs.yml b/.github/workflows/generate-nix-options-docs.yml new file mode 100644 index 00000000..cee4054c --- /dev/null +++ b/.github/workflows/generate-nix-options-docs.yml @@ -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 }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 43e8badc..d0b19c1e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -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" diff --git a/.github/workflows/sync-website.yml b/.github/workflows/sync-website.yml new file mode 100644 index 00000000..b3c10af0 --- /dev/null +++ b/.github/workflows/sync-website.yml @@ -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 diff --git a/.github/workflows/sync-wiki.yml b/.github/workflows/sync-wiki.yml new file mode 100644 index 00000000..ef30fe77 --- /dev/null +++ b/.github/workflows/sync-wiki.yml @@ -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 diff --git a/README.md b/README.md index b78ccfa9..d96ab5a7 100644 --- a/README.md +++ b/README.md @@ -1,288 +1,129 @@ -# Mango Wayland Compositor -
- MangoWC Logo +
+ Mango Logo + +

Mango Wayland Compositor

+ +

A fast, feature-rich Wayland compositor built on dwl

+ +Stars +License +Packaged in +Discord +
-This project's development is based on [dwl](https://codeberg.org/dwl/dwl/). - - -1. **Lightweight & Fast Build** - - - _Mango_ is as lightweight as _dwl_, and can be built completely within a few seconds. Despite this, _Mango_ does not compromise on functionality. - -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) +--- https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f -# Our discord -[mangowc](https://discord.gg/CPjbDxesh5) +> See all layouts in action at [mangowm.github.io](https://mangowm.github.io/) -# Supported layouts +## Why Mango? -- tile -- scroller -- monocle -- grid -- deck -- center_tile -- vertical_tile -- vertical_grid -- vertical_scroller +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. -# Installation +- **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 -## Dependencies +## Vision -- glibc -- wayland -- wayland-protocols -- libinput -- libdrm -- libxkbcommon -- pixman -- git -- meson -- ninja -- libdisplay-info -- libliftoff -- hwdata -- seatd -- pcre2 -- xorg-xwayland -- libxcb +**Stability first.** After months of testing, Mango is solid enough for daily use. Breaking changes will be minimal. + +**Practicality over novelty.** Features get added when they genuinely improve daily workflows — not for the sake of completeness. + +**Focused scope.** Niche requests are evaluated by community interest. Significant upvotes move things forward. + +## Installation + +[![Packaging status](https://repology.org/badge/vertical-allrepos/mangowm.svg)](https://repology.org/project/mangowm/versions) + +### Arch Linux -## Arch Linux -The package is in the Arch User Repository and is available 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) + + + + + + + +
+ +
+ dl09r +
+
+ +
+ tonybanters +
+
+ +
+ vinthara +
+
-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. - -image - - -Thanks to the following friends for their sponsorship of this project - -[@tonybanters](https://github.com/tonybanters) + + + + + +
+ Network: BEP20 (BSC)
+ Address: 0xf9cda472f2556671d2504afc4c35340ec5615da1 +
+ sponsor QR +
diff --git a/config.conf b/assets/config.conf similarity index 90% rename from config.conf rename to assets/config.conf index 15b654c1..ab607f19 100644 --- a/config.conf +++ b/assets/config.conf @@ -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 diff --git a/assets/crypto_sponserme_qrcode.png b/assets/crypto_sponserme_qrcode.png new file mode 100644 index 00000000..970b9743 Binary files /dev/null and b/assets/crypto_sponserme_qrcode.png differ diff --git a/assets/mango-portals.conf b/assets/mango-portals.conf new file mode 100644 index 00000000..aebea31a --- /dev/null +++ b/assets/mango-portals.conf @@ -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 diff --git a/mango.desktop b/assets/mango.desktop similarity index 100% rename from mango.desktop rename to assets/mango.desktop diff --git a/docs/bindings/index.mdx b/docs/bindings/index.mdx new file mode 100644 index 00000000..4c3a5bda --- /dev/null +++ b/docs/bindings/index.mdx @@ -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. + + + + + + + + diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md new file mode 100644 index 00000000..6c8a8c58 --- /dev/null +++ b/docs/bindings/keys.md @@ -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=` 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,`) | +| `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.0–1.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. | diff --git a/docs/bindings/meta.json b/docs/bindings/meta.json new file mode 100644 index 00000000..f1b629b6 --- /dev/null +++ b/docs/bindings/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Bindings & Input", + "pages": ["keys", "mouse-gestures"] +} diff --git a/docs/bindings/mouse-gestures.md b/docs/bindings/mouse-gestures.md new file mode 100644 index 00000000..e96b6dbd --- /dev/null +++ b/docs/bindings/mouse-gestures.md @@ -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 +``` diff --git a/docs/configuration/basics.md b/docs/configuration/basics.md new file mode 100644 index 00000000..7afa343b --- /dev/null +++ b/docs/configuration/basics.md @@ -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 +``` diff --git a/docs/configuration/index.mdx b/docs/configuration/index.mdx new file mode 100644 index 00000000..2bcd3a7e --- /dev/null +++ b/docs/configuration/index.mdx @@ -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. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/configuration/input.md b/docs/configuration/input.md new file mode 100644 index 00000000..b821bf29 --- /dev/null +++ b/docs/configuration/input.md @@ -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.1–10.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.1–10.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 +``` diff --git a/docs/configuration/meta.json b/docs/configuration/meta.json new file mode 100644 index 00000000..bc209b4e --- /dev/null +++ b/docs/configuration/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Configuration", + "pages": ["basics", "monitors", "input", "xdg-portals", "miscellaneous"] +} diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md new file mode 100644 index 00000000..cd8d167d --- /dev/null +++ b/docs/configuration/miscellaneous.md @@ -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, 1–3: 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.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | +| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | diff --git a/docs/configuration/monitors.md b/docs/configuration/monitors.md new file mode 100644 index 00000000..a8001ae0 --- /dev/null +++ b/docs/configuration/monitors.md @@ -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. \ No newline at end of file diff --git a/docs/configuration/xdg-portals.md b/docs/configuration/xdg-portals.md new file mode 100644 index 00000000..27819ad8 --- /dev/null +++ b/docs/configuration/xdg-portals.md @@ -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. \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 00000000..9c9288de --- /dev/null +++ b/docs/faq.md @@ -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 +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..d308370d --- /dev/null +++ b/docs/index.md @@ -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. diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 00000000..8cb7eafe --- /dev/null +++ b/docs/installation.md @@ -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 + ``` diff --git a/docs/ipc.md b/docs/ipc.md new file mode 100644 index 00000000..489293fa --- /dev/null +++ b/docs/ipc.md @@ -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 [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 ` | Returns full JSON details for a specific monitor. | +| `get focusing-client` | Returns full JSON details for the client currently in focus. | +| `get client ` | Returns full JSON details for a client with the given ID. | +| `get tag ` | Queries status of a specific tag on a monitor. | +| `get tags ` | 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 []` | 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 ` +* `watch focusing-client` +* `watch client ` +* `watch tags ` +* `watch all-monitors` +* `watch all-tags` +* `watch all-clients` +* `watch keymode` +* `watch keyboardlayout` +* `watch last_open_surface []` + +*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 ,[args...] [client,]` + +*Example:* +```bash +# operate specific client by id +mmsg dispatch exchange_client,left client,375 +# operate current client +mmsg dispatch exchange_client,left +```` diff --git a/docs/meta.json b/docs/meta.json new file mode 100644 index 00000000..40a214d5 --- /dev/null +++ b/docs/meta.json @@ -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" + ] +} diff --git a/docs/nix-options.md b/docs/nix-options.md new file mode 100644 index 00000000..2537d9d8 --- /dev/null +++ b/docs/nix-options.md @@ -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:* + - [\](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + + + +### package + + + +The mango package to use + + + +*Type:* +package + + + +*Default:* + +```nix + +``` + +*Declared by:* + - [\](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:* + - [\](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:* + - [\](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### package + + + +The mango package to use + + + +*Type:* +package + + + +*Default:* + +```nix + +``` + +*Declared by:* + - [\](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:* + - [\](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:* + - [\](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 don’t fit the structured +settings format, or for options that aren’t 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:* + - [\](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 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\. + + + +*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:* + - [\](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:* + - [\](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:* + - [\](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:* + - [\](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:* + - [\](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:* + - [\](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + diff --git a/docs/quick-start.md b/docs/quick-start.md new file mode 100644 index 00000000..bc192474 --- /dev/null +++ b/docs/quick-start.md @@ -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. diff --git a/docs/screenshot.md b/docs/screenshot.md new file mode 100644 index 00000000..8c512313 --- /dev/null +++ b/docs/screenshot.md @@ -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 +``` diff --git a/docs/visuals/animations.md b/docs/visuals/animations.md new file mode 100644 index 00000000..76477e05 --- /dev/null +++ b/docs/visuals/animations.md @@ -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.0–1.0) +- `fadeout_begin_opacity` — Starting opacity for fade-out animations (0.0–1.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) | \ No newline at end of file diff --git a/docs/visuals/effects.md b/docs/visuals/effects.md new file mode 100644 index 00000000..23c1f206 --- /dev/null +++ b/docs/visuals/effects.md @@ -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 +``` diff --git a/docs/visuals/index.mdx b/docs/visuals/index.mdx new file mode 100644 index 00000000..f71ae2f8 --- /dev/null +++ b/docs/visuals/index.mdx @@ -0,0 +1,19 @@ +--- +title: Visuals +description: Customize borders, colors, effects, and animations. +icon: Palette +--- + +Customize the look of your desktop. + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/visuals/meta.json b/docs/visuals/meta.json new file mode 100644 index 00000000..58723c4e --- /dev/null +++ b/docs/visuals/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Visuals", + "pages": ["theming", "status-bar", "effects", "animations"] +} diff --git a/docs/visuals/status-bar.md b/docs/visuals/status-bar.md new file mode 100644 index 00000000..f2924e83 --- /dev/null +++ b/docs/visuals/status-bar.md @@ -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). \ No newline at end of file diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md new file mode 100644 index 00000000..2f9993be --- /dev/null +++ b/docs/visuals/theming.md @@ -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 +``` diff --git a/docs/window-management/index.mdx b/docs/window-management/index.mdx new file mode 100644 index 00000000..b96c5891 --- /dev/null +++ b/docs/window-management/index.mdx @@ -0,0 +1,19 @@ +--- +title: Window Management +description: Layouts, rules, and window behavior. +icon: LayoutGrid +--- + +Window management with layouts, rules, and scratchpad support. + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/window-management/layouts.md b/docs/window-management/layouts.md new file mode 100644 index 00000000..02f218a3 --- /dev/null +++ b/docs/window-management/layouts.md @@ -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 +``` diff --git a/docs/window-management/meta.json b/docs/window-management/meta.json new file mode 100644 index 00000000..e0937d14 --- /dev/null +++ b/docs/window-management/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Window Management", + "pages": ["layouts", "rules", "overview", "scratchpad"] +} diff --git a/docs/window-management/overview.md b/docs/window-management/overview.md new file mode 100644 index 00000000..1ba416e9 --- /dev/null +++ b/docs/window-management/overview.md @@ -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. \ No newline at end of file diff --git a/docs/window-management/rules.md b/docs/window-management/rules.md new file mode 100644 index 00000000..b5ae81b1 --- /dev/null +++ b/docs/window-management/rules.md @@ -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.1–0.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 +``` diff --git a/docs/window-management/scratchpad.md b/docs/window-management/scratchpad.md new file mode 100644 index 00000000..398182f9 --- /dev/null +++ b/docs/window-management/scratchpad.md @@ -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 +``` diff --git a/flake.lock b/flake.lock index 2917a6fd..03d3a7bc 100644 --- a/flake.lock +++ b/flake.lock @@ -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": { diff --git a/flake.nix b/flake.nix index b7158bbd..33b97c9b 100644 --- a/flake.nix +++ b/flake.nix @@ -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; diff --git a/format.sh b/format.sh index 1291ff8f..6b204b00 100644 --- a/format.sh +++ b/format.sh @@ -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 diff --git a/mangowc.scm b/mangowm.scm similarity index 59% rename from mangowc.scm rename to mangowm.scm index 9c55d43e..e2b0287a 100644 --- a/mangowc.scm +++ b/mangowm.scm @@ -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 diff --git a/meson.build b/meson.build index f6f6d6f9..938371e5 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ -project('mango', ['c', 'cpp'], - version : '0.12.1', +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.5',version: '>=0.5.0') +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,12 @@ 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 +111,9 @@ executable('mango', libinput_dep, libwayland_client_dep, pcre2_dep, + pixman_dep, + cjson_dep, + pangocairo_dep, ], install : true, c_args : c_args, @@ -130,7 +135,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 +150,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')) diff --git a/mmsg/arg.h b/mmsg/arg.h deleted file mode 100644 index c3b0d7b9..00000000 --- a/mmsg/arg.h +++ /dev/null @@ -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 diff --git a/mmsg/dynarr.h b/mmsg/dynarr.h deleted file mode 100644 index 45cd356a..00000000 --- a/mmsg/dynarr.h +++ /dev/null @@ -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 diff --git a/mmsg/mmsg.1 b/mmsg/mmsg.1 new file mode 100644 index 00000000..8a935f50 --- /dev/null +++ b/mmsg/mmsg.1 @@ -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 [\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 +Return details of the named monitor. +.TP +\fBget focusing\-client\fR +Return details of the currently focused client. +.TP +\fBget client\fR +Return details of the client with the given numeric ID. +.TP +\fBget tag\fR +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 +List tags for a specific monitor. +.TP +\fBdispatch\fR [,arg...] [client,] +Invoke an internal compositor function. +The function name and its arguments are separated by commas. +Optionally add \fBclient,\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 +Stream updates for the named monitor. +.TP +\fBwatch focusing\-client\fR +Stream updates for the focused client. +.TP +\fBwatch client\fR +Stream updates for the client with the given ID. +.TP +\fBwatch tags\fR +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. \ No newline at end of file diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index fb1d04fd..adb0b539 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -1,825 +1,156 @@ -#include "arg.h" -#include "dwl-ipc-unstable-v2-protocol.h" -#include "dynarr.h" -#include -#include +#define _GNU_SOURCE +#include #include #include #include +#include +#include #include -#include -#include - -#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, - "mmsg - MangoWC IPC\n" - "\n" - "SYNOPSIS:\n" - "\tmmsg [-OTLq]\n" - "\tmmsg [-o ] -s [-t ] [-l ] [-c ] [-d " - ",,,,,]\n" - "\tmmsg [-o ] (-g | -w) [-OotlcvmfxekbA]\n" - "\n" - "OPERATION MODES:\n" - "\t-g Get values (tags, layout, focused client)\n" - "\t-s Set values (switch tags, layouts)\n" - "\t-w Watch mode (stream events)\n" - "\n" - "GENERAL OPTIONS:\n" - "\t-O Get all output (monitor) information\n" - "\t-T Get number of tags\n" - "\t-L Get all available layouts\n" - "\t-q Quit MangoWC\n" - "\t-o Select output (monitor)\n" - "\n" - "GET OPTIONS (used with -g or -w):\n" - "\t-O Get output name\n" - "\t-o Get output (monitor) focus information\n" - "\t-t Get selected tags\n" - "\t-l Get current layout\n" - "\t-c Get title and appid of focused clients\n" - "\t-v Get visibility of statusbar\n" - "\t-m Get fullscreen status\n" - "\t-f Get floating status\n" - "\t-x Get focused client geometry\n" - "\t-e Get name of last focused layer\n" - "\t-k Get current keyboard layout\n" - "\t-b Get current keybind mode\n" - "\t-A Get scale factor of monitor\n" - "\n" - "SET OPTIONS (used with -s):\n" - "\t-o Select output (monitor)\n" - "\t-t Set selected tags (can be used with [+-^.] " - "modifiers)\n" - "\t-l Set current layout\n" - "\t-c Get title and appid of focused client\n" - "\t-d , Dispatch internal command (max 5 args)\n"); - exit(2); + printf("Usage: mmsg [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 Show monitor details\n"); + printf(" get focusing-client Show focused client " + "details\n"); + printf(" get client Show client details by " + "ID\n"); + printf(" get tag Show tag details " + "(1‑based 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 List tags for a monitor\n"); + printf(" dispatch [,arg...] [client,] Call a compositor " + "function\n"); + printf(" and arguments are separated by commas.\n"); + printf(" Add 'client,' 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 Stream monitor changes\n"); + printf(" watch focusing-client Stream focused client " + "changes\n"); + printf( + " watch client Stream client changes\n"); + printf(" watch tags 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 [args...]\n"); + fprintf(stderr, " get ... one-shot request\n"); + fprintf(stderr, " watch ... persistent stream\n"); + return EXIT_FAILURE; + } - struct wl_registry *registry = wl_display_get_registry(display); - wl_registry_add_listener(registry, ®istry_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; +} \ No newline at end of file diff --git a/nix/default.nix b/nix/default.nix index 6085565e..8c35eb83 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -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; diff --git a/nix/generate-options.nix b/nix/generate-options.nix new file mode 100644 index 00000000..f8df3745 --- /dev/null +++ b/nix/generate-options.nix @@ -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 = ""; + }; + + 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 diff --git a/nix/hm-modules.nix b/nix/hm-modules.nix index 85d57908..eba05e2f 100644 --- a/nix/hm-modules.nix +++ b/nix/hm-modules.nix @@ -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 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"; + }; }; - }; - }; + } + ); } diff --git a/nix/lib.nix b/nix/lib.nix new file mode 100644 index 00000000..9dfd2ff6 --- /dev/null +++ b/nix/lib.nix @@ -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; +} diff --git a/nix/nixos-modules.nix b/nix/nixos-modules.nix index 33811022..9144bbf1 100644 --- a/nix/nixos-modules.nix +++ b/nix/nixos-modules.nix @@ -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; }; diff --git a/protocols/meson.build b/protocols/meson.build index cafab64a..922a76ed 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -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', diff --git a/src/action/client.h b/src/action/client.h new file mode 100644 index 00000000..876284cd --- /dev/null +++ b/src/action/client.h @@ -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); +} diff --git a/src/animation/client.h b/src/animation/client.h index b60c07f5..412daaaa 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -1,3 +1,4 @@ +#include "wlr/util/log.h" void client_actual_size(Client *c, int32_t *width, int32_t *height) { *width = c->animation.current.width - 2 * (int32_t)c->bw; @@ -10,22 +11,22 @@ void set_rect_size(struct wlr_scene_rect *rect, int32_t width, int32_t height) { struct fx_corner_radii set_client_corner_location(Client *c) { struct fx_corner_radii current_corner_location = - corner_radii_all(border_radius); - struct wlr_box target_geom = animations ? c->animation.current : c->geom; - if (target_geom.x + border_radius <= c->mon->m.x) { + corner_radii_all(config.border_radius); + struct wlr_box target_geom = config.animations ? c->animation.current : c->geom; + if (target_geom.x + config.border_radius <= c->mon->m.x) { current_corner_location.top_left = 0; // 清除左标志位 current_corner_location.bottom_left = 0; // 清除左标志位 } - if (target_geom.x + target_geom.width - border_radius >= + if (target_geom.x + target_geom.width - config.border_radius >= c->mon->m.x + c->mon->m.width) { current_corner_location.top_right = 0; // 清除右标志位 current_corner_location.bottom_right = 0; // 清除右标志位 } - if (target_geom.y + border_radius <= c->mon->m.y) { + if (target_geom.y + config.border_radius <= c->mon->m.y) { current_corner_location.top_left = 0; // 清除上标志位 current_corner_location.top_right = 0; // 清除上标志位 } - if (target_geom.y + target_geom.height - border_radius >= + if (target_geom.y + target_geom.height - config.border_radius >= c->mon->m.y + c->mon->m.height) { current_corner_location.bottom_left = 0; // 清除下标志位 current_corner_location.bottom_right = 0; // 清除下标志位 @@ -59,15 +60,16 @@ int32_t is_special_animation_rule(Client *c) { } else if (c->mon->visible_tiling_clients == 1 && !c->isfloating) { return DOWN; } else if (c->mon->visible_tiling_clients == 2 && !c->isfloating && - !new_is_master && is_horizontal_stack_layout(c->mon)) { + !config.new_is_master && is_horizontal_stack_layout(c->mon)) { return RIGHT; - } else if (!c->isfloating && new_is_master && + } else if (!c->isfloating && config.new_is_master && is_horizontal_stack_layout(c->mon)) { return LEFT; } else if (c->mon->visible_tiling_clients == 2 && !c->isfloating && - !new_is_master && is_horizontal_right_stack_layout(c->mon)) { + !config.new_is_master && + is_horizontal_right_stack_layout(c->mon)) { return LEFT; - } else if (!c->isfloating && new_is_master && + } else if (!c->isfloating && config.new_is_master && is_horizontal_right_stack_layout(c->mon)) { return RIGHT; } else { @@ -75,6 +77,14 @@ int32_t is_special_animation_rule(Client *c) { } } +void set_overview_enter_animation(Client *c) { + struct wlr_box geo = c->geom; + c->animainit_geom.width = geo.width * 1.2; + c->animainit_geom.height = geo.height * 1.2; + c->animainit_geom.x = geo.x + (geo.width - c->animainit_geom.width) / 2; + c->animainit_geom.y = geo.y + (geo.height - c->animainit_geom.height) / 2; +} + void set_client_open_animation(Client *c, struct wlr_box geo) { int32_t slide_direction; int32_t horizontal, horizontal_value; @@ -82,7 +92,8 @@ void set_client_open_animation(Client *c, struct wlr_box geo) { int32_t special_direction; int32_t center_x, center_y; - if ((!c->animation_type_open && strcmp(animation_type_open, "fade") == 0) || + if ((!c->animation_type_open && + strcmp(config.animation_type_open, "fade") == 0) || (c->animation_type_open && strcmp(c->animation_type_open, "fade") == 0)) { c->animainit_geom.width = geo.width; @@ -91,11 +102,11 @@ void set_client_open_animation(Client *c, struct wlr_box geo) { c->animainit_geom.y = geo.y; return; } else if ((!c->animation_type_open && - strcmp(animation_type_open, "zoom") == 0) || + strcmp(config.animation_type_open, "zoom") == 0) || (c->animation_type_open && strcmp(c->animation_type_open, "zoom") == 0)) { - c->animainit_geom.width = geo.width * zoom_initial_ratio; - c->animainit_geom.height = geo.height * zoom_initial_ratio; + c->animainit_geom.width = geo.width * config.zoom_initial_ratio; + c->animainit_geom.height = geo.height * config.zoom_initial_ratio; c->animainit_geom.x = geo.x + (geo.width - c->animainit_geom.width) / 2; c->animainit_geom.y = geo.y + (geo.height - c->animainit_geom.height) / 2; @@ -229,6 +240,39 @@ void scene_buffer_apply_effect(struct wlr_scene_buffer *buffer, int32_t sx, return; wlr_scene_buffer_set_corner_radii(buffer, buffer_data->corner_location); + +} + +void scene_buffer_apply_overview_effect(struct wlr_scene_buffer *buffer, + int32_t sx, int32_t sy, void *data) { + BufferData *buffer_data = (BufferData *)data; + + int32_t surface_width = 0; + int32_t surface_height = 0; + bool is_subsurface = false; + + struct wlr_scene_tree *parent_tree = buffer->node.parent; + if (parent_tree->node.data != NULL) { + SnapshotMetadata *meta = (SnapshotMetadata *)parent_tree->node.data; + surface_width = meta->orig_width; + surface_height = meta->orig_height; + is_subsurface = meta->is_subsurface; + } + + surface_height = surface_height * buffer_data->height_scale; + surface_width = surface_width * buffer_data->width_scale; + + if (is_subsurface && surface_width > 0 && surface_height > 0) { + wlr_scene_buffer_set_dest_size(buffer, surface_width, surface_height); + } else if (buffer_data->height > 0 && buffer_data->width > 0) { + wlr_scene_buffer_set_dest_size(buffer, buffer_data->width, + buffer_data->height); + } + + if (is_subsurface) + return; + + wlr_scene_buffer_set_corner_radii(buffer, buffer_data->corner_location); } void buffer_set_effect(Client *c, BufferData data) { @@ -249,11 +293,17 @@ void buffer_set_effect(Client *c, BufferData data) { data.corner_location = corner_radii_none(); } - if (blur && !c->noblur) { + if (config.blur && !c->noblur) { wlr_scene_blur_set_corner_radii(c->blur, data.corner_location); } - wlr_scene_node_for_each_buffer(&c->scene_surface->node, - scene_buffer_apply_effect, &data); + + if (c->overview_scene_surface) { + wlr_scene_node_for_each_buffer( + &c->scene_surface->node, scene_buffer_apply_overview_effect, &data); + } else { + wlr_scene_node_for_each_buffer(&c->scene_surface->node, + scene_buffer_apply_effect, &data); + } } void client_draw_shadow(Client *c) { @@ -261,7 +311,8 @@ void client_draw_shadow(Client *c) { if (c->iskilling || !client_surface(c)->mapped || c->isnoshadow) return; - if (!shadows || (!c->isfloating && shadow_only_floating)) { + if (!config.shadows || c->isfullscreen || + (!c->isfloating && config.shadow_only_floating)) { if (c->shadow->node.enabled) wlr_scene_node_set_enabled(&c->shadow->node, false); return; @@ -272,7 +323,7 @@ void client_draw_shadow(Client *c) { bool hit_no_border = check_hit_no_border(c); struct fx_corner_radii current_corner_location = - c->isfullscreen || (no_radius_when_single && c->mon && + c->isfullscreen || (config.no_radius_when_single && c->mon && c->mon->visible_tiling_clients == 1) ? corner_radii_none() : set_client_corner_location(c); @@ -282,29 +333,26 @@ void client_draw_shadow(Client *c) { int32_t width, height; client_actual_size(c, &width, &height); - int32_t delta = shadows_size + (int32_t)c->bw - bwoffset; + int32_t delta = config.shadows_size + (int32_t)c->bw - bwoffset; - /* we calculate where to clip the shadow */ struct wlr_box client_box = { .x = bwoffset, .y = bwoffset, - .width = width + (int32_t)c->bw - bwoffset, - .height = height + (int32_t)c->bw - bwoffset, + .width = width + 2 * (int32_t)c->bw - 2 * bwoffset, + .height = height + 2 * (int32_t)c->bw - 2 * bwoffset, }; struct wlr_box shadow_box = { - .x = shadows_position_x + bwoffset, - .y = shadows_position_y + bwoffset, + .x = config.shadows_position_x + bwoffset, + .y = config.shadows_position_y + bwoffset, .width = width + 2 * delta, .height = height + 2 * delta, }; struct wlr_box intersection_box; wlr_box_intersection(&intersection_box, &client_box, &shadow_box); - /* clipped region takes shadow relative coords, so we translate everything - * by its position */ - intersection_box.x -= shadows_position_x + bwoffset; - intersection_box.y -= shadows_position_y + bwoffset; + intersection_box.x -= config.shadows_position_x + bwoffset; + intersection_box.y -= config.shadows_position_y + bwoffset; struct clipped_region clipped_region = { .area = intersection_box, @@ -337,10 +385,10 @@ void client_draw_shadow(Client *c) { top_offset = GEZERO(c->mon->m.y - absolute_shadow_box.y); } - left_offset = MIN(left_offset, shadow_box.width); - right_offset = MIN(right_offset, shadow_box.width); - top_offset = MIN(top_offset, shadow_box.height); - bottom_offset = MIN(bottom_offset, shadow_box.height); + left_offset = MANGO_MIN(left_offset, shadow_box.width); + right_offset = MANGO_MIN(right_offset, shadow_box.width); + top_offset = MANGO_MIN(top_offset, shadow_box.height); + bottom_offset = MANGO_MIN(bottom_offset, shadow_box.height); wlr_scene_node_set_position(&c->shadow->node, shadow_box.x + left_offset, shadow_box.y + top_offset); @@ -379,27 +427,146 @@ void client_draw_blur(Client *c, struct wlr_box clip_box, struct ivec2 offset) { } } +void global_draw_tab_bar(Client *c, int32_t x, int32_t y, int32_t width, + int32_t height) { + if (!c->tab_bar_node) + return; + + if (height <= 0) { + wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, false); + } + + wlr_scene_node_set_position(&c->tab_bar_node->scene_buffer->node, x, y); + wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, true); + mango_tab_bar_node_set_size(c->tab_bar_node, width, height); +} + +void apply_split_border(Client *c, bool hit_no_border) { + + if (c->iskilling || !c->mon || !client_surface(c)->mapped) + return; + + const Layout *layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]; + + if (hit_no_border || !ISTILED(c) || layout->id != DWINDLE || + !config.dwindle_manual_split || c->isfullscreen) { + if (c->splitindicator[0]->node.enabled) { + wlr_scene_node_set_enabled(&c->splitindicator[0]->node, false); + } + if (c->splitindicator[1]->node.enabled) { + wlr_scene_node_set_enabled(&c->splitindicator[1]->node, false); + } + return; + } else { + + DwindleNode **root = + &c->mon->pertag->dwindle_root[c->mon->pertag->curtag]; + DwindleNode *dnode = dwindle_find_leaf(*root, c); + + if (!dnode) { + wlr_scene_node_set_enabled(&c->splitindicator[0]->node, false); + wlr_scene_node_set_enabled(&c->splitindicator[1]->node, false); + return; + } else { + if (dnode->custom_leaf_split_h) { + wlr_scene_node_set_enabled(&c->splitindicator[0]->node, false); + wlr_scene_node_set_enabled(&c->splitindicator[1]->node, true); + } else { + wlr_scene_node_set_enabled(&c->splitindicator[0]->node, true); + wlr_scene_node_set_enabled(&c->splitindicator[1]->node, false); + } + } + } + + struct wlr_box fullgeom = c->animation.current; + // 一但在GEZERO如果使用无符号,那么其他数据也会转换为无符号导致没有负数出错 + int32_t bw = (int32_t)c->bw; + + int32_t right_offset, bottom_offset, left_offset, top_offset; + + if (c == grabc) { + right_offset = 0; + bottom_offset = 0; + left_offset = 0; + top_offset = 0; + } else { + right_offset = + GEZERO(c->animation.current.x + c->animation.current.width - + c->mon->m.x - c->mon->m.width); + bottom_offset = + GEZERO(c->animation.current.y + c->animation.current.height - + c->mon->m.y - c->mon->m.height); + + left_offset = GEZERO(c->mon->m.x - c->animation.current.x); + top_offset = GEZERO(c->mon->m.y - c->animation.current.y); + } + + int32_t border_down_width = + GEZERO(fullgeom.width - 2 * config.border_radius - + GEZERO((left_offset + right_offset) - config.border_radius)); + int32_t border_down_height = + GEZERO(bw - bottom_offset - GEZERO(top_offset + bw - fullgeom.height)); + + int32_t border_right_width = + GEZERO(bw - right_offset - GEZERO(left_offset + bw - fullgeom.width)); + int32_t border_right_height = + GEZERO(fullgeom.height - 2 * config.border_radius - + GEZERO((top_offset + bottom_offset) - config.border_radius)); + + int32_t border_down_x = GEZERO(config.border_radius + + GEZERO(left_offset - config.border_radius)); + int32_t border_down_y = GEZERO(fullgeom.height - bw) + + GEZERO(top_offset + bw - fullgeom.height); + + int32_t border_right_x = + GEZERO(fullgeom.width - bw) + GEZERO(left_offset + bw - fullgeom.width); + int32_t border_right_y = GEZERO(config.border_radius + + GEZERO(top_offset - config.border_radius)); + + set_rect_size(c->splitindicator[0], border_down_width, border_down_height); + set_rect_size(c->splitindicator[1], border_right_width, + border_right_height); + wlr_scene_node_set_position(&c->splitindicator[0]->node, border_down_x, + border_down_y); + wlr_scene_node_set_position(&c->splitindicator[1]->node, border_right_x, + border_right_y); +} + void apply_border(Client *c) { if (!c || c->iskilling || !client_surface(c)->mapped) return; + if (c->isfullscreen) { + if (c->border->node.enabled) { + wlr_scene_node_set_position(&c->scene_surface->node, 0, 0); + wlr_scene_node_set_enabled(&c->border->node, false); + } + return; + } else { + if (!c->border->node.enabled) { + wlr_scene_node_set_enabled(&c->border->node, true); + } + } + bool hit_no_border = check_hit_no_border(c); + apply_split_border(c, hit_no_border); + struct fx_corner_radii current_corner_location = c->isfullscreen || (no_radius_when_single && c->mon && c->mon->visible_tiling_clients == 1) ? corner_radii_none() : set_client_corner_location(c); - if (hit_no_border && smartgaps) { + if (hit_no_border && config.smartgaps) { c->bw = 0; c->fake_no_border = true; - } else if (hit_no_border && !smartgaps) { + } else if (hit_no_border && !config.smartgaps) { wlr_scene_rect_set_size(c->border, 0, 0); wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); c->fake_no_border = true; return; } else if (!c->isfullscreen && VISIBLEON(c, c->mon)) { - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; c->fake_no_border = false; } @@ -450,12 +617,12 @@ void apply_border(Client *c) { if (right_offset > 0) { inner_surface_width = - MIN(clip_box.width, inner_surface_width + right_offset); + MANGO_MIN(clip_box.width, inner_surface_width + right_offset); } if (bottom_offset > 0) { inner_surface_height = - MIN(clip_box.height, inner_surface_height + bottom_offset); + MANGO_MIN(clip_box.height, inner_surface_height + bottom_offset); } struct clipped_region clipped_region = { @@ -527,14 +694,208 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { (ISSCROLLTILED(c) || c->animation.tagouting || c->animation.tagining)) { c->is_clip_to_hide = true; wlr_scene_node_set_enabled(&c->scene->node, false); - } else if (c->is_clip_to_hide && VISIBLEON(c, c->mon)) { + } else if (c->is_clip_to_hide && VISIBLEON(c, 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); } return offset; } +void client_set_drop_area(Client *c) { + bool first_draw = false; + int32_t drop_direction = UNDIR; + + if (!c || !c->mon) + return; + + if (!c->enable_drop_area_draw && !c->droparea->node.enabled) { + return; + } + + if (!c->enable_drop_area_draw && c->droparea->node.enabled) { + wlr_scene_node_lower_to_bottom(&c->droparea->node); + wlr_scene_node_set_enabled(&c->droparea->node, false); + return; + } else if (c->enable_drop_area_draw && !c->droparea->node.enabled) { + wlr_scene_node_raise_to_top(&c->droparea->node); + wlr_scene_node_set_enabled(&c->droparea->node, true); + first_draw = true; + } + + int32_t bw = (int32_t)c->bw; + int32_t client_width = c->geom.width - 2 * bw; + int32_t client_height = c->geom.height - 2 * bw; + + // 光标在窗口客户区内的相对坐标 + double rel_x = cursor->x - c->geom.x - bw; + double rel_y = cursor->y - c->geom.y - bw; + + struct wlr_box drop_box; + + const Layout *cur_layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]; + bool dwindle_familiar = + cur_layout->id == DWINDLE && config.dwindle_drop_simple_split; + + if (dwindle_familiar) { + bool split_h = c->geom.width >= c->geom.height; + float ratio = config.dwindle_split_ratio; + if (split_h) { + if (rel_x < client_width * 0.5) { + drop_direction = LEFT; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = (int32_t)(client_width * ratio); + drop_box.height = client_height; + } else { + drop_direction = RIGHT; + drop_box.x = bw + (int32_t)(client_width * ratio); + drop_box.y = bw; + drop_box.width = client_width - (int32_t)(client_width * ratio); + drop_box.height = client_height; + } + } else { + if (rel_y < client_height * 0.5) { + drop_direction = UP; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = (int32_t)(client_height * ratio); + } else { + drop_direction = DOWN; + drop_box.x = bw; + drop_box.y = bw + (int32_t)(client_height * ratio); + drop_box.width = client_width; + drop_box.height = + client_height - (int32_t)(client_height * ratio); + } + } + } else if (cur_layout->id == TILE || cur_layout->id == DECK || + cur_layout->id == CENTER_TILE || cur_layout->id == RIGHT_TILE) { + + if (c->ismaster) { + if (c->mon->visible_tiling_clients == 1) { + if (rel_x < client_width * 0.5) { + drop_direction = LEFT; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } else { + drop_direction = RIGHT; + drop_box.x = bw + client_width / 2; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } + } else { + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height; + drop_direction = UNDIR; + } + } else { + if (rel_y < client_height * 0.5) { + drop_direction = UP; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } else { + drop_direction = DOWN; + drop_box.x = bw; + drop_box.y = bw + client_height / 2; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } + } + } else if (cur_layout->id == VERTICAL_TILE || + cur_layout->id == VERTICAL_DECK) { + if (c->ismaster) { + if (c->mon->visible_tiling_clients == 1) { + if (rel_y < client_height * 0.5) { + drop_direction = UP; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } else { + drop_direction = DOWN; + drop_box.x = bw; + drop_box.y = bw + client_height / 2; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } + } else { + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height; + drop_direction = UNDIR; + } + + } else { + if (rel_x < client_width * 0.5) { + drop_direction = LEFT; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } else { + drop_direction = RIGHT; + drop_box.x = bw + client_width / 2; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } + } + + } else { + double dist_left = rel_x; + double dist_right = client_width - rel_x; + double dist_top = rel_y; + double dist_bottom = client_height - rel_y; + + if (dist_left <= dist_right && dist_left <= dist_top && + dist_left <= dist_bottom) { + drop_direction = LEFT; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } else if (dist_right <= dist_top && dist_right <= dist_bottom) { + drop_direction = RIGHT; + drop_box.x = bw + client_width / 2; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } else if (dist_top <= dist_bottom) { + drop_direction = UP; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } else { + drop_direction = DOWN; + drop_box.x = bw; + drop_box.y = bw + client_height / 2; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } + } + + if (!first_draw && c->drop_direction == drop_direction) { + return; + } + c->drop_direction = drop_direction; + + wlr_scene_node_set_position(&c->droparea->node, drop_box.x, drop_box.y); + wlr_scene_rect_set_size(c->droparea, drop_box.width, drop_box.height); +} + void client_apply_clip(Client *c, float factor) { if (c->iskilling || !client_surface(c)->mapped) @@ -548,7 +909,7 @@ void client_apply_clip(Client *c, float factor) { struct fx_corner_radii current_corner_location = set_client_corner_location(c); - if (!animations) { + if (!config.animations && !c->overview_scene_surface) { c->animation.running = false; c->need_output_flush = false; c->animainit_geom = c->current = c->pending = c->animation.current = @@ -567,7 +928,10 @@ void client_apply_clip(Client *c, float factor) { return; } - wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); + if (!c->overview_scene_surface) { + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, + &clip_box); + } buffer_set_effect(c, (BufferData){1.0f, 1.0f, clip_box.width, clip_box.height, @@ -618,7 +982,9 @@ void client_apply_clip(Client *c, float factor) { } // 应用窗口表面剪切 - wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); + if (!c->overview_scene_surface) { + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); + } // 获取剪切后的表面的实际大小用于计算缩放 int32_t acutal_surface_width = geometry.width - offset.x - offset.width; @@ -632,7 +998,7 @@ void client_apply_clip(Client *c, float factor) { buffer_data.height = clip_box.height; buffer_data.corner_location = current_corner_location; - if (factor == 1.0) { + if (factor == 1.0 && !c->overview_scene_surface) { buffer_data.width_scale = 1.0; buffer_data.height_scale = 1.0; } else { @@ -685,19 +1051,19 @@ void fadeout_client_animation_next_tick(Client *c) { 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); + double opacity = MANGO_MAX(percent, 0); - if (animation_fade_out && !c->nofadeout) + if (config.animation_fade_out && !c->nofadeout) wlr_scene_node_for_each_buffer(&c->scene->node, scene_buffer_apply_opacity, &opacity); if ((c->animation_type_close && strcmp(c->animation_type_close, "zoom") == 0) || (!c->animation_type_close && - strcmp(animation_type_close, "zoom") == 0)) { + strcmp(config.animation_type_close, "zoom") == 0)) { buffer_data.width = width; buffer_data.height = height; @@ -753,6 +1119,8 @@ void client_animation_next_tick(Client *c) { c->is_pending_open_animation = false; + client_apply_clip(c, factor); + if (animation_passed >= 1.0) { // clear the open action state @@ -762,11 +1130,11 @@ void client_animation_next_tick(Client *c) { c->animation.tagining = false; c->animation.running = false; + c->animation.overining = false; if (c->animation.tagouting) { c->animation.tagouting = false; wlr_scene_node_set_enabled(&c->scene->node, false); - client_set_suspended(c, true); c->animation.tagouted = true; c->animation.current = c->geom; } @@ -775,15 +1143,15 @@ void client_animation_next_tick(Client *c) { surface = pointer_c && pointer_c == c ? client_surface(pointer_c) : NULL; - if (surface && pointer_c == selmon->sel) { + + // avoid game window force grab pointer in overview mode + if (surface && pointer_c == selmon->sel && !selmon->isoverview) { wlr_seat_pointer_notify_enter(seat, surface, sx, sy); } // end flush in next frame, not the current frame c->need_output_flush = false; } - - client_apply_clip(c, factor); } void init_fadeout_client(Client *c) { @@ -802,14 +1170,18 @@ void init_fadeout_client(Client *c) { if ((c->animation_type_close && strcmp(c->animation_type_close, "none") == 0) || (!c->animation_type_close && - strcmp(animation_type_close, "none") == 0)) { + strcmp(config.animation_type_close, "none") == 0)) { return; } Client *fadeout_client = ecalloc(1, sizeof(*fadeout_client)); wlr_scene_node_set_enabled(&c->scene->node, true); - client_set_border_color(c, bordercolor); + client_set_border_color(c, config.bordercolor); + if (c->overview_scene_surface) { + wlr_scene_node_destroy(&c->overview_scene_surface->node); + c->overview_scene_surface = NULL; + } fadeout_client->scene = wlr_scene_tree_snapshot(&c->scene->node, layers[LyrFadeOut]); wlr_scene_node_set_enabled(&c->scene->node, false); @@ -819,7 +1191,7 @@ void init_fadeout_client(Client *c) { return; } - fadeout_client->animation.duration = animation_duration_close; + fadeout_client->animation.duration = config.animation_duration_close; fadeout_client->geom = fadeout_client->current = fadeout_client->animainit_geom = fadeout_client->animation.initial = c->animation.current; @@ -836,7 +1208,7 @@ void init_fadeout_client(Client *c) { fadeout_client->animation.initial.y = 0; if ((!c->animation_type_close && - strcmp(animation_type_close, "fade") == 0) || + strcmp(config.animation_type_close, "fade") == 0) || (c->animation_type_close && strcmp(c->animation_type_close, "fade") == 0)) { fadeout_client->current.x = 0; @@ -846,7 +1218,7 @@ void init_fadeout_client(Client *c) { } else if ((c->animation_type_close && strcmp(c->animation_type_close, "slide") == 0) || (!c->animation_type_close && - strcmp(animation_type_close, "slide") == 0)) { + strcmp(config.animation_type_close, "slide") == 0)) { fadeout_client->current.y = c->geom.y + c->geom.height / 2 > c->mon->m.y + c->mon->m.height / 2 ? c->mon->m.height - @@ -856,16 +1228,16 @@ void init_fadeout_client(Client *c) { } else { fadeout_client->current.y = (fadeout_client->geom.height - - fadeout_client->geom.height * zoom_end_ratio) / + fadeout_client->geom.height * config.zoom_end_ratio) / 2; fadeout_client->current.x = (fadeout_client->geom.width - - fadeout_client->geom.width * zoom_end_ratio) / + fadeout_client->geom.width * config.zoom_end_ratio) / 2; fadeout_client->current.width = - fadeout_client->geom.width * zoom_end_ratio; + fadeout_client->geom.width * config.zoom_end_ratio; fadeout_client->current.height = - fadeout_client->geom.height * zoom_end_ratio; + fadeout_client->geom.height * config.zoom_end_ratio; } fadeout_client->animation.time_started = get_now_in_ms(); @@ -900,14 +1272,12 @@ void client_set_pending_state(Client *c) { if (!c || c->iskilling) return; - // 判断是否需要动画 - if (!animations) { + if (!config.animations) { c->animation.should_animate = false; - } else if (animations && c->animation.tagining) { + } else if (config.animations && c->animation.tagining) { c->animation.should_animate = true; - } else if (!animations || c == grabc || - (!c->is_pending_open_animation && - wlr_box_equal(&c->current, &c->pending))) { + } else if (c == grabc || (!c->is_pending_open_animation && + wlr_box_equal(&c->current, &c->pending))) { c->animation.should_animate = false; } else { c->animation.should_animate = true; @@ -916,7 +1286,7 @@ void client_set_pending_state(Client *c) { if (((c->animation_type_open && strcmp(c->animation_type_open, "none") == 0) || (!c->animation_type_open && - strcmp(animation_type_open, "none") == 0)) && + strcmp(config.animation_type_open, "none") == 0)) && c->animation.action == OPEN) { c->animation.duration = 0; } @@ -963,8 +1333,8 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { if (is_scroller_layout(c->mon) && (!c->isfloating || c == grabc)) { c->geom = geo; - c->geom.width = MAX(1 + 2 * (int32_t)c->bw, c->geom.width); - c->geom.height = MAX(1 + 2 * (int32_t)c->bw, c->geom.height); + c->geom.width = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.width); + c->geom.height = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.height); } else { // 这里会限制不允许窗口划出屏幕 c->geom = geo; applybounds( @@ -981,20 +1351,23 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { c->animation.begin_fade_in = false; } - if (c->animation.action == OPEN && !c->animation.tagining && - !c->animation.tagouting && wlr_box_equal(&c->geom, &c->current)) { + if (c->animation.overining) { + c->animation.action = OVERVIEW; + } else if (c->animation.action == OPEN && !c->animation.tagining && + !c->animation.tagouting && + wlr_box_equal(&c->geom, &c->current)) { c->animation.action = c->animation.action; } else if (c->animation.tagouting) { - c->animation.duration = animation_duration_tag; + c->animation.duration = config.animation_duration_tag; c->animation.action = TAG; } else if (c->animation.tagining) { - c->animation.duration = animation_duration_tag; + c->animation.duration = config.animation_duration_tag; c->animation.action = TAG; } else if (c->is_pending_open_animation) { - c->animation.duration = animation_duration_open; + c->animation.duration = config.animation_duration_open; c->animation.action = OPEN; } else { - c->animation.duration = animation_duration_move; + c->animation.duration = config.animation_duration_move; c->animation.action = MOVE; } @@ -1015,14 +1388,20 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { } bool hit_no_border = check_hit_no_border(c); - if (hit_no_border && smartgaps) { + if (hit_no_border && config.smartgaps) { c->bw = 0; c->fake_no_border = true; } // c->geom 是真实的窗口大小和位置,跟过度的动画无关,用于计算布局 - c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw, - c->geom.height - 2 * c->bw); + if (!c->mon->isoverview || !config.ov_no_resize) { + c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw, + c->geom.height - 2 * c->bw); + } + + if (c->configure_serial != 0) { + c->mon->resizing_count_pending++; + } if (c == grabc) { c->animation.running = false; @@ -1066,6 +1445,15 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { c->animainit_geom = c->geom; } + if (config.animations && config.ov_no_resize && c->mon->isoverview && + c != c->mon->sel && c->animation.action == OVERVIEW) { + set_overview_enter_animation(c); + } + + if (!config.animations && config.ov_no_resize && c->mon->isoverview) { + c->animainit_geom = c->geom; + } + // 开始应用动画设置 client_set_pending_state(c); @@ -1082,13 +1470,14 @@ bool client_draw_fadeout_frame(Client *c) { void client_set_focused_opacity_animation(Client *c) { float *border_color = get_border_color(c); + wlr_scene_node_lower_to_bottom(&c->border->node); - if (!animations) { + if (!config.animations) { setborder_color(c); return; } - c->opacity_animation.duration = animation_duration_focus; + c->opacity_animation.duration = config.animation_duration_focus; memcpy(c->opacity_animation.target_border_color, border_color, sizeof(c->opacity_animation.target_border_color)); c->opacity_animation.target_opacity = c->focused_opacity; @@ -1102,15 +1491,14 @@ void client_set_focused_opacity_animation(Client *c) { } void client_set_unfocused_opacity_animation(Client *c) { - // Start border color animation to unfocused float *border_color = get_border_color(c); - - if (!animations) { + wlr_scene_node_raise_to_top(&c->border->node); + if (!config.animations) { setborder_color(c); return; } - c->opacity_animation.duration = animation_duration_focus; + c->opacity_animation.duration = config.animation_duration_focus; memcpy(c->opacity_animation.target_border_color, border_color, sizeof(c->opacity_animation.target_border_color)); // Start opacity animation to unfocused @@ -1145,13 +1533,14 @@ bool client_apply_focus_opacity(Client *c) { double opacity_eased_progress = find_animation_curve_at(linear_progress, OPAFADEIN); - float percent = - animation_fade_in && !c->nofadein ? opacity_eased_progress : 1.0; + float percent = config.animation_fade_in && !c->nofadein + ? opacity_eased_progress + : 1.0; float opacity = c == selmon->sel ? c->focused_opacity : c->unfocused_opacity; - float target_opacity = - percent * (1.0 - fadein_begin_opacity) + fadein_begin_opacity; + float target_opacity = percent * (1.0 - config.fadein_begin_opacity) + + config.fadein_begin_opacity; if (target_opacity > opacity) { target_opacity = opacity; } @@ -1171,7 +1560,7 @@ bool client_apply_focus_opacity(Client *c) { 1.0)); } client_set_border_color(c, c->opacity_animation.target_border_color); - } else if (animations && c->opacity_animation.running) { + } else if (config.animations && c->opacity_animation.running) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); @@ -1232,7 +1621,7 @@ bool client_draw_frame(Client *c) { return client_apply_focus_opacity(c); } - if (animations && c->animation.running) { + if (config.animations && c->animation.running) { client_animation_next_tick(c); } else { wlr_scene_node_set_position(&c->scene->node, c->pending.x, diff --git a/src/animation/common.h b/src/animation/common.h index 3b1be386..cfd50045 100644 --- a/src/animation/common.h +++ b/src/animation/common.h @@ -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, @@ -178,10 +215,6 @@ static bool scene_node_snapshot(struct wlr_scene_node *node, int32_t lx, wlr_scene_buffer_set_corner_radii(snapshot_buffer, scene_buffer->corners); - 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); diff --git a/src/animation/layer.h b/src/animation/layer.h index 27874026..3d8191b1 100644 --- a/src/animation/layer.h +++ b/src/animation/layer.h @@ -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,22 +174,20 @@ 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, - .corners = corner_radii_all(border_radius), + .corners = corner_radii_all(config.border_radius), }; wlr_scene_node_set_position(&l->shadow->node, shadow_box.x, shadow_box.y); @@ -260,7 +257,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, @@ -278,12 +275,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); @@ -325,12 +322,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) { if (blur && !l->noblur && !blur_optimized) { wlr_scene_blur_set_strength(l->blur, opacity); wlr_scene_blur_set_alpha(l->blur, opacity); @@ -351,7 +348,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( @@ -378,7 +375,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; } @@ -388,7 +385,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; } @@ -411,7 +408,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; @@ -427,14 +424,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 - @@ -446,7 +443,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动画的结束绝对坐标和大小 @@ -491,17 +488,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)) { @@ -515,8 +513,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) { @@ -528,7 +525,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; } @@ -575,7 +572,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 { diff --git a/src/animation/tag.h b/src/animation/tag.h index 8e65a93a..859ee1b8 100644 --- a/src/animation/tag.h +++ b/src/animation/tag.h @@ -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; } } diff --git a/src/client/client.h b/src/client/client.h index 49ab3988..8e291e9e 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -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 unlikely 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 diff --git a/src/common/util.c b/src/common/util.c index a15cca7c..8e562b19 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -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; + } +} \ No newline at end of file diff --git a/src/common/util.h b/src/common/util.h index 8fb60338..c7f83f2b 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -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); \ No newline at end of file +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); \ No newline at end of file diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 74b0cdcf..d37a4ca4 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -58,6 +58,7 @@ typedef struct { uint32_t tags; int32_t isfloating; int32_t isfullscreen; + int32_t isfakefullscreen; float scroller_proportion; const char *animation_type_open; const char *animation_type_close; @@ -78,18 +79,19 @@ typedef struct { int32_t ignore_maximize; int32_t ignore_minimize; int32_t isnosizehint; - const char *monitor; + int32_t idleinhibit_when_focus; + char *monitor; int32_t offsetx; int32_t offsety; - int32_t width; - int32_t height; + float width; + float height; int32_t nofocus; int32_t nofadein; int32_t nofadeout; int32_t no_force_center; int32_t isterm; int32_t allow_csd; - int32_t force_maximize; + int32_t force_fakemaximize; int32_t force_tiled_state; int32_t force_tearing; int32_t noswallow; @@ -111,6 +113,7 @@ typedef struct { int32_t width, height; // Monitor resolution float refresh; // Refresh rate int32_t vrr; // variable refresh rate + int32_t custom; // enable custom mode } ConfigMonitorRule; // 修改后的宏定义 @@ -165,7 +168,11 @@ typedef struct { char *monitor_serial; float mfact; int32_t nmaster; + float scroller_default_proportion; + float scroller_default_proportion_single; + int32_t scroller_ignore_proportion_single; int32_t no_render_border; + int32_t open_as_floating; int32_t no_hide; } ConfigTagRule; @@ -211,7 +218,9 @@ typedef struct { int32_t scroller_ignore_proportion_single; int32_t scroller_focus_center; int32_t scroller_prefer_center; + int32_t scroller_prefer_overspread; int32_t edge_scroller_pointer_focus; + double edge_scroller_focus_allow_speed; int32_t focus_cross_monitor; int32_t exchange_cross_monitor; int32_t scratchpad_cross_monitor; @@ -222,6 +231,7 @@ typedef struct { int32_t snap_distance; int32_t enable_floating_snap; int32_t drag_tile_to_tile; + int32_t drag_tile_small; uint32_t swipe_min_threshold; float focused_opacity; float unfocused_opacity; @@ -237,13 +247,26 @@ typedef struct { int32_t center_master_overspread; int32_t center_when_single_stack; - uint32_t hotarea_size; - uint32_t hotarea_corner; - uint32_t enable_hotarea; - uint32_t ov_tab_mode; + /* dwindle layout */ + int32_t dwindle_vsplit; + int32_t dwindle_hsplit; + int32_t dwindle_preserve_split; + int32_t dwindle_smart_split; + int32_t dwindle_smart_resize; + int32_t dwindle_drop_simple_split; + int32_t dwindle_manual_split; + float dwindle_split_ratio; + + int32_t hotarea_size; + int32_t hotarea_corner; + int32_t enable_hotarea; + int32_t ov_tab_mode; + int32_t ov_no_resize; + int32_t overviewgappi; int32_t overviewgappo; uint32_t cursor_hide_timeout; + uint32_t cursor_hide_on_keypress; uint32_t axis_bind_apply_timeout; uint32_t focus_on_activate; @@ -258,30 +281,41 @@ typedef struct { int32_t repeat_delay; uint32_t numlockon; - /* Trackpad */ - int32_t disable_trackpad; - int32_t tap_to_click; - int32_t tap_and_drag; - int32_t drag_lock; - int32_t mouse_natural_scrolling; - int32_t trackpad_natural_scrolling; + /* common pointer */ int32_t disable_while_typing; int32_t left_handed; int32_t middle_button_emulation; - uint32_t accel_profile; - double accel_speed; uint32_t scroll_method; uint32_t scroll_button; uint32_t click_method; uint32_t send_events_mode; - uint32_t button_map; + /* mouse */ + int32_t mouse_natural_scrolling; + uint32_t mouse_accel_profile; + double mouse_accel_speed; double axis_scroll_factor; + /* tablet */ + char *tablet_map_to_mon; + + /* Trackpad */ + int32_t trackpad_natural_scrolling; + uint32_t trackpad_accel_profile; + double trackpad_accel_speed; + double trackpad_scroll_factor; + int32_t disable_trackpad; + int32_t tap_to_click; + int32_t tap_and_drag; + int32_t drag_lock; + uint32_t button_map; + + /* window effects */ int32_t blur; int32_t blur_layer; int32_t blur_optimized; int32_t border_radius; + int32_t border_radius_location_default; struct blur_data blur_params; int32_t shadows; int32_t shadow_only_floating; @@ -292,16 +326,20 @@ typedef struct { int32_t shadows_position_y; float shadowscolor[4]; + /* appearance */ int32_t smartgaps; uint32_t gappih; uint32_t gappiv; uint32_t gappoh; uint32_t gappov; uint32_t borderpx; + uint32_t tab_bar_height; float scratchpad_width_ratio; float scratchpad_height_ratio; float rootcolor[4]; float bordercolor[4]; + float dropcolor[4]; + float splitcolor[4]; float focuscolor[4]; float maximizescreencolor[4]; float urgentcolor[4]; @@ -309,7 +347,8 @@ typedef struct { float globalcolor[4]; float overlaycolor[4]; - char autostart[3][256]; + int32_t log_level; + uint32_t capslock; ConfigTagRule *tag_rules; // 动态数组 int32_t tag_rules_count; // 数量 @@ -353,22 +392,35 @@ typedef struct { int32_t single_scratchpad; int32_t xwayland_persistence; int32_t syncobj_enable; + int32_t tag_carousel; + float drag_tile_refresh_interval; + float drag_floating_refresh_interval; int32_t allow_tearing; int32_t allow_shortcuts_inhibit; int32_t allow_lock_transparent; struct xkb_rule_names xkb_rules; + char xkb_rules_rules[128]; + char xkb_rules_model[128]; + char xkb_rules_layout[128]; + char xkb_rules_variant[128]; + char xkb_rules_options[128]; char keymode[28]; struct xkb_context *ctx; struct xkb_keymap *keymap; + DecorateDrawData jumplabeldata; + DecorateDrawData tabdata; } Config; typedef int32_t (*FuncType)(const Arg *); Config config; bool parse_config_file(Config *config, const char *file_path, bool must_exist); +bool apply_rule_to_state(Monitor *m, const ConfigMonitorRule *rule, + struct wlr_output_state *state, int vrr, int custom); +bool monitor_matches_rule(Monitor *m, const ConfigMonitorRule *rule); // Helper function to trim whitespace from start and end of a string void trim_whitespace(char *str) { @@ -516,6 +568,26 @@ int32_t parse_direction(const char *str) { } } +int32_t parse_force(const char *str) { + // 将输入字符串转换为小写 + char lowerStr[10]; + int32_t i = 0; + while (str[i] && i < 9) { + lowerStr[i] = tolower(str[i]); + i++; + } + lowerStr[i] = '\0'; + + // 根据转换后的小写字符串返回对应的枚举值 + if (strcmp(lowerStr, "unforce") == 0) { + return UNFORCE; + } else if (strcmp(lowerStr, "force") == 0) { + return FORCE; + } else { + return UNFORCE; + } +} + int32_t parse_fold_state(const char *str) { // 将输入字符串转换为小写 char lowerStr[10]; @@ -619,9 +691,14 @@ uint32_t parse_mod(const char *mod_str) { // 分割处理每个部分 token = strtok_r(input_copy, "+", &saveptr); while (token != NULL) { - // 去除空白 - while (*token == ' ' || *token == '\t') - token++; + // 去除前后空白 + trim_whitespace(token); + + // 如果 token 变成空字符串则跳过 + if (*token == '\0') { + token = strtok_r(NULL, "+", &saveptr); + continue; + } if (strncmp(token, "code:", 5) == 0) { // 处理 code: 形式 @@ -653,7 +730,6 @@ uint32_t parse_mod(const char *mod_str) { } } } else { - // 完整的 modifier 检查(保留原始所有检查项) if (!strcmp(token, "super") || !strcmp(token, "super_l") || !strcmp(token, "super_r")) { mod |= WLR_MODIFIER_LOGO; @@ -831,6 +907,24 @@ uint32_t parse_button(const char *str) { } lowerStr[i] = '\0'; // 确保字符串正确终止 + // 解析 "code:数字" 格式 + if (strncmp(lowerStr, "code:", 5) == 0) { + const char *numStart = lowerStr + 5; // 跳过 "code:" + char *endptr; + unsigned long val = strtoul(numStart, &endptr, 10); + + // 检查是否成功转换且无多余字符,且值未溢出(在 uint32_t 范围内) + if (endptr != numStart && *endptr == '\0' && val <= UINT32_MAX) { + return (uint32_t)val; + } else { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid code format: " + "\033[1m\033[31m%s\n", + str); + return UINT32_MAX; + } + } + // 根据转换后的小写字符串返回对应的按钮编号 if (strcmp(lowerStr, "btn_left") == 0) { return BTN_LEFT; @@ -920,6 +1014,8 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "focusdir") == 0) { func = focusdir; (*arg).i = parse_direction(arg_value); + } else if (strcmp(func_name, "focusid") == 0) { + func = focusid; } else if (strcmp(func_name, "incnmaster") == 0) { func = incnmaster; (*arg).i = atoi(arg_value); @@ -939,11 +1035,15 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "toggleoverview") == 0) { func = toggleoverview; (*arg).i = atoi(arg_value); + } else if (strcmp(func_name, "togglejump") == 0) { + func = togglejump; + (*arg).i = atoi(arg_value); } else if (strcmp(func_name, "set_proportion") == 0) { func = set_proportion; (*arg).f = atof(arg_value); } else if (strcmp(func_name, "switch_proportion_preset") == 0) { func = switch_proportion_preset; + (*arg).i = parse_circle_direction(arg_value); } else if (strcmp(func_name, "viewtoleft") == 0) { func = viewtoleft; (*arg).i = atoi(arg_value); @@ -961,6 +1061,7 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).i = atoi(arg_value); } else if (strcmp(func_name, "killclient") == 0) { func = killclient; + (*arg).i = parse_force(arg_value); } else if (strcmp(func_name, "centerwin") == 0) { func = centerwin; } else if (strcmp(func_name, "focuslast") == 0) { @@ -1016,6 +1117,7 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).v = strdup(arg_value); } else if (strcmp(func_name, "switch_keyboard_layout") == 0) { func = switch_keyboard_layout; + (*arg).i = CLAMP_INT(atoi(arg_value), 0, 100); } else if (strcmp(func_name, "setlayout") == 0) { func = setlayout; (*arg).v = strdup(arg_value); @@ -1179,6 +1281,14 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "scroller_stack") == 0) { func = scroller_stack; (*arg).i = parse_direction(arg_value); + } else if (strcmp(func_name, "toggle_all_floating") == 0) { + func = toggle_all_floating; + } else if (strcmp(func_name, "dwindle_toggle_split_direction") == 0) { + func = dwindle_toggle_split_direction; + } else if (strcmp(func_name, "dwindle_split_horizontal") == 0) { + func = dwindle_split_horizontal; + } else if (strcmp(func_name, "dwindle_split_vertical") == 0) { + func = dwindle_split_vertical; } else { return NULL; } @@ -1337,8 +1447,12 @@ bool parse_option(Config *config, char *key, char *value) { config->scroller_focus_center = atoi(value); } else if (strcmp(key, "scroller_prefer_center") == 0) { config->scroller_prefer_center = atoi(value); + } else if (strcmp(key, "scroller_prefer_overspread") == 0) { + config->scroller_prefer_overspread = atoi(value); } else if (strcmp(key, "edge_scroller_pointer_focus") == 0) { config->edge_scroller_pointer_focus = atoi(value); + } else if (strcmp(key, "edge_scroller_focus_allow_speed") == 0) { + config->edge_scroller_focus_allow_speed = atof(value); } else if (strcmp(key, "focus_cross_monitor") == 0) { config->focus_cross_monitor = atoi(value); } else if (strcmp(key, "exchange_cross_monitor") == 0) { @@ -1389,6 +1503,12 @@ bool parse_option(Config *config, char *key, char *value) { config->xwayland_persistence = atoi(value); } else if (strcmp(key, "syncobj_enable") == 0) { config->syncobj_enable = atoi(value); + } else if (strcmp(key, "tag_carousel") == 0) { + config->tag_carousel = atoi(value); + } else if (strcmp(key, "drag_tile_refresh_interval") == 0) { + config->drag_tile_refresh_interval = atof(value); + } else if (strcmp(key, "drag_floating_refresh_interval") == 0) { + config->drag_floating_refresh_interval = atof(value); } else if (strcmp(key, "allow_tearing") == 0) { config->allow_tearing = atoi(value); } else if (strcmp(key, "allow_shortcuts_inhibit") == 0) { @@ -1405,6 +1525,8 @@ bool parse_option(Config *config, char *key, char *value) { config->enable_floating_snap = atoi(value); } else if (strcmp(key, "drag_tile_to_tile") == 0) { config->drag_tile_to_tile = atoi(value); + } else if (strcmp(key, "drag_tile_small") == 0) { + config->drag_tile_small = atoi(value); } else if (strcmp(key, "swipe_min_threshold") == 0) { config->swipe_min_threshold = atoi(value); } else if (strcmp(key, "focused_opacity") == 0) { @@ -1412,25 +1534,25 @@ bool parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "unfocused_opacity") == 0) { config->unfocused_opacity = atof(value); } else if (strcmp(key, "xkb_rules_rules") == 0) { - strncpy(xkb_rules_rules, value, sizeof(xkb_rules_rules) - 1); - xkb_rules_rules[sizeof(xkb_rules_rules) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_rules, value, + sizeof(config->xkb_rules_rules) - 1); + config->xkb_rules_rules[sizeof(config->xkb_rules_rules) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_model") == 0) { - strncpy(xkb_rules_model, value, sizeof(xkb_rules_model) - 1); - xkb_rules_model[sizeof(xkb_rules_model) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_model, value, + sizeof(config->xkb_rules_model) - 1); + config->xkb_rules_model[sizeof(config->xkb_rules_model) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_layout") == 0) { - strncpy(xkb_rules_layout, value, sizeof(xkb_rules_layout) - 1); - xkb_rules_layout[sizeof(xkb_rules_layout) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_layout, value, + sizeof(config->xkb_rules_layout) - 1); + config->xkb_rules_layout[sizeof(config->xkb_rules_layout) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_variant") == 0) { - strncpy(xkb_rules_variant, value, sizeof(xkb_rules_variant) - 1); - xkb_rules_variant[sizeof(xkb_rules_variant) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_variant, value, + sizeof(config->xkb_rules_variant) - 1); + config->xkb_rules_variant[sizeof(config->xkb_rules_variant) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_options") == 0) { - strncpy(xkb_rules_options, value, sizeof(xkb_rules_options) - 1); - xkb_rules_options[sizeof(xkb_rules_options) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_options, value, + sizeof(config->xkb_rules_options) - 1); + config->xkb_rules_options[sizeof(config->xkb_rules_options) - 1] = '\0'; } else if (strcmp(key, "scroller_proportion_preset") == 0) { // 1. 统计 value 中有多少个逗号,确定需要解析的浮点数个数 int32_t count = 0; // 初始化为 0 @@ -1572,6 +1694,22 @@ bool parse_option(Config *config, char *key, char *value) { config->center_master_overspread = atoi(value); } else if (strcmp(key, "center_when_single_stack") == 0) { config->center_when_single_stack = atoi(value); + } else if (strcmp(key, "dwindle_vsplit") == 0) { + config->dwindle_vsplit = atoi(value); + } else if (strcmp(key, "dwindle_hsplit") == 0) { + config->dwindle_hsplit = atoi(value); + } else if (strcmp(key, "dwindle_preserve_split") == 0) { + config->dwindle_preserve_split = atoi(value); + } else if (strcmp(key, "dwindle_smart_split") == 0) { + config->dwindle_smart_split = atoi(value); + } else if (strcmp(key, "dwindle_smart_resize") == 0) { + config->dwindle_smart_resize = atoi(value); + } else if (strcmp(key, "dwindle_drop_simple_split") == 0) { + config->dwindle_drop_simple_split = atoi(value); + } else if (strcmp(key, "dwindle_manual_split") == 0) { + config->dwindle_manual_split = atoi(value); + } else if (strcmp(key, "dwindle_split_ratio") == 0) { + config->dwindle_split_ratio = atof(value); } else if (strcmp(key, "hotarea_size") == 0) { config->hotarea_size = atoi(value); } else if (strcmp(key, "hotarea_corner") == 0) { @@ -1580,12 +1718,16 @@ bool parse_option(Config *config, char *key, char *value) { config->enable_hotarea = atoi(value); } else if (strcmp(key, "ov_tab_mode") == 0) { config->ov_tab_mode = atoi(value); + } else if (strcmp(key, "ov_no_resize") == 0) { + config->ov_no_resize = atoi(value); } else if (strcmp(key, "overviewgappi") == 0) { config->overviewgappi = atoi(value); } else if (strcmp(key, "overviewgappo") == 0) { config->overviewgappo = atoi(value); } else if (strcmp(key, "cursor_hide_timeout") == 0) { config->cursor_hide_timeout = atoi(value); + } else if (strcmp(key, "cursor_hide_on_keypress") == 0) { + config->cursor_hide_on_keypress = atoi(value); } else if (strcmp(key, "axis_bind_apply_timeout") == 0) { config->axis_bind_apply_timeout = atoi(value); } else if (strcmp(key, "focus_on_activate") == 0) { @@ -1624,16 +1766,160 @@ bool parse_option(Config *config, char *key, char *value) { config->cursor_size = atoi(value); } else if (strcmp(key, "cursor_theme") == 0) { config->cursor_theme = strdup(value); + } else if (strcmp(key, "tab_bar_decorate_font_desc") == 0) { + config->tabdata.font_desc = strdup(value); + } else if (strcmp(key, "tab_bar_decorate_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "tab_bar_decorate_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->tabdata.fg_color, color); + } + } else if (strcmp(key, "tab_bar_decorate_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "tab_bar_decorate_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->tabdata.bg_color, color); + } + } else if (strcmp(key, "tab_bar_decorate_focus_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "tab_bar_decorate_focus_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->tabdata.focus_fg_color, color); + } + } else if (strcmp(key, "tab_bar_decorate_focus_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "tab_bar_decorate_focus_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->tabdata.focus_bg_color, color); + } + } else if (strcmp(key, "tab_bar_decorate_border_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "tab_bar_decorate_border_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->tabdata.border_color, color); + } + } else if (strcmp(key, "tab_bar_decorate_border_width") == 0) { + config->tabdata.border_width = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "tab_bar_decorate_corner_radius") == 0) { + config->tabdata.corner_radius = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "tab_bar_decorate_padding_x") == 0) { + config->tabdata.padding_x = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "tab_bar_decorate_padding_y") == 0) { + config->tabdata.padding_y = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_font_desc") == 0) { + config->jumplabeldata.font_desc = strdup(value); + } else if (strcmp(key, "jump_label_decorate_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.fg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.bg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_focus_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_focus_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.focus_fg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_focus_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_focus_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.focus_bg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_border_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_border_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.border_color, color); + } + } else if (strcmp(key, "jump_label_decorate_border_width") == 0) { + config->jumplabeldata.border_width = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_corner_radius") == 0) { + config->jumplabeldata.corner_radius = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_padding_x") == 0) { + config->jumplabeldata.padding_x = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_padding_y") == 0) { + config->jumplabeldata.padding_y = CLAMP_INT(atoi(value), 0, 100); } else if (strcmp(key, "disable_while_typing") == 0) { config->disable_while_typing = atoi(value); } else if (strcmp(key, "left_handed") == 0) { config->left_handed = atoi(value); } else if (strcmp(key, "middle_button_emulation") == 0) { config->middle_button_emulation = atoi(value); - } else if (strcmp(key, "accel_profile") == 0) { - config->accel_profile = atoi(value); - } else if (strcmp(key, "accel_speed") == 0) { - config->accel_speed = atof(value); + } else if (strcmp(key, "mouse_accel_profile") == 0) { + config->mouse_accel_profile = atoi(value); + } else if (strcmp(key, "mouse_accel_speed") == 0) { + config->mouse_accel_speed = atof(value); + } else if (strcmp(key, "trackpad_accel_profile") == 0) { + config->trackpad_accel_profile = atoi(value); + } else if (strcmp(key, "trackpad_accel_speed") == 0) { + config->trackpad_accel_speed = atof(value); } else if (strcmp(key, "scroll_method") == 0) { config->scroll_method = atoi(value); } else if (strcmp(key, "scroll_button") == 0) { @@ -1646,6 +1932,12 @@ bool parse_option(Config *config, char *key, char *value) { config->button_map = atoi(value); } else if (strcmp(key, "axis_scroll_factor") == 0) { config->axis_scroll_factor = atof(value); + } else if (strcmp(key, "tablet_map_to_mon") == 0) { + if (config->tablet_map_to_mon) + free(config->tablet_map_to_mon); + config->tablet_map_to_mon = strdup(value); + } else if (strcmp(key, "trackpad_scroll_factor") == 0) { + config->trackpad_scroll_factor = atof(value); } else if (strcmp(key, "gappih") == 0) { config->gappih = atoi(value); } else if (strcmp(key, "gappiv") == 0) { @@ -1660,6 +1952,8 @@ bool parse_option(Config *config, char *key, char *value) { config->scratchpad_height_ratio = atof(value); } else if (strcmp(key, "borderpx") == 0) { config->borderpx = atoi(value); + } else if (strcmp(key, "tab_bar_height") == 0) { + config->tab_bar_height = atoi(value); } else if (strcmp(key, "rootcolor") == 0) { int64_t color = parse_color(value); if (color == -1) { @@ -1695,6 +1989,28 @@ bool parse_option(Config *config, char *key, char *value) { } else { convert_hex_to_rgba(config->bordercolor, color); } + } else if (strcmp(key, "dropcolor") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid dropcolor " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->dropcolor, color); + } + } else if (strcmp(key, "splitcolor") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid splitcolor " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->splitcolor, color); + } } else if (strcmp(key, "focuscolor") == 0) { int64_t color = parse_color(value); if (color == -1) { @@ -1791,6 +2107,7 @@ bool parse_option(Config *config, char *key, char *value) { rule->height = -1; rule->refresh = 0.0f; rule->vrr = 0; + rule->custom = 0; bool parse_error = false; char *token = strtok(value, ","); @@ -1828,6 +2145,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->refresh = CLAMP_FLOAT(atof(val), 0.001f, 1000.0f); } else if (strcmp(key, "vrr") == 0) { rule->vrr = CLAMP_INT(atoi(val), 0, 1); + } else if (strcmp(key, "custom") == 0) { + rule->custom = CLAMP_INT(atoi(val), 0, 1); } else { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Unknown " @@ -1840,6 +2159,13 @@ bool parse_option(Config *config, char *key, char *value) { token = strtok(NULL, ","); } + if (!rule->name && !rule->make && !rule->model && !rule->serial) { + fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Monitor rule " + "must have at least one of the following " + "options: name, make, model, serial\n"); + return false; + } + config->monitor_rules_count++; return !parse_error; } else if (strcmp(key, "tagrule") == 0) { @@ -1866,7 +2192,11 @@ bool parse_option(Config *config, char *key, char *value) { rule->nmaster = 0; rule->mfact = 0.0f; rule->no_render_border = 0; + rule->open_as_floating = 0; rule->no_hide = 0; + rule->scroller_default_proportion = 0.0f; + rule->scroller_default_proportion_single = 0.0f; + rule->scroller_ignore_proportion_single = -1; bool parse_error = false; char *token = strtok(value, ","); @@ -1894,12 +2224,25 @@ bool parse_option(Config *config, char *key, char *value) { rule->monitor_serial = strdup(val); } else if (strcmp(key, "no_render_border") == 0) { rule->no_render_border = CLAMP_INT(atoi(val), 0, 1); + } else if (strcmp(key, "open_as_floating") == 0) { + rule->open_as_floating = CLAMP_INT(atoi(val), 0, 1); } else if (strcmp(key, "no_hide") == 0) { rule->no_hide = CLAMP_INT(atoi(val), 0, 1); } else if (strcmp(key, "nmaster") == 0) { rule->nmaster = CLAMP_INT(atoi(val), 1, 99); } else if (strcmp(key, "mfact") == 0) { rule->mfact = CLAMP_FLOAT(atof(val), 0.1f, 0.9f); + } else if (strcmp(key, "scroller_default_proportion") == 0) { + rule->scroller_default_proportion = + CLAMP_FLOAT(atof(val), 0.0f, 1.0f); + } else if (strcmp(key, "scroller_default_proportion_single") == + 0) { + rule->scroller_default_proportion_single = + CLAMP_FLOAT(atof(val), 0.0f, 1.0f); + } else if (strcmp(key, "scroller_ignore_proportion_single") == + 0) { + rule->scroller_ignore_proportion_single = + CLAMP_INT(atoi(val), 0, 1); } else { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Unknown " @@ -1996,6 +2339,7 @@ bool parse_option(Config *config, char *key, char *value) { // int32_t rule value, relay to a client property rule->isfloating = -1; rule->isfullscreen = -1; + rule->isfakefullscreen = -1; rule->isnoborder = -1; rule->isnoshadow = -1; rule->isnoradius = -1; @@ -2011,9 +2355,10 @@ bool parse_option(Config *config, char *key, char *value) { rule->ignore_maximize = -1; rule->ignore_minimize = -1; rule->isnosizehint = -1; + rule->idleinhibit_when_focus = -1; rule->isterm = -1; rule->allow_csd = -1; - rule->force_maximize = -1; + rule->force_fakemaximize = -1; rule->force_tiled_state = -1; rule->force_tearing = -1; rule->noswallow = -1; @@ -2084,9 +2429,9 @@ bool parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "no_force_center") == 0) { rule->no_force_center = atoi(val); } else if (strcmp(key, "width") == 0) { - rule->width = atoi(val); + rule->width = atof(val); } else if (strcmp(key, "height") == 0) { - rule->height = atoi(val); + rule->height = atof(val); } else if (strcmp(key, "isnoborder") == 0) { rule->isnoborder = atoi(val); } else if (strcmp(key, "isnoshadow") == 0) { @@ -2123,12 +2468,14 @@ bool parse_option(Config *config, char *key, char *value) { rule->ignore_minimize = atoi(val); } else if (strcmp(key, "isnosizehint") == 0) { rule->isnosizehint = atoi(val); + } else if (strcmp(key, "idleinhibit_when_focus") == 0) { + rule->idleinhibit_when_focus = atoi(val); } else if (strcmp(key, "isterm") == 0) { rule->isterm = atoi(val); } else if (strcmp(key, "allow_csd") == 0) { rule->allow_csd = atoi(val); - } else if (strcmp(key, "force_maximize") == 0) { - rule->force_maximize = atoi(val); + } else if (strcmp(key, "force_fakemaximize") == 0) { + rule->force_fakemaximize = atoi(val); } else if (strcmp(key, "force_tiled_state") == 0) { rule->force_tiled_state = atoi(val); } else if (strcmp(key, "force_tearing") == 0) { @@ -2141,6 +2488,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->scroller_proportion = atof(val); } else if (strcmp(key, "isfullscreen") == 0) { rule->isfullscreen = atoi(val); + } else if (strcmp(key, "isfakefullscreen") == 0) { + rule->isfakefullscreen = atoi(val); } else if (strcmp(key, "globalkeybinding") == 0) { char mod_str[256], keysym_str[256]; sscanf(val, "%255[^-]-%255[a-zA-Z]", mod_str, keysym_str); @@ -2308,6 +2657,7 @@ bool parse_option(Config *config, char *key, char *value) { binding->arg.v = NULL; binding->arg.v2 = NULL; binding->arg.v3 = NULL; + binding->arg.tc = NULL; binding->func = parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); @@ -2389,6 +2739,7 @@ bool parse_option(Config *config, char *key, char *value) { binding->arg.v = NULL; binding->arg.v2 = NULL; binding->arg.v3 = NULL; + binding->arg.tc = NULL; // TODO: remove this in next version if (binding->mod == 0 && @@ -2474,6 +2825,7 @@ bool parse_option(Config *config, char *key, char *value) { binding->arg.v = NULL; binding->arg.v2 = NULL; binding->arg.v3 = NULL; + binding->arg.tc = NULL; binding->func = parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); @@ -2624,6 +2976,7 @@ bool parse_option(Config *config, char *key, char *value) { binding->arg.v = NULL; binding->arg.v2 = NULL; binding->arg.v3 = NULL; + binding->arg.tc = NULL; binding->func = parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); @@ -3047,6 +3400,21 @@ void free_config(void) { config.cursor_theme = NULL; } + if (config.jumplabeldata.font_desc) { + free((void *)config.jumplabeldata.font_desc); + config.jumplabeldata.font_desc = NULL; + } + + if (config.tabdata.font_desc) { + free((void *)config.tabdata.font_desc); + config.tabdata.font_desc = NULL; + } + + if (config.tablet_map_to_mon) { + free(config.tablet_map_to_mon); + config.tablet_map_to_mon = NULL; + } + // 释放 circle_layout free_circle_layout(&config); @@ -3058,351 +3426,463 @@ void free_config(void) { } void override_config(void) { - // 动画启用 - animations = CLAMP_INT(config.animations, 0, 1); - layer_animations = CLAMP_INT(config.layer_animations, 0, 1); - - // 标签动画方向 - tag_animation_direction = CLAMP_INT(config.tag_animation_direction, 0, 1); - - // 动画淡入淡出设置 - animation_fade_in = CLAMP_INT(config.animation_fade_in, 0, 1); - animation_fade_out = CLAMP_INT(config.animation_fade_out, 0, 1); - zoom_initial_ratio = CLAMP_FLOAT(config.zoom_initial_ratio, 0.1f, 1.0f); - zoom_end_ratio = CLAMP_FLOAT(config.zoom_end_ratio, 0.1f, 1.0f); - fadein_begin_opacity = CLAMP_FLOAT(config.fadein_begin_opacity, 0.0f, 1.0f); - fadeout_begin_opacity = + config.animations = CLAMP_INT(config.animations, 0, 1); + config.layer_animations = CLAMP_INT(config.layer_animations, 0, 1); + config.tag_animation_direction = + CLAMP_INT(config.tag_animation_direction, 0, 1); + config.animation_fade_in = CLAMP_INT(config.animation_fade_in, 0, 1); + config.animation_fade_out = CLAMP_INT(config.animation_fade_out, 0, 1); + config.zoom_initial_ratio = + CLAMP_FLOAT(config.zoom_initial_ratio, 0.1f, 1.0f); + config.zoom_end_ratio = CLAMP_FLOAT(config.zoom_end_ratio, 0.1f, 1.0f); + config.fadein_begin_opacity = + CLAMP_FLOAT(config.fadein_begin_opacity, 0.0f, 1.0f); + config.fadeout_begin_opacity = CLAMP_FLOAT(config.fadeout_begin_opacity, 0.0f, 1.0f); - - // 打开关闭动画类型 - animation_type_open = config.animation_type_open; - animation_type_close = config.animation_type_close; - - // layer打开关闭动画类型 - layer_animation_type_open = config.layer_animation_type_open; - layer_animation_type_close = config.layer_animation_type_close; - - // 动画时间限制在合理范围(1-50000ms) - animation_duration_move = + config.animation_duration_move = CLAMP_INT(config.animation_duration_move, 1, 50000); - animation_duration_open = + config.animation_duration_open = CLAMP_INT(config.animation_duration_open, 1, 50000); - animation_duration_tag = CLAMP_INT(config.animation_duration_tag, 1, 50000); - animation_duration_close = + config.animation_duration_tag = + CLAMP_INT(config.animation_duration_tag, 1, 50000); + config.animation_duration_close = CLAMP_INT(config.animation_duration_close, 1, 50000); - animation_duration_focus = + config.animation_duration_focus = CLAMP_INT(config.animation_duration_focus, 1, 50000); - - // 滚动布局设置 - scroller_default_proportion = + config.scroller_default_proportion = CLAMP_FLOAT(config.scroller_default_proportion, 0.1f, 1.0f); - scroller_default_proportion_single = + config.scroller_default_proportion_single = CLAMP_FLOAT(config.scroller_default_proportion_single, 0.1f, 1.0f); - scroller_ignore_proportion_single = + config.scroller_ignore_proportion_single = CLAMP_INT(config.scroller_ignore_proportion_single, 0, 1); - scroller_focus_center = CLAMP_INT(config.scroller_focus_center, 0, 1); - scroller_prefer_center = CLAMP_INT(config.scroller_prefer_center, 0, 1); - edge_scroller_pointer_focus = + config.scroller_focus_center = + CLAMP_INT(config.scroller_focus_center, 0, 1); + config.scroller_prefer_center = + CLAMP_INT(config.scroller_prefer_center, 0, 1); + config.scroller_prefer_overspread = + CLAMP_INT(config.scroller_prefer_overspread, 0, 1); + config.edge_scroller_pointer_focus = CLAMP_INT(config.edge_scroller_pointer_focus, 0, 1); - scroller_structs = CLAMP_INT(config.scroller_structs, 0, 1000); - - // 主从布局设置 - default_mfact = CLAMP_FLOAT(config.default_mfact, 0.1f, 0.9f); - default_nmaster = CLAMP_INT(config.default_nmaster, 1, 1000); - center_master_overspread = CLAMP_INT(config.center_master_overspread, 0, 1); - center_when_single_stack = CLAMP_INT(config.center_when_single_stack, 0, 1); - new_is_master = CLAMP_INT(config.new_is_master, 0, 1); - - // 概述模式设置 - hotarea_size = CLAMP_INT(config.hotarea_size, 1, 1000); - hotarea_corner = CLAMP_INT(config.hotarea_corner, 0, 3); - enable_hotarea = CLAMP_INT(config.enable_hotarea, 0, 1); - ov_tab_mode = CLAMP_INT(config.ov_tab_mode, 0, 1); - overviewgappi = CLAMP_INT(config.overviewgappi, 0, 1000); - overviewgappo = CLAMP_INT(config.overviewgappo, 0, 1000); - - // 杂项设置 - xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); - syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1); - allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2); - allow_shortcuts_inhibit = CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1); - allow_lock_transparent = CLAMP_INT(config.allow_lock_transparent, 0, 1); - axis_bind_apply_timeout = + config.edge_scroller_focus_allow_speed = + CLAMP_FLOAT(config.edge_scroller_focus_allow_speed, 0.0f, 1000.0f); + config.scroller_structs = CLAMP_INT(config.scroller_structs, 0, 1000); + config.default_mfact = CLAMP_FLOAT(config.default_mfact, 0.1f, 0.9f); + config.default_nmaster = CLAMP_INT(config.default_nmaster, 1, 1000); + config.center_master_overspread = + CLAMP_INT(config.center_master_overspread, 0, 1); + config.center_when_single_stack = + CLAMP_INT(config.center_when_single_stack, 0, 1); + config.new_is_master = CLAMP_INT(config.new_is_master, 0, 1); + config.dwindle_vsplit = CLAMP_INT(config.dwindle_vsplit, 0, 2); + config.dwindle_hsplit = CLAMP_INT(config.dwindle_hsplit, 0, 2); + config.dwindle_preserve_split = + CLAMP_INT(config.dwindle_preserve_split, 0, 1); + config.dwindle_smart_split = CLAMP_INT(config.dwindle_smart_split, 0, 1); + config.dwindle_smart_resize = CLAMP_INT(config.dwindle_smart_resize, 0, 1); + config.dwindle_drop_simple_split = + CLAMP_INT(config.dwindle_drop_simple_split, 0, 1); + config.dwindle_manual_split = CLAMP_INT(config.dwindle_manual_split, 0, 1); + config.dwindle_split_ratio = + CLAMP_FLOAT(config.dwindle_split_ratio, 0.05f, 0.95f); + config.hotarea_size = CLAMP_INT(config.hotarea_size, 1, 1000); + config.hotarea_corner = CLAMP_INT(config.hotarea_corner, 0, 3); + config.enable_hotarea = CLAMP_INT(config.enable_hotarea, 0, 1); + config.ov_tab_mode = CLAMP_INT(config.ov_tab_mode, 0, 1); + config.ov_no_resize = CLAMP_INT(config.ov_no_resize, 0, 1); + config.overviewgappi = CLAMP_INT(config.overviewgappi, 0, 1000); + config.overviewgappo = CLAMP_INT(config.overviewgappo, 0, 1000); + config.xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); + config.syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1); + config.drag_tile_refresh_interval = + CLAMP_FLOAT(config.drag_tile_refresh_interval, 1.0f, 16.0f); + config.drag_floating_refresh_interval = + CLAMP_FLOAT(config.drag_floating_refresh_interval, 0.0f, 1000.0f); + config.drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); + config.drag_tile_small = CLAMP_INT(config.drag_tile_small, 0, 1); + config.allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2); + config.allow_shortcuts_inhibit = + CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1); + config.allow_lock_transparent = + CLAMP_INT(config.allow_lock_transparent, 0, 1); + config.axis_bind_apply_timeout = CLAMP_INT(config.axis_bind_apply_timeout, 0, 1000); - focus_on_activate = CLAMP_INT(config.focus_on_activate, 0, 1); - idleinhibit_ignore_visible = + config.focus_on_activate = CLAMP_INT(config.focus_on_activate, 0, 1); + config.idleinhibit_ignore_visible = CLAMP_INT(config.idleinhibit_ignore_visible, 0, 1); - sloppyfocus = CLAMP_INT(config.sloppyfocus, 0, 1); - warpcursor = CLAMP_INT(config.warpcursor, 0, 1); - drag_corner = CLAMP_INT(config.drag_corner, 0, 4); - drag_warp_cursor = CLAMP_INT(config.drag_warp_cursor, 0, 1); - focus_cross_monitor = CLAMP_INT(config.focus_cross_monitor, 0, 1); - exchange_cross_monitor = CLAMP_INT(config.exchange_cross_monitor, 0, 1); - scratchpad_cross_monitor = CLAMP_INT(config.scratchpad_cross_monitor, 0, 1); - focus_cross_tag = CLAMP_INT(config.focus_cross_tag, 0, 1); - view_current_to_back = CLAMP_INT(config.view_current_to_back, 0, 1); - enable_floating_snap = CLAMP_INT(config.enable_floating_snap, 0, 1); - snap_distance = CLAMP_INT(config.snap_distance, 0, 99999); - cursor_size = CLAMP_INT(config.cursor_size, 4, 512); - no_border_when_single = CLAMP_INT(config.no_border_when_single, 0, 1); - no_radius_when_single = CLAMP_INT(config.no_radius_when_single, 0, 1); - cursor_hide_timeout = - CLAMP_INT(config.cursor_hide_timeout, 0, 36000); // 0-10小时 - drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); - single_scratchpad = CLAMP_INT(config.single_scratchpad, 0, 1); - - // 键盘设置 - repeat_rate = CLAMP_INT(config.repeat_rate, 1, 1000); - repeat_delay = CLAMP_INT(config.repeat_delay, 1, 20000); - numlockon = CLAMP_INT(config.numlockon, 0, 1); - - // 触控板设置 - disable_trackpad = CLAMP_INT(config.disable_trackpad, 0, 1); - tap_to_click = CLAMP_INT(config.tap_to_click, 0, 1); - tap_and_drag = CLAMP_INT(config.tap_and_drag, 0, 1); - drag_lock = CLAMP_INT(config.drag_lock, 0, 1); - trackpad_natural_scrolling = + config.sloppyfocus = CLAMP_INT(config.sloppyfocus, 0, 1); + config.warpcursor = CLAMP_INT(config.warpcursor, 0, 1); + config.drag_corner = CLAMP_INT(config.drag_corner, 0, 4); + config.drag_warp_cursor = CLAMP_INT(config.drag_warp_cursor, 0, 1); + config.focus_cross_monitor = CLAMP_INT(config.focus_cross_monitor, 0, 1); + config.exchange_cross_monitor = + CLAMP_INT(config.exchange_cross_monitor, 0, 1); + config.scratchpad_cross_monitor = + CLAMP_INT(config.scratchpad_cross_monitor, 0, 1); + config.focus_cross_tag = CLAMP_INT(config.focus_cross_tag, 0, 1); + config.view_current_to_back = CLAMP_INT(config.view_current_to_back, 0, 1); + config.enable_floating_snap = CLAMP_INT(config.enable_floating_snap, 0, 1); + config.snap_distance = CLAMP_INT(config.snap_distance, 0, 99999); + config.cursor_size = CLAMP_INT(config.cursor_size, 4, 512); + config.no_border_when_single = + CLAMP_INT(config.no_border_when_single, 0, 1); + config.no_radius_when_single = + CLAMP_INT(config.no_radius_when_single, 0, 1); + config.cursor_hide_timeout = + CLAMP_INT(config.cursor_hide_timeout, 0, 36000); + config.cursor_hide_on_keypress = + CLAMP_INT(config.cursor_hide_on_keypress, 0, 1); + config.single_scratchpad = CLAMP_INT(config.single_scratchpad, 0, 1); + config.repeat_rate = CLAMP_INT(config.repeat_rate, 1, 1000); + config.repeat_delay = CLAMP_INT(config.repeat_delay, 1, 20000); + config.numlockon = CLAMP_INT(config.numlockon, 0, 1); + config.disable_trackpad = CLAMP_INT(config.disable_trackpad, 0, 1); + config.tap_to_click = CLAMP_INT(config.tap_to_click, 0, 1); + config.tap_and_drag = CLAMP_INT(config.tap_and_drag, 0, 1); + config.drag_lock = CLAMP_INT(config.drag_lock, 0, 1); + config.trackpad_natural_scrolling = CLAMP_INT(config.trackpad_natural_scrolling, 0, 1); - disable_while_typing = CLAMP_INT(config.disable_while_typing, 0, 1); - left_handed = CLAMP_INT(config.left_handed, 0, 1); - middle_button_emulation = CLAMP_INT(config.middle_button_emulation, 0, 1); - swipe_min_threshold = CLAMP_INT(config.swipe_min_threshold, 1, 1000); - - // 鼠标设置 - mouse_natural_scrolling = CLAMP_INT(config.mouse_natural_scrolling, 0, 1); - accel_profile = CLAMP_INT(config.accel_profile, 0, 2); - accel_speed = CLAMP_FLOAT(config.accel_speed, -1.0f, 1.0f); - scroll_method = CLAMP_INT(config.scroll_method, 0, 4); - scroll_button = CLAMP_INT(config.scroll_button, 272, 276); - click_method = CLAMP_INT(config.click_method, 0, 2); - send_events_mode = CLAMP_INT(config.send_events_mode, 0, 2); - button_map = CLAMP_INT(config.button_map, 0, 1); - axis_scroll_factor = CLAMP_FLOAT(config.axis_scroll_factor, 0.1f, 10.0f); - - // 外观设置 - gappih = CLAMP_INT(config.gappih, 0, 1000); - gappiv = CLAMP_INT(config.gappiv, 0, 1000); - gappoh = CLAMP_INT(config.gappoh, 0, 1000); - gappov = CLAMP_INT(config.gappov, 0, 1000); - scratchpad_width_ratio = + config.disable_while_typing = CLAMP_INT(config.disable_while_typing, 0, 1); + config.left_handed = CLAMP_INT(config.left_handed, 0, 1); + config.middle_button_emulation = + CLAMP_INT(config.middle_button_emulation, 0, 1); + config.swipe_min_threshold = CLAMP_INT(config.swipe_min_threshold, 1, 1000); + config.mouse_natural_scrolling = + CLAMP_INT(config.mouse_natural_scrolling, 0, 1); + config.mouse_accel_profile = CLAMP_INT(config.mouse_accel_profile, 0, 2); + config.mouse_accel_speed = + CLAMP_FLOAT(config.mouse_accel_speed, -1.0f, 1.0f); + config.trackpad_accel_profile = + CLAMP_INT(config.trackpad_accel_profile, 0, 2); + config.trackpad_accel_speed = + CLAMP_FLOAT(config.trackpad_accel_speed, -1.0f, 1.0f); + config.scroll_method = CLAMP_INT(config.scroll_method, 0, 4); + config.scroll_button = CLAMP_INT(config.scroll_button, 272, 279); + config.click_method = CLAMP_INT(config.click_method, 0, 2); + config.send_events_mode = CLAMP_INT(config.send_events_mode, 0, 2); + config.button_map = CLAMP_INT(config.button_map, 0, 1); + config.axis_scroll_factor = + CLAMP_FLOAT(config.axis_scroll_factor, 0.1f, 10.0f); + config.trackpad_scroll_factor = + CLAMP_FLOAT(config.trackpad_scroll_factor, 0.1f, 10.0f); + config.gappih = CLAMP_INT(config.gappih, 0, 1000); + config.gappiv = CLAMP_INT(config.gappiv, 0, 1000); + config.gappoh = CLAMP_INT(config.gappoh, 0, 1000); + config.gappov = CLAMP_INT(config.gappov, 0, 1000); + config.scratchpad_width_ratio = CLAMP_FLOAT(config.scratchpad_width_ratio, 0.1f, 1.0f); - scratchpad_height_ratio = + config.scratchpad_height_ratio = CLAMP_FLOAT(config.scratchpad_height_ratio, 0.1f, 1.0f); - borderpx = CLAMP_INT(config.borderpx, 0, 200); - smartgaps = CLAMP_INT(config.smartgaps, 0, 1); + config.borderpx = CLAMP_INT(config.borderpx, 0, 200); + config.tab_bar_height = CLAMP_INT(config.tab_bar_height, 0, 500); + config.smartgaps = CLAMP_INT(config.smartgaps, 0, 1); + config.blur = CLAMP_INT(config.blur, 0, 1); + config.blur_layer = CLAMP_INT(config.blur_layer, 0, 1); + config.blur_optimized = CLAMP_INT(config.blur_optimized, 0, 1); + config.border_radius = CLAMP_INT(config.border_radius, 0, 100); + config.blur_params.num_passes = + CLAMP_INT(config.blur_params.num_passes, 0, 10); + config.blur_params.radius = CLAMP_INT(config.blur_params.radius, 0, 100); + config.blur_params.noise = CLAMP_FLOAT(config.blur_params.noise, 0, 1); + config.blur_params.brightness = + CLAMP_FLOAT(config.blur_params.brightness, 0, 1); + config.blur_params.contrast = + CLAMP_FLOAT(config.blur_params.contrast, 0, 1); + config.blur_params.saturation = + CLAMP_FLOAT(config.blur_params.saturation, 0, 1); + config.shadows = CLAMP_INT(config.shadows, 0, 1); + config.shadow_only_floating = CLAMP_INT(config.shadow_only_floating, 0, 1); + config.layer_shadows = CLAMP_INT(config.layer_shadows, 0, 1); + config.shadows_size = CLAMP_INT(config.shadows_size, 0, 100); + config.shadows_blur = CLAMP_INT(config.shadows_blur, 0, 100); + config.shadows_position_x = + CLAMP_INT(config.shadows_position_x, -1000, 1000); + config.shadows_position_y = + CLAMP_INT(config.shadows_position_y, -1000, 1000); + config.focused_opacity = CLAMP_FLOAT(config.focused_opacity, 0.0f, 1.0f); + config.unfocused_opacity = + CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); - blur = CLAMP_INT(config.blur, 0, 1); - blur_layer = CLAMP_INT(config.blur_layer, 0, 1); - blur_optimized = CLAMP_INT(config.blur_optimized, 0, 1); - border_radius = CLAMP_INT(config.border_radius, 0, 100); - blur_params.num_passes = CLAMP_INT(config.blur_params.num_passes, 0, 10); - blur_params.radius = CLAMP_INT(config.blur_params.radius, 0, 100); - blur_params.noise = CLAMP_FLOAT(config.blur_params.noise, 0, 1); - blur_params.brightness = CLAMP_FLOAT(config.blur_params.brightness, 0, 1); - blur_params.contrast = CLAMP_FLOAT(config.blur_params.contrast, 0, 1); - blur_params.saturation = CLAMP_FLOAT(config.blur_params.saturation, 0, 1); - shadows = CLAMP_INT(config.shadows, 0, 1); - shadow_only_floating = CLAMP_INT(config.shadow_only_floating, 0, 1); - layer_shadows = CLAMP_INT(config.layer_shadows, 0, 1); - shadows_size = CLAMP_INT(config.shadows_size, 0, 100); - shadows_blur = CLAMP_INT(config.shadows_blur, 0, 100); - shadows_position_x = CLAMP_INT(config.shadows_position_x, -1000, 1000); - shadows_position_y = CLAMP_INT(config.shadows_position_y, -1000, 1000); - focused_opacity = CLAMP_FLOAT(config.focused_opacity, 0.0f, 1.0f); - unfocused_opacity = CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); - memcpy(shadowscolor, config.shadowscolor, sizeof(shadowscolor)); + config.tabdata.border_width = + CLAMP_INT(config.tabdata.border_width, 0, 100); + config.tabdata.corner_radius = + CLAMP_INT(config.tabdata.corner_radius, 0, 100); + config.tabdata.padding_x = CLAMP_INT(config.tabdata.padding_x, 0, 100); + config.tabdata.padding_y = CLAMP_INT(config.tabdata.padding_y, 0, 100); - // 复制颜色数组 - memcpy(rootcolor, config.rootcolor, sizeof(rootcolor)); - memcpy(bordercolor, config.bordercolor, sizeof(bordercolor)); - memcpy(focuscolor, config.focuscolor, sizeof(focuscolor)); - memcpy(maximizescreencolor, config.maximizescreencolor, - sizeof(maximizescreencolor)); - memcpy(urgentcolor, config.urgentcolor, sizeof(urgentcolor)); - memcpy(scratchpadcolor, config.scratchpadcolor, sizeof(scratchpadcolor)); - memcpy(globalcolor, config.globalcolor, sizeof(globalcolor)); - memcpy(overlaycolor, config.overlaycolor, sizeof(overlaycolor)); - - // 复制动画曲线 - memcpy(animation_curve_move, config.animation_curve_move, - sizeof(animation_curve_move)); - memcpy(animation_curve_open, config.animation_curve_open, - sizeof(animation_curve_open)); - memcpy(animation_curve_tag, config.animation_curve_tag, - sizeof(animation_curve_tag)); - memcpy(animation_curve_close, config.animation_curve_close, - sizeof(animation_curve_close)); - memcpy(animation_curve_focus, config.animation_curve_focus, - sizeof(animation_curve_focus)); - memcpy(animation_curve_opafadein, config.animation_curve_opafadein, - sizeof(animation_curve_opafadein)); - memcpy(animation_curve_opafadeout, config.animation_curve_opafadeout, - sizeof(animation_curve_opafadeout)); + config.jumplabeldata.border_width = + CLAMP_INT(config.jumplabeldata.border_width, 0, 100); + config.jumplabeldata.corner_radius = + CLAMP_INT(config.jumplabeldata.corner_radius, 0, 100); + config.jumplabeldata.padding_x = + CLAMP_INT(config.jumplabeldata.padding_x, 0, 100); + config.jumplabeldata.padding_y = + CLAMP_INT(config.jumplabeldata.padding_y, 0, 100); } void set_value_default() { - /* animaion */ - config.animations = animations; // 是否启用动画 - config.layer_animations = layer_animations; // 是否启用layer动画 - config.animation_fade_in = animation_fade_in; // Enable animation fade in - config.animation_fade_out = animation_fade_out; // Enable animation fade out - config.tag_animation_direction = tag_animation_direction; // 标签动画方向 - config.zoom_initial_ratio = zoom_initial_ratio; // 动画起始窗口比例 - config.zoom_end_ratio = zoom_end_ratio; // 动画结束窗口比例 - config.fadein_begin_opacity = - fadein_begin_opacity; // Begin opac window ratio for animations - config.fadeout_begin_opacity = fadeout_begin_opacity; - config.animation_duration_move = - animation_duration_move; // Animation move speed - config.animation_duration_open = - animation_duration_open; // Animation open speed - config.animation_duration_tag = - animation_duration_tag; // Animation tag speed - config.animation_duration_close = - animation_duration_close; // Animation tag speed - config.animation_duration_focus = - animation_duration_focus; // Animation focus opacity speed + config.animations = 1; + config.layer_animations = 0; + config.animation_fade_in = 1; + config.animation_fade_out = 1; + config.tag_animation_direction = HORIZONTAL; + config.zoom_initial_ratio = 0.4f; + config.zoom_end_ratio = 0.8f; + config.fadein_begin_opacity = 0.5f; + config.fadeout_begin_opacity = 0.5f; + config.animation_duration_move = 500; + config.animation_duration_open = 400; + config.animation_duration_tag = 300; + config.animation_duration_close = 300; + config.animation_duration_focus = 0; - /* appearance */ - config.axis_bind_apply_timeout = - axis_bind_apply_timeout; // 滚轮绑定动作的触发的时间间隔 - config.focus_on_activate = - focus_on_activate; // 收到窗口激活请求是否自动跳转聚焦 - config.new_is_master = new_is_master; // 新窗口是否插在头部 - config.default_mfact = default_mfact; // master 窗口比例 - config.default_nmaster = default_nmaster; // 默认master数量 - config.center_master_overspread = - center_master_overspread; // 中心master时是否铺满 - config.center_when_single_stack = - center_when_single_stack; // 单个stack时是否居中 + config.axis_bind_apply_timeout = 100; + config.focus_on_activate = 1; + config.new_is_master = 1; + config.default_mfact = 0.55f; + config.default_nmaster = 1; + config.center_master_overspread = 0; + config.center_when_single_stack = 1; - config.numlockon = numlockon; // 是否打开右边小键盘 + config.dwindle_vsplit = 1; + config.dwindle_hsplit = 1; + config.dwindle_preserve_split = 0; + config.dwindle_smart_split = 0; + config.dwindle_smart_resize = 0; + config.dwindle_drop_simple_split = 1; + config.dwindle_manual_split = 0; + config.dwindle_split_ratio = 0.5f; - config.ov_tab_mode = ov_tab_mode; // alt tab切换模式 - config.hotarea_size = hotarea_size; // 热区大小,10x10 - config.hotarea_corner = hotarea_corner; - config.enable_hotarea = enable_hotarea; // 是否启用鼠标热区 - config.smartgaps = smartgaps; /* 1 means no outer gap when there is - only one window */ - config.sloppyfocus = sloppyfocus; /* focus follows mouse */ - config.gappih = gappih; /* horiz inner gap between windows */ - config.gappiv = gappiv; /* vert inner gap between windows */ - config.gappoh = - gappoh; /* horiz outer gap between windows and screen edge */ - config.gappov = gappov; /* vert outer gap between windows and screen edge */ - config.scratchpad_width_ratio = scratchpad_width_ratio; - config.scratchpad_height_ratio = scratchpad_height_ratio; + config.log_level = WLR_ERROR; + config.numlockon = 0; + config.capslock = 0; + config.ov_tab_mode = 1; + config.ov_no_resize = 1; + config.hotarea_size = 10; + config.hotarea_corner = BOTTOM_LEFT; + config.enable_hotarea = 0; + config.smartgaps = 0; + config.sloppyfocus = 1; + config.gappih = 5; + config.gappiv = 5; + config.gappoh = 10; + config.gappov = 10; + config.scratchpad_width_ratio = 0.8f; + config.scratchpad_height_ratio = 0.9f; - config.scroller_structs = scroller_structs; - config.scroller_default_proportion = scroller_default_proportion; - config.scroller_default_proportion_single = - scroller_default_proportion_single; - config.scroller_ignore_proportion_single = - scroller_ignore_proportion_single; - config.scroller_focus_center = scroller_focus_center; - config.scroller_prefer_center = scroller_prefer_center; - config.edge_scroller_pointer_focus = edge_scroller_pointer_focus; - config.focus_cross_monitor = focus_cross_monitor; - config.exchange_cross_monitor = exchange_cross_monitor; - config.scratchpad_cross_monitor = scratchpad_cross_monitor; - config.focus_cross_tag = focus_cross_tag; - config.axis_scroll_factor = axis_scroll_factor; - config.view_current_to_back = view_current_to_back; - config.single_scratchpad = single_scratchpad; - config.xwayland_persistence = xwayland_persistence; - config.syncobj_enable = syncobj_enable; - config.allow_tearing = allow_tearing; - config.allow_shortcuts_inhibit = allow_shortcuts_inhibit; - config.allow_lock_transparent = allow_lock_transparent; - config.no_border_when_single = no_border_when_single; - config.no_radius_when_single = no_radius_when_single; - config.snap_distance = snap_distance; - config.drag_tile_to_tile = drag_tile_to_tile; - config.enable_floating_snap = enable_floating_snap; - config.swipe_min_threshold = swipe_min_threshold; + config.scroller_structs = 20; + config.scroller_default_proportion = 0.9f; + config.scroller_default_proportion_single = 1.0f; + config.scroller_ignore_proportion_single = 1; + config.scroller_focus_center = 0; + config.scroller_prefer_center = 0; + config.scroller_prefer_overspread = 1; + config.edge_scroller_pointer_focus = 1; + config.edge_scroller_focus_allow_speed = 0.0f; + config.focus_cross_monitor = 0; + config.exchange_cross_monitor = 0; + config.scratchpad_cross_monitor = 0; + config.focus_cross_tag = 0; + config.axis_scroll_factor = 1.0; + config.trackpad_scroll_factor = 1.0; + config.view_current_to_back = 0; + config.single_scratchpad = 1; + config.xwayland_persistence = 1; + config.syncobj_enable = 0; + config.tag_carousel = 0; + config.drag_tile_refresh_interval = 8.0f; + config.drag_floating_refresh_interval = 8.0f; + config.allow_tearing = TEARING_DISABLED; + config.allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; + config.allow_lock_transparent = 0; + config.no_border_when_single = 0; + config.no_radius_when_single = 0; + config.snap_distance = 30; + config.drag_tile_to_tile = 0; + config.drag_tile_small = 1; + config.enable_floating_snap = 0; + config.swipe_min_threshold = 1; - config.idleinhibit_ignore_visible = - idleinhibit_ignore_visible; /* 1 means idle inhibitors will - disable idle tracking even if it's - surface isn't visible - */ + config.idleinhibit_ignore_visible = 0; - config.borderpx = borderpx; - config.overviewgappi = overviewgappi; /* overview时 窗口与边缘 缝隙大小 */ - config.overviewgappo = overviewgappo; /* overview时 窗口与窗口 缝隙大小 */ - config.cursor_hide_timeout = cursor_hide_timeout; + config.borderpx = 4; + config.tab_bar_height = 50; + config.overviewgappi = 5; + config.overviewgappo = 30; + config.cursor_hide_timeout = 0; + config.cursor_hide_on_keypress = 0; - config.warpcursor = warpcursor; /* Warp cursor to focused client */ - config.drag_corner = drag_corner; - config.drag_warp_cursor = drag_warp_cursor; + config.warpcursor = 1; + config.drag_corner = 3; + config.drag_warp_cursor = 1; - config.repeat_rate = repeat_rate; - config.repeat_delay = repeat_delay; + config.repeat_rate = 25; + config.repeat_delay = 600; - /* Trackpad */ - config.disable_trackpad = disable_trackpad; - config.tap_to_click = tap_to_click; - config.tap_and_drag = tap_and_drag; - config.drag_lock = drag_lock; - config.mouse_natural_scrolling = mouse_natural_scrolling; - config.cursor_size = cursor_size; - config.trackpad_natural_scrolling = trackpad_natural_scrolling; - config.disable_while_typing = disable_while_typing; - config.left_handed = left_handed; - config.middle_button_emulation = middle_button_emulation; - config.accel_profile = accel_profile; - config.accel_speed = accel_speed; - config.scroll_method = scroll_method; - config.scroll_button = scroll_button; - config.click_method = click_method; - config.send_events_mode = send_events_mode; - config.button_map = button_map; + config.disable_trackpad = 0; + config.tap_to_click = 1; + config.tap_and_drag = 1; + config.drag_lock = 1; + config.mouse_natural_scrolling = 0; + config.cursor_size = 24; + config.trackpad_natural_scrolling = 0; + config.disable_while_typing = 1; + config.left_handed = 0; + config.middle_button_emulation = 0; + config.mouse_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + config.mouse_accel_speed = 0.0; + config.trackpad_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + config.trackpad_accel_speed = 0.0; + config.scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + config.scroll_button = 274; + config.click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + config.send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + config.button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; - config.blur = blur; - config.blur_layer = blur_layer; - config.blur_optimized = blur_optimized; - config.border_radius = border_radius; - config.blur_params.num_passes = blur_params_num_passes; - config.blur_params.radius = blur_params_radius; - config.blur_params.noise = blur_params_noise; - config.blur_params.brightness = blur_params_brightness; - config.blur_params.contrast = blur_params_contrast; - config.blur_params.saturation = blur_params_saturation; - config.shadows = shadows; - config.shadow_only_floating = shadow_only_floating; - config.layer_shadows = layer_shadows; - config.shadows_size = shadows_size; - config.shadows_blur = shadows_blur; - config.shadows_position_x = shadows_position_x; - config.shadows_position_y = shadows_position_y; - config.focused_opacity = focused_opacity; - config.unfocused_opacity = unfocused_opacity; - memcpy(config.shadowscolor, shadowscolor, sizeof(shadowscolor)); + config.blur = 0; + config.blur_layer = 0; + config.blur_optimized = 1; + config.border_radius = 0; + config.border_radius_location_default = CORNER_LOCATION_ALL; + config.blur_params.num_passes = 1; + config.blur_params.radius = 5; + config.blur_params.noise = 0.02f; + config.blur_params.brightness = 0.9f; + config.blur_params.contrast = 0.9f; + config.blur_params.saturation = 1.2f; + config.shadows = 0; + config.shadow_only_floating = 1; + config.layer_shadows = 0; + config.shadows_size = 10; + config.shadows_blur = 15.0f; + config.shadows_position_x = 0; + config.shadows_position_y = 0; + config.focused_opacity = 1.0f; + config.unfocused_opacity = 1.0f; - memcpy(config.animation_curve_move, animation_curve_move, - sizeof(animation_curve_move)); - memcpy(config.animation_curve_open, animation_curve_open, - sizeof(animation_curve_open)); - memcpy(config.animation_curve_tag, animation_curve_tag, - sizeof(animation_curve_tag)); - memcpy(config.animation_curve_close, animation_curve_close, - sizeof(animation_curve_close)); - memcpy(config.animation_curve_focus, animation_curve_focus, - sizeof(animation_curve_focus)); - memcpy(config.animation_curve_opafadein, animation_curve_opafadein, - sizeof(animation_curve_opafadein)); - memcpy(config.animation_curve_opafadeout, animation_curve_opafadeout, - sizeof(animation_curve_opafadeout)); + config.shadowscolor[0] = 0.0f; + config.shadowscolor[1] = 0.0f; + config.shadowscolor[2] = 0.0f; + config.shadowscolor[3] = 1.0f; - memcpy(config.rootcolor, rootcolor, sizeof(rootcolor)); - memcpy(config.bordercolor, bordercolor, sizeof(bordercolor)); - memcpy(config.focuscolor, focuscolor, sizeof(focuscolor)); - memcpy(config.maximizescreencolor, maximizescreencolor, - sizeof(maximizescreencolor)); - memcpy(config.urgentcolor, urgentcolor, sizeof(urgentcolor)); - memcpy(config.scratchpadcolor, scratchpadcolor, sizeof(scratchpadcolor)); - memcpy(config.globalcolor, globalcolor, sizeof(globalcolor)); - memcpy(config.overlaycolor, overlaycolor, sizeof(overlaycolor)); + config.animation_curve_move[0] = 0.46; + config.animation_curve_move[1] = 1.0; + config.animation_curve_move[2] = 0.29; + config.animation_curve_move[3] = 0.99; + config.animation_curve_open[0] = 0.46; + config.animation_curve_open[1] = 1.0; + config.animation_curve_open[2] = 0.29; + config.animation_curve_open[3] = 0.99; + config.animation_curve_tag[0] = 0.46; + config.animation_curve_tag[1] = 1.0; + config.animation_curve_tag[2] = 0.29; + config.animation_curve_tag[3] = 0.99; + config.animation_curve_close[0] = 0.46; + config.animation_curve_close[1] = 1.0; + config.animation_curve_close[2] = 0.29; + config.animation_curve_close[3] = 0.99; + config.animation_curve_focus[0] = 0.46; + config.animation_curve_focus[1] = 1.0; + config.animation_curve_focus[2] = 0.29; + config.animation_curve_focus[3] = 0.99; + config.animation_curve_opafadein[0] = 0.46; + config.animation_curve_opafadein[1] = 1.0; + config.animation_curve_opafadein[2] = 0.29; + config.animation_curve_opafadein[3] = 0.99; + config.animation_curve_opafadeout[0] = 0.5; + config.animation_curve_opafadeout[1] = 0.5; + config.animation_curve_opafadeout[2] = 0.5; + config.animation_curve_opafadeout[3] = 0.5; + + config.tabdata.fg_color[0] = 0xc4 / 255.0f; + config.tabdata.fg_color[1] = 0x93 / 255.0f; + config.tabdata.fg_color[2] = 0x9d / 255.0f; + config.tabdata.fg_color[3] = 1.0f; + config.tabdata.bg_color[0] = 0x32 / 255.0f; + config.tabdata.bg_color[1] = 0x32 / 255.0f; + config.tabdata.bg_color[2] = 0x32 / 255.0f; + config.tabdata.bg_color[3] = 1.0f; + config.tabdata.focus_fg_color[0] = 0xed / 255.0f; + config.tabdata.focus_fg_color[1] = 0xa6 / 255.0f; + config.tabdata.focus_fg_color[2] = 0xb4 / 255.0f; + config.tabdata.focus_fg_color[3] = 1.0f; + config.tabdata.focus_bg_color[0] = 0x4e / 255.0f; + config.tabdata.focus_bg_color[1] = 0x45 / 255.0f; + config.tabdata.focus_bg_color[2] = 0x3c / 255.0f; + config.tabdata.focus_bg_color[3] = 1.0f; + config.tabdata.border_color[0] = 0x8b / 255.0f; + config.tabdata.border_color[1] = 0xaa / 255.0f; + config.tabdata.border_color[2] = 0x9b / 255.0f; + config.tabdata.border_color[3] = 1.0f; + config.tabdata.border_width = 4; + config.tabdata.corner_radius = 5; + config.tabdata.padding_x = 0; + config.tabdata.padding_y = 0; + + config.jumplabeldata.fg_color[0] = 0xc4 / 255.0f; + config.jumplabeldata.fg_color[1] = 0x93 / 255.0f; + config.jumplabeldata.fg_color[2] = 0x9d / 255.0f; + config.jumplabeldata.fg_color[3] = 1.0f; + config.jumplabeldata.bg_color[0] = 0x32 / 255.0f; + config.jumplabeldata.bg_color[1] = 0x32 / 255.0f; + config.jumplabeldata.bg_color[2] = 0x32 / 255.0f; + config.jumplabeldata.bg_color[3] = 1.0f; + config.jumplabeldata.focus_fg_color[0] = 0xed / 255.0f; + config.jumplabeldata.focus_fg_color[1] = 0xa6 / 255.0f; + config.jumplabeldata.focus_fg_color[2] = 0xb4 / 255.0f; + config.jumplabeldata.focus_fg_color[3] = 1.0f; + config.jumplabeldata.focus_bg_color[0] = 0x4e / 255.0f; + config.jumplabeldata.focus_bg_color[1] = 0x45 / 255.0f; + config.jumplabeldata.focus_bg_color[2] = 0x3c / 255.0f; + config.jumplabeldata.focus_bg_color[3] = 1.0f; + config.jumplabeldata.border_color[0] = 0x8b / 255.0f; + config.jumplabeldata.border_color[1] = 0xaa / 255.0f; + config.jumplabeldata.border_color[2] = 0x9b / 255.0f; + config.jumplabeldata.border_color[3] = 1.0f; + config.jumplabeldata.border_width = 4; + config.jumplabeldata.corner_radius = 5; + config.jumplabeldata.padding_x = 10; + config.jumplabeldata.padding_y = 10; + + config.rootcolor[0] = 0x32 / 255.0f; + config.rootcolor[1] = 0x32 / 255.0f; + config.rootcolor[2] = 0x32 / 255.0f; + config.rootcolor[3] = 1.0f; + config.bordercolor[0] = 0x44 / 255.0f; + config.bordercolor[1] = 0x44 / 255.0f; + config.bordercolor[2] = 0x44 / 255.0f; + config.bordercolor[3] = 1.0f; + config.dropcolor[0] = 0xd5 / 255.0f; + config.dropcolor[1] = 0x89 / 255.0f; + config.dropcolor[2] = 0x9d / 255.0f; + config.dropcolor[3] = 0.5f; + config.splitcolor[0] = 0xeb / 255.0f; + config.splitcolor[1] = 0x44 / 255.0f; + config.splitcolor[2] = 0x1e / 255.0f; + config.splitcolor[3] = 1.0f; + config.focuscolor[0] = 0xc6 / 255.0f; + config.focuscolor[1] = 0x6b / 255.0f; + config.focuscolor[2] = 0x25 / 255.0f; + config.focuscolor[3] = 1.0f; + config.maximizescreencolor[0] = 0x89 / 255.0f; + config.maximizescreencolor[1] = 0xaa / 255.0f; + config.maximizescreencolor[2] = 0x61 / 255.0f; + config.maximizescreencolor[3] = 1.0f; + config.urgentcolor[0] = 0xad / 255.0f; + config.urgentcolor[1] = 0x40 / 255.0f; + config.urgentcolor[2] = 0x1f / 255.0f; + config.urgentcolor[3] = 1.0f; + config.scratchpadcolor[0] = 0x51 / 255.0f; + config.scratchpadcolor[1] = 0x6c / 255.0f; + config.scratchpadcolor[2] = 0x93 / 255.0f; + config.scratchpadcolor[3] = 1.0f; + config.globalcolor[0] = 0xb1 / 255.0f; + config.globalcolor[1] = 0x53 / 255.0f; + config.globalcolor[2] = 0xa7 / 255.0f; + config.globalcolor[3] = 1.0f; + config.overlaycolor[0] = 0x14 / 255.0f; + config.overlaycolor[1] = 0xa5 / 255.0f; + config.overlaycolor[2] = 0x7c / 255.0f; + config.overlaycolor[3] = 1.0f; } void set_default_key_bindings(Config *config) { @@ -3438,13 +3918,14 @@ bool parse_config(void) { free_config(); - // 重置config结构体,确保所有指针初始化为NULL memset(&config, 0, sizeof(config)); - memset(&xkb_rules_rules, 0, sizeof(xkb_rules_rules)); - memset(&xkb_rules_model, 0, sizeof(xkb_rules_model)); - memset(&xkb_rules_layout, 0, sizeof(xkb_rules_layout)); - memset(&xkb_rules_variant, 0, sizeof(xkb_rules_variant)); - memset(&xkb_rules_options, 0, sizeof(xkb_rules_options)); + + // 重新将xkb_rules指针指向静态数组 + config.xkb_rules.layout = config.xkb_rules_layout; + config.xkb_rules.variant = config.xkb_rules_variant; + config.xkb_rules.options = config.xkb_rules_options; + config.xkb_rules.rules = config.xkb_rules_rules; + config.xkb_rules.model = config.xkb_rules_model; // 初始化动态数组的指针为NULL,避免野指针 config.window_rules = NULL; @@ -3474,6 +3955,9 @@ bool parse_config(void) { config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; + config.jumplabeldata.font_desc = NULL; + config.tabdata.font_desc = NULL; + config.tablet_map_to_mon = NULL; strcpy(config.keymode, "default"); create_config_keymap(); @@ -3508,7 +3992,7 @@ bool parse_config(void) { } void reset_blur_params(void) { - if (blur) { + if (config.blur) { Monitor *m = NULL; wl_list_for_each(m, &mons, link) { if (m->blur != NULL) { @@ -3518,9 +4002,9 @@ void reset_blur_params(void) { wlr_scene_node_reparent(&m->blur->node, layers[LyrBlur]); wlr_scene_optimized_blur_set_size(m->blur, m->m.width, m->m.height); wlr_scene_set_blur_data( - scene, blur_params.num_passes, blur_params.radius, - blur_params.noise, blur_params.brightness, blur_params.contrast, - blur_params.saturation); + scene, config.blur_params.num_passes, config.blur_params.radius, + config.blur_params.noise, config.blur_params.brightness, + config.blur_params.contrast, config.blur_params.saturation); } } else { Monitor *m = NULL; @@ -3537,17 +4021,15 @@ void reset_blur_params(void) { void reapply_monitor_rules(void) { ConfigMonitorRule *mr; Monitor *m = NULL; - int32_t ji, vrr; + int32_t ji, vrr, custom; int32_t mx, my; struct wlr_output_state state; - struct wlr_output_mode *internal_mode = NULL; - wlr_output_state_init(&state); - bool match_rule = false; wl_list_for_each(m, &mons, link) { - if (!m->wlr_output->enabled) { + if (!m->wlr_output->enabled) continue; - } + + wlr_output_state_init(&state); for (ji = 0; ji < config.monitor_rules_count; ji++) { if (config.monitor_rules_count < 1) @@ -3555,78 +4037,43 @@ void reapply_monitor_rules(void) { mr = &config.monitor_rules[ji]; - // 检查是否匹配的变量 - match_rule = true; - - // 检查四个标识字段的匹配 - if (mr->name != NULL) { - if (!regex_match(mr->name, m->wlr_output->name)) { - match_rule = false; - } - } - - if (mr->make != NULL) { - if (m->wlr_output->make == NULL || - strcmp(mr->make, m->wlr_output->make) != 0) { - match_rule = false; - } - } - - if (mr->model != NULL) { - if (m->wlr_output->model == NULL || - strcmp(mr->model, m->wlr_output->model) != 0) { - match_rule = false; - } - } - - if (mr->serial != NULL) { - if (m->wlr_output->serial == NULL || - strcmp(mr->serial, m->wlr_output->serial) != 0) { - match_rule = false; - } - } - - // 只有当所有指定的标识都匹配时才应用规则 - if (match_rule) { + if (monitor_matches_rule(m, mr)) { mx = mr->x == INT32_MAX ? m->m.x : mr->x; my = mr->y == INT32_MAX ? m->m.y : mr->y; vrr = mr->vrr >= 0 ? mr->vrr : 0; + custom = mr->custom >= 0 ? mr->custom : 0; - if (mr->width > 0 && mr->height > 0 && mr->refresh > 0) { - internal_mode = get_nearest_output_mode( - m->wlr_output, mr->width, mr->height, mr->refresh); - if (internal_mode) { - wlr_output_state_set_mode(&state, internal_mode); - } else if (wlr_output_is_headless(m->wlr_output)) { - wlr_output_state_set_custom_mode( - &state, mr->width, mr->height, - (int32_t)roundf(mr->refresh * 1000)); - } - } - - if (vrr) { - enable_adaptive_sync(m, &state); - } else { - wlr_output_state_set_adaptive_sync_enabled(&state, false); - } - - wlr_output_state_set_scale(&state, mr->scale); - wlr_output_state_set_transform(&state, mr->rr); + (void)apply_rule_to_state(m, mr, &state, vrr, custom); wlr_output_layout_add(output_layout, m->wlr_output, mx, my); + wlr_output_commit_state(m->wlr_output, &state); + break; } } - wlr_output_commit_state(m->wlr_output, &state); wlr_output_state_finish(&state); - updatemons(NULL, NULL); + } + updatemons(NULL, NULL); +} + +void set_xcursor_env() { + if (config.cursor_size > 0) { + char size_str[16]; + snprintf(size_str, sizeof(size_str), "%d", config.cursor_size); + setenv("XCURSOR_SIZE", size_str, 1); + } else { + setenv("XCURSOR_SIZE", "24", 1); + } + + if (config.cursor_theme) { + setenv("XCURSOR_THEME", config.cursor_theme, 1); } } void reapply_cursor_style(void) { - if (hide_source) { - wl_event_source_timer_update(hide_source, 0); - wl_event_source_remove(hide_source); - hide_source = NULL; + if (hide_cursor_source) { + wl_event_source_timer_update(hide_cursor_source, 0); + wl_event_source_remove(hide_cursor_source); + hide_cursor_source = NULL; } wlr_cursor_unset_image(cursor); @@ -3638,7 +4085,10 @@ void reapply_cursor_style(void) { cursor_mgr = NULL; } - cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); + set_xcursor_env(); + + cursor_mgr = + wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); Monitor *m = NULL; wl_list_for_each(m, &mons, link) { @@ -3647,26 +4097,37 @@ void reapply_cursor_style(void) { wlr_cursor_set_xcursor(cursor, cursor_mgr, "left_ptr"); - hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), - hidecursor, cursor); + hide_cursor_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + hidecursor, cursor); if (cursor_hidden) { wlr_cursor_unset_image(cursor); } else { - wl_event_source_timer_update(hide_source, cursor_hide_timeout * 1000); + wl_event_source_timer_update(hide_cursor_source, + config.cursor_hide_timeout * 1000); } } -void reapply_rootbg(void) { wlr_scene_rect_set_color(root_bg, rootcolor); } +void reapply_rootbg(void) { + wlr_scene_rect_set_color(root_bg, config.rootcolor); +} -void reapply_border(void) { +void reapply_property(void) { Client *c = NULL; // reset border width when config change wl_list_for_each(c, &clients, link) { if (c && !c->iskilling) { if (!c->isnoborder && !c->isfullscreen) { - c->bw = borderpx; + c->bw = config.borderpx; } + + mango_jump_label_node_apply_config(c->jump_label_node, + &config.jumplabeldata); + mango_tab_bar_node_apply_config(c->tab_bar_node, &config.tabdata); + + wlr_scene_rect_set_color(c->droparea, config.dropcolor); + wlr_scene_rect_set_color(c->splitindicator[0], config.splitcolor); + wlr_scene_rect_set_color(c->splitindicator[1], config.splitcolor); } } } @@ -3678,7 +4139,7 @@ void reapply_keyboard(void) { continue; } wlr_keyboard_set_repeat_info((struct wlr_keyboard *)id->device_data, - repeat_rate, repeat_delay); + config.repeat_rate, config.repeat_delay); } } @@ -3707,12 +4168,12 @@ void reapply_master(void) { if (!m->wlr_output->enabled) { continue; } - m->pertag->nmasters[i] = default_nmaster; - m->pertag->mfacts[i] = default_mfact; - m->gappih = gappih; - m->gappiv = gappiv; - m->gappoh = gappoh; - m->gappov = gappov; + m->pertag->nmasters[i] = config.default_nmaster; + m->pertag->mfacts[i] = config.default_mfact; + m->gappih = config.gappih; + m->gappiv = config.gappiv; + m->gappoh = config.gappoh; + m->gappov = config.gappov; } } } @@ -3724,8 +4185,14 @@ void parse_tagrule(Monitor *m) { bool match_rule = false; for (i = 0; i <= LENGTH(tags); i++) { - m->pertag->nmasters[i] = default_nmaster; - m->pertag->mfacts[i] = default_mfact; + m->pertag->nmasters[i] = config.default_nmaster; + m->pertag->mfacts[i] = config.default_mfact; + m->pertag->scroller_default_proportion[i] = + config.scroller_default_proportion; + m->pertag->scroller_default_proportion_single[i] = + config.scroller_default_proportion_single; + m->pertag->scroller_ignore_proportion_single[i] = + config.scroller_ignore_proportion_single; } for (i = 0; i < config.tag_rules_count; i++) { @@ -3778,6 +4245,17 @@ void parse_tagrule(Monitor *m) { m->pertag->mfacts[tr.id] = tr.mfact; if (tr.no_render_border >= 0) m->pertag->no_render_border[tr.id] = tr.no_render_border; + if (tr.open_as_floating >= 0) + m->pertag->open_as_floating[tr.id] = tr.open_as_floating; + if (tr.scroller_default_proportion > 0.0f) + m->pertag->scroller_default_proportion[tr.id] = + tr.scroller_default_proportion; + if (tr.scroller_default_proportion_single > 0.0f) + m->pertag->scroller_default_proportion_single[tr.id] = + tr.scroller_default_proportion_single; + if (tr.scroller_ignore_proportion_single >= 0) + m->pertag->scroller_ignore_proportion_single[tr.id] = + tr.scroller_ignore_proportion_single; } } @@ -3810,7 +4288,7 @@ void reset_option(void) { run_exec(); reapply_cursor_style(); - reapply_border(); + reapply_property(); reapply_rootbg(); reapply_keyboard(); reapply_pointer(); @@ -3825,6 +4303,6 @@ void reset_option(void) { int32_t reload_config(const Arg *arg) { parse_config(); reset_option(); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 1; } diff --git a/src/config/preset.h b/src/config/preset.h index 58e2f779..6952518e 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -1,131 +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_tile_refresh_interval = 16.0; -double drag_floating_refresh_interval = 8.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, @@ -133,105 +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 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); diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index 22ef6123..6960b001 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -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); \ No newline at end of file +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); \ No newline at end of file diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 1d29bc97..e7cfd020 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1,8 +1,9 @@ int32_t bind_to_view(const Arg *arg) { - + if (!selmon) + return 0; uint32_t target = arg->ui; - if (view_current_to_back && selmon->pertag->curtag && + if (config.view_current_to_back && selmon->pertag->curtag && (target & TAGMASK) == (selmon->tagset[selmon->seltags])) { if (selmon->pertag->prevtag) target = 1 << (selmon->pertag->prevtag - 1); @@ -10,13 +11,13 @@ int32_t bind_to_view(const Arg *arg) { target = 0; } - if (!view_current_to_back && + if (!config.view_current_to_back && (target & TAGMASK) == (selmon->tagset[selmon->seltags])) { return 0; } if ((int32_t)target == INT_MIN && selmon->pertag->curtag == 0) { - if (view_current_to_back && selmon->pertag->prevtag) + if (config.view_current_to_back && selmon->pertag->prevtag) target = 1 << (selmon->pertag->prevtag - 1); else target = 0; @@ -33,10 +34,8 @@ int32_t bind_to_view(const Arg *arg) { int32_t chvt(const Arg *arg) { struct timespec ts; - // prevent the animation to rquest the new frame allow_frame_scheduling = false; - // backup current tag and monitor name if (selmon) { chvt_backup_tag = selmon->pertag->curtag; strncpy(chvt_backup_selmon, selmon->wlr_output->name, @@ -45,19 +44,15 @@ int32_t chvt(const Arg *arg) { wlr_session_change_vt(session, arg->ui); - // wait for DRM device to stabilize and ensure the session state is inactive ts.tv_sec = 0; - ts.tv_nsec = 100000000; // 200ms + ts.tv_nsec = 100000000; nanosleep(&ts, NULL); - // allow frame scheduling, - // because session state is now inactive, rendermon will not enter allow_frame_scheduling = true; return 1; } int32_t create_virtual_output(const Arg *arg) { - if (!wlr_backend_is_multi(backend)) { wlr_log(WLR_ERROR, "Expected a multi backend"); return 0; @@ -76,7 +71,6 @@ int32_t create_virtual_output(const Arg *arg) { } int32_t destroy_all_virtual_output(const Arg *arg) { - if (!wlr_backend_is_multi(backend)) { wlr_log(WLR_ERROR, "Expected a multi backend"); return 0; @@ -85,8 +79,6 @@ int32_t destroy_all_virtual_output(const Arg *arg) { Monitor *m, *tmp; wl_list_for_each_safe(m, tmp, &mons, link) { if (wlr_output_is_headless(m->wlr_output)) { - // if(selmon == m) - // selmon = NULL; wlr_output_destroy(m->wlr_output); wlr_log(WLR_INFO, "Virtual output destroyed"); } @@ -95,24 +87,35 @@ int32_t destroy_all_virtual_output(const Arg *arg) { } int32_t defaultgaps(const Arg *arg) { - setgaps(gappoh, gappov, gappih, gappiv); + setgaps(config.gappoh, config.gappov, config.gappih, config.gappiv); return 0; } int32_t exchange_client(const Arg *arg) { - Client *c = selmon->sel; + if (!selmon) + return 0; + Client *c = arg->tc ? arg->tc : selmon->sel; if (!c || c->isfloating) return 0; if ((c->isfullscreen || c->ismaximizescreen) && !is_scroller_layout(c->mon)) return 0; - exchange_two_client(c, direction_select(arg)); + Client *tc = direction_select(arg); + tc = get_focused_stack_client(tc, arg->tc); + + if (!tc) + return 0; + + exchange_two_client(c, tc); return 0; } int32_t exchange_stack_client(const Arg *arg) { - Client *c = selmon->sel; + if (!selmon) + return 0; + + Client *c = arg->tc ? arg->tc : selmon->sel; Client *tc = NULL; if (!c || c->isfloating || c->isfullscreen || c->ismaximizescreen) return 0; @@ -127,12 +130,18 @@ int32_t exchange_stack_client(const Arg *arg) { } int32_t focusdir(const Arg *arg) { + + if (!selmon) + return 0; + Client *c = NULL; c = direction_select(arg); - c = get_focused_stack_client(c); + + if (!selmon->isoverview) + c = get_focused_stack_client(c, arg->tc); if (c) { focusclient(c, 1); - if (warpcursor) + if (config.warpcursor) warp_cursor(c); } else { if (config.focus_cross_tag) { @@ -148,7 +157,6 @@ int32_t focusdir(const Arg *arg) { } int32_t focuslast(const Arg *arg) { - Client *c = NULL; Client *tc = NULL; bool begin = false; @@ -188,7 +196,7 @@ int32_t focuslast(const Arg *arg) { } int32_t toggle_trackpad_enable(const Arg *arg) { - disable_trackpad = !disable_trackpad; + config.disable_trackpad = !config.disable_trackpad; return 0; } @@ -204,7 +212,7 @@ int32_t focusmon(const Arg *arg) { if (!m->wlr_output->enabled) { continue; } - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { tm = m; break; } @@ -217,10 +225,10 @@ int32_t focusmon(const Arg *arg) { return 0; selmon = tm; - if (warpcursor) { + if (config.warpcursor) { warp_cursor_to_selmon(selmon); } - c = focustop(selmon); + c = arg->tc ? arg->tc : focustop(selmon); if (!c) { selmon->sel = NULL; wlr_seat_pointer_notify_clear_focus(seat); @@ -233,8 +241,7 @@ int32_t focusmon(const Arg *arg) { } int32_t focusstack(const Arg *arg) { - /* Focus the next or previous client (in tiling order) on selmon */ - Client *sel = focustop(selmon); + Client *sel = arg->tc ? arg->tc : focustop(selmon); Client *tc = NULL; if (!sel) @@ -244,13 +251,12 @@ int32_t focusstack(const Arg *arg) { } else { tc = get_next_stack_client(sel, true); } - /* If only one client is visible on selmon, then c == sel */ if (!tc) return 0; focusclient(tc, 1); - if (warpcursor) + if (config.warpcursor) warp_cursor(tc); return 0; } @@ -259,48 +265,62 @@ int32_t incnmaster(const Arg *arg) { if (!arg || !selmon) return 0; selmon->pertag->nmasters[selmon->pertag->curtag] = - MAX(selmon->pertag->nmasters[selmon->pertag->curtag] + arg->i, 0); + MANGO_MAX(selmon->pertag->nmasters[selmon->pertag->curtag] + arg->i, 0); arrange(selmon, false, false); return 0; } int32_t incgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh + arg->i, selmon->gappov + arg->i, selmon->gappih + arg->i, selmon->gappiv + arg->i); return 0; } int32_t incigaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov, selmon->gappih + arg->i, selmon->gappiv + arg->i); return 0; } int32_t incogaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh + arg->i, selmon->gappov + arg->i, selmon->gappih, selmon->gappiv); return 0; } int32_t incihgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov, selmon->gappih + arg->i, selmon->gappiv); return 0; } int32_t incivgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov, selmon->gappih, selmon->gappiv + arg->i); return 0; } int32_t incohgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh + arg->i, selmon->gappov, selmon->gappih, selmon->gappiv); return 0; } int32_t incovgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov + arg->i, selmon->gappih, selmon->gappiv); return 0; @@ -329,10 +349,13 @@ int32_t setmfact(const Arg *arg) { } int32_t killclient(const Arg *arg) { - Client *c = NULL; - c = selmon->sel; + Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); if (c) { - pending_kill_client(c); + if (arg->i == FORCE) { + client_pending_force_kill(c); + } else { + pending_kill_client(c); + } } return 0; } @@ -349,28 +372,36 @@ int32_t moveresize(const Arg *arg) { grabc = NULL; return 0; } - /* Float the window and tell motionnotify to grab it */ if (grabc->isfloating == 0 && arg->ui == CurMove) { grabc->drag_to_tile = true; + exit_scroller_stack(grabc); setfloating(grabc, 1); + grabc->drag_tile_float_backup_geom = grabc->float_geom; + grabc->old_stack_inner_per = 0.0f; + grabc->old_master_inner_per = 0.0f; + set_size_per(grabc->mon, grabc); + } + + if (grabc && grabc->drag_to_tile && config.drag_tile_small) { + grabc->geom.x = cursor->x - 150; + grabc->geom.y = cursor->y - 150; + grabc->geom.width = 300; + grabc->geom.height = 300; + resize(grabc, grabc->geom, 1); } switch (cursor_mode = arg->ui) { case CurMove: - grabcx = cursor->x - grabc->geom.x; grabcy = cursor->y - grabc->geom.y; wlr_cursor_set_xcursor(cursor, cursor_mgr, "grab"); break; case CurResize: - /* Doesn't work for X11 output - the next absolute motion event - * returns the cursor to where it started */ if (grabc->isfloating) { - rzcorner = drag_corner; + rzcorner = config.drag_corner; grabcx = (int)round(cursor->x); grabcy = (int)round(cursor->y); if (rzcorner == 4) - /* identify the closest corner index */ rzcorner = (grabcx - grabc->geom.x < grabc->geom.x + grabc->geom.width - grabcx ? 0 @@ -380,7 +411,7 @@ int32_t moveresize(const Arg *arg) { ? 0 : 2); - if (drag_warp_cursor) { + if (config.drag_warp_cursor) { grabcx = rzcorner & 1 ? grabc->geom.x + grabc->geom.width : grabc->geom.x; grabcy = rzcorner & 2 ? grabc->geom.y + grabc->geom.height @@ -398,12 +429,11 @@ int32_t moveresize(const Arg *arg) { } int32_t movewin(const Arg *arg) { - Client *c = NULL; - c = selmon->sel; + Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); if (!c || c->isfullscreen) return 0; if (!c->isfloating) - togglefloating(NULL); + setfloating(c, 1); switch (arg->ui) { case NUM_TYPE_MINUS: @@ -441,16 +471,15 @@ int32_t quit(const Arg *arg) { } int32_t resizewin(const Arg *arg) { - Client *c = NULL; - c = selmon->sel; + Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); int32_t offsetx = 0, offsety = 0; if (!c || c->isfullscreen || c->ismaximizescreen) return 0; - int32_t animations_state_backup = animations; + int32_t animations_state_backup = config.animations; if (!c->isfloating) - animations = 0; + config.animations = 0; if (ISTILED(c)) { switch (arg->ui) { @@ -477,7 +506,7 @@ int32_t resizewin(const Arg *arg) { break; } resize_tile_client(c, false, offsetx, offsety, 0); - animations = animations_state_backup; + config.animations = animations_state_backup; return 0; } @@ -508,23 +537,22 @@ int32_t resizewin(const Arg *arg) { c->iscustomsize = 1; c->float_geom = c->geom; resize(c, c->geom, 0); - animations = animations_state_backup; + config.animations = animations_state_backup; return 0; } int32_t restore_minimized(const Arg *arg) { - Client *c = NULL; + Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); if (selmon && selmon->isoverview) return 0; - if (selmon && selmon->sel && selmon->sel->is_in_scratchpad && - selmon->sel->is_scratchpad_show) { - selmon->sel->isminimized = 0; - selmon->sel->is_scratchpad_show = 0; - selmon->sel->is_in_scratchpad = 0; - selmon->sel->isnamedscratchpad = 0; - setborder_color(selmon->sel); + if (c && c->is_in_scratchpad && c->is_scratchpad_show) { + client_pending_minimized_state(c, 0); + c->is_scratchpad_show = 0; + c->is_in_scratchpad = 0; + c->isnamedscratchpad = 0; + setborder_color(c); return 0; } @@ -546,13 +574,15 @@ int32_t restore_minimized(const Arg *arg) { int32_t setlayout(const Arg *arg) { int32_t jk; + if (!selmon) + return 0; for (jk = 0; jk < LENGTH(layouts); jk++) { if (strcmp(layouts[jk].name, arg->v) == 0) { selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[jk]; clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false, false); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } } @@ -566,29 +596,116 @@ int32_t setkeymode(const Arg *arg) { } else { keymode.isdefault = false; } - printstatus(); + printstatus(IPC_WATCH_KEYMODE); return 1; } int32_t set_proportion(const Arg *arg) { + if (!selmon) + return 0; if (selmon->isoverview || !is_scroller_layout(selmon)) return 0; if (selmon->visible_tiling_clients == 1 && - !scroller_ignore_proportion_single) + !config.scroller_ignore_proportion_single) return 0; - Client *tc = selmon->sel; + Client *tc = arg->tc ? arg->tc : selmon->sel; + if (!tc) + return 0; - if (tc) { - tc = get_scroll_stack_head(tc); - uint32_t max_client_width = - selmon->w.width - 2 * scroller_structs - gappih; - tc->scroller_proportion = arg->f; - tc->geom.width = max_client_width * arg->f; - arrange(selmon, false, false); + tc = scroll_get_stack_head_client(tc); + if (!tc) + return 0; + + Monitor *m = tc->mon; + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = m->pertag->scroller_state[tag]; + struct ScrollerStackNode *node = NULL; + + if (st) + node = find_scroller_node(st, tc); + + if (node) + node->scroller_proportion = arg->f; + tc->scroller_proportion = arg->f; + + uint32_t max_client_width = + m->w.width - 2 * config.scroller_structs - config.gappih; + tc->geom.width = max_client_width * arg->f; + + arrange(m, false, false); + return 0; +} + +int32_t switch_proportion_preset(const Arg *arg) { + float target_proportion = 0; + if (!selmon) + return 0; + + if (config.scroller_proportion_preset_count == 0) + return 0; + + if (selmon->isoverview || !is_scroller_layout(selmon)) + return 0; + + if (selmon->visible_tiling_clients == 1 && + !config.scroller_ignore_proportion_single) + return 0; + + Client *tc = arg->tc ? arg->tc : selmon->sel; + if (!tc) + return 0; + + tc = scroll_get_stack_head_client(tc); + if (!tc) + return 0; + + Monitor *m = tc->mon; + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = m->pertag->scroller_state[tag]; + struct ScrollerStackNode *node = NULL; + + if (st) + node = find_scroller_node(st, tc); + + float current_proportion = + node ? node->scroller_proportion : tc->scroller_proportion; + + for (int32_t i = 0; i < config.scroller_proportion_preset_count; i++) { + if (config.scroller_proportion_preset[i] == current_proportion) { + if (arg->i == NEXT) { + if (i == config.scroller_proportion_preset_count - 1) + target_proportion = config.scroller_proportion_preset[0]; + else + target_proportion = + config.scroller_proportion_preset[i + 1]; + } else { + if (i == 0) + target_proportion = + config.scroller_proportion_preset + [config.scroller_proportion_preset_count - 1]; + else + target_proportion = + config.scroller_proportion_preset[i - 1]; + } + break; + } } + + if (target_proportion == 0.0f) + target_proportion = config.scroller_proportion_preset[0]; + + if (node) + node->scroller_proportion = target_proportion; + tc->scroller_proportion = target_proportion; + + uint32_t max_client_width = + m->w.width - 2 * config.scroller_structs - config.gappih; + tc->geom.width = max_client_width * target_proportion; + + arrange(m, false, false); return 0; } @@ -596,11 +713,13 @@ int32_t smartmovewin(const Arg *arg) { Client *c = NULL, *tc = NULL; int32_t nx, ny; int32_t buttom, top, left, right, tar; - c = selmon->sel; + if (!selmon) + return 0; + c = arg->tc ? arg->tc : selmon->sel; if (!c || c->isfullscreen) return 0; if (!c->isfloating) - setfloating(selmon->sel, true); + setfloating(c, true); nx = c->geom.x; ny = c->geom.y; @@ -616,14 +735,14 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.x + c->geom.width < tc->geom.x || c->geom.x > tc->geom.x + tc->geom.width) continue; - buttom = tc->geom.y + tc->geom.height + gappiv; + buttom = tc->geom.y + tc->geom.height + config.gappiv; if (top > buttom && ny < buttom) { - tar = MAX(tar, buttom); + tar = MANGO_MAX(tar, buttom); }; } ny = tar == -99999 ? ny : tar; - ny = MAX(ny, c->mon->w.y + c->mon->gappov); + ny = MANGO_MAX(ny, c->mon->w.y + c->mon->gappov); break; case DOWN: tar = 99999; @@ -636,14 +755,14 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.x + c->geom.width < tc->geom.x || c->geom.x > tc->geom.x + tc->geom.width) continue; - top = tc->geom.y - gappiv; + top = tc->geom.y - config.gappiv; if (buttom < top && (ny + c->geom.height) > top) { - tar = MIN(tar, top - c->geom.height); + tar = MANGO_MIN(tar, top - c->geom.height); }; } ny = tar == 99999 ? ny : tar; - ny = MIN(ny, c->mon->w.y + c->mon->w.height - c->geom.height - - c->mon->gappov); + ny = MANGO_MIN(ny, c->mon->w.y + c->mon->w.height - c->geom.height - + c->mon->gappov); break; case LEFT: tar = -99999; @@ -656,14 +775,14 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.y + c->geom.height < tc->geom.y || c->geom.y > tc->geom.y + tc->geom.height) continue; - right = tc->geom.x + tc->geom.width + gappih; + right = tc->geom.x + tc->geom.width + config.gappih; if (left > right && nx < right) { - tar = MAX(tar, right); + tar = MANGO_MAX(tar, right); }; } nx = tar == -99999 ? nx : tar; - nx = MAX(nx, c->mon->w.x + c->mon->gappoh); + nx = MANGO_MAX(nx, c->mon->w.x + c->mon->gappoh); break; case RIGHT: tar = 99999; @@ -675,14 +794,14 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.y + c->geom.height < tc->geom.y || c->geom.y > tc->geom.y + tc->geom.height) continue; - left = tc->geom.x - gappih; + left = tc->geom.x - config.gappih; if (right < left && (nx + c->geom.width) > left) { - tar = MIN(tar, left - c->geom.width); + tar = MANGO_MIN(tar, left - c->geom.width); }; } nx = tar == 99999 ? nx : tar; - nx = MIN(nx, c->mon->w.x + c->mon->w.width - c->geom.width - - c->mon->gappoh); + nx = MANGO_MIN(nx, c->mon->w.x + c->mon->w.width - c->geom.width - + c->mon->gappoh); break; } @@ -697,7 +816,9 @@ int32_t smartresizewin(const Arg *arg) { Client *c = NULL, *tc = NULL; int32_t nw, nh; int32_t buttom, top, left, right, tar; - c = selmon->sel; + if (!selmon) + return 0; + c = arg->tc ? arg->tc : selmon->sel; if (!c || c->isfullscreen) return 0; if (!c->isfloating) @@ -708,7 +829,7 @@ int32_t smartresizewin(const Arg *arg) { switch (arg->i) { case UP: nh -= selmon->w.height / 8; - nh = MAX(nh, selmon->w.height / 10); + nh = MANGO_MAX(nh, selmon->w.height / 10); break; case DOWN: tar = -99999; @@ -721,18 +842,18 @@ int32_t smartresizewin(const Arg *arg) { if (c->geom.x + c->geom.width < tc->geom.x || c->geom.x > tc->geom.x + tc->geom.width) continue; - top = tc->geom.y - gappiv; + top = tc->geom.y - config.gappiv; if (buttom < top && (nh + c->geom.y) > top) { - tar = MAX(tar, top - c->geom.y); + tar = MANGO_MAX(tar, top - c->geom.y); }; } nh = tar == -99999 ? nh : tar; - if (c->geom.y + nh + gappov > selmon->w.y + selmon->w.height) - nh = selmon->w.y + selmon->w.height - c->geom.y - gappov; + if (c->geom.y + nh + config.gappov > selmon->w.y + selmon->w.height) + nh = selmon->w.y + selmon->w.height - c->geom.y - config.gappov; break; case LEFT: nw -= selmon->w.width / 16; - nw = MAX(nw, selmon->w.width / 10); + nw = MANGO_MAX(nw, selmon->w.width / 10); break; case RIGHT: tar = 99999; @@ -744,15 +865,15 @@ int32_t smartresizewin(const Arg *arg) { if (c->geom.y + c->geom.height < tc->geom.y || c->geom.y > tc->geom.y + tc->geom.height) continue; - left = tc->geom.x - gappih; + left = tc->geom.x - config.gappih; if (right < left && (nw + c->geom.x) > left) { - tar = MIN(tar, left - c->geom.x); + tar = MANGO_MIN(tar, left - c->geom.x); }; } nw = tar == 99999 ? nw : tar; - if (c->geom.x + nw + gappoh > selmon->w.x + selmon->w.width) - nw = selmon->w.x + selmon->w.width - c->geom.x - gappoh; + if (c->geom.x + nw + config.gappoh > selmon->w.x + selmon->w.width) + nw = selmon->w.x + selmon->w.width - c->geom.x - config.gappoh; break; } @@ -764,8 +885,7 @@ int32_t smartresizewin(const Arg *arg) { } int32_t centerwin(const Arg *arg) { - Client *c = NULL; - c = selmon->sel; + Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); if (!c || c->isfullscreen || c->ismaximizescreen) return 0; @@ -780,7 +900,7 @@ int32_t centerwin(const Arg *arg) { if (!is_scroller_layout(selmon)) return 0; - Client *stack_head = get_scroll_stack_head(c); + Client *stack_head = scroll_get_stack_head_client(c); if (selmon->pertag->ltidxs[selmon->pertag->curtag]->id == SCROLLER) { stack_head->geom.x = selmon->w.x + (selmon->w.width - stack_head->geom.width) / 2; @@ -798,64 +918,63 @@ int32_t spawn_shell(const Arg *arg) { return 0; if (fork() == 0) { - // 1. 忽略可能导致 coredump 的信号 - signal(SIGSEGV, SIG_IGN); - signal(SIGABRT, SIG_IGN); - signal(SIGILL, SIG_IGN); + signal(SIGSEGV, SIG_DFL); + signal(SIGABRT, SIG_DFL); + signal(SIGILL, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + int fd_max = sysconf(_SC_OPEN_MAX); + for (int i = 3; i < fd_max; i++) { + close(i); + } dup2(STDERR_FILENO, STDOUT_FILENO); setsid(); execlp("sh", "sh", "-c", arg->v, (char *)NULL); - - // fallback to bash execlp("bash", "bash", "-c", arg->v, (char *)NULL); - // if execlp fails, we should not reach here - wlr_log(WLR_ERROR, + wlr_log(WLR_DEBUG, "mango: failed to execute command '%s' with shell: %s\n", - arg->v, strerror(errno)); + (char *)arg->v, strerror(errno)); _exit(EXIT_FAILURE); } return 0; } int32_t spawn(const Arg *arg) { - if (!arg->v) return 0; if (fork() == 0) { - // 1. 忽略可能导致 coredump 的信号 - signal(SIGSEGV, SIG_IGN); - signal(SIGABRT, SIG_IGN); - signal(SIGILL, SIG_IGN); + signal(SIGSEGV, SIG_DFL); + signal(SIGABRT, SIG_DFL); + signal(SIGILL, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + // close all file descriptors inherited from the parent process to + // prevent IPC handle leakage that can block clients + int fd_max = sysconf(_SC_OPEN_MAX); + for (int i = 3; i < fd_max; i++) { + close(i); + } dup2(STDERR_FILENO, STDOUT_FILENO); setsid(); - // 2. 解析参数 - char *argv[64]; - int32_t argc = 0; - char *token = strtok((char *)arg->v, " "); - while (token != NULL && argc < 63) { - wordexp_t p; - if (wordexp(token, &p, 0) == 0) { - argv[argc++] = p.we_wordv[0]; - } else { - argv[argc++] = token; - } - token = strtok(NULL, " "); + wordexp_t p; + if (wordexp(arg->v, &p, 0) != 0) { + wlr_log(WLR_DEBUG, "mango: wordexp failed for '%s'\n", + (char *)arg->v); + _exit(EXIT_FAILURE); } - argv[argc] = NULL; - // 3. 执行命令 - execvp(argv[0], argv); + execvp(p.we_wordv[0], p.we_wordv); - // 4. execvp 失败时:打印错误并直接退出(避免 coredump) - wlr_log(WLR_ERROR, "mango: execvp '%s' failed: %s\n", argv[0], + wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", p.we_wordv[0], strerror(errno)); - _exit(EXIT_FAILURE); // 使用 _exit 避免缓冲区刷新等操作 + wordfree(&p); + _exit(EXIT_FAILURE); } return 0; } @@ -892,7 +1011,6 @@ int32_t switch_keyboard_layout(const Arg *arg) { return 0; } - // 1. 获取当前布局和计算下一个布局 xkb_layout_index_t current = xkb_state_serialize_layout( keyboard->xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); const int32_t num_layouts = xkb_keymap_num_layouts(keyboard->keymap); @@ -900,16 +1018,20 @@ int32_t switch_keyboard_layout(const Arg *arg) { wlr_log(WLR_INFO, "Only one layout available"); return 0; } - xkb_layout_index_t next = (current + 1) % num_layouts; - // 6. 应用新 keymap + xkb_layout_index_t next = 0; + if (arg->i > 0 && arg->i <= num_layouts) { + next = arg->i - 1; + } else { + next = (current + 1) % num_layouts; + } + uint32_t depressed = keyboard->modifiers.depressed; uint32_t latched = keyboard->modifiers.latched; uint32_t locked = keyboard->modifiers.locked; wlr_keyboard_notify_modifiers(keyboard, depressed, latched, locked, next); - // 7. 更新 seat wlr_seat_set_keyboard(seat, keyboard); wlr_seat_keyboard_notify_modifiers(seat, &keyboard->modifiers); @@ -922,12 +1044,11 @@ int32_t switch_keyboard_layout(const Arg *arg) { struct wlr_keyboard *tkb = (struct wlr_keyboard *)id->device_data; wlr_keyboard_notify_modifiers(tkb, depressed, latched, locked, next); - // 7. 更新 seat wlr_seat_set_keyboard(seat, tkb); wlr_seat_keyboard_notify_modifiers(seat, &tkb->modifiers); } - printstatus(); + printstatus(IPC_WATCH_KB_LAYOUT); return 0; } @@ -937,10 +1058,13 @@ int32_t switch_layout(const Arg *arg) { char *target_layout_name = NULL; uint32_t len; + if (!selmon) + return 0; + if (config.circle_layout_count != 0) { for (jk = 0; jk < config.circle_layout_count; jk++) { - len = MAX( + len = MANGO_MAX( strlen(config.circle_layout[jk]), strlen(selmon->pertag->ltidxs[selmon->pertag->curtag]->name)); @@ -959,7 +1083,8 @@ int32_t switch_layout(const Arg *arg) { } for (ji = 0; ji < LENGTH(layouts); ji++) { - len = MAX(strlen(layouts[ji].name), strlen(target_layout_name)); + len = + MANGO_MAX(strlen(layouts[ji].name), strlen(target_layout_name)); if (strncmp(layouts[ji].name, target_layout_name, len) == 0) { selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[ji]; @@ -968,7 +1093,7 @@ int32_t switch_layout(const Arg *arg) { } clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false, false); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } @@ -979,71 +1104,32 @@ int32_t switch_layout(const Arg *arg) { jk == LENGTH(layouts) - 1 ? &layouts[0] : &layouts[jk + 1]; clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false, false); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } } return 0; } -int32_t switch_proportion_preset(const Arg *arg) { - float target_proportion = 0; - - if (config.scroller_proportion_preset_count == 0) { - return 0; - } - - if (selmon->isoverview || !is_scroller_layout(selmon)) - return 0; - - if (selmon->visible_tiling_clients == 1 && - !scroller_ignore_proportion_single) - return 0; - - Client *tc = selmon->sel; - - if (tc) { - tc = get_scroll_stack_head(tc); - for (int32_t i = 0; i < config.scroller_proportion_preset_count; i++) { - if (config.scroller_proportion_preset[i] == - tc->scroller_proportion) { - if (i == config.scroller_proportion_preset_count - 1) { - target_proportion = config.scroller_proportion_preset[0]; - break; - } else { - target_proportion = - config.scroller_proportion_preset[i + 1]; - break; - } - } - } - - if (target_proportion == 0) { - target_proportion = config.scroller_proportion_preset[0]; - } - - uint32_t max_client_width = - selmon->w.width - 2 * scroller_structs - gappih; - tc->scroller_proportion = target_proportion; - tc->geom.width = max_client_width * target_proportion; - arrange(selmon, false, false); - } - return 0; -} - int32_t tag(const Arg *arg) { - Client *target_client = selmon->sel; + if (!selmon) + return 0; + Client *target_client = arg->tc ? arg->tc : selmon->sel; tag_client(arg, target_client); return 0; } int32_t tagmon(const Arg *arg) { - Monitor *m = NULL, *cm = NULL; - Client *c = focustop(selmon); + Monitor *m = NULL, *cm = NULL, *oldmon = NULL; + if (!selmon) + return 0; + Client *c = arg->tc ? arg->tc : focustop(selmon); if (!c) return 0; + oldmon = c->mon; + if (arg->i != UNDIR) { m = dirtomon(arg->i); } else if (arg->v) { @@ -1051,7 +1137,7 @@ int32_t tagmon(const Arg *arg) { if (!cm->wlr_output->enabled) { continue; } - if (regex_match(arg->v, cm->wlr_output->name)) { + if (match_monitor_spec(arg->v, cm)) { m = cm; break; } @@ -1071,24 +1157,22 @@ int32_t tagmon(const Arg *arg) { return 0; } - if (c == selmon->sel) { - selmon->sel = NULL; + if (c == oldmon->sel) { + oldmon->sel = NULL; } setmon(c, m, newtags, true); client_update_oldmonname_record(c, m); - reset_foreign_tolevel(c); + reset_foreign_tolevel(c, oldmon, c->mon); c->float_geom.width = - (int32_t)(c->float_geom.width * c->mon->w.width / selmon->w.width); + (int32_t)(c->float_geom.width * c->mon->w.width / oldmon->w.width); c->float_geom.height = - (int32_t)(c->float_geom.height * c->mon->w.height / selmon->w.height); + (int32_t)(c->float_geom.height * c->mon->w.height / oldmon->w.height); selmon = c->mon; c->float_geom = setclient_coordinate_center(c, c->mon, c->float_geom, 0, 0); - // 重新计算居中的坐标 - // 重新计算居中的坐标 if (c->isfloating) { c->geom = c->float_geom; target = get_tags_first_tag(c->tags); @@ -1102,7 +1186,7 @@ int32_t tagmon(const Arg *arg) { focusclient(c, 1); arrange(selmon, false, false); } - if (warpcursor) { + if (config.warpcursor) { warp_cursor_to_selmon(c->mon); } return 0; @@ -1110,12 +1194,11 @@ int32_t tagmon(const Arg *arg) { int32_t tagsilent(const Arg *arg) { Client *fc = NULL; - Client *target_client = NULL; + Client *target_client = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); - if (!selmon || !selmon->sel) + if (!target_client) return 0; - target_client = selmon->sel; target_client->tags = arg->ui & TAGMASK; wl_list_for_each(fc, &clients, link) { if (fc && fc != target_client && target_client->tags & fc->tags && @@ -1123,26 +1206,53 @@ int32_t tagsilent(const Arg *arg) { clear_fullscreen_flag(fc); } } - exit_scroller_stack(target_client); focusclient(focustop(selmon), 1); arrange(target_client->mon, false, false); return 0; } int32_t tagtoleft(const Arg *arg) { - if (selmon->sel != NULL && - __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1 && - selmon->tagset[selmon->seltags] > 1) { - tag(&(Arg){.ui = selmon->tagset[selmon->seltags] >> 1, .i = arg->i}); + if (!selmon) + return 0; + + Client *sel = arg->tc ? arg->tc : selmon->sel; + if (sel != NULL && + __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1) { + uint32_t target = selmon->tagset[selmon->seltags] >> 1; + + if (target == 0) { + if (!config.tag_carousel) + return 0; + target = (1 << (LENGTH(tags) - 1)) & TAGMASK; + selmon->carousel_anim_dir = -1; + } + + Arg a = {.ui = target & TAGMASK, .i = arg->i, .tc = sel}; + tag(&a); + selmon->carousel_anim_dir = 0; } return 0; } int32_t tagtoright(const Arg *arg) { - if (selmon->sel != NULL && - __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1 && - selmon->tagset[selmon->seltags] & (TAGMASK >> 1)) { - tag(&(Arg){.ui = selmon->tagset[selmon->seltags] << 1, .i = arg->i}); + if (!selmon) + return 0; + + Client *sel = arg->tc ? arg->tc : selmon->sel; + if (sel != NULL && + __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1) { + uint32_t target = selmon->tagset[selmon->seltags] << 1; + + if (!(target & TAGMASK)) { + if (!config.tag_carousel) + return 0; + target = 1; + selmon->carousel_anim_dir = 1; + } + + Arg a = {.ui = target & TAGMASK, .i = arg->i, .tc = sel}; + tag(&a); + selmon->carousel_anim_dir = 0; } return 0; } @@ -1152,6 +1262,9 @@ int32_t toggle_named_scratchpad(const Arg *arg) { char *arg_id = arg->v; char *arg_title = arg->v2; + if (selmon && selmon->isoverview) + return 0; + target_client = get_client_by_id_or_title(arg_id, arg_title); if (!target_client && arg->v3) { @@ -1161,12 +1274,13 @@ int32_t toggle_named_scratchpad(const Arg *arg) { } target_client->isnamedscratchpad = 1; - apply_named_scratchpad(target_client); return 0; } int32_t toggle_render_border(const Arg *arg) { + if (!selmon) + return 0; render_border = !render_border; arrange(selmon, false, false); return 0; @@ -1181,11 +1295,12 @@ int32_t toggle_scratchpad(const Arg *arg) { return 0; wl_list_for_each_safe(c, tmp, &clients, link) { - if (!scratchpad_cross_monitor && c->mon != selmon) { + if (!config.scratchpad_cross_monitor && c->mon != selmon) { continue; } - if (single_scratchpad && c->isnamedscratchpad && !c->isminimized) { + if (config.single_scratchpad && c->isnamedscratchpad && + !c->isminimized) { set_minimized(c); continue; } @@ -1202,14 +1317,19 @@ int32_t toggle_scratchpad(const Arg *arg) { } int32_t togglefakefullscreen(const Arg *arg) { - Client *sel = focustop(selmon); + if (!selmon) + return 0; + Client *sel = arg->tc ? arg->tc : focustop(selmon); if (sel) setfakefullscreen(sel, !sel->isfakefullscreen); return 0; } int32_t togglefloating(const Arg *arg) { - Client *sel = focustop(selmon); + if (!selmon) + return 0; + + Client *sel = arg->tc ? arg->tc : focustop(selmon); if (selmon && selmon->isoverview) return 0; @@ -1217,18 +1337,23 @@ int32_t togglefloating(const Arg *arg) { if (!sel) return 0; + bool isfloating = sel->isfloating; + if ((sel->isfullscreen || sel->ismaximizescreen)) { - sel->isfloating = 1; + isfloating = 1; } else { - sel->isfloating = !sel->isfloating; + isfloating = !sel->isfloating; } - setfloating(sel, sel->isfloating); + setfloating(sel, isfloating); return 0; } int32_t togglefullscreen(const Arg *arg) { - Client *sel = focustop(selmon); + if (!selmon) + return 0; + + Client *sel = arg->tc ? arg->tc : focustop(selmon); if (!sel) return 0; @@ -1237,38 +1362,44 @@ int32_t togglefullscreen(const Arg *arg) { sel->isnamedscratchpad = 0; if (sel->isfullscreen) - setfullscreen(sel, 0); + setfullscreen(sel, 0, true); else - setfullscreen(sel, 1); + setfullscreen(sel, 1, true); return 0; } int32_t toggleglobal(const Arg *arg) { - if (!selmon->sel) + if (!selmon) return 0; - if (selmon->sel->is_in_scratchpad) { - selmon->sel->is_in_scratchpad = 0; - selmon->sel->is_scratchpad_show = 0; - selmon->sel->isnamedscratchpad = 0; + + Client *c = arg->tc ? arg->tc : selmon->sel; + if (!c) + return 0; + + if (c->is_in_scratchpad) { + c->is_in_scratchpad = 0; + c->is_scratchpad_show = 0; + c->isnamedscratchpad = 0; } - selmon->sel->isglobal ^= 1; - if (selmon->sel->isglobal && - (selmon->sel->prev_in_stack || selmon->sel->next_in_stack)) { - exit_scroller_stack(selmon->sel); - arrange(selmon, false, false); - } - setborder_color(selmon->sel); + c->isglobal ^= 1; + setborder_color(c); return 0; } int32_t togglegaps(const Arg *arg) { + if (!selmon) + return 0; + enablegaps ^= 1; arrange(selmon, false, false); return 0; } int32_t togglemaximizescreen(const Arg *arg) { - Client *sel = focustop(selmon); + if (!selmon) + return 0; + + Client *sel = arg->tc ? arg->tc : focustop(selmon); if (!sel) return 0; @@ -1277,38 +1408,44 @@ int32_t togglemaximizescreen(const Arg *arg) { sel->isnamedscratchpad = 0; if (sel->ismaximizescreen) - setmaximizescreen(sel, 0); + setmaximizescreen(sel, 0, true); else - setmaximizescreen(sel, 1); + setmaximizescreen(sel, 1, true); setborder_color(sel); return 0; } int32_t toggleoverlay(const Arg *arg) { - if (!selmon->sel || !selmon->sel->mon || selmon->sel->isfullscreen) { + if (!selmon) + return 0; + + Client *c = arg->tc ? arg->tc : selmon->sel; + if (!c || !c->mon || c->isfullscreen) { return 0; } - selmon->sel->isoverlay ^= 1; + c->isoverlay ^= 1; - if (selmon->sel->isoverlay) { - wlr_scene_node_reparent(&selmon->sel->scene->node, layers[LyrOverlay]); - wlr_scene_node_raise_to_top(&selmon->sel->scene->node); - } else if (client_should_overtop(selmon->sel) && selmon->sel->isfloating) { - wlr_scene_node_reparent(&selmon->sel->scene->node, layers[LyrTop]); + if (c->isoverlay) { + wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]); + wlr_scene_node_raise_to_top(&c->scene->node); + } else if (client_should_overtop(c) && c->isfloating) { + wlr_scene_node_reparent(&c->scene->node, layers[LyrTop]); } else { - wlr_scene_node_reparent( - &selmon->sel->scene->node, - layers[selmon->sel->isfloating ? LyrTop : LyrTile]); + wlr_scene_node_reparent(&c->scene->node, + layers[c->isfloating ? LyrTop : LyrTile]); } - setborder_color(selmon->sel); + setborder_color(c); return 0; } int32_t toggletag(const Arg *arg) { + if (!selmon) + return 0; + uint32_t newtags; - Client *sel = focustop(selmon); + Client *sel = arg->tc ? arg->tc : focustop(selmon); if (!sel) return 0; @@ -1325,76 +1462,97 @@ int32_t toggletag(const Arg *arg) { focusclient(focustop(selmon), 1); arrange(selmon, false, false); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } int32_t toggleview(const Arg *arg) { + if (!selmon) + return 0; + uint32_t newtagset; uint32_t target; + Client *c = NULL; target = arg->ui == 0 ? ~0 & TAGMASK : arg->ui; - newtagset = - selmon ? selmon->tagset[selmon->seltags] ^ (target & TAGMASK) : 0; + newtagset = selmon->tagset[selmon->seltags] ^ (target & TAGMASK); if (newtagset) { selmon->tagset[selmon->seltags] = newtagset; focusclient(focustop(selmon), 1); + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, selmon) && ISTILED(c)) { + set_size_per(selmon, c); + } + } arrange(selmon, false, false); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } int32_t viewtoleft(const Arg *arg) { - uint32_t target = selmon->tagset[selmon->seltags]; - - if (selmon->isoverview || selmon->pertag->curtag == 0) { + if (!selmon) return 0; - } + if (selmon->isoverview || selmon->pertag->curtag == 0) + return 0; + + uint32_t target = selmon->tagset[selmon->seltags]; target >>= 1; if (target == 0) { - return 0; + if (!config.tag_carousel) + return 0; + target = (1 << (LENGTH(tags) - 1)) & TAGMASK; + selmon->carousel_anim_dir = -1; } - if (!selmon || (target) == selmon->tagset[selmon->seltags]) + if (target == selmon->tagset[selmon->seltags]) return 0; view(&(Arg){.ui = target & TAGMASK, .i = arg->i}, true); + selmon->carousel_anim_dir = 0; return 0; } int32_t viewtoright(const Arg *arg) { - if (selmon->isoverview || selmon->pertag->curtag == 0) { + if (!selmon) return 0; - } + + if (selmon->isoverview || selmon->pertag->curtag == 0) + return 0; + uint32_t target = selmon->tagset[selmon->seltags]; target <<= 1; - if (!selmon || (target) == selmon->tagset[selmon->seltags]) - return 0; if (!(target & TAGMASK)) { - return 0; + if (!config.tag_carousel) + return 0; + target = 1; + selmon->carousel_anim_dir = 1; } + if (target == selmon->tagset[selmon->seltags]) + return 0; + view(&(Arg){.ui = target & TAGMASK, .i = arg->i}, true); + selmon->carousel_anim_dir = 0; return 0; } int32_t viewtoleft_have_client(const Arg *arg) { + if (!selmon) + return 0; + + if (selmon->isoverview) + return 0; + uint32_t n; uint32_t current = get_tags_first_tag_num(selmon->tagset[selmon->seltags]); bool found = false; - - if (selmon->isoverview) { - return 0; - } - - if (current <= 1) - return 0; + bool wrapped = false; for (n = current - 1; n >= 1; n--) { if (get_tag_status(n, selmon)) { @@ -1403,22 +1561,36 @@ int32_t viewtoleft_have_client(const Arg *arg) { } } - if (found) + if (!found && config.tag_carousel) { + for (n = LENGTH(tags); n > current; n--) { + if (get_tag_status(n, selmon)) { + found = true; + wrapped = true; + break; + } + } + } + + if (found) { + if (wrapped) + selmon->carousel_anim_dir = -1; view(&(Arg){.ui = (1 << (n - 1)) & TAGMASK, .i = arg->i}, true); + selmon->carousel_anim_dir = 0; + } return 0; } int32_t viewtoright_have_client(const Arg *arg) { + if (!selmon) + return 0; + + if (selmon->isoverview) + return 0; + uint32_t n; uint32_t current = get_tags_first_tag_num(selmon->tagset[selmon->seltags]); bool found = false; - - if (selmon->isoverview) { - return 0; - } - - if (current >= LENGTH(tags)) - return 0; + bool wrapped = false; for (n = current + 1; n <= LENGTH(tags); n++) { if (get_tag_status(n, selmon)) { @@ -1427,27 +1599,49 @@ int32_t viewtoright_have_client(const Arg *arg) { } } - if (found) + if (!found && config.tag_carousel) { + for (n = 1; n < current; n++) { + if (get_tag_status(n, selmon)) { + found = true; + wrapped = true; + break; + } + } + } + + if (found) { + if (wrapped) + selmon->carousel_anim_dir = 1; view(&(Arg){.ui = (1 << (n - 1)) & TAGMASK, .i = arg->i}, true); + selmon->carousel_anim_dir = 0; + } return 0; } int32_t viewcrossmon(const Arg *arg) { + if (!selmon) + return 0; + focusmon(&(Arg){.v = arg->v, .i = UNDIR}); view_in_mon(arg, true, selmon, true); return 0; } int32_t tagcrossmon(const Arg *arg) { - if (!selmon || !selmon->sel) + if (!selmon) return 0; - if (regex_match(selmon->wlr_output->name, arg->v)) { - tag_client(arg, selmon->sel); + Client *c = arg->tc ? arg->tc : selmon->sel; + if (!c) + return 0; + + if (match_monitor_spec(arg->v, selmon)) { + tag_client(arg, c); return 0; } - tagmon(&(Arg){.ui = arg->ui, .i = UNDIR, .v = arg->v}); + Arg a = {.ui = arg->ui, .i = UNDIR, .v = arg->v, .tc = c}; + tagmon(&a); return 0; } @@ -1466,20 +1660,18 @@ int32_t comboview(const Arg *arg) { view(&(Arg){.ui = newtags}, false); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } int32_t zoom(const Arg *arg) { - Client *c = NULL, *sel = focustop(selmon); + Client *c = NULL, *sel = arg->tc ? arg->tc : focustop(selmon); if (!sel || !selmon || !selmon->pertag->ltidxs[selmon->pertag->curtag]->arrange || sel->isfloating) return 0; - /* Search for the first tiled window that is not sel, marking sel as - * NULL if we pass it along the way */ wl_list_for_each(c, &clients, link) if (VISIBLEON(c, selmon) && !c->isfloating) { if (c != sel) @@ -1487,12 +1679,9 @@ int32_t zoom(const Arg *arg) { sel = NULL; } - /* Return if no other tiled window was found */ if (&c->link == &clients) return 0; - /* If we passed sel, move c to the front; otherwise, move sel to the - * front */ if (!sel) sel = c; wl_list_remove(&sel->link); @@ -1511,20 +1700,38 @@ int32_t setoption(const Arg *arg) { } int32_t minimized(const Arg *arg) { + if (!selmon) + return 0; if (selmon && selmon->isoverview) return 0; - if (selmon->sel && !selmon->sel->isminimized) { - set_minimized(selmon->sel); + Client *c = arg->tc ? arg->tc : selmon->sel; + if (c && !c->isminimized) { + set_minimized(c); } return 0; } +void fix_mon_tagset_from_overview(Monitor *m) { + if (m->tagset[m->seltags] == (m->ovbk_prev_tagset & TAGMASK)) { + m->tagset[m->seltags ^ 1] = m->ovbk_current_tagset; + m->pertag->prevtag = get_tags_first_tag_num(m->ovbk_current_tagset); + } else { + m->tagset[m->seltags ^ 1] = m->ovbk_prev_tagset; + m->pertag->prevtag = get_tags_first_tag_num(m->ovbk_prev_tagset); + } +} + int32_t toggleoverview(const Arg *arg) { Client *c = NULL; + if (!selmon) + return 0; - if (selmon->isoverview && ov_tab_mode && arg->i != 1 && selmon->sel) { + Client *sel = arg->tc ? arg->tc : selmon->sel; + + if (selmon->isoverview && config.ov_tab_mode && !selmon->is_jump_mode && + arg->i != 1 && sel) { focusstack(&(Arg){.i = 1}); return 0; } @@ -1533,6 +1740,10 @@ int32_t toggleoverview(const Arg *arg) { uint32_t target; uint32_t visible_client_number = 0; + if (!selmon->isoverview && selmon->is_jump_mode) { + finish_jump_mode(selmon); + } + if (selmon->isoverview) { wl_list_for_each(c, &clients, link) if (c && c->mon == selmon && !client_is_unmanaged(c) && @@ -1542,40 +1753,73 @@ int32_t toggleoverview(const Arg *arg) { visible_client_number++; } if (visible_client_number > 0) { + selmon->ovbk_current_tagset = selmon->tagset[selmon->seltags]; + selmon->ovbk_prev_tagset = selmon->tagset[selmon->seltags ^ 1]; target = ~0 & TAGMASK; } else { selmon->isoverview ^= 1; return 0; } - } else if (!selmon->isoverview && selmon->sel) { - target = get_tags_first_tag(selmon->sel->tags); - } else if (!selmon->isoverview && !selmon->sel) { + } else if (!selmon->isoverview && sel) { + target = get_tags_first_tag(sel->tags); + } else if (!selmon->isoverview && !sel) { target = (1 << (selmon->pertag->prevtag - 1)); view(&(Arg){.ui = target}, false); + fix_mon_tagset_from_overview(selmon); refresh_monitors_workspaces_status(selmon); return 0; } - // 正常视图到overview,退出所有窗口的浮动和全屏状态参与平铺, - // overview到正常视图,还原之前退出的浮动和全屏窗口状态 if (selmon->isoverview) { + wlr_seat_pointer_clear_focus(seat); + + if (cursor_hidden) { + handlecursoractivity(); + } else { + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + } + wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !client_is_unmanaged(c) && - !client_is_x11_popup(c) && !c->isunglobal) + !client_is_x11_popup(c) && !c->isunglobal && !c->isminimized && + client_surface(c)->mapped) { + c->animation.overining = true; overview_backup(c); + } } } else { + + selmon->tagset[selmon->seltags] = target; wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !c->iskilling && - !client_is_unmanaged(c) && !c->isunglobal && - !client_is_x11_popup(c) && client_surface(c)->mapped) + !client_is_unmanaged(c) && !c->isunglobal && !c->isminimized && + !client_is_x11_popup(c) && client_surface(c)->mapped) { overview_restore(c, &(Arg){.ui = target}); + } } } view(&(Arg){.ui = target}, false); - + fix_mon_tagset_from_overview(selmon); refresh_monitors_workspaces_status(selmon); + + return 0; +} + +int32_t togglejump(const Arg *arg) { + if (!selmon) + return 0; + + if (!selmon->isoverview) { + begin_jump_mode(selmon); + toggleoverview(arg); + return 0; + } + + if (selmon->isoverview) { + toggleoverview(arg); + } + return 0; } @@ -1583,7 +1827,7 @@ int32_t disable_monitor(const Arg *arg) { Monitor *m = NULL; struct wlr_output_state state = {0}; wl_list_for_each(m, &mons, link) { - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { wlr_output_state_set_enabled(&state, false); wlr_output_commit_state(m->wlr_output, &state); m->asleep = 1; @@ -1598,7 +1842,7 @@ int32_t enable_monitor(const Arg *arg) { Monitor *m = NULL; struct wlr_output_state state = {0}; wl_list_for_each(m, &mons, link) { - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { wlr_output_state_set_enabled(&state, true); wlr_output_commit_state(m->wlr_output, &state); m->asleep = 0; @@ -1613,7 +1857,7 @@ int32_t toggle_monitor(const Arg *arg) { Monitor *m = NULL; struct wlr_output_state state = {0}; wl_list_for_each(m, &mons, link) { - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { wlr_output_state_set_enabled(&state, !m->wlr_output->enabled); wlr_output_commit_state(m->wlr_output, &state); m->asleep = !m->wlr_output->enabled; @@ -1624,105 +1868,178 @@ int32_t toggle_monitor(const Arg *arg) { return 0; } +int32_t scroller_apply_stack(Client *c, Client *target_client, + int32_t direction) { + if (!c || !c->mon || c->isfloating || !is_scroller_layout(c->mon)) + return 0; + + Monitor *m = c->mon; + uint32_t tag = m->pertag->curtag; + + bool is_horizontal = (m->pertag->ltidxs[tag]->id == SCROLLER); + + if (is_horizontal && (direction == UP || direction == DOWN)) + return 0; + if (!is_horizontal && (direction == LEFT || direction == RIGHT)) + return 0; + + struct TagScrollerState *st = ensure_scroller_state(m, tag); + + struct ScrollerStackNode *cnode = find_scroller_node(st, c); + + if (!cnode) + return 0; + + struct ScrollerStackNode *tnode = + target_client ? find_scroller_node(st, target_client) : NULL; + + if (direction == UNDIR && target_client && target_client->mon == c->mon) { + scroller_insert_stack(c, target_client, false); + return 0; + } + + if (cnode->prev_in_stack || cnode->next_in_stack) { + struct ScrollerStackNode *move_out_refer_node = + cnode->prev_in_stack ? cnode->prev_in_stack : cnode->next_in_stack; + scroller_node_remove(st, cnode); + + update_scroller_state(c->mon); + + Client *stack_head = + scroll_get_stack_head_client(move_out_refer_node->client); + Client *stack_tail = + scroll_get_stack_tail_client(move_out_refer_node->client); + + if (direction == LEFT || direction == UP) { + if (c != stack_head) { + wl_list_remove(&c->link); + wl_list_insert(stack_head->link.prev, &c->link); + } + } else if (direction == RIGHT || direction == DOWN) { + if (c != stack_tail) { + wl_list_remove(&c->link); + wl_list_insert(&stack_tail->link, &c->link); + } + } + sync_scroller_state_to_clients(m, tag); + arrange(m, false, false); + return 0; + } + + if (!tnode || target_client->mon != c->mon) + return 0; + + struct ScrollerStackNode *tail = tnode; + while (tail->next_in_stack) + tail = tail->next_in_stack; + + scroller_insert_stack(c, tail->client, false); + + if (c != tail->client) { + wl_list_remove(&c->link); + wl_list_insert(&tail->client->link, &c->link); + } + return 0; +} + int32_t scroller_stack(const Arg *arg) { - Client *c = selmon->sel; - Client *stack_head = NULL; - Client *source_stack_head = NULL; + if (!selmon) + return 0; + Client *c = arg->tc ? arg->tc : selmon->sel; if (!c || !c->mon || c->isfloating || !is_scroller_layout(selmon)) return 0; - if (c && (!client_only_in_one_tag(c) || c->isglobal || c->isunglobal)) + Client *target_client = find_client_by_direction(c, arg, false); + + return scroller_apply_stack(c, target_client, arg->i); +} + +int32_t toggle_all_floating(const Arg *arg) { + if (!selmon) return 0; - bool is_horizontal_layout = - c->mon->pertag->ltidxs[c->mon->pertag->curtag]->id == SCROLLER ? true - : false; - - Client *target_client = find_client_by_direction(c, arg, false, true); - - if (target_client && (!client_only_in_one_tag(target_client) || - target_client->isglobal || target_client->isunglobal)) + Client *ref = arg->tc ? arg->tc : selmon->sel; + if (!ref) return 0; - if (target_client) { - stack_head = get_scroll_stack_head(target_client); - } + bool should_floating = !ref->isfloating; - if (c) { - source_stack_head = get_scroll_stack_head(c); - } + Client *c; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, selmon)) { + if (c->isfloating && !should_floating) { + c->old_master_inner_per = 0.0f; + c->old_stack_inner_per = 0.0f; + set_size_per(selmon, c); + } - if (stack_head == source_stack_head) { - return 0; - } - - if (c->isfullscreen) { - setfullscreen(c, 0); - } - - if (c->ismaximizescreen) { - setmaximizescreen(c, 0); - } - - if (c->prev_in_stack) { - if ((is_horizontal_layout && arg->i == LEFT) || - (!is_horizontal_layout && arg->i == UP)) { - exit_scroller_stack(c); - wl_list_remove(&c->link); - wl_list_insert(source_stack_head->link.prev, &c->link); - arrange(selmon, false, false); - - } else if ((is_horizontal_layout && arg->i == RIGHT) || - (!is_horizontal_layout && arg->i == DOWN)) { - exit_scroller_stack(c); - wl_list_remove(&c->link); - wl_list_insert(&source_stack_head->link, &c->link); - arrange(selmon, false, false); + if (c->isfloating != should_floating) { + setfloating(c, should_floating); + } } + } + return 0; +} + +int32_t dwindle_set_split_direction(Client *c, bool istoggle, bool horizontal) { + const Layout *layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]; + + if (layout->id != DWINDLE) return 0; - } else if (c->next_in_stack) { - Client *next_in_stack = c->next_in_stack; - if ((is_horizontal_layout && arg->i == LEFT) || - (!is_horizontal_layout && arg->i == UP)) { - exit_scroller_stack(c); - wl_list_remove(&c->link); - wl_list_insert(next_in_stack->link.prev, &c->link); - arrange(selmon, false, false); - } else if ((is_horizontal_layout && arg->i == RIGHT) || - (!is_horizontal_layout && arg->i == DOWN)) { - exit_scroller_stack(c); - wl_list_remove(&c->link); - wl_list_insert(&next_in_stack->link, &c->link); - arrange(selmon, false, false); - } + + DwindleNode **root = &selmon->pertag->dwindle_root[selmon->pertag->curtag]; + DwindleNode *leaf = dwindle_find_leaf(*root, c); + + if (!leaf) return 0; - } - if (!target_client || target_client->mon != c->mon) { + if (istoggle) { + leaf->custom_leaf_split_h = !leaf->custom_leaf_split_h; + } else if (horizontal) { + leaf->custom_leaf_split_h = true; + } else { + leaf->custom_leaf_split_h = false; + } + bool hit_no_border = check_hit_no_border(c); + apply_split_border(c, hit_no_border); + return 0; +} + +int32_t dwindle_toggle_split_direction(const Arg *arg) { + if (!selmon) return 0; - } - exit_scroller_stack(c); + Client *c = arg->tc ? arg->tc : selmon->sel; + if (!c || !c->mon || c->isfloating) + return 0; + return dwindle_set_split_direction(c, true, false); +} - // Find the tail of target_client's stack - Client *stack_tail = target_client; - while (stack_tail->next_in_stack) { - stack_tail = stack_tail->next_in_stack; - } +int32_t dwindle_split_horizontal(const Arg *arg) { + if (!selmon) + return 0; - // Add c to the stack - stack_tail->next_in_stack = c; - c->prev_in_stack = stack_tail; - c->next_in_stack = NULL; + Client *c = arg->tc ? arg->tc : selmon->sel; + if (!c || !c->mon || c->isfloating) + return 0; + return dwindle_set_split_direction(c, false, true); +} - if (stack_head->ismaximizescreen) { - setmaximizescreen(stack_head, 0); - } +int32_t dwindle_split_vertical(const Arg *arg) { + if (!selmon) + return 0; - if (stack_head->isfullscreen) { - setfullscreen(stack_head, 0); - } + Client *c = arg->tc ? arg->tc : selmon->sel; + if (!c || !c->mon || c->isfloating) + return 0; + return dwindle_set_split_direction(c, false, false); +} - arrange(selmon, false, false); +int32_t focusid(const Arg *arg) { + if (!selmon || !arg->tc) + return 0; + + Client *c = arg->tc; + client_active(c); return 0; } \ No newline at end of file diff --git a/src/draw/text-node.c b/src/draw/text-node.c new file mode 100644 index 00000000..c85b25dc --- /dev/null +++ b/src/draw/text-node.c @@ -0,0 +1,845 @@ +#include "text-node.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +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); + } +} \ No newline at end of file diff --git a/src/draw/text-node.h b/src/draw/text-node.h new file mode 100644 index 00000000..079af908 --- /dev/null +++ b/src/draw/text-node.h @@ -0,0 +1,166 @@ +#ifndef jump_label_node_H +#define jump_label_node_H + +#include +#include +#include +#include +#include +#include + +// 原有结构体,假设已存在 +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 \ No newline at end of file diff --git a/src/ext-protocol/all.h b/src/ext-protocol/all.h index bc690fe7..fac2779d 100644 --- a/src/ext-protocol/all.h +++ b/src/ext-protocol/all.h @@ -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" \ No newline at end of file +#include "text-input.h" diff --git a/src/ext-protocol/dwl-ipc.h b/src/ext-protocol/dwl-ipc.h index ab0bdb8d..53d96309 100644 --- a/src/ext-protocol/dwl-ipc.h +++ b/src/ext-protocol/dwl-ipc.h @@ -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, diff --git a/src/ext-protocol/foreign-toplevel.h b/src/ext-protocol/foreign-toplevel.h index 89f3839a..a8c49c77 100644 --- a/src/ext-protocol/foreign-toplevel.h +++ b/src/ext-protocol/foreign-toplevel.h @@ -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); } diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h new file mode 100644 index 00000000..34c82dbf --- /dev/null +++ b/src/ext-protocol/tablet.h @@ -0,0 +1,458 @@ +#include +#include +#include + +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); +} diff --git a/src/ext-protocol/tearing.h b/src/ext-protocol/tearing.h index 8e02656a..5a9851e4 100644 --- a/src/ext-protocol/tearing.h +++ b/src/ext-protocol/tearing.h @@ -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 */ diff --git a/src/fetch/client.h b/src/fetch/client.h index bf30e175..a44f4ca7 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -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; -} +} \ No newline at end of file diff --git a/src/fetch/common.h b/src/fetch/common.h index 58e69dc1..acb5d0e8 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -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; + } + } } \ No newline at end of file diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h index 041a170b..0bff62f5 100644 --- a/src/fetch/monitor.h +++ b/src/fetch/monitor.h @@ -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++) { @@ -105,3 +113,72 @@ Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly) { 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; +} diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h new file mode 100644 index 00000000..9ac1a7b2 --- /dev/null +++ b/src/ipc/ipc.h @@ -0,0 +1,1053 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ipc_watch_client { + struct wl_list link; + int fd; + struct wl_event_source *source; + enum ipc_watch_type type; + union { + struct { + char name[64]; + } monitor; + struct { + uint32_t id; + } client; + struct { + char mon_name[64]; + } tags; + } target; +}; + +static struct wl_list watch_clients; + +struct ipc_client_state { + int fd; + struct wl_event_source *source; + struct wl_event_loop *loop; + char *buf; + size_t buf_len; + size_t buf_cap; +}; + +static void ipc_remove_watch_client(struct ipc_watch_client *wc); +static void ipc_notify_json_to_fd(int fd, cJSON *json); + +/* ---------- 工具函数 ---------- */ + +static Monitor *monitor_by_name(const char *name) { + Monitor *m; + wl_list_for_each(m, &mons, link) { + if (strcmp(m->wlr_output->name, name) == 0) + return m; + } + return NULL; +} + +static Client *client_by_id(uint32_t id) { + Client *c; + wl_list_for_each(c, &clients, link) { + if (c->id == id) + return c; + } + return NULL; +} + +static const char *ipc_get_layout_str(void) { + struct wlr_keyboard *keyboard = &kb_group->wlr_group->keyboard; + xkb_layout_index_t current = xkb_state_serialize_layout( + keyboard->xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); + static char layout[32]; + const char *name = xkb_keymap_layout_get_name(keyboard->keymap, current); + snprintf(layout, sizeof(layout), "%s", name ? name : ""); + return layout; +} + +static cJSON *tags_mask_to_array(uint32_t tagmask) { + cJSON *arr = cJSON_CreateArray(); + for (int i = 0; i < LENGTH(tags); i++) + if (tagmask & (1 << i)) + cJSON_AddItemToArray(arr, cJSON_CreateNumber(i + 1)); + return arr; +} + +static cJSON *build_tags_json(Monitor *m) { + cJSON *tags_array = cJSON_CreateArray(); + Client *c = NULL; + for (int tag = 1; tag <= LENGTH(tags); tag++) { + int numclients = 0; + bool is_active = false, is_urgent = false; + uint32_t tagmask = 1 << (tag - 1); + if (tagmask & m->tagset[m->seltags]) + is_active = true; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (!(c->tags & tagmask & TAGMASK)) + continue; + if (c->isurgent) + is_urgent = true; + numclients++; + } + cJSON *tag_obj = cJSON_CreateObject(); + cJSON_AddNumberToObject(tag_obj, "index", tag); + cJSON_AddBoolToObject(tag_obj, "is_active", is_active); + cJSON_AddBoolToObject(tag_obj, "is_urgent", is_urgent); + cJSON_AddStringToObject(tag_obj, "layout", + m->pertag->ltidxs[tag]->symbol); + cJSON_AddNumberToObject(tag_obj, "client_count", numclients); + cJSON_AddItemToArray(tags_array, tag_obj); + } + return tags_array; +} + +static cJSON *monitor_active_client(Monitor *m) { + cJSON *obj = cJSON_CreateObject(); + if (!m->sel) { + cJSON_AddNullToObject(obj, "id"); + cJSON_AddNullToObject(obj, "title"); + cJSON_AddNullToObject(obj, "appid"); + return obj; + } + Client *c = m->sel; + cJSON_AddNumberToObject(obj, "id", c->id); + cJSON_AddStringToObject(obj, "title", client_get_title(c)); + cJSON_AddStringToObject(obj, "appid", client_get_appid(c)); + return obj; +} + +static cJSON *monitor_active_tags(Monitor *m) { + cJSON *arr = cJSON_CreateArray(); + uint32_t tagset; + if (m->isoverview) { + cJSON_AddItemToArray(arr, cJSON_CreateNumber(0)); + return arr; + } + tagset = m->tagset[m->seltags]; + for (int i = 0; i < LENGTH(tags); i++) + if (tagset & (1 << i)) + cJSON_AddItemToArray(arr, cJSON_CreateNumber(i + 1)); + return arr; +} + +static cJSON *build_client_json(Client *c) { + cJSON *obj = cJSON_CreateObject(); + + cJSON_AddNumberToObject(obj, "id", c->id); + cJSON_AddNumberToObject(obj, "pid", c->pid); + cJSON_AddStringToObject(obj, "title", client_get_title(c)); + cJSON_AddStringToObject(obj, "appid", client_get_appid(c)); + cJSON_AddStringToObject(obj, "monitor", + c->mon ? c->mon->wlr_output->name : ""); + cJSON_AddItemToObject(obj, "tags", tags_mask_to_array(c->tags)); + cJSON_AddBoolToObject(obj, "is_focused", c->isfocusing); + cJSON_AddBoolToObject(obj, "is_fullscreen", c->isfullscreen); + cJSON_AddBoolToObject(obj, "is_floating", c->isfloating); + cJSON_AddBoolToObject(obj, "is_maximized", c->ismaximizescreen); + cJSON_AddBoolToObject(obj, "is_global", c->isglobal); + cJSON_AddBoolToObject(obj, "is_unglobal", c->isunglobal); + cJSON_AddBoolToObject(obj, "is_overlay", c->isoverlay); + cJSON_AddBoolToObject(obj, "is_fakefullscreen", c->isfakefullscreen); + cJSON_AddBoolToObject(obj, "is_minimized", c->isminimized); + cJSON_AddBoolToObject(obj, "is_urgent", c->isurgent); + cJSON_AddBoolToObject(obj, "is_scratchpad", c->is_in_scratchpad); + cJSON_AddBoolToObject(obj, "is_namedscratchpad", c->isnamedscratchpad); + cJSON_AddNumberToObject(obj, "x", c->geom.x); + cJSON_AddNumberToObject(obj, "y", c->geom.y); + cJSON_AddNumberToObject(obj, "width", c->geom.width); + cJSON_AddNumberToObject(obj, "height", c->geom.height); + cJSON_AddNumberToObject(obj, "scroller_proportion", + (double)c->scroller_proportion); + return obj; +} + +static cJSON *build_monitor_json(Monitor *m) { + cJSON *resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "name", m->wlr_output->name); + cJSON_AddBoolToObject(resp, "active", m == selmon); + cJSON_AddNumberToObject(resp, "x", m->m.x); + cJSON_AddNumberToObject(resp, "y", m->m.y); + cJSON_AddNumberToObject(resp, "width", m->m.width); + cJSON_AddNumberToObject(resp, "height", m->m.height); + cJSON_AddNumberToObject(resp, "scale", m->wlr_output->scale); + cJSON_AddNumberToObject(resp, "layout_index", + m->pertag->ltidxs[m->pertag->curtag] - layouts); + cJSON_AddStringToObject(resp, "layout_symbol", + m->pertag->ltidxs[m->pertag->curtag]->symbol); + cJSON_AddStringToObject(resp, "last_open_surface", m->last_open_surface); + cJSON_AddItemToObject(resp, "tags", build_tags_json(m)); + cJSON_AddItemToObject(resp, "active_tags", monitor_active_tags(m)); + cJSON_AddItemToObject(resp, "active_client", monitor_active_client(m)); + cJSON_AddItemToObject(resp, "keymode", cJSON_CreateString(keymode.mode)); + cJSON_AddItemToObject(resp, "keyboardlayout", + cJSON_CreateString(ipc_get_layout_str())); + return resp; +} + +static cJSON *build_all_tags_entry(Monitor *m) { + cJSON *entry = cJSON_CreateObject(); + cJSON_AddStringToObject(entry, "monitor", m->wlr_output->name); + cJSON_AddItemToObject(entry, "tags", build_tags_json(m)); + return entry; +} + +static cJSON *build_all_tags_response(void) { + cJSON *arr = cJSON_CreateArray(); + Monitor *m; + wl_list_for_each(m, &mons, link) + cJSON_AddItemToArray(arr, build_all_tags_entry(m)); + cJSON *resp = cJSON_CreateObject(); + cJSON_AddItemToObject(resp, "all_tags", arr); + return resp; +} + +static cJSON *build_monitor_tags_response(Monitor *m) { + cJSON *resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "monitor", m->wlr_output->name); + cJSON_AddItemToObject(resp, "tags", build_tags_json(m)); + cJSON_AddItemToObject(resp, "active_tags", monitor_active_tags(m)); + return resp; +} + +static void send_static_json(int fd, const char *json_str) { + size_t len = strlen(json_str); + send(fd, json_str, len, 0); +} + +/* ---------- 一次性命令处理 ---------- */ +static void handle_command(int client_fd, const char *cmd_raw) { + cJSON *resp = NULL; + char *json_str = NULL; + char cmd[1024]; + + strncpy(cmd, cmd_raw, sizeof(cmd) - 1); + cmd[sizeof(cmd) - 1] = '\0'; + for (char *p = cmd; *p; p++) + if (*p == ',') + *p = ' '; + + if (strcmp(cmd, "get version") == 0) { + resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "version", VERSION); + } else if (strcmp(cmd, "get keymode") == 0) { + resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "keymode", keymode.mode); + } else if (strcmp(cmd, "get keyboardlayout") == 0) { + resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "layout", ipc_get_layout_str()); + } else if (strcmp(cmd, "get last_open_surface") == 0 || + strncmp(cmd, "get last_open_surface ", 22) == 0) { + Monitor *m; + if (cmd[21] == '\0') { // exactly "get last_open_surface" + m = selmon; + } else { + m = monitor_by_name(cmd + 22); + } + if (!m) { + send_static_json(client_fd, "{\"error\":\"monitor not found\"}\n"); + return; + } + resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "monitor", m->wlr_output->name); + cJSON_AddStringToObject(resp, "last_open_surface", + m->last_open_surface); + } else if (strncmp(cmd, "get monitor ", 12) == 0) { + Monitor *m = monitor_by_name(cmd + 12); + if (!m) { + send_static_json(client_fd, "{\"error\":\"monitor not found\"}\n"); + return; + } + resp = build_monitor_json(m); + } else if (strcmp(cmd, "get focusing-client") == 0) { + if (selmon && selmon->sel) { + resp = build_client_json(selmon->sel); + } else { + send_static_json(client_fd, "{\"error\":\"no focused client\"}\n"); + return; + } + } else if (strncmp(cmd, "get client ", 11) == 0) { + Client *c = client_by_id((uint32_t)atoi(cmd + 11)); + if (!c) { + send_static_json(client_fd, "{\"error\":\"client not found\"}\n"); + return; + } + resp = build_client_json(c); + } else if (strncmp(cmd, "get tag ", 8) == 0) { + char mon_name[64]; + int ext_tag_idx; + if (sscanf(cmd + 8, "%63s %d", mon_name, &ext_tag_idx) != 2) { + send_static_json( + client_fd, + "{\"error\":\"usage: get tag \"}\n"); + return; + } + int tag_idx = ext_tag_idx - 1; + Monitor *m = monitor_by_name(mon_name); + if (!m || tag_idx < 0 || tag_idx >= LENGTH(tags)) { + send_static_json(client_fd, + "{\"error\":\"invalid monitor or tag index\"}\n"); + return; + } + uint32_t tagmask = 1 << tag_idx; + int numclients = 0, focused_client = 0; + bool is_active = false, is_urgent = false; + if (tagmask & m->tagset[m->seltags]) + is_active = true; + + Client *c, *focused = focustop(m); + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !(c->tags & tagmask)) + continue; + if (c == focused) + focused_client = 1; + if (c->isurgent) + is_urgent = true; + numclients++; + } + resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "monitor", m->wlr_output->name); + cJSON_AddNumberToObject(resp, "tag_index", ext_tag_idx); + cJSON_AddBoolToObject(resp, "is_active", is_active); + cJSON_AddBoolToObject(resp, "is_urgent", is_urgent); + cJSON_AddNumberToObject(resp, "client_count", numclients); + cJSON_AddBoolToObject(resp, "focused_client", focused_client); + } else if (strcmp(cmd, "get all-clients") == 0) { + cJSON *arr = cJSON_CreateArray(); + Client *c; + wl_list_for_each(c, &clients, link) + cJSON_AddItemToArray(arr, build_client_json(c)); + resp = cJSON_CreateObject(); + cJSON_AddItemToObject(resp, "clients", arr); + } else if (strcmp(cmd, "get all-monitors") == 0) { + cJSON *arr = cJSON_CreateArray(); + Monitor *m; + wl_list_for_each(m, &mons, link) + cJSON_AddItemToArray(arr, build_monitor_json(m)); + resp = cJSON_CreateObject(); + cJSON_AddItemToObject(resp, "monitors", arr); + } else if (strcmp(cmd, "get all-tags") == 0) { + resp = build_all_tags_response(); + } else if (strncmp(cmd, "get tags ", 9) == 0) { + Monitor *m = monitor_by_name(cmd + 9); + if (!m) { + send_static_json(client_fd, "{\"error\":\"monitor not found\"}\n"); + return; + } + resp = build_monitor_tags_response(m); + } else if (strncmp(cmd, "dispatch ", 9) == 0) { + char *dispatch_copy = strdup(cmd_raw + 9); + char *out = dispatch_copy, *ptr = dispatch_copy; + int client_id = -1; + while (*ptr) { + while (*ptr == ' ' || *ptr == '\t') + *out++ = *ptr++; + if (strncmp(ptr, "client,", 7) == 0) { + char *end; + long id = strtol(ptr + 7, &end, 10); + if (id > 0 && end > ptr + 7 && (*end == '\0' || *end == ',')) { + client_id = (int)id; + ptr = end; + if (*ptr == ',') + ptr++; + continue; + } + } + *out++ = *ptr++; + } + *out = '\0'; + + char *tokens[6] = {NULL}; + int token_count = 0; + char *saveptr; + char *token = strtok_r(dispatch_copy, ",", &saveptr); + while (token && token_count < 6) { + while (*token == ' ' || *token == '\t') + token++; + char *end = token + strlen(token) - 1; + while (end >= token && (*end == ' ' || *end == '\t')) + *end-- = '\0'; + tokens[token_count++] = token; + token = strtok_r(NULL, ",", &saveptr); + } + + Arg arg = {0}; + int32_t (*func)(const Arg *) = parse_func_name( + token_count > 0 ? tokens[0] : "", &arg, + token_count > 1 ? tokens[1] : "", token_count > 2 ? tokens[2] : "", + token_count > 3 ? tokens[3] : "", token_count > 4 ? tokens[4] : "", + token_count > 5 ? tokens[5] : ""); + + if (func && client_id > 0) + arg.tc = client_by_id((uint32_t)client_id); + + if (func) { + func(&arg); + send_static_json(client_fd, "{\"success\":true}\n"); + } else { + send_static_json(client_fd, "{\"error\":\"unknown function\"}\n"); + } + + if (arg.v) + free(arg.v); + if (arg.v2) + free(arg.v2); + if (arg.v3) + free(arg.v3); + free(dispatch_copy); + return; // Fast path exit + } else { + send_static_json(client_fd, "{\"error\":\"unknown command\"}\n"); + return; + } + + if (resp) { + json_str = cJSON_PrintUnformatted(resp); + if (json_str) { + size_t len = strlen(json_str); + char *msg = malloc(len + 2); + if (msg) { + snprintf(msg, len + 2, "%s\n", json_str); + send(client_fd, msg, len + 1, 0); + free(msg); + } + free(json_str); + } + cJSON_Delete(resp); + } +} + +/* ---------- Watch 模式支持 ---------- */ +static void ipc_notify_json_to_fd(int fd, cJSON *json) { + char *str = cJSON_PrintUnformatted(json); + if (!str) + return; + size_t len = strlen(str); + char *msg = malloc(len + 2); + if (!msg) { + free(str); + return; + } + snprintf(msg, len + 2, "%s\n", str); + if (send(fd, msg, len + 1, 0) < 0) { + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->fd == fd) { + ipc_remove_watch_client(wc); + break; + } + } + } + free(msg); + free(str); +} + +static void ipc_remove_watch_client(struct ipc_watch_client *wc) { + wl_list_remove(&wc->link); + wl_event_source_remove(wc->source); + close(wc->fd); + free(wc); +} + +static int ipc_watch_data_handler(int fd, uint32_t mask, void *data) { + struct ipc_watch_client *wc = data; + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + ipc_remove_watch_client(wc); + return 0; + } + if (mask & WL_EVENT_READABLE) { + char buf[64]; + ssize_t n = recv(fd, buf, sizeof(buf), 0); + if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) { + ipc_remove_watch_client(wc); + } + } + return 0; +} + +static bool handle_watch_command(int fd, const char *cmd, + struct ipc_client_state *client) { + enum ipc_watch_type type = IPC_WATCH_NONE; + const char *arg = NULL; + uint32_t client_id = 0; + + if (strncmp(cmd, "watch monitor ", 14) == 0) { + type = IPC_WATCH_MONITOR; + arg = cmd + 14; + } else if (strcmp(cmd, "watch focusing-client") == 0) { + type = IPC_WATCH_FOCUSING_CLIENT; + } else if (strncmp(cmd, "watch client ", 13) == 0) { + type = IPC_WATCH_CLIENT; + client_id = (uint32_t)atoi(cmd + 13); + } else if (strncmp(cmd, "watch tags ", 11) == 0) { + type = IPC_WATCH_TAGS; + arg = cmd + 11; + } else if (strcmp(cmd, "watch all-monitors") == 0) { + type = IPC_WATCH_ALL_MONITORS; + } else if (strcmp(cmd, "watch all-tags") == 0) { + type = IPC_WATCH_ALL_TAGS; + } else if (strcmp(cmd, "watch all-clients") == 0) { + type = IPC_WATCH_ALL_CLIENTS; + } else if (strcmp(cmd, "watch keymode") == 0) { + type = IPC_WATCH_KEYMODE; + } else if (strcmp(cmd, "watch keyboardlayout") == 0) { + type = IPC_WATCH_KB_LAYOUT; + } else if (strcmp(cmd, "watch last_open_surface") == 0 || + strncmp(cmd, "watch last_open_surface ", 24) == 0) { + type = IPC_WATCH_LAST_OPEN_SURFACE; + if (cmd[24] != '\0') { // has argument after the space + arg = cmd + 24; + } else { + arg = NULL; // default to selmon + } + } + + if (type == IPC_WATCH_NONE) + return false; + + struct ipc_watch_client *wc = calloc(1, sizeof(*wc)); + wc->fd = fd; + wc->type = type; + + if ((type == IPC_WATCH_MONITOR || type == IPC_WATCH_LAST_OPEN_SURFACE) && + arg) + snprintf(wc->target.monitor.name, sizeof(wc->target.monitor.name), "%s", + arg); + else if (type == IPC_WATCH_TAGS && arg) + snprintf(wc->target.tags.mon_name, sizeof(wc->target.tags.mon_name), + "%s", arg); + else if (type == IPC_WATCH_CLIENT) + wc->target.client.id = client_id; + + wl_event_source_remove(client->source); + wc->source = wl_event_loop_add_fd( + client->loop, fd, WL_EVENT_READABLE | WL_EVENT_HANGUP | WL_EVENT_ERROR, + ipc_watch_data_handler, wc); + wl_list_insert(&watch_clients, &wc->link); + + /* 推送初始状态 */ + cJSON *json = NULL; + switch (type) { + case IPC_WATCH_MONITOR: { + Monitor *m = monitor_by_name(arg); + if (m) + json = build_monitor_json(m); + break; + } + case IPC_WATCH_LAST_OPEN_SURFACE: { + Monitor *m = NULL; + if (arg) { + m = monitor_by_name(arg); + } else { + m = selmon; + } + if (m) { + json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "monitor", m->wlr_output->name); + cJSON_AddStringToObject(json, "last_open_surface", + m->last_open_surface); + } + break; + } + case IPC_WATCH_FOCUSING_CLIENT: { + if (selmon && selmon->sel) { + json = build_client_json(selmon->sel); + } else { + json = cJSON_CreateObject(); + cJSON_AddNullToObject(json, "id"); + cJSON_AddNullToObject(json, "title"); + cJSON_AddNullToObject(json, "appid"); + } + break; + } + case IPC_WATCH_CLIENT: { + Client *c = client_by_id(client_id); + if (c) + json = build_client_json(c); + break; + } + case IPC_WATCH_TAGS: { + Monitor *m = monitor_by_name(arg); + if (m) + json = build_monitor_tags_response(m); + break; + } + case IPC_WATCH_ALL_MONITORS: { + cJSON *arr = cJSON_CreateArray(); + Monitor *m; + wl_list_for_each(m, &mons, link) + cJSON_AddItemToArray(arr, build_monitor_json(m)); + json = cJSON_CreateObject(); + cJSON_AddItemToObject(json, "monitors", arr); + break; + } + case IPC_WATCH_ALL_TAGS: { + json = build_all_tags_response(); + break; + } + case IPC_WATCH_ALL_CLIENTS: { + cJSON *arr = cJSON_CreateArray(); + Client *c; + wl_list_for_each(c, &clients, link) + cJSON_AddItemToArray(arr, build_client_json(c)); + json = cJSON_CreateObject(); + cJSON_AddItemToObject(json, "clients", arr); + break; + } + case IPC_WATCH_KEYMODE: { + json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "keymode", keymode.mode); + break; + } + case IPC_WATCH_KB_LAYOUT: { + json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "layout", ipc_get_layout_str()); + break; + } + default: + break; + } + + if (json) { + ipc_notify_json_to_fd(fd, json); + cJSON_Delete(json); + } + + free(client->buf); + free(client); + return true; +} + +/* ---------- Socket 事件处理 ---------- */ +static int ipc_handle_client_data(int fd, uint32_t mask, void *data) { + struct ipc_client_state *client = data; + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) + goto cleanup; + + if (mask & WL_EVENT_READABLE) { + size_t available = client->buf_cap - client->buf_len; + if (available < 4096) { + size_t new_cap = client->buf_cap ? client->buf_cap * 2 : 8192; + char *new_buf = realloc(client->buf, new_cap); + if (!new_buf) { + wlr_log(WLR_ERROR, "IPC: out of memory"); + goto cleanup; + } + client->buf = new_buf; + client->buf_cap = new_cap; + available = client->buf_cap - client->buf_len; + } + + ssize_t n = recv(fd, client->buf + client->buf_len, available - 1, 0); + if (n <= 0) + goto cleanup; + + client->buf_len += n; + client->buf[client->buf_len] = '\0'; + + char *nl = memchr(client->buf, '\n', client->buf_len); + if (!nl) { + if (client->buf_len > 1024 * 1024) + goto cleanup; + return 0; + } + *nl = '\0'; + char *cmd = client->buf; + + bool is_watch = handle_watch_command(fd, cmd, client); + if (is_watch) + return 0; + + handle_command(fd, cmd); + goto cleanup; + } + return 0; + +cleanup: + close(client->fd); + wl_event_source_remove(client->source); + free(client->buf); + free(client); + return 0; +} + +static int ipc_handle_connection(int fd, uint32_t mask, void *data) { + struct wl_event_loop *loop = data; + int client_fd = accept(fd, NULL, NULL); + if (client_fd < 0) + return 0; + + // 设置 O_NONBLOCK + int flags = fcntl(client_fd, F_GETFL, 0); + fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); + // 设置 FD_CLOEXEC + flags = fcntl(client_fd, F_GETFD, 0); + fcntl(client_fd, F_SETFD, flags | FD_CLOEXEC); + + struct ipc_client_state *client = calloc(1, sizeof(*client)); + client->fd = client_fd; + client->loop = loop; + client->source = wl_event_loop_add_fd( + loop, client_fd, WL_EVENT_READABLE | WL_EVENT_HANGUP | WL_EVENT_ERROR, + ipc_handle_client_data, client); + return 0; +} + +/* ---------- 外部通知接口 ---------- */ + +void ipc_notify_monitor(Monitor *m) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_MONITOR && + strcmp(m->wlr_output->name, wc->target.monitor.name) == 0) { + if (!json_str) { + cJSON *json = build_monitor_json(m); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, 0) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_last_surface_ws_name(Monitor *m) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type != IPC_WATCH_LAST_OPEN_SURFACE) + continue; + + bool match = false; + if (wc->target.monitor.name[0] == '\0') { + if (m == selmon) + match = true; + } else { + if (strcmp(m->wlr_output->name, wc->target.monitor.name) == 0) + match = true; + } + + if (!match) + continue; + + if (!json_str) { + cJSON *json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "monitor", m->wlr_output->name); + cJSON_AddStringToObject(json, "last_open_surface", + m->last_open_surface); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, 0) < 0) + ipc_remove_watch_client(wc); + } + free(json_str); +} + +void ipc_notify_focusing_client(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_FOCUSING_CLIENT) { + if (!json_str) { + cJSON *json = NULL; + if (selmon && selmon->sel) { + json = build_client_json(selmon->sel); + } else { + json = cJSON_CreateObject(); + cJSON_AddNullToObject(json, "id"); + cJSON_AddNullToObject(json, "title"); + cJSON_AddNullToObject(json, "appid"); + } + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, 0) < 0) + ipc_remove_watch_client(wc); + } + } + free(json_str); +} + +void ipc_notify_client(Client *c) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_CLIENT && c->id == wc->target.client.id) { + if (!json_str) { + cJSON *json = build_client_json(c); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, 0) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_tags(Monitor *m) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_TAGS && + strcmp(m->wlr_output->name, wc->target.tags.mon_name) == 0) { + if (!json_str) { + cJSON *json = build_monitor_tags_response(m); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, 0) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_all_monitors(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_ALL_MONITORS) { + if (!json_str) { + cJSON *arr = cJSON_CreateArray(); + Monitor *m; + wl_list_for_each(m, &mons, link) + cJSON_AddItemToArray(arr, build_monitor_json(m)); + cJSON *json = cJSON_CreateObject(); + cJSON_AddItemToObject(json, "monitors", arr); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, 0) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_all_clients(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_ALL_CLIENTS) { + if (!json_str) { + cJSON *arr = cJSON_CreateArray(); + Client *c; + wl_list_for_each(c, &clients, link) + cJSON_AddItemToArray(arr, build_client_json(c)); + cJSON *json = cJSON_CreateObject(); + cJSON_AddItemToObject(json, "clients", arr); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, 0) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_all_tags(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_ALL_TAGS) { + if (!json_str) { + cJSON *json = build_all_tags_response(); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, 0) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_keymode(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_KEYMODE) { + if (!json_str) { + cJSON *json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "keymode", keymode.mode); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, 0) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_kb_layout(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_KB_LAYOUT) { + if (!json_str) { + cJSON *json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "layout", ipc_get_layout_str()); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, 0) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +/* ---------- 初始化与清理 ---------- */ +static int ipc_sock_fd = -1; +static struct wl_event_source *ipc_event_source = NULL; +static char ipc_socket_path[256]; + +void ipc_init(struct wl_event_loop *event_loop) { + wl_list_init(&watch_clients); + + const char *xdg_runtime = getenv("XDG_RUNTIME_DIR"); + if (!xdg_runtime) + return; + + snprintf(ipc_socket_path, sizeof(ipc_socket_path), "%s/mango-%d.sock", + xdg_runtime, getpid()); + + ipc_sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (ipc_sock_fd < 0) + return; + + // 设置 FD_CLOEXEC + int flags = fcntl(ipc_sock_fd, F_GETFD, 0); + if (flags == -1 || fcntl(ipc_sock_fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + wlr_log(WLR_ERROR, "failed to set FD_CLOEXEC on IPC socket"); + close(ipc_sock_fd); + return; + } + // 设置 O_NONBLOCK + flags = fcntl(ipc_sock_fd, F_GETFL, 0); + if (flags == -1 || fcntl(ipc_sock_fd, F_SETFL, flags | O_NONBLOCK) == -1) { + wlr_log(WLR_ERROR, "failed to set O_NONBLOCK on IPC socket"); + close(ipc_sock_fd); + return; + } + + struct sockaddr_un addr = {.sun_family = AF_UNIX}; + strncpy(addr.sun_path, ipc_socket_path, sizeof(addr.sun_path) - 1); + + unlink(ipc_socket_path); + if (bind(ipc_sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(ipc_sock_fd); + return; + } + listen(ipc_sock_fd, 16); + + setenv("MANGO_INSTANCE_SIGNATURE", ipc_socket_path, 1); + + ipc_event_source = + wl_event_loop_add_fd(event_loop, ipc_sock_fd, WL_EVENT_READABLE, + ipc_handle_connection, event_loop); +} + +void ipc_cleanup(void) { + if (ipc_event_source) + wl_event_source_remove(ipc_event_source); + if (ipc_sock_fd >= 0) + close(ipc_sock_fd); + unlink(ipc_socket_path); + unsetenv("MANGO_INSTANCE_SIGNATURE"); + + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) + ipc_remove_watch_client(wc); +} \ No newline at end of file diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 37213640..4782b3ea 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -5,8 +5,13 @@ void set_size_per(Monitor *m, Client *c) { if (!m || !c) return; + const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; + wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c) { + if (current_layout->id == CENTER_TILE && + (fc->isleftstack ^ c->isleftstack)) + continue; c->master_mfact_per = fc->master_mfact_per; c->master_inner_per = fc->master_inner_per; c->stack_inner_per = fc->stack_inner_per; @@ -20,6 +25,31 @@ void set_size_per(Monitor *m, Client *c) { c->master_inner_per = 1.0f; c->stack_inner_per = 1.0f; } + + if (!c->iscustom_scroller_proportion) { + c->scroller_proportion = + m->pertag->scroller_default_proportion[m->pertag->curtag]; + } + + if (!c->iscustom_scroller_proportion_single) { + c->scroller_proportion_single = + m->pertag->scroller_default_proportion_single[m->pertag->curtag]; + } +} + +void monocle_set_focus(Client *c, bool focused) { + + if (!c || !c->mon) + return; + + c->is_monocle_hide = !focused; + mango_tab_bar_node_set_focus(c->tab_bar_node, focused); + wlr_scene_node_set_enabled(&c->scene->node, focused); + + if (!focused) { + c->animation.current = c->animainit_geom = c->animation.initial = + c->pending = c->current = c->geom; + } } void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, @@ -35,33 +65,28 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, bool begin_find_nextnext = false; bool begin_find_prevprev = false; - // 从当前节点的下一个开始遍历 + /* 寻找 next / nextnext */ for (node = grabc->link.next; node != &clients; node = node->next) { tc = wl_container_of(node, tc, link); if (begin_find_nextnext && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { nextnext = tc; break; } - - if (!begin_find_nextnext && VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (!begin_find_nextnext && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { next = tc; begin_find_nextnext = true; continue; } } - // 从当前节点的上一个开始遍历 + /* 寻找 prev / prevprev */ for (node = grabc->link.prev; node != &clients; node = node->prev) { tc = wl_container_of(node, tc, link); - if (begin_find_prevprev && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { prevprev = tc; break; } - - if (!begin_find_prevprev && VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (!begin_find_prevprev && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { prev = tc; begin_find_prevprev = true; continue; @@ -72,7 +97,6 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, drag_begin_cursorx = cursor->x; drag_begin_cursory = cursor->y; start_drag_window = true; - // 记录初始状态 grabc->old_master_mfact_per = grabc->master_mfact_per; grabc->old_master_inner_per = grabc->master_inner_per; grabc->old_stack_inner_per = grabc->stack_inner_per; @@ -80,12 +104,9 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, cursor->y < grabc->geom.y + grabc->geom.height / 2; grabc->cursor_in_left_half = cursor->x < grabc->geom.x + grabc->geom.width / 2; - // 记录初始几何信息 grabc->drag_begin_geom = grabc->geom; } else { - // 计算相对于屏幕尺寸的比例变化 if (isdrag) { - offsetx = cursor->x - drag_begin_cursorx; offsety = cursor->y - drag_begin_cursory; } else { @@ -108,109 +129,156 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, delta_y = (float)(offsety) * (grabc->old_stack_inner_per) / grabc->drag_begin_geom.height; } - bool moving_up; - bool moving_down; + bool moving_up, moving_down; if (!isdrag) { - moving_up = offsety < 0 ? true : false; - moving_down = offsety > 0 ? true : false; + moving_up = offsety < 0; + moving_down = offsety > 0; } else { moving_up = cursor->y < drag_begin_cursory; moving_down = cursor->y > drag_begin_cursory; } if (grabc->ismaster && !prev) { - if (moving_up) { + if (moving_up) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } else if (grabc->ismaster && next && !next->ismaster) { - if (moving_up) { + if (moving_up) delta_y = fabsf(delta_y); - } else { + else delta_y = -fabsf(delta_y); - } } else if (!grabc->ismaster && prev && prev->ismaster) { - if (moving_up) { + if (moving_up) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } else if (!grabc->ismaster && !next) { - if (moving_up) { + if (moving_up) delta_y = fabsf(delta_y); - } else { + else delta_y = -fabsf(delta_y); - } } else if (type == CENTER_TILE && !grabc->ismaster && !nextnext) { - if (moving_up) { + if (moving_up) delta_y = fabsf(delta_y); - } else { + else delta_y = -fabsf(delta_y); - } } else if (type == CENTER_TILE && !grabc->ismaster && prevprev && prevprev->ismaster) { - if (moving_up) { + if (moving_up) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } else if ((grabc->cursor_in_upper_half && moving_up) || (!grabc->cursor_in_upper_half && moving_down)) { - // 光标在窗口上方且向上移动,或在窗口下方且向下移动 → 增加高度 - delta_y = fabsf(delta_y); - delta_y = delta_y * 2; + delta_y = fabsf(delta_y) * 2; } else { - // 其他情况 → 减小高度 - delta_y = -fabsf(delta_y); - delta_y = delta_y * 2; + delta_y = -fabsf(delta_y) * 2; } - if (!grabc->ismaster && grabc->isleftstack && type == CENTER_TILE) { + if (!grabc->ismaster && grabc->isleftstack && type == CENTER_TILE) delta_x = delta_x * -1.0f; - } - if (grabc->ismaster && type == CENTER_TILE && - grabc->cursor_in_left_half) { + grabc->cursor_in_left_half) delta_x = delta_x * -1.0f; - } - - if (grabc->ismaster && type == CENTER_TILE) { + if (grabc->ismaster && type == CENTER_TILE) delta_x = delta_x * 2; - } - - if (type == RIGHT_TILE) { + if (type == RIGHT_TILE) delta_x = delta_x * -1.0f; - } - // 直接设置新的比例,基于初始值 + 变化量 float new_master_mfact_per = grabc->old_master_mfact_per + delta_x; float new_master_inner_per = grabc->old_master_inner_per + delta_y; float new_stack_inner_per = grabc->old_stack_inner_per + delta_y; - // 应用限制,确保比例在合理范围内 new_master_mfact_per = fmaxf(0.1f, fminf(0.9f, new_master_mfact_per)); new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); - // 应用到所有平铺窗口 - wl_list_for_each(tc, &clients, link) { - if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { - tc->master_mfact_per = new_master_mfact_per; + // 实时缩放同组其他窗口的比例,保持组内总和为 1, + // 不然增加的比例并不是排布后的比例 + if (isdrag) { + if (grabc->ismaster) { + /* 主窗口组:调整所有主窗口的 master_inner_per */ + float cur_other_sum = 1.0f - grabc->master_inner_per; + float new_other_sum = 1.0f - new_master_inner_per; + if (cur_other_sum > 0.001f) { + float scale = new_other_sum / cur_other_sum; + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc) && + tc->ismaster && tc != grabc) + tc->master_inner_per *= scale; + } + } + } else { + /* 栈窗口组:根据布局类型分开处理 */ + if (type == CENTER_TILE) { + /* 仅缩放同侧栈窗口的 stack_inner_per */ + float cur_other_sum = 1.0f - grabc->stack_inner_per; + float new_other_sum = 1.0f - new_stack_inner_per; + if (cur_other_sum > 0.001f) { + float scale = new_other_sum / cur_other_sum; + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc) && + !tc->ismaster && tc != grabc && + tc->isleftstack == grabc->isleftstack) + tc->stack_inner_per *= scale; + } + } + } else { + /* TILE / RIGHT_TILE / DECK:所有栈窗口共用一个比例组 */ + float cur_other_sum = 1.0f - grabc->stack_inner_per; + float new_other_sum = 1.0f - new_stack_inner_per; + if (cur_other_sum > 0.001f) { + float scale = new_other_sum / cur_other_sum; + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc) && + !tc->ismaster && tc != grabc) + tc->stack_inner_per *= scale; + } + } + } + } + } else { + /* 键盘步进 */ + wl_list_for_each(tc, &clients, link) { + if (!VISIBLEON(tc, grabc->mon) || !ISTILED(tc)) + continue; + if (tc != grabc) { + if (!tc->ismaster && new_stack_inner_per != 1.0f && + grabc->old_stack_inner_per != 1.0f && + (type != CENTER_TILE || + !(grabc->isleftstack ^ tc->isleftstack))) + tc->stack_inner_per = (1 - new_stack_inner_per) / + (1 - grabc->old_stack_inner_per) * + tc->stack_inner_per; + if (tc->ismaster && new_master_inner_per != 1.0f && + grabc->old_master_inner_per != 1.0f) + tc->master_inner_per = + (1.0f - new_master_inner_per) / + (1.0f - grabc->old_master_inner_per) * + tc->master_inner_per; + } } } + /* 将新比例应用到抓取窗口本身 */ grabc->master_inner_per = new_master_inner_per; grabc->stack_inner_per = new_stack_inner_per; + /* 广播 master_mfact_per 到所有平铺窗口 */ + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) + tc->master_mfact_per = new_master_mfact_per; + } + if (!isdrag) { arrange(grabc->mon, false, false); return; } if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_tile_refresh_interval) { + time - last_apply_drap_time > config.drag_tile_refresh_interval) { arrange(grabc->mon, false, false); last_apply_drap_time = time; } @@ -225,23 +293,19 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, Client *prev = NULL; struct wl_list *node; - // 从当前节点的下一个开始遍历 + /* 寻找 next */ for (node = grabc->link.next; node != &clients; node = node->next) { tc = wl_container_of(node, tc, link); - - if (VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { next = tc; break; } } - // 从当前节点的上一个开始遍历 + /* 寻找 prev */ for (node = grabc->link.prev; node != &clients; node = node->prev) { tc = wl_container_of(node, tc, link); - - if (VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { prev = tc; break; } @@ -251,8 +315,6 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, drag_begin_cursorx = cursor->x; drag_begin_cursory = cursor->y; start_drag_window = true; - - // 记录初始状态 grabc->old_master_mfact_per = grabc->master_mfact_per; grabc->old_master_inner_per = grabc->master_inner_per; grabc->old_stack_inner_per = grabc->stack_inner_per; @@ -260,13 +322,9 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, cursor->y < grabc->geom.y + grabc->geom.height / 2; grabc->cursor_in_left_half = cursor->x < grabc->geom.x + grabc->geom.width / 2; - // 记录初始几何信息 grabc->drag_begin_geom = grabc->geom; } else { - // 计算相对于屏幕尺寸的比例变化 - // 计算相对于屏幕尺寸的比例变化 if (isdrag) { - offsetx = cursor->x - drag_begin_cursorx; offsety = cursor->y - drag_begin_cursory; } else { @@ -279,7 +337,6 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, } if (grabc->ismaster) { - // 垂直版本:左右移动调整高度比例,上下移动调整宽度比例 delta_x = (float)(offsetx) * (grabc->old_master_inner_per) / grabc->drag_begin_geom.width; delta_y = (float)(offsety) * (grabc->old_master_mfact_per) / @@ -291,120 +348,480 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, grabc->drag_begin_geom.height; } - bool moving_left; - bool moving_right; - + bool moving_left, moving_right; if (!isdrag) { - moving_left = offsetx < 0 ? true : false; - moving_right = offsetx > 0 ? true : false; + moving_left = offsetx < 0; + moving_right = offsetx > 0; } else { moving_left = cursor->x < drag_begin_cursorx; moving_right = cursor->x > drag_begin_cursorx; } - // 调整主区域和栈区域的高度比例(垂直分割) if (grabc->ismaster && !prev) { - if (moving_left) { - delta_x = -fabsf(delta_x); // 向上移动减少主区域高度 - } else { - delta_x = fabsf(delta_x); // 向下移动增加主区域高度 - } + if (moving_left) + delta_x = -fabsf(delta_x); + else + delta_x = fabsf(delta_x); } else if (grabc->ismaster && next && !next->ismaster) { - if (moving_left) { - delta_x = fabsf(delta_x); // 向上移动增加主区域高度 - } else { - delta_x = -fabsf(delta_x); // 向下移动减少主区域高度 - } + if (moving_left) + delta_x = fabsf(delta_x); + else + delta_x = -fabsf(delta_x); } else if (!grabc->ismaster && prev && prev->ismaster) { - if (moving_left) { - delta_x = -fabsf(delta_x); // 向上移动减少栈区域高度 - } else { - delta_x = fabsf(delta_x); // 向下移动增加栈区域高度 - } + if (moving_left) + delta_x = -fabsf(delta_x); + else + delta_x = fabsf(delta_x); } else if (!grabc->ismaster && !next) { - if (moving_left) { - delta_x = fabsf(delta_x); // 向上移动增加栈区域高度 - } else { - delta_x = -fabsf(delta_x); // 向下移动减少栈区域高度 - } + if (moving_left) + delta_x = fabsf(delta_x); + else + delta_x = -fabsf(delta_x); } else if ((grabc->cursor_in_left_half && moving_left) || (!grabc->cursor_in_left_half && moving_right)) { - // 光标在窗口左侧且向左移动,或在窗口右侧且向右移动 → 增加宽度 - delta_x = fabsf(delta_x); - delta_x = delta_x * 2; + delta_x = fabsf(delta_x) * 2; } else { - // 其他情况 → 减小宽度 - delta_x = -fabsf(delta_x); - delta_x = delta_x * 2; + delta_x = -fabsf(delta_x) * 2; } - // 直接设置新的比例,基于初始值 + 变化量 - float new_master_mfact_per = grabc->old_master_mfact_per + - delta_y; // 垂直:delta_y调整主区域高度 - float new_master_inner_per = grabc->old_master_inner_per + - delta_x; // 垂直:delta_x调整主区域内部宽度 - float new_stack_inner_per = grabc->old_stack_inner_per + - delta_x; // 垂直:delta_x调整栈区域内部宽度 + float new_master_mfact_per = grabc->old_master_mfact_per + delta_y; + float new_master_inner_per = grabc->old_master_inner_per + delta_x; + float new_stack_inner_per = grabc->old_stack_inner_per + delta_x; - // 应用限制,确保比例在合理范围内 new_master_mfact_per = fmaxf(0.1f, fminf(0.9f, new_master_mfact_per)); new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); - // 应用到所有平铺窗口 - wl_list_for_each(tc, &clients, link) { - if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { - tc->master_mfact_per = new_master_mfact_per; + // 实时缩放同组其他窗口的比例,保持组内总和为 1, + // 不然增加的比例并不是排布后的比例 + + if (isdrag) { + if (grabc->ismaster) { + float cur_other_sum = 1.0f - grabc->master_inner_per; + float new_other_sum = 1.0f - new_master_inner_per; + if (cur_other_sum > 0.001f) { + float scale = new_other_sum / cur_other_sum; + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc) && + tc->ismaster && tc != grabc) + tc->master_inner_per *= scale; + } + } + } else { + /* 所有栈窗口(垂直布局没有左侧/右侧区分) */ + float cur_other_sum = 1.0f - grabc->stack_inner_per; + float new_other_sum = 1.0f - new_stack_inner_per; + if (cur_other_sum > 0.001f) { + float scale = new_other_sum / cur_other_sum; + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc) && + !tc->ismaster && tc != grabc) + tc->stack_inner_per *= scale; + } + } + } + } else { + /* 键盘步进 */ + wl_list_for_each(tc, &clients, link) { + if (!VISIBLEON(tc, grabc->mon) || !ISTILED(tc)) + continue; + if (tc != grabc) { + if (!tc->ismaster && new_stack_inner_per != 1.0f && + grabc->old_stack_inner_per != 1.0f) + tc->stack_inner_per = (1 - new_stack_inner_per) / + (1 - grabc->old_stack_inner_per) * + tc->stack_inner_per; + if (tc->ismaster && new_master_inner_per != 1.0f && + grabc->old_master_inner_per != 1.0f) + tc->master_inner_per = + (1.0f - new_master_inner_per) / + (1.0f - grabc->old_master_inner_per) * + tc->master_inner_per; + } } } grabc->master_inner_per = new_master_inner_per; grabc->stack_inner_per = new_stack_inner_per; + /* 广播 master_mfact_per */ + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) + tc->master_mfact_per = new_master_mfact_per; + } + if (!isdrag) { arrange(grabc->mon, false, false); return; } if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_tile_refresh_interval) { + time - last_apply_drap_time > config.drag_tile_refresh_interval) { arrange(grabc->mon, false, false); last_apply_drap_time = time; } } } +void resize_tile_dwindle(Client *grabc, bool isdrag, int32_t offsetx, + int32_t offsety, uint32_t time, bool isvertical) { + + if (!isdrag) { + dwindle_resize_client_step(grabc->mon, grabc, offsetx, offsety); + return; + } + + if (last_apply_drap_time == 0 || + time - last_apply_drap_time > config.drag_tile_refresh_interval) { + dwindle_resize_client(grabc->mon, grabc); + last_apply_drap_time = time; + } +} + +void resize_tile_grid_fair(Client *grabc, bool isdrag, int32_t offsetx, + int32_t offsety, uint32_t time) { + if (!grabc || grabc->isfullscreen || grabc->ismaximizescreen) + return; + Monitor *m = grabc->mon; + if (m->isoverview) + return; + + if (m->visible_tiling_clients <= 1) + return; + + // 获取当前布局 ID + const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; + + if (!start_drag_window && isdrag) { + drag_begin_cursorx = cursor->x; + drag_begin_cursory = cursor->y; + start_drag_window = true; + + Client *c; + wl_list_for_each(c, &clients, link) { + c->old_grid_col_per = + (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; + c->old_grid_row_per = + (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; + } + + grabc->old_grid_col_per = grabc->grid_col_per; + grabc->old_grid_row_per = grabc->grid_row_per; + + grabc->cursor_in_left_half = + cursor->x < grabc->geom.x + grabc->geom.width / 2; + grabc->cursor_in_upper_half = + cursor->y < grabc->geom.y + grabc->geom.height / 2; + grabc->drag_begin_geom = grabc->geom; + } else { + if (isdrag) { + offsetx = cursor->x - drag_begin_cursorx; + offsety = cursor->y - drag_begin_cursory; + } else { + grabc->drag_begin_geom = grabc->geom; + Client *c; + wl_list_for_each(c, &clients, link) { + c->old_grid_col_per = + (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; + c->old_grid_row_per = + (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; + } + grabc->cursor_in_upper_half = false; + grabc->cursor_in_left_half = false; + } + + // 以屏幕分辨率为基准算出缩放比变化的量 + float delta_x = (float)offsetx * grabc->old_grid_col_per / + grabc->drag_begin_geom.width; + float delta_y = (float)offsety * grabc->old_grid_row_per / + grabc->drag_begin_geom.height; + + int adj_c_idx = grabc->grid_col_idx; + int adj_r_idx = grabc->grid_row_idx; + float sign_x = 1.0f, sign_y = 1.0f; + + if (isdrag) { + if (grabc->cursor_in_left_half) { + adj_c_idx -= 1; + sign_x = -1.0f; + } else { + adj_c_idx += 1; + sign_x = 1.0f; + } + + if (grabc->cursor_in_upper_half) { + adj_r_idx -= 1; + sign_y = -1.0f; + } else { + adj_r_idx += 1; + sign_y = 1.0f; + } + } + // 键盘热键逻辑不变 + int max_col = -1, max_row = -1, min_col = INT32_MAX, + min_row = INT32_MAX; + Client *tmp; + wl_list_for_each(tmp, &clients, link) { + if (tmp->mon != m || !VISIBLEON(tmp, m) || !ISTILED(tmp)) + continue; + if (tmp->grid_col_idx > max_col) + max_col = tmp->grid_col_idx; + if (tmp->grid_row_idx > max_row) + max_row = tmp->grid_row_idx; + if (tmp->grid_col_idx < min_col) + min_col = tmp->grid_col_idx; + if (tmp->grid_row_idx < min_row) + min_row = tmp->grid_row_idx; + } + + adj_c_idx = grabc->grid_col_idx + 1; + adj_r_idx = grabc->grid_row_idx + 1; + sign_x = 1.0f; + sign_y = 1.0f; + + if (grabc->grid_col_idx == max_col) { + adj_c_idx = grabc->grid_col_idx - 1; + sign_x = -1.0f; + } + if (grabc->grid_row_idx == max_row) { + adj_r_idx = grabc->grid_row_idx - 1; + sign_y = -1.0f; + } + if (grabc->grid_col_idx == min_col) { + adj_c_idx = grabc->grid_col_idx + 1; + sign_x = 1.0f; + } + if (grabc->grid_row_idx == min_row) { + adj_r_idx = grabc->grid_row_idx + 1; + sign_y = 1.0f; + } + + float dx = delta_x * sign_x; + float dy = delta_y * sign_y; + + float my_old_col = grabc->old_grid_col_per; + float my_old_row = grabc->old_grid_row_per; + float adj_old_col = -1.0f, adj_old_row = -1.0f; + + Client *c; + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !VISIBLEON(c, m) || !ISTILED(c)) + continue; + if (c->grid_col_idx == adj_c_idx && adj_old_col < 0) + adj_old_col = c->old_grid_col_per; + if (c->grid_row_idx == adj_r_idx && adj_old_row < 0) + adj_old_row = c->old_grid_row_per; + } + + // 应用列宽调节 + if (adj_old_col > 0.0f) { + float dx_clamped = dx; + if (my_old_col + dx_clamped < 0.1f) + dx_clamped = 0.1f - my_old_col; + if (adj_old_col - dx_clamped < 0.1f) + dx_clamped = adj_old_col - 0.1f; + + float new_my_col = my_old_col + dx_clamped; + float new_adj_col = adj_old_col - dx_clamped; + + // 处理被强行锁死在 1.0f 的列边界,头部是个错位窗口 + if (current_layout && current_layout->id == VERTICAL_FAIR) { + int32_t n_tiling = m->visible_tiling_clients; + int32_t l_rows; + for (l_rows = 0; l_rows <= n_tiling; l_rows++) { + if (l_rows * l_rows >= n_tiling) + break; + } + int32_t base_cols = n_tiling / l_rows; + // 当调节边界恰好处于非对称的锁死列(如 3 窗口下的 col 0 与 col + // 1 之间) + if ((grabc->grid_col_idx == base_cols - 1 && + adj_c_idx == base_cols) || + (grabc->grid_col_idx == base_cols && + adj_c_idx == base_cols - 1)) { + + float p_col = + (grabc->grid_col_idx == base_cols - 1) + ? (my_old_col + dx) / (my_old_col + adj_old_col) + : (adj_old_col - dx) / (my_old_col + adj_old_col); + if (p_col < 0.01f) + p_col = 0.01f; + if (p_col > 0.99f) + p_col = 0.99f; + + // 反推非线性真实权重值 + float new_r_var_per = p_col / (1.0f - p_col); + if (new_r_var_per < 0.1f) + new_r_var_per = 0.1f; + if (new_r_var_per > 10.0f) + new_r_var_per = 10.0f; + + if (grabc->grid_col_idx == base_cols - 1) { + new_my_col = new_r_var_per; + new_adj_col = 1.0f; + } else { + new_my_col = 1.0f; + new_adj_col = new_r_var_per; + } + } + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !VISIBLEON(c, m) || !ISTILED(c)) + continue; + if (c->grid_col_idx == grabc->grid_col_idx) + c->grid_col_per = new_my_col; + if (c->grid_col_idx == adj_c_idx) + c->grid_col_per = new_adj_col; + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !VISIBLEON(c, m) || !ISTILED(c)) + continue; + if (c->grid_row_idx == 0) { + if (c->grid_col_idx == grabc->grid_col_idx) + c->grid_col_per = new_my_col; + else if (c->grid_col_idx == adj_c_idx) + c->grid_col_per = new_adj_col; + } + } + } + + // 应用行高调节 + if (adj_old_row > 0.0f) { + float dy_clamped = dy; + if (my_old_row + dy_clamped < 0.1f) + dy_clamped = 0.1f - my_old_row; + if (adj_old_row - dy_clamped < 0.1f) + dy_clamped = adj_old_row - 0.1f; + + float new_my_row = my_old_row + dy_clamped; + float new_adj_row = adj_old_row - dy_clamped; + + // 处理被强行锁死在 1.0f 的行边界,头部是个错位窗口 + if (current_layout && current_layout->id == FAIR) { + int32_t n_tiling = m->visible_tiling_clients; + int32_t l_cols; + for (l_cols = 0; l_cols <= n_tiling; l_cols++) { + if (l_cols * l_cols >= n_tiling) + break; + } + int32_t base_rows = n_tiling / l_cols; + // 当调节边界恰好处于非对称的锁死行(如 3 窗口下的 row 0 与 row + // 1 之间) + if ((grabc->grid_row_idx == base_rows - 1 && + adj_r_idx == base_rows) || + (grabc->grid_row_idx == base_rows && + adj_r_idx == base_rows - 1)) { + + float p_row = + (grabc->grid_row_idx == base_rows - 1) + ? (my_old_row + dy) / (my_old_row + adj_old_row) + : (adj_old_row - dy) / (my_old_row + adj_old_row); + if (p_row < 0.01f) + p_row = 0.01f; + if (p_row > 0.99f) + p_row = 0.99f; + + // 反推非线性真实权重值 + float new_r_var_per = p_row / (1.0f - p_row); + if (new_r_var_per < 0.1f) + new_r_var_per = 0.1f; + if (new_r_var_per > 10.0f) + new_r_var_per = 10.0f; + + if (grabc->grid_row_idx == base_rows - 1) { + new_my_row = new_r_var_per; + new_adj_row = 1.0f; + } else { + new_my_row = 1.0f; + new_adj_row = new_r_var_per; + } + } + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !VISIBLEON(c, m) || !ISTILED(c)) + continue; + if (c->grid_row_idx == grabc->grid_row_idx) + c->grid_row_per = new_my_row; + if (c->grid_row_idx == adj_r_idx) + c->grid_row_per = new_adj_row; + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !VISIBLEON(c, m) || !ISTILED(c)) + continue; + if (c->grid_col_idx == 0) { + if (c->grid_row_idx == grabc->grid_row_idx) + c->grid_row_per = new_my_row; + else if (c->grid_row_idx == adj_r_idx) + c->grid_row_per = new_adj_row; + } + } + } + + if (!isdrag) { + arrange(m, false, false); + return; + } + + if (last_apply_drap_time == 0 || + time - last_apply_drap_time > config.drag_tile_refresh_interval) { + arrange(m, false, false); + last_apply_drap_time = time; + } + } +} + void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, int32_t offsety, uint32_t time, bool isvertical) { + + if (!grabc || grabc->isfullscreen || grabc->ismaximizescreen) + return; + if (grabc->mon->isoverview) + return; + + Monitor *m = grabc->mon; + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = m->pertag->scroller_state[tag]; + if (!st) + return; + + struct ScrollerStackNode *curnode = find_scroller_node(st, grabc); + if (!curnode) + return; + + struct ScrollerStackNode *headnode = curnode; + while (headnode->prev_in_stack) + headnode = headnode->prev_in_stack; + + Client *stack_head_client = headnode->client; + + if (m->visible_scroll_tiling_clients == 1 && + !config.scroller_ignore_proportion_single) + return; + float delta_x, delta_y; float new_scroller_proportion; float new_stack_proportion; - Client *stack_head = get_scroll_stack_head(grabc); - - if (grabc && grabc->mon->visible_tiling_clients == 1 && - !scroller_ignore_proportion_single) - return; if (!start_drag_window && isdrag) { drag_begin_cursorx = cursor->x; drag_begin_cursory = cursor->y; start_drag_window = true; - // 记录初始状态 - stack_head->old_scroller_pproportion = stack_head->scroller_proportion; - grabc->old_stack_proportion = grabc->stack_proportion; + headnode->client->old_scroller_pproportion = + headnode->scroller_proportion; + grabc->old_stack_proportion = curnode->stack_proportion; grabc->cursor_in_left_half = cursor->x < grabc->geom.x + grabc->geom.width / 2; grabc->cursor_in_upper_half = cursor->y < grabc->geom.y + grabc->geom.height / 2; - // 记录初始几何信息 grabc->drag_begin_geom = grabc->geom; } else { - // 计算相对于屏幕尺寸的比例变化 - // 计算相对于屏幕尺寸的比例变化 if (isdrag) { - offsetx = cursor->x - drag_begin_cursorx; offsety = cursor->y - drag_begin_cursory; } else { @@ -412,37 +829,33 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, grabc->old_master_inner_per = grabc->master_inner_per; grabc->old_stack_inner_per = grabc->stack_inner_per; grabc->drag_begin_geom = grabc->geom; - stack_head->old_scroller_pproportion = - stack_head->scroller_proportion; - grabc->old_stack_proportion = grabc->stack_proportion; + stack_head_client->old_scroller_pproportion = + headnode->scroller_proportion; + grabc->old_stack_proportion = curnode->stack_proportion; grabc->cursor_in_upper_half = false; grabc->cursor_in_left_half = false; } if (isvertical) { delta_y = (float)(offsety) * - (stack_head->old_scroller_pproportion) / + (headnode->client->old_scroller_pproportion) / grabc->drag_begin_geom.height; delta_x = (float)(offsetx) * (grabc->old_stack_proportion) / grabc->drag_begin_geom.width; } else { delta_x = (float)(offsetx) * - (stack_head->old_scroller_pproportion) / + (headnode->client->old_scroller_pproportion) / grabc->drag_begin_geom.width; delta_y = (float)(offsety) * (grabc->old_stack_proportion) / grabc->drag_begin_geom.height; } - bool moving_up; - bool moving_down; - bool moving_left; - bool moving_right; - + bool moving_up, moving_down, moving_left, moving_right; if (!isdrag) { - moving_up = offsety < 0 ? true : false; - moving_down = offsety > 0 ? true : false; - moving_left = offsetx < 0 ? true : false; - moving_right = offsetx > 0 ? true : false; + moving_up = offsety < 0; + moving_down = offsety > 0; + moving_left = offsetx < 0; + moving_right = offsetx > 0; } else { moving_up = cursor->y < drag_begin_cursory; moving_down = cursor->y > drag_begin_cursory; @@ -452,10 +865,8 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, if ((grabc->cursor_in_upper_half && moving_up) || (!grabc->cursor_in_upper_half && moving_down)) { - // 光标在窗口上方且向上移动,或在窗口下方且向下移动 → 增加高度 delta_y = fabsf(delta_y); } else { - // 其他情况 → 减小高度 delta_y = -fabsf(delta_y); } @@ -467,89 +878,107 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, } if (isvertical) { - if (!grabc->next_in_stack && grabc->prev_in_stack && !isdrag) { + if (!curnode->next_in_stack && curnode->prev_in_stack && !isdrag) { delta_x = delta_x * -1.0f; } - if (!grabc->next_in_stack && grabc->prev_in_stack && isdrag) { - if (moving_right) { + if (!curnode->next_in_stack && curnode->prev_in_stack && isdrag) { + if (moving_right) delta_x = -fabsf(delta_x); - } else { + else delta_x = fabsf(delta_x); - } } - if (!grabc->prev_in_stack && grabc->next_in_stack && isdrag) { - if (moving_left) { + if (!curnode->prev_in_stack && curnode->next_in_stack && isdrag) { + if (moving_left) delta_x = -fabsf(delta_x); - } else { + else delta_x = fabsf(delta_x); - } } - if (isdrag) { - if (moving_up) { + if (moving_up) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } - } else { - if (!grabc->next_in_stack && grabc->prev_in_stack && !isdrag) { + if (!curnode->next_in_stack && curnode->prev_in_stack && !isdrag) { delta_y = delta_y * -1.0f; } - if (!grabc->next_in_stack && grabc->prev_in_stack && isdrag) { - if (moving_down) { + if (!curnode->next_in_stack && curnode->prev_in_stack && isdrag) { + if (moving_down) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } - if (!grabc->prev_in_stack && grabc->next_in_stack && isdrag) { - if (moving_up) { + if (!curnode->prev_in_stack && curnode->next_in_stack && isdrag) { + if (moving_up) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } - if (isdrag) { - if (moving_left) { + if (moving_left) delta_x = -fabsf(delta_x); - } else { + else delta_x = fabsf(delta_x); - } } } - // 直接设置新的比例,基于初始值 + 变化量 if (isvertical) { new_scroller_proportion = - stack_head->old_scroller_pproportion + delta_y; + headnode->client->old_scroller_pproportion + delta_y; new_stack_proportion = grabc->old_stack_proportion + delta_x; - } else { new_scroller_proportion = - stack_head->old_scroller_pproportion + delta_x; + headnode->client->old_scroller_pproportion + delta_x; new_stack_proportion = grabc->old_stack_proportion + delta_y; } - // 应用限制,确保比例在合理范围内 new_scroller_proportion = fmaxf(0.1f, fminf(1.0f, new_scroller_proportion)); - new_stack_proportion = fmaxf(0.1f, fminf(1.0f, new_stack_proportion)); + new_stack_proportion = fmaxf(0.1f, fminf(0.9f, new_stack_proportion)); - grabc->stack_proportion = new_stack_proportion; + // 保持总和为 1,避免后续 arrange 归一化吞掉位移 + if (isdrag) { + float current_other_sum = 1.0f - curnode->stack_proportion; + float new_other_sum = 1.0f - new_stack_proportion; + if (current_other_sum > 0.001f) { + float scale = new_other_sum / current_other_sum; + for (struct ScrollerStackNode *tc = headnode; tc; + tc = tc->next_in_stack) { + if (tc != curnode) { + tc->stack_proportion *= scale; + } + } + } + } else { + // 键盘步进 + if (grabc->old_stack_proportion != 1.0f) { + for (struct ScrollerStackNode *tc = headnode; tc; + tc = tc->next_in_stack) { + if (tc != curnode) { + tc->stack_proportion = + (1.0f - new_stack_proportion) / + (1.0f - grabc->old_stack_proportion) * + tc->stack_proportion; + } + } + } + } - stack_head->scroller_proportion = new_scroller_proportion; + curnode->stack_proportion = new_stack_proportion; + headnode->scroller_proportion = new_scroller_proportion; + + /* 同步回全局字段 */ + sync_scroller_state_to_clients(m, tag); if (!isdrag) { - arrange(grabc->mon, false, false); + arrange(m, false, false); return; } if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_tile_refresh_interval) { - arrange(grabc->mon, false, false); + time - last_apply_drap_time > config.drag_tile_refresh_interval) { + arrange(m, false, false); last_apply_drap_time = time; } } @@ -567,8 +996,7 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx, const Layout *current_layout = grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]; if (current_layout->id == TILE || current_layout->id == DECK || - current_layout->id == CENTER_TILE || current_layout->id == RIGHT_TILE || - (current_layout->id == TGMIX && grabc->mon->visible_tiling_clients <= 3) + current_layout->id == CENTER_TILE || current_layout->id == RIGHT_TILE ) { resize_tile_master_horizontal(grabc, isdrag, offsetx, offsety, time, @@ -581,6 +1009,25 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx, resize_tile_scroller(grabc, isdrag, offsetx, offsety, time, false); } else if (current_layout->id == VERTICAL_SCROLLER) { resize_tile_scroller(grabc, isdrag, offsetx, offsety, time, true); + } else if (current_layout->id == DWINDLE) { + resize_tile_dwindle(grabc, isdrag, offsetx, offsety, time, true); + } else if (current_layout->id == GRID || + current_layout->id == VERTICAL_GRID || + current_layout->id == FAIR || + current_layout->id == VERTICAL_FAIR) { + resize_tile_grid_fair(grabc, isdrag, offsetx, offsety, time); + } +} + +/* If there are no calculation omissions, +these two functions will never be triggered. +Just in case to facilitate the final investigation*/ + +void check_size_per_valid(Client *c) { + if (c->ismaster) { + assert(c->master_inner_per > 0.0f && c->master_inner_per <= 1.0f); + } else { + assert(c->stack_inner_per > 0.0f && c->stack_inner_per <= 1.0f); } } @@ -598,7 +1045,8 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, if (m->pertag->ltidxs[m->pertag->curtag]->id != CENTER_TILE) { wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISTILED(c)) { + if (VISIBLEON(c, m) && ISFAKETILED(c)) { + if (total_master_inner_percent > 0.0 && i < nmasters) { c->ismaster = true; c->stack_inner_per = stack_num ? 1.0f / stack_num : 1.0f; @@ -614,17 +1062,20 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, : 1.0f; } i++; + + check_size_per_valid(c); } } } else { wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISTILED(c)) { + if (VISIBLEON(c, m) && ISFAKETILED(c)) { + if (total_master_inner_percent > 0.0 && i < nmasters) { c->ismaster = true; if ((stack_index % 2) ^ (tile_cilent_num % 2 == 0)) { c->stack_inner_per = - stack_num > 1 ? 1.0f / ((stack_num - 1) / 2) : 1.0f; - + stack_num > 1 ? 1.0f / ((stack_num - 1) / 2.0f) + : 1.0f; } else { c->stack_inner_per = stack_num > 1 ? 2.0f / stack_num : 1.0f; @@ -653,13 +1104,15 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, } } i++; + + check_size_per_valid(c); } } } } -void // 17 -arrange(Monitor *m, bool want_animation, bool from_view) { +void pre_caculate_before_arrange(Monitor *m, bool want_animation, + bool from_view, bool only_caculate) { Client *c = NULL; double total_stack_inner_percent = 0; double total_master_inner_percent = 0; @@ -671,25 +1124,39 @@ arrange(Monitor *m, bool want_animation, bool from_view) { int32_t master_num = 0; int32_t stack_num = 0; - if (!m) - return; - - if (!m->wlr_output->enabled) - return; m->visible_clients = 0; m->visible_tiling_clients = 0; m->visible_scroll_tiling_clients = 0; + m->visible_fake_tiling_clients = 0; + + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = m->pertag->scroller_state[tag]; + + const Layout *cur_layout = m->pertag->ltidxs[m->pertag->curtag]; + if (cur_layout->id == SCROLLER || cur_layout->id == VERTICAL_SCROLLER) { + update_scroller_state(m); + } wl_list_for_each(c, &clients, link) { - if (!client_only_in_one_tag(c) || c->isglobal || c->isunglobal) { - exit_scroller_stack(c); - } - if (from_view && (c->isglobal || c->isunglobal)) { set_size_per(m, c); } + if (m->is_jump_mode && !c->jump_label_node) { + client_add_jump_label_node(c); + } + + if (m->pertag->ltidxs[m->pertag->curtag]->id == MONOCLE && + !c->tab_bar_node) { + client_add_tab_bar_node(c); + } + + if (c->tab_bar_node && c->mon == m) { + wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, + false); + } + if (c->mon == m && (c->isglobal || c->isunglobal)) { c->tags = m->tagset[m->seltags]; } @@ -708,10 +1175,19 @@ arrange(Monitor *m, bool want_animation, bool from_view) { if (ISTILED(c)) { m->visible_tiling_clients++; + + /* 更新可见滚动客户端计数 */ + if (st) { + struct ScrollerStackNode *n = find_scroller_node(st, c); + if (n && !n->prev_in_stack) /* 是堆叠头部 */ + m->visible_scroll_tiling_clients++; + } else if (ISSCROLLTILED(c)) { + m->visible_scroll_tiling_clients++; + } } - if (ISSCROLLTILED(c) && !c->prev_in_stack) { - m->visible_scroll_tiling_clients++; + if (ISFAKETILED(c)) { + m->visible_fake_tiling_clients++; } } } @@ -724,8 +1200,7 @@ arrange(Monitor *m, bool want_animation, bool from_view) { if (c->mon == m) { if (VISIBLEON(c, m)) { - if (ISTILED(c)) { - + if (ISFAKETILED(c)) { if (i < nmasters) { master_num++; total_master_inner_percent += c->master_inner_per; @@ -744,18 +1219,20 @@ arrange(Monitor *m, bool want_animation, bool from_view) { c->stack_inner_per; } } - i++; } - set_arrange_visible(m, c, want_animation); + if (!only_caculate) + set_arrange_visible(m, c, want_animation); } else { - set_arrange_hidden(m, c, want_animation); + if (!only_caculate) + set_arrange_hidden(m, c, want_animation); } } - if (c->mon == m && c->ismaximizescreen && !c->animation.tagouted && - !c->animation.tagouting && VISIBLEON(c, m)) { + if (!only_caculate && c->mon == m && c->ismaximizescreen && + !c->animation.tagouted && !c->animation.tagouting && + VISIBLEON(c, m)) { reset_maximizescreen_size(c); } } @@ -764,6 +1241,22 @@ arrange(Monitor *m, bool want_animation, bool from_view) { m, m->visible_tiling_clients, total_left_stack_hight_percent, total_right_stack_hight_percent, total_stack_inner_percent, total_master_inner_percent, master_num, stack_num); +} + +void // 17 +arrange(Monitor *m, bool want_animation, bool from_view) { + + if (!m) + return; + + if (!m->wlr_output->enabled) + return; + + if (!m->sel) { + m->sel = focustop(m); + } + + pre_caculate_before_arrange(m, want_animation, from_view, false); if (m->isoverview) { overviewlayout.arrange(m); @@ -776,5 +1269,5 @@ arrange(Monitor *m, bool want_animation, bool from_view) { checkidleinhibitor(NULL); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } diff --git a/src/layout/dwindle.h b/src/layout/dwindle.h new file mode 100644 index 00000000..5e56a6bf --- /dev/null +++ b/src/layout/dwindle.h @@ -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]); +} \ No newline at end of file diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index e1a335d1..96c0ed54 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -1,446 +1,237 @@ -// 网格布局窗口大小和位置计算 -void grid(Monitor *m) { - int32_t i, n; - int32_t cx, cy, cw, ch; - int32_t dx; - int32_t cols, rows, overcols; - Client *c = NULL; - n = 0; - int32_t target_gappo = - enablegaps ? m->isoverview ? overviewgappo : gappoh : 0; - int32_t target_gappi = - enablegaps ? m->isoverview ? overviewgappi : gappih : 0; - float single_width_ratio = m->isoverview ? 0.7 : 0.9; - float single_height_ratio = m->isoverview ? 0.8 : 0.9; - - n = m->isoverview ? m->visible_clients : m->visible_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))) { - cw = (m->w.width - 2 * target_gappo) * single_width_ratio; - ch = (m->w.height - 2 * target_gappo) * single_height_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); - return; - } - } - } - - if (n == 2) { - cw = (m->w.width - 2 * target_gappo - target_gappi) / 2; - ch = (m->w.height - 2 * target_gappo) * 0.65; - 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))) { - if (i == 0) { - c->geom.x = m->w.x + target_gappo; - c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - } else if (i == 1) { - c->geom.x = m->w.x + cw + target_gappo + target_gappi; - c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - } - i++; - } - } - return; - } - - // 计算列数和行数 - for (cols = 0; cols <= n / 2; cols++) { - if (cols * cols >= n) { - break; - } - } - rows = (cols && (cols - 1) * cols >= n) ? cols - 1 : cols; - - // 计算每个客户端的高度和宽度 - ch = (m->w.height - 2 * target_gappo - (rows - 1) * target_gappi) / rows; - cw = (m->w.width - 2 * target_gappo - (cols - 1) * target_gappi) / cols; - - // 处理多余的列 - overcols = n % cols; - if (overcols) { - dx = (m->w.width - overcols * cw - (overcols - 1) * target_gappi) / 2 - - target_gappo; - } - - // 调整每个客户端的位置和大小 - 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 % cols) * (cw + target_gappi); - cy = m->w.y + (i / cols) * (ch + target_gappi); - if (overcols && i >= n - overcols) { - cx += dx; - } - 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); - i++; - } - } -} - -void deck(Monitor *m) { - int32_t mw, my; - int32_t i, n = 0; +void tile(Monitor *m) { + int32_t i, n = 0, h, r, ie = enablegaps, mw, my, ty; Client *c = NULL; Client *fc = NULL; - float mfact; - uint32_t nmasters = m->pertag->nmasters[m->pertag->curtag]; + double mfact = 0; + int32_t master_num = 0; + int32_t stack_num = 0; - int32_t cur_gappih = enablegaps ? m->gappih : 0; - int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - int32_t cur_gappov = enablegaps ? m->gappov : 0; - - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - - 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; if (n == 0) return; - wl_list_for_each(fc, &clients, link) { + 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 (VISIBLEON(fc, m) && ISTILED(fc)) + cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappoh; + + wl_list_for_each(fc, &clients, link) { + 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]; - // Calculate master width including outer gaps - if (n > nmasters) - mw = nmasters ? round((m->w.width - 2 * cur_gappoh) * mfact) : 0; + if (n > m->pertag->nmasters[m->pertag->curtag]) + mw = m->pertag->nmasters[m->pertag->curtag] + ? (m->w.width + cur_gappih * ie) * mfact + : 0; else - mw = m->w.width - 2 * cur_gappoh; + mw = m->w.width - 2 * cur_gappoh + cur_gappih * ie; + + i = 0; + my = ty = cur_gappov; + + int32_t master_surplus_height = + (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (master_num - 1)); + float master_surplus_ratio = 1.0; + + int32_t slave_surplus_height = + (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (stack_num - 1)); + float slave_surplus_ratio = 1.0; - i = my = 0; wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; - if (i < nmasters) { - c->master_mfact_per = mfact; - // Master area clients - resize( - c, - (struct wlr_box){.x = m->w.x + cur_gappoh, - .y = m->w.y + cur_gappov + my, - .width = mw, - .height = (m->w.height - 2 * cur_gappov - my) / - (MIN(n, nmasters) - i)}, - 0); - my += c->geom.height; + if (i < m->pertag->nmasters[m->pertag->curtag]) { + r = MANGO_MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; + if (c->master_inner_per > 0.0f) { + h = master_surplus_height * c->master_inner_per / + master_surplus_ratio; + master_surplus_height = master_surplus_height - h; + master_surplus_ratio = + master_surplus_ratio - c->master_inner_per; + c->master_mfact_per = mfact; + } else { + h = (m->w.height - my - cur_gappov - + cur_gappiv * ie * (r - 1)) / + r; + c->master_inner_per = h / (m->w.height - my - cur_gappov - + cur_gappiv * ie * (r - 1)); + c->master_mfact_per = mfact; + } + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + cur_gappoh, + .y = m->w.y + my, + .width = mw - cur_gappih * ie, + .height = h}, + 0); + my += h + cur_gappiv * ie; // 使用理论高度累加 } else { - // Stack area clients - c->master_mfact_per = mfact; - resize(c, - (struct wlr_box){.x = m->w.x + mw + cur_gappoh + cur_gappih, - .y = m->w.y + cur_gappov, - .width = m->w.width - mw - 2 * cur_gappoh - - cur_gappih, - .height = m->w.height - 2 * cur_gappov}, - 0); - if (c == focustop(m)) - wlr_scene_node_raise_to_top(&c->scene->node); + r = n - i; + if (c->stack_inner_per > 0.0f) { + h = slave_surplus_height * c->stack_inner_per / + slave_surplus_ratio; + slave_surplus_height = slave_surplus_height - h; + slave_surplus_ratio = slave_surplus_ratio - c->stack_inner_per; + c->master_mfact_per = mfact; + } else { + h = (m->w.height - ty - cur_gappov - + cur_gappiv * ie * (r - 1)) / + r; + c->stack_inner_per = h / (m->w.height - ty - cur_gappov - + cur_gappiv * ie * (r - 1)); + c->master_mfact_per = mfact; + } + + client_tile_resize( + c, + (struct wlr_box){.x = m->w.x + mw + cur_gappoh, + .y = m->w.y + ty, + .width = m->w.width - mw - 2 * cur_gappoh, + .height = h}, + 0); + ty += h + cur_gappiv * ie; // 使用理论高度累加 } i++; } } -void horizontal_scroll_adjust_fullandmax(Client *c, - struct wlr_box *target_geom) { - Monitor *m = c->mon; - int32_t cur_gappih = enablegaps ? m->gappih : 0; - int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - int32_t cur_gappov = enablegaps ? m->gappov : 0; +void right_tile(Monitor *m) { + int32_t i, n = 0, h, r, ie = enablegaps, mw, my, ty; + Client *c = NULL; + Client *fc = NULL; + double mfact = 0; + int32_t master_num = 0; + int32_t stack_num = 0; - cur_gappih = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappoh = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + 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; - if (c->isfullscreen) { - target_geom->height = m->m.height; - target_geom->width = m->m.width; - target_geom->y = m->m.y; - return; - } - - if (c->ismaximizescreen) { - target_geom->height = m->w.height - 2 * cur_gappov; - target_geom->width = m->w.width - 2 * cur_gappoh; - target_geom->y = m->w.y + cur_gappov; - return; - } - - target_geom->height = m->w.height - 2 * cur_gappov; - target_geom->y = m->w.y + (m->w.height - target_geom->height) / 2; -} - -void arrange_stack(Client *scroller_stack_head, struct wlr_box geometry, - int32_t gappiv) { - int32_t stack_size = 0; - Client *iter = scroller_stack_head; - - while (iter) { - stack_size++; - iter = iter->next_in_stack; - } - - if (stack_size == 0) + if (n == 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_height; - int32_t current_y = geometry.y; - int32_t remain_client_height = geometry.height - (stack_size - 1) * gappiv; - float remain_proportion = 1.0f; - - iter = scroller_stack_head; - while (iter) { - - client_height = - remain_client_height * (iter->stack_proportion / remain_proportion); - - struct wlr_box client_geom = {.x = geometry.x, - .y = current_y, - .width = geometry.width, - .height = client_height}; - resize(iter, client_geom, 0); - remain_proportion -= iter->stack_proportion; - remain_client_height -= client_height; - current_y += client_height + gappiv; - iter = iter->next_in_stack; - } -} - -void horizontal_check_scroller_root_inside_mon(Client *c, - struct wlr_box *geometry) { - if (!GEOMINSIDEMON(geometry, c->mon)) { - geometry->x = c->mon->w.x + (c->mon->w.width - geometry->width) / 2; - } -} - -// 滚动布局 -void scroller(Monitor *m) { - int32_t i, n, j; - float single_proportion = 1.0; - - Client *c = NULL, *root_client = NULL; - Client **tempClients = NULL; // 初始化为 NULL - struct wlr_box target_geom; - int32_t focus_client_index = 0; - bool need_scroller = false; - int32_t cur_gappih = enablegaps ? m->gappih : 0; - int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - int32_t cur_gappov = enablegaps ? m->gappov : 0; 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; - cur_gappih = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappoh = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappoh; - int32_t max_client_width = m->w.width - 2 * scroller_structs - cur_gappih; - - n = m->visible_scroll_tiling_clients; - - if (n == 0) { - return; // 没有需要处理的客户端,直接返回 - } - - // 动态分配内存 - tempClients = malloc(n * sizeof(Client *)); - if (!tempClients) { - // 处理内存分配失败的情况 - return; - } - - // 第二次遍历,填充 tempClients - 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.height = m->w.height - 2 * cur_gappov; - target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion; - target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; - target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; - horizontal_check_scroller_root_inside_mon(c, &target_geom); - arrange_stack(c, target_geom, cur_gappiv); - 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.x >= m->w.x + scroller_structs && - c->geom.x + c->geom.width <= - m->w.x + m->w.width - scroller_structs) { - need_scroller = false; - } else { - need_scroller = true; - } - focus_client_index = i; + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; - } } - if (n == 1 && scroller_ignore_proportion_single) { - need_scroller = true; - } + mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per + : m->pertag->mfacts[m->pertag->curtag]; - if (start_drag_window) - need_scroller = false; + if (n > m->pertag->nmasters[m->pertag->curtag]) + mw = m->pertag->nmasters[m->pertag->curtag] + ? (m->w.width + cur_gappih * ie) * mfact + : 0; + else + mw = m->w.width - 2 * cur_gappoh + cur_gappih * ie; - target_geom.height = m->w.height - 2 * cur_gappov; - target_geom.width = max_client_width * c->scroller_proportion; - target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; - horizontal_scroll_adjust_fullandmax(tempClients[focus_client_index], - &target_geom); - if (tempClients[focus_client_index]->isfullscreen) { - target_geom.x = m->m.x; - horizontal_check_scroller_root_inside_mon( - tempClients[focus_client_index], &target_geom); - arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv); - } else if (tempClients[focus_client_index]->ismaximizescreen) { - target_geom.x = m->w.x + cur_gappoh; - horizontal_check_scroller_root_inside_mon( - tempClients[focus_client_index], &target_geom); - arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv); - } else if (need_scroller) { - if (scroller_focus_center || - ((!m->prevsel || - (ISSCROLLTILED(m->prevsel) && - (m->prevsel->scroller_proportion * max_client_width) + - (root_client->scroller_proportion * max_client_width) > - m->w.width - 2 * scroller_structs - cur_gappih)) && - scroller_prefer_center)) { - target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + i = 0; + my = ty = cur_gappov; + + int32_t master_surplus_height = + (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (master_num - 1)); + float master_surplus_ratio = 1.0; + + int32_t slave_surplus_height = + (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (stack_num - 1)); + float slave_surplus_ratio = 1.0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) + continue; + if (i < m->pertag->nmasters[m->pertag->curtag]) { + r = MANGO_MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; + if (c->master_inner_per > 0.0f) { + h = master_surplus_height * c->master_inner_per / + master_surplus_ratio; + master_surplus_height = master_surplus_height - h; + master_surplus_ratio = + master_surplus_ratio - c->master_inner_per; + c->master_mfact_per = mfact; + } else { + h = (m->w.height - my - cur_gappov - + cur_gappiv * ie * (r - 1)) / + r; + c->master_inner_per = h / (m->w.height - my - cur_gappov - + cur_gappiv * ie * (r - 1)); + c->master_mfact_per = mfact; + } + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + m->w.width - mw - + cur_gappoh + + cur_gappih * ie, + .y = m->w.y + my, + .width = mw - cur_gappih * ie, + .height = h}, + 0); + my += h + cur_gappiv * ie; // 使用理论高度累加 } else { - target_geom.x = root_client->geom.x > m->w.x + (m->w.width) / 2 - ? m->w.x + (m->w.width - - root_client->scroller_proportion * - max_client_width - - scroller_structs) - : m->w.x + scroller_structs; + r = n - i; + if (c->stack_inner_per > 0.0f) { + h = slave_surplus_height * c->stack_inner_per / + slave_surplus_ratio; + slave_surplus_height = slave_surplus_height - h; + slave_surplus_ratio = slave_surplus_ratio - c->stack_inner_per; + c->master_mfact_per = mfact; + } else { + h = (m->w.height - ty - cur_gappov - + cur_gappiv * ie * (r - 1)) / + r; + c->stack_inner_per = h / (m->w.height - ty - cur_gappov - + cur_gappiv * ie * (r - 1)); + c->master_mfact_per = mfact; + } + + client_tile_resize( + c, + (struct wlr_box){.x = m->w.x + cur_gappoh, + .y = m->w.y + ty, + .width = m->w.width - mw - 2 * cur_gappoh, + .height = h}, + 0); + ty += h + cur_gappiv * ie; // 使用理论高度累加 } - horizontal_check_scroller_root_inside_mon( - tempClients[focus_client_index], &target_geom); - arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv); - } else { - target_geom.x = c->geom.x; - horizontal_check_scroller_root_inside_mon( - tempClients[focus_client_index], &target_geom); - arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv); + i++; } - - for (i = 1; i <= focus_client_index; i++) { - c = tempClients[focus_client_index - i]; - target_geom.width = max_client_width * c->scroller_proportion; - horizontal_scroll_adjust_fullandmax(c, &target_geom); - target_geom.x = tempClients[focus_client_index - i + 1]->geom.x - - cur_gappih - target_geom.width; - - arrange_stack(c, target_geom, cur_gappiv); - } - - for (i = 1; i < n - focus_client_index; i++) { - c = tempClients[focus_client_index + i]; - target_geom.width = max_client_width * c->scroller_proportion; - horizontal_scroll_adjust_fullandmax(c, &target_geom); - target_geom.x = tempClients[focus_client_index + i - 1]->geom.x + - cur_gappih + - tempClients[focus_client_index + i - 1]->geom.width; - arrange_stack(c, target_geom, cur_gappiv); - } - - free(tempClients); // 最后释放内存 } void center_tile(Monitor *m) { @@ -451,10 +242,9 @@ void center_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; if (n == 0) @@ -462,7 +252,7 @@ void center_tile(Monitor *m) { // 获取第一个可见的平铺客户端用于主区域宽度百分比 wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc)) + if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; } @@ -473,10 +263,18 @@ void center_tile(Monitor *m) { int32_t cur_gappoh = enablegaps ? m->gappoh : 0; // 外部水平间隙 // 智能间隙处理 - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappoh; int32_t nmasters = m->pertag->nmasters[m->pertag->curtag]; mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per @@ -489,7 +287,8 @@ void center_tile(Monitor *m) { tw = mw; // 判断是否需要主区域铺满 - int32_t should_overspread = center_master_overspread && (n <= nmasters); + int32_t should_overspread = + config.center_master_overspread && (n <= nmasters); int32_t master_surplus_height = (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (master_num - 1)); @@ -515,14 +314,14 @@ void center_tile(Monitor *m) { mx = cur_gappoh + tw + cur_gappih * ie; } else if (n - nmasters == 1) { // 单个堆叠窗口的处理 - if (center_when_single_stack) { + if (config.center_when_single_stack) { // stack在右边,master居中,左边空着 tw = (m->w.width - mw) / 2 - cur_gappoh - cur_gappih * ie; - mx = cur_gappoh + tw + cur_gappih * ie; // master居中 + mx = cur_gappoh + tw + cur_gappih * ie; } else { // stack在右边,master在左边 tw = m->w.width - mw - 2 * cur_gappoh - cur_gappih * ie; - mx = cur_gappoh; // master在左边 + mx = cur_gappoh; } } else { // 只有主区域窗口:居中显示 @@ -533,7 +332,7 @@ void center_tile(Monitor *m) { // 主区域铺满模式(只有主区域窗口时) mw = m->w.width - 2 * cur_gappoh; mx = cur_gappoh; - tw = 0; // 堆叠区域宽度为0 + tw = 0; } oty = cur_gappov; @@ -541,12 +340,12 @@ void center_tile(Monitor *m) { i = 0; wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < nmasters) { // 主区域窗口 - r = MIN(n, nmasters) - i; + r = MANGO_MIN(n, nmasters) - i; if (c->master_inner_per > 0.0f) { h = master_surplus_height * c->master_inner_per / master_surplus_ratio; @@ -563,13 +362,13 @@ void center_tile(Monitor *m) { c->master_mfact_per = mfact; } - resize(c, - (struct wlr_box){.x = m->w.x + mx, - .y = m->w.y + my, - .width = mw, - .height = h}, - 0); - my += c->geom.height + cur_gappiv * ie; + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + mx, + .y = m->w.y + my, + .width = mw, + .height = h}, + 0); + my += h + cur_gappiv * ie; // 使用理论高度累加 } else { // 堆叠区域窗口 int32_t stack_index = i - nmasters; @@ -592,21 +391,19 @@ void center_tile(Monitor *m) { } int32_t stack_x; - if (center_when_single_stack) { - // 放在右侧(master居中时,stack在右边) + if (config.center_when_single_stack) { stack_x = m->w.x + mx + mw + cur_gappih * ie; } else { - // 放在右侧(master在左边时,stack在右边) stack_x = m->w.x + mx + mw + cur_gappih * ie; } - resize(c, - (struct wlr_box){.x = stack_x, - .y = m->w.y + ety, - .width = tw, - .height = h}, - 0); - ety += c->geom.height + cur_gappiv * ie; + client_tile_resize(c, + (struct wlr_box){.x = stack_x, + .y = m->w.y + ety, + .width = tw, + .height = h}, + 0); + ety += h + cur_gappiv * ie; // 使用理论高度累加 } else { // 多个堆叠窗口:交替放在左右两侧 r = (n - i + 1) / 2; @@ -633,13 +430,13 @@ void center_tile(Monitor *m) { int32_t stack_x = m->w.x + mx + mw + cur_gappih * ie; - resize(c, - (struct wlr_box){.x = stack_x, - .y = m->w.y + ety, - .width = tw, - .height = h}, - 0); - ety += c->geom.height + cur_gappiv * ie; + client_tile_resize(c, + (struct wlr_box){.x = stack_x, + .y = m->w.y + ety, + .width = tw, + .height = h}, + 0); + ety += h + cur_gappiv * ie; // 使用理论高度累加 } else { // 左侧堆叠窗口 if (c->stack_inner_per > 0.0f) { @@ -661,13 +458,13 @@ void center_tile(Monitor *m) { } int32_t stack_x = m->w.x + cur_gappoh; - resize(c, - (struct wlr_box){.x = stack_x, - .y = m->w.y + oty, - .width = tw, - .height = h}, - 0); - oty += c->geom.height + cur_gappiv * ie; + client_tile_resize(c, + (struct wlr_box){.x = stack_x, + .y = m->w.y + oty, + .width = tw, + .height = h}, + 0); + oty += h + cur_gappiv * ie; // 使用理论高度累加 } } } @@ -675,258 +472,519 @@ void center_tile(Monitor *m) { } } -void tile(Monitor *m) { - int32_t i, n = 0, h, r, ie = enablegaps, mw, my, ty; +void deck(Monitor *m) { + int32_t mw, my; + int32_t i, n = 0; Client *c = NULL; Client *fc = NULL; - double mfact = 0; - int32_t master_num = 0; - int32_t stack_num = 0; + float mfact; + uint32_t nmasters = m->pertag->nmasters[m->pertag->curtag]; - n = m->visible_tiling_clients; - master_num = m->pertag->nmasters[m->pertag->curtag]; - master_num = n > master_num ? master_num : n; - stack_num = n - master_num; + int32_t cur_gappih = enablegaps ? m->gappih : 0; + int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; + + cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappih; + 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_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; - - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - wl_list_for_each(fc, &clients, link) { - - if (VISIBLEON(fc, m) && ISTILED(fc)) + if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; } mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per : m->pertag->mfacts[m->pertag->curtag]; - if (n > m->pertag->nmasters[m->pertag->curtag]) - mw = m->pertag->nmasters[m->pertag->curtag] - ? (m->w.width + cur_gappih * ie) * mfact - : 0; + if (n > nmasters) + mw = nmasters ? round((m->w.width - 2 * cur_gappoh) * mfact) : 0; else - mw = m->w.width - 2 * cur_gappoh + cur_gappih * ie; - i = 0; - my = ty = cur_gappov; - - int32_t master_surplus_height = - (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (master_num - 1)); - float master_surplus_ratio = 1.0; - - int32_t slave_surplus_height = - (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (stack_num - 1)); - float slave_surplus_ratio = 1.0; + mw = m->w.width - 2 * cur_gappoh; + i = my = 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; - if (c->master_inner_per > 0.0f) { - h = master_surplus_height * c->master_inner_per / - master_surplus_ratio; - master_surplus_height = master_surplus_height - h; - master_surplus_ratio = - master_surplus_ratio - c->master_inner_per; - c->master_mfact_per = mfact; - } else { - h = (m->w.height - my - cur_gappov - - cur_gappiv * ie * (r - 1)) / - r; - c->master_inner_per = h / (m->w.height - my - cur_gappov - - cur_gappiv * ie * (r - 1)); - c->master_mfact_per = mfact; - } - resize(c, - (struct wlr_box){.x = m->w.x + cur_gappoh, - .y = m->w.y + my, - .width = mw - cur_gappih * ie, - .height = h}, - 0); - my += c->geom.height + cur_gappiv * ie; + if (i < nmasters) { + c->master_mfact_per = mfact; + int32_t h = (m->w.height - 2 * cur_gappov - my) / + (MANGO_MIN(n, nmasters) - i); + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + cur_gappoh, + .y = m->w.y + cur_gappov + my, + .width = mw, + .height = h}, + 0); + my += h; } else { - r = n - i; - if (c->stack_inner_per > 0.0f) { - h = slave_surplus_height * c->stack_inner_per / - slave_surplus_ratio; - slave_surplus_height = slave_surplus_height - h; - slave_surplus_ratio = slave_surplus_ratio - c->stack_inner_per; - c->master_mfact_per = mfact; - } else { - h = (m->w.height - ty - cur_gappov - - cur_gappiv * ie * (r - 1)) / - r; - c->stack_inner_per = h / (m->w.height - ty - cur_gappov - - cur_gappiv * ie * (r - 1)); - c->master_mfact_per = mfact; - } - - // wlr_log(WLR_ERROR, "stack_inner_per: %f", c->stack_inner_per); - - resize(c, - (struct wlr_box){.x = m->w.x + mw + cur_gappoh, - .y = m->w.y + ty, - .width = m->w.width - mw - 2 * cur_gappoh, - .height = h}, - 0); - ty += c->geom.height + cur_gappiv * ie; + // Stack area clients + c->master_mfact_per = mfact; + client_tile_resize( + c, + (struct wlr_box){.x = m->w.x + mw + cur_gappoh + cur_gappih, + .y = m->w.y + cur_gappov, + .width = m->w.width - mw - 2 * cur_gappoh - + cur_gappih, + .height = m->w.height - 2 * cur_gappov}, + 0); + if (c == focustop(m)) + wlr_scene_node_raise_to_top(&c->scene->node); } i++; } } -void right_tile(Monitor *m) { - int32_t i, n = 0, h, r, ie = enablegaps, mw, my, ty; - Client *c = NULL; - Client *fc = NULL; - double mfact = 0; - int32_t master_num = 0; - int32_t stack_num = 0; - - n = m->visible_tiling_clients; - master_num = m->pertag->nmasters[m->pertag->curtag]; - master_num = n > master_num ? master_num : n; - stack_num = n - master_num; - - 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; - - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - - wl_list_for_each(fc, &clients, link) { - - if (VISIBLEON(fc, m) && ISTILED(fc)) - break; - } - - mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per - : m->pertag->mfacts[m->pertag->curtag]; - - if (n > m->pertag->nmasters[m->pertag->curtag]) - mw = m->pertag->nmasters[m->pertag->curtag] - ? (m->w.width + cur_gappih * ie) * mfact - : 0; - else - mw = m->w.width - 2 * cur_gappoh + cur_gappih * ie; - i = 0; - my = ty = cur_gappov; - - int32_t master_surplus_height = - (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (master_num - 1)); - float master_surplus_ratio = 1.0; - - int32_t slave_surplus_height = - (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (stack_num - 1)); - float slave_surplus_ratio = 1.0; - - wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) - continue; - if (i < m->pertag->nmasters[m->pertag->curtag]) { - r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; - if (c->master_inner_per > 0.0f) { - h = master_surplus_height * c->master_inner_per / - master_surplus_ratio; - master_surplus_height = master_surplus_height - h; - master_surplus_ratio = - master_surplus_ratio - c->master_inner_per; - c->master_mfact_per = mfact; - } else { - h = (m->w.height - my - cur_gappov - - cur_gappiv * ie * (r - 1)) / - r; - c->master_inner_per = h / (m->w.height - my - cur_gappov - - cur_gappiv * ie * (r - 1)); - c->master_mfact_per = mfact; - } - resize(c, - (struct wlr_box){.x = m->w.x + m->w.width - mw - cur_gappoh + - cur_gappih * ie, - .y = m->w.y + my, - .width = mw - cur_gappih * ie, - .height = h}, - 0); - my += c->geom.height + cur_gappiv * ie; - } else { - r = n - i; - if (c->stack_inner_per > 0.0f) { - h = slave_surplus_height * c->stack_inner_per / - slave_surplus_ratio; - slave_surplus_height = slave_surplus_height - h; - slave_surplus_ratio = slave_surplus_ratio - c->stack_inner_per; - c->master_mfact_per = mfact; - } else { - h = (m->w.height - ty - cur_gappov - - cur_gappiv * ie * (r - 1)) / - r; - c->stack_inner_per = h / (m->w.height - ty - cur_gappov - - cur_gappiv * ie * (r - 1)); - c->master_mfact_per = mfact; - } - - // wlr_log(WLR_ERROR, "stack_inner_per: %f", c->stack_inner_per); - - resize(c, - (struct wlr_box){.x = m->w.x + cur_gappoh, - .y = m->w.y + ty, - .width = m->w.width - mw - 2 * cur_gappoh, - .height = h}, - 0); - ty += c->geom.height + cur_gappiv * ie; - } - i++; - } -} - -void // 17 -monocle(Monitor *m) { - Client *c = NULL; +void monocle(Monitor *m) { + Client *c = NULL, *fc = NULL; struct wlr_box geom; - int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + int32_t cur_gapiv = enablegaps ? m->gappiv : 0; + int32_t cur_gapih = enablegaps ? m->gappih : 0; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + if (config.smartgaps && m->visible_fake_tiling_clients == 1) { + cur_gappov = cur_gappoh = cur_gapiv = cur_gapih = 0; + } - wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) + int n = m->visible_fake_tiling_clients; + if (n == 0) + return; + + wl_list_for_each(c, &fstack, flink) { + if (c->iskilling || c->isunglobal || !ISFAKETILED(c)) continue; + if (VISIBLEON(c, m)) { + fc = c; + break; + } + } + + if (n == 1) { geom.x = m->w.x + cur_gappoh; geom.y = m->w.y + cur_gappov; geom.width = m->w.width - 2 * cur_gappoh; geom.height = m->w.height - 2 * cur_gappov; - resize(c, geom, 0); + client_tile_resize(fc, geom, 0); + monocle_set_focus(fc, true); + return; + } + + int tab_bar_height = config.tab_bar_height; + + int tab_bar_inner_gap_height = + config.tab_bar_height > 0 ? 2 * cur_gapiv : 0; + int tab_bar_y_offset = config.tab_bar_height > 0 ? cur_gapiv : 0; + + int tab_y = m->w.y + cur_gappov; + int main_y = tab_y + tab_bar_height + tab_bar_y_offset; + int main_height = m->w.height - 2 * cur_gappov - tab_bar_inner_gap_height - + tab_bar_height; + + int tab_area_width = m->w.width - 2 * cur_gappoh; + + int total_gaps = (n - 1) * cur_gapih; + int base_width = (tab_area_width - total_gaps) / n; + int remainder = (tab_area_width - total_gaps) % n; + + int tab_x = m->w.x + cur_gappoh; + int idx = 0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) + continue; + + if (c == fc) { + monocle_set_focus(c, true); + } else { + monocle_set_focus(c, false); + } + + geom.x = m->w.x + cur_gappoh; + geom.y = main_y; + geom.width = m->w.width - 2 * cur_gappoh; + geom.height = main_height; + client_tile_resize(c, geom, 0); + + int tw = base_width + (idx < remainder ? 1 : 0); + global_draw_tab_bar(c, tab_x, tab_y, tw, tab_bar_height); + + tab_x += tw + cur_gapih; + idx++; } - if ((c = focustop(m))) - wlr_scene_node_raise_to_top(&c->scene->node); } -void tgmix(Monitor *m) { - int32_t n = m->visible_tiling_clients; - if (n <= 3) { - tile(m); +// 网格布局窗口大小和位置计算 +void grid(Monitor *m) { + int32_t i, n; + int32_t cw, ch; + int32_t cols, rows, overcols; + Client *c = NULL; + n = 0; + int32_t target_gappo = enablegaps ? config.gappoh : 0; + int32_t target_gappi = enablegaps ? config.gappih : 0; + float single_width_ratio = 0.9; + float single_height_ratio = 0.9; + struct wlr_box target_geom; + + n = m->visible_fake_tiling_clients; + + if (n == 0) return; - } else { - grid(m); + + 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)) || + ISFAKETILED(c))) { + cw = (m->w.width - 2 * target_gappo) * single_width_ratio; + ch = (m->w.height - 2 * target_gappo) * single_height_ratio; + 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) { + float col_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)) || + ISFAKETILED(c))) { + if (i < 2) + col_pers[i] = + (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; + i++; + } + } + + float sum_col = col_pers[0] + col_pers[1]; + float avail_w = m->w.width - 2 * target_gappo - target_gappi; + ch = + (m->w.height - 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 && + ((m->isoverview && !client_is_x11_popup(c)) || + ISFAKETILED(c))) { + c->grid_col_idx = i; + c->grid_row_idx = 0; + c->grid_col_per = col_pers[i]; + c->grid_row_per = 1.0f; + + // 根据分配的权重动态计算当前窗口的宽度 + cw = avail_w * (col_pers[i] / sum_col); + + if (i == 0) { + target_geom.x = m->w.x + target_gappo; + } else if (i == 1) { + // 第二个窗口的 X 坐标紧跟第一个窗口后面 + float cw0 = avail_w * (col_pers[0] / sum_col); + target_geom.x = m->w.x + target_gappo + cw0 + target_gappi; + } + target_geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; + target_geom.width = cw; + target_geom.height = ch; + client_tile_resize(c, target_geom, 0); + i++; + } + } return; } + + // 计算列数和行数 + for (cols = 0; cols <= n / 2; cols++) { + if (cols * cols >= n) + break; + } + rows = (cols && (cols - 1) * cols >= n) ? cols - 1 : cols; + overcols = n % cols; + + 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)) || ISFAKETILED(c))) { + int32_t c_idx = i % cols; + int32_t r_idx = i / cols; + 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 && + ((m->isoverview && !client_is_x11_popup(c)) || ISFAKETILED(c))) { + int32_t c_idx = i % cols; + int32_t r_idx = i / cols; + + // 矫正属性及标记索引 + 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; + + // X 坐标及宽度计算 + float fl_cx = m->w.x + target_gappo; + float fl_cw = 0.0f; + + if (overcols && i >= n - overcols) { + float over_w = 0.0f; + for (int j = 0; j < overcols; j++) + over_w += avail_w * (col_pers[j] / sum_col); + over_w += (overcols - 1) * target_gappi; + float dx = (m->w.width - over_w) / 2.0f - target_gappo; + + fl_cx += dx; + for (int j = 0; j < c_idx; j++) + fl_cx += avail_w * (col_pers[j] / sum_col) + target_gappi; + fl_cw = avail_w * (col_pers[c_idx] / sum_col); + } else { + for (int j = 0; j < c_idx; j++) + fl_cx += avail_w * (col_pers[j] / sum_col) + target_gappi; + fl_cw = (c_idx == cols - 1) + ? (m->w.x + m->w.width - target_gappo - fl_cx) + : avail_w * (col_pers[c_idx] / sum_col); + } + + // Y 坐标及高度计算 + float fl_cy = m->w.y + target_gappo; + for (int j = 0; j < r_idx; j++) + fl_cy += avail_h * (row_pers[j] / sum_row) + target_gappi; + float fl_ch = (r_idx == rows - 1) + ? (m->w.y + m->w.height - target_gappo - fl_cy) + : avail_h * (row_pers[r_idx] / sum_row); + + 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 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 cols; + for (cols = 0; cols <= n; cols++) { + if (cols * cols >= n) + break; + } + + int32_t base_rows = n / cols; + int32_t remainder = n % cols; + int32_t first_group_cols = cols - remainder; + int32_t first_group_count = first_group_cols * base_rows; + int32_t max_rows = base_rows + (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 col_pers[cols]; + float row_pers[max_rows]; + for (i = 0; i < cols; i++) + col_pers[i] = 0.0f; + for (i = 0; i < max_rows; i++) + row_pers[i] = 0.0f; + + // 直接基于数组进行两遍比例锁定 + for (i = 0; i < n; i++) { + c = arr[i]; + int32_t col_idx = + (i < first_group_count) + ? (i / base_rows) + : (first_group_cols + (i - first_group_count) / max_rows); + int32_t row_idx = (i < first_group_count) + ? (i % base_rows) + : ((i - first_group_count) % max_rows); + + if (c->grid_col_idx == col_idx && c->grid_col_per > 0.0f) + col_pers[col_idx] = c->grid_col_per; + if (c->grid_row_idx == row_idx && c->grid_row_per > 0.0f) + row_pers[row_idx] = c->grid_row_per; + } + for (i = 0; i < n; i++) { + c = arr[i]; + int32_t col_idx = + (i < first_group_count) + ? (i / base_rows) + : (first_group_cols + (i - first_group_count) / max_rows); + int32_t row_idx = (i < first_group_count) + ? (i % base_rows) + : ((i - first_group_count) % max_rows); + + if (col_pers[col_idx] == 0.0f && c->grid_col_per > 0.0f) + col_pers[col_idx] = c->grid_col_per; + if (row_pers[row_idx] == 0.0f && c->grid_row_per > 0.0f) + row_pers[row_idx] = c->grid_row_per; + } + + // 兜底策略与总权重计算 + float sum_col = 0.0f; + for (i = 0; i < cols; i++) { + if (col_pers[i] == 0.0f) + col_pers[i] = 1.0f; + sum_col += col_pers[i]; + } + for (i = 0; i < max_rows; i++) { + if (row_pers[i] == 0.0f) + row_pers[i] = 1.0f; + } + + // 预计算所有列的 X 坐标和宽度 + float col_x[cols], col_w[cols]; + float avail_w = m->w.width - 2 * cur_gappoh - (cols - 1) * cur_gappih; + float next_x = m->w.x + cur_gappoh; + for (i = 0; i < cols; i++) { + col_x[i] = next_x; + col_w[i] = (i == cols - 1) ? (m->w.x + m->w.width - cur_gappoh - next_x) + : (avail_w * (col_pers[i] / sum_col)); + next_x += col_w[i] + cur_gappih; + } + + // 预计算两组不同的行几何参数(解决不同列行数不一致的问题) + float row_y_base[base_rows], row_h_base[base_rows]; + float sum_row_base = 0.0f; + for (i = 0; i < base_rows; i++) + sum_row_base += row_pers[i]; + float avail_h_base = + m->w.height - 2 * cur_gappov - (base_rows - 1) * cur_gappiv; + float next_y = m->w.y + cur_gappov; + for (i = 0; i < base_rows; i++) { + row_y_base[i] = next_y; + row_h_base[i] = (i == base_rows - 1) + ? (m->w.y + m->w.height - cur_gappov - next_y) + : (avail_h_base * (row_pers[i] / sum_row_base)); + next_y += row_h_base[i] + cur_gappiv; + } + + float row_y_max[max_rows], row_h_max[max_rows]; + if (remainder > 0) { + float sum_row_max = 0.0f; + for (i = 0; i < max_rows; i++) + sum_row_max += row_pers[i]; + float avail_h_max = + m->w.height - 2 * cur_gappov - (max_rows - 1) * cur_gappiv; + next_y = m->w.y + cur_gappov; + for (i = 0; i < max_rows; i++) { + row_y_max[i] = next_y; + row_h_max[i] = (i == max_rows - 1) + ? (m->w.y + m->w.height - cur_gappov - next_y) + : (avail_h_max * (row_pers[i] / sum_row_max)); + next_y += row_h_max[i] + cur_gappiv; + } + } + + // 最终渲染布局 + for (i = 0; i < n; i++) { + c = arr[i]; + int32_t col_idx, row_idx; + float fl_cx, fl_cy, fl_cw, fl_ch; + + if (i < first_group_count) { + col_idx = i / base_rows; + row_idx = i % base_rows; + fl_cy = row_y_base[row_idx]; + fl_ch = row_h_base[row_idx]; + } else { + int32_t offset = i - first_group_count; + col_idx = first_group_cols + (offset / max_rows); + row_idx = offset % max_rows; + fl_cy = row_y_max[row_idx]; + fl_ch = row_h_max[row_idx]; + } + + c->grid_col_per = col_pers[col_idx]; + c->grid_row_per = row_pers[row_idx]; + c->grid_col_idx = col_idx; + c->grid_row_idx = row_idx; + + fl_cx = col_x[col_idx]; + fl_cw = col_w[col_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); + } +} \ No newline at end of file diff --git a/src/layout/layout.h b/src/layout/layout.h index f896ac27..1f82eeaa 100644 --- a/src/layout/layout.h +++ b/src/layout/layout.h @@ -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}, }; \ No newline at end of file diff --git a/src/layout/overview.h b/src/layout/overview.h new file mode 100644 index 00000000..1ae9e7a0 --- /dev/null +++ b/src/layout/overview.h @@ -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); + } +} \ No newline at end of file diff --git a/src/layout/scroll.h b/src/layout/scroll.h new file mode 100644 index 00000000..b800c5cf --- /dev/null +++ b/src/layout/scroll.h @@ -0,0 +1,1069 @@ +/* 获取或创建指定 monitor 某个 tag 的 scroller 状态 */ +static struct TagScrollerState *ensure_scroller_state(Monitor *m, + uint32_t tag) { + if (!m->pertag->scroller_state[tag]) { + struct TagScrollerState *st = + calloc(1, sizeof(struct TagScrollerState)); + m->pertag->scroller_state[tag] = st; + } + return m->pertag->scroller_state[tag]; +} + +/* 在 tag 状态中查找客户端对应的节点(无则返回 NULL) */ +static struct ScrollerStackNode *find_scroller_node(struct TagScrollerState *st, + Client *c) { + if (!st) + return NULL; + for (struct ScrollerStackNode *n = st->all_first; n; n = n->all_next) + if (n->client == c) + return n; + return NULL; +} + +/* 创建一个新节点并插入到 tag 状态的 all 链表中 */ +static struct ScrollerStackNode * +scroller_node_create(struct TagScrollerState *st, Client *c) { + struct ScrollerStackNode *n = calloc(1, sizeof(*n)); + n->client = c; + n->scroller_proportion = c->scroller_proportion; + n->stack_proportion = c->stack_proportion; + n->scroller_proportion_single = c->scroller_proportion_single; + n->next_in_stack = NULL; + n->prev_in_stack = NULL; + n->all_next = st->all_first; + st->all_first = n; + st->count++; + return n; +} + +/* 从 tag 状态中移除一个节点并释放 */ +static void scroller_node_remove(struct TagScrollerState *st, + struct ScrollerStackNode *target) { + if (!st || !target) + return; + + /* 保存邻居 */ + struct ScrollerStackNode *prev = target->prev_in_stack; + struct ScrollerStackNode *next = target->next_in_stack; + + /* 从堆叠链表中摘除 */ + if (prev) + prev->next_in_stack = next; + if (next) + next->prev_in_stack = prev; + + /* 从 all 链表摘除 */ + struct ScrollerStackNode **indirect = &st->all_first; + while (*indirect && *indirect != target) + indirect = &(*indirect)->all_next; + if (*indirect == target) { + *indirect = target->all_next; + st->count--; + } + free(target); +} + +/* 清空一个 tag 的全部 scroller 状态 */ +static void clear_scroller_state(struct TagScrollerState *st) { + if (!st) + return; + struct ScrollerStackNode *n = st->all_first; + while (n) { + struct ScrollerStackNode *next = n->all_next; + free(n); + n = next; + } + free(st); +} + +/* 在 Monitor 销毁时清理所有 tag 的 scroller 状态 */ +static void cleanup_monitor_scroller(Monitor *m) { + for (int t = 0; t < LENGTH(tags) + 1; t++) { + if (m->pertag->scroller_state[t]) { + clear_scroller_state(m->pertag->scroller_state[t]); + m->pertag->scroller_state[t] = NULL; + } + } +} + +/* 将某个 tag 的状态同步回所有客户端的全局字段 */ +static void sync_scroller_state_to_clients(Monitor *m, uint32_t tag) { + struct TagScrollerState *st = m->pertag->scroller_state[tag]; + if (!st) + return; + for (struct ScrollerStackNode *n = st->all_first; n; n = n->all_next) { + Client *c = n->client; + c->scroller_proportion = n->scroller_proportion; + c->stack_proportion = n->stack_proportion; + c->scroller_proportion_single = n->scroller_proportion_single; + } +} + +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 = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.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 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 horizontal_scroll_adjust_fullandmax(Client *c, + struct wlr_box *target_geom) { + Monitor *m = c->mon; + int32_t cur_gappih = enablegaps ? m->gappih : 0; + int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; + + cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappoh; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; + + if (c->isfullscreen) { + target_geom->height = m->m.height; + target_geom->width = m->m.width; + target_geom->y = m->m.y; + return; + } + + if (c->ismaximizescreen) { + target_geom->height = m->w.height - 2 * cur_gappov; + target_geom->width = m->w.width - 2 * cur_gappoh; + target_geom->y = m->w.y + cur_gappov; + return; + } + + target_geom->height = m->w.height - 2 * cur_gappov; + target_geom->y = m->w.y + (m->w.height - target_geom->height) / 2; +} + +void horizontal_check_scroller_root_inside_mon(Client *c, + struct wlr_box *geometry) { + if (!GEOMINSIDEMON(geometry, c->mon)) { + geometry->x = c->mon->w.x + (c->mon->w.width - geometry->width) / 2; + } +} + +void arrange_stack_node(struct ScrollerStackNode *head, struct wlr_box geometry, + int32_t gappiv) { + int32_t stack_size = 0; + struct ScrollerStackNode *iter = head; + while (iter) { + stack_size++; + iter = iter->next_in_stack; + } + if (stack_size == 0) + return; + + /* 归一化比例 */ + float total_proportion = 0.0f; + iter = 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 = head; + while (iter) { + iter->stack_proportion /= total_proportion; + iter = iter->next_in_stack; + } + + /* 竖向排列(水平堆叠) */ + int32_t client_height; + int32_t current_y = geometry.y; + int32_t remain_client_height = geometry.height - (stack_size - 1) * gappiv; + float remain_proportion = 1.0f; + + iter = head; + while (iter) { + client_height = + remain_client_height * (iter->stack_proportion / remain_proportion); + struct wlr_box client_geom = {.x = geometry.x, + .y = current_y, + .width = geometry.width, + .height = client_height}; + resize(iter->client, client_geom, 0); + remain_proportion -= iter->stack_proportion; + remain_client_height -= client_height; + current_y += client_height + gappiv; + iter = iter->next_in_stack; + } +} + +void arrange_stack_vertical_node(struct ScrollerStackNode *head, + struct wlr_box geometry, int32_t gappih) { + int32_t stack_size = 0; + struct ScrollerStackNode *iter = head; + while (iter) { + stack_size++; + iter = iter->next_in_stack; + } + if (stack_size == 0) + return; + + /* 归一化比例 */ + float total_proportion = 0.0f; + iter = 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 = head; + while (iter) { + 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 = 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, client_geom, 0); + remain_proportion -= iter->stack_proportion; + remain_client_width -= client_width; + current_x += client_width + gappih; + iter = iter->next_in_stack; + } +} + +void scroller(Monitor *m) { + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = ensure_scroller_state(m, tag); + Client *c = NULL; + float scroller_default_proportion_single = + m->pertag->scroller_default_proportion_single[tag]; + int32_t scroller_ignore_proportion_single = + m->pertag->scroller_ignore_proportion_single[tag]; + + /* 按全局客户端链表顺序收集所有堆叠头,确保视觉顺序正确 */ + struct ScrollerStackNode *heads[64]; + int32_t n_heads = 0; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && ISSCROLLTILED(c)) { + struct ScrollerStackNode *node = find_scroller_node(st, c); + if (node && !node->prev_in_stack) { + bool already = false; + for (int k = 0; k < n_heads; k++) { + if (heads[k] == node) { + already = true; + break; + } + } + if (!already) + heads[n_heads++] = node; + } + } + } + + if (n_heads == 0) { + sync_scroller_state_to_clients(m, tag); + return; + } + + m->visible_scroll_tiling_clients = n_heads; + + int32_t cur_gappih = enablegaps ? m->gappih : 0; + int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; + int32_t cur_gappiv = enablegaps ? m->gappiv : 0; + if (config.smartgaps && n_heads == 1) { + cur_gappih = cur_gappoh = cur_gappov = 0; + } + int32_t max_client_width = + m->w.width - 2 * config.scroller_structs - cur_gappih; + + /* 单客户端特例 */ + if (n_heads == 1 && !scroller_ignore_proportion_single && + !heads[0]->client->isfullscreen && + !heads[0]->client->ismaximizescreen) { + struct ScrollerStackNode *head = heads[0]; + float single_proportion = head->scroller_proportion_single > 0.0f + ? head->scroller_proportion_single + : scroller_default_proportion_single; + struct wlr_box target_geom; + target_geom.height = m->w.height - 2 * cur_gappov; + target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion; + target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; + horizontal_check_scroller_root_inside_mon(head->client, &target_geom); + arrange_stack_node(head, target_geom, cur_gappiv); + sync_scroller_state_to_clients(m, tag); + return; + } + + struct ScrollerStackNode *root_node = NULL; + if (m->sel && ISSCROLLTILED(m->sel)) { + root_node = find_scroller_node(st, m->sel); + if (root_node) { + while (root_node->prev_in_stack) + root_node = root_node->prev_in_stack; + } + } + if (!root_node && m->prevsel && ISSCROLLTILED(m->prevsel)) { + root_node = find_scroller_node(st, m->prevsel); + if (root_node) { + while (root_node->prev_in_stack) + root_node = root_node->prev_in_stack; + } + } + if (!root_node) + root_node = heads[n_heads / 2]; /* 简单回退 */ + + int32_t focus_index = -1; + for (int i = 0; i < n_heads; i++) { + if (heads[i] == root_node) { + focus_index = i; + break; + } + } + if (focus_index < 0) + focus_index = n_heads / 2; + + /* 判断是否需要滚动、overspread、center */ + bool need_scroller = false; + bool over_overspread_to_left = false; + Client *root_client = root_node->client; + + if (root_client->geom.x >= m->w.x + config.scroller_structs && + root_client->geom.x + root_client->geom.width <= + m->w.x + m->w.width - config.scroller_structs) { + need_scroller = false; + } else { + need_scroller = true; + } + + bool need_apply_overspread = + config.scroller_prefer_overspread && n_heads > 1 && + (focus_index == 0 || focus_index == n_heads - 1) && + heads[focus_index]->scroller_proportion < 1.0f; + + if (need_apply_overspread) { + if (focus_index == 0) { + over_overspread_to_left = true; + } else { + over_overspread_to_left = false; + } + if (over_overspread_to_left && + (!INSIDEMON(heads[1]->client) || + (heads[1]->scroller_proportion + heads[0]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else if (!over_overspread_to_left && + (!INSIDEMON(heads[focus_index - 1]->client) || + (heads[focus_index - 1]->scroller_proportion + + heads[focus_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else { + need_apply_overspread = false; + } + } + + bool need_apply_center = + config.scroller_focus_center || n_heads == 1 || + (config.scroller_prefer_center && !need_apply_overspread && + (!m->prevsel || + (ISSCROLLTILED(m->prevsel) && + (m->prevsel->scroller_proportion * max_client_width) + + (heads[focus_index]->scroller_proportion * + max_client_width) > + m->w.width - 2 * config.scroller_structs - cur_gappih))); + + if (n_heads == 1 && scroller_ignore_proportion_single) { + need_scroller = true; + } + if (start_drag_window) + need_scroller = false; + + struct wlr_box target_geom; + target_geom.height = m->w.height - 2 * cur_gappov; + target_geom.width = + max_client_width * heads[focus_index]->scroller_proportion; + target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; + horizontal_scroll_adjust_fullandmax(heads[focus_index]->client, + &target_geom); + + if (heads[focus_index]->client->isfullscreen) { + target_geom.x = m->m.x; + horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); + } else if (heads[focus_index]->client->ismaximizescreen) { + target_geom.x = m->w.x + cur_gappoh; + horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); + } else if (need_scroller) { + if (need_apply_center) { + target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + } else if (need_apply_overspread) { + if (over_overspread_to_left) { + target_geom.x = m->w.x + config.scroller_structs; + } else { + target_geom.x = + m->w.x + (m->w.width - + heads[focus_index]->scroller_proportion * + max_client_width - + config.scroller_structs); + } + } else { + target_geom.x = + root_client->geom.x > m->w.x + (m->w.width) / 2 + ? m->w.x + (m->w.width - + heads[focus_index]->scroller_proportion * + max_client_width - + config.scroller_structs) + : m->w.x + config.scroller_structs; + } + horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); + } else { + target_geom.x = root_client->geom.x; + horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); + } + + /* 排列左侧的堆叠 */ + for (int i = 1; i <= focus_index; i++) { + struct ScrollerStackNode *cur = heads[focus_index - i]; + struct wlr_box left_geom; + left_geom.height = m->w.height - 2 * cur_gappov; + left_geom.width = max_client_width * cur->scroller_proportion; + horizontal_scroll_adjust_fullandmax(cur->client, &left_geom); + left_geom.x = heads[focus_index - i + 1]->client->geom.x - cur_gappih - + left_geom.width; + arrange_stack_node(cur, left_geom, cur_gappiv); + } + + /* 排列右侧的堆叠 */ + for (int i = 1; i < n_heads - focus_index; i++) { + struct ScrollerStackNode *cur = heads[focus_index + i]; + struct wlr_box right_geom; + right_geom.height = m->w.height - 2 * cur_gappov; + right_geom.width = max_client_width * cur->scroller_proportion; + horizontal_scroll_adjust_fullandmax(cur->client, &right_geom); + right_geom.x = heads[focus_index + i - 1]->client->geom.x + cur_gappih + + heads[focus_index + i - 1]->client->geom.width; + arrange_stack_node(cur, right_geom, cur_gappiv); + } + + sync_scroller_state_to_clients(m, tag); +} + +void vertical_scroller(Monitor *m) { + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = ensure_scroller_state(m, tag); + Client *c = NULL; + float scroller_default_proportion_single = + m->pertag->scroller_default_proportion_single[tag]; + int32_t scroller_ignore_proportion_single = + m->pertag->scroller_ignore_proportion_single[tag]; + + /* 按全局顺序收集堆叠头 */ + struct ScrollerStackNode *heads[64]; + int32_t n_heads = 0; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && ISSCROLLTILED(c)) { + struct ScrollerStackNode *node = find_scroller_node(st, c); + if (node && !node->prev_in_stack) { + bool already = false; + for (int k = 0; k < n_heads; k++) + if (heads[k] == node) + already = true; + if (!already) + heads[n_heads++] = node; + } + } + } + + if (n_heads == 0) { + sync_scroller_state_to_clients(m, tag); + return; + } + + m->visible_scroll_tiling_clients = n_heads; + + 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; + if (config.smartgaps && n_heads == 1) { + cur_gappiv = cur_gappov = cur_gappoh = 0; + } + int32_t max_client_height = + m->w.height - 2 * config.scroller_structs - cur_gappiv; + + if (n_heads == 1 && !scroller_ignore_proportion_single && + !heads[0]->client->isfullscreen && + !heads[0]->client->ismaximizescreen) { + struct ScrollerStackNode *head = heads[0]; + float single_proportion = head->scroller_proportion_single > 0.0f + ? head->scroller_proportion_single + : scroller_default_proportion_single; + struct wlr_box target_geom; + 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(head->client, &target_geom); + arrange_stack_vertical_node(head, target_geom, cur_gappih); + sync_scroller_state_to_clients(m, tag); + return; + } + + struct ScrollerStackNode *root_node = NULL; + if (m->sel && ISSCROLLTILED(m->sel)) { + root_node = find_scroller_node(st, m->sel); + if (root_node) { + while (root_node->prev_in_stack) + root_node = root_node->prev_in_stack; + } + } + if (!root_node && m->prevsel && ISSCROLLTILED(m->prevsel)) { + root_node = find_scroller_node(st, m->prevsel); + if (root_node) { + while (root_node->prev_in_stack) + root_node = root_node->prev_in_stack; + } + } + if (!root_node) + root_node = heads[n_heads / 2]; + + int32_t focus_index = -1; + for (int i = 0; i < n_heads; i++) { + if (heads[i] == root_node) { + focus_index = i; + break; + } + } + if (focus_index < 0) + focus_index = n_heads / 2; + + bool need_scroller = false; + bool over_overspread_to_up = false; + Client *root_client = root_node->client; + + if (root_client->geom.y >= m->w.y + config.scroller_structs && + root_client->geom.y + root_client->geom.height <= + m->w.y + m->w.height - config.scroller_structs) { + need_scroller = false; + } else { + need_scroller = true; + } + + bool need_apply_overspread = + config.scroller_prefer_overspread && n_heads > 1 && + (focus_index == 0 || focus_index == n_heads - 1) && + heads[focus_index]->scroller_proportion < 1.0f; + + if (need_apply_overspread) { + if (focus_index == 0) { + over_overspread_to_up = true; + } else { + over_overspread_to_up = false; + } + if (over_overspread_to_up && + (!INSIDEMON(heads[1]->client) || + (heads[1]->scroller_proportion + heads[0]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else if (!over_overspread_to_up && + (!INSIDEMON(heads[focus_index - 1]->client) || + (heads[focus_index - 1]->scroller_proportion + + heads[focus_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else { + need_apply_overspread = false; + } + } + + bool need_apply_center = + config.scroller_focus_center || n_heads == 1 || + (config.scroller_prefer_center && !need_apply_overspread && + (!m->prevsel || + (ISSCROLLTILED(m->prevsel) && + (m->prevsel->scroller_proportion * max_client_height) + + (heads[focus_index]->scroller_proportion * + max_client_height) > + m->w.height - 2 * config.scroller_structs - cur_gappiv))); + + if (n_heads == 1 && scroller_ignore_proportion_single) { + need_scroller = true; + } + if (start_drag_window) + need_scroller = false; + + struct wlr_box target_geom; + target_geom.width = m->w.width - 2 * cur_gappoh; + target_geom.height = + max_client_height * heads[focus_index]->scroller_proportion; + target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + vertical_scroll_adjust_fullandmax(heads[focus_index]->client, &target_geom); + + if (heads[focus_index]->client->isfullscreen) { + target_geom.y = m->m.y; + vertical_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_vertical_node(heads[focus_index], target_geom, + cur_gappih); + } else if (heads[focus_index]->client->ismaximizescreen) { + target_geom.y = m->w.y + cur_gappov; + vertical_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_vertical_node(heads[focus_index], target_geom, + cur_gappih); + } else if (need_scroller) { + if (need_apply_center) { + target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; + } else if (need_apply_overspread) { + if (over_overspread_to_up) { + target_geom.y = m->w.y + config.scroller_structs; + } else { + target_geom.y = + m->w.y + (m->w.height - + heads[focus_index]->scroller_proportion * + max_client_height - + config.scroller_structs); + } + } else { + target_geom.y = + root_client->geom.y > m->w.y + (m->w.height) / 2 + ? m->w.y + (m->w.height - + heads[focus_index]->scroller_proportion * + max_client_height - + config.scroller_structs) + : m->w.y + config.scroller_structs; + } + vertical_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_vertical_node(heads[focus_index], target_geom, + cur_gappih); + } else { + target_geom.y = root_client->geom.y; + vertical_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_vertical_node(heads[focus_index], target_geom, + cur_gappih); + } + + for (int i = 1; i <= focus_index; i++) { + struct ScrollerStackNode *cur = heads[focus_index - i]; + struct wlr_box up_geom; + up_geom.width = m->w.width - 2 * cur_gappoh; + up_geom.height = max_client_height * cur->scroller_proportion; + vertical_scroll_adjust_fullandmax(cur->client, &up_geom); + up_geom.y = heads[focus_index - i + 1]->client->geom.y - cur_gappiv - + up_geom.height; + arrange_stack_vertical_node(cur, up_geom, cur_gappih); + } + + for (int i = 1; i < n_heads - focus_index; i++) { + struct ScrollerStackNode *cur = heads[focus_index + i]; + struct wlr_box down_geom; + down_geom.width = m->w.width - 2 * cur_gappoh; + down_geom.height = max_client_height * cur->scroller_proportion; + vertical_scroll_adjust_fullandmax(cur->client, &down_geom); + down_geom.y = heads[focus_index + i - 1]->client->geom.y + cur_gappiv + + heads[focus_index + i - 1]->client->geom.height; + arrange_stack_vertical_node(cur, down_geom, cur_gappih); + } + + sync_scroller_state_to_clients(m, tag); +} + +void scroller_remove_client(Client *c) { + Monitor *m; + wl_list_for_each(m, &mons, link) { + for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) { + struct TagScrollerState *st = m->pertag->scroller_state[t]; + if (!st) + continue; + struct ScrollerStackNode *node = find_scroller_node(st, c); + if (node) { + scroller_node_remove(st, node); + } + } + } +} + +void scroller_insert_stack(Client *c, Client *target_client, + bool insert_before) { + if (!target_client || target_client->mon != c->mon) + return; + + if (c->isfullscreen) + setfullscreen(c, 0, true); + if (c->ismaximizescreen) + setmaximizescreen(c, 0, true); + + Monitor *m = c->mon; + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = ensure_scroller_state(m, tag); + + struct ScrollerStackNode *cnode = find_scroller_node(st, c); + if (cnode) + scroller_node_remove(st, cnode); + + struct ScrollerStackNode *tnode = find_scroller_node(st, target_client); + if (!tnode) + tnode = scroller_node_create(st, target_client); + + struct ScrollerStackNode *newnode = scroller_node_create(st, c); + /* 将新节点插入到 tnode 的前面或后面 */ + if (insert_before) { + newnode->next_in_stack = tnode; + newnode->prev_in_stack = tnode->prev_in_stack; + if (tnode->prev_in_stack) + tnode->prev_in_stack->next_in_stack = newnode; + tnode->prev_in_stack = newnode; + wl_list_remove(&c->link); + wl_list_insert(tnode->client->link.prev, &c->link); + } else { + newnode->prev_in_stack = tnode; + newnode->next_in_stack = tnode->next_in_stack; + if (tnode->next_in_stack) + tnode->next_in_stack->prev_in_stack = newnode; + tnode->next_in_stack = newnode; + wl_list_remove(&c->link); + wl_list_insert(&tnode->client->link, &c->link); + } + + /* 处理堆叠头部的全屏/最大化状态*/ + struct ScrollerStackNode *head = tnode; + while (head->prev_in_stack) + head = head->prev_in_stack; + Client *stack_head = head->client; + if (stack_head->ismaximizescreen) + setmaximizescreen(stack_head, 0, true); + if (stack_head->isfullscreen) + setfullscreen(stack_head, 0, true); + + /* 同步到 Client 字段 */ + sync_scroller_state_to_clients(m, tag); + + arrange(m, false, false); +} + +void scroller_drop_tile(Client *c, Client *closest, int vertical) { + + // 必须先更新,不然里面节点还存着的是cnode的信息, + // 会造成stach_head/stack_tail指向的客户端不对 + update_scroller_state(c->mon); + + Client *stack_head = scroll_get_stack_head_client(closest); + Client *stack_tail = scroll_get_stack_tail_client(closest); + + if (vertical) { + if (closest->drop_direction == LEFT) { + setfloating(c, 0); + scroller_insert_stack(c, closest, true); + return; + } else if (closest->drop_direction == RIGHT) { + setfloating(c, 0); + scroller_insert_stack(c, closest, false); + return; + } else if (closest->drop_direction == UP) { + if (c != stack_head) { + wl_list_remove(&c->link); + wl_list_insert(stack_head->link.prev, &c->link); + } + } else if (closest->drop_direction == DOWN) { + if (c != stack_tail) { + wl_list_remove(&c->link); + wl_list_insert(&stack_tail->link, &c->link); + } + } + } else { + if (closest->drop_direction == UP) { + setfloating(c, 0); + scroller_insert_stack(c, closest, true); + return; + } else if (closest->drop_direction == DOWN) { + setfloating(c, 0); + scroller_insert_stack(c, closest, false); + return; + } else if (closest->drop_direction == LEFT) { + if (c != stack_head) { + wl_list_remove(&c->link); + wl_list_insert(stack_head->link.prev, &c->link); + } + } else if (closest->drop_direction == RIGHT) { + if (c != stack_tail) { + wl_list_remove(&c->link); + wl_list_insert(&stack_tail->link, &c->link); + } + } + } + + setfloating(c, 0); +} + +Client *scroll_get_stack_head_client(Client *c) { + if (!c || !c->mon) + return c; + uint32_t tag = c->mon->pertag->curtag; + struct TagScrollerState *st = c->mon->pertag->scroller_state[tag]; + if (st) { + struct ScrollerStackNode *n = find_scroller_node(st, c); + if (n) { + while (n->prev_in_stack) + n = n->prev_in_stack; + return n->client; + } + } + return c; +} + +Client *scroll_get_stack_tail_client(Client *c) { + if (!c || !c->mon) + return c; + uint32_t tag = c->mon->pertag->curtag; + struct TagScrollerState *st = c->mon->pertag->scroller_state[tag]; + if (st) { + struct ScrollerStackNode *n = find_scroller_node(st, c); + if (n) { + while (n->next_in_stack) + n = n->next_in_stack; + return n->client; + } + } + return c; +} + +static void update_scroller_state(Monitor *m) { + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = ensure_scroller_state(m, tag); + + /* 收集当前可见的所有 scroller 平铺窗口 */ + Client *vis[512]; + int32_t count = 0; + Client *c; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && ISSCROLLTILED(c)) + vis[count++] = c; + if (count == 512) + break; + } + + /* 移除不再可见的节点 */ + struct ScrollerStackNode *n = st->all_first; + while (n) { + bool found = false; + for (int i = 0; i < count; i++) { + if (vis[i] == n->client) { + found = true; + break; + } + } + struct ScrollerStackNode *next = n->all_next; + if (!found) + scroller_node_remove(st, n); + n = next; + } + + /* 为新的可见窗口创建节点 */ + for (int i = 0; i < count; i++) { + if (!find_scroller_node(st, vis[i])) { + scroller_node_create(st, vis[i]); + } + } +} + +static void scroller_swap_nodes_in_same_stack(struct ScrollerStackNode *n1, + struct ScrollerStackNode *n2) { + float tmp_sc = n1->scroller_proportion; + float tmp_st = n1->stack_proportion; + n1->scroller_proportion = n2->scroller_proportion; + n1->stack_proportion = n2->stack_proportion; + n2->scroller_proportion = tmp_sc; + n2->stack_proportion = tmp_st; + + struct ScrollerStackNode *p1 = n1->prev_in_stack; + struct ScrollerStackNode *next1 = n1->next_in_stack; + struct ScrollerStackNode *p2 = n2->prev_in_stack; + struct ScrollerStackNode *next2 = n2->next_in_stack; + + if (n1->next_in_stack == n2) { + n1->next_in_stack = next2; + n2->prev_in_stack = p1; + n1->prev_in_stack = n2; + n2->next_in_stack = n1; + if (p1) + p1->next_in_stack = n2; + if (next2) + next2->prev_in_stack = n1; + } else if (n2->next_in_stack == n1) { + n2->next_in_stack = next1; + n1->prev_in_stack = p2; + n2->prev_in_stack = n1; + n1->next_in_stack = n2; + if (p2) + p2->next_in_stack = n1; + if (next1) + next1->prev_in_stack = n2; + } else { + if (p1) + p1->next_in_stack = n2; + if (next1) + next1->prev_in_stack = n2; + if (p2) + p2->next_in_stack = n1; + if (next2) + next2->prev_in_stack = n1; + n1->prev_in_stack = p2; + n1->next_in_stack = next2; + n2->prev_in_stack = p1; + n2->next_in_stack = next1; + } +} + +static void scroller_swap_different_stacks(struct ScrollerStackNode *head1, + struct ScrollerStackNode *head2) { + Client *head1_c = head1->client; + Client *head2_c = head2->client; + Client *tail1_c = scroll_get_stack_tail_client(head1_c); + Client *tail2_c = scroll_get_stack_tail_client(head2_c); + + struct wl_list *p1 = head1_c->link.prev; + struct wl_list *n1_next = tail1_c->link.next; + struct wl_list *p2 = head2_c->link.prev; + struct wl_list *n2_next = tail2_c->link.next; + + if (n1_next == &head2_c->link) { + p2->next = n2_next; + n2_next->prev = p2; + p1->next = &head2_c->link; + head2_c->link.prev = p1; + tail2_c->link.next = &head1_c->link; + head1_c->link.prev = &tail2_c->link; + } else if (n2_next == &head1_c->link) { + p1->next = n1_next; + n1_next->prev = p1; + p2->next = &head1_c->link; + head1_c->link.prev = p2; + tail1_c->link.next = &head2_c->link; + head2_c->link.prev = &tail1_c->link; + } else { + p1->next = &head2_c->link; + head2_c->link.prev = p1; + tail2_c->link.next = n1_next; + n1_next->prev = &tail2_c->link; + + p2->next = &head1_c->link; + head1_c->link.prev = p2; + tail1_c->link.next = n2_next; + n2_next->prev = &tail1_c->link; + } +} + +void exchange_two_scroller_clients(Client *c1, Client *c2) { + + if (!c1 || !c2 || !c1->mon || !c2->mon) + return; + + struct ScrollerStackNode *n1 = NULL; + struct ScrollerStackNode *n2 = NULL; + Monitor *m1 = c1->mon; + Monitor *m2 = c2->mon; + uint32_t tag1 = m1->pertag->curtag; + uint32_t tag2 = m2->pertag->curtag; + + struct TagScrollerState *st1 = ensure_scroller_state(m1, tag1); + n1 = find_scroller_node(st1, c1); + + struct TagScrollerState *st2 = ensure_scroller_state(m2, tag2); + n2 = find_scroller_node(st2, c2); + + if (!n1 && !n2) + return; + + if (m1 != m2 && ((n1 && n1->prev_in_stack) || (n2 && n2->prev_in_stack) || + (n1 && n1->next_in_stack) || (n2 && n2->next_in_stack))) { + return; + } + + client_swap_layout_properties(c1, c2); + + if (n1 && n2) { + struct ScrollerStackNode *head1 = n1; + while (head1->prev_in_stack) + head1 = head1->prev_in_stack; + struct ScrollerStackNode *head2 = n2; + while (head2->prev_in_stack) + head2 = head2->prev_in_stack; + + if (head1 == head2) { + scroller_swap_nodes_in_same_stack(n1, n2); + sync_scroller_state_to_clients(m1, tag1); + wl_list_swap(&c1->link, &c2->link); + } else { + scroller_swap_different_stacks(head1, head2); + } + } else { + wl_list_swap(&c1->link, &c2->link); + } + + if (m1 != m2) { + client_swap_monitors_and_tags(c1, c2); + } + finish_exchange_arrange_and_focus(c1, c2, m1, m2); + + return; +} \ No newline at end of file diff --git a/src/layout/vertical.h b/src/layout/vertical.h index f7bd442c..6c018399 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -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); + } } \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 4a0657aa..c01fc59d 100644 --- a/src/mango.c +++ b/src/mango.c @@ -73,6 +73,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -94,10 +97,11 @@ #include #endif #include "common/util.h" +#include "draw/text-node.h" /* macros */ -#define MAX(A, B) ((A) > (B) ? (A) : (B)) -#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MANGO_MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MANGO_MIN(A, B) ((A) < (B) ? (A) : (B)) #define GEZERO(A) ((A) >= 0 ? (A) : 0) #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) #define INSIDEMON(A) \ @@ -114,8 +118,13 @@ #define ISSCROLLTILED(A) \ (A && !(A)->isfloating && !(A)->isminimized && !(A)->iskilling && \ !(A)->isunglobal) +#define ISFAKETILED(A) \ + (A && !(A)->isfloating && !(A)->isminimized && !(A)->iskilling && \ + !(A)->isunglobal) #define VISIBLEON(C, M) \ - ((C) && (M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + ((C) && (M) && (C)->mon == (M) && \ + (((C)->tags & (M)->tagset[(M)->seltags] || (C)->isglobal || \ + (C)->isunglobal))) #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) #define TAGMASK ((1 << LENGTH(tags)) - 1) @@ -144,6 +153,11 @@ #define BAKED_POINTS_COUNT 256 +#define IPC_WATCH_ARRANGGE \ + IPC_WATCH_MONITOR | IPC_WATCH_CLIENT | IPC_WATCH_TAGS | \ + IPC_WATCH_ALL_MONITORS | IPC_WATCH_ALL_TAGS | IPC_WATCH_ALL_CLIENTS | \ + IPC_WATCH_LAST_OPEN_SURFACE | IPC_WATCH_FOCUSING_CLIENT + /* enums */ enum { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; @@ -156,6 +170,7 @@ enum { LyrBg, LyrBlur, LyrBottom, + LyrDecorate, LyrTile, LyrTop, LyrFadeOut, @@ -164,6 +179,9 @@ enum { LyrBlock, NUM_LAYERS }; /* scene layers */ + +enum mango_node_type { MANGO_TITLE_NODE, MANGO_jump_label_node }; + #ifdef XWAYLAND enum { NetWMWindowTypeDialog, @@ -174,10 +192,11 @@ enum { }; /* EWMH atoms */ #endif enum { UP, DOWN, LEFT, RIGHT, UNDIR }; /* smartmovewin */ -enum { NONE, OPEN, MOVE, CLOSE, TAG, FOCUS, OPAFADEIN, OPAFADEOUT }; +enum { NONE, OPEN, MOVE, CLOSE, TAG, FOCUS, OPAFADEIN, OPAFADEOUT, OVERVIEW }; enum { UNFOLD, FOLD, INVALIDFOLD }; enum { PREV, NEXT }; enum { STATE_UNSPECIFIED = 0, STATE_ENABLED, STATE_DISABLED }; +enum { FORCE, UNFORCE }; enum tearing_mode { TEARING_DISABLED = 0, @@ -190,26 +209,18 @@ enum seat_config_shortcuts_inhibit { SHORTCUTS_INHIBIT_ENABLE, }; -// 事件掩码枚举 -enum print_event_type { - PRINT_ACTIVE = 1 << 0, - PRINT_TAG = 1 << 1, - PRINT_LAYOUT = 1 << 2, - PRINT_TITLE = 1 << 3, - PRINT_APPID = 1 << 4, - PRINT_LAYOUT_SYMBOL = 1 << 5, - PRINT_FULLSCREEN = 1 << 6, - PRINT_FLOATING = 1 << 7, - PRINT_X = 1 << 8, - PRINT_Y = 1 << 9, - PRINT_WIDTH = 1 << 10, - PRINT_HEIGHT = 1 << 11, - PRINT_LAST_LAYER = 1 << 12, - PRINT_KB_LAYOUT = 1 << 13, - PRINT_KEYMODE = 1 << 14, - PRINT_SCALEFACTOR = 1 << 15, - PRINT_FRAME = 1 << 16, - PRINT_ALL = (1 << 17) - 1 // 所有位都设为1 +enum ipc_watch_type { + IPC_WATCH_NONE = 0, + IPC_WATCH_MONITOR = 1 << 0, + IPC_WATCH_CLIENT = 1 << 1, + IPC_WATCH_TAGS = 1 << 2, + IPC_WATCH_ALL_MONITORS = 1 << 3, + IPC_WATCH_ALL_TAGS = 1 << 4, + IPC_WATCH_ALL_CLIENTS = 1 << 5, + IPC_WATCH_KEYMODE = 1 << 6, + IPC_WATCH_KB_LAYOUT = 1 << 7, + IPC_WATCH_LAST_OPEN_SURFACE = 1 << 8, + IPC_WATCH_FOCUSING_CLIENT = 1 << 9, }; typedef struct Pertag Pertag; @@ -234,8 +245,14 @@ typedef struct { char *v3; uint32_t ui; uint32_t ui2; + Client *tc; } Arg; +typedef struct { + enum mango_node_type type; + void *node_data; +} MangoNodeData; + typedef struct { uint32_t mod; uint32_t button; @@ -259,8 +276,8 @@ typedef struct { struct wl_list link; struct wlr_input_device *wlr_device; struct libinput_device *libinput_device; - struct wl_listener destroy_listener; // 用于监听设备销毁事件 - void *device_data; // 新增:指向设备特定数据(如 Switch) + struct wl_listener destroy_listener; + void *device_data; } InputDevice; typedef struct { @@ -278,6 +295,7 @@ struct dwl_animation { bool tagouting; bool begin_fade_in; bool tag_from_rule; + bool overining; uint32_t time_started; uint32_t duration; struct wlr_box initial; @@ -315,6 +333,8 @@ struct Client { Monitor *mon; struct wlr_scene_tree *scene; struct wlr_scene_rect *border; /* top, bottom, left, right */ + struct wlr_scene_rect *droparea; + struct wlr_scene_rect *splitindicator[4]; struct wlr_scene_shadow *shadow; struct wlr_scene_rect *shield; struct wlr_scene_blur *blur; @@ -323,6 +343,9 @@ struct Client { struct wlr_scene *image_capture_scene; struct wlr_ext_image_capture_source_v1 *image_capture_source; struct wlr_scene_surface *image_capture_scene_surface; + struct wlr_scene_tree *overview_scene_surface; + struct mango_jump_label_node *jump_label_node; + struct mango_tab_bar_node *tab_bar_node; struct wl_list link; struct wl_list flink; struct wl_list fadeout_link; @@ -345,6 +368,7 @@ struct Client { struct wl_listener configure; struct wl_listener set_hints; struct wl_listener set_geometry; + struct wl_listener commmitx11; #endif uint32_t bw; uint32_t tags, oldtags, mini_restore_tag; @@ -353,7 +377,7 @@ struct Client { struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; int32_t isfloating, isurgent, isfullscreen, isfakefullscreen, need_float_size_reduce, isminimized, isoverlay, isnosizehint, - ignore_maximize, ignore_minimize; + ignore_maximize, ignore_minimize, idleinhibit_when_focus; int32_t ismaximizescreen; int32_t overview_backup_bw; int32_t fullscreen_backup_x, fullscreen_backup_y, fullscreen_backup_w, @@ -376,6 +400,8 @@ struct Client { int32_t is_in_scratchpad; int32_t iscustomsize; int32_t iscustompos; + int32_t iscustom_scroller_proportion; + int32_t iscustom_scroller_proportion_single; int32_t is_scratchpad_show; int32_t isglobal; int32_t isnoborder; @@ -388,6 +414,7 @@ struct Client { int32_t istagswitching; int32_t isnamedscratchpad; int32_t shield_when_capture; + bool is_monocle_hide; bool is_pending_open_animation; bool is_restoring_from_ov; float scroller_proportion; @@ -398,7 +425,7 @@ struct Client { struct dwl_opacity_animation opacity_animation; int32_t isterm, noswallow; int32_t allow_csd; - int32_t force_maximize; + int32_t force_fakemaximize; int32_t force_tiled_state; pid_t pid; Client *swallowing, *swallowedby; @@ -420,6 +447,7 @@ struct Client { double old_master_mfact_per, old_master_inner_per, old_stack_inner_per; double old_scroller_pproportion; bool ismaster; + bool old_ismaster; bool cursor_in_upper_half, cursor_in_left_half; bool isleftstack; int32_t tearing_hint; @@ -427,8 +455,17 @@ struct Client { int32_t allow_shortcuts_inhibit; float scroller_proportion_single; bool isfocusing; - struct Client *next_in_stack; - struct Client *prev_in_stack; + char jump_char; + bool enable_drop_area_draw; + int32_t drop_direction; + struct wlr_box drag_tile_float_backup_geom; + float grid_col_per; + float grid_row_per; + float old_grid_col_per; + float old_grid_row_per; + int32_t grid_col_idx; + int32_t grid_row_idx; + uint32_t id; }; typedef struct { @@ -456,6 +493,8 @@ typedef struct { struct wl_listener modifiers; struct wl_listener key; struct wl_listener destroy; + + uint32_t layout_index; } KeyboardGroup; typedef struct { @@ -495,6 +534,13 @@ typedef struct { bool being_unmapped; } LayerSurface; +typedef struct { + struct wlr_xdg_popup *wlr_popup; + struct wl_listener destroy; + struct wl_listener commit; + struct wl_listener reposition; +} Popup; + typedef struct { const char *symbol; void (*arrange)(Monitor *); @@ -512,11 +558,15 @@ struct Monitor { struct wl_listener request_state; struct wl_listener destroy_lock_surface; struct wlr_session_lock_surface_v1 *lock_surface; + struct wl_event_source *skip_frame_timeout; struct wlr_box m; /* monitor area, layout-relative */ struct wlr_box w; /* window area, layout-relative */ struct wl_list layers[4]; /* LayerSurface::link */ uint32_t seltags; uint32_t tagset[2]; + bool skiping_frame; + uint32_t resizing_count_pending; + uint32_t resizing_count_current; struct wl_list dwl_ipc_outputs; int32_t gappih; /* horizontal gap between windows */ @@ -524,16 +574,22 @@ struct Monitor { int32_t gappoh; /* horizontal outer gaps */ int32_t gappov; /* vertical outer gaps */ Pertag *pertag; + uint32_t ovbk_current_tagset; + uint32_t ovbk_prev_tagset; Client *sel, *prevsel; int32_t isoverview; + int32_t is_jump_mode; int32_t is_in_hotarea; int32_t asleep; uint32_t visible_clients; uint32_t visible_tiling_clients; uint32_t visible_scroll_tiling_clients; + uint32_t visible_fake_tiling_clients; struct wlr_scene_optimized_blur *blur; - char last_surface_ws_name[256]; + char last_open_surface[256]; struct wlr_ext_workspace_group_handle_v1 *ext_group; + bool iscleanuping; + int8_t carousel_anim_dir; }; typedef struct { @@ -555,6 +611,47 @@ struct capture_session_tracker { struct wlr_ext_image_copy_capture_session_v1 *session; }; +typedef struct DwindleNode DwindleNode; +struct DwindleNode { + bool is_split; + bool split_h; + bool split_locked; + bool custom_leaf_split_h; + float ratio; + float drag_init_ratio; + int32_t container_x; + int32_t container_y; + int32_t container_w; + int32_t container_h; + DwindleNode *parent; + DwindleNode *first; + DwindleNode *second; + Client *client; +}; + +struct ScrollerStackNode { + Client *client; + float scroller_proportion; + float stack_proportion; + float scroller_proportion_single; + + struct ScrollerStackNode *next_in_stack; + struct ScrollerStackNode *prev_in_stack; + struct ScrollerStackNode *all_next; +}; + +struct TagScrollerState { + struct ScrollerStackNode *all_first; /* 所有节点的单链表头 */ + int count; +}; + +typedef struct { + int32_t orig_width; + int32_t orig_height; + bool is_subsurface; + struct wl_listener destroy; +} SnapshotMetadata; + /* function declarations */ static void applybounds( Client *c, @@ -570,6 +667,7 @@ static void axisnotify(struct wl_listener *listener, void *data); // 滚轮事件处理 static void buttonpress(struct wl_listener *listener, void *data); // 鼠标按键事件处理 +static bool handle_buttonpress(struct wlr_pointer_button_event *event); static int32_t ongesture(struct wlr_pointer_swipe_end_event *event); static void swipe_begin(struct wl_listener *listener, void *data); static void swipe_update(struct wl_listener *listener, void *data); @@ -648,8 +746,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double sy_unaccel); static void motionrelative(struct wl_listener *listener, void *data); -static void reset_foreign_tolevel(Client *c); -static void remove_foreign_topleve(Client *c); +static void reset_foreign_tolevel(Client *c, Monitor *oldmon, Monitor *newmon); static void add_foreign_topleve(Client *c); static void exchange_two_client(Client *c1, Client *c2); static void outputmgrapply(struct wl_listener *listener, void *data); @@ -658,7 +755,7 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, static void outputmgrtest(struct wl_listener *listener, void *data); static void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time); -static void printstatus(void); +static void printstatus(enum ipc_watch_type type); static void quitsignal(int32_t signo); static void powermgrsetmode(struct wl_listener *listener, void *data); static void rendermon(struct wl_listener *listener, void *data); @@ -670,8 +767,9 @@ static void run(char *startup_cmd); static void setcursor(struct wl_listener *listener, void *data); static void setfloating(Client *c, int32_t floating); static void setfakefullscreen(Client *c, int32_t fakefullscreen); -static void setfullscreen(Client *c, int32_t fullscreen); -static void setmaximizescreen(Client *c, int32_t maximizescreen); +static void setfullscreen(Client *c, int32_t fullscreen, bool rearrange); +static void setmaximizescreen(Client *c, int32_t maximizescreen, + bool rearrange); static void reset_maximizescreen_size(Client *c); static void setgaps(int32_t oh, int32_t ov, int32_t ih, int32_t iv); @@ -772,6 +870,7 @@ static struct wlr_scene_tree * wlr_scene_tree_snapshot(struct wlr_scene_node *node, struct wlr_scene_tree *parent); static bool is_scroller_layout(Monitor *m); +static bool is_monocle_layout(Monitor *m); static bool is_centertile_layout(Monitor *m); static void create_output(struct wlr_backend *backend, void *data); static void get_layout_abbr(char *abbr, const char *full_name); @@ -793,12 +892,50 @@ static float *get_border_color(Client *c); static void clear_fullscreen_and_maximized_state(Monitor *m); static void request_fresh_all_monitors(void); static Client *find_client_by_direction(Client *tc, const Arg *arg, - bool findfloating, bool ignore_align); + bool findfloating); static void exit_scroller_stack(Client *c); -static Client *get_scroll_stack_head(Client *c); +static Client *scroll_get_stack_head_client(Client *c); static bool client_only_in_one_tag(Client *c); -static Client *get_focused_stack_client(Client *sc); +static Client *get_focused_stack_client(Client *sc, + Client *custom_focus_client); static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); +static void monitor_stop_skip_frame_timer(Monitor *m); +static int monitor_skip_frame_timeout_callback(void *data); +static Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly); +static bool match_monitor_spec(char *spec, Monitor *m); +static void last_cursor_surface_destroy(struct wl_listener *listener, + void *data); +static int32_t keep_idle_inhibit(void *data); +static void check_keep_idle_inhibit(Client *c); +static void pre_caculate_before_arrange(Monitor *m, bool want_animation, + bool from_view, bool only_caculate); +static void client_pending_fullscreen_state(Client *c, int32_t isfullscreen); +static void client_pending_maximized_state(Client *c, int32_t ismaximized); +static void client_pending_minimized_state(Client *c, int32_t isminimized); +static void scroller_insert_stack(Client *c, Client *target_client, + bool insert_before); +static void dwindle_move_client(DwindleNode **root, Client *c, Client *target, + float ratio, int32_t dir); +static void dwindle_resize_client_step(Monitor *m, Client *c, int32_t dx, + int32_t dy); +static void dwindle_resize_client(Monitor *m, Client *c); + +static struct TagScrollerState *ensure_scroller_state(Monitor *m, uint32_t tag); +static struct ScrollerStackNode *find_scroller_node(struct TagScrollerState *st, + Client *c); +static void sync_scroller_state_to_clients(Monitor *m, uint32_t tag); +static void scroller_node_remove(struct TagScrollerState *st, + struct ScrollerStackNode *target); +static struct ScrollerStackNode * +scroller_node_create(struct TagScrollerState *st, Client *c); +static void update_scroller_state(Monitor *m); +Client *scroll_get_stack_tail_client(Client *c); +static DwindleNode *dwindle_find_leaf(DwindleNode *node, Client *c); +static void overview_backup_surface(Client *c); + +static void create_jump_hints(Monitor *m); +static void finish_jump_mode(Monitor *m); +static void begin_jump_mode(Monitor *m); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -861,7 +998,7 @@ static KeyboardGroup *kb_group; static struct wl_list inputdevices; static struct wl_list keyboard_shortcut_inhibitors; static uint32_t cursor_mode; -static Client *grabc; +static Client *grabc, *dropc; static int32_t rzcorner; static int32_t grabcx, grabcy; /* client-relative */ @@ -902,15 +1039,26 @@ struct dvec2 *baked_points_focus; struct dvec2 *baked_points_opafadein; struct dvec2 *baked_points_opafadeout; -static struct wl_event_source *hide_source; +static struct wl_event_source *hide_cursor_source; +static struct wl_event_source *keep_idle_inhibit_source; static bool cursor_hidden = false; static bool tag_combo = false; static const char *cli_config_path = NULL; static int active_capture_count = 0; +static bool cli_debug_log = false; static KeyMode keymode = { .mode = {'d', 'e', 'f', 'a', 'u', 'l', 't', '\0'}, .isdefault = true, }; + +static char *env_vars[] = {"DISPLAY", + "WAYLAND_DISPLAY", + "XDG_CURRENT_DESKTOP", + "XDG_SESSION_TYPE", + "XCURSOR_THEME", + "XCURSOR_SIZE", + "MANGO_INSTANCE_SIGNATURE", + NULL}; static struct { enum wp_cursor_shape_device_v1_shape shape; struct wlr_surface *surface; @@ -920,16 +1068,21 @@ static struct { #include "client/client.h" #include "config/preset.h" - struct Pertag { - uint32_t curtag, prevtag; /* current and previous tag */ - int32_t nmasters[LENGTH(tags) + 1]; /* number of windows in master area */ - float mfacts[LENGTH(tags) + 1]; /* mfacts per tag */ - bool no_hide[LENGTH(tags) + 1]; /* no_hide per tag */ - bool no_render_border[LENGTH(tags) + 1]; /* no_render_border per tag */ - const Layout - *ltidxs[LENGTH(tags) + 1]; /* matrix of tags and layouts indexes */ + uint32_t curtag, prevtag; + int32_t nmasters[LENGTH(tags) + 1]; + float mfacts[LENGTH(tags) + 1]; + int32_t no_hide[LENGTH(tags) + 1]; + int32_t no_render_border[LENGTH(tags) + 1]; + int32_t open_as_floating[LENGTH(tags) + 1]; + float scroller_default_proportion[LENGTH(tags) + 1]; + float scroller_default_proportion_single[LENGTH(tags) + 1]; + int32_t scroller_ignore_proportion_single[LENGTH(tags) + 1]; + struct DwindleNode *dwindle_root[LENGTH(tags) + 1]; + const Layout *ltidxs[LENGTH(tags) + 1]; + struct TagScrollerState *scroller_state[LENGTH(tags) + 1]; }; +#include "config/parse_config.h" static struct wl_signal mango_print_status; @@ -970,14 +1123,17 @@ static struct wl_listener new_session_lock = {.notify = locksession}; static struct wl_listener drm_lease_request = {.notify = requestdrmlease}; static struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor = { .notify = handle_keyboard_shortcuts_inhibit_new_inhibitor}; +static struct wl_listener last_cursor_surface_destroy_listener = { + .notify = last_cursor_surface_destroy}; #ifdef XWAYLAND -static void fix_xwayland_unmanaged_coordinate(Client *c); +static void fix_xwayland_coordinate(struct wlr_box *geom); static int32_t synckeymap(void *data); static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); static void createnotifyx11(struct wl_listener *listener, void *data); static void dissociatex11(struct wl_listener *listener, void *data); +static void commitx11(struct wl_listener *listener, void *data); static void associatex11(struct wl_listener *listener, void *data); static void sethints(struct wl_listener *listener, void *data); static void xwaylandready(struct wl_listener *listener, void *data); @@ -988,16 +1144,20 @@ static struct wlr_xwayland *xwayland; static struct wl_event_source *sync_keymap; #endif +#include "action/client.h" #include "animation/client.h" #include "animation/common.h" #include "animation/layer.h" #include "animation/tag.h" -#include "config/parse_config.h" #include "dispatch/bind_define.h" #include "ext-protocol/all.h" #include "fetch/fetch.h" +#include "ipc/ipc.h" #include "layout/arrange.h" +#include "layout/dwindle.h" #include "layout/horizontal.h" +#include "layout/overview.h" +#include "layout/scroll.h" #include "layout/vertical.h" void client_change_mon(Client *c, Monitor *m) { @@ -1010,8 +1170,8 @@ void client_change_mon(Client *c, Monitor *m) { void applybounds(Client *c, struct wlr_box *bbox) { /* set minimum possible */ - c->geom.width = MAX(1 + 2 * (int32_t)c->bw, c->geom.width); - c->geom.height = MAX(1 + 2 * (int32_t)c->bw, c->geom.height); + c->geom.width = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.width); + c->geom.height = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.height); if (c->geom.x >= bbox->x + bbox->width) c->geom.x = bbox->x + bbox->width - c->geom.width; @@ -1044,20 +1204,42 @@ void clear_fullscreen_flag(Client *c) { } if (c->isfullscreen) { - setfullscreen(c, false); + setfullscreen(c, false, true); } if (c->ismaximizescreen) { - setmaximizescreen(c, 0); + setmaximizescreen(c, 0, true); } } +void client_pending_fullscreen_state(Client *c, int32_t isfullscreen) { + c->isfullscreen = isfullscreen; + + if (c->foreign_toplevel && !c->iskilling) + wlr_foreign_toplevel_handle_v1_set_fullscreen(c->foreign_toplevel, + isfullscreen); +} + +void client_pending_maximized_state(Client *c, int32_t ismaximized) { + c->ismaximizescreen = ismaximized; + if (c->foreign_toplevel && !c->iskilling) + wlr_foreign_toplevel_handle_v1_set_maximized(c->foreign_toplevel, + ismaximized); +} + +void client_pending_minimized_state(Client *c, int32_t isminimized) { + c->isminimized = isminimized; + if (c->foreign_toplevel && !c->iskilling) + wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, + isminimized); +} + void show_scratchpad(Client *c) { c->is_scratchpad_show = 1; if (c->isfullscreen || c->ismaximizescreen) { - c->isfullscreen = 0; // 清除窗口全屏标志 - c->ismaximizescreen = 0; - c->bw = c->isnoborder ? 0 : borderpx; + client_pending_fullscreen_state(c, 0); + client_pending_maximized_state(c, 0); + c->bw = c->isnoborder ? 0 : config.borderpx; } /* return if fullscreen */ @@ -1065,10 +1247,10 @@ void show_scratchpad(Client *c) { setfloating(c, 1); c->geom.width = c->iscustomsize ? c->float_geom.width - : c->mon->w.width * scratchpad_width_ratio; - c->geom.height = c->iscustomsize - ? c->float_geom.height - : c->mon->w.height * scratchpad_height_ratio; + : c->mon->w.width * config.scratchpad_width_ratio; + c->geom.height = + c->iscustomsize ? c->float_geom.height + : c->mon->w.height * config.scratchpad_height_ratio; // 重新计算居中的坐标 c->float_geom = c->geom = c->animainit_geom = c->animation.current = setclient_coordinate_center(c, c->mon, c->geom, 0, 0); @@ -1095,9 +1277,6 @@ void swallow(Client *c, Client *w) { c->bw = w->bw; c->isfloating = w->isfloating; c->isurgent = w->isurgent; - c->isfullscreen = w->isfullscreen; - c->ismaximizescreen = w->ismaximizescreen; - c->isminimized = w->isminimized; c->is_in_scratchpad = w->is_in_scratchpad; c->is_scratchpad_show = w->is_scratchpad_show; c->tags = w->tags; @@ -1107,18 +1286,36 @@ void swallow(Client *c, Client *w) { c->master_inner_per = w->master_inner_per; c->master_mfact_per = w->master_mfact_per; c->scroller_proportion = w->scroller_proportion; - c->next_in_stack = w->next_in_stack; - c->prev_in_stack = w->prev_in_stack; - if (w->next_in_stack) - w->next_in_stack->prev_in_stack = c; - if (w->prev_in_stack) - w->prev_in_stack->next_in_stack = c; + c->isglobal = w->isglobal; + c->overview_backup_geom = w->overview_backup_geom; + + /* 调整 w 的邻居指针,让它们指向 c */ c->stack_proportion = w->stack_proportion; + + if (w->overview_scene_surface) { + wlr_scene_node_destroy(&w->scene_surface->node); + w->scene_surface = w->overview_scene_surface; + w->overview_scene_surface = NULL; + } + + if (c->mon && c->mon->isoverview && config.ov_no_resize) { + overview_backup_surface(c); + } + + if (w->tab_bar_node) { + wlr_scene_node_set_enabled(&w->tab_bar_node->scene_buffer->node, false); + } + + /* 全局链表替换 */ wl_list_insert(&w->link, &c->link); wl_list_insert(&w->flink, &c->flink); - if (w->foreign_toplevel) - remove_foreign_topleve(w); + if (w->foreign_toplevel) { + wlr_foreign_toplevel_handle_v1_output_leave(w->foreign_toplevel, + w->mon->wlr_output); + wlr_foreign_toplevel_handle_v1_destroy(w->foreign_toplevel); + w->foreign_toplevel = NULL; + } wlr_scene_node_set_enabled(&w->scene->node, false); wlr_scene_node_set_enabled(&c->scene->node, true); @@ -1126,23 +1323,68 @@ void swallow(Client *c, Client *w) { if (!c->foreign_toplevel && c->mon) add_foreign_toplevel(c); + else if (c->foreign_toplevel && c->mon) { + wlr_foreign_toplevel_handle_v1_output_enter(c->foreign_toplevel, + c->mon->wlr_output); + } - if (c->isminimized && c->foreign_toplevel) { - wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, - false); - wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, true); + client_pending_fullscreen_state(c, w->isfullscreen); + client_pending_maximized_state(c, w->ismaximizescreen); + client_pending_minimized_state(c, w->isminimized); + + if (!w->mon) + return; + + const Layout *layout = w->mon->pertag->ltidxs[w->mon->pertag->curtag]; + + if (layout->id == DWINDLE || layout->id == SCROLLER || + layout->id == VERTICAL_SCROLLER) { + + for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) { + /* dwindle */ + + if (layout->id == DWINDLE) { + + DwindleNode **root = &w->mon->pertag->dwindle_root[t]; + dwindle_remove(root, c); + DwindleNode *dnode = dwindle_find_leaf(*root, w); + if (dnode) + dnode->client = c; + } + + // scroller + if (layout->id == SCROLLER || layout->id == VERTICAL_SCROLLER) { + struct TagScrollerState *st = w->mon->pertag->scroller_state[t]; + if (!st) + continue; + /* 先移除 c 在任意 tag 中的旧节点 */ + struct ScrollerStackNode *cn = find_scroller_node(st, c); + if (cn) + scroller_node_remove(st, cn); + + /* 将 w 的节点(如果存在)转给 c */ + struct ScrollerStackNode *wn = find_scroller_node(st, w); + if (wn) + wn->client = c; + } + } + } + + /* 同步当前活动 tag 的全局客户端字段 */ + if (layout->id == SCROLLER || layout->id == VERTICAL_SCROLLER) { + sync_scroller_state_to_clients(w->mon, w->mon->pertag->curtag); } } bool switch_scratchpad_client_state(Client *c) { - if (scratchpad_cross_monitor && selmon && c->mon != selmon && + if (config.scratchpad_cross_monitor && selmon && c->mon != selmon && c->is_in_scratchpad) { // 保存原始monitor用于尺寸计算 Monitor *oldmon = c->mon; c->scratchpad_switching_mon = true; c->mon = selmon; - reset_foreign_tolevel(c); + reset_foreign_tolevel(c, oldmon, c->mon); client_update_oldmonname_record(c, selmon); // 根据新monitor调整窗口尺寸 @@ -1190,12 +1432,12 @@ void apply_named_scratchpad(Client *target_client) { 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; } - if (single_scratchpad && c->is_in_scratchpad && c->is_scratchpad_show && - c != target_client) { + if (config.single_scratchpad && c->is_in_scratchpad && + c->is_scratchpad_show && c != target_client) { set_minimized(c); } } @@ -1256,30 +1498,30 @@ void toggle_hotarea(int32_t x_root, int32_t y_root) { // 根据热角位置计算不同的热区坐标 unsigned hx, hy; - switch (hotarea_corner) { + switch (config.hotarea_corner) { case BOTTOM_RIGHT: // 右下角 - hx = selmon->m.x + selmon->m.width - hotarea_size; - hy = selmon->m.y + selmon->m.height - hotarea_size; + hx = selmon->m.x + selmon->m.width - config.hotarea_size; + hy = selmon->m.y + selmon->m.height - config.hotarea_size; break; case TOP_LEFT: // 左上角 - hx = selmon->m.x + hotarea_size; - hy = selmon->m.y + hotarea_size; + hx = selmon->m.x + config.hotarea_size; + hy = selmon->m.y + config.hotarea_size; break; case TOP_RIGHT: // 右上角 - hx = selmon->m.x + selmon->m.width - hotarea_size; - hy = selmon->m.y + hotarea_size; + hx = selmon->m.x + selmon->m.width - config.hotarea_size; + hy = selmon->m.y + config.hotarea_size; break; case BOTTOM_LEFT: // 左下角(默认) default: - hx = selmon->m.x + hotarea_size; - hy = selmon->m.y + selmon->m.height - hotarea_size; + hx = selmon->m.x + config.hotarea_size; + hy = selmon->m.y + selmon->m.height - config.hotarea_size; break; } // 判断鼠标是否在热区内 int in_hotarea = 0; - switch (hotarea_corner) { + switch (config.hotarea_corner) { case BOTTOM_RIGHT: // 右下角 in_hotarea = (y_root > hy && x_root > hx && x_root <= (selmon->m.x + selmon->m.width) && @@ -1301,10 +1543,11 @@ void toggle_hotarea(int32_t x_root, int32_t y_root) { break; } - if (enable_hotarea == 1 && selmon->is_in_hotarea == 0 && in_hotarea) { + if (config.enable_hotarea == 1 && selmon->is_in_hotarea == 0 && + in_hotarea) { toggleoverview(&arg); selmon->is_in_hotarea = 1; - } else if (enable_hotarea == 1 && selmon->is_in_hotarea == 1 && + } else if (config.enable_hotarea == 1 && selmon->is_in_hotarea == 1 && !in_hotarea) { selmon->is_in_hotarea = 0; } @@ -1313,7 +1556,7 @@ void toggle_hotarea(int32_t x_root, int32_t y_root) { static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, isterm); APPLY_INT_PROP(c, r, allow_csd); - APPLY_INT_PROP(c, r, force_maximize); + APPLY_INT_PROP(c, r, force_fakemaximize); APPLY_INT_PROP(c, r, force_tiled_state); APPLY_INT_PROP(c, r, force_tearing); APPLY_INT_PROP(c, r, noswallow); @@ -1323,6 +1566,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, no_force_center); APPLY_INT_PROP(c, r, isfloating); APPLY_INT_PROP(c, r, isfullscreen); + APPLY_INT_PROP(c, r, isfakefullscreen); APPLY_INT_PROP(c, r, isnoborder); APPLY_INT_PROP(c, r, isnoshadow); APPLY_INT_PROP(c, r, isnoradius); @@ -1336,6 +1580,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, ignore_maximize); APPLY_INT_PROP(c, r, ignore_minimize); APPLY_INT_PROP(c, r, isnosizehint); + APPLY_INT_PROP(c, r, idleinhibit_when_focus); APPLY_INT_PROP(c, r, isunglobal); APPLY_INT_PROP(c, r, noblur); APPLY_INT_PROP(c, r, allow_shortcuts_inhibit); @@ -1356,7 +1601,7 @@ void set_float_malposition(Client *tc) { y = tc->geom.y; xreverse = 1; yreverse = 1; - offset = MIN(tc->mon->w.width / 20, tc->mon->w.height / 20); + offset = MANGO_MIN(tc->mon->w.width / 20, tc->mon->w.height / 20); wl_list_for_each(c, &clients, link) { if (c->isfloating && c != tc && VISIBLEON(c, tc->mon) && @@ -1390,6 +1635,25 @@ void set_float_malposition(Client *tc) { tc->float_geom.y = tc->geom.y = y; } +void client_reset_mon_tags(Client *c, Monitor *mon, uint32_t newtags) { + if (!newtags && mon && !mon->isoverview) { + c->tags = mon->tagset[mon->seltags]; + } else if (!newtags && mon && mon->isoverview) { + c->tags = mon->ovbk_current_tagset; + } else if (newtags) { + c->tags = newtags; + } else { + c->tags = mon->tagset[mon->seltags]; + } +} + +void check_match_tag_floating_rule(Client *c, Monitor *mon) { + if (c->tags && !c->isfloating && mon && !c->swallowedby && + mon->pertag->open_as_floating[get_tags_first_tag_num(c->tags)]) { + c->isfloating = 1; + } +} + void applyrules(Client *c) { /* rule matching */ const char *appid, *title; @@ -1399,6 +1663,9 @@ void applyrules(Client *c) { Client *fc = NULL; Client *parent = NULL; + if (!c) + return; + parent = client_get_parent(c); Monitor *mon = parent && parent->mon ? parent->mon : selmon; @@ -1407,7 +1674,7 @@ void applyrules(Client *c) { #ifdef XWAYLAND if (c->isfloating && client_is_x11(c)) { - fix_xwayland_unmanaged_coordinate(c); + fix_xwayland_coordinate(&c->geom); c->float_geom = c->geom; } #endif @@ -1437,7 +1704,7 @@ void applyrules(Client *c) { // set monitor of client wl_list_for_each(m, &mons, link) { - if (regex_match(r->monitor, m->wlr_output->name)) { + if (match_monitor_spec(r->monitor, m)) { mon = m; } } @@ -1446,12 +1713,24 @@ void applyrules(Client *c) { c->isfloating = 1; } + if (r->scroller_proportion > 0.0f) { + c->iscustom_scroller_proportion = 1; + } + + if (r->scroller_proportion_single > 0.0f) { + c->iscustom_scroller_proportion_single = 1; + } + // set geometry of floating client - if (r->width > 0) + if (r->width > 1) c->float_geom.width = r->width; - if (r->height > 0) + else if (r->width > 0 && r->width <= 1) + c->float_geom.width = round(mon->m.width * r->width); + if (r->height > 1) c->float_geom.height = r->height; + else if (r->height > 0 && r->height <= 1) + c->float_geom.height = round(mon->m.height * r->height); if (r->width > 0 || r->height > 0) { c->iscustomsize = 1; @@ -1478,16 +1757,19 @@ void applyrules(Client *c) { // the hit size if (!c->iscustompos && (!client_is_x11(c) || (c->geom.x == 0 && c->geom.y == 0))) { + struct wlr_box pending_center_geom = + c->iscustomsize ? c->float_geom : c->geom; c->float_geom = c->geom = - setclient_coordinate_center(c, mon, c->geom, 0, 0); - } else { + setclient_coordinate_center(c, mon, pending_center_geom, 0, 0); + } else if (!c->iscustomsize) { c->float_geom = c->geom; } /*-----------------------apply rule action-------------------------*/ // rule action only apply after map not apply in the init commit - if (!client_surface(c)->mapped) + struct wlr_surface *surface = client_surface(c); + if (!surface || !surface->mapped) return; // apply swallow rule @@ -1510,11 +1792,23 @@ void applyrules(Client *c) { int32_t fullscreen_state_backup = c->isfullscreen || client_wants_fullscreen(c); - setmon(c, mon, newtags, - !c->isopensilent && - !(client_is_x11_popup(c) && client_should_ignore_focus(c)) && - (!c->istagsilent || !newtags || - newtags & mon->tagset[mon->seltags])); + + bool should_init_get_focus = + !c->isopensilent && + !(client_is_x11_popup(c) && client_should_ignore_focus(c)) && mon && + (!c->istagsilent || !newtags || newtags & mon->tagset[mon->seltags]); + + if (!should_init_get_focus) { + wl_list_remove(&c->flink); + wl_list_insert(fstack.prev, &c->flink); + } + + setmon(c, mon, newtags, should_init_get_focus); + + if (!c->isfloating) { + c->old_stack_inner_per = c->stack_inner_per; + c->old_master_inner_per = c->master_inner_per; + } if (c->mon && !(c->mon == selmon && c->tags & c->mon->tagset[c->mon->seltags]) && @@ -1523,14 +1817,18 @@ void applyrules(Client *c) { view_in_mon(&(Arg){.ui = c->tags}, true, c->mon, true); } - setfullscreen(c, fullscreen_state_backup); + setfullscreen(c, fullscreen_state_backup, true); + + if (c->isfakefullscreen) { + setfakefullscreen(c, 1); + } /* if there is a new non-floating window in the current tag, the fullscreen window in the current tag will exit fullscreen and participate in tiling */ wl_list_for_each(fc, &clients, - link) if (fc && fc != c && c->tags & fc->tags && + link) if (fc && fc != c && c->tags & fc->tags && c->mon && VISIBLEON(fc, c->mon) && ISFULLSCREEN(fc) && !c->isfloating) { clear_fullscreen_flag(fc); @@ -1549,7 +1847,7 @@ void applyrules(Client *c) { } // apply overlay rule - if (c->isoverlay) { + if (c->isoverlay && c->scene) { wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]); wlr_scene_node_raise_to_top(&c->scene->node); } @@ -1587,7 +1885,7 @@ void apply_window_snap(Client *c) { int32_t snap_up_mon = 0, snap_down_mon = 0, snap_left_mon = 0, snap_right_mon = 0; - uint32_t cbw = !render_border || c->fake_no_border ? borderpx : 0; + uint32_t cbw = !render_border || c->fake_no_border ? config.borderpx : 0; uint32_t tcbw; uint32_t cx, cy, cw, ch, tcx, tcy, tcw, tch; cx = c->geom.x + cbw; @@ -1599,14 +1897,14 @@ void apply_window_snap(Client *c) { if (!c || !c->mon || !client_surface(c)->mapped || c->iskilling) return; - if (!c->isfloating || !enable_floating_snap) + if (!c->isfloating || !config.enable_floating_snap) return; wl_list_for_each(tc, &clients, link) { if (tc && tc->isfloating && !tc->iskilling && client_surface(tc)->mapped && VISIBLEON(tc, c->mon)) { - tcbw = !render_border || tc->fake_no_border ? borderpx : 0; + tcbw = !render_border || tc->fake_no_border ? config.borderpx : 0; tcx = tc->geom.x + tcbw; tcy = tc->geom.y + tcbw; tcw = tc->geom.width - 2 * tcbw; @@ -1660,19 +1958,19 @@ void apply_window_snap(Client *c) { if (snap_right_screen >= 0 && snap_right_screen < snap_right) snap_right = snap_right_screen; - if (snap_left < snap_right && snap_left < snap_distance) { + if (snap_left < snap_right && snap_left < config.snap_distance) { c->geom.x = c->geom.x - snap_left; } - if (snap_right <= snap_left && snap_right < snap_distance) { + if (snap_right <= snap_left && snap_right < config.snap_distance) { c->geom.x = c->geom.x + snap_right; } - if (snap_up < snap_down && snap_up < snap_distance) { + if (snap_up < snap_down && snap_up < config.snap_distance) { c->geom.y = c->geom.y - snap_up; } - if (snap_down <= snap_up && snap_down < snap_distance) { + if (snap_down <= snap_up && snap_down < config.snap_distance) { c->geom.y = c->geom.y + snap_down; } @@ -1686,9 +1984,10 @@ void focuslayer(LayerSurface *l) { client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); } -void reset_exclusive_layer(Monitor *m) { +void reset_exclusive_layers_focus(Monitor *m) { LayerSurface *l = NULL; int32_t i; + bool neet_change_focus_to_client = false; uint32_t layers_above_shell[] = { ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, ZWLR_LAYER_SHELL_V1_LAYER_TOP, @@ -1699,28 +1998,47 @@ void reset_exclusive_layer(Monitor *m) { return; for (i = 0; i < (int32_t)LENGTH(layers_above_shell); i++) { - wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { + wl_list_for_each(l, &m->layers[layers_above_shell[i]], link) { if (l == exclusive_focus && l->layer_surface->current.keyboard_interactive != - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { + exclusive_focus = NULL; + + neet_change_focus_to_client = true; + } + + if (l->layer_surface->surface == + seat->keyboard_state.focused_surface && + l->being_unmapped) { + neet_change_focus_to_client = true; + } + if (l->layer_surface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE && l->layer_surface->surface == - seat->keyboard_state.focused_surface) - focusclient(focustop(selmon), 1); + seat->keyboard_state.focused_surface) { + neet_change_focus_to_client = true; + } if (locked || l->layer_surface->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE || - !l->mapped || l == exclusive_focus) + l->being_unmapped) continue; /* Deactivate the focused client. */ exclusive_focus = l; - focuslayer(l); + neet_change_focus_to_client = false; + if (l->layer_surface->surface != + seat->keyboard_state.focused_surface) + focuslayer(l); return; } } + + if (neet_change_focus_to_client) { + focusclient(focustop(selmon), 1); + } } void arrangelayers(Monitor *m) { @@ -1730,6 +2048,9 @@ void arrangelayers(Monitor *m) { if (!m->wlr_output->enabled) return; + if (m->iscleanuping) + return; + /* Arrange exclusive surfaces from top->bottom */ for (i = 3; i >= 0; i--) arrangelayer(m, &m->layers[i], &usable_area, 1); @@ -1742,14 +2063,23 @@ void arrangelayers(Monitor *m) { /* Arrange non-exlusive surfaces from top->bottom */ for (i = 3; i >= 0; i--) arrangelayer(m, &m->layers[i], &usable_area, 0); +} - /* Find topmost keyboard interactive layer, if such a layer exists */ - reset_exclusive_layer(m); +bool pointer_is_trackpad(struct wlr_pointer *pointer) { + struct libinput_device *device; + + if (wlr_input_device_is_libinput(&pointer->base) && + (device = wlr_libinput_get_device_handle(&pointer->base))) { + if (libinput_device_config_tap_get_finger_count(device) > 0) { + return true; + } + } + + return false; } void // 鼠标滚轮事件 axisnotify(struct wl_listener *listener, void *data) { - /* This event is forwarded by the cursor when a pointer emits an axis event, * for example when you move the scroll wheel. */ struct wlr_pointer_axis_event *event = data; @@ -1758,6 +2088,7 @@ axisnotify(struct wl_listener *listener, void *data) { AxisBinding *a; int32_t ji; uint32_t adir; + double target_scroll_factor; // IDLE_NOTIFY_ACTIVITY; handlecursoractivity(); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); @@ -1785,7 +2116,8 @@ axisnotify(struct wl_listener *listener, void *data) { a = &config.axis_bindings[ji]; if (CLEANMASK(mods) == CLEANMASK(a->mod) && // 按键一致 adir == a->dir && a->func) { // 滚轮方向判断一致且处理函数存在 - if (event->time_msec - axis_apply_time > axis_bind_apply_timeout || + if (event->time_msec - axis_apply_time > + config.axis_bind_apply_timeout || axis_apply_dir * event->delta < 0) { a->func(&a->arg); axis_apply_time = event->time_msec; @@ -1803,10 +2135,16 @@ axisnotify(struct wl_listener *listener, void *data) { * implemented checking the event's orientation and the delta of the event */ /* Notify the client with pointer focus of the axis event. */ + + target_scroll_factor = pointer_is_trackpad(event->pointer) + ? config.trackpad_scroll_factor + : config.axis_scroll_factor; + wlr_seat_pointer_notify_axis( seat, // 滚轮事件发送给客户端也就是窗口 - event->time_msec, event->orientation, event->delta * axis_scroll_factor, - roundf(event->delta_discrete * axis_scroll_factor), event->source, + event->time_msec, event->orientation, + event->delta * target_scroll_factor, + roundf(event->delta_discrete * target_scroll_factor), event->source, event->relative_direction); } @@ -1825,7 +2163,8 @@ int32_t ongesture(struct wlr_pointer_swipe_end_event *event) { } // Require absolute distance movement beyond a small thresh-hold - if (adx * adx + ady * ady < swipe_min_threshold * swipe_min_threshold) { + if (adx * adx + ady * ady < + config.swipe_min_threshold * config.swipe_min_threshold) { return handled; } @@ -1929,57 +2268,101 @@ void hold_end(struct wl_listener *listener, void *data) { event->time_msec, event->cancelled); } -void place_drag_tile_client(Client *c) { - Client *tc = NULL; - Client *closest_client = NULL; - long min_distant = LONG_MAX; - long temp_distant; - int32_t x, y; +Client *find_closest_tiled_client(Client *c) { + Client *tc, *closest = NULL; + long min_dist = LONG_MAX; + Monitor *cursor_mon = xytomon(cursor->x, cursor->y); wl_list_for_each(tc, &clients, link) { - if (tc != c && ISTILED(tc) && VISIBLEON(tc, c->mon)) { - x = tc->geom.x + (int32_t)(tc->geom.width / 2) - cursor->x; - y = tc->geom.y + (int32_t)(tc->geom.height / 2) - cursor->y; - temp_distant = x * x + y * y; - if (temp_distant < min_distant) { - min_distant = temp_distant; - closest_client = tc; - } + if (tc == c || !ISTILED(tc) || !VISIBLEON(tc, cursor_mon)) + continue; + + if (cursor->x >= tc->geom.x && + cursor->x < tc->geom.x + tc->geom.width && + cursor->y >= tc->geom.y && + cursor->y < tc->geom.y + tc->geom.height) { + return tc; + } + + int32_t dx = tc->geom.x + (int32_t)(tc->geom.width / 2) - cursor->x; + int32_t dy = tc->geom.y + (int32_t)(tc->geom.height / 2) - cursor->y; + long dist = (long)dx * dx + (long)dy * dy; + + if (dist < min_dist) { + min_dist = dist; + closest = tc; } } - if (closest_client && closest_client->link.prev != &c->link) { - wl_list_remove(&c->link); - c->link.next = &closest_client->link; - c->link.prev = closest_client->link.prev; - closest_client->link.prev->next = &c->link; - closest_client->link.prev = &c->link; - } else if (closest_client) { - exchange_two_client(c, closest_client); + + return closest; +} + +void place_drag_tile_client(Client *c) { + Client *closest = find_closest_tiled_client(c); + + if (closest && closest->mon) { + const Layout *layout = + closest->mon->pertag->ltidxs[closest->mon->pertag->curtag]; + + if (closest->drop_direction == UNDIR) { + setfloating(c, 0); + wl_list_remove(&c->link); + wl_list_insert(closest->link.prev, &c->link); + arrange(closest->mon, false, false); + return; + } + + if (layout->id == SCROLLER) { + scroller_drop_tile(c, closest, 0); + return; + } + if (layout->id == VERTICAL_SCROLLER) { + scroller_drop_tile(c, closest, 1); + return; + } + if (layout->id == DWINDLE) { + uint32_t tag = c->mon->pertag->curtag; + bool insert_before = (closest->drop_direction == LEFT || + closest->drop_direction == UP); + bool split_h = (closest->drop_direction == LEFT || + closest->drop_direction == RIGHT); + dwindle_insert(&c->mon->pertag->dwindle_root[tag], c, closest, + config.dwindle_split_ratio, insert_before, split_h, + !config.dwindle_drop_simple_split); + setfloating(c, 0); + return; + } + + if (closest->drop_direction == LEFT || closest->drop_direction == UP) { + wl_list_remove(&c->link); + wl_list_insert(closest->link.prev, &c->link); + } else { + wl_list_remove(&c->link); + wl_list_insert(&closest->link, &c->link); + } } + setfloating(c, 0); } bool check_trackpad_disabled(struct wlr_pointer *pointer) { - struct libinput_device *device; - - if (!disable_trackpad) + if (!config.disable_trackpad) { return false; - - if (wlr_input_device_is_libinput(&pointer->base) && - (device = wlr_libinput_get_device_handle(&pointer->base))) { - - // 如果是触摸板且被禁用,忽略事件 - if (libinput_device_config_tap_get_finger_count(device) > 0) { - return true; // 不处理事件 - } } - return false; + return pointer_is_trackpad(pointer); } void // 鼠标按键事件 buttonpress(struct wl_listener *listener, void *data) { struct wlr_pointer_button_event *event = data; + + if (!handle_buttonpress(event)) + wlr_seat_pointer_notify_button(seat, event->time_msec, event->button, + event->state); +} + +bool handle_buttonpress(struct wlr_pointer_button_event *event) { struct wlr_keyboard *hard_keyboard, *keyboard; uint32_t hard_mods, mods; Client *c = NULL; @@ -1994,8 +2377,8 @@ buttonpress(struct wl_listener *listener, void *data) { handlecursoractivity(); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); - if (check_trackpad_disabled(event->pointer)) { - return; + if (event->pointer && check_trackpad_disabled(event->pointer)) { + return true; } switch (event->state) { @@ -2024,6 +2407,28 @@ buttonpress(struct wl_listener *listener, void *data) { } } + // overview模式下鼠标左键跳转,右键关闭窗口 + if (selmon && selmon->isoverview && event->button == BTN_LEFT && c) { + toggleoverview(&(Arg){.i = 1}); + return true; + } + + if (selmon && selmon->isoverview && event->button == BTN_RIGHT && c) { + pending_kill_client(c); + return true; + } + + // handle click on tile node + struct wlr_scene_node *node = wlr_scene_node_at( + &layers[LyrDecorate]->node, cursor->x, cursor->y, NULL, NULL); + if (node && node->data) { + MangoNodeData *mangonodedata = (MangoNodeData *)node->data; + if (mangonodedata->type == MANGO_TITLE_NODE) { + Client *c = mangonodedata->node_data; + focusclient(c, 1); + } + } + // 当鼠标焦点在layer上的时候,不检测虚拟键盘的mod状态, // 避免layer虚拟键盘锁死mod按键状态 hard_keyboard = &kb_group->wlr_group->keyboard; @@ -2040,22 +2445,12 @@ buttonpress(struct wl_listener *listener, void *data) { break; m = &config.mouse_bindings[ji]; - if (selmon->isoverview && event->button == BTN_LEFT && c) { - toggleoverview(&(Arg){.i = 1}); - return; - } - - if (selmon->isoverview && event->button == BTN_RIGHT && c) { - pending_kill_client(c); - return; - } - if (CLEANMASK(mods) == CLEANMASK(m->mod) && event->button == m->button && m->func && (CLEANMASK(m->mod) != 0 || (event->button != BTN_LEFT && event->button != BTN_RIGHT))) { m->func(&m->arg); - return; + return true; } } break; @@ -2081,22 +2476,26 @@ buttonpress(struct wl_listener *listener, void *data) { grabc = NULL; start_drag_window = false; last_apply_drap_time = 0; - if (tmpc->drag_to_tile && drag_tile_to_tile) { + if (tmpc->drag_to_tile && config.drag_tile_to_tile) { place_drag_tile_client(tmpc); + tmpc->float_geom = tmpc->drag_tile_float_backup_geom; } else { apply_window_snap(tmpc); } tmpc->drag_to_tile = false; - return; + if (dropc) { + dropc->enable_drop_area_draw = false; + client_set_drop_area(dropc); + dropc = NULL; + } + return true; } else { cursor_mode = CurNormal; } break; } - /* If the event wasn't handled by the compositor, notify the client with - * pointer focus that a button press has occurred */ - wlr_seat_pointer_notify_button(seat, event->time_msec, event->button, - event->state); + /* If the event wasn't handled by the compositor, return false */ + return false; } void checkidleinhibitor(struct wlr_surface *exclude) { @@ -2114,7 +2513,7 @@ void checkidleinhibitor(struct wlr_surface *exclude) { toplevel_from_wlr_surface(inhibitor->surface, &c, NULL); - if (idleinhibit_ignore_visible) { + if (config.idleinhibit_ignore_visible) { inhibited = 1; break; } @@ -2129,6 +2528,11 @@ void checkidleinhibitor(struct wlr_surface *exclude) { wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); } +void last_cursor_surface_destroy(struct wl_listener *listener, void *data) { + last_cursor.surface = NULL; + wl_list_remove(&listener->link); +} + void setcursorshape(struct wl_listener *listener, void *data) { struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; if (cursor_mode != CurNormal && cursor_mode != CurPressed) @@ -2137,6 +2541,11 @@ void setcursorshape(struct wl_listener *listener, void *data) { * actually has pointer focus first. If so, we can tell the cursor to * use the provided cursor shape. */ if (event->seat_client == seat->pointer_state.focused_client) { + /* Remove surface destroy listener if active */ + if (last_cursor.surface && + last_cursor_surface_destroy_listener.link.prev != NULL) + wl_list_remove(&last_cursor_surface_destroy_listener.link); + last_cursor.shape = event->shape; last_cursor.surface = NULL; if (!cursor_hidden) @@ -2146,12 +2555,17 @@ void setcursorshape(struct wl_listener *listener, void *data) { } void cleanuplisteners(void) { + wl_list_remove(&ext_manager_commit_listener.link); // 0.7 wl_list_remove(&print_status_listener.link); wl_list_remove(&cursor_axis.link); wl_list_remove(&cursor_button.link); wl_list_remove(&cursor_frame.link); wl_list_remove(&cursor_motion.link); wl_list_remove(&cursor_motion_absolute.link); + wl_list_remove(&tablet_tool_proximity.link); + wl_list_remove(&tablet_tool_axis.link); + wl_list_remove(&tablet_tool_button.link); + wl_list_remove(&tablet_tool_tip.link); wl_list_remove(&gpu_reset.link); wl_list_remove(&new_idle_inhibitor.link); wl_list_remove(&layout_change.link); @@ -2188,6 +2602,7 @@ void cleanuplisteners(void) { } void cleanup(void) { + ipc_cleanup(); cleanuplisteners(); #ifdef XWAYLAND wlr_xwayland_destroy(xwayland); @@ -2213,6 +2628,8 @@ void cleanup(void) { /* Destroy after the wayland display (when the monitors are already destroyed) to avoid destroying them with an invalid scene output. */ wlr_scene_node_destroy(&scene->tree.node); + + mango_text_global_finish(); } void cleanupmon(struct wl_listener *listener, void *data) { @@ -2220,6 +2637,8 @@ void cleanupmon(struct wl_listener *listener, void *data) { LayerSurface *l = NULL, *tmp = NULL; uint32_t i; + m->iscleanuping = true; + /* m->layers[i] are intentionally not unlinked */ for (i = 0; i < LENGTH(m->layers); i++) { wl_list_for_each_safe(l, tmp, &m->layers[i], link) @@ -2246,7 +2665,16 @@ void cleanupmon(struct wl_listener *listener, void *data) { wlr_scene_node_destroy(&m->blur->node); m->blur = NULL; } + if (m->skip_frame_timeout) { + monitor_stop_skip_frame_timer(m); + wl_event_source_remove(m->skip_frame_timeout); + m->skip_frame_timeout = NULL; + } m->wlr_output->data = NULL; + + cleanup_monitor_dwindle(m); + cleanup_monitor_scroller(m); + free(m->pertag); free(m); } @@ -2271,7 +2699,13 @@ void closemon(Monitor *m) { if (c->mon == m) { if (selmon == NULL) { - remove_foreign_topleve(c); + if (c->foreign_toplevel) { + wlr_foreign_toplevel_handle_v1_output_leave( + c->foreign_toplevel, c->mon->wlr_output); + wlr_foreign_toplevel_handle_v1_destroy(c->foreign_toplevel); + c->foreign_toplevel = NULL; + } + c->mon = NULL; } else { client_change_mon(c, selmon); @@ -2284,7 +2718,7 @@ void closemon(Monitor *m) { } if (selmon) { focusclient(focustop(selmon), 1); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } } @@ -2302,7 +2736,7 @@ static void iter_layer_scene_buffers(struct wlr_scene_buffer *buffer, wlr_scene_blur_set_transparency_mask_source(l->blur, buffer); wlr_scene_blur_set_size(l->blur, l->geom.width, l->geom.height); - if (blur_optimized) { + if (config.blur_optimized) { wlr_scene_blur_set_should_only_blur_bottom_layer(l->blur, true); } else { wlr_scene_blur_set_should_only_blur_bottom_layer(l->blur, false); @@ -2322,6 +2756,19 @@ void layer_flush_blur_background(LayerSurface *l) { } } +void layer_flush_blur_background(LayerSurface *l) { + if (!config.blur) + return; + + // 如果背景层发生变化,标记优化的模糊背景缓存需要更新 + if (l->layer_surface->current.layer == + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND) { + if (l->mon) { + wlr_scene_optimized_blur_mark_dirty(l->mon->blur); + } + } +} + void maplayersurfacenotify(struct wl_listener *listener, void *data) { LayerSurface *l = wl_container_of(listener, l, map); struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; @@ -2332,9 +2779,9 @@ void maplayersurfacenotify(struct wl_listener *listener, void *data) { if (!l->mon) return; - strncpy(l->mon->last_surface_ws_name, layer_surface->namespace, - sizeof(l->mon->last_surface_ws_name) - 1); // 最多拷贝255个字符 - l->mon->last_surface_ws_name[sizeof(l->mon->last_surface_ws_name) - 1] = + strncpy(l->mon->last_open_surface, layer_surface->namespace, + sizeof(l->mon->last_open_surface) - 1); // 最多拷贝255个字符 + l->mon->last_open_surface[sizeof(l->mon->last_open_surface) - 1] = '\0'; // 确保字符串以null结尾 // 初始化几何位置 @@ -2377,20 +2824,14 @@ void maplayersurfacenotify(struct wl_listener *listener, void *data) { } // 初始化动画 - if (animations && layer_animations && !l->noanim) { - l->animation.duration = animation_duration_open; + if (config.animations && config.layer_animations && !l->noanim) { + l->animation.duration = config.animation_duration_open; l->animation.action = OPEN; layer_set_pending_state(l); } // 刷新布局,让窗口能感应到exclude_zone变化以及设置独占表面 arrangelayers(l->mon); - - // 按需交互layer需要像正常窗口一样抢占非独占layer的焦点 - if (!exclusive_focus && - l->layer_surface->current.keyboard_interactive == - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) { - focuslayer(l); - } + reset_exclusive_layers_focus(l->mon); } void commitlayersurfacenotify(struct wl_listener *listener, void *data) { @@ -2410,7 +2851,12 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { l->layer_surface->current = l->layer_surface->pending; arrangelayers(l->mon); l->layer_surface->current = old_state; - + // 按需交互layer只在map之前设置焦点 + if (!exclusive_focus && + l->layer_surface->current.keyboard_interactive == + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) { + focuslayer(l); + } return; } @@ -2425,7 +2871,8 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { get_layer_target_geometry(l, &box); - if (animations && layer_animations && !l->noanim && l->mapped && + if (config.animations && config.layer_animations && !l->noanim && + l->mapped && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND && !wlr_box_equal(&box, &l->geom)) { @@ -2435,13 +2882,12 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { l->geom.width = box.width; l->geom.height = box.height; l->animation.action = MOVE; - l->animation.duration = animation_duration_move; + l->animation.duration = config.animation_duration_move; l->need_output_flush = true; layer_set_pending_state(l); } - if (blur && blur_layer) { - // 设置非背景layer模糊 + if (config.blur && config.blur_layer) { if (!l->noblur && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM && @@ -2456,28 +2902,31 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { layer_flush_blur_background(l); - if (layer_surface == exclusive_focus && - layer_surface->current.keyboard_interactive != - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) - exclusive_focus = NULL; - if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped) return; l->mapped = layer_surface->surface->mapped; - if (scene_layer != l->scene->node.parent) { - wlr_scene_node_reparent(&l->scene->node, scene_layer); - wl_list_remove(&l->link); - wl_list_insert(&l->mon->layers[layer_surface->current.layer], &l->link); - wlr_scene_node_reparent( - &l->popups->node, - (layer_surface->current.layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP - ? layers[LyrTop] - : scene_layer)); + if (layer_surface->current.committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { + if (scene_layer != l->scene->node.parent) { + wlr_scene_node_reparent(&l->scene->node, scene_layer); + wl_list_remove(&l->link); + wl_list_insert(&l->mon->layers[layer_surface->current.layer], + &l->link); + wlr_scene_node_reparent( + &l->popups->node, + (layer_surface->current.layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP + ? layers[LyrTop] + : scene_layer)); + } } arrangelayers(l->mon); + + if (layer_surface->current.committed & + WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY) { + reset_exclusive_layers_focus(l->mon); + } } void commitnotify(struct wl_listener *listener, void *data) { @@ -2553,37 +3002,113 @@ void destroydecoration(struct wl_listener *listener, void *data) { wl_list_remove(&c->set_decoration_mode.link); } -void commitpopup(struct wl_listener *listener, void *data) { - struct wlr_surface *surface = data; - struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); - LayerSurface *l = NULL; +static bool popup_unconstrain(Popup *popup) { + struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; Client *c = NULL; - struct wlr_box box; + LayerSurface *l = NULL; int32_t type = -1; - if (!popup || !popup->base->initial_commit) - goto commitpopup_listen_free; - - type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); - if (!popup->parent || !popup->parent->data || type < 0) - goto commitpopup_listen_free; - - wlr_scene_node_raise_to_top(popup->parent->data); - - popup->base->surface->data = - wlr_scene_xdg_surface_create(popup->parent->data, popup->base); - if ((l && !l->mon) || (c && !c->mon)) { - wlr_xdg_popup_destroy(popup); - goto commitpopup_listen_free; + if (!wlr_popup || !wlr_popup->parent) { + return false; } - box = type == LayerShell ? l->mon->m : c->mon->w; - box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x); - box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y); - wlr_xdg_popup_unconstrain_from_box(popup, &box); -commitpopup_listen_free: - wl_list_remove(&listener->link); - free(listener); + struct wlr_scene_node *parent_node = wlr_popup->parent->data; + if (!parent_node) { + wlr_log(WLR_ERROR, "Popup parent has no scene node"); + return false; + } + + type = toplevel_from_wlr_surface(wlr_popup->base->surface, &c, &l); + if ((l && !l->mon) || (c && !c->mon)) { + return true; + } + + struct wlr_box usable = type == LayerShell ? l->mon->m : c->mon->w; + + int lx, ly; + struct wlr_box constraint_box; + + if (type == LayerShell) { + wlr_scene_node_coords(&l->scene_layer->tree->node, &lx, &ly); + constraint_box.x = usable.x - lx; + constraint_box.y = usable.y - ly; + constraint_box.width = usable.width; + constraint_box.height = usable.height; + } else { + constraint_box.x = + usable.x - (c->geom.x + c->bw - c->surface.xdg->current.geometry.x); + constraint_box.y = + usable.y - (c->geom.y + c->bw - c->surface.xdg->current.geometry.y); + constraint_box.width = usable.width; + constraint_box.height = usable.height; + } + + wlr_xdg_popup_unconstrain_from_box(wlr_popup, &constraint_box); + return false; +} + +static void destroypopup(struct wl_listener *listener, void *data) { + Popup *popup = wl_container_of(listener, popup, destroy); + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->reposition.link); + free(popup); +} + +static void commitpopup(struct wl_listener *listener, void *data) { + Popup *popup = wl_container_of(listener, popup, commit); + + struct wlr_surface *surface = data; + bool should_destroy = false; + struct wlr_xdg_popup *wlr_popup = + wlr_xdg_popup_try_from_wlr_surface(surface); + + if (!wlr_popup->base->initial_commit) + return; + + if (!wlr_popup->parent || !wlr_popup->parent->data) { + should_destroy = true; + goto cleanup_popup_commit; + } + + wlr_scene_node_raise_to_top(wlr_popup->parent->data); + + wlr_popup->base->surface->data = + wlr_scene_xdg_surface_create(wlr_popup->parent->data, wlr_popup->base); + + popup->wlr_popup = wlr_popup; + + should_destroy = popup_unconstrain(popup); + +cleanup_popup_commit: + + wl_list_remove(&popup->commit.link); + popup->commit.notify = NULL; + + if (should_destroy) { + wlr_xdg_popup_destroy(wlr_popup); + } +} + +static void repositionpopup(struct wl_listener *listener, void *data) { + Popup *popup = wl_container_of(listener, popup, reposition); + (void)popup_unconstrain(popup); +} + +static void createpopup(struct wl_listener *listener, void *data) { + struct wlr_xdg_popup *wlr_popup = data; + + Popup *popup = calloc(1, sizeof(Popup)); + if (!popup) + return; + + popup->destroy.notify = destroypopup; + wl_signal_add(&wlr_popup->events.destroy, &popup->destroy); + + popup->commit.notify = commitpopup; + wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); + + popup->reposition.notify = repositionpopup; + wl_signal_add(&wlr_popup->events.reposition, &popup->reposition); } void createdecoration(struct wl_listener *listener, void *data) { @@ -2641,22 +3166,21 @@ KeyboardGroup *createkeyboardgroup(void) { group->wlr_group = wlr_keyboard_group_create(); group->wlr_group->data = group; - /* Prepare an XKB keymap and assign it to the keyboard group. */ context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, + if (!(keymap = xkb_keymap_new_from_names(context, &config.xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS))) die("failed to compile keymap"); wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); - if (numlockon) { + if (config.numlockon) { xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM); if (mod_index != XKB_MOD_INVALID) locked_mods |= (uint32_t)1 << mod_index; } - if (capslock) { + if (config.capslock) { xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS); if (mod_index != XKB_MOD_INVALID) @@ -2670,8 +3194,8 @@ KeyboardGroup *createkeyboardgroup(void) { xkb_keymap_unref(keymap); xkb_context_unref(context); - wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, - repeat_delay); + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, + config.repeat_rate, config.repeat_delay); /* Set up listeners for keyboard events */ LISTEN(&group->wlr_group->keyboard.events.key, &group->key, keypress); @@ -2724,7 +3248,6 @@ void createlayersurface(struct wl_listener *listener, void *data) { LISTEN(&l->scene->node.events.destroy, &l->destroy, destroylayernodenotify); wl_list_insert(&l->mon->layers[layer_surface->pending.layer], &l->link); - wlr_surface_send_enter(surface, layer_surface->output); } void createlocksurface(struct wl_listener *listener, void *data) { @@ -2779,18 +3302,59 @@ void enable_adaptive_sync(Monitor *m, struct wlr_output_state *state) { } } +bool monitor_matches_rule(Monitor *m, const ConfigMonitorRule *rule) { + if (rule->name != NULL && !regex_match(rule->name, m->wlr_output->name)) + return false; + if (rule->make != NULL && (m->wlr_output->make == NULL || + strcmp(rule->make, m->wlr_output->make) != 0)) + return false; + if (rule->model != NULL && (m->wlr_output->model == NULL || + strcmp(rule->model, m->wlr_output->model) != 0)) + return false; + if (rule->serial != NULL && + (m->wlr_output->serial == NULL || + strcmp(rule->serial, m->wlr_output->serial) != 0)) + return false; + return true; +} + +/* 将规则中的显示参数应用到 wlr_output_state 中,返回是否设置了自定义模式 */ +bool apply_rule_to_state(Monitor *m, const ConfigMonitorRule *rule, + struct wlr_output_state *state, int vrr, int custom) { + bool mode_set = false; + if (rule->width > 0 && rule->height > 0 && rule->refresh > 0) { + struct wlr_output_mode *internal_mode = get_nearest_output_mode( + m->wlr_output, rule->width, rule->height, rule->refresh); + if (internal_mode) { + wlr_output_state_set_mode(state, internal_mode); + mode_set = true; + } else if (custom || wlr_output_is_headless(m->wlr_output)) { + wlr_output_state_set_custom_mode( + state, rule->width, rule->height, + (int32_t)roundf(rule->refresh * 1000)); + mode_set = true; + } + } + if (vrr) { + enable_adaptive_sync(m, state); + } else { + wlr_output_state_set_adaptive_sync_enabled(state, false); + } + wlr_output_state_set_scale(state, rule->scale); + wlr_output_state_set_transform(state, rule->rr); + return mode_set; +} + void createmon(struct wl_listener *listener, void *data) { /* This event is raised by the backend when a new output (aka a display or * monitor) becomes available. */ struct wlr_output *wlr_output = data; const ConfigMonitorRule *r; uint32_t i; - int32_t ji, vrr; + int32_t ji, vrr, custom; struct wlr_output_state state; Monitor *m = NULL; - struct wlr_output_mode *internal_mode = NULL; bool custom_monitor_mode = false; - bool match_rule = false; if (!wlr_output_init_render(wlr_output, alloc, drw)) return; @@ -2803,7 +3367,17 @@ void createmon(struct wl_listener *listener, void *data) { return; } + struct wl_event_loop *loop = wl_display_get_event_loop(dpy); m = wlr_output->data = ecalloc(1, sizeof(*m)); + + m->iscleanuping = false; + m->skip_frame_timeout = + wl_event_loop_add_timer(loop, monitor_skip_frame_timeout_callback, m); + m->skiping_frame = false; + m->resizing_count_pending = 0; + m->resizing_count_current = 0; + m->carousel_anim_dir = 0; + m->wlr_output = wlr_output; m->wlr_output->data = m; @@ -2812,17 +3386,19 @@ void createmon(struct wl_listener *listener, void *data) { for (i = 0; i < LENGTH(m->layers); i++) wl_list_init(&m->layers[i]); - wlr_output_state_init(&state); - /* Initialize monitor state using configured rules */ - m->gappih = gappih; - m->gappiv = gappiv; - m->gappoh = gappoh; - m->gappov = gappov; + m->gappih = config.gappih; + m->gappiv = config.gappiv; + m->gappoh = config.gappoh; + m->gappov = config.gappov; m->isoverview = 0; m->sel = NULL; m->is_in_hotarea = 0; + m->m.x = INT32_MAX; + m->m.y = INT32_MAX; float scale = 1; enum wl_output_transform rr = WL_OUTPUT_TRANSFORM_NORMAL; + + wlr_output_state_init(&state); wlr_output_state_set_scale(&state, scale); wlr_output_state_set_transform(&state, rr); @@ -2832,74 +3408,21 @@ void createmon(struct wl_listener *listener, void *data) { r = &config.monitor_rules[ji]; - // 检查是否匹配的变量 - match_rule = true; - - // 检查四个标识字段的匹配 - if (r->name != NULL) { - if (!regex_match(r->name, m->wlr_output->name)) { - match_rule = false; - } - } - - if (r->make != NULL) { - if (m->wlr_output->make == NULL || - strcmp(r->make, m->wlr_output->make) != 0) { - match_rule = false; - } - } - - if (r->model != NULL) { - if (m->wlr_output->model == NULL || - strcmp(r->model, m->wlr_output->model) != 0) { - match_rule = false; - } - } - - if (r->serial != NULL) { - if (m->wlr_output->serial == NULL || - strcmp(r->serial, m->wlr_output->serial) != 0) { - match_rule = false; - } - } - - if (match_rule) { + if (monitor_matches_rule(m, r)) { m->m.x = r->x == INT32_MAX ? INT32_MAX : r->x; m->m.y = r->y == INT32_MAX ? INT32_MAX : r->y; vrr = r->vrr >= 0 ? r->vrr : 0; + custom = r->custom >= 0 ? r->custom : 0; scale = r->scale; rr = r->rr; - if (r->width > 0 && r->height > 0 && r->refresh > 0) { - internal_mode = get_nearest_output_mode(m->wlr_output, r->width, - r->height, r->refresh); - if (internal_mode) { - custom_monitor_mode = true; - wlr_output_state_set_mode(&state, internal_mode); - } else if (wlr_output_is_headless(m->wlr_output)) { - custom_monitor_mode = true; - wlr_output_state_set_custom_mode( - &state, r->width, r->height, - (int32_t)roundf(r->refresh * 1000)); - } + if (apply_rule_to_state(m, r, &state, vrr, custom)) { + custom_monitor_mode = true; } - - if (vrr) { - enable_adaptive_sync(m, &state); - } else { - wlr_output_state_set_adaptive_sync_enabled(&state, false); - } - - wlr_output_state_set_scale(&state, r->scale); - wlr_output_state_set_transform(&state, r->rr); - break; + break; // 只应用第一个匹配规则 } } - /* The mode is a tuple of (width, height, refresh rate), and each - * monitor supports only a specific set of modes. We just pick the - * monitor's preferred mode; a more sophisticated compositor would let - * the user configure it. */ if (!custom_monitor_mode) wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); @@ -2915,7 +3438,11 @@ void createmon(struct wl_listener *listener, void *data) { wlr_output_state_finish(&state); wl_list_insert(&mons, &m->link); + m->pertag = calloc(1, sizeof(Pertag)); + for (int i = 0; i < LENGTH(tags) + 1; i++) + m->pertag->scroller_state[i] = NULL; + if (chvt_backup_tag && regex_match(chvt_backup_selmon, m->wlr_output->name)) { m->tagset[0] = m->tagset[1] = (1 << (chvt_backup_tag - 1)) & TAGMASK; @@ -2928,8 +3455,8 @@ void createmon(struct wl_listener *listener, void *data) { } for (i = 0; i <= LENGTH(tags); i++) { - m->pertag->nmasters[i] = default_nmaster; - m->pertag->mfacts[i] = default_mfact; + m->pertag->nmasters[i] = config.default_nmaster; + m->pertag->mfacts[i] = config.default_mfact; m->pertag->ltidxs[i] = &layouts[0]; } @@ -2957,12 +3484,11 @@ void createmon(struct wl_listener *listener, void *data) { else wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); - if (blur) { + if (config.blur) { m->blur = wlr_scene_optimized_blur_create(&scene->tree, 0, 0); wlr_scene_node_set_position(&m->blur->node, m->m.x, m->m.y); wlr_scene_node_reparent(&m->blur->node, layers[LyrBlur]); wlr_scene_optimized_blur_set_size(m->blur, m->m.width, m->m.height); - // wlr_scene_node_set_enabled(&m->blur->node, 1); } m->ext_group = wlr_ext_workspace_group_handle_v1_create( ext_manager, EXT_WORKSPACE_ENABLE_CAPS); @@ -2972,7 +3498,7 @@ void createmon(struct wl_listener *listener, void *data) { add_workspace_by_tag(i, m); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void // fix for 0.5 @@ -2988,7 +3514,7 @@ createnotify(struct wl_listener *listener, void *data) { /* Allocate a Client for this surface */ c = toplevel->base->data = ecalloc(1, sizeof(*c)); c->surface.xdg = toplevel->base; - c->bw = borderpx; + c->bw = config.borderpx; LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); @@ -3032,46 +3558,14 @@ void destroyinputdevice(struct wl_listener *listener, void *data) { free(input_dev); } -void configure_pointer(struct libinput_device *device) { - if (libinput_device_config_tap_get_finger_count(device)) { - libinput_device_config_tap_set_enabled(device, tap_to_click); - libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); - libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); - libinput_device_config_tap_set_button_map(device, button_map); - libinput_device_config_scroll_set_natural_scroll_enabled( - device, trackpad_natural_scrolling); - } else { - libinput_device_config_scroll_set_natural_scroll_enabled( - device, mouse_natural_scrolling); - } - - if (libinput_device_config_dwt_is_available(device)) - libinput_device_config_dwt_set_enabled(device, disable_while_typing); - - if (libinput_device_config_left_handed_is_available(device)) - libinput_device_config_left_handed_set(device, left_handed); - - if (libinput_device_config_middle_emulation_is_available(device)) - libinput_device_config_middle_emulation_set_enabled( - device, middle_button_emulation); - - if (libinput_device_config_scroll_get_methods(device) != - LIBINPUT_CONFIG_SCROLL_NO_SCROLL) - libinput_device_config_scroll_set_method(device, scroll_method); - if (libinput_device_config_scroll_get_methods(device) == - LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) - libinput_device_config_scroll_set_button(device, scroll_button); - - if (libinput_device_config_click_get_methods(device) != - LIBINPUT_CONFIG_CLICK_METHOD_NONE) - libinput_device_config_click_set_method(device, click_method); - - if (libinput_device_config_send_events_get_modes(device)) - libinput_device_config_send_events_set_mode(device, send_events_mode); - - if (accel_profile && libinput_device_config_accel_is_available(device)) { - libinput_device_config_accel_set_profile(device, accel_profile); - libinput_device_config_accel_set_speed(device, accel_speed); +void pointer_set_accel(struct libinput_device *device, bool natural_scrolling, + uint32_t mouse_accel_profile, double mouse_accel_speed) { + libinput_device_config_scroll_set_natural_scroll_enabled(device, + natural_scrolling); + if (mouse_accel_profile && + libinput_device_config_accel_is_available(device)) { + libinput_device_config_accel_set_profile(device, mouse_accel_profile); + libinput_device_config_accel_set_speed(device, mouse_accel_speed); } else { // profile cannot be directly applied to 0, need to set to 1 first libinput_device_config_accel_set_profile(device, 1); @@ -3080,6 +3574,49 @@ void configure_pointer(struct libinput_device *device) { } } +void configure_pointer(struct libinput_device *device) { + if (libinput_device_config_tap_get_finger_count(device)) { + libinput_device_config_tap_set_enabled(device, config.tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, + config.tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, + config.drag_lock); + libinput_device_config_tap_set_button_map(device, config.button_map); + pointer_set_accel(device, config.trackpad_natural_scrolling, + config.trackpad_accel_profile, + config.trackpad_accel_speed); + } else { + pointer_set_accel(device, config.mouse_natural_scrolling, + config.mouse_accel_profile, config.mouse_accel_speed); + } + + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(device, + config.disable_while_typing); + + if (libinput_device_config_left_handed_is_available(device)) + libinput_device_config_left_handed_set(device, config.left_handed); + + if (libinput_device_config_middle_emulation_is_available(device)) + libinput_device_config_middle_emulation_set_enabled( + device, config.middle_button_emulation); + + if (libinput_device_config_scroll_get_methods(device) != + LIBINPUT_CONFIG_SCROLL_NO_SCROLL) + libinput_device_config_scroll_set_method(device, config.scroll_method); + if (libinput_device_config_scroll_get_methods(device) == + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) + libinput_device_config_scroll_set_button(device, config.scroll_button); + + if (libinput_device_config_click_get_methods(device) != + LIBINPUT_CONFIG_CLICK_METHOD_NONE) + libinput_device_config_click_set_method(device, config.click_method); + + if (libinput_device_config_send_events_get_modes(device)) + libinput_device_config_send_events_set_mode(device, + config.send_events_mode); +} + void createpointer(struct wlr_pointer *pointer) { struct libinput_device *device = NULL; @@ -3161,24 +3698,33 @@ void createpointerconstraint(struct wl_listener *listener, void *data) { pointer_constraint->constraint = data; LISTEN(&pointer_constraint->constraint->events.destroy, &pointer_constraint->destroy, destroypointerconstraint); -} -void createpopup(struct wl_listener *listener, void *data) { - /* This event is raised when a client (either xdg-shell or layer-shell) - * creates a new popup. */ - struct wlr_xdg_popup *popup = data; - LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); + if (!selmon || !selmon->sel) + return; + + struct wlr_surface *focused_surface = client_surface(selmon->sel); + if (focused_surface && + focused_surface == pointer_constraint->constraint->surface) { + cursorconstrain(pointer_constraint->constraint); + } } void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { if (active_constraint == constraint) return; - if (active_constraint) + if (active_constraint) { + if (constraint == NULL) { + cursorwarptohint(); + } wlr_pointer_constraint_v1_send_deactivated(active_constraint); + } active_constraint = constraint; - wlr_pointer_constraint_v1_send_activated(constraint); + + if (constraint) { + wlr_pointer_constraint_v1_send_activated(constraint); + } } void cursorframe(struct wl_listener *listener, void *data) { @@ -3265,8 +3811,7 @@ void destroylocksurface(struct wl_listener *listener, void *data) { if (lock_surface->surface != seat->keyboard_state.focused_surface) { if (exclusive_focus && !locked) { - exclusive_focus = NULL; - reset_exclusive_layer(m); + reset_exclusive_layers_focus(m); } return; } @@ -3275,9 +3820,7 @@ void destroylocksurface(struct wl_listener *listener, void *data) { surface = wl_container_of(cur_lock->surfaces.next, surface, link); client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); } else if (!locked) { - exclusive_focus = NULL; - reset_exclusive_layer(selmon); - focusclient(focustop(selmon), 1); + reset_exclusive_layers_focus(selmon); } else { wlr_seat_keyboard_clear_focus(seat); } @@ -3382,6 +3925,8 @@ void focusclient(Client *c, int32_t lift) { selmon->sel = c; c->isfocusing = true; + check_keep_idle_inhibit(c); + if (last_focus_client && !last_focus_client->iskilling && last_focus_client != c) { last_focus_client->isfocusing = false; @@ -3392,17 +3937,17 @@ void focusclient(Client *c, int32_t lift) { // decide whether need to re-arrange - if (c && selmon->prevsel && - (selmon->prevsel->tags & selmon->tagset[selmon->seltags]) && - (c->tags & selmon->tagset[selmon->seltags]) && !c->isfloating && - is_scroller_layout(selmon)) { - arrange(selmon, false, false); - } - // change focus link position wl_list_remove(&c->flink); wl_list_insert(&fstack, &c->flink); + if (c && selmon->prevsel && + (selmon->prevsel->tags & selmon->tagset[selmon->seltags]) && + (c->tags & selmon->tagset[selmon->seltags]) && !c->isfloating && + (is_scroller_layout(selmon) || is_monocle_layout(selmon))) { + arrange(selmon, false, false); + } + // change border color c->isurgent = 0; } @@ -3411,8 +3956,14 @@ void focusclient(Client *c, int32_t lift) { wl_list_for_each(um, &mons, link) { if (um->wlr_output->enabled && um != selmon && um->sel && !um->sel->iskilling && um->sel->isfocusing) { + um->sel->isfocusing = false; client_set_unfocused_opacity_animation(um->sel); + + if (um->sel->foreign_toplevel) { + wlr_foreign_toplevel_handle_v1_set_activated( + um->sel->foreign_toplevel, false); + } } } @@ -3444,7 +3995,7 @@ void focusclient(Client *c, int32_t lift) { client_activate_surface(old_keyboard_focus_surface, 0); } } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); if (!c) { @@ -3456,6 +4007,9 @@ void focusclient(Client *c, int32_t lift) { // clear text input focus state dwl_im_relay_set_focus(dwl_input_method_relay, NULL); wlr_seat_keyboard_notify_clear_focus(seat); + if (active_constraint) { + cursorconstrain(NULL); + } return; } @@ -3472,6 +4026,18 @@ void focusclient(Client *c, int32_t lift) { /* Activate the new client */ client_activate_surface(client_surface(c), 1); + + if (active_constraint && active_constraint->surface != client_surface(c)) { + cursorconstrain(NULL); + } + + struct wlr_pointer_constraint_v1 *constraint; + wl_list_for_each(constraint, &pointer_constraints->constraints, link) { + if (constraint->surface == client_surface(c)) { + cursorconstrain(constraint); + break; + } + } } void // 0.6 @@ -3481,7 +4047,7 @@ fullscreennotify(struct wl_listener *listener, void *data) { if (!c || c->iskilling) return; - setfullscreen(c, client_wants_fullscreen(c)); + setfullscreen(c, client_wants_fullscreen(c), true); } void requestmonstate(struct wl_listener *listener, void *data) { @@ -3525,6 +4091,12 @@ void inputdevice(struct wl_listener *listener, void *data) { case WLR_INPUT_DEVICE_KEYBOARD: createkeyboard(wlr_keyboard_from_input_device(device)); break; + case WLR_INPUT_DEVICE_TABLET: + createtablet(device); + break; + case WLR_INPUT_DEVICE_TABLET_PAD: + createtabletpad(device); + break; case WLR_INPUT_DEVICE_POINTER: createpointer(wlr_pointer_from_input_device(device)); break; @@ -3731,8 +4303,8 @@ void keypress(struct wl_listener *listener, void *data) { wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); // ov tab mode detect moe key release - if (ov_tab_mode && !locked && group == kb_group && - event->state == WL_KEYBOARD_KEY_STATE_RELEASED && + if (config.ov_tab_mode && !selmon->is_jump_mode && !locked && + group == kb_group && event->state == WL_KEYBOARD_KEY_STATE_RELEASED && (keycode == 133 || keycode == 37 || keycode == 64 || keycode == 50 || keycode == 134 || keycode == 105 || keycode == 108 || keycode == 62) && selmon && selmon->sel) { @@ -3741,6 +4313,11 @@ void keypress(struct wl_listener *listener, void *data) { } } + if (config.cursor_hide_on_keypress && !cursor_hidden && + event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + hidecursor(NULL); + } + /* On _press_ if there is no active screen locker, * attempt to process a compositor keybinding. */ for (i = 0; i < nsyms; i++) @@ -3767,6 +4344,27 @@ void keypress(struct wl_listener *listener, void *data) { if (handled) return; + if (selmon && selmon->is_jump_mode && + event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) { + xkb_keysym_t sym = xkb_keysym_to_lower(syms[i]); + if (sym >= XKB_KEY_a && sym <= XKB_KEY_z) { + char c_char = 'A' + (sym - XKB_KEY_a); + Client *c; + wl_list_for_each(c, &clients, link) { + if (c->mon == selmon && c->jump_char == c_char) { + focusclient(c, 1); + toggleoverview(&(Arg){.i = 1}); + return; + } + } + } else if (sym == XKB_KEY_Escape) { + togglejump(&(Arg){.i = 0}); + return; + } + } + } + /* don't pass when popup is focused * this is better than having popups (like fuzzel or wmenu) closing * while typing in a passed keybind */ @@ -3804,6 +4402,14 @@ void keypressmod(struct wl_listener *listener, void *data) { wlr_seat_keyboard_notify_modifiers( seat, &group->wlr_group->keyboard.modifiers); } + + xkb_layout_index_t current = xkb_state_serialize_layout( + group->wlr_group->keyboard.xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); + + if (current != group->layout_index) { + group->layout_index = current; + printstatus(IPC_WATCH_KB_LAYOUT); + } } void pending_kill_client(Client *c) { @@ -3815,7 +4421,7 @@ void pending_kill_client(Client *c) { void locksession(struct wl_listener *listener, void *data) { struct wlr_session_lock_v1 *session_lock = data; SessionLock *lock; - if (!allow_lock_transparent) { + if (!config.allow_lock_transparent) { wlr_scene_node_set_enabled(&locked_bg->node, true); } if (cur_lock) { @@ -3852,7 +4458,7 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, if (wlr_subsurface_try_from_wlr_surface(surface) != NULL) return; - if (blur && c && !c->noblur) { + if (config.blur && c && !c->noblur) { wlr_scene_node_set_enabled(&c->blur->node, true); if (blur_optimized) { wlr_scene_blur_set_should_only_blur_bottom_layer(c->blur, true); @@ -3865,7 +4471,33 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, } void init_client_properties(Client *c) { + c->grid_col_per = 1.0f; + c->grid_row_per = 1.0f; + c->is_monocle_hide = false; + c->jump_label_node = NULL; + c->tab_bar_node = NULL; + c->overview_scene_surface = NULL; + c->drop_direction = UNDIR; + c->enable_drop_area_draw = false; c->isfocusing = false; + c->isfloating = 0; + c->isfakefullscreen = 0; + c->isnoanimation = 0; + c->isopensilent = 0; + c->istagsilent = 0; + c->noswallow = 0; + c->isterm = 0; + c->noblur = 0; + c->tearing_hint = 0; + c->overview_isfullscreenbak = 0; + c->overview_ismaximizescreenbak = 0; + c->overview_isfloatingbak = 0; + c->pid = 0; + c->swallowing = NULL; + c->swallowedby = NULL; + c->ismaster = 0; + c->old_ismaster = 0; + c->isleftstack = 0; c->ismaximizescreen = 0; c->isfullscreen = 0; c->need_float_size_reduce = 0; @@ -3883,13 +4515,13 @@ void init_client_properties(Client *c) { c->is_restoring_from_ov = 0; c->isurgent = 0; c->need_output_flush = 0; - c->scroller_proportion = scroller_default_proportion; + c->scroller_proportion = config.scroller_default_proportion; c->is_pending_open_animation = true; c->drag_to_tile = false; c->scratchpad_switching_mon = false; c->fake_no_border = false; - c->focused_opacity = focused_opacity; - c->unfocused_opacity = unfocused_opacity; + c->focused_opacity = config.focused_opacity; + c->unfocused_opacity = config.unfocused_opacity; c->nofocus = 0; c->nofadein = 0; c->nofadeout = 0; @@ -3902,26 +4534,31 @@ void init_client_properties(Client *c) { c->ignore_minimize = 1; c->iscustomsize = 0; c->iscustompos = 0; + c->iscustom_scroller_proportion = 0; + c->iscustom_scroller_proportion_single = 0; c->master_mfact_per = 0.0f; c->master_inner_per = 0.0f; c->stack_inner_per = 0.0f; + c->old_stack_inner_per = 0.0f; + c->old_master_inner_per = 0.0f; + c->old_master_mfact_per = 0.0f; c->isterm = 0; c->allow_csd = 0; - c->force_maximize = 0; + c->force_fakemaximize = 0; c->force_tiled_state = 1; c->force_tearing = 0; c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; + c->idleinhibit_when_focus = 0; c->scroller_proportion_single = 0.0f; c->float_geom.width = 0; c->float_geom.height = 0; c->float_geom.x = 0; c->float_geom.y = 0; c->stack_proportion = 0.0f; - c->next_in_stack = NULL; - c->prev_in_stack = NULL; - memcpy(c->opacity_animation.initial_border_color, bordercolor, + memset(c->oldmonname, 0, sizeof(c->oldmonname)); + memcpy(c->opacity_animation.initial_border_color, config.bordercolor, sizeof(c->opacity_animation.initial_border_color)); - memcpy(c->opacity_animation.current_border_color, bordercolor, + memcpy(c->opacity_animation.current_border_color, config.bordercolor, sizeof(c->opacity_animation.current_border_color)); c->opacity_animation.initial_opacity = c->unfocused_opacity; c->opacity_animation.current_opacity = c->unfocused_opacity; @@ -3932,6 +4569,10 @@ mapnotify(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ Client *at_client = NULL; Client *c = wl_container_of(listener, c, map); + int32_t i = 0; + + c->id = generate_client_id(); + /* Create scene tree for this client and its border */ c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell); @@ -3951,7 +4592,7 @@ mapnotify(struct wl_listener *listener, void *data) { c->bw = 0; c->isnoborder = 1; } else { - c->bw = borderpx; + c->bw = config.borderpx; } if (client_should_global(c)) { @@ -3961,6 +4602,7 @@ mapnotify(struct wl_listener *listener, void *data) { // init client geom c->geom.width += 2 * c->bw; c->geom.height += 2 * c->bw; + c->overview_backup_geom = c->geom; struct wlr_ext_foreign_toplevel_handle_v1_state foreign_toplevel_state = { .app_id = client_get_appid(c), @@ -3976,34 +4618,50 @@ mapnotify(struct wl_listener *listener, void *data) { /* Handle unmanaged clients first so we can return prior create borders */ +#ifdef XWAYLAND if (client_is_unmanaged(c)) { /* Unmanaged clients always are floating */ -#ifdef XWAYLAND - if (client_is_x11(c)) { - fix_xwayland_unmanaged_coordinate(c); - LISTEN(&c->surface.xwayland->events.set_geometry, &c->set_geometry, - setgeometrynotify); - } -#endif - wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]); + fix_xwayland_coordinate(&c->geom); wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_xwayland_surface_configure(c->surface.xwayland, c->geom.x, + c->geom.y, c->geom.width, + c->geom.height); + LISTEN(&c->surface.xwayland->events.set_geometry, &c->set_geometry, + setgeometrynotify); + wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]); if (client_wants_focus(c)) { focusclient(c, 1); exclusive_focus = c; } return; } - +#endif // extra node - c->border = wlr_scene_rect_create(c->scene, 0, 0, - c->isurgent ? urgentcolor : bordercolor); + + for (i = 0; i < 2; i++) { + c->splitindicator[i] = wlr_scene_rect_create( + c->scene, 0, 0, + c->isurgent ? config.urgentcolor : config.splitcolor); + c->splitindicator[i]->node.data = c; + wlr_scene_node_lower_to_bottom(&c->splitindicator[i]->node); + wlr_scene_node_set_enabled(&c->splitindicator[i]->node, false); + } + + c->droparea = wlr_scene_rect_create(c->scene, 0, 0, config.dropcolor); + wlr_scene_node_lower_to_bottom(&c->droparea->node); + wlr_scene_node_set_position(&c->droparea->node, 0, 0); + wlr_scene_node_set_enabled(&c->droparea->node, false); + + c->border = wlr_scene_rect_create( + c->scene, 0, 0, c->isurgent ? config.urgentcolor : config.bordercolor); wlr_scene_node_lower_to_bottom(&c->border->node); wlr_scene_node_set_position(&c->border->node, 0, 0); - wlr_scene_rect_set_corner_radii(c->border, corner_radii_all(border_radius)); + wlr_scene_rect_set_corner_radii(c->border, corner_radii_all(config.border_radius)); wlr_scene_node_set_enabled(&c->border->node, true); - c->shadow = wlr_scene_shadow_create(c->scene, 0, 0, border_radius, - shadows_blur, shadowscolor); + c->shadow = + wlr_scene_shadow_create(c->scene, 0, 0, config.border_radius, + config.shadows_blur, config.shadowscolor); c->blur = wlr_scene_blur_create(c->scene_surface, 0, 0); wlr_scene_node_lower_to_bottom(&c->blur->node); @@ -4017,7 +4675,7 @@ mapnotify(struct wl_listener *listener, void *data) { wlr_scene_node_lower_to_bottom(&c->shield->node); wlr_scene_node_set_enabled(&c->shield->node, false); - if (new_is_master && selmon && !is_scroller_layout(selmon)) + if (config.new_is_master && selmon && !is_scroller_layout(selmon)) // tile at the top wl_list_insert(&clients, &c->link); // 新窗口是master,头部入栈 else if (selmon && is_scroller_layout(selmon) && @@ -4025,21 +4683,19 @@ mapnotify(struct wl_listener *listener, void *data) { if (selmon->sel && ISSCROLLTILED(selmon->sel) && VISIBLEON(selmon->sel, selmon)) { - at_client = get_scroll_stack_head(selmon->sel); + at_client = scroll_get_stack_tail_client(selmon->sel); } else { at_client = center_tiled_select(selmon); } if (at_client) { - at_client->link.next->prev = &c->link; - c->link.prev = &at_client->link; - c->link.next = at_client->link.next; - at_client->link.next = &c->link; + wl_list_insert(&at_client->link, &c->link); } else { wl_list_insert(clients.prev, &c->link); // 尾部入栈 } } else wl_list_insert(clients.prev, &c->link); // 尾部入栈 + wl_list_insert(&fstack, &c->flink); applyrules(c); @@ -4052,14 +4708,19 @@ mapnotify(struct wl_listener *listener, void *data) { // apply buffer effects of client wlr_scene_node_for_each_buffer(&c->scene_surface->node, iter_xdg_scene_buffers, c); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); // set border color setborder_color(c); + if (c->mon && c->mon->isoverview && config.ov_no_resize) { + overview_backup_surface(c); + } + // make sure the animation is open type c->is_pending_open_animation = true; resize(c, c->geom, 0); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void maximizenotify(struct wl_listener *listener, void *data) { @@ -4074,15 +4735,15 @@ void maximizenotify(struct wl_listener *listener, void *data) { } if (client_request_maximize(c, data)) { - setmaximizescreen(c, 1); + setmaximizescreen(c, 1, true); } else { - setmaximizescreen(c, 0); + setmaximizescreen(c, 0, true); } } void unminimize(Client *c) { if (c && c->is_in_scratchpad && c->is_scratchpad_show) { - c->isminimized = 0; + client_pending_minimized_state(c, 0); c->is_scratchpad_show = 0; c->is_in_scratchpad = 0; c->isnamedscratchpad = 0; @@ -4110,13 +4771,16 @@ void set_minimized(Client *c) { c->oldtags = c->mon->tagset[c->mon->seltags]; c->mini_restore_tag = c->tags; c->tags = 0; - c->isminimized = 1; + client_pending_minimized_state(c, 1); c->is_in_scratchpad = 1; c->is_scratchpad_show = 0; focusclient(focustop(selmon), 1); arrange(c->mon, false, false); - wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, false); - wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, true); + + if (c->foreign_toplevel) + wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, + false); + wl_list_remove(&c->link); // 从原来位置移除 wl_list_insert(clients.prev, &c->link); // 插入尾部 } @@ -4192,11 +4856,48 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel) { double sx = 0, sy = 0, sx_confined, sy_confined; Client *c = NULL, *w = NULL; + Client *closet_drop_client = NULL; LayerSurface *l = NULL; struct wlr_surface *surface = NULL; - struct wlr_pointer_constraint_v1 *constraint; bool should_lock = false; + /* time is 0 in internal calls meant to restore pointer focus. */ + if (time) { + wlr_relative_pointer_manager_v1_send_relative_motion( + relative_pointer_mgr, seat, (uint64_t)time * 1000, dx, dy, + dx_unaccel, dy_unaccel); + + if (active_constraint && cursor_mode != CurResize && + cursor_mode != CurMove) { + if (active_constraint->surface == + seat->pointer_state.focused_surface) { + + if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) + return; + + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c) { + sx = cursor->x - c->geom.x - c->bw; + sy = cursor->y - c->geom.y - c->bw; + if (wlr_region_confine(&active_constraint->region, sx, sy, + sx + dx, sy + dy, &sx_confined, + &sy_confined)) { + dx = sx_confined - sx; + dy = sy_confined - sy; + } + } + } + } + + wlr_cursor_move(cursor, device, dx, dy); + handlecursoractivity(); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + /* Update selmon (even while dragging a window) */ + if (config.sloppyfocus) + selmon = xytomon(cursor->x, cursor->y); + } + /* Find the client under the pointer and send the event along. */ xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); @@ -4210,43 +4911,6 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, sy = cursor->y - (l ? l->scene->node.y : w->geom.y); } - /* time is 0 in internal calls meant to restore pointer focus. */ - if (time) { - wlr_relative_pointer_manager_v1_send_relative_motion( - relative_pointer_mgr, seat, (uint64_t)time * 1000, dx, dy, - dx_unaccel, dy_unaccel); - - wl_list_for_each(constraint, &pointer_constraints->constraints, link) - cursorconstrain(constraint); - - if (active_constraint && cursor_mode != CurResize && - cursor_mode != CurMove) { - toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); - if (c && active_constraint->surface == - seat->pointer_state.focused_surface) { - sx = cursor->x - c->geom.x - c->bw; - sy = cursor->y - c->geom.y - c->bw; - if (wlr_region_confine(&active_constraint->region, sx, sy, - sx + dx, sy + dy, &sx_confined, - &sy_confined)) { - dx = sx_confined - sx; - dy = sy_confined - sy; - } - - if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) - return; - } - } - - wlr_cursor_move(cursor, device, dx, dy); - handlecursoractivity(); - wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); - - /* Update selmon (even while dragging a window) */ - if (sloppyfocus) - selmon = xytomon(cursor->x, cursor->y); - } - /* Update drag icon's position */ wlr_scene_node_set_position(&drag_icon->node, (int32_t)round(cursor->x), (int32_t)round(cursor->y)); @@ -4260,13 +4924,32 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, .y = (int32_t)round(cursor->y) - grabcy, .width = grabc->geom.width, .height = grabc->geom.height}; + if (config.drag_tile_to_tile && grabc->drag_to_tile) { + closet_drop_client = find_closest_tiled_client(grabc); + if (closet_drop_client && dropc && closet_drop_client != dropc) { + dropc->enable_drop_area_draw = false; + client_set_drop_area(dropc); + dropc = closet_drop_client; + dropc->enable_drop_area_draw = true; + client_set_drop_area(dropc); + } else if (closet_drop_client) { + dropc = closet_drop_client; + dropc->enable_drop_area_draw = true; + client_set_drop_area(dropc); + } else if (dropc) { + dropc->enable_drop_area_draw = false; + client_set_drop_area(dropc); + dropc = NULL; + } + } resize(grabc, grabc->float_geom, 1); return; } else if (cursor_mode == CurResize) { if (grabc->isfloating) { grabc->iscustomsize = 1; if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_floating_refresh_interval) { + time - last_apply_drap_time > + config.drag_floating_refresh_interval) { resize_floating_window(grabc); last_apply_drap_time = time; } @@ -4282,19 +4965,30 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, if (!surface && !seat->drag && !cursor_hidden) wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); - if (c && c->mon && !c->animation.running && (INSIDEMON(c) || !ISTILED(c))) { + if (c && c->mon && !c->animation.running && + (INSIDEMON(c) || !ISSCROLLTILED(c))) { scroller_focus_lock = 0; } should_lock = false; + double speed = 0.0f; + + if (config.edge_scroller_pointer_focus) { + speed = sqrt(dx * dx + dy * dy); + } + if (!scroller_focus_lock || !(c && c->mon && !INSIDEMON(c))) { - if (c && c->mon && is_scroller_layout(c->mon) && !INSIDEMON(c)) { + if (c && c->mon && ISSCROLLTILED(c) && is_scroller_layout(c->mon) && + !INSIDEMON(c)) { should_lock = true; } - if (!(!edge_scroller_pointer_focus && c && c->mon && - is_scroller_layout(c->mon) && !INSIDEMON(c))) + if (!((!config.edge_scroller_pointer_focus || + speed < config.edge_scroller_focus_allow_speed) && + c && c->mon && ISSCROLLTILED(c) && is_scroller_layout(c->mon) && + !INSIDEMON(c))) { pointerfocus(c, surface, sx, sy, time); + } if (should_lock && c && c->mon && ISTILED(c) && c == c->mon->sel) { scroller_focus_lock = 1; @@ -4418,9 +5112,10 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time) { struct timespec now; - if (sloppyfocus && !start_drag_window && c && time && c->scene && + if (config.sloppyfocus && !start_drag_window && c && time && c->scene && c->scene->node.enabled && !c->animation.tagining && - (surface != seat->pointer_state.focused_surface) && + (surface != seat->pointer_state.focused_surface || + (selmon && selmon->isoverview && selmon->sel != c)) && !client_is_unmanaged(c) && VISIBLEON(c, c->mon)) focusclient(c, 0); @@ -4438,12 +5133,20 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, /* Let the client know that the mouse cursor has entered one * of its surfaces, and make keyboard focus follow if desired. * wlroots makes this a no-op if surface is already focused */ - wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + + if (!c || !c->mon || !c->mon->isoverview) { + // don't let window get pointer focus, + // avoid game window force grab pointer in overview mode + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + } + wlr_seat_pointer_notify_motion(seat, time, sx, sy); } // 修改printstatus函数,接受掩码参数 -void printstatus(void) { wl_signal_emit(&mango_print_status, NULL); } +void printstatus(enum ipc_watch_type type) { + wl_signal_emit(&mango_print_status, &type); +} // 会话销毁时的回调 void handle_session_destroy(struct wl_listener *listener, void *data) { @@ -4515,14 +5218,47 @@ void scene_buffer_apply_opacity(struct wlr_scene_buffer *buffer, int32_t sx, } void client_set_opacity(Client *c, double opacity) { + opacity = CLAMP_FLOAT(opacity, 0.0f, 1.0f); wlr_scene_node_for_each_buffer(&c->scene_surface->node, scene_buffer_apply_opacity, &opacity); } +void monitor_stop_skip_frame_timer(Monitor *m) { + if (m->skip_frame_timeout) + wl_event_source_timer_update(m->skip_frame_timeout, 0); + m->skiping_frame = false; + m->resizing_count_pending = 0; + m->resizing_count_current = 0; +} + +static int monitor_skip_frame_timeout_callback(void *data) { + Monitor *m = data; + Client *c, *tmp; + + wl_list_for_each_safe(c, tmp, &clients, link) { c->configure_serial = 0; } + + monitor_stop_skip_frame_timer(m); + wlr_output_schedule_frame(m->wlr_output); + + return 1; +} + +void monitor_check_skip_frame_timeout(Monitor *m) { + if (m->skiping_frame && + m->resizing_count_pending == m->resizing_count_current) { + return; + } + + if (m->skip_frame_timeout) { + m->resizing_count_current = m->resizing_count_pending; + m->skiping_frame = true; + wl_event_source_timer_update(m->skip_frame_timeout, 100); // 100ms + } +} + void rendermon(struct wl_listener *listener, void *data) { Monitor *m = wl_container_of(listener, m, frame); Client *c = NULL, *tmp = NULL; - struct wlr_output_state pending = {0}; LayerSurface *l = NULL, *tmpl = NULL; int32_t i; struct wl_list *layer_list; @@ -4558,15 +5294,19 @@ void rendermon(struct wl_listener *listener, void *data) { // 绘制客户端 wl_list_for_each(c, &clients, link) { need_more_frames = client_draw_frame(c) || need_more_frames; - if (!animations && !(allow_tearing && frame_allow_tearing) && - c->configure_serial && !c->isfloating && - client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) { + if (!config.animations && !grabc && c->configure_serial && + client_is_rendered_on_mon(c, m)) { + monitor_check_skip_frame_timeout(m); goto skip; } } + if (m->skiping_frame) { + monitor_stop_skip_frame_timer(m); + } + // 只有在需要帧时才构建和提交状态 - if (allow_tearing && frame_allow_tearing) { + if (config.allow_tearing && frame_allow_tearing) { apply_tear_state(m); } else { wlr_scene_output_commit(m->scene_output, NULL); @@ -4575,12 +5315,7 @@ void rendermon(struct wl_listener *listener, void *data) { skip: // 发送帧完成通知 clock_gettime(CLOCK_MONOTONIC, &now); - if (allow_tearing && frame_allow_tearing) { - wlr_scene_output_send_frame_done(m->scene_output, &now); - } else { - wlr_scene_output_send_frame_done(m->scene_output, &now); - wlr_output_state_finish(&pending); - } + wlr_scene_output_send_frame_done(m->scene_output, &now); // 如果需要更多帧,确保安排下一帧 if (need_more_frames && allow_frame_scheduling) { @@ -4637,129 +5372,71 @@ void setborder_color(Client *c) { } void exchange_two_client(Client *c1, Client *c2) { - - Monitor *tmp_mon = NULL; - uint32_t tmp_tags; - double master_inner_per = 0.0f; - double master_mfact_per = 0.0f; - double stack_inner_per = 0.0f; - float scroller_proportion = 0.0f; - float stack_proportion = 0.0f; - if (c1 == NULL || c2 == NULL || - (!exchange_cross_monitor && c1->mon != c2->mon)) { + (!config.exchange_cross_monitor && c1->mon != c2->mon)) { return; } - if (c1->mon != c2->mon && (c1->prev_in_stack || c2->prev_in_stack || - c1->next_in_stack || c2->next_in_stack)) - return; + Monitor *m1 = c1->mon; + Monitor *m2 = c2->mon; + const Layout *layout1 = m1->pertag->ltidxs[m1->pertag->curtag]; + const Layout *layout2 = m2->pertag->ltidxs[m2->pertag->curtag]; - Client *c1head = get_scroll_stack_head(c1); - Client *c2head = get_scroll_stack_head(c2); - - // 交换布局参数 - if (c1head == c2head) { - scroller_proportion = c1->scroller_proportion; - stack_proportion = c1->stack_proportion; - - c1->scroller_proportion = c2->scroller_proportion; - c1->stack_proportion = c2->stack_proportion; - c2->scroller_proportion = scroller_proportion; - c2->stack_proportion = stack_proportion; - } - - master_inner_per = c1->master_inner_per; - master_mfact_per = c1->master_mfact_per; - 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; - - // 交换栈链表连接 - Client *tmp1_next_in_stack = c1->next_in_stack; - Client *tmp1_prev_in_stack = c1->prev_in_stack; - Client *tmp2_next_in_stack = c2->next_in_stack; - Client *tmp2_prev_in_stack = c2->prev_in_stack; - - // 处理相邻节点的情况 - if (c1->next_in_stack == c2) { - c1->next_in_stack = tmp2_next_in_stack; - c2->next_in_stack = c1; - c1->prev_in_stack = c2; - c2->prev_in_stack = tmp1_prev_in_stack; - if (tmp1_prev_in_stack) - tmp1_prev_in_stack->next_in_stack = c2; - if (tmp2_next_in_stack) - tmp2_next_in_stack->prev_in_stack = c1; - } else if (c2->next_in_stack == c1) { - c2->next_in_stack = tmp1_next_in_stack; - c1->next_in_stack = c2; - c2->prev_in_stack = c1; - c1->prev_in_stack = tmp2_prev_in_stack; - if (tmp2_prev_in_stack) - tmp2_prev_in_stack->next_in_stack = c1; - if (tmp1_next_in_stack) - tmp1_next_in_stack->prev_in_stack = c2; - } else if (is_scroller_layout(c1->mon) && - (c1->prev_in_stack || c2->prev_in_stack)) { - Client *c1head = get_scroll_stack_head(c1); - Client *c2head = get_scroll_stack_head(c2); - exchange_two_client(c1head, c2head); - focusclient(c1, 0); + if (layout1->id == SCROLLER || layout2->id == SCROLLER || + layout1->id == VERTICAL_SCROLLER || layout2->id == VERTICAL_SCROLLER) { + exchange_two_scroller_clients(c1, c2); return; } - // 交换全局链表连接 - struct wl_list *tmp1_prev = c1->link.prev; - struct wl_list *tmp2_prev = c2->link.prev; - struct wl_list *tmp1_next = c1->link.next; - struct wl_list *tmp2_next = c2->link.next; - - // 处理相邻节点的情况 - if (c1->link.next == &c2->link) { - c1->link.next = c2->link.next; - c1->link.prev = &c2->link; - c2->link.next = &c1->link; - c2->link.prev = tmp1_prev; - tmp1_prev->next = &c2->link; - tmp2_next->prev = &c1->link; - } else if (c2->link.next == &c1->link) { - c2->link.next = c1->link.next; - c2->link.prev = &c1->link; - c1->link.next = &c2->link; - c1->link.prev = tmp2_prev; - tmp2_prev->next = &c1->link; - tmp1_next->prev = &c2->link; - } else { // 不为相邻节点 - c2->link.next = tmp1_next; - c2->link.prev = tmp1_prev; - c1->link.next = tmp2_next; - c1->link.prev = tmp2_prev; - - tmp1_prev->next = &c2->link; - tmp1_next->prev = &c2->link; - tmp2_prev->next = &c1->link; - tmp2_next->prev = &c1->link; + if (layout1->id == DWINDLE && layout2->id == DWINDLE) { + dwindle_swap_clients(c1, c2); + return; } - // 处理跨监视器交换 - if (exchange_cross_monitor) { - tmp_mon = c2->mon; - tmp_tags = c2->tags; - setmon(c2, c1->mon, c1->tags, false); - setmon(c1, tmp_mon, tmp_tags, false); - arrange(c1->mon, false, false); - arrange(c2->mon, false, false); - focusclient(c1, 0); - } else { - arrange(c1->mon, false, false); + client_swap_layout_properties(c1, c2); + + wl_list_swap(&c1->link, &c2->link); + + if (m1 != m2) { + client_swap_monitors_and_tags(c1, c2); } + + finish_exchange_arrange_and_focus(c1, c2, m1, m2); +} + +void set_activation_env() { + if (!getenv("DBUS_SESSION_BUS_ADDRESS")) { + wlr_log(WLR_INFO, "Not updating dbus execution environment: " + "DBUS_SESSION_BUS_ADDRESS not set"); + return; + } + + wlr_log(WLR_INFO, "Updating dbus execution environment"); + + char *env_keys = join_strings(env_vars, " "); + + // first command: dbus-update-activation-environment + const char *arg1 = env_keys; + char *cmd1 = string_printf("dbus-update-activation-environment %s", arg1); + if (!cmd1) { + wlr_log(WLR_ERROR, "Failed to allocate command string"); + goto cleanup; + } + spawn(&(Arg){.v = cmd1}); + free(cmd1); + + // second command: systemctl --user + const char *action = "import-environment"; + char *cmd2 = string_printf("systemctl --user %s %s", action, env_keys); + if (!cmd2) { + wlr_log(WLR_ERROR, "Failed to allocate command string"); + goto cleanup; + } + spawn(&(Arg){.v = cmd2}); + free(cmd2); + +cleanup: + free(env_keys); } void // 17 @@ -4805,7 +5482,7 @@ run(char *startup_cmd) { if (fd_set_nonblock(STDOUT_FILENO) < 0) close(STDOUT_FILENO); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); /* At this point the outputs are initialized, choose initial selmon * based on cursor position, and set default cursor image */ @@ -4819,6 +5496,8 @@ run(char *startup_cmd) { wlr_cursor_set_xcursor(cursor, cursor_mgr, "left_ptr"); handlecursoractivity(); + set_activation_env(); + run_exec(); run_exec_once(); @@ -4847,10 +5526,21 @@ void setcursor(struct wl_listener *listener, void *data) { * hardware cursor on the output that it's currently on and continue to * do so as the cursor moves between outputs. */ if (event->seat_client == seat->pointer_state.focused_client) { + /* Clear previous surface destroy listener if any */ + if (last_cursor.surface && + last_cursor_surface_destroy_listener.link.prev != NULL) + wl_list_remove(&last_cursor_surface_destroy_listener.link); + last_cursor.shape = 0; last_cursor.surface = event->surface; last_cursor.hotspot_x = event->hotspot_x; last_cursor.hotspot_y = event->hotspot_y; + + /* Track surface destruction to avoid dangling pointer */ + if (event->surface) + wl_signal_add(&event->surface->events.destroy, + &last_cursor_surface_destroy_listener); + if (!cursor_hidden) wlr_cursor_set_surface(cursor, event->surface, event->hotspot_x, event->hotspot_y); @@ -4873,12 +5563,12 @@ setfloating(Client *c, int32_t floating) { if (floating == 1 && c != grabc) { - if (c->isfullscreen || c->ismaximizescreen) { - c->isfullscreen = 0; // 清除窗口全屏标志 - c->ismaximizescreen = 0; - c->bw = c->isnoborder ? 0 : borderpx; + if (c->isfullscreen) { + client_pending_fullscreen_state(c, 0); + client_set_fullscreen(c, 0); } + client_pending_maximized_state(c, 0); exit_scroller_stack(c); // 重新计算居中的坐标 @@ -4890,11 +5580,13 @@ setfloating(Client *c, int32_t floating) { // restore to the memeroy geom if (c->float_geom.width > 0 && c->float_geom.height > 0) { - if (c->mon && c->float_geom.width >= c->mon->w.width - gappoh) { + if (c->mon && + c->float_geom.width >= c->mon->w.width - config.gappoh) { c->float_geom.width = c->mon->w.width * 0.9; window_size_outofrange = true; } - if (c->mon && c->float_geom.height >= c->mon->w.height - gappov) { + if (c->mon && + c->float_geom.height >= c->mon->w.height - config.gappov) { c->float_geom.height = c->mon->w.height * 0.9; window_size_outofrange = true; } @@ -4918,7 +5610,8 @@ setfloating(Client *c, int32_t floating) { // 让当前tag中的全屏窗口退出全屏参与平铺 wl_list_for_each(fc, &clients, link) if (fc && fc != c && VISIBLEON(fc, c->mon) && - c->tags & fc->tags && ISFULLSCREEN(fc)) { + c->tags & fc->tags && ISFULLSCREEN(fc) && + old_floating_state) { clear_fullscreen_flag(fc); } } @@ -4932,11 +5625,7 @@ setfloating(Client *c, int32_t floating) { layers[c->isfloating ? LyrTop : LyrTile]); } - if (!c->isfloating && old_floating_state) { - set_size_per(c->mon, c); - } - - if (!c->force_maximize) + if (!c->force_fakemaximize) client_set_maximized(c, false); if (!c->isfloating || c->force_tiled_state) { @@ -4947,40 +5636,40 @@ setfloating(Client *c, int32_t floating) { } arrange(c->mon, false, false); + + if (!c->isfloating) { + c->old_master_inner_per = c->master_inner_per; + c->old_stack_inner_per = c->stack_inner_per; + } + setborder_color(c); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void reset_maximizescreen_size(Client *c) { - c->geom.x = c->mon->w.x + gappoh; - c->geom.y = c->mon->w.y + gappov; - c->geom.width = c->mon->w.width - 2 * gappoh; - c->geom.height = c->mon->w.height - 2 * gappov; + c->geom.x = c->mon->w.x + config.gappoh; + c->geom.y = c->mon->w.y + config.gappov; + c->geom.width = c->mon->w.width - 2 * config.gappoh; + c->geom.height = c->mon->w.height - 2 * config.gappov; resize(c, c->geom, 0); } void exit_scroller_stack(Client *c) { - // If c is already in a stack, remove it. - if (c->prev_in_stack) { - c->prev_in_stack->next_in_stack = c->next_in_stack; - } + if (!c || !c->mon) + return; - if (!c->prev_in_stack && c->next_in_stack) { - c->next_in_stack->scroller_proportion = c->scroller_proportion; - wl_list_remove(&c->next_in_stack->link); - wl_list_insert(&c->link, &c->next_in_stack->link); + uint32_t tag = c->mon->pertag->curtag; + struct TagScrollerState *st = c->mon->pertag->scroller_state[tag]; + if (st) { + struct ScrollerStackNode *n = find_scroller_node(st, c); + if (n) { + scroller_node_remove(st, n); + return; /* 节点已移除,客户端指针已在函数内清空 */ + } } - - if (c->next_in_stack) { - c->next_in_stack->prev_in_stack = c->prev_in_stack; - } - - c->prev_in_stack = NULL; - c->next_in_stack = NULL; - c->stack_proportion = 0.0f; } -void setmaximizescreen(Client *c, int32_t maximizescreen) { +void setmaximizescreen(Client *c, int32_t maximizescreen, bool rearrange) { struct wlr_box maximizescreen_box; if (!c || !c->mon || !client_surface(c)->mapped || c->iskilling) return; @@ -4988,47 +5677,41 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { if (c->mon->isoverview) return; - int32_t old_maximizescreen_state = c->ismaximizescreen; - c->ismaximizescreen = maximizescreen; + client_pending_maximized_state(c, maximizescreen); if (maximizescreen) { - if (c->isfullscreen) - setfullscreen(c, 0); + if (c->isfullscreen) { + client_pending_fullscreen_state(c, 0); + client_set_fullscreen(c, 0); + } exit_scroller_stack(c); - if (c->isfloating) - c->float_geom = c->geom; - - maximizescreen_box.x = c->mon->w.x + gappoh; - maximizescreen_box.y = c->mon->w.y + gappov; - maximizescreen_box.width = c->mon->w.width - 2 * gappoh; - maximizescreen_box.height = c->mon->w.height - 2 * gappov; - wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层 + maximizescreen_box.x = c->mon->w.x + config.gappoh; + maximizescreen_box.y = c->mon->w.y + config.gappov; + maximizescreen_box.width = c->mon->w.width - 2 * config.gappoh; + maximizescreen_box.height = c->mon->w.height - 2 * config.gappov; + wlr_scene_node_raise_to_top(&c->scene->node); if (!is_scroller_layout(c->mon) || c->isfloating) resize(c, maximizescreen_box, 0); - c->ismaximizescreen = 1; } else { - c->bw = c->isnoborder ? 0 : borderpx; - c->ismaximizescreen = 0; + c->bw = c->isnoborder ? 0 : config.borderpx; if (c->isfloating) setfloating(c, 1); } wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrTop : LyrTile]); - if (!c->ismaximizescreen && old_maximizescreen_state) { - set_size_per(c->mon, c); - } - if (!c->force_maximize && !c->ismaximizescreen) { + if (!c->force_fakemaximize && !c->ismaximizescreen) { client_set_maximized(c, false); - } else if (!c->force_maximize && c->ismaximizescreen) { + } else if (!c->force_fakemaximize && c->ismaximizescreen) { client_set_maximized(c, true); } - arrange(c->mon, false, false); + if (rearrange) + arrange(c->mon, false, false); } void setfakefullscreen(Client *c, int32_t fakefullscreen) { @@ -5037,12 +5720,13 @@ void setfakefullscreen(Client *c, int32_t fakefullscreen) { return; if (c->isfullscreen) - setfullscreen(c, 0); + setfullscreen(c, 0, true); client_set_fullscreen(c, fakefullscreen); } -void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自带全屏 +void setfullscreen(Client *c, int32_t fullscreen, + bool rearrange) // 用自定义全屏代理自带全屏 { if (!c || !c->mon || !client_surface(c)->mapped || c->iskilling) @@ -5051,30 +5735,29 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 if (c->mon->isoverview) return; - int32_t old_fullscreen_state = c->isfullscreen; c->isfullscreen = fullscreen; client_set_fullscreen(c, fullscreen); + client_pending_fullscreen_state(c, fullscreen); if (fullscreen) { - if (c->ismaximizescreen) - setmaximizescreen(c, 0); + + if (c->ismaximizescreen && !c->force_fakemaximize) { + client_set_maximized(c, false); + } + + client_pending_maximized_state(c, 0); exit_scroller_stack(c); - - if (c->isfloating) - c->float_geom = c->geom; - c->isfakefullscreen = 0; c->bw = 0; wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层 if (!is_scroller_layout(c->mon) || c->isfloating) resize(c, c->mon->m, 1); - c->isfullscreen = 1; + } else { - c->bw = c->isnoborder ? 0 : borderpx; - c->isfullscreen = 0; + c->bw = c->isnoborder ? 0 : config.borderpx; if (c->isfloating) setfloating(c, 1); } @@ -5089,18 +5772,15 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 layers[fullscreen || c->isfloating ? LyrTop : LyrTile]); } - if (!c->isfullscreen && old_fullscreen_state) { - set_size_per(c->mon, c); - } - - arrange(c->mon, false, false); + if (rearrange) + arrange(c->mon, false, false); } void setgaps(int32_t oh, int32_t ov, int32_t ih, int32_t iv) { - selmon->gappoh = MAX(oh, 0); - selmon->gappov = MAX(ov, 0); - selmon->gappih = MAX(ih, 0); - selmon->gappiv = MAX(iv, 0); + selmon->gappoh = MANGO_MAX(oh, 0); + selmon->gappov = MANGO_MAX(ov, 0); + selmon->gappih = MANGO_MAX(ih, 0); + selmon->gappiv = MANGO_MAX(iv, 0); arrange(selmon, false, false); } @@ -5132,9 +5812,8 @@ void reset_keyboard_layout(void) { return; } - // 现在安全地创建真正的keymap struct xkb_keymap *new_keymap = xkb_keymap_new_from_names( - context, &xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS); + context, &config.xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!new_keymap) { // 理论上这里不应该失败,因为前面已经验证过了 wlr_log(WLR_ERROR, @@ -5218,13 +5897,13 @@ void setmon(Client *c, Monitor *m, uint32_t newtags, bool focus) { arrange(oldmon, false, false); if (m) { /* Make sure window actually overlaps with the monitor */ - reset_foreign_tolevel(c); + reset_foreign_tolevel(c, oldmon, m); resize(c, c->geom, 0); - c->tags = - newtags ? newtags - : m->tagset[m->seltags]; /* assign tags of target monitor */ + client_reset_mon_tags(c, m, newtags); + check_match_tag_floating_rule(c, m); setfloating(c, c->isfloating); - setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfullscreen(c, c->isfullscreen, + true); /* This will call arrange(c->mon) */ } if (focus && !client_is_x11_popup(c)) { @@ -5264,10 +5943,11 @@ void show_hide_client(Client *c) { c->tags = c->oldtags; arrange(c->mon, false, false); } - c->isminimized = 0; - wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, false); + client_pending_minimized_state(c, 0); focusclient(c, 1); - wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, true); + + if (c->foreign_toplevel) + wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, true); } void create_output(struct wlr_backend *backend, void *data) { @@ -5294,37 +5974,95 @@ void create_output(struct wlr_backend *backend, void *data) { // 修改信号处理函数,接收掩码参数 void handle_print_status(struct wl_listener *listener, void *data) { + enum ipc_watch_type type = *(enum ipc_watch_type *)data; + + if (type & IPC_WATCH_KEYMODE) { + ipc_notify_keymode(); + } + if (type & IPC_WATCH_KB_LAYOUT) { + ipc_notify_kb_layout(); + } + if (type & IPC_WATCH_FOCUSING_CLIENT) { + ipc_notify_focusing_client(); + } + if (type & IPC_WATCH_ALL_TAGS) { + ipc_notify_all_tags(); + } + if (type & IPC_WATCH_ALL_CLIENTS) { + ipc_notify_all_clients(); + } + if (type & + (IPC_WATCH_ALL_MONITORS | IPC_WATCH_KEYMODE | IPC_WATCH_KB_LAYOUT | + IPC_WATCH_FOCUSING_CLIENT | IPC_WATCH_TAGS)) { + ipc_notify_all_monitors(); + } + + if (type & IPC_WATCH_CLIENT) { + Client *c = NULL; + wl_list_for_each(c, &clients, link) { + if (c->iskilling) + continue; + ipc_notify_client(c); + } + } + Monitor *m = NULL; wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) { continue; } - dwl_ext_workspace_printstatus(m); + if (type & IPC_WATCH_MONITOR) { + ipc_notify_monitor(m); + } + if (type & IPC_WATCH_TAGS) { + ipc_notify_tags(m); + } + + if (type & IPC_WATCH_LAST_OPEN_SURFACE) { + ipc_notify_last_surface_ws_name(m); + } + + dwl_ext_workspace_printstatus(m); dwl_ipc_output_printstatus(m); } } void setup(void) { - setenv("XCURSOR_SIZE", "24", 1); setenv("XDG_CURRENT_DESKTOP", "mango", 1); + setenv("_JAVA_AWT_WM_NONREPARENTING", "1", 1); + parse_config(); + if (cli_debug_log) { + config.log_level = WLR_DEBUG; + } init_baked_points(); - int32_t drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + int32_t drm_fd, i; + int32_t sig[] = {SIGCHLD, SIGINT, + SIGTERM}; // 不设置SIGPIPE,因为ipc发送失败不应该影响主程序 struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; sigemptyset(&sa.sa_mask); for (i = 0; i < LENGTH(sig); i++) sigaction(sig[i], &sa, NULL); - wlr_log_init(log_level, NULL); + // 单独为 SIGPIPE 设置忽略 + struct sigaction sa_pipe = {.sa_flags = 0, .sa_handler = SIG_IGN}; + sigemptyset(&sa_pipe.sa_mask); + sigaction(SIGPIPE, &sa_pipe, NULL); + + wlr_log_init(config.log_level, NULL); /* The Wayland display is managed by libwayland. It handles accepting * clients from the Unix socket, manging Wayland globals, and so on. */ dpy = wl_display_create(); event_loop = wl_display_get_event_loop(dpy); + + ipc_init(event_loop); + + tablet_mgr = wlr_tablet_v2_create(dpy); /* The backend is a wlroots feature which abstracts the underlying input * and output hardware. The autocreate option will choose the most * suitable backend based on the current environment, such as opening an @@ -5345,7 +6083,7 @@ void setup(void) { /* Initialize the scene graph used to lay out windows */ scene = wlr_scene_create(); - root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); + root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, config.rootcolor); for (i = 0; i < NUM_LAYERS; i++) layers[i] = wlr_scene_tree_create(&scene->tree); drag_icon = wlr_scene_tree_create(&scene->tree); @@ -5370,7 +6108,7 @@ void setup(void) { scene, wlr_linux_dmabuf_v1_create_with_renderer(dpy, 4, drw)); } - if (syncobj_enable && (drm_fd = wlr_renderer_get_drm_fd(drw)) >= 0 && + if (config.syncobj_enable && (drm_fd = wlr_renderer_get_drm_fd(drw)) >= 0 && drw->features.timeline && backend->features.timeline) wlr_linux_drm_syncobj_manager_v1_create(dpy, 1, drm_fd); @@ -5460,6 +6198,9 @@ void setup(void) { idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); wl_signal_add(&idle_inhibit_mgr->events.new_inhibitor, &new_idle_inhibitor); + keep_idle_inhibit_source = wl_event_loop_add_timer( + wl_display_get_event_loop(dpy), keep_idle_inhibit, NULL); + layer_shell = wlr_layer_shell_v1_create(dpy, 4); wl_signal_add(&layer_shell->events.new_surface, &new_layer_surface); @@ -5501,9 +6242,11 @@ void setup(void) { * cursor images are available at all scale factors on the screen * (necessary for HiDPI support). Scaled cursors will be loaded with * each output. */ - // cursor_mgr = wlr_xcursor_manager_create(cursor_theme, 24); - cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); + set_xcursor_env(); + + cursor_mgr = + wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); /* * wlr_cursor *only* displays an image on screen. It does not move * around when the pointer moves. However, we can attach input devices @@ -5522,13 +6265,18 @@ void setup(void) { wl_signal_add(&cursor->events.button, &cursor_button); wl_signal_add(&cursor->events.axis, &cursor_axis); wl_signal_add(&cursor->events.frame, &cursor_frame); + wl_signal_add(&cursor->events.tablet_tool_proximity, + &tablet_tool_proximity); + wl_signal_add(&cursor->events.tablet_tool_axis, &tablet_tool_axis); + wl_signal_add(&cursor->events.tablet_tool_button, &tablet_tool_button); + wl_signal_add(&cursor->events.tablet_tool_tip, &tablet_tool_tip); // 这两句代码会造成obs窗口里的鼠标光标消失,不知道注释有什么影响 cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); - hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), - hidecursor, cursor); + hide_cursor_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + hidecursor, cursor); /* * Configures a seat, which is a single "seat" at which a user sits and * operates the computer. This conceptually includes up to one keyboard, @@ -5536,6 +6284,8 @@ void setup(void) { * to let us know when new input devices are available on the backend. */ wl_list_init(&inputdevices); + wl_list_init(&tablets); + wl_list_init(&tablet_pads); wl_list_init(&keyboard_shortcut_inhibitors); wl_signal_add(&backend->events.new_input, &new_input_device); virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); @@ -5556,6 +6306,8 @@ void setup(void) { LISTEN_STATIC(&cursor->events.hold_end, hold_end); seat = wlr_seat_create(dpy, "seat0"); + + wl_list_init(&last_cursor_surface_destroy_listener.link); wl_signal_add(&seat->events.request_set_cursor, &request_cursor); wl_signal_add(&seat->events.request_set_selection, &request_set_sel); wl_signal_add(&seat->events.request_set_primary_selection, @@ -5574,10 +6326,10 @@ void setup(void) { wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); wl_signal_add(&output_mgr->events.test, &output_mgr_test); - // blur - wlr_scene_set_blur_data(scene, blur_params.num_passes, blur_params.radius, - blur_params.noise, blur_params.brightness, - blur_params.contrast, blur_params.saturation); + wlr_scene_set_blur_data( + scene, config.blur_params.num_passes, config.blur_params.radius, + config.blur_params.noise, config.blur_params.brightness, + config.blur_params.contrast, config.blur_params.saturation); /* create text_input-, and input_method-protocol relevant globals */ input_method_manager = wlr_input_method_manager_v2_create(dpy); @@ -5610,7 +6362,8 @@ void setup(void) { * Initialise the XWayland X server. * It will be started when the first X client is started. */ - xwayland = wlr_xwayland_create(dpy, compositor, !xwayland_persistence); + xwayland = + wlr_xwayland_create(dpy, compositor, !config.xwayland_persistence); if (xwayland) { wl_signal_add(&xwayland->events.ready, &xwayland_ready); wl_signal_add(&xwayland->events.new_surface, &new_xwayland_surface); @@ -5638,7 +6391,6 @@ void tag_client(const Arg *arg, Client *target_client) { Client *fc = NULL; if (target_client && arg->ui & TAGMASK) { - exit_scroller_stack(target_client); target_client->tags = arg->ui & TAGMASK; target_client->istagswitching = 1; @@ -5655,11 +6407,9 @@ void tag_client(const Arg *arg, Client *target_client) { } focusclient(target_client, 1); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } -void overview(Monitor *m) { grid(m); } - // 目标窗口有其他窗口和它同个tag就返回0 uint32_t want_restore_fullscreen(Client *target_client) { Client *c = NULL; @@ -5677,6 +6427,36 @@ uint32_t want_restore_fullscreen(Client *target_client) { return 1; } +void overview_backup_surface(Client *c) { + + if (c->overview_scene_surface) { + return; + } + + struct wlr_box geometry; + client_get_geometry(c, &geometry); + struct wlr_box clip_box = (struct wlr_box){ + .x = geometry.x, + .y = geometry.y, + .width = c->overview_backup_geom.width - 2 * config.borderpx, + .height = c->overview_backup_geom.height - 2 * config.borderpx, + }; + + if (client_is_x11(c)) { + clip_box.x = 0; + clip_box.y = 0; + } + + c->overview_scene_surface = c->scene_surface; + wlr_scene_node_set_enabled(&c->scene_surface->node, true); + wlr_scene_node_set_position(&c->scene_surface->node, 0, 0); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); + c->scene_surface = + wlr_scene_tree_snapshot(&c->scene_surface->node, c->scene); + wlr_scene_node_set_enabled(&c->overview_scene_surface->node, false); + wlr_scene_node_set_enabled(&c->scene_surface->node, true); +} + // 普通视图切换到overview时保存窗口的旧状态 void overview_backup(Client *c) { c->overview_isfloatingbak = c->isfloating; @@ -5691,11 +6471,16 @@ void overview_backup(Client *c) { if (c->isfloating) { c->isfloating = 0; } - if (c->isfullscreen || c->ismaximizescreen) { - c->isfullscreen = 0; // 清除窗口全屏标志 - c->ismaximizescreen = 0; + + if (config.ov_no_resize) { + overview_backup_surface(c); } - c->bw = c->isnoborder ? 0 : borderpx; + + if (c->isfullscreen || c->ismaximizescreen) { + client_pending_fullscreen_state(c, 0); // 清除窗口全屏标志 + client_pending_maximized_state(c, 0); + } + c->bw = c->isnoborder ? 0 : config.borderpx; client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); @@ -5714,18 +6499,24 @@ void overview_restore(Client *c, const Arg *arg) { c->animation.tagining = false; c->is_restoring_from_ov = (arg->ui & c->tags & TAGMASK) == 0 ? true : false; + if (c->overview_scene_surface) { + wlr_scene_node_destroy(&c->scene_surface->node); + c->scene_surface = c->overview_scene_surface; + c->overview_scene_surface = NULL; + } + if (c->isfloating) { // XRaiseWindow(dpy, c->win); // 提升悬浮窗口到顶层 resize(c, c->overview_backup_geom, 0); } else if (c->isfullscreen || c->ismaximizescreen) { if (want_restore_fullscreen(c) && c->ismaximizescreen) { - setmaximizescreen(c, 1); + setmaximizescreen(c, 1, false); } else if (want_restore_fullscreen(c) && c->isfullscreen) { - setfullscreen(c, 1); + setfullscreen(c, 1, false); } else { - c->isfullscreen = 0; - c->ismaximizescreen = 0; - setfullscreen(c, false); + client_pending_fullscreen_state(c, 0); + client_pending_maximized_state(c, 0); + setfullscreen(c, false, false); } } else { if (c->is_restoring_from_ov) { @@ -5736,7 +6527,7 @@ void overview_restore(Client *c, const Arg *arg) { if (c->bw == 0 && !c->isfullscreen) { // 如果是在ov模式中创建的窗口,没有bw记录 - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; } if (c->isfloating && !c->force_tiled_state) { @@ -5745,7 +6536,8 @@ void overview_restore(Client *c, const Arg *arg) { } void handlecursoractivity(void) { - wl_event_source_timer_update(hide_source, cursor_hide_timeout * 1000); + wl_event_source_timer_update(hide_cursor_source, + config.cursor_hide_timeout * 1000); if (!cursor_hidden) return; @@ -5755,7 +6547,7 @@ void handlecursoractivity(void) { if (last_cursor.shape) wlr_cursor_set_xcursor(cursor, cursor_mgr, wlr_cursor_shape_v1_name(last_cursor.shape)); - else + else if (last_cursor.surface) wlr_cursor_set_surface(cursor, last_cursor.surface, last_cursor.hotspot_x, last_cursor.hotspot_y); } @@ -5766,6 +6558,36 @@ int32_t hidecursor(void *data) { return 1; } +void check_keep_idle_inhibit(Client *c) { + if (c && c->idleinhibit_when_focus && keep_idle_inhibit_source) { + wl_event_source_timer_update(keep_idle_inhibit_source, 1000); + } +} + +int32_t keep_idle_inhibit(void *data) { + + if (!idle_inhibit_mgr) { + wl_event_source_timer_update(keep_idle_inhibit_source, 0); + return 1; + } + + if (session && !session->active) { + wl_event_source_timer_update(keep_idle_inhibit_source, 0); + return 1; + } + + if (!selmon || !selmon->sel || !selmon->sel->idleinhibit_when_focus) { + wl_event_source_timer_update(keep_idle_inhibit_source, 0); + return 1; + } + + if (seat && idle_notifier) { + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + wl_event_source_timer_update(keep_idle_inhibit_source, 1000); + } + return 1; +} + void unlocksession(struct wl_listener *listener, void *data) { SessionLock *lock = wl_container_of(listener, lock, unlock); destroylock(lock, 1); @@ -5780,17 +6602,20 @@ void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { init_fadeout_layers(l); wlr_scene_node_set_enabled(&l->scene->node, false); + if (l == exclusive_focus) exclusive_focus = NULL; + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) arrangelayers(l->mon); - if (l->layer_surface->surface == seat->keyboard_state.focused_surface) - focusclient(focustop(selmon), 1); + + reset_exclusive_layers_focus(l->mon); + motionnotify(0, NULL, 0, 0, 0, 0); - l->being_unmapped = false; layer_flush_blur_background(l); wlr_scene_node_destroy(&l->shadow->node); l->shadow = NULL; + l->being_unmapped = false; } void unmapnotify(struct wl_listener *listener, void *data) { @@ -5799,11 +6624,17 @@ void unmapnotify(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, unmap); Monitor *m = NULL; Client *nextfocus = NULL; - Client *next_in_stack = c->next_in_stack; - Client *prev_in_stack = c->prev_in_stack; c->iskilling = 1; + struct ScrollerStackNode *target_node = + c->mon ? find_scroller_node( + c->mon->pertag->scroller_state[c->mon->pertag->curtag], c) + : NULL; + struct ScrollerStackNode *prev_node = + target_node ? target_node->prev_in_stack : NULL; + struct ScrollerStackNode *next_node = + target_node ? target_node->next_in_stack : NULL; - if (animations && !c->is_clip_to_hide && !c->isminimized && + if (config.animations && !c->is_clip_to_hide && !c->isminimized && (!c->mon || VISIBLEON(c, c->mon))) init_fadeout_client(c); @@ -5818,7 +6649,8 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->swallowedby->mon = c->mon; swallow(c->swallowedby, c); } else { - exit_scroller_stack(c); + scroller_remove_client(c); + dwindle_remove_client(c); } if (c == grabc) { @@ -5826,6 +6658,10 @@ void unmapnotify(struct wl_listener *listener, void *data) { grabc = NULL; } + if (c == dropc) { + dropc = NULL; + } + wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) { continue; @@ -5839,10 +6675,10 @@ void unmapnotify(struct wl_listener *listener, void *data) { } if (c->mon && c->mon == selmon) { - if (next_in_stack && !c->swallowedby) { - nextfocus = next_in_stack; - } else if (prev_in_stack && !c->swallowedby) { - nextfocus = prev_in_stack; + if (next_node && !c->swallowedby) { + nextfocus = next_node->client; + } else if (prev_node && !c->swallowedby) { + nextfocus = prev_node->client; } else { nextfocus = focustop(selmon); } @@ -5881,8 +6717,8 @@ void unmapnotify(struct wl_listener *listener, void *data) { } if (c->swallowedby) { - setmaximizescreen(c->swallowedby, c->ismaximizescreen); - setfullscreen(c->swallowedby, c->isfullscreen); + setmaximizescreen(c->swallowedby, c->ismaximizescreen, true); + setfullscreen(c->swallowedby, c->isfullscreen, true); c->swallowedby->swallowing = NULL; c->swallowedby = NULL; } @@ -5893,13 +6729,20 @@ void unmapnotify(struct wl_listener *listener, void *data) { } c->stack_proportion = 0.0f; - c->next_in_stack = NULL; - c->prev_in_stack = NULL; + + if (c->jump_label_node) { + mango_jump_label_node_destroy(c->jump_label_node); + c->jump_label_node = NULL; + } + if (c->tab_bar_node) { + mango_tab_bar_node_destroy(c->tab_bar_node); + c->tab_bar_node = NULL; + } wlr_scene_node_destroy(&c->image_capture_scene_surface->buffer->node); wlr_scene_node_destroy(&c->image_capture_scene->tree.node); wlr_scene_node_destroy(&c->scene->node); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); motionnotify(0, NULL, 0, 0, 0, 0); } @@ -5911,7 +6754,7 @@ void updatemons(struct wl_listener *listener, void *data) { * positions, focus, and the stored configuration in wlroots' * output-manager implementation. */ - struct wlr_output_configuration_v1 *config = + struct wlr_output_configuration_v1 *output_config = wlr_output_configuration_v1_create(); Client *c = NULL; struct wlr_output_configuration_head_v1 *config_head; @@ -5922,8 +6765,8 @@ void updatemons(struct wl_listener *listener, void *data) { wl_list_for_each(m, &mons, link) { if (m->wlr_output->enabled || m->asleep) continue; - config_head = - wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head = wlr_output_configuration_head_v1_create(output_config, + m->wlr_output); config_head->state.enabled = 0; /* Remove this output from the layout to avoid cursor enter inside * it */ @@ -5951,8 +6794,8 @@ void updatemons(struct wl_listener *listener, void *data) { wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) continue; - config_head = - wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head = wlr_output_configuration_head_v1_create(output_config, + m->wlr_output); oldx = m->m.x; oldy = m->m.y; @@ -5969,7 +6812,8 @@ void updatemons(struct wl_listener *listener, void *data) { c->geom.x += mon_pos_offsetx; c->geom.y += mon_pos_offsety; c->float_geom = c->geom; - resize(c, c->geom, 1); + if (VISIBLEON(c, m)) + resize(c, c->geom, 1); } // restore window to old monitor @@ -5985,7 +6829,7 @@ void updatemons(struct wl_listener *listener, void *data) { */ wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); - if (blur && m->blur) { + if (config.blur && m->blur) { wlr_scene_node_set_position(&m->blur->node, m->m.x, m->m.y); wlr_scene_optimized_blur_set_size(m->blur, m->m.width, m->m.height); } @@ -6016,7 +6860,11 @@ void updatemons(struct wl_listener *listener, void *data) { wl_list_for_each(c, &clients, link) { if (!c->mon && client_surface(c)->mapped) { c->mon = selmon; - reset_foreign_tolevel(c); + reset_foreign_tolevel(c, NULL, c->mon); + } + if (c->tags == 0 && !c->is_in_scratchpad) { + c->tags = selmon->tagset[selmon->seltags]; + set_size_per(selmon, c); } } focusclient(focustop(selmon), 1); @@ -6034,7 +6882,7 @@ void updatemons(struct wl_listener *listener, void *data) { * it's at the wrong position after all. */ wlr_cursor_move(cursor, NULL, 0, 0); - wlr_output_manager_v1_set_configuration(output_mgr, config); + wlr_output_manager_v1_set_configuration(output_mgr, output_config); } void updatetitle(struct wl_listener *listener, void *data) { @@ -6045,6 +6893,7 @@ void updatetitle(struct wl_listener *listener, void *data) { const char *title; title = client_get_title(c); + mango_tab_bar_node_update(c->tab_bar_node, title, 1.0); if (title && c->foreign_toplevel) wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); if (title && c->ext_foreign_toplevel) { @@ -6056,7 +6905,7 @@ void updatetitle(struct wl_listener *listener, void *data) { }); } if (c == focustop(c->mon)) - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void // 17 fix to 0.5 @@ -6068,7 +6917,7 @@ urgent(struct wl_listener *listener, void *data) { if (!c || !c->foreign_toplevel) return; - if (focus_on_activate && !c->istagsilent && c != selmon->sel) { + if (config.focus_on_activate && !c->istagsilent && c != selmon->sel) { if (!(c->mon == selmon && c->tags & c->mon->tagset[c->mon->seltags])) view_in_mon(&(Arg){.ui = c->tags}, true, c->mon, true); focusclient(c, 1); @@ -6076,7 +6925,7 @@ urgent(struct wl_listener *listener, void *data) { c->isurgent = 1; if (client_surface(c)->mapped) setborder_color(c); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } } @@ -6093,10 +6942,14 @@ void view_in_mon(const Arg *arg, bool want_animation, Monitor *m, } if (arg->ui == UINT32_MAX) { - m->pertag->prevtag = get_tags_first_tag_num(m->tagset[m->seltags]); - m->seltags ^= 1; /* toggle sel tagset */ - m->pertag->curtag = get_tags_first_tag_num(m->tagset[m->seltags]); - goto toggleseltags; + if (m->tagset[0] != m->tagset[1]) { + m->pertag->prevtag = get_tags_first_tag_num(m->tagset[m->seltags]); + m->seltags ^= 1; /* toggle sel tagset */ + m->pertag->curtag = get_tags_first_tag_num(m->tagset[m->seltags]); + goto toggleseltags; + } else { + return; + } } if ((m->tagset[m->seltags] & arg->ui & TAGMASK) != 0) { @@ -6131,7 +6984,7 @@ toggleseltags: if (changefocus) focusclient(focustop(m), 1); arrange(m, want_animation, true); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void view(const Arg *arg, bool want_animation) { @@ -6167,7 +7020,7 @@ void handle_keyboard_shortcuts_inhibit_new_inhibitor( struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data; - if (allow_shortcuts_inhibit == SHORTCUTS_INHIBIT_DISABLE) { + if (config.allow_shortcuts_inhibit == SHORTCUTS_INHIBIT_DISABLE) { return; } @@ -6243,16 +7096,17 @@ void virtualpointer(struct wl_listener *listener, void *data) { } #ifdef XWAYLAND -void fix_xwayland_unmanaged_coordinate(Client *c) { +void fix_xwayland_coordinate(struct wlr_box *geom) { if (!selmon) return; // 1. 如果窗口已经在当前活动显示器内,直接返回 - if (c->geom.x >= selmon->m.x && c->geom.x < selmon->m.x + selmon->m.width && - c->geom.y >= selmon->m.y && c->geom.y < selmon->m.y + selmon->m.height) + if (geom->x >= selmon->m.x && geom->x <= selmon->m.x + selmon->m.width && + geom->y >= selmon->m.y && geom->y <= selmon->m.y + selmon->m.height) return; - c->geom = setclient_coordinate_center(c, selmon, c->geom, 0, 0); + geom->x = selmon->m.x + (selmon->m.width - geom->width) / 2; + geom->y = selmon->m.y + (selmon->m.height - geom->height) / 2; } int32_t synckeymap(void *data) { @@ -6274,20 +7128,18 @@ void activatex11(struct wl_listener *listener, void *data) { return; if (c->isminimized) { - c->isminimized = 0; + client_pending_minimized_state(c, 0); c->tags = c->mini_restore_tag; c->is_scratchpad_show = 0; c->is_in_scratchpad = 0; c->isnamedscratchpad = 0; - wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, - false); setborder_color(c); if (VISIBLEON(c, c->mon)) { need_arrange = true; } } - if (focus_on_activate && !c->istagsilent && c != selmon->sel) { + if (config.focus_on_activate && !c->istagsilent && c != selmon->sel) { if (!(c->mon == selmon && c->tags & c->mon->tagset[c->mon->seltags])) view_in_mon(&(Arg){.ui = c->tags}, true, c->mon, true); wlr_xwayland_surface_activate(c->surface.xwayland, 1); @@ -6303,30 +7155,47 @@ void activatex11(struct wl_listener *listener, void *data) { arrange(c->mon, false, false); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void configurex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, configure); struct wlr_xwayland_surface_configure_event *event = data; + struct wlr_box new_geo; + new_geo.x = event->x; + new_geo.y = event->y; + new_geo.width = event->width; + new_geo.height = event->height; + fix_xwayland_coordinate(&new_geo); + if (!client_surface(c) || !client_surface(c)->mapped) { - wlr_xwayland_surface_configure(c->surface.xwayland, event->x, event->y, - event->width, event->height); + + wlr_xwayland_surface_configure(c->surface.xwayland, new_geo.x, + new_geo.y, new_geo.width, + new_geo.height); return; } + if (client_is_unmanaged(c)) { - wlr_scene_node_set_position(&c->scene->node, event->x, event->y); - wlr_xwayland_surface_configure(c->surface.xwayland, event->x, event->y, - event->width, event->height); + wlr_scene_node_set_position(&c->scene->node, new_geo.x, new_geo.y); + wlr_xwayland_surface_configure(c->surface.xwayland, new_geo.x, + new_geo.y, new_geo.width, + new_geo.height); return; } - if ((c->isfloating && c != grabc) || - !c->mon->pertag->ltidxs[c->mon->pertag->curtag]->arrange) { + + if (c->isfloating && c != grabc) { + new_geo.x = new_geo.x - c->bw; + new_geo.y = new_geo.y - c->bw; + new_geo.width = new_geo.width + c->bw * 2; + new_geo.height = new_geo.height + c->bw * 2; + fix_xwayland_coordinate(&new_geo); + resize(c, - (struct wlr_box){.x = event->x - c->bw, - .y = event->y - c->bw, - .width = event->width + c->bw * 2, - .height = event->height + c->bw * 2}, + (struct wlr_box){.x = new_geo.x, + .y = new_geo.y, + .width = new_geo.width, + .height = new_geo.height}, 0); } else { arrange(c->mon, false, false); @@ -6355,17 +7224,34 @@ void createnotifyx11(struct wl_listener *listener, void *data) { LISTEN(&xsurface->events.request_minimize, &c->minimize, minimizenotify); } +void commitx11(struct wl_listener *listener, void *data) { + Client *c = wl_container_of(listener, c, commmitx11); + struct wlr_surface_state *state = &c->surface.xwayland->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) { + c->configure_serial = 0; + } +} + void associatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, associate); LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); + LISTEN(&client_surface(c)->events.commit, &c->commmitx11, commitx11); } void dissociatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, dissociate); wl_list_remove(&c->map.link); wl_list_remove(&c->unmap.link); + wl_list_remove(&c->commmitx11.link); } void sethints(struct wl_listener *listener, void *data) { @@ -6375,7 +7261,7 @@ void sethints(struct wl_listener *listener, void *data) { return; c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); if (c->isurgent && surface && surface->mapped) setborder_color(c); @@ -6418,7 +7304,7 @@ int32_t main(int32_t argc, char *argv[]) { if (c == 's') { startup_cmd = optarg; } else if (c == 'd') { - log_level = WLR_DEBUG; + cli_debug_log = true; } else if (c == 'v') { printf("mango " VERSION "\n"); return EXIT_SUCCESS;