1
0
Fork 0
forked from wry/wry

all: split reusable components into workspace crates

This commit is contained in:
kossLAN 2026-05-29 09:14:53 -04:00
parent 2a079ed800
commit 657e7ce2f7
No known key found for this signature in database
225 changed files with 7422 additions and 17602 deletions

95
Cargo.lock generated
View file

@ -639,6 +639,13 @@ dependencies = [
"libloading",
]
[[package]]
name = "jay-cmm"
version = "0.1.0"
dependencies = [
"jay-utils",
]
[[package]]
name = "jay-compositor"
version = "1.12.0"
@ -665,8 +672,17 @@ dependencies = [
"isnt 0.2.0",
"jay-algorithms",
"jay-ash",
"jay-cmm",
"jay-config",
"jay-criteria",
"jay-edid",
"jay-formats",
"jay-geometry",
"jay-layout-animation",
"jay-time",
"jay-toml-config",
"jay-units",
"jay-utils",
"kbvm",
"libloading",
"linearize",
@ -711,6 +727,60 @@ dependencies = [
"uapi",
]
[[package]]
name = "jay-config-schema"
version = "0.1.0"
[[package]]
name = "jay-criteria"
version = "0.1.0"
dependencies = [
"ahash",
"jay-utils",
"linearize",
"regex",
]
[[package]]
name = "jay-edid"
version = "0.1.0"
dependencies = [
"bstr",
"thiserror",
]
[[package]]
name = "jay-formats"
version = "0.1.0"
dependencies = [
"ahash",
"clap",
"jay-ash",
"jay-config",
]
[[package]]
name = "jay-geometry"
version = "0.1.0"
dependencies = [
"jay-algorithms",
"smallvec",
]
[[package]]
name = "jay-layout-animation"
version = "0.1.0"
dependencies = [
"jay-geometry",
]
[[package]]
name = "jay-time"
version = "0.1.0"
dependencies = [
"uapi",
]
[[package]]
name = "jay-toml-config"
version = "0.12.0"
@ -720,6 +790,7 @@ dependencies = [
"error_reporter",
"indexmap",
"jay-config",
"jay-config-schema",
"kbvm",
"log",
"phf",
@ -731,6 +802,30 @@ dependencies = [
"walkdir",
]
[[package]]
name = "jay-units"
version = "0.1.0"
[[package]]
name = "jay-utils"
version = "0.1.0"
dependencies = [
"ahash",
"arrayvec",
"bstr",
"cfg-if",
"isnt 0.2.0",
"jay-config",
"linearize",
"log",
"parking_lot",
"rand 0.10.0",
"serde",
"smallvec",
"thiserror",
"uapi",
]
[[package]]
name = "js-sys"
version = "0.3.91"

View file

@ -13,7 +13,25 @@ name = "jay"
path = "src/main.rs"
[workspace]
members = ["jay-config", "toml-config", "algorithms", "toml-spec", "wire-to-xml", "xml-to-wire"]
resolver = "3"
members = [
"jay-config",
"jay-config-schema",
"geometry",
"layout-animation",
"formats",
"edid",
"units",
"utils",
"criteria",
"cmm",
"time",
"toml-config",
"algorithms",
"toml-spec",
"wire-to-xml",
"xml-to-wire",
]
[profile.release]
panic = "abort"
@ -26,6 +44,15 @@ panic = "abort"
jay-config = { version = "1.10.0", path = "jay-config" }
jay-toml-config = { version = "0.12.0", path = "toml-config" }
jay-algorithms = { version = "0.4.0", path = "algorithms" }
jay-geometry = { version = "0.1.0", path = "geometry" }
jay-layout-animation = { version = "0.1.0", path = "layout-animation" }
jay-formats = { version = "0.1.0", path = "formats" }
jay-edid = { version = "0.1.0", path = "edid" }
jay-units = { version = "0.1.0", path = "units" }
jay-utils = { version = "0.1.0", path = "utils" }
jay-criteria = { version = "0.1.0", path = "criteria" }
jay-cmm = { version = "0.1.0", path = "cmm" }
jay-time = { version = "0.1.0", path = "time" }
uapi = "0.2.13"
thiserror = "2.0.11"

View file

@ -3,7 +3,7 @@
[![crates.io](https://img.shields.io/crates/v/jay-compositor.svg)](http://crates.io/crates/jay-compositor)
Jay is a Wayland compositor for Linux with an i3-like tiling layout,
Vulkan and OpenGL rendering, multi-GPU support, screen sharing, and more.
Vulkan and OpenGL rendering, multi-GPU support, and more.
![screenshot.png](static/screenshot.png)

View file

@ -35,7 +35,6 @@
- [Mouse Interactions](mouse.md)
- [Input Modes](input-modes.md)
- [Window & Client Rules](window-rules.md)
- [Screen Sharing](screen-sharing.md)
- [HDR & Color Management](hdr.md)
# Reference

View file

@ -674,18 +674,6 @@ Show color management status:
## Other Commands
### `jay portal`
Run the Jay desktop portal (provides screen sharing and other XDG desktop
portal interfaces):
```shell
~$ jay portal
```
Normally the portal is started automatically. This command is for running it
manually or debugging.
### `jay seat-test`
Test input events from a seat. Prints all keyboard, pointer, touch, gesture,
@ -697,24 +685,6 @@ tablet, and switch events to stdout:
~$ jay seat-test -a # test all seats simultaneously
```
### `jay run-privileged`
Run a program with access to a privileged Wayland socket:
```shell
~$ jay run-privileged my-program --arg1
```
### `jay run-tagged`
Run a program with a tagged Wayland connection. All Wayland connections from the
spawned process tree will carry the specified tag, which can be matched in
[client rules](window-rules.md):
```shell
~$ jay run-tagged my-tag firefox
```
### `jay generate-completion`
Generate shell completion scripts:

View file

@ -62,16 +62,10 @@ on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
```
> [!IMPORTANT]
> Screen lockers that use the Wayland session lock protocol (like swaylock)
> need `privileged = true` in the exec configuration. This grants the process
> the necessary permissions to lock the session.
You can also combine multiple actions:
```toml
@ -80,7 +74,6 @@ on-idle = [
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
},
{ type = "exec", exec = ["notify-send", "System locked"] },
@ -100,7 +93,6 @@ on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
```

View file

@ -30,9 +30,8 @@ the color management protocol.
## Libei
[libei](https://gitlab.freedesktop.org/libinput/libei) allows applications to
emulate input events. By default, applications can only access libei through
the portal (which prompts the user for permission). Setting `enable-socket`
exposes an unauthenticated socket that any application can use without a prompt.
emulate input events. Setting `enable-socket` exposes an unauthenticated socket
that any application can use.
```toml
libei.enable-socket = false # default

View file

@ -145,7 +145,6 @@ alt-shift-r = "reload-config-toml"
(the next pressed key identifies the mark). See [Marks](#marks) below.
- `enable-window-management`, `disable-window-management` -- programmatically
enable or disable [window management mode](../floating.md#window-management-mode)
- `reload-config-so` -- reload the shared-library configuration (`config.so`)
See the [specification](https://github.com/mahkoh/jay/blob/master/toml-spec/spec/spec.generated.md) for the full list of simple
actions.
@ -309,7 +308,6 @@ alt-s = {
type = "exec",
exec = {
shell = "grim - | wl-copy",
privileged = true,
},
}
```
@ -328,12 +326,6 @@ Table fields:
`env`
: Per-process environment variables
`privileged`
: If `true`, grants access to privileged Wayland protocols (default: `false`)
`tag`
: Tag to apply to all Wayland connections spawned by this process
### Practical examples
Volume control with `pactl`:
@ -362,7 +354,6 @@ Print = {
type = "exec",
exec = {
shell = "grim - | wl-copy",
privileged = true,
},
}
```

View file

@ -63,15 +63,10 @@ on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
```
> [!NOTE]
> Screen lockers need `privileged = true` to access the privileged Wayland
> protocols required for locking the session.
You can combine idle with a grace period. The idle timeout and grace period are
configured separately in the `[idle]` section (see [Idle & Screen
Locking](idle.md)):
@ -83,7 +78,6 @@ on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
```
@ -97,7 +91,6 @@ on-idle = [
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
},
]

View file

@ -73,7 +73,7 @@ The available color keys in the `[theme]` table are:
"Focused-inactive" refers to a window that was most recently focused in its
container but whose container is not the active one. The "captured" colors apply
when a window is being recorded (e.g. via screen sharing).
when a window is being captured.
### Example

View file

@ -87,5 +87,5 @@ Xwayland client itself:
```toml
[[clients]]
match.is-xwayland = true
# ... grant capabilities, etc.
# ... configure client-specific behavior
```

View file

@ -61,10 +61,7 @@ Commands:
unlock Unlocks the compositor
screenshot Take a screenshot
idle Inspect/modify the idle (screensaver) settings
run-privileged Run a privileged program
run-tagged Run a program with a connection tag
seat-test Tests the events produced by a seat
portal Run the desktop portal
randr Inspect/modify graphics card and connector settings
input Inspect/modify input settings
xwayland Inspect/modify xwayland settings
@ -101,17 +98,6 @@ runtime.
See [GPUs](configuration/gpu.md) for details.
## Screen Sharing
Jay supports screen sharing via xdg-desktop-portal. Three capture modes are
available:
- **Window capture** -- share a single window.
- **Output capture** -- share an entire monitor.
- **Workspace capture** -- like output capture, but only one workspace is shown.
See [Screen Sharing](screen-sharing.md) for setup instructions.
## Screen Locking
Jay can automatically lock your screen and disable outputs after inactivity.
@ -154,20 +140,6 @@ Jay supports running X11 applications seamlessly through Xwayland. See
Jay supports clipboard managers via the `zwlr_data_control_manager_v1` and
`ext_data_control_manager_v1` protocols.
## Privilege Separation
Jay splits protocols into unprivileged and privileged protocols. By default,
applications only have access to unprivileged protocols. This means that tools
like screen lockers, status bars, and clipboard managers need to be explicitly
granted access.
Jay provides several ways to grant privileges, from giving a program full
access to all privileged protocols down to granting individual capabilities to
specific tagged processes. See
[Granting Privileges](window-rules.md#granting-privileges) for a detailed
guide and the [Protocol Support](#protocol-support) section below for the full
list of protocols and their privilege requirements.
## Push to Talk
Jay's shortcut system allows you to execute an action when a key is pressed and

View file

@ -63,7 +63,6 @@ For Vulkan, you also need the driver for your GPU:
- **Linux 6.7 or later** -- required for explicit sync (needed for Nvidia GPUs).
- **Xwayland** -- required for running X11 applications.
- **PipeWire** -- required for screen sharing.
- **logind** (part of systemd) -- required when running Jay from a virtual terminal or display manager.
## Building
@ -129,14 +128,7 @@ retains `CAP_SYS_NICE` solely for creating elevated Vulkan queues later.
> [!NOTE]
> You need to re-run the `setcap` command each time you update the Jay binary.
### SCHED_RR and config.so
`SCHED_RR` and `config.so` are mutually exclusive: running untrusted code at
real-time priority would be a security risk. Jay enforces this as follows:
- If `config.so` exists in the config directory, Jay skips the `SCHED_RR`
elevation (elevated Vulkan queues are still created).
- If Jay has already elevated to `SCHED_RR`, it refuses to load `config.so`.
### SCHED_RR
You can also skip `SCHED_RR` explicitly by setting `JAY_NO_REALTIME=1`:
@ -144,11 +136,7 @@ You can also skip `SCHED_RR` explicitly by setting `JAY_NO_REALTIME=1`:
~$ JAY_NO_REALTIME=1 jay run
```
This still allows elevated Vulkan queues and does not affect `config.so`
loading.
The mutual exclusion can be overridden at compile time by building Jay with
`JAY_ALLOW_REALTIME_CONFIG_SO=1`.
This still allows elevated Vulkan queues.
## Recommended Applications
@ -156,7 +144,6 @@ The following applications work well with Jay:
- **[Alacritty](https://alacritty.org/)** -- the default terminal emulator in the built-in configuration.
- **[bemenu](https://github.com/Cloudef/bemenu)** -- the default application launcher in the built-in configuration.
- **[xdg-desktop-portal-gtk4](https://github.com/mahkoh/xdg-desktop-portal-gtk4)** -- a file-picker portal with thumbnail support. Used automatically when installed.
- **[wl-tray-bridge](https://github.com/mahkoh/wl-tray-bridge)** -- shows D-Bus StatusNotifierItem applications as tray icons.
- **[mako](https://github.com/emersion/mako)** -- a notification daemon. Launched automatically by the default configuration.
- **[window-to-tray](https://github.com/mahkoh/wl-proxy/tree/master/apps/window-to-tray)** -- run most Wayland applications as tray applications (e.g. `window-to-tray pavucontrol-qt`).

View file

@ -22,12 +22,11 @@
Jay is a Wayland compositor for Linux with an i3-inspired tiling layout. It
supports Vulkan and OpenGL rendering, multi-GPU setups, fractional scaling,
variable refresh rate (VRR), tearing presentation, HDR, and screen sharing via
xdg-desktop-portal. X11 applications are supported through Xwayland.
variable refresh rate (VRR), tearing presentation, and HDR. X11 applications
are supported through Xwayland.
Jay is configured through a declarative TOML file, with an optional advanced
mode that uses a shared library for programmatic control. A comprehensive
command-line interface makes scripting and automation straightforward.
Jay is configured through a declarative TOML file. A comprehensive command-line
interface makes scripting and automation straightforward.
See the [Features](features.md) chapter for a comprehensive overview of what
Jay can do, or jump straight to [Installation](installation.md) to get started.

View file

@ -84,8 +84,8 @@ This is especially useful for:
## Other
**Toplevel selection.** Some actions (like screen sharing) ask you to select a
window, indicated by a purple overlay. During this selection, right-click a
**Toplevel selection.** Some actions ask you to select a window, indicated by a
purple overlay. During this selection, right-click a
tile's title to select the entire container instead of an individual tile.
**Canceling interactions.** Press `Escape` to cancel any in-progress mouse

View file

@ -1,111 +0,0 @@
# Screen Sharing
Jay supports screen sharing via
[xdg-desktop-portal](https://github.com/flatpak/xdg-desktop-portal). Three
capture types are available:
- **Window capture** -- share a single window.
- **Output capture** -- share an entire monitor.
- **Workspace capture** -- like output capture, but only a single workspace is
shown.
## Requirements
[PipeWire](https://pipewire.org/) must be installed and running. Verify with:
```shell
~$ systemctl --user status pipewire
```
## Portal Setup
Jay implements its own portal backend for the `ScreenCast` and `RemoteDesktop`
interfaces. Two configuration files must be installed so that
`xdg-desktop-portal` knows to use Jay's backend.
### If the Repository is Checked Out
```shell
~$ sudo cp etc/jay.portal /usr/share/xdg-desktop-portal/portals/jay.portal
~$ sudo cp etc/jay-portals.conf /usr/share/xdg-desktop-portal/jay-portals.conf
```
### If Installed via cargo install
Create the files manually:
```shell
~$ sudo tee /usr/share/xdg-desktop-portal/portals/jay.portal > /dev/null << 'EOF'
[portal]
DBusName=org.freedesktop.impl.portal.desktop.jay
Interfaces=org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.RemoteDesktop;
EOF
```
```shell
~$ sudo tee /usr/share/xdg-desktop-portal/jay-portals.conf > /dev/null << 'EOF'
[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=jay
org.freedesktop.impl.portal.RemoteDesktop=jay
org.freedesktop.impl.portal.Inhibit=none
org.freedesktop.impl.portal.FileChooser=gtk4
EOF
```
### Restart the Portal
After installing the files, restart the portal service:
```shell
~$ systemctl --user restart xdg-desktop-portal
```
## Configuration
### workspace-capture
The top-level `workspace-capture` setting controls whether newly created
workspaces can be captured via workspace capture. The default is `true`:
```toml
workspace-capture = false
```
Set this to `false` if you want to prevent workspace-level capture by default.
### Capture Indicator Colors
When a window is being recorded, its title bar color changes to make the
capture visually obvious. You can customize these colors in the `[theme]`
table:
```toml
[theme]
captured-focused-title-bg-color = "#900000"
captured-unfocused-title-bg-color = "#5f0000"
```
- `captured-focused-title-bg-color` -- background color of focused title bars
that are being recorded.
- `captured-unfocused-title-bg-color` -- background color of unfocused title
bars that are being recorded.
## The jay portal Command
Jay's portal backend is normally started automatically when a screen-sharing
request comes in via D-Bus activation. If you need to start it manually for
debugging purposes:
```shell
~$ jay portal
```
## Troubleshooting
If screen sharing does not work:
1. Verify PipeWire is running: `systemctl --user status pipewire`
2. Verify the portal files are installed in `/usr/share/xdg-desktop-portal/`.
3. Restart the portal: `systemctl --user restart xdg-desktop-portal`
4. Check the Jay log for errors: `jay log`

View file

@ -54,57 +54,6 @@ bindings.
> when any config file exists. Always use `jay config init` to start with a
> working configuration.
## Application doesn't have access to a protocol
Jay splits Wayland protocols into unprivileged and privileged. By default,
applications only have access to unprivileged protocols. If a program like a
screen locker, status bar, clipboard manager, or screen-capture tool is not
working, it likely needs access to one or more privileged protocols.
Common symptoms include:
- **swaylock** does nothing or fails to lock the screen (needs `session-lock`).
- **waybar** or **i3bar** shows no workspace information (needs
`foreign-toplevel-list`).
- **wl-copy** / **cliphist** cannot access the clipboard (needs
`data-control`).
- **grim** or **slurp** cannot capture the screen (needs `screencopy`).
**Quick fix -- grant all privileges:**
The simplest approach is to launch the program with full access to all
privileged protocols. In your config, set `privileged = true` in the exec
action:
```toml
on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
```
Or from the command line:
```shell
~$ jay run-privileged waybar
```
**Better fix -- grant only the capabilities needed:**
Use a client rule to grant specific capabilities:
```toml
[[clients]]
match.comm = "waybar"
capabilities = ["layer-shell", "foreign-toplevel-list"]
```
See [Granting Privileges](window-rules.md#granting-privileges) for the full
list of capabilities and more advanced approaches using connection tags.
## Wrong keyboard layout
The default keyboard layout is US QWERTY. To change it:
@ -132,45 +81,6 @@ layout = "de"
This takes effect immediately but does not persist across restarts unless
configured in the config file.
## Screen sharing doesn't work
Screen sharing requires PipeWire and the Jay desktop portal.
**1. Check that PipeWire is running:**
```shell
~$ systemctl --user status pipewire
```
If it is not running, start it:
```shell
~$ systemctl --user start pipewire
```
**2. Check that the portal files are installed:**
Jay needs two files to be found by the XDG desktop portal framework:
- A portal definition file (e.g. `/usr/share/xdg-desktop-portal/portals/jay.portal`).
- A portal configuration file (e.g. `/usr/share/xdg-desktop-portal/jay-portals.conf`).
These files are included in the Jay repository under `etc/`. If you built Jay
from source and did not install them, copy them manually:
```shell
~$ sudo cp etc/jay.portal /usr/share/xdg-desktop-portal/portals/
~$ sudo cp etc/jay-portals.conf /usr/share/xdg-desktop-portal/
```
**3. Restart the portal:**
```shell
~$ systemctl --user restart xdg-desktop-portal
```
See the [Screen Sharing](screen-sharing.md) chapter for more details.
## X11 applications don't work
Jay uses Xwayland to run X11 applications.

View file

@ -31,12 +31,6 @@ Each client rule can have the following fields:
`latch`
: An action to run when a client stops matching.
`capabilities`
: Wayland protocol access granted to matching clients.
`sandbox-bounding-capabilities`
: Upper bounds for protocols available to child sandboxes.
### Client Match Criteria
All client match criteria are constant over the lifetime of a client. If no
@ -70,142 +64,6 @@ implicitly AND-combined.
`exe` / `exe-regex`
: The client's `/proc/pid/exe` path.
`tag` / `tag-regex`
: The connection tag of the client.
### Granting Privileges
Jay splits Wayland protocols into unprivileged and privileged. By default,
applications only have access to unprivileged protocols. This means that tools
like screen lockers, status bars, screen-capture utilities, and clipboard
managers will not work unless you explicitly grant them the necessary
privileges.
See the [Protocol Support](features.md#protocol-support) table in the Features
chapter for the full list of protocols and whether they are privileged.
There are three ways to grant privileges, from simplest to most fine-grained.
#### 1. Grant all privileges via `privileged = true` (exec) or `jay run-privileged`
The simplest approach gives a program access to **all** privileged protocols.
This is appropriate for trusted tools like screen lockers where you don't want
to think about which specific protocols they need.
In the config, set `privileged = true` in the exec table:
```toml
on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
```
From the command line, use `jay run-privileged`:
```shell
~$ jay run-privileged waybar
```
Both methods connect the program to a privileged Wayland socket that grants
access to all privileged protocols.
#### 2. Grant capabilities via connection tags
Connection tags let you combine the CLI with client rules for precise control.
You tag a program at launch time, then write a client rule that matches
the tag and grants specific capabilities.
First, launch the program with a tag -- either from the command line:
```shell
~$ jay run-tagged bar waybar
```
Or from the config using the `tag` field in an exec action:
```toml
[shortcuts]
alt-w = {
type = "exec",
exec = {
prog = "waybar",
tag = "bar",
},
}
```
Then write a client rule that matches the tag and grants capabilities:
```toml
[[clients]]
match.tag = "bar"
capabilities = ["layer-shell", "foreign-toplevel-list"]
```
This way, only the specific instance you launched with the tag receives the
privileges -- other programs with the same binary name do not.
Available capability values: `none`, `all`, `data-control`,
`virtual-keyboard`, `foreign-toplevel-list`, `idle-notifier`, `session-lock`,
`layer-shell`, `screencopy`, `seat-manager`, `drm-lease`, `input-method`,
`workspace-manager`, `foreign-toplevel-manager`, `head-manager`,
`gamma-control-manager`, `virtual-pointer`.
**Default capabilities:** unsandboxed clients receive `layer-shell` and
`drm-lease`. Sandboxed clients receive only `drm-lease`. If any client rule
matches, its capabilities **replace** the defaults entirely. If multiple rules
match, their capabilities are unioned together, but the defaults are not
included unless a matching rule also grants them.
#### 3. Grant capabilities via client match rules
Client rules can also match programs by properties like their executable name
instead of a tag. This is convenient when you always want a given program to
have certain capabilities, regardless of how it was launched:
```toml
[[clients]]
match.comm = "waybar"
capabilities = ["layer-shell", "foreign-toplevel-list"]
# Vim 9.2 uses the data-control protocol for seamless wayland integration.
[[clients]]
match.comm = "vim"
match.sandboxed = false
capabilities = "data-control"
# Older versions use wl-copy and wl-paste.
[[clients]]
match.any = [
{ comm = "wl-copy" },
{ comm = "wl-paste" },
]
match.sandboxed = false
capabilities = "data-control"
```
> [!NOTE]
> Client match criteria like `comm`, `exe`, and `pid` are checked when a
> client connects. Any process with a matching name receives the specified
> capabilities. If you need to restrict privileges to programs you launch
> yourself, use connection tags (method 2) instead.
#### Bounding capabilities (sandboxes)
Capabilities can never exceed the client's **bounding capabilities**. Use
`sandbox-bounding-capabilities` on a client rule to set the upper bound for
protocols available to sandboxes created by that client:
```toml
[[clients]]
match.comm = "flatpak-portal"
sandbox-bounding-capabilities = ["drm-lease", "layer-shell"]
```
## Window Rules
Window rules operate on individual windows. They are defined with `[[windows]]`
@ -456,18 +314,6 @@ action = {
}
```
### Grant Protocol Access to a Trusted App
```toml
[[clients]]
match.comm = "swaylock"
capabilities = ["session-lock", "layer-shell"]
[[clients]]
match.comm = "waybar"
capabilities = ["layer-shell", "foreign-toplevel-list"]
```
### Suppress Focus Stealing for Chromium Screen-Share Windows
```toml

View file

@ -123,16 +123,15 @@ laptop.
## Workspace Capture
By default, newly created workspaces can be captured for screen sharing. You
can disable this globally:
By default, newly created workspaces can be captured by capture clients. You can
disable this globally:
```toml
workspace-capture = false
```
When workspace capture is enabled, screen-sharing applications can share
individual workspaces (in addition to full outputs and individual windows). See
[Screen Sharing](screen-sharing.md) for more details.
When workspace capture is enabled, compositor-native capture clients may capture
individual workspaces instead of whole outputs.
## Matching Windows by Workspace

8
cmm/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "jay-cmm"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-utils = { version = "0.1.0", path = "../utils" }

View file

@ -1,15 +1,13 @@
use {
crate::{
cmm::{
cmm_eotf::Eotf,
cmm_luminance::{Luminance, TargetLuminance, white_balance},
cmm_manager::Shared,
cmm_primaries::{NamedPrimaries, Primaries},
cmm_render_intent::RenderIntent,
cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment},
},
utils::ordered_float::F64,
cmm_eotf::Eotf,
cmm_luminance::{Luminance, TargetLuminance, white_balance},
cmm_manager::Shared,
cmm_primaries::{NamedPrimaries, Primaries},
cmm_render_intent::RenderIntent,
cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment},
},
jay_utils::ordered_float::F64,
std::rc::Rc,
};

View file

@ -1,4 +1,4 @@
use crate::utils::ordered_float::F32;
use jay_utils::ordered_float::F32;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Eotf {

View file

@ -1,10 +1,8 @@
use crate::{
cmm::{
cmm_render_intent::RenderIntent,
cmm_transform::{ColorMatrix, Xyz},
},
utils::ordered_float::F64,
cmm_render_intent::RenderIntent,
cmm_transform::{ColorMatrix, Xyz},
};
use jay_utils::ordered_float::F64;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Luminance {
@ -38,7 +36,6 @@ impl Luminance {
white: F64(203.0),
};
#[expect(dead_code)]
pub const HLG: Self = Self {
min: F64(0.005),
max: F64(1000.0),

View file

@ -1,16 +1,14 @@
use {
crate::{
cmm::{
cmm_description::{
ColorDescription, ColorDescriptionIds, LinearColorDescription,
LinearColorDescriptionId, LinearColorDescriptionIds,
},
cmm_eotf::Eotf,
cmm_luminance::{Luminance, TargetLuminance},
cmm_primaries::{NamedPrimaries, Primaries},
cmm_description::{
ColorDescription, ColorDescriptionIds, LinearColorDescription,
LinearColorDescriptionId, LinearColorDescriptionIds,
},
utils::{copyhashmap::CopyHashMap, numcell::NumCell, ordered_float::F64},
cmm_eotf::Eotf,
cmm_luminance::{Luminance, TargetLuminance},
cmm_primaries::{NamedPrimaries, Primaries},
},
jay_utils::{copyhashmap::CopyHashMap, numcell::NumCell, ordered_float::F64},
std::rc::{Rc, Weak},
};

View file

@ -1,4 +1,4 @@
use {crate::utils::ordered_float::F64, std::hash::Hash};
use {jay_utils::ordered_float::F64, std::hash::Hash};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum NamedPrimaries {

View file

@ -1,11 +1,3 @@
use crate::{
ifs::color_management::{
ABSOLUTE_NO_ADAPTATION_SINCE, RENDER_INTENT_ABSOLUTE_NO_ADAPTATION,
RENDER_INTENT_PERCEPTUAL, RENDER_INTENT_RELATIVE, RENDER_INTENT_RELATIVE_BPC,
},
object::Version,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub enum RenderIntent {
#[default]
@ -16,19 +8,6 @@ pub enum RenderIntent {
}
impl RenderIntent {
pub fn from_wayland(intent: u32, version: Version) -> Option<Self> {
let res = match intent {
RENDER_INTENT_PERCEPTUAL => Self::Perceptual,
RENDER_INTENT_RELATIVE => Self::Relative,
RENDER_INTENT_RELATIVE_BPC => Self::RelativeBpc,
RENDER_INTENT_ABSOLUTE_NO_ADAPTATION if version >= ABSOLUTE_NO_ADAPTATION_SINCE => {
Self::AbsoluteNoAdaptation
}
_ => return None,
};
Some(res)
}
pub fn black_point_compensation(self) -> bool {
match self {
RenderIntent::Perceptual => true,

View file

@ -1,5 +1,5 @@
mod matrices {
use crate::{cmm::cmm_primaries::Primaries, utils::ordered_float::F64};
use {crate::cmm_primaries::Primaries, jay_utils::ordered_float::F64};
fn check(primaries: Primaries, expected: [[f64; 4]; 3]) {
let (ltg, gtl) = primaries.matrices();
@ -134,7 +134,7 @@ mod matrices {
}
mod transforms {
use crate::cmm::{
use crate::{
cmm_eotf::Eotf, cmm_luminance::Luminance, cmm_manager::ColorManager,
cmm_primaries::Primaries, cmm_render_intent::RenderIntent,
};

View file

@ -1,10 +1,6 @@
use {
crate::{
cmm::{cmm_eotf::Eotf, cmm_primaries::Primaries},
gfx_api::AlphaMode,
theme::Color,
utils::ordered_float::F64,
},
crate::cmm_primaries::Primaries,
jay_utils::ordered_float::F64,
std::{
fmt,
fmt::{Debug, Formatter},
@ -129,29 +125,6 @@ impl<T, U> Mul<[f64; 3]> for ColorMatrix<T, U> {
}
}
impl<T, U> Mul<Color> for ColorMatrix<T, U> {
type Output = Color;
fn mul(self, rhs: Color) -> Self::Output {
let mut rgba = rhs.to_array(Eotf::Linear);
let a = rgba[3];
if a < 1.0 && a > 0.0 {
for c in &mut rgba[..3] {
*c /= a;
}
}
let [r, g, b] = self * [rgba[0] as f64, rgba[1] as f64, rgba[2] as f64];
Color::new(
Eotf::Linear,
AlphaMode::Straight,
r as f32,
g as f32,
b as f32,
a,
)
}
}
impl<T, U> ColorMatrix<T, U> {
pub const fn new(m: [[f64; 4]; 3]) -> Self {
let m = [

53
cmm/src/lib.rs Normal file
View file

@ -0,0 +1,53 @@
macro_rules! linear_ids {
($ids:ident, $id:ident, $ty:ty $(,)?) => {
#[derive(Debug)]
pub struct $ids {
next: jay_utils::numcell::NumCell<$ty>,
}
impl Default for $ids {
fn default() -> Self {
Self {
next: jay_utils::numcell::NumCell::new(1),
}
}
}
impl $ids {
pub fn next(&self) -> $id {
$id(self.next.fetch_add(1))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct $id($ty);
impl $id {
#[allow(dead_code)]
pub fn raw(&self) -> $ty {
self.0
}
#[allow(dead_code)]
pub fn from_raw(id: $ty) -> Self {
Self(id)
}
}
impl std::fmt::Display for $id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
};
}
pub mod cmm_description;
pub mod cmm_eotf;
pub mod cmm_luminance;
pub mod cmm_manager;
pub mod cmm_primaries;
pub mod cmm_render_intent;
#[cfg(test)]
mod cmm_tests;
pub mod cmm_transform;

12
criteria/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "jay-criteria"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-utils = { version = "0.1.0", path = "../utils" }
ahash = "0.8.7"
linearize = { version = "0.1.3", features = ["derive"] }
regex = "1.11.1"

View file

@ -1,5 +1,5 @@
use {
crate::criteria::{
crate::{
CritMatcherId,
crit_graph::{CritTarget, crit_upstream::CritUpstreamNode},
},

View file

@ -1,5 +1,5 @@
use {
crate::criteria::{
crate::{
CritUpstreamNode,
crit_graph::{
CritDownstream, CritDownstreamData, CritTarget, CritUpstreamData,

View file

@ -1,5 +1,5 @@
use {
crate::criteria::{
crate::{
CritMatcherId, CritUpstreamNode, FixedRootMatcher, RootMatcherMap,
crit_graph::{
CritTarget, CritUpstreamData,

View file

@ -1,11 +1,9 @@
use {
crate::{
criteria::{
CritDestroyListener, CritMatcherId, FixedRootMatcher, crit_leaf::CritLeafEvent,
crit_matchers::critm_constant::CritMatchConstant,
},
utils::{copyhashmap::CopyHashMap, queue::AsyncQueue},
CritDestroyListener, CritMatcherId, FixedRootMatcher, crit_leaf::CritLeafEvent,
crit_matchers::critm_constant::CritMatchConstant,
},
jay_utils::{copyhashmap::CopyHashMap, queue::AsyncQueue},
std::{
hash::Hash,
rc::{Rc, Weak},

View file

@ -1,16 +1,14 @@
use {
crate::{
criteria::{
CritDestroyListener, CritMatcherId,
crit_graph::{
WeakCritTargetOwner,
crit_downstream::CritDownstream,
crit_target::{CritTarget, CritTargetOwner},
},
crit_per_target_data::CritPerTargetData,
CritDestroyListener, CritMatcherId,
crit_graph::{
WeakCritTargetOwner,
crit_downstream::CritDownstream,
crit_target::{CritTarget, CritTargetOwner},
},
utils::copyhashmap::CopyHashMap,
crit_per_target_data::CritPerTargetData,
},
jay_utils::copyhashmap::CopyHashMap,
std::{
cell::RefMut,
mem,

View file

@ -1,12 +1,10 @@
use {
crate::{
criteria::{
CritUpstreamNode,
crit_graph::{CritDownstream, CritDownstreamData, CritMgr, CritTarget},
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
},
utils::{cell_ext::CellExt, queue::AsyncQueue},
CritUpstreamNode,
crit_graph::{CritDownstream, CritDownstreamData, CritMgr, CritTarget},
crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData},
},
jay_utils::{cell_ext::CellExt, queue::AsyncQueue},
std::{
cell::Cell,
rc::{Rc, Weak},
@ -24,7 +22,7 @@ where
events: Rc<AsyncQueue<CritLeafEvent<Target>>>,
}
pub(in crate::criteria) struct NodeHolder<Target>
pub struct NodeHolder<Target>
where
Target: CritTarget,
{
@ -77,7 +75,7 @@ impl<Target> CritLeafMatcher<Target>
where
Target: CritTarget,
{
pub(in crate::criteria) fn new(
pub(crate) fn new(
mgr: &Target::Mgr,
upstream: &Rc<dyn CritUpstreamNode<Target>>,
on_match: impl Fn(Target::LeafData) -> Box<dyn FnOnce()> + 'static,

View file

@ -1,5 +1,5 @@
use {
crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
crate::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
std::{marker::PhantomData, rc::Rc},
};

View file

@ -1,5 +1,5 @@
use {
crate::criteria::{
crate::{
CritMatcherIds, FixedRootMatcher,
crit_graph::{
CritFixedRootCriterion, CritFixedRootCriterionBase, CritMgr, CritRoot, CritRootFixed,

View file

@ -1,5 +1,5 @@
use {
crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
crate::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
std::{marker::PhantomData, rc::Rc},
};

View file

@ -1,5 +1,5 @@
use {
crate::criteria::{
crate::{
CritLiteralOrRegex, RootMatcherMap,
crit_graph::{CritRootCriterion, CritTarget},
},

View file

@ -1,5 +1,5 @@
use {
crate::criteria::{
crate::{
CritMatcherId,
crit_graph::{CritTarget, CritTargetOwner, WeakCritTargetOwner},
},
@ -28,7 +28,7 @@ where
data: T,
}
pub(super) trait CritDestroyListenerBase<Target>: 'static
pub trait CritDestroyListenerBase<Target>: 'static
where
Target: CritTarget,
{

131
criteria/src/lib.rs Normal file
View file

@ -0,0 +1,131 @@
pub mod crit_graph;
pub mod crit_leaf;
pub mod crit_matchers;
pub mod crit_per_target_data;
use {
crate::{
crit_graph::{CritMgr, CritMiddle, CritRoot, CritRootCriterion, CritRootFixed},
crit_leaf::CritLeafMatcher,
crit_matchers::{critm_any_or_all::CritMatchAnyOrAll, critm_exactly::CritMatchExactly},
},
jay_utils::{copyhashmap::CopyHashMap, numcell::NumCell},
linearize::StaticMap,
regex::Regex,
std::rc::{Rc, Weak},
};
pub use {
crit_graph::{CritTarget, CritUpstreamNode},
crit_per_target_data::CritDestroyListener,
};
#[derive(Debug)]
pub struct CritMatcherIds {
next: NumCell<u64>,
}
impl Default for CritMatcherIds {
fn default() -> Self {
Self {
next: NumCell::new(1),
}
}
}
impl CritMatcherIds {
pub fn next(&self) -> CritMatcherId {
CritMatcherId(self.next.fetch_add(1))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct CritMatcherId(u64);
impl CritMatcherId {
#[allow(clippy::allow_attributes, dead_code)]
pub fn raw(&self) -> u64 {
self.0
}
#[allow(clippy::allow_attributes, dead_code)]
pub fn from_raw(id: u64) -> Self {
Self(id)
}
}
impl std::fmt::Display for CritMatcherId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
pub type RootMatcherMap<Target, T> = CopyHashMap<CritMatcherId, Weak<CritRoot<Target, T>>>;
pub type FixedRootMatcher<Target, T> =
StaticMap<bool, Rc<CritRoot<Target, CritRootFixed<Target, T>>>>;
#[derive(Clone)]
pub enum CritLiteralOrRegex {
Literal(String),
Regex(Regex),
}
impl CritLiteralOrRegex {
fn matches(&self, string: &str) -> bool {
match self {
CritLiteralOrRegex::Literal(p) => string == p,
CritLiteralOrRegex::Regex(r) => r.is_match(string),
}
}
}
pub trait CritMgrExt: CritMgr {
fn list(
&self,
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
all: bool,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
if upstream.is_empty() {
return self.match_constant()[all].clone();
}
CritMiddle::new(self, upstream, CritMatchAnyOrAll::new(upstream, all))
}
fn exactly(
&self,
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
num: usize,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
if num > upstream.len() {
return self.match_constant()[false].clone();
}
if num == 0 {
let upstream: Vec<_> = upstream.iter().map(|u| u.not(self)).collect();
return self.list(&upstream, true);
}
CritMiddle::new(self, upstream, CritMatchExactly::new(upstream, num))
}
fn leaf(
&self,
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
on_match: impl Fn(<Self::Target as CritTarget>::LeafData) -> Box<dyn FnOnce()> + 'static,
) -> Rc<CritLeafMatcher<Self::Target>> {
CritLeafMatcher::new(self, upstream, on_match)
}
fn not(
&self,
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
upstream.not(self)
}
fn root<T>(&self, criterion: T) -> Rc<dyn CritUpstreamNode<Self::Target>>
where
T: CritRootCriterion<Self::Target>,
{
CritRoot::new(self.roots(), self.id(), criterion)
}
}
impl<T> CritMgrExt for T where T: CritMgr {}

11
edid/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "jay-edid"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "EDID parsing for Jay"
repository = "https://github.com/mahkoh/jay"
[dependencies]
bstr = { version = "1.9.0", default-features = false, features = ["std"] }
thiserror = "2.0.11"

1312
edid/src/lib.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +0,0 @@
[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=jay
org.freedesktop.impl.portal.RemoteDesktop=jay
org.freedesktop.impl.portal.Inhibit=none
org.freedesktop.impl.portal.FileChooser=gtk4

View file

@ -1,3 +0,0 @@
[portal]
DBusName=org.freedesktop.impl.portal.desktop.jay
Interfaces=org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.RemoteDesktop;

13
formats/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "jay-formats"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "Pixel format tables for Jay"
repository = "https://github.com/mahkoh/jay"
[dependencies]
ahash = "0.8.7"
ash = { package = "jay-ash", version = "0.3.0" }
clap = { version = "4.4.18", features = ["derive", "wrap_help"] }
jay-config = { version = "1.10.0", path = "../jay-config" }

559
formats/src/lib.rs Normal file
View file

@ -0,0 +1,559 @@
use {
ahash::AHashMap,
ash::vk,
clap::{ValueEnum, builder::PossibleValue},
jay_config::video::Format as ConfigFormat,
std::{
fmt::{self, Debug, Write},
sync::LazyLock,
},
};
pub type GLenum = u32;
pub type GLint = i32;
const GL_RGBA: GLint = 0x1908;
const GL_RGBA8: GLenum = 0x8058;
const GL_BGRA_EXT: GLint = 0x80E1;
const GL_UNSIGNED_BYTE: GLint = 0x1401;
#[derive(Copy, Clone, Debug)]
pub struct FormatShmInfo {
pub gl_format: GLint,
pub gl_internal_format: GLenum,
pub gl_type: GLint,
}
#[derive(Copy, Clone, Debug)]
pub struct Format {
pub name: &'static str,
pub vk_format: vk::Format,
pub drm: u32,
pub wl_id: Option<u32>,
pub external_only_guess: bool,
pub has_alpha: bool,
pub opaque: Option<&'static Format>,
pub shm_info: Option<FormatShmInfo>,
pub config: ConfigFormat,
pub bpp: u32,
}
const fn default(config: ConfigFormat) -> Format {
Format {
name: "",
vk_format: vk::Format::UNDEFINED,
drm: 0,
wl_id: None,
external_only_guess: false,
has_alpha: false,
opaque: None,
shm_info: None,
config,
bpp: 4,
}
}
impl PartialEq for Format {
fn eq(&self, other: &Self) -> bool {
self.drm == other.drm
}
}
impl Eq for Format {}
impl ValueEnum for &'static Format {
fn value_variants<'a>() -> &'a [Self] {
ref_formats()
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(PossibleValue::new(self.name))
}
}
static FORMATS_MAP: LazyLock<AHashMap<u32, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.drm, format).is_none());
}
map
});
static FORMATS_REFS: LazyLock<Vec<&'static Format>> = LazyLock::new(|| FORMATS.iter().collect());
static FORMATS_NAMES: LazyLock<AHashMap<&'static str, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.name, format).is_none());
}
map
});
static FORMATS_CONFIG: LazyLock<AHashMap<ConfigFormat, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.config, format).is_none());
}
map
});
#[test]
fn formats_dont_panic() {
formats();
named_formats();
config_formats();
}
pub fn formats() -> &'static AHashMap<u32, &'static Format> {
&FORMATS_MAP
}
pub fn ref_formats() -> &'static [&'static Format] {
&FORMATS_REFS
}
pub fn named_formats() -> &'static AHashMap<&'static str, &'static Format> {
&FORMATS_NAMES
}
pub fn config_formats() -> &'static AHashMap<ConfigFormat, &'static Format> {
&FORMATS_CONFIG
}
const fn fourcc_code(a: char, b: char, c: char, d: char) -> u32 {
(a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24)
}
pub fn debug(fourcc: u32) -> impl Debug {
fmt::from_fn(move |fmt| {
fmt.write_char(fourcc as u8 as char)?;
fmt.write_char((fourcc >> 8) as u8 as char)?;
fmt.write_char((fourcc >> 16) as u8 as char)?;
fmt.write_char((fourcc >> 24) as u8 as char)?;
Ok(())
})
}
const ARGB8888_ID: u32 = 0;
const ARGB8888_DRM: u32 = fourcc_code('A', 'R', '2', '4');
const XRGB8888_ID: u32 = 1;
const XRGB8888_DRM: u32 = fourcc_code('X', 'R', '2', '4');
pub fn map_wayland_format_id(id: u32) -> u32 {
match id {
ARGB8888_ID => ARGB8888_DRM,
XRGB8888_ID => XRGB8888_DRM,
_ => id,
}
}
pub static ARGB8888: &Format = &Format {
name: "argb8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_BGRA_EXT,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::B8G8R8A8_UNORM,
bpp: 4,
drm: ARGB8888_DRM,
wl_id: Some(ARGB8888_ID),
external_only_guess: false,
has_alpha: true,
opaque: Some(XRGB8888),
config: ConfigFormat::ARGB8888,
};
pub static XRGB8888: &Format = &Format {
name: "xrgb8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_BGRA_EXT,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::B8G8R8A8_UNORM,
bpp: 4,
drm: XRGB8888_DRM,
wl_id: Some(XRGB8888_ID),
external_only_guess: false,
has_alpha: false,
opaque: None,
config: ConfigFormat::XRGB8888,
};
static ABGR8888: &Format = &Format {
name: "abgr8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_RGBA,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::R8G8B8A8_UNORM,
bpp: 4,
drm: fourcc_code('A', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: true,
opaque: Some(XBGR8888),
config: ConfigFormat::ABGR8888,
};
static XBGR8888: &Format = &Format {
name: "xbgr8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_RGBA,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::R8G8B8A8_UNORM,
bpp: 4,
drm: fourcc_code('X', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: false,
opaque: None,
config: ConfigFormat::XBGR8888,
};
static R8: &Format = &Format {
name: "r8",
vk_format: vk::Format::R8_UNORM,
bpp: 1,
drm: fourcc_code('R', '8', ' ', ' '),
..default(ConfigFormat::R8)
};
static GR88: &Format = &Format {
name: "gr88",
vk_format: vk::Format::R8G8_UNORM,
bpp: 2,
drm: fourcc_code('G', 'R', '8', '8'),
..default(ConfigFormat::GR88)
};
static RGB888: &Format = &Format {
name: "rgb888",
vk_format: vk::Format::B8G8R8_UNORM,
bpp: 3,
drm: fourcc_code('R', 'G', '2', '4'),
..default(ConfigFormat::RGB888)
};
static BGR888: &Format = &Format {
name: "bgr888",
vk_format: vk::Format::R8G8B8_UNORM,
bpp: 3,
drm: fourcc_code('B', 'G', '2', '4'),
..default(ConfigFormat::BGR888)
};
static RGBA4444: &Format = &Format {
name: "rgba4444",
vk_format: vk::Format::R4G4B4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'A', '1', '2'),
has_alpha: true,
opaque: Some(RGBX4444),
..default(ConfigFormat::RGBA4444)
};
static RGBX4444: &Format = &Format {
name: "rgbx4444",
vk_format: vk::Format::R4G4B4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'X', '1', '2'),
..default(ConfigFormat::RGBX4444)
};
static BGRA4444: &Format = &Format {
name: "bgra4444",
vk_format: vk::Format::B4G4R4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'A', '1', '2'),
has_alpha: true,
opaque: Some(BGRX4444),
..default(ConfigFormat::BGRA4444)
};
static BGRX4444: &Format = &Format {
name: "bgrx4444",
vk_format: vk::Format::B4G4R4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'X', '1', '2'),
..default(ConfigFormat::BGRX4444)
};
static RGB565: &Format = &Format {
name: "rgb565",
vk_format: vk::Format::R5G6B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'G', '1', '6'),
..default(ConfigFormat::RGB565)
};
static BGR565: &Format = &Format {
name: "bgr565",
vk_format: vk::Format::B5G6R5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'G', '1', '6'),
..default(ConfigFormat::BGR565)
};
static RGBA5551: &Format = &Format {
name: "rgba5551",
vk_format: vk::Format::R5G5B5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'A', '1', '5'),
has_alpha: true,
opaque: Some(RGBX5551),
..default(ConfigFormat::RGBA5551)
};
static RGBX5551: &Format = &Format {
name: "rgbx5551",
vk_format: vk::Format::R5G5B5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'X', '1', '5'),
..default(ConfigFormat::RGBX5551)
};
static BGRA5551: &Format = &Format {
name: "bgra5551",
vk_format: vk::Format::B5G5R5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'A', '1', '5'),
has_alpha: true,
opaque: Some(BGRX5551),
..default(ConfigFormat::BGRA5551)
};
static BGRX5551: &Format = &Format {
name: "bgrx5551",
vk_format: vk::Format::B5G5R5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'X', '1', '5'),
..default(ConfigFormat::BGRX5551)
};
static ARGB1555: &Format = &Format {
name: "argb1555",
vk_format: vk::Format::A1R5G5B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('A', 'R', '1', '5'),
has_alpha: true,
opaque: Some(XRGB1555),
..default(ConfigFormat::ARGB1555)
};
static XRGB1555: &Format = &Format {
name: "xrgb1555",
vk_format: vk::Format::A1R5G5B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('X', 'R', '1', '5'),
..default(ConfigFormat::XRGB1555)
};
static ARGB2101010: &Format = &Format {
name: "argb2101010",
vk_format: vk::Format::A2R10G10B10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('A', 'R', '3', '0'),
has_alpha: true,
opaque: Some(XRGB2101010),
..default(ConfigFormat::ARGB2101010)
};
static XRGB2101010: &Format = &Format {
name: "xrgb2101010",
vk_format: vk::Format::A2R10G10B10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('X', 'R', '3', '0'),
..default(ConfigFormat::XRGB2101010)
};
static ABGR2101010: &Format = &Format {
name: "abgr2101010",
vk_format: vk::Format::A2B10G10R10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('A', 'B', '3', '0'),
has_alpha: true,
opaque: Some(XBGR2101010),
..default(ConfigFormat::ABGR2101010)
};
static XBGR2101010: &Format = &Format {
name: "xbgr2101010",
vk_format: vk::Format::A2B10G10R10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('X', 'B', '3', '0'),
..default(ConfigFormat::XBGR2101010)
};
static ABGR16161616: &Format = &Format {
name: "abgr16161616",
vk_format: vk::Format::R16G16B16A16_UNORM,
bpp: 8,
drm: fourcc_code('A', 'B', '4', '8'),
has_alpha: true,
opaque: Some(XBGR16161616),
..default(ConfigFormat::ABGR16161616)
};
static XBGR16161616: &Format = &Format {
name: "xbgr16161616",
vk_format: vk::Format::R16G16B16A16_UNORM,
bpp: 8,
drm: fourcc_code('X', 'B', '4', '8'),
..default(ConfigFormat::XBGR16161616)
};
pub static ABGR16161616F: &Format = &Format {
name: "abgr16161616f",
vk_format: vk::Format::R16G16B16A16_SFLOAT,
bpp: 8,
drm: fourcc_code('A', 'B', '4', 'H'),
has_alpha: true,
opaque: Some(XBGR16161616F),
..default(ConfigFormat::ABGR16161616F)
};
static XBGR16161616F: &Format = &Format {
name: "xbgr16161616f",
vk_format: vk::Format::R16G16B16A16_SFLOAT,
bpp: 8,
drm: fourcc_code('X', 'B', '4', 'H'),
..default(ConfigFormat::XBGR16161616F)
};
static BGR161616: &Format = &Format {
name: "bgr161616",
vk_format: vk::Format::R16G16B16_UNORM,
bpp: 6,
drm: fourcc_code('B', 'G', '4', '8'),
..default(ConfigFormat::BGR161616)
};
static R16F: &Format = &Format {
name: "r16f",
vk_format: vk::Format::R16_SFLOAT,
bpp: 2,
drm: fourcc_code('R', ' ', ' ', 'H'),
..default(ConfigFormat::R16F)
};
static GR1616F: &Format = &Format {
name: "gr1616f",
vk_format: vk::Format::R16G16_SFLOAT,
bpp: 4,
drm: fourcc_code('G', 'R', ' ', 'H'),
..default(ConfigFormat::GR1616F)
};
static BGR161616F: &Format = &Format {
name: "bgr161616f",
vk_format: vk::Format::R16G16B16_SFLOAT,
bpp: 6,
drm: fourcc_code('B', 'G', 'R', 'H'),
..default(ConfigFormat::BGR161616F)
};
static R32F: &Format = &Format {
name: "r32f",
vk_format: vk::Format::R32_SFLOAT,
bpp: 4,
drm: fourcc_code('R', ' ', ' ', 'F'),
..default(ConfigFormat::R32F)
};
static GR3232F: &Format = &Format {
name: "gr3232f",
vk_format: vk::Format::R32G32_SFLOAT,
bpp: 8,
drm: fourcc_code('G', 'R', ' ', 'F'),
..default(ConfigFormat::GR3232F)
};
static BGR323232F: &Format = &Format {
name: "bgr323232f",
vk_format: vk::Format::R32G32B32_SFLOAT,
bpp: 12,
drm: fourcc_code('B', 'G', 'R', 'F'),
..default(ConfigFormat::BGR323232F)
};
static ABGR32323232F: &Format = &Format {
name: "abgr32323232f",
vk_format: vk::Format::R32G32B32A32_SFLOAT,
bpp: 16,
drm: fourcc_code('A', 'B', '8', 'F'),
has_alpha: true,
..default(ConfigFormat::ABGR32323232F)
};
pub static FORMATS: &[Format] = &[
*ARGB8888,
*XRGB8888,
*ABGR8888,
*XBGR8888,
*R8,
*GR88,
*RGB888,
*BGR888,
#[cfg(target_endian = "little")]
*RGBA4444,
#[cfg(target_endian = "little")]
*RGBX4444,
#[cfg(target_endian = "little")]
*BGRA4444,
#[cfg(target_endian = "little")]
*BGRX4444,
#[cfg(target_endian = "little")]
*RGB565,
#[cfg(target_endian = "little")]
*BGR565,
#[cfg(target_endian = "little")]
*RGBA5551,
#[cfg(target_endian = "little")]
*RGBX5551,
#[cfg(target_endian = "little")]
*BGRA5551,
#[cfg(target_endian = "little")]
*BGRX5551,
#[cfg(target_endian = "little")]
*ARGB1555,
#[cfg(target_endian = "little")]
*XRGB1555,
#[cfg(target_endian = "little")]
*ARGB2101010,
#[cfg(target_endian = "little")]
*XRGB2101010,
#[cfg(target_endian = "little")]
*ABGR2101010,
#[cfg(target_endian = "little")]
*XBGR2101010,
#[cfg(target_endian = "little")]
*ABGR16161616,
#[cfg(target_endian = "little")]
*XBGR16161616,
#[cfg(target_endian = "little")]
*ABGR16161616F,
#[cfg(target_endian = "little")]
*XBGR16161616F,
#[cfg(target_endian = "little")]
*BGR161616,
#[cfg(target_endian = "little")]
*R16F,
#[cfg(target_endian = "little")]
*GR1616F,
#[cfg(target_endian = "little")]
*BGR161616F,
#[cfg(target_endian = "little")]
*R32F,
#[cfg(target_endian = "little")]
*GR3232F,
#[cfg(target_endian = "little")]
*BGR323232F,
#[cfg(target_endian = "little")]
*ABGR32323232F,
];

11
geometry/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "jay-geometry"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "Geometry primitives for Jay"
repository = "https://github.com/mahkoh/jay"
[dependencies]
jay-algorithms = { version = "0.4.0", path = "../algorithms" }
smallvec = { version = "1.11.1", features = ["const_generics", "const_new", "union"] }

365
geometry/src/lib.rs Normal file
View file

@ -0,0 +1,365 @@
mod region;
#[cfg(test)]
mod tests;
pub use region::{DamageQueue, RegionBuilder};
use {
jay_algorithms::rect::{NoTag, RectRaw, Tag},
smallvec::SmallVec,
std::fmt::{Debug, Formatter},
};
#[derive(Copy, Clone, Eq, PartialEq, Default)]
#[repr(transparent)]
pub struct Rect<T = NoTag>
where
T: Tag,
{
raw: RectRaw<T>,
}
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct Region<T = NoTag>
where
T: Tag,
{
rects: SmallVec<[RectRaw<T>; 1]>,
extents: Rect,
}
impl Debug for Rect {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.raw, f)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
pub struct RectOverflow {
pub left: i32,
pub right: i32,
pub top: i32,
pub bottom: i32,
}
impl RectOverflow {
pub fn is_contained(&self) -> bool {
self.left <= 0 && self.right <= 0 && self.top <= 0 && self.bottom <= 0
}
pub fn x_overflow(&self) -> bool {
self.left > 0 || self.right > 0
}
pub fn y_overflow(&self) -> bool {
self.top > 0 || self.bottom > 0
}
}
impl<T> Rect<T>
where
T: Tag,
{
pub fn untag(&self) -> Rect {
Rect {
raw: RectRaw {
x1: self.raw.x1,
y1: self.raw.y1,
x2: self.raw.x2,
y2: self.raw.y2,
tag: NoTag,
},
}
}
}
impl Rect {
pub fn new_empty(x: i32, y: i32) -> Self {
Self {
raw: RectRaw {
x1: x,
y1: y,
x2: x,
y2: y,
tag: NoTag,
},
}
}
pub fn new(x1: i32, y1: i32, x2: i32, y2: i32) -> Option<Self> {
if x2 < x1 || y2 < y1 {
return None;
}
Some(Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
})
}
#[cfg_attr(not(test), expect(dead_code))]
fn new_unchecked_danger(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
}
}
pub fn new_sized(x1: i32, y1: i32, width: i32, height: i32) -> Option<Self> {
if width < 0 || height < 0 {
return None;
}
Self::new(x1, y1, x1 + width, y1 + height)
}
pub fn new_saturating(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x2.max(x1),
y2: y2.max(y1),
tag: NoTag,
},
}
}
pub fn new_sized_saturating(x1: i32, y1: i32, width: i32, height: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x1.saturating_add(width.max(0)),
y2: y1.saturating_add(height.max(0)),
tag: NoTag,
},
}
}
pub fn union(&self, other: Self) -> Self {
Self {
raw: RectRaw {
x1: self.raw.x1.min(other.raw.x1),
y1: self.raw.y1.min(other.raw.y1),
x2: self.raw.x2.max(other.raw.x2),
y2: self.raw.y2.max(other.raw.y2),
tag: NoTag,
},
}
}
pub fn intersect(&self, other: Self) -> Self {
let x1 = self.raw.x1.max(other.raw.x1);
let y1 = self.raw.y1.max(other.raw.y1);
let x2 = self.raw.x2.min(other.raw.x2).max(x1);
let y2 = self.raw.y2.min(other.raw.y2).max(y1);
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
}
}
pub fn with_size_saturating(&self, width: i32, height: i32) -> Self {
Self::new_sized_saturating(self.raw.x1, self.raw.y1, width, height)
}
pub fn with_tag(&self, tag: u32) -> Rect<u32> {
Rect {
raw: RectRaw {
x1: self.raw.x1,
y1: self.raw.y1,
x2: self.raw.x2,
y2: self.raw.y2,
tag,
},
}
}
}
impl<T> Rect<T>
where
T: Tag,
{
#[cfg_attr(not(test), expect(dead_code))]
fn new_unchecked_danger_tagged(x1: i32, y1: i32, x2: i32, y2: i32, tag: T) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag,
},
}
}
pub fn intersects(&self, other: &Self) -> bool {
self.raw.x1 < other.raw.x2
&& other.raw.x1 < self.raw.x2
&& self.raw.y1 < other.raw.y2
&& other.raw.y1 < self.raw.y2
}
pub fn contains(&self, x: i32, y: i32) -> bool {
self.raw.x1 <= x && self.raw.y1 <= y && self.raw.x2 > x && self.raw.y2 > y
}
pub fn not_contains(&self, x: i32, y: i32) -> bool {
!self.contains(x, y)
}
pub fn dist_squared(&self, x: i32, y: i32) -> i128 {
let x = x as i64;
let y = y as i64;
let x1 = self.raw.x1 as i64;
let x2 = self.raw.x2 as i64;
let y1 = self.raw.y1 as i64;
let y2 = self.raw.y2 as i64;
let mut dx = 0;
if x1 > x {
dx = x1 - x;
} else if x2 < x {
dx = x - x2;
}
let mut dy = 0;
if y1 > y {
dy = y1 - y;
} else if y2 < y {
dy = y - y2;
}
let dx = dx as i128;
let dy = dy as i128;
dx * dx + dy * dy
}
pub fn contains_rect<U>(&self, rect: &Rect<U>) -> bool
where
U: Tag,
{
self.raw.x1 <= rect.raw.x1
&& self.raw.y1 <= rect.raw.x1
&& rect.raw.x2 <= self.raw.x2
&& rect.raw.y2 <= self.raw.y2
}
pub fn get_overflow<U>(&self, child: &Rect<U>) -> RectOverflow
where
U: Tag,
{
RectOverflow {
left: self.raw.x1 - child.raw.x1,
right: child.raw.x2 - self.raw.x2,
top: self.raw.y1 - child.raw.y1,
bottom: child.raw.y2 - self.raw.y2,
}
}
pub fn is_empty(&self) -> bool {
self.raw.x1 == self.raw.x2 || self.raw.y1 == self.raw.y2
}
pub fn is_not_empty(&self) -> bool {
!self.is_empty()
}
pub fn to_origin(&self) -> Self {
Self {
raw: RectRaw {
x1: 0,
y1: 0,
x2: self.raw.x2 - self.raw.x1,
y2: self.raw.y2 - self.raw.y1,
tag: self.raw.tag,
},
}
}
pub fn move_(&self, dx: i32, dy: i32) -> Self {
Self {
raw: RectRaw {
x1: self.raw.x1.saturating_add(dx),
y1: self.raw.y1.saturating_add(dy),
x2: self.raw.x2.saturating_add(dx),
y2: self.raw.y2.saturating_add(dy),
tag: self.raw.tag,
},
}
}
pub fn at_point(&self, x1: i32, y1: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x1 + self.raw.x2 - self.raw.x1,
y2: y1 + self.raw.y2 - self.raw.y1,
tag: self.raw.tag,
},
}
}
pub fn translate(&self, x: i32, y: i32) -> (i32, i32) {
(x.wrapping_sub(self.raw.x1), y.wrapping_sub(self.raw.y1))
}
pub fn translate_inv(&self, x: i32, y: i32) -> (i32, i32) {
(x.wrapping_add(self.raw.x1), y.wrapping_add(self.raw.y1))
}
pub fn x1(&self) -> i32 {
self.raw.x1
}
pub fn x2(&self) -> i32 {
self.raw.x2
}
pub fn y1(&self) -> i32 {
self.raw.y1
}
pub fn y2(&self) -> i32 {
self.raw.y2
}
pub fn width(&self) -> i32 {
self.raw.x2 - self.raw.x1
}
pub fn height(&self) -> i32 {
self.raw.y2 - self.raw.y1
}
pub fn position(&self) -> (i32, i32) {
(self.raw.x1, self.raw.y1)
}
pub fn size(&self) -> (i32, i32) {
(self.width(), self.height())
}
pub fn center(&self) -> (i32, i32) {
(
self.raw.x1 + self.width() / 2,
self.raw.y1 + self.height() / 2,
)
}
pub fn tag(&self) -> T {
self.raw.tag
}
}

View file

@ -1,11 +1,5 @@
use {
crate::{
rect::{Rect, Region},
utils::{
array,
ptr_ext::{MutPtrExt, PtrExt},
},
},
crate::{Rect, Region},
jay_algorithms::rect::{
RectRaw, Tag,
region::{
@ -15,6 +9,7 @@ use {
},
smallvec::SmallVec,
std::{
array,
borrow::Cow,
cell::UnsafeCell,
fmt::{Debug, Formatter},
@ -176,7 +171,6 @@ where
}
}
#[cfg_attr(not(feature = "it"), expect(dead_code))]
pub fn extents(&self) -> Rect {
self.extents
}
@ -274,7 +268,6 @@ impl RegionBuilder {
self.base.clone()
}
#[expect(dead_code)]
pub fn clear(&mut self) {
self.pending.clear();
self.base = Region::empty();
@ -321,26 +314,26 @@ impl DamageQueue {
}
pub fn damage(&self, rects: &[Rect]) {
let datas = unsafe { self.datas.get().deref_mut() };
let datas = unsafe { &mut *self.datas.get() };
for data in datas {
data.extend(rects);
}
}
pub fn clear(&self) {
let data = unsafe { &mut self.datas.get().deref_mut()[self.this] };
let data = unsafe { &mut (&mut *self.datas.get())[self.this] };
data.clear();
}
pub fn clear_all(&self) {
let datas = unsafe { self.datas.get().deref_mut() };
let datas = unsafe { &mut *self.datas.get() };
for data in datas {
data.clear();
}
}
pub fn get(&self) -> Region {
let data = unsafe { &self.datas.get().deref()[self.this] };
let data = unsafe { &(&*self.datas.get())[self.this] };
Region::from_rects2(data)
}
}

View file

@ -1,5 +1,5 @@
use {
crate::rect::{Rect, Region},
crate::{Rect, Region},
jay_algorithms::rect::{NoTag, RectRaw},
};

View file

@ -0,0 +1,9 @@
[package]
name = "jay-config-schema"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "Shared configuration schema declarations for the Jay compositor"
repository = "https://github.com/mahkoh/jay"
[dependencies]

View file

@ -0,0 +1,13 @@
#[derive(Debug, Clone, Default)]
pub struct Animations {
pub enabled: Option<bool>,
pub duration_ms: Option<u32>,
pub style: Option<String>,
pub curve: Option<AnimationCurveConfig>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AnimationCurveConfig {
Preset(String),
CubicBezier([f32; 4]),
}

View file

@ -0,0 +1,9 @@
//! Shared configuration schema declarations for Jay.
//!
//! This crate is the target home for option structs, defaults, validation
//! policy, and docs metadata that need to be consumed by TOML parsing,
//! generated config documentation, and compositor-side application code.
pub mod animations;
pub use animations::{AnimationCurveConfig, Animations};

View file

@ -12,7 +12,6 @@ use {
},
bincode::Options,
serde::{Deserialize, Serialize},
std::marker::PhantomData,
};
pub const VERSION: u32 = 1;
@ -31,12 +30,6 @@ pub struct ConfigEntry {
pub handle_msg: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize),
}
pub struct ConfigEntryGen<T> {
_phantom: PhantomData<T>,
}
impl<T: Config> ConfigEntryGen<T> {}
pub fn bincode_ops() -> impl Options {
bincode::DefaultOptions::new()
.with_fixint_encoding()
@ -44,10 +37,6 @@ pub fn bincode_ops() -> impl Options {
.with_no_limit()
}
pub trait Config {
extern "C" fn configure();
}
#[derive(Serialize, Deserialize, Debug)]
pub struct WireMode {
pub width: i32,
@ -99,7 +88,6 @@ pub enum ClientCriterionStringField {
SandboxInstanceId,
Comm,
Exe,
Tag,
}
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]

View file

@ -3,16 +3,14 @@
use {
crate::{
_private::{
ClientCriterionIpc, ClientCriterionStringField, Config, ConfigEntry, ConfigEntryGen,
GenericCriterionIpc, PollableId, VERSION, WindowCriterionIpc,
WindowCriterionStringField, WireMode, bincode_ops,
ClientCriterionIpc, ClientCriterionStringField, GenericCriterionIpc, PollableId,
WindowCriterionIpc, WindowCriterionStringField, WireMode, bincode_ops,
ipc::{
ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, WorkspaceSource,
},
logging,
},
Axis, Direction, ModifiedKeySym, PciId, Workspace,
client::{Client, ClientCapabilities, ClientCriterion, ClientMatcher, MatchedClient},
client::{Client, ClientCriterion, ClientMatcher, MatchedClient},
exec::Command,
input::{
FallbackOutputMode, FocusFollowsMouseMode, InputDevice, LayerDirection, Seat,
@ -199,35 +197,6 @@ unsafe fn with_client<T, F: FnOnce(&ConfigClient) -> T>(data: *const u8, f: F) -
})
}
impl<T: Config> ConfigEntryGen<T> {
pub const ENTRY: ConfigEntry = ConfigEntry {
version: VERSION,
init: Self::init,
unref,
handle_msg,
};
pub unsafe extern "C" fn init(
srv_data: *const u8,
srv_unref: unsafe extern "C" fn(data: *const u8),
srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize),
init_data: *const u8,
size: usize,
) -> *const u8 {
logging::init();
unsafe {
init(
srv_data,
srv_unref,
srv_handler,
init_data,
size,
T::configure,
)
}
}
}
pub unsafe extern "C" fn init(
srv_data: *const u8,
srv_unref: unsafe extern "C" fn(data: *const u8),
@ -348,15 +317,7 @@ impl ConfigClient {
.drain()
.map(|(a, b)| (a, b.into_raw_fd()))
.collect();
if command.tag.is_some() {
self.send(&ClientMessage::Run3 {
prog: &command.prog,
args: command.args.clone(),
env,
fds,
tag: command.tag.as_deref(),
});
} else if fds.is_empty() {
if fds.is_empty() {
self.send(&ClientMessage::Run {
prog: &command.prog,
args: command.args.clone(),
@ -1556,22 +1517,6 @@ impl ConfigClient {
connector
}
pub fn set_client_matcher_capabilities(
&self,
matcher: ClientMatcher,
caps: ClientCapabilities,
) {
self.send(&ClientMessage::SetClientMatcherCapabilities { matcher, caps });
}
pub fn set_client_matcher_bounding_capabilities(
&self,
matcher: ClientMatcher,
caps: ClientCapabilities,
) {
self.send(&ClientMessage::SetClientMatcherBoundingCapabilities { matcher, caps });
}
pub fn latch<F: FnOnce() + 'static>(&self, seat: Seat, f: F) {
if !self.feat_mod_mask.get() {
log::error!("compositor does not support latching");
@ -1673,12 +1618,6 @@ impl ConfigClient {
})
}
pub fn get_socket_path(&self) -> Option<String> {
let res = self.send_with_response(&ClientMessage::GetSocketPath);
get_response!(res, None, GetSocketPath { path });
Some(path)
}
pub fn create_pollable(&self, fd: i32) -> Result<PollableId, String> {
let res = self.send_with_response(&ClientMessage::AddPollable { fd });
get_response!(
@ -1867,8 +1806,6 @@ impl ConfigClient {
ClientCriterion::CommRegex(t) => string!(t, Comm, true),
ClientCriterion::Exe(t) => string!(t, Exe, false),
ClientCriterion::ExeRegex(t) => string!(t, Exe, true),
ClientCriterion::Tag(t) => string!(t, Tag, false),
ClientCriterion::TagRegex(t) => string!(t, Tag, true),
};
let res = self.send_with_response(&ClientMessage::CreateClientMatcher { criterion });
get_response!(

View file

@ -2,7 +2,7 @@ use {
crate::{
_private::{ClientCriterionIpc, PollableId, WindowCriterionIpc, WireMode},
Axis, Direction, PciId, Workspace,
client::{Client, ClientCapabilities, ClientMatcher},
client::{Client, ClientMatcher},
input::{
FallbackOutputMode, FocusFollowsMouseMode, InputDevice, LayerDirection, Seat,
SwitchEvent, Timeline, acceleration::AccelProfile, capability::Capability,
@ -488,7 +488,6 @@ pub enum ClientMessage<'a> {
SetExplicitSyncEnabled {
enabled: bool,
},
GetSocketPath,
DeviceSetKeymap {
device: InputDevice,
keymap: Keymap,
@ -806,14 +805,6 @@ pub enum ClientMessage<'a> {
SetTitleFont {
font: &'a str,
},
SetClientMatcherCapabilities {
matcher: ClientMatcher,
caps: ClientCapabilities,
},
SetClientMatcherBoundingCapabilities {
matcher: ClientMatcher,
caps: ClientCapabilities,
},
ShowWorkspaceOn {
seat: Seat,
workspace: Workspace,
@ -868,13 +859,6 @@ pub enum ClientMessage<'a> {
SetXWaylandEnabled {
enabled: bool,
},
Run3 {
prog: &'a str,
args: Vec<String>,
env: Vec<(String, String)>,
fds: Vec<(i32, i32)>,
tag: Option<&'a str>,
},
ConnectorSupportsArbitraryModes {
connector: Connector,
},
@ -1081,9 +1065,6 @@ pub enum Response {
GetInputDeviceDevnode {
devnode: String,
},
GetSocketPath {
path: String,
},
GetFloatAboveFullscreen {
above: bool,
},

View file

@ -91,10 +91,6 @@ pub enum ClientCriterion<'a> {
Exe(&'a str),
/// Matches the `/proc/pid/exe` of the client with a regular expression.
ExeRegex(&'a str),
/// Matches the tag of the client verbatim.
Tag(&'a str),
/// Matches the tag of the client with a regular expression.
TagRegex(&'a str),
}
impl ClientCriterion<'_> {
@ -110,19 +106,6 @@ impl ClientCriterion<'_> {
self.to_matcher().bind(cb);
}
/// Sets the capabilities granted to clients matching this matcher.
///
/// This leaks the matcher.
pub fn set_capabilities(self, caps: ClientCapabilities) {
self.to_matcher().set_capabilities(caps);
}
/// Sets the upper capability bounds for clients in sandboxes created by this client.
///
/// This leaks the matcher.
pub fn set_sandbox_bounding_capabilities(self, caps: ClientCapabilities) {
self.to_matcher().set_sandbox_bounding_capabilities(caps);
}
}
impl ClientMatcher {
@ -140,35 +123,6 @@ impl ClientMatcher {
get!().set_client_matcher_handler(self, cb);
}
/// Sets the capabilities granted to clients matching this matcher.
///
/// If multiple matchers match a client, the capabilities are added.
///
/// If no matcher matches a client, it is granted the default capabilities depending
/// on whether it's sandboxed or not. If it is not sandboxed, it is granted the
/// capabilities [`CC_LAYER_SHELL`] and [`CC_DRM_LEASE`]. Otherwise it is granted the
/// capability [`CC_DRM_LEASE`].
///
/// Regardless of any capabilities set through this function, the capabilities of the
/// client can never exceed its bounding capabilities.
pub fn set_capabilities(self, caps: ClientCapabilities) {
get!().set_client_matcher_capabilities(self, caps);
}
/// Sets the upper capability bounds for clients in sandboxes created by this client.
///
/// If multiple matchers match a client, the capabilities are added.
///
/// If no matcher matches a client, the bounding capabilities for sandboxes depend on
/// whether the client is itself sandboxed. If it is sandboxed, the bounding
/// capabilities are the effective capabilities of the client. Otherwise the bounding
/// capabilities are all capabilities.
///
/// Regardless of any capabilities set through this function, the capabilities set
/// through this function can never exceed the client's bounding capabilities.
pub fn set_sandbox_bounding_capabilities(self, caps: ClientCapabilities) {
get!().set_client_matcher_bounding_capabilities(self, caps);
}
}
impl MatchedClient {
@ -195,45 +149,3 @@ impl Deref for MatchedClient {
&self.client
}
}
bitflags! {
/// Capabilities granted to a client.
#[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq)]
pub struct ClientCapabilities(pub u64) {
/// Grants access to the `ext_data_control_manager_v1` and
/// `zwlr_data_control_manager_v1` globals.
pub const CC_DATA_CONTROL = 1 << 0,
/// Grants access to the `zwp_virtual_keyboard_manager_v1` global.
pub const CC_VIRTUAL_KEYBOARD = 1 << 1,
/// Grants access to the `ext_foreign_toplevel_list_v1` global.
pub const CC_FOREIGN_TOPLEVEL_LIST = 1 << 2,
/// Grants access to the `ext_idle_notifier_v1` global.
pub const CC_IDLE_NOTIFIER = 1 << 3,
/// Grants access to the `ext_session_lock_manager_v1` global.
pub const CC_SESSION_LOCK = 1 << 4,
/// Grants access to the `zwlr_layer_shell_v1` global.
pub const CC_LAYER_SHELL = 1 << 6,
/// Grants access to the `ext_image_copy_capture_manager_v1` and
/// `zwlr_screencopy_manager_v1` globals.
pub const CC_SCREENCOPY = 1 << 7,
/// Grants access to the `ext_transient_seat_manager_v1` global.
pub const CC_SEAT_MANAGER = 1 << 8,
/// Grants access to the `wp_drm_lease_device_v1` global.
pub const CC_DRM_LEASE = 1 << 9,
/// Grants access to the `zwp_input_method_manager_v2` global.
pub const CC_INPUT_METHOD = 1 << 10,
/// Grants access to the `ext_workspace_manager_v1` global.
pub const CC_WORKSPACE_MANAGER = 1 << 11,
/// Grants access to the `zwlr_foreign_toplevel_manager_v1` global.
pub const CC_FOREIGN_TOPLEVEL_MANAGER = 1 << 12,
/// Grants access to the `jay_head_manager_v1` and `zwlr_output_manager_v1`
/// globals.
pub const CC_HEAD_MANAGER = 1 << 13,
/// Grants access to the `zwlr_gamma_control_manager_v1` global.
pub const CC_GAMMA_CONTROL_MANAGER = 1 << 14,
/// Grants access to the `zwlr_virtual_pointer_manager_v1` global.
pub const CC_VIRTUAL_POINTER = 1 << 15,
/// Grants access to the `ext_foreign_toplevel_geometry_tracking_manager_v1` global.
pub const CC_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING = 1 << 16,
}
}

View file

@ -22,7 +22,6 @@ pub struct Command {
pub(crate) args: Vec<String>,
pub(crate) env: HashMap<String, String>,
pub(crate) fds: RefCell<HashMap<i32, OwnedFd>>,
pub(crate) tag: Option<String>,
}
impl Command {
@ -38,7 +37,6 @@ impl Command {
args: vec![],
env: Default::default(),
fds: Default::default(),
tag: Default::default(),
}
}
@ -84,27 +82,6 @@ impl Command {
self.fd(2, fd)
}
/// Runs the application with access to privileged wayland protocols.
///
/// The default is `false`.
pub fn privileged(&mut self) -> &mut Self {
match get!(self).get_socket_path() {
Some(path) => {
self.env("WAYLAND_DISPLAY", &format!("{path}.jay"));
}
_ => {
log::error!("Compositor did not send the socket path");
}
}
self
}
/// Adds a tag to Wayland connections created by the spawned command.
pub fn tag(&mut self, tag: &str) -> &mut Self {
self.tag = Some(tag.to_owned());
self
}
/// Executes the command.
///
/// This consumes all attached file descriptors.

View file

@ -832,8 +832,6 @@ pub enum SwitchEvent {
/// Enables or disables the unauthenticated libei socket.
///
/// Even if the socket is disabled, application can still request access via the portal.
///
/// The default is `false`.
pub fn set_libei_socket_enabled(enabled: bool) {
get!().set_ei_socket_enabled(enabled);

View file

@ -1,36 +1,5 @@
//! This crate allows you to configure the Jay compositor.
//!
//! A minimal example configuration looks as follows:
//!
//! ```rust
//! use jay_config::{config, quit, reload};
//! use jay_config::input::get_default_seat;
//! use jay_config::keyboard::mods::ALT;
//! use jay_config::keyboard::syms::{SYM_q, SYM_r};
//!
//! fn configure() {
//! let seat = get_default_seat();
//! // Create a key binding to exit the compositor.
//! seat.bind(ALT | SYM_q, || quit());
//! // Reload the configuration.
//! seat.bind(ALT | SYM_r, || reload());
//! }
//!
//! config!(configure);
//! ```
//!
//! You should configure your crate to be compiled as a shared library:
//!
//! ```toml
//! [lib]
//! crate-type = ["cdylib"]
//! ```
//!
//! After compiling it, copy the shared library to `$HOME/.config/jay/config.so` and restart
//! the compositor. It should then use your configuration file.
//!
//! Note that you do not have to restart the compositor every time you want to reload your
//! configuration afterwards. Instead, simply invoke the [`reload`] function via a shortcut.
//! Internal Rust configuration API used by Jay's built-in TOML configuration
//! implementation.
#![allow(
clippy::zero_prefixed_literal,

View file

@ -1,21 +1,3 @@
/// Declares the entry point of the configuration.
#[macro_export]
macro_rules! config {
($f:path) => {
#[unsafe(no_mangle)]
#[used]
pub static mut JAY_CONFIG_ENTRY_V1: $crate::_private::ConfigEntry = {
struct X;
impl $crate::_private::Config for X {
extern "C" fn configure() {
$f();
}
}
$crate::_private::ConfigEntryGen::<X>::ENTRY
};
};
}
macro_rules! try_get {
() => {{
unsafe {

View file

@ -0,0 +1,10 @@
[package]
name = "jay-layout-animation"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "Layout animation planning for Jay"
repository = "https://github.com/mahkoh/jay"
[dependencies]
jay-geometry = { version = "0.1.0", path = "../geometry" }

3410
layout-animation/src/lib.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
use {
crate::{
async_engine::SpawnedFuture,
client::ClientCaps,
security_context_acceptor::AcceptorMetadata,
state::State,
utils::{
@ -46,63 +45,49 @@ struct AllocatedSocket {
name: String,
// /run/user/1000/wayland-x
path: Ustring,
insecure: Rc<OwnedFd>,
socket: Rc<OwnedFd>,
// /run/user/1000/wayland-x.lock
lock_path: Ustring,
_lock_fd: OwnedFd,
// /run/user/1000/wayland-x.jay
secure_path: Ustring,
secure: Rc<OwnedFd>,
}
impl Drop for AllocatedSocket {
fn drop(&mut self) {
let _ = uapi::unlink(&self.path);
let _ = uapi::unlink(&self.lock_path);
let _ = uapi::unlink(&self.secure_path);
}
}
fn bind_socket(
insecure: &Rc<OwnedFd>,
secure: &Rc<OwnedFd>,
xrd: &str,
id: u32,
) -> Result<AllocatedSocket, AcceptorError> {
fn bind_socket(socket: &Rc<OwnedFd>, xrd: &str, id: u32) -> Result<AllocatedSocket, AcceptorError> {
let mut addr: c::sockaddr_un = uapi::pod_zeroed();
addr.sun_family = c::AF_UNIX as _;
let name = format!("wayland-{}", id);
let path = format_ustr!("{}/{}", xrd, name);
let jay_path = format_ustr!("{}.jay", path.display());
let lock_path = format_ustr!("{}.lock", path.display());
if jay_path.len() + 1 > addr.sun_path.len() {
if path.len() + 1 > addr.sun_path.len() {
return Err(AcceptorError::XrdTooLong(xrd.to_string()));
}
let lock_fd = uapi::open(&*lock_path, c::O_CREAT | c::O_CLOEXEC | c::O_RDWR, 0o644)
.map_os_err(AcceptorError::OpenLockFile)?;
uapi::flock(lock_fd.raw(), c::LOCK_EX | c::LOCK_NB).map_os_err(AcceptorError::LockLockFile)?;
for (name, fd) in [(&path, insecure), (&jay_path, secure)] {
match uapi::lstat(name).to_os_error() {
Ok(_) => {
log::info!("Unlinking {}", name.display());
let _ = uapi::unlink(name);
}
Err(OsError(c::ENOENT)) => {}
Err(e) => return Err(AcceptorError::SocketStat(e)),
match uapi::lstat(&path).to_os_error() {
Ok(_) => {
log::info!("Unlinking {}", path.display());
let _ = uapi::unlink(&path);
}
let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]);
sun_path[..name.len()].copy_from_slice(name.as_bytes());
sun_path[name.len()] = 0;
uapi::bind(fd.raw(), &addr).map_os_err(AcceptorError::BindFailed)?;
Err(OsError(c::ENOENT)) => {}
Err(e) => return Err(AcceptorError::SocketStat(e)),
}
let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]);
sun_path[..path.len()].copy_from_slice(path.as_bytes());
sun_path[path.len()] = 0;
uapi::bind(socket.raw(), &addr).map_os_err(AcceptorError::BindFailed)?;
Ok(AllocatedSocket {
name,
path,
insecure: insecure.clone(),
socket: socket.clone(),
lock_path,
_lock_fd: lock_fd,
secure_path: jay_path,
secure: secure.clone(),
})
}
@ -111,17 +96,11 @@ fn allocate_socket() -> Result<AllocatedSocket, AcceptorError> {
Some(d) => d,
_ => return Err(AcceptorError::XrdNotSet),
};
let mut fds = [None, None];
for fd in &mut fds {
let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
.map(Rc::new)
.map_os_err(AcceptorError::SocketFailed)?;
*fd = Some(socket);
}
let unsecure = fds[0].take().unwrap();
let secure = fds[1].take().unwrap();
let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
.map(Rc::new)
.map_os_err(AcceptorError::SocketFailed)?;
for i in 1..1000 {
match bind_socket(&unsecure, &secure, &xrd, i) {
match bind_socket(&socket, &xrd, i) {
Ok(s) => return Ok(s),
Err(e) => {
log::warn!("Cannot use the wayland-{} socket: {}", i, ErrorFmt(e));
@ -137,19 +116,12 @@ impl Acceptor {
) -> Result<(Rc<Acceptor>, Vec<SpawnedFuture<()>>), AcceptorError> {
let socket = allocate_socket()?;
log::info!("bound to socket {}", socket.path.display());
for fd in [&socket.secure, &socket.insecure] {
uapi::listen(fd.raw(), 4096).map_os_err(AcceptorError::ListenFailed)?;
}
uapi::listen(socket.socket.raw(), 4096).map_os_err(AcceptorError::ListenFailed)?;
let acc = Rc::new(Acceptor { socket });
let futures = vec![
state.eng.spawn(
"secure acceptor",
accept(acc.socket.secure.clone(), state.clone(), true),
),
state.eng.spawn(
"insecure acceptor",
accept(acc.socket.insecure.clone(), state.clone(), false),
),
state
.eng
.spawn("client acceptor", accept(acc.socket.socket.clone(), state.clone())),
];
state.acceptor.set(Some(acc.clone()));
Ok((acc, futures))
@ -160,16 +132,13 @@ impl Acceptor {
}
#[cfg_attr(not(feature = "it"), expect(dead_code))]
pub fn secure_path(&self) -> &Ustr {
self.socket.secure_path.as_ustr()
pub fn socket_path(&self) -> &Ustr {
self.socket.path.as_ustr()
}
}
async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, secure: bool) {
let metadata = Rc::new(AcceptorMetadata {
secure,
..Default::default()
});
async fn accept(fd: Rc<OwnedFd>, state: Rc<State>) {
let metadata = Rc::new(AcceptorMetadata::default());
loop {
let fd = match state.ring.accept(&fd, c::SOCK_CLOEXEC).await {
Ok(fd) => fd,
@ -181,7 +150,7 @@ async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, secure: bool) {
let id = state.clients.id();
if let Err(e) = state
.clients
.spawn(id, &state, fd, ClientCaps::all(), false, &metadata)
.spawn(id, &state, fd, &metadata)
{
log::error!("Could not spawn a client: {}", ErrorFmt(e));
break;

File diff suppressed because it is too large Load diff

View file

@ -14,8 +14,6 @@ mod pid;
mod quit;
mod randr;
mod reexec;
mod run_privileged;
mod run_tagged;
pub mod screenshot;
mod seat_test;
mod set_log_level;
@ -29,15 +27,13 @@ use {
cli::{
clients::ClientsArgs, color_management::ColorManagementArgs, config::ConfigArgs,
damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs,
json::VERBOSE_JSON, randr::RandrArgs, reexec::ReexecArgs, run_tagged::RunTaggedArgs,
tree::TreeArgs, xwayland::XwaylandArgs,
json::VERBOSE_JSON, randr::RandrArgs, reexec::ReexecArgs, tree::TreeArgs,
xwayland::XwaylandArgs,
},
compositor::{LogLevel, start_compositor},
format::{Format, ref_formats},
portal,
pr_caps::drop_all_pr_caps,
},
clap::{Args, Parser, Subcommand, ValueEnum, ValueHint, builder::PossibleValue},
clap::{Args, Parser, Subcommand, ValueEnum, ValueHint},
clap_complete::Shell,
std::sync::atomic::Ordering::Relaxed,
};
@ -88,14 +84,8 @@ pub enum Cmd {
Idle(IdleArgs),
/// Turn monitors on or off.
Dpms(DpmsArgs),
/// Run a privileged program.
RunPrivileged(RunPrivilegedArgs),
/// Run a program with a connection tag.
RunTagged(RunTaggedArgs),
/// Tests the events produced by a seat.
SeatTest(SeatTestArgs),
/// Run the desktop portal.
Portal,
/// Inspect/modify graphics card and connector settings.
Randr(RandrArgs),
/// Inspect/modify input settings.
@ -147,13 +137,6 @@ pub enum DpmsState {
Off,
}
#[derive(Args, Debug)]
pub struct RunPrivilegedArgs {
/// The program to run
#[clap(required = true, trailing_var_arg = true, value_hint = ValueHint::CommandWithArguments)]
pub program: Vec<String>,
}
#[derive(ValueEnum, Debug, Copy, Clone, Hash, Default, PartialEq)]
pub enum ScreenshotFormat {
/// The PNG image format.
@ -240,16 +223,6 @@ pub struct GenerateArgs {
shell: Shell,
}
impl ValueEnum for &'static Format {
fn value_variants<'a>() -> &'a [Self] {
ref_formats()
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(PossibleValue::new(self.name))
}
}
pub fn main() {
let cli = Jay::parse();
if not_matches!(cli.command, Cmd::Run(_)) {
@ -268,10 +241,7 @@ pub fn main() {
Cmd::Idle(a) => idle::main(cli.global, a),
Cmd::Dpms(a) => dpms::main(cli.global, a),
Cmd::Unlock => unlock::main(cli.global),
Cmd::RunPrivileged(a) => run_privileged::main(cli.global, a),
Cmd::RunTagged(a) => run_tagged::main(cli.global, a),
Cmd::SeatTest(a) => seat_test::main(cli.global, a),
Cmd::Portal => portal::run_freestanding(cli.global),
Cmd::Randr(a) => randr::main(cli.global, a),
Cmd::Input(a) => input::main(cli.global, a),
Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a),

View file

@ -167,7 +167,6 @@ pub struct Client {
pub is_xwayland: bool,
pub comm: Option<String>,
pub exe: Option<String>,
pub tag: Option<String>,
}
pub async fn handle_client_query(
@ -212,9 +211,6 @@ pub async fn handle_client_query(
Exe::handle(tl, id, c.clone(), |c, event| {
last!(c).exe = Some(event.exe.to_string());
});
Tag::handle(tl, id, c.clone(), |c, event| {
last!(c).tag = Some(event.tag.to_string());
});
tl.round_trip().await;
mem::take(&mut *c.borrow_mut())
.into_iter()
@ -253,7 +249,6 @@ impl ClientPrinter<'_> {
bol!(is_xwayland, "xwayland");
opt!(comm, "comm");
opt!(exe, "exe");
opt!(tag, "tag");
}
}
@ -269,6 +264,5 @@ pub fn make_json_client(client: &Client) -> JsonClient<'_> {
is_xwayland: client.is_xwayland,
comm: client.comm.as_deref(),
exe: client.exe.as_deref(),
tag: client.tag.as_deref(),
}
}

View file

@ -66,8 +66,6 @@ pub struct JsonClient<'a> {
pub comm: Option<&'a str>,
#[serde(skip_serializing_if = "is_none")]
pub exe: Option<&'a str>,
#[serde(skip_serializing_if = "is_none")]
pub tag: Option<&'a str>,
}
#[derive(Serialize)]

View file

@ -1,35 +0,0 @@
use {
crate::{
cli::{GlobalArgs, RunPrivilegedArgs},
compositor::WAYLAND_DISPLAY,
logger::Logger,
utils::{errorfmt::ErrorFmt, oserror::OsErrorExt, xrd::xrd},
},
std::path::PathBuf,
uapi::UstrPtr,
};
pub fn main(global: GlobalArgs, args: RunPrivilegedArgs) {
Logger::install_stderr(global.log_level);
if let Some(xrd) = xrd() {
let mut wd = match std::env::var(WAYLAND_DISPLAY) {
Ok(v) => v,
_ => fatal!("{} is not set", WAYLAND_DISPLAY),
};
wd.push_str(".jay");
let mut path = PathBuf::from(xrd);
path.push(&wd);
if path.exists() {
unsafe {
std::env::set_var(WAYLAND_DISPLAY, &wd);
}
}
}
let mut argv = UstrPtr::new();
for arg in &args.program {
argv.push(arg.as_str());
}
let program = args.program[0].as_str();
let res = uapi::execvp(program, &argv).to_os_error().unwrap_err();
fatal!("Could not execute `{}`: {}", program, ErrorFmt(res));
}

View file

@ -1,70 +0,0 @@
use {
crate::{
cli::GlobalArgs,
compositor::WAYLAND_DISPLAY,
tools::tool_client::{Handle, ToolClient, with_tool_client},
utils::{errorfmt::ErrorFmt, oserror::OsErrorExt},
wire::{jay_acceptor_request, jay_compositor},
},
clap::{Args, ValueHint},
std::{cell::Cell, env, rc::Rc},
uapi::UstrPtr,
};
#[derive(Args, Debug)]
pub struct RunTaggedArgs {
/// Specifies a tag to apply to all spawned wayland connections.
tag: String,
/// The program to run.
#[clap(required = true, trailing_var_arg = true, value_hint = ValueHint::CommandWithArguments)]
pub program: Vec<String>,
}
pub fn main(global: GlobalArgs, run_tagged_args: RunTaggedArgs) {
with_tool_client(global.log_level, |tc| async move {
let run_tagged = Rc::new(RunTagged { tc: tc.clone() });
run_tagged.run(run_tagged_args).await;
});
}
struct RunTagged {
tc: Rc<ToolClient>,
}
impl RunTagged {
async fn run(&self, args: RunTaggedArgs) {
let tc = &self.tc;
let comp = tc.jay_compositor().await;
let req = tc.id();
tc.send(jay_compositor::GetTaggedAcceptor {
self_id: comp,
id: req,
tag: &args.tag,
});
let res = Rc::new(Cell::new(None));
jay_acceptor_request::Done::handle(&tc, req, res.clone(), |res, ev| {
res.set(Some(Ok(ev.name.to_owned())));
});
jay_acceptor_request::Failed::handle(&tc, req, res.clone(), |res, ev| {
res.set(Some(Err(ev.msg.to_owned())));
});
tc.round_trip().await;
match res.take().unwrap() {
Ok(n) => {
unsafe {
env::set_var(WAYLAND_DISPLAY, &n);
}
let mut argv = UstrPtr::new();
for arg in &args.program {
argv.push(arg.as_str());
}
let program = args.program[0].as_str();
let res = uapi::execvp(program, &argv).to_os_error().unwrap_err();
fatal!("Could not execute `{}`: {}", program, ErrorFmt(res));
}
Err(msg) => {
fatal!("Could not create acceptor: {}", msg);
}
}
}
}

View file

@ -25,7 +25,6 @@ use {
pending_serial::PendingSerial,
pid_info::{PidInfo, get_pid_info, get_socket_creds},
pidfd_send_signal::pidfd_send_signal,
static_text::StaticText,
},
wire::WlRegistryId,
},
@ -71,35 +70,6 @@ bitflags! {
CAP_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING = 1 << 16,
}
impl StaticText for ClientCapsEnum {
fn text(&self) -> &'static str {
match self {
ClientCapsEnum::CAP_DATA_CONTROL_MANAGER => "data-control",
ClientCapsEnum::CAP_VIRTUAL_KEYBOARD_MANAGER => "virtual-keyboard",
ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_LIST => "foreign-toplevel-list",
ClientCapsEnum::CAP_IDLE_NOTIFIER => "idle-notifier",
ClientCapsEnum::CAP_SESSION_LOCK_MANAGER => "session-lock",
ClientCapsEnum::CAP_JAY_COMPOSITOR => "jay-compositor",
ClientCapsEnum::CAP_LAYER_SHELL => "layer-shell",
ClientCapsEnum::CAP_SCREENCOPY_MANAGER => "screencopy",
ClientCapsEnum::CAP_SEAT_MANAGER => "seat-manager",
ClientCapsEnum::CAP_DRM_LEASE => "drm-lease",
ClientCapsEnum::CAP_INPUT_METHOD => "input-method",
ClientCapsEnum::CAP_WORKSPACE => "workspace-manager",
ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_MANAGER => "foreign-toplevel-manager",
ClientCapsEnum::CAP_HEAD_MANAGER => "head-manager",
ClientCapsEnum::CAP_GAMMA_CONTROL_MANAGER => "gamma-control-manager",
ClientCapsEnum::CAP_VIRTUAL_POINTER_MANAGER => "virtual-pointer",
ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING => {
"foreign-toplevel-geometry-tracking"
}
}
}
}
pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0);
pub const CAPS_DEFAULT_SANDBOXED: ClientCaps = ClientCaps(CAP_DRM_LEASE.0);
#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct ClientId(u64);
@ -156,24 +126,12 @@ impl Clients {
id: ClientId,
global: &Rc<State>,
socket: Rc<OwnedFd>,
bounding_caps: ClientCaps,
set_bounding_caps_for_children: bool,
acceptor: &Rc<AcceptorMetadata>,
) -> Result<(), ClientError> {
let Some((uid, pid)) = get_socket_creds(&socket) else {
return Ok(());
};
self.spawn2(
id,
global,
socket,
uid,
pid,
bounding_caps,
set_bounding_caps_for_children,
false,
acceptor,
)?;
self.spawn2(id, global, socket, uid, pid, false, acceptor)?;
Ok(())
}
@ -184,15 +142,9 @@ impl Clients {
socket: Rc<OwnedFd>,
uid: c::uid_t,
pid: c::pid_t,
bounding_caps: ClientCaps,
set_bounding_caps_for_children: bool,
is_xwayland: bool,
acceptor: &Rc<AcceptorMetadata>,
) -> Result<Rc<Client>, ClientError> {
let effective_caps = match acceptor.sandboxed {
true => CAPS_DEFAULT_SANDBOXED,
false => CAPS_DEFAULT,
};
let data = Rc::new_cyclic(|slf| Client {
id,
state: global.clone(),
@ -204,8 +156,6 @@ impl Clients {
shutdown: Default::default(),
tracker: Default::default(),
is_xwayland,
effective_caps: Cell::new(effective_caps & bounding_caps),
bounding_caps_for_children: Cell::new(bounding_caps),
last_enter_serial: Default::default(),
pid_info: get_pid_info(uid, pid),
serials: Default::default(),
@ -226,10 +176,6 @@ impl Clients {
acceptor: acceptor.clone(),
});
track!(data, data);
global.update_capabilities(&data, bounding_caps, set_bounding_caps_for_children);
if acceptor.secure || is_xwayland {
data.effective_caps.set(ClientCaps::all());
}
let display = Rc::new(WlDisplay::new(&data));
track!(data, display);
data.objects.display.set(Some(display.clone()));
@ -239,13 +185,12 @@ impl Clients {
data: data.clone(),
};
log::info!(
"Client {} connected, pid: {}, uid: {}, fd: {}, comm: {:?}, caps: {:?}",
"Client {} connected, pid: {}, uid: {}, fd: {}, comm: {:?}",
id,
pid,
uid,
client.data.socket.raw(),
data.pid_info.comm,
data.effective_caps.get(),
);
client.data.property_changed(CL_CHANGED_NEW);
self.clients.borrow_mut().insert(client.data.id, client);
@ -274,9 +219,8 @@ impl Clients {
{
let clients = self.clients.borrow();
for client in clients.values() {
if client.data.effective_caps.get().contains(required_caps)
&& (!xwayland_only || client.data.is_xwayland)
{
let _ = required_caps;
if !xwayland_only || client.data.is_xwayland {
f(&client.data);
}
}
@ -336,8 +280,6 @@ pub struct Client {
shutdown: AsyncEvent,
pub tracker: Tracker<Client>,
pub is_xwayland: bool,
pub effective_caps: Cell<ClientCaps>,
pub bounding_caps_for_children: Cell<ClientCaps>,
pub last_enter_serial: Cell<Option<u64>>,
pub pid_info: PidInfo,
pub serials: RefCell<VecDeque<SerialRange>>,
@ -349,7 +291,7 @@ pub struct Client {
pub wire_scale: Cell<Option<i32>>,
pub focus_stealing_serial: Cell<Option<u64>>,
pub changed_properties: Cell<ClMatcherChange>,
pub destroyed: CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Rc<Self>>>>,
pub destroyed: CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Self>>>,
pub acceptor: Rc<AcceptorMetadata>,
}

View file

@ -1,9 +1,48 @@
pub mod cmm_description;
pub mod cmm_eotf;
pub mod cmm_luminance;
pub mod cmm_manager;
pub mod cmm_primaries;
pub mod cmm_render_intent;
#[cfg(test)]
mod cmm_tests;
pub mod cmm_transform;
pub mod cmm_description {
pub use jay_cmm::cmm_description::*;
}
pub mod cmm_eotf {
pub use jay_cmm::cmm_eotf::*;
}
pub mod cmm_luminance {
pub use jay_cmm::cmm_luminance::*;
}
pub mod cmm_manager {
pub use jay_cmm::cmm_manager::*;
}
pub mod cmm_primaries {
pub use jay_cmm::cmm_primaries::*;
}
pub mod cmm_render_intent {
use crate::{
ifs::color_management::{
ABSOLUTE_NO_ADAPTATION_SINCE, RENDER_INTENT_ABSOLUTE_NO_ADAPTATION,
RENDER_INTENT_PERCEPTUAL, RENDER_INTENT_RELATIVE, RENDER_INTENT_RELATIVE_BPC,
},
object::Version,
};
pub use jay_cmm::cmm_render_intent::*;
pub fn from_wayland(intent: u32, version: Version) -> Option<RenderIntent> {
let res = match intent {
RENDER_INTENT_PERCEPTUAL => RenderIntent::Perceptual,
RENDER_INTENT_RELATIVE => RenderIntent::Relative,
RENDER_INTENT_RELATIVE_BPC => RenderIntent::RelativeBpc,
RENDER_INTENT_ABSOLUTE_NO_ADAPTATION if version >= ABSOLUTE_NO_ADAPTATION_SINCE => {
RenderIntent::AbsoluteNoAdaptation
}
_ => return None,
};
Some(res)
}
}
pub mod cmm_transform {
pub use jay_cmm::cmm_transform::*;
}

View file

@ -50,7 +50,6 @@ use {
leaks,
logger::Logger,
output_schedule::OutputSchedule,
portal::{self, PortalStartup},
pr_caps::{PrCapsThread, pr_caps},
scale::Scale,
sighand::{self, SighandError},
@ -121,19 +120,10 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
None
};
let forker = create_forker(reaper_pid);
let portal = portal::run_from_compositor(global.log_level);
enable_profiler();
let logger = Logger::install_compositor(global.log_level);
let portal = match portal {
Ok(p) => Some(p),
Err(e) => {
log::error!("Could not spawn portal: {}", ErrorFmt(e));
None
}
};
let res = start_compositor2(
Some(forker),
portal,
Some(logger.clone()),
args,
None,
@ -152,7 +142,7 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
#[cfg(feature = "it")]
pub fn start_compositor_for_test(future: TestFuture) -> Result<(), CompositorError> {
let res = start_compositor2(None, None, None, RunArgs::default(), Some(future), None);
let res = start_compositor2(None, None, RunArgs::default(), Some(future), None);
leaks::log_leaked();
res
}
@ -194,7 +184,6 @@ pub type TestFuture = Box<dyn Fn(&Rc<State>) -> Box<dyn Future<Output = ()>>>;
fn start_compositor2(
forker: Option<Rc<ForkerProxy>>,
portal: Option<PortalStartup>,
logger: Option<Arc<Logger>>,
run_args: RunArgs,
test_future: Option<TestFuture>,
@ -308,7 +297,6 @@ fn start_compositor2(
idle_inhibitor_ids: Default::default(),
run_toplevel,
config_dir: explicit_config_dir.or_else(config_dir),
config_file_id: NumCell::new(1),
tracker: Default::default(),
data_offer_ids: Default::default(),
data_source_ids: Default::default(),
@ -342,7 +330,6 @@ fn start_compositor2(
keyboard_state_ids: Default::default(),
physical_keyboard_ids: Default::default(),
security_context_acceptors: Default::default(),
tagged_acceptors: Default::default(),
cursor_user_group_ids: Default::default(),
cursor_user_ids: Default::default(),
cursor_user_groups: Default::default(),
@ -420,13 +407,6 @@ fn start_compositor2(
forker.setenv(key.as_bytes(), val.as_bytes());
}
}
let mut _portal = None;
if let (Some(portal), Some(logger)) = (portal, &logger) {
_portal = Some(engine.spawn(
"portal",
portal.spawn(engine.clone(), ring.clone(), logger.clone()),
));
}
let _compositor = engine.spawn("compositor", start_compositor3(state.clone(), test_future));
ring.run()?;
state.clear();
@ -487,14 +467,7 @@ fn load_config(
if for_test {
return ConfigProxy::for_test(state);
}
match ConfigProxy::from_config_dir(state) {
Ok(c) => c,
Err(e) => {
log::warn!("Could not load config.so: {}", ErrorFmt(e));
log::warn!("Using default config");
ConfigProxy::default(state)
}
}
ConfigProxy::default(state)
}
fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {

View file

@ -5,18 +5,14 @@ use crate::it::test_config::TEST_CONFIG_ENTRY;
use {
crate::{
backend::{ConnectorId, DrmDeviceId, InputDeviceId},
client::{Client, ClientCaps},
config::handler::ConfigProxyHandler,
ifs::wl_seat::SeatId,
state::State,
tree::{TileState, ToplevelData, ToplevelIdentifier},
utils::{
clonecell::CloneCell,
nice::{JAY_NO_REALTIME, dont_allow_config_so},
numcell::NumCell,
ptr_ext::PtrExt,
unlink_on_drop::UnlinkOnDrop,
xrd::xrd,
},
},
bincode::Options,
@ -30,27 +26,9 @@ use {
video::{Connector, DrmDevice},
window::{self},
},
libloading::Library,
std::{cell::Cell, io, mem, path::Path, ptr, rc::Rc},
thiserror::Error,
std::{cell::Cell, mem, ptr, rc::Rc},
};
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("Could not load the config library")]
CouldNotLoadLibrary(#[source] libloading::Error),
#[error("Config library does not contain the entry symbol")]
LibraryDoesNotContainEntry(#[source] libloading::Error),
#[error("Could not determine the config directory")]
ConfigDirNotSet,
#[error("Could not copy the config file")]
CopyConfigFile(#[source] io::Error),
#[error("XDG_RUNTIME_DIR is not set")]
XrdNotSet,
#[error("Custom config.so is not permitted")]
NotPermitted,
}
pub struct ConfigProxy {
handler: CloneCell<Option<Rc<ConfigProxyHandler>>>,
}
@ -181,16 +159,6 @@ impl ConfigProxy {
self.handler.get()?.initial_tile_state(data)
}
pub fn update_capabilities(
&self,
data: &Rc<Client>,
bounding_caps: ClientCaps,
set_bounding_caps: bool,
) {
if let Some(handler) = self.handler.get() {
handler.update_capabilities(data, bounding_caps, set_bounding_caps);
}
}
}
impl Drop for ConfigProxy {
@ -215,18 +183,11 @@ unsafe extern "C" fn default_client_init(
}
impl ConfigProxy {
fn new(
lib: Option<Library>,
entry: &ConfigEntry,
state: &Rc<State>,
path: Option<String>,
) -> Self {
fn new(entry: &ConfigEntry, state: &Rc<State>) -> Self {
let version = entry.version.min(VERSION);
let data = Rc::new(ConfigProxyHandler {
path,
client_data: Cell::new(ptr::null()),
dropped: Cell::new(false),
_lib: lib,
_version: version,
unref: entry.unref,
handle_msg: entry.handle_msg,
@ -249,8 +210,6 @@ impl ConfigProxy {
client_matchers: Default::default(),
client_matcher_cache: Default::default(),
client_matcher_leafs: Default::default(),
client_matcher_capabilities: Default::default(),
client_matcher_bounding_capabilities: Default::default(),
window_matcher_ids: NumCell::new(1),
window_matchers: Default::default(),
window_matcher_cache: Default::default(),
@ -291,75 +250,12 @@ impl ConfigProxy {
unref: jay_config::_private::client::unref,
handle_msg: jay_config::_private::client::handle_msg,
};
Self::new(None, &entry, state, None)
Self::new(&entry, state)
}
#[cfg(feature = "it")]
pub fn for_test(state: &Rc<State>) -> Self {
Self::new(None, &TEST_CONFIG_ENTRY, state, None)
}
pub fn from_config_dir(state: &Rc<State>) -> Result<Self, ConfigError> {
if dont_allow_config_so() {
if have_config_so(state.config_dir.as_deref()) {
log::warn!("Not loading config.so because");
log::warn!(" 1. Jay was started with CAP_SYS_NICE");
log::warn!(" 2. Jay was not started with {}=1", JAY_NO_REALTIME);
log::warn!(" 3. The scheduler was elevated to SCHED_RR");
log::warn!(
" 4. Jay was not compiled with {}=1",
jay_allow_realtime_config_so!(),
);
}
return Err(ConfigError::NotPermitted);
}
let dir = match state.config_dir.as_deref() {
Some(d) => d,
_ => return Err(ConfigError::ConfigDirNotSet),
};
let file = format!("{}/{CONFIG_SO}", dir);
unsafe { Self::from_file(&file, state) }
}
pub unsafe fn from_file(path: &str, state: &Rc<State>) -> Result<Self, ConfigError> {
// Here we have to do a bit of a dance to support reloading. glibc will
// never load a library twice unless it has been unloaded in between.
// glibc identifies libraries by their file path and by their inode
// number. If either of those match, glibc considers the libraries
// identical. If the inode has not changed then this is not a problem
// for us since we don't want glibc to do any unnecessary work.
// However, if the user has created a new config with a new inode, then
// glibc will still not reload the library if we try to load it from
// the canonical location ~/.config/jay/config.so since it already has
// a library with that path loaded. To work around this, create a
// temporary copy with an incrementing number and load the library
// from there.
let xrd = match xrd() {
Some(x) => x,
_ => return Err(ConfigError::XrdNotSet),
};
let copy = format!(
"{}/.jay_config.so.{}.{}",
xrd,
uapi::getpid(),
state.config_file_id.fetch_add(1)
);
let _ = uapi::unlink(copy.as_str());
if let Err(e) = std::fs::copy(path, &copy) {
return Err(ConfigError::CopyConfigFile(e));
}
let unlink = UnlinkOnDrop(&copy);
let lib = match unsafe { Library::new(&copy) } {
Ok(l) => l,
Err(e) => return Err(ConfigError::CouldNotLoadLibrary(e)),
};
let entry = unsafe { lib.get::<&'static ConfigEntry>(b"JAY_CONFIG_ENTRY_V1\0") };
let entry = match entry {
Ok(e) => *e,
Err(e) => return Err(ConfigError::LibraryDoesNotContainEntry(e)),
};
mem::forget(unlink);
Ok(Self::new(Some(lib), entry, state, Some(copy)))
Self::new(&TEST_CONFIG_ENTRY, state)
}
}
@ -388,15 +284,3 @@ pub struct InvokedShortcut {
pub effective_mods: Modifiers,
pub sym: KeySym,
}
const CONFIG_SO: &str = "config.so";
pub fn have_config_so(config_dir: Option<&str>) -> bool {
let Some(dir) = config_dir else {
return false;
};
let mut dir = dir.to_owned();
dir.push_str("/");
dir.push_str(CONFIG_SO);
Path::new(&dir).exists()
}

View file

@ -6,9 +6,9 @@ use {
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId,
transaction::BackendConnectorTransactionError,
},
client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientId},
client::{Client, ClientId},
cmm::cmm_eotf::Eotf,
compositor::{MAX_EXTENTS, WAYLAND_DISPLAY},
compositor::MAX_EXTENTS,
criteria::{
CritLiteralOrRegex, CritMgrExt, CritTarget, CritUpstreamNode,
clm::ClmLeafMatcher,
@ -25,7 +25,6 @@ use {
output_schedule::map_cursor_hz,
scale::Scale,
state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State},
tagged_acceptor::TaggedAcceptorError,
theme::{ThemeColor, ThemeSized},
tree::{
ContainerSplit, OutputNode, TearingMode, TileState, ToplevelData, ToplevelIdentifier,
@ -37,7 +36,7 @@ use {
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
numcell::NumCell,
oserror::{OsError, OsErrorExt},
oserror::OsErrorExt,
stack::Stack,
timer::{TimerError, TimerFd},
},
@ -50,7 +49,7 @@ use {
ipc::{ClientMessage, Response, ServerMessage, WorkspaceSource},
},
Axis, Direction, Workspace,
client::{Client as ConfigClient, ClientCapabilities, ClientMatcher},
client::{Client as ConfigClient, ClientMatcher},
input::{
FallbackOutputMode, FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, Timeline,
acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT, AccelProfile},
@ -76,7 +75,6 @@ use {
xwayland::XScalingMode,
},
kbvm::Keycode,
libloading::Library,
log::Level,
regex::Regex,
std::{
@ -92,10 +90,8 @@ use {
};
pub(super) struct ConfigProxyHandler {
pub path: Option<String>,
pub client_data: Cell<*const u8>,
pub dropped: Cell<bool>,
pub _lib: Option<Library>,
pub _version: u32,
pub unref: unsafe extern "C" fn(data: *const u8),
pub handle_msg: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize),
@ -121,24 +117,9 @@ pub(super) struct ConfigProxyHandler {
pub client_matcher_ids: NumCell<u64>,
pub client_matchers:
CopyHashMap<ClientMatcher, Rc<CachedCriterion<ClientCriterionIpc, Rc<Client>>>>,
pub client_matcher_cache: CriterionCache<ClientCriterionIpc, Rc<Client>>,
CopyHashMap<ClientMatcher, Rc<CachedCriterion<ClientCriterionIpc, Client>>>,
pub client_matcher_cache: CriterionCache<ClientCriterionIpc, Client>,
pub client_matcher_leafs: CopyHashMap<ClientMatcher, Rc<ClmLeafMatcher>>,
pub client_matcher_capabilities: CopyHashMap<
ClientMatcher,
(
Rc<CachedCriterion<ClientCriterionIpc, Rc<Client>>>,
ClientCaps,
),
>,
pub client_matcher_bounding_capabilities: CopyHashMap<
ClientMatcher,
(
Rc<CachedCriterion<ClientCriterionIpc, Rc<Client>>>,
ClientCaps,
),
>,
pub window_matcher_ids: NumCell<u64>,
pub window_matchers:
CopyHashMap<WindowMatcher, Rc<CachedCriterion<WindowCriterionIpc, ToplevelData>>>,
@ -218,11 +199,6 @@ impl ConfigProxyHandler {
self.window_matcher_leafs.clear();
self.window_matchers.clear();
if let Some(path) = &self.path
&& let Err(e) = uapi::unlink(path.as_str())
{
log::error!("Could not unlink {}: {}", path, ErrorFmt(OsError(e.0)));
}
}
pub fn send(&self, msg: &ServerMessage) {
@ -1185,19 +1161,6 @@ impl ConfigProxyHandler {
self.state.set_color_management_enabled(enabled);
}
fn handle_get_socket_path(&self) {
match self.state.acceptor.get() {
Some(a) => {
self.respond(Response::GetSocketPath {
path: a.socket_name().to_string(),
});
}
_ => {
log::warn!("There is no acceptor");
}
}
}
fn handle_connector_connected(&self, connector: Connector) -> Result<(), CphError> {
let connector = self.get_connector(connector)?;
self.respond(Response::ConnectorConnected {
@ -1937,18 +1900,9 @@ impl ConfigProxyHandler {
&self,
prog: &str,
args: Vec<String>,
mut env: Vec<(String, String)>,
env: Vec<(String, String)>,
fds: Vec<(i32, i32)>,
tag: Option<&str>,
) -> Result<(), CphError> {
if let Some(tag) = tag {
let display = self
.state
.tagged_acceptors
.get(&self.state, tag)
.map_err(CphError::CreateTaggedAcceptor)?;
env.push((WAYLAND_DISPLAY.to_string(), display.to_string()));
}
let fds: Vec<_> = fds
.into_iter()
.map(|(a, b)| (a, Rc::new(OwnedFd::new(b))))
@ -2125,7 +2079,7 @@ impl ConfigProxyHandler {
fn get_client_matcher(
&self,
matcher: ClientMatcher,
) -> Result<Rc<CachedCriterion<ClientCriterionIpc, Rc<Client>>>, CphError> {
) -> Result<Rc<CachedCriterion<ClientCriterionIpc, Client>>, CphError> {
self.client_matchers
.get(&matcher)
.ok_or(CphError::ClientMatcherDoesNotExist(matcher))
@ -2226,7 +2180,6 @@ impl ConfigProxyHandler {
}
ClientCriterionStringField::Comm => mgr.comm(needle),
ClientCriterionStringField::Exe => mgr.exe(needle),
ClientCriterionStringField::Tag => mgr.tag(needle),
}
}
ClientCriterionIpc::Sandboxed => mgr.sandboxed(),
@ -2249,8 +2202,6 @@ impl ConfigProxyHandler {
fn handle_destroy_client_matcher(&self, matcher: ClientMatcher) {
self.client_matchers.remove(&matcher);
self.client_matcher_leafs.remove(&matcher);
self.client_matcher_capabilities.remove(&matcher);
self.client_matcher_bounding_capabilities.remove(&matcher);
}
fn handle_enable_client_matcher_events(
@ -2862,28 +2813,6 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_set_client_matcher_capabilities(
&self,
matcher: ClientMatcher,
caps: ClientCapabilities,
) -> Result<(), CphError> {
let m = self.get_client_matcher(matcher)?;
self.client_matcher_capabilities
.set(matcher, (m, caps.to_client_caps()));
Ok(())
}
fn handle_set_client_matcher_bounding_capabilities(
&self,
matcher: ClientMatcher,
caps: ClientCapabilities,
) -> Result<(), CphError> {
let m = self.get_client_matcher(matcher)?;
self.client_matcher_bounding_capabilities
.set(matcher, (m, caps.to_client_caps()));
Ok(())
}
pub fn handle_request(self: &Rc<Self>, msg: &[u8]) {
if let Err(e) = self.handle_request_(msg) {
log::error!("Could not handle client request: {}", ErrorFmt(e));
@ -2946,7 +2875,7 @@ impl ConfigProxyHandler {
ClientMessage::GetSeats => self.handle_get_seats(),
ClientMessage::RemoveSeat { .. } => {}
ClientMessage::Run { prog, args, env } => {
self.handle_run(prog, args, env, vec![], None).wrn("run")?
self.handle_run(prog, args, env, vec![]).wrn("run")?
}
ClientMessage::GrabKb { kb, grab } => self.handle_grab(kb, grab).wrn("grab")?,
ClientMessage::SetColor { colorable, color } => {
@ -3154,7 +3083,7 @@ impl ConfigProxyHandler {
args,
env,
fds,
} => self.handle_run(prog, args, env, fds, None).wrn("run")?,
} => self.handle_run(prog, args, env, fds).wrn("run")?,
ClientMessage::DisableDefaultSeat => self.state.create_default_seat.set(false),
ClientMessage::DestroyKeymap { keymap } => self.handle_destroy_keymap(keymap),
ClientMessage::GetConnectorName { connector } => self
@ -3207,7 +3136,6 @@ impl ConfigProxyHandler {
ClientMessage::SetExplicitSyncEnabled { enabled } => {
self.handle_set_explicit_sync_enabled(enabled)
}
ClientMessage::GetSocketPath => self.handle_get_socket_path(),
ClientMessage::DeviceSetKeymap { device, keymap } => self
.handle_set_device_keymap(device, keymap)
.wrn("set_device_keymap")?,
@ -3508,12 +3436,6 @@ impl ConfigProxyHandler {
.wrn("connector_set_blend_space")?,
ClientMessage::SetBarFont { font } => self.handle_set_bar_font(font),
ClientMessage::SetTitleFont { font } => self.handle_set_title_font(font),
ClientMessage::SetClientMatcherCapabilities { matcher, caps } => self
.handle_set_client_matcher_capabilities(matcher, caps)
.wrn("set_client_matcher_capabilities")?,
ClientMessage::SetClientMatcherBoundingCapabilities { matcher, caps } => self
.handle_set_client_matcher_bounding_capabilities(matcher, caps)
.wrn("set_client_matcher_bounding_capabilities")?,
ClientMessage::ShowWorkspaceOn {
seat,
workspace,
@ -3559,13 +3481,6 @@ impl ConfigProxyHandler {
ClientMessage::SetXWaylandEnabled { enabled } => self
.handle_set_x_wayland_enabled(enabled)
.wrn("set_x_wayland_enabled")?,
ClientMessage::Run3 {
prog,
args,
env,
fds,
tag,
} => self.handle_run(prog, args, env, fds, tag).wrn("run")?,
ClientMessage::ConnectorSupportsArbitraryModes { connector } => self
.handle_connector_supports_arbitrary_modes(connector)
.wrn("connector_supports_arbitrary_modes")?,
@ -3638,41 +3553,6 @@ impl ConfigProxyHandler {
None
}
pub fn update_capabilities(
&self,
data: &Rc<Client>,
bounding_caps: ClientCaps,
set_bounding_caps: bool,
) {
let mut have_caps = false;
let mut have_bounding_caps = false;
let mut caps = ClientCaps::none();
let mut new_bounding_caps = ClientCaps::none();
for (matcher, state) in self.client_matcher_capabilities.lock().values() {
if matcher.node.pull(data) {
have_caps = true;
caps |= *state;
}
}
for (matcher, state) in self.client_matcher_bounding_capabilities.lock().values() {
if matcher.node.pull(data) {
have_bounding_caps = true;
new_bounding_caps |= *state;
}
}
if have_caps {
caps &= bounding_caps;
data.effective_caps.set(caps);
}
if !have_bounding_caps && set_bounding_caps {
have_bounding_caps = true;
new_bounding_caps = data.effective_caps.get();
}
if have_bounding_caps {
new_bounding_caps &= bounding_caps;
data.bounding_caps_for_children.set(new_bounding_caps);
}
}
}
#[derive(Debug, Error)]
@ -3771,8 +3651,6 @@ enum CphError {
UnknownFallbackOutputMode(FallbackOutputMode),
#[error("Unknown tile state {0:?}")]
UnknownTileState(ConfigTileState),
#[error("Could not create a tagged acceptor")]
CreateTaggedAcceptor(#[source] TaggedAcceptorError),
}
trait WithRequestName {
@ -3784,13 +3662,3 @@ impl WithRequestName for Result<(), CphError> {
self.map_err(move |e| CphError::FailedRequest(request, Box::new(e)))
}
}
trait ClientCapabilitiesExt {
fn to_client_caps(self) -> ClientCaps;
}
impl ClientCapabilitiesExt for ClientCapabilities {
fn to_client_caps(self) -> ClientCaps {
ClientCaps(self.0 as u32) & !CAP_JAY_COMPOSITOR & ClientCaps::all()
}
}

View file

@ -1,96 +1,4 @@
pub mod clm;
mod crit_graph;
pub mod crit_leaf;
mod crit_matchers;
mod crit_per_target_data;
pub mod tlm;
use {
crate::{
criteria::{
crit_graph::{CritMgr, CritMiddle, CritRoot, CritRootCriterion, CritRootFixed},
crit_leaf::CritLeafMatcher,
crit_matchers::{critm_any_or_all::CritMatchAnyOrAll, critm_exactly::CritMatchExactly},
},
utils::copyhashmap::CopyHashMap,
},
linearize::StaticMap,
regex::Regex,
std::rc::{Rc, Weak},
};
pub use {
crit_graph::{CritTarget, CritUpstreamNode},
crit_per_target_data::CritDestroyListener,
};
linear_ids!(CritMatcherIds, CritMatcherId, u64);
type RootMatcherMap<Target, T> = CopyHashMap<CritMatcherId, Weak<CritRoot<Target, T>>>;
type FixedRootMatcher<Target, T> = StaticMap<bool, Rc<CritRoot<Target, CritRootFixed<Target, T>>>>;
#[derive(Clone)]
pub enum CritLiteralOrRegex {
Literal(String),
Regex(Regex),
}
impl CritLiteralOrRegex {
fn matches(&self, string: &str) -> bool {
match self {
CritLiteralOrRegex::Literal(p) => string == p,
CritLiteralOrRegex::Regex(r) => r.is_match(string),
}
}
}
pub trait CritMgrExt: CritMgr {
fn list(
&self,
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
all: bool,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
if upstream.is_empty() {
return self.match_constant()[all].clone();
}
CritMiddle::new(self, upstream, CritMatchAnyOrAll::new(upstream, all))
}
fn exactly(
&self,
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
num: usize,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
if num > upstream.len() {
return self.match_constant()[false].clone();
}
if num == 0 {
let upstream: Vec<_> = upstream.iter().map(|u| u.not(self)).collect();
return self.list(&upstream, true);
}
CritMiddle::new(self, upstream, CritMatchExactly::new(upstream, num))
}
fn leaf(
&self,
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
on_match: impl Fn(<Self::Target as CritTarget>::LeafData) -> Box<dyn FnOnce()> + 'static,
) -> Rc<CritLeafMatcher<Self::Target>> {
CritLeafMatcher::new(self, upstream, on_match)
}
fn not(
&self,
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
upstream.not(self)
}
fn root<T>(&self, criterion: T) -> Rc<dyn CritUpstreamNode<Self::Target>>
where
T: CritRootCriterion<Self::Target>,
{
CritRoot::new(self.roots(), self.id(), criterion)
}
}
impl<T> CritMgrExt for T where T: CritMgr {}
pub use jay_criteria::*;

View file

@ -13,7 +13,7 @@ use {
clmm_sandboxed::ClmMatchSandboxed,
clmm_string::{
ClmMatchComm, ClmMatchExe, ClmMatchSandboxAppId, ClmMatchSandboxEngine,
ClmMatchSandboxInstanceId, ClmMatchTag,
ClmMatchSandboxInstanceId,
},
clmm_uid::ClmMatchUid,
},
@ -39,19 +39,19 @@ bitflags! {
CL_CHANGED_NEW,
}
type ClmFixedRootMatcher<T> = FixedRootMatcher<Rc<Client>, T>;
type ClmFixedRootMatcher<T> = FixedRootMatcher<Client, T>;
pub struct ClMatcherManager {
ids: Rc<CritMatcherIds>,
changes: AsyncQueue<Rc<Client>>,
leaf_events: Rc<AsyncQueue<CritLeafEvent<Rc<Client>>>>,
constant: ClmFixedRootMatcher<CritMatchConstant<Rc<Client>>>,
leaf_events: Rc<AsyncQueue<CritLeafEvent<Client>>>,
constant: ClmFixedRootMatcher<CritMatchConstant<Client>>,
sandboxed: ClmFixedRootMatcher<ClmMatchSandboxed>,
is_xwayland: ClmFixedRootMatcher<ClmMatchIsXwayland>,
matchers: Rc<RootMatchers>,
}
type ClmRootMatcherMap<T> = RootMatcherMap<Rc<Client>, T>;
type ClmRootMatcherMap<T> = RootMatcherMap<Client, T>;
#[derive(Default)]
pub struct RootMatchers {
@ -62,7 +62,6 @@ pub struct RootMatchers {
pid: ClmRootMatcherMap<ClmMatchPid>,
comm: ClmRootMatcherMap<ClmMatchComm>,
exe: ClmRootMatcherMap<ClmMatchExe>,
tag: ClmRootMatcherMap<ClmMatchTag>,
id: ClmRootMatcherMap<ClmMatchId>,
}
@ -75,7 +74,6 @@ impl RootMatchers {
self.pid.clear();
self.comm.clear();
self.exe.clear();
self.tag.clear();
self.id.clear();
}
}
@ -98,8 +96,8 @@ pub async fn handle_cl_leaf_events(state: Rc<State>) {
}
}
pub type ClmUpstreamNode = dyn CritUpstreamNode<Rc<Client>>;
pub type ClmLeafMatcher = CritLeafMatcher<Rc<Client>>;
pub type ClmUpstreamNode = dyn CritUpstreamNode<Client>;
pub type ClmLeafMatcher = CritLeafMatcher<Client>;
impl ClMatcherManager {
pub fn new(ids: &Rc<CritMatcherIds>) -> Self {
@ -146,6 +144,7 @@ impl ClMatcherManager {
}
fn update_matches(&self, data: &Rc<Client>) {
let data = data.as_ref();
let mut changed = data.changed_properties.take();
if changed.contains(CL_CHANGED_DESTROYED) {
for destroyed in data.destroyed.lock().drain_values() {
@ -186,7 +185,6 @@ impl ClMatcherManager {
unconditional!(pid);
unconditional!(comm);
unconditional!(exe);
unconditional!(tag);
unconditional!(id);
fixed!(sandboxed);
fixed!(is_xwayland);
@ -230,20 +228,29 @@ impl ClMatcherManager {
self.root(ClmMatchExe::new(string))
}
pub fn tag(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
self.root(ClmMatchTag::new(string))
}
}
impl CritTarget for Rc<Client> {
pub struct ClientTargetOwner {
client: Rc<Client>,
}
pub struct WeakClientTargetOwner {
state: Weak<State>,
id: ClientId,
}
impl CritTarget for Client {
type Id = ClientId;
type Mgr = ClMatcherManager;
type RootMatchers = RootMatchers;
type LeafData = ClientId;
type Owner = Weak<Client>;
type Owner = WeakClientTargetOwner;
fn owner(&self) -> Self::Owner {
Rc::downgrade(self)
WeakClientTargetOwner {
state: Rc::downgrade(&self.state),
id: self.id,
}
}
fn id(&self) -> Self::Id {
@ -259,25 +266,27 @@ impl CritTarget for Rc<Client> {
}
}
impl CritTargetOwner for Rc<Client> {
type Target = Rc<Client>;
impl CritTargetOwner for ClientTargetOwner {
type Target = Client;
fn data(&self) -> &Self::Target {
self
&self.client
}
}
impl WeakCritTargetOwner for Weak<Client> {
type Target = Rc<Client>;
type Owner = Rc<Client>;
impl WeakCritTargetOwner for WeakClientTargetOwner {
type Target = Client;
type Owner = ClientTargetOwner;
fn upgrade(&self) -> Option<Self::Owner> {
self.upgrade()
let state = self.state.upgrade()?;
let client = state.clients.get(self.id).ok()?;
Some(ClientTargetOwner { client })
}
}
impl CritMgr for ClMatcherManager {
type Target = Rc<Client>;
type Target = Client;
fn id(&self) -> CritMatcherId {
self.ids.next()

View file

@ -1,6 +1,6 @@
macro_rules! fixed_root_criterion {
($ty:ty, $field:ident) => {
impl crate::criteria::crit_graph::CritFixedRootCriterionBase<Rc<crate::client::Client>>
impl crate::criteria::crit_graph::CritFixedRootCriterionBase<crate::client::Client>
for $ty
{
fn constant(&self) -> bool {
@ -10,7 +10,7 @@ macro_rules! fixed_root_criterion {
fn not<'a>(
&self,
mgr: &'a crate::criteria::clm::ClMatcherManager,
) -> &'a crate::criteria::FixedRootMatcher<Rc<crate::client::Client>, Self> {
) -> &'a crate::criteria::FixedRootMatcher<crate::client::Client, Self> {
&mgr.$field
}
}

View file

@ -3,17 +3,16 @@ use {
client::{Client, ClientId},
criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion},
},
std::rc::Rc,
};
pub struct ClmMatchId(pub ClientId);
impl CritRootCriterion<Rc<Client>> for ClmMatchId {
fn matches(&self, data: &Rc<Client>) -> bool {
impl CritRootCriterion<Client> for ClmMatchId {
fn matches(&self, data: &Client) -> bool {
data.id == self.0
}
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Rc<Client>, Self>> {
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Client, Self>> {
Some(&roots.id)
}
}

View file

@ -1,14 +1,13 @@
use {
crate::{client::Client, criteria::crit_graph::CritFixedRootCriterion},
std::rc::Rc,
};
pub struct ClmMatchIsXwayland(pub bool);
fixed_root_criterion!(ClmMatchIsXwayland, is_xwayland);
impl CritFixedRootCriterion<Rc<Client>> for ClmMatchIsXwayland {
fn matches(&self, data: &Rc<Client>) -> bool {
impl CritFixedRootCriterion<Client> for ClmMatchIsXwayland {
fn matches(&self, data: &Client) -> bool {
data.is_xwayland
}
}

View file

@ -3,18 +3,17 @@ use {
client::Client,
criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion},
},
std::rc::Rc,
uapi::c,
};
pub struct ClmMatchPid(pub c::pid_t);
impl CritRootCriterion<Rc<Client>> for ClmMatchPid {
fn matches(&self, data: &Rc<Client>) -> bool {
impl CritRootCriterion<Client> for ClmMatchPid {
fn matches(&self, data: &Client) -> bool {
data.pid_info.pid == self.0
}
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Rc<Client>, Self>> {
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Client, Self>> {
Some(&roots.pid)
}
}

View file

@ -1,14 +1,13 @@
use {
crate::{client::Client, criteria::crit_graph::CritFixedRootCriterion},
std::rc::Rc,
};
pub struct ClmMatchSandboxed(pub bool);
fixed_root_criterion!(ClmMatchSandboxed, sandboxed);
impl CritFixedRootCriterion<Rc<Client>> for ClmMatchSandboxed {
fn matches(&self, data: &Rc<Client>) -> bool {
impl CritFixedRootCriterion<Client> for ClmMatchSandboxed {
fn matches(&self, data: &Client) -> bool {
data.acceptor.sandboxed
}
}

View file

@ -7,15 +7,14 @@ use {
},
security_context_acceptor::AcceptorMetadata,
},
std::{marker::PhantomData, rc::Rc},
std::marker::PhantomData,
};
pub type ClmMatchString<T> = CritMatchString<Rc<Client>, T>;
pub type ClmMatchString<T> = CritMatchString<Client, T>;
pub type ClmMatchSandboxEngine = ClmMatchString<AcceptorMetadataAccess<SandboxEngineField>>;
pub type ClmMatchSandboxAppId = ClmMatchString<AcceptorMetadataAccess<SandboxAppIdField>>;
pub type ClmMatchSandboxInstanceId = ClmMatchString<AcceptorMetadataAccess<SandboxInstanceIdField>>;
pub type ClmMatchTag = ClmMatchString<AcceptorMetadataAccess<TagField>>;
pub type ClmMatchComm = ClmMatchString<CommAccess>;
pub type ClmMatchExe = ClmMatchString<ExeAccess>;
@ -33,13 +32,12 @@ trait AcceptorMetadataField: Sized + 'static {
pub struct SandboxEngineField;
pub struct SandboxAppIdField;
pub struct SandboxInstanceIdField;
pub struct TagField;
impl<T> StringAccess<Rc<Client>> for AcceptorMetadataAccess<T>
impl<T> StringAccess<Client> for AcceptorMetadataAccess<T>
where
T: AcceptorMetadataField,
{
fn with_string(data: &Rc<Client>, f: impl FnOnce(&str) -> bool) -> bool {
fn with_string(data: &Client, f: impl FnOnce(&str) -> bool) -> bool {
f(T::field(&data.acceptor).as_deref().unwrap_or_default())
}
@ -84,20 +82,8 @@ impl AcceptorMetadataField for SandboxInstanceIdField {
}
}
impl AcceptorMetadataField for TagField {
fn field(meta: &AcceptorMetadata) -> &Option<String> {
&meta.tag
}
fn nodes(
roots: &RootMatchers,
) -> &ClmRootMatcherMap<ClmMatchString<AcceptorMetadataAccess<Self>>> {
&roots.tag
}
}
impl StringAccess<Rc<Client>> for CommAccess {
fn with_string(data: &Rc<Client>, f: impl FnOnce(&str) -> bool) -> bool {
impl StringAccess<Client> for CommAccess {
fn with_string(data: &Client, f: impl FnOnce(&str) -> bool) -> bool {
f(&data.pid_info.comm)
}
@ -106,8 +92,8 @@ impl StringAccess<Rc<Client>> for CommAccess {
}
}
impl StringAccess<Rc<Client>> for ExeAccess {
fn with_string(data: &Rc<Client>, f: impl FnOnce(&str) -> bool) -> bool {
impl StringAccess<Client> for ExeAccess {
fn with_string(data: &Client, f: impl FnOnce(&str) -> bool) -> bool {
f(&data.pid_info.exe)
}

View file

@ -3,18 +3,17 @@ use {
client::Client,
criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion},
},
std::rc::Rc,
uapi::c,
};
pub struct ClmMatchUid(pub c::uid_t);
impl CritRootCriterion<Rc<Client>> for ClmMatchUid {
fn matches(&self, data: &Rc<Client>) -> bool {
impl CritRootCriterion<Client> for ClmMatchUid {
fn matches(&self, data: &Client) -> bool {
data.pid_info.uid == self.0
}
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Rc<Client>, Self>> {
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Client, Self>> {
Some(&roots.uid)
}
}

View file

@ -408,10 +408,10 @@ impl CritTarget for ToplevelData {
type Mgr = TlMatcherManager;
type RootMatchers = RootMatchers;
type LeafData = ToplevelIdentifier;
type Owner = Weak<dyn ToplevelNode>;
type Owner = WeakToplevelTargetOwner;
fn owner(&self) -> Self::Owner {
self.slf.clone()
WeakToplevelTargetOwner(self.slf.clone())
}
fn id(&self) -> Self::Id {
@ -427,20 +427,24 @@ impl CritTarget for ToplevelData {
}
}
impl CritTargetOwner for Rc<dyn ToplevelNode> {
pub struct ToplevelTargetOwner(Rc<dyn ToplevelNode>);
pub struct WeakToplevelTargetOwner(Weak<dyn ToplevelNode>);
impl CritTargetOwner for ToplevelTargetOwner {
type Target = ToplevelData;
fn data(&self) -> &Self::Target {
self.tl_data()
self.0.tl_data()
}
}
impl WeakCritTargetOwner for Weak<dyn ToplevelNode> {
impl WeakCritTargetOwner for WeakToplevelTargetOwner {
type Target = ToplevelData;
type Owner = Rc<dyn ToplevelNode>;
type Owner = ToplevelTargetOwner;
fn upgrade(&self) -> Option<Self::Owner> {
self.upgrade()
self.0.upgrade().map(ToplevelTargetOwner)
}
}

View file

@ -21,7 +21,7 @@ pub struct TlmMatchClient {
id: CritMatcherId,
state: Rc<State>,
node: Rc<ClmUpstreamNode>,
upstream: CritDownstreamData<Rc<Client>>,
upstream: CritDownstreamData<Client>,
downstream: CritUpstreamData<ToplevelData, ()>,
}
@ -73,8 +73,8 @@ impl CritUpstreamNodeBase<ToplevelData> for TlmMatchClient {
}
}
impl CritDownstream<Rc<Client>> for TlmMatchClient {
fn update_matched(self: Rc<Self>, target: &Rc<Client>, matched: bool) {
impl CritDownstream<Client> for TlmMatchClient {
fn update_matched(self: Rc<Self>, target: &Client, matched: bool) {
let handle = |data: &ToplevelData| {
let node = match matched {
true => self.downstream.get_or_create(data),

File diff suppressed because it is too large Load diff

View file

@ -1,137 +1 @@
use std::{
cmp::Ordering,
fmt::{Debug, Display, Formatter},
ops::{Add, AddAssign, Div, Mul, Sub, SubAssign},
};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
#[repr(transparent)]
pub struct Fixed(pub i32);
impl Fixed {
pub const EPSILON: Self = Fixed(1);
pub fn is_integer(self) -> bool {
self.0 & 255 == 0
}
pub fn from_f64(f: f64) -> Self {
Self((f * 256.0) as i32)
}
pub fn from_f32(f: f32) -> Self {
Self::from_f64(f as f64)
}
pub fn to_f64(self) -> f64 {
self.0 as f64 / 256.0
}
pub fn to_f32(self) -> f32 {
self.0 as f32 / 256.0
}
pub fn from_1616(i: i32) -> Self {
Self(i >> 8)
}
pub fn to_int(self) -> i32 {
self.0 >> 8
}
pub fn from_int(i: i32) -> Self {
Self(i << 8)
}
pub fn round_down(self) -> i32 {
self.0 >> 8
}
pub fn apply_fract(self, i: i32) -> Self {
Self((i << 8) | (self.0 & 255))
}
}
impl PartialEq<i32> for Fixed {
fn eq(&self, other: &i32) -> bool {
self.0 == *other << 8
}
}
impl PartialOrd<i32> for Fixed {
fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
self.0.partial_cmp(&(*other << 8))
}
}
impl Debug for Fixed {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.to_f64(), f)
}
}
impl Display for Fixed {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.to_f64(), f)
}
}
impl Sub for Fixed {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl Add for Fixed {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl Sub<i32> for Fixed {
type Output = Self;
fn sub(self, rhs: i32) -> Self::Output {
Self(self.0 - (rhs << 8))
}
}
impl Add<i32> for Fixed {
type Output = Self;
fn add(self, rhs: i32) -> Self::Output {
Self(self.0 + (rhs << 8))
}
}
impl Mul<i32> for Fixed {
type Output = Self;
fn mul(self, rhs: i32) -> Self::Output {
Self(self.0 * rhs)
}
}
impl Div<i32> for Fixed {
type Output = Self;
fn div(self, rhs: i32) -> Self::Output {
Self(self.0 / rhs)
}
}
impl AddAssign for Fixed {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
impl SubAssign for Fixed {
fn sub_assign(&mut self, rhs: Self) {
self.0 -= rhs.0;
}
}
pub use jay_units::fixed::*;

View file

@ -1,583 +1 @@
use {
crate::{
gfx_apis::gl::sys::{GL_BGRA_EXT, GL_RGBA, GL_RGBA8, GL_UNSIGNED_BYTE, GLenum, GLint},
pipewire::pw_pod::{
SPA_VIDEO_FORMAT_ABGR_210LE, SPA_VIDEO_FORMAT_ARGB_210LE, SPA_VIDEO_FORMAT_BGR,
SPA_VIDEO_FORMAT_BGR15, SPA_VIDEO_FORMAT_BGR16, SPA_VIDEO_FORMAT_BGRA,
SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_GRAY8, SPA_VIDEO_FORMAT_RGB,
SPA_VIDEO_FORMAT_RGB16, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_RGBx,
SPA_VIDEO_FORMAT_UNKNOWN, SPA_VIDEO_FORMAT_xBGR_210LE, SPA_VIDEO_FORMAT_xRGB_210LE,
SpaVideoFormat,
},
},
ahash::AHashMap,
ash::vk,
jay_config::video::Format as ConfigFormat,
std::{
fmt::{self, Debug, Write},
sync::LazyLock,
},
};
#[derive(Copy, Clone, Debug)]
pub struct FormatShmInfo {
pub gl_format: GLint,
pub gl_internal_format: GLenum,
pub gl_type: GLint,
}
#[derive(Copy, Clone, Debug)]
pub struct Format {
pub name: &'static str,
pub vk_format: vk::Format,
pub drm: u32,
pub wl_id: Option<u32>,
pub external_only_guess: bool,
pub has_alpha: bool,
pub pipewire: SpaVideoFormat,
pub opaque: Option<&'static Format>,
pub shm_info: Option<FormatShmInfo>,
pub config: ConfigFormat,
pub bpp: u32,
}
const fn default(config: ConfigFormat) -> Format {
Format {
name: "",
vk_format: vk::Format::UNDEFINED,
drm: 0,
wl_id: None,
external_only_guess: false,
has_alpha: false,
pipewire: SPA_VIDEO_FORMAT_UNKNOWN,
opaque: None,
shm_info: None,
config,
bpp: 4,
}
}
impl PartialEq for Format {
fn eq(&self, other: &Self) -> bool {
self.drm == other.drm
}
}
impl Eq for Format {}
static FORMATS_MAP: LazyLock<AHashMap<u32, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.drm, format).is_none());
}
map
});
static PW_FORMATS_MAP: LazyLock<AHashMap<SpaVideoFormat, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
if format.pipewire != SPA_VIDEO_FORMAT_UNKNOWN {
assert!(map.insert(format.pipewire, format).is_none());
}
}
map
});
static FORMATS_REFS: LazyLock<Vec<&'static Format>> = LazyLock::new(|| FORMATS.iter().collect());
static FORMATS_NAMES: LazyLock<AHashMap<&'static str, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.name, format).is_none());
}
map
});
static FORMATS_CONFIG: LazyLock<AHashMap<ConfigFormat, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.config, format).is_none());
}
map
});
#[test]
fn formats_dont_panic() {
formats();
pw_formats();
named_formats();
config_formats();
}
pub fn formats() -> &'static AHashMap<u32, &'static Format> {
&FORMATS_MAP
}
pub fn pw_formats() -> &'static AHashMap<SpaVideoFormat, &'static Format> {
&PW_FORMATS_MAP
}
pub fn ref_formats() -> &'static [&'static Format] {
&FORMATS_REFS
}
pub fn named_formats() -> &'static AHashMap<&'static str, &'static Format> {
&FORMATS_NAMES
}
pub fn config_formats() -> &'static AHashMap<ConfigFormat, &'static Format> {
&FORMATS_CONFIG
}
const fn fourcc_code(a: char, b: char, c: char, d: char) -> u32 {
(a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24)
}
#[expect(dead_code)]
pub fn debug(fourcc: u32) -> impl Debug {
fmt::from_fn(move |fmt| {
fmt.write_char(fourcc as u8 as char)?;
fmt.write_char((fourcc >> 8) as u8 as char)?;
fmt.write_char((fourcc >> 16) as u8 as char)?;
fmt.write_char((fourcc >> 24) as u8 as char)?;
Ok(())
})
}
const ARGB8888_ID: u32 = 0;
const ARGB8888_DRM: u32 = fourcc_code('A', 'R', '2', '4');
const XRGB8888_ID: u32 = 1;
const XRGB8888_DRM: u32 = fourcc_code('X', 'R', '2', '4');
pub fn map_wayland_format_id(id: u32) -> u32 {
match id {
ARGB8888_ID => ARGB8888_DRM,
XRGB8888_ID => XRGB8888_DRM,
_ => id,
}
}
pub static ARGB8888: &Format = &Format {
name: "argb8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_BGRA_EXT,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::B8G8R8A8_UNORM,
bpp: 4,
drm: ARGB8888_DRM,
wl_id: Some(ARGB8888_ID),
external_only_guess: false,
has_alpha: true,
pipewire: SPA_VIDEO_FORMAT_BGRA,
opaque: Some(XRGB8888),
config: ConfigFormat::ARGB8888,
};
pub static XRGB8888: &Format = &Format {
name: "xrgb8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_BGRA_EXT,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::B8G8R8A8_UNORM,
bpp: 4,
drm: XRGB8888_DRM,
wl_id: Some(XRGB8888_ID),
external_only_guess: false,
has_alpha: false,
pipewire: SPA_VIDEO_FORMAT_BGRx,
opaque: None,
config: ConfigFormat::XRGB8888,
};
static ABGR8888: &Format = &Format {
name: "abgr8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_RGBA,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::R8G8B8A8_UNORM,
bpp: 4,
drm: fourcc_code('A', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: true,
pipewire: SPA_VIDEO_FORMAT_RGBA,
opaque: Some(XBGR8888),
config: ConfigFormat::ABGR8888,
};
static XBGR8888: &Format = &Format {
name: "xbgr8888",
shm_info: Some(FormatShmInfo {
gl_format: GL_RGBA,
gl_internal_format: GL_RGBA8,
gl_type: GL_UNSIGNED_BYTE,
}),
vk_format: vk::Format::R8G8B8A8_UNORM,
bpp: 4,
drm: fourcc_code('X', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: false,
pipewire: SPA_VIDEO_FORMAT_RGBx,
opaque: None,
config: ConfigFormat::XBGR8888,
};
static R8: &Format = &Format {
name: "r8",
vk_format: vk::Format::R8_UNORM,
bpp: 1,
drm: fourcc_code('R', '8', ' ', ' '),
pipewire: SPA_VIDEO_FORMAT_GRAY8,
..default(ConfigFormat::R8)
};
static GR88: &Format = &Format {
name: "gr88",
vk_format: vk::Format::R8G8_UNORM,
bpp: 2,
drm: fourcc_code('G', 'R', '8', '8'),
..default(ConfigFormat::GR88)
};
static RGB888: &Format = &Format {
name: "rgb888",
vk_format: vk::Format::B8G8R8_UNORM,
bpp: 3,
drm: fourcc_code('R', 'G', '2', '4'),
pipewire: SPA_VIDEO_FORMAT_BGR,
..default(ConfigFormat::RGB888)
};
static BGR888: &Format = &Format {
name: "bgr888",
vk_format: vk::Format::R8G8B8_UNORM,
bpp: 3,
drm: fourcc_code('B', 'G', '2', '4'),
pipewire: SPA_VIDEO_FORMAT_RGB,
..default(ConfigFormat::BGR888)
};
static RGBA4444: &Format = &Format {
name: "rgba4444",
vk_format: vk::Format::R4G4B4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'A', '1', '2'),
has_alpha: true,
opaque: Some(RGBX4444),
..default(ConfigFormat::RGBA4444)
};
static RGBX4444: &Format = &Format {
name: "rgbx4444",
vk_format: vk::Format::R4G4B4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'X', '1', '2'),
..default(ConfigFormat::RGBX4444)
};
static BGRA4444: &Format = &Format {
name: "bgra4444",
vk_format: vk::Format::B4G4R4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'A', '1', '2'),
has_alpha: true,
opaque: Some(BGRX4444),
..default(ConfigFormat::BGRA4444)
};
static BGRX4444: &Format = &Format {
name: "bgrx4444",
vk_format: vk::Format::B4G4R4A4_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'X', '1', '2'),
..default(ConfigFormat::BGRX4444)
};
static RGB565: &Format = &Format {
name: "rgb565",
vk_format: vk::Format::R5G6B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'G', '1', '6'),
pipewire: SPA_VIDEO_FORMAT_BGR16,
..default(ConfigFormat::RGB565)
};
static BGR565: &Format = &Format {
name: "bgr565",
vk_format: vk::Format::B5G6R5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'G', '1', '6'),
pipewire: SPA_VIDEO_FORMAT_RGB16,
..default(ConfigFormat::BGR565)
};
static RGBA5551: &Format = &Format {
name: "rgba5551",
vk_format: vk::Format::R5G5B5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'A', '1', '5'),
has_alpha: true,
opaque: Some(RGBX5551),
..default(ConfigFormat::RGBA5551)
};
static RGBX5551: &Format = &Format {
name: "rgbx5551",
vk_format: vk::Format::R5G5B5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('R', 'X', '1', '5'),
..default(ConfigFormat::RGBX5551)
};
static BGRA5551: &Format = &Format {
name: "bgra5551",
vk_format: vk::Format::B5G5R5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'A', '1', '5'),
has_alpha: true,
opaque: Some(BGRX5551),
..default(ConfigFormat::BGRA5551)
};
static BGRX5551: &Format = &Format {
name: "bgrx5551",
vk_format: vk::Format::B5G5R5A1_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('B', 'X', '1', '5'),
..default(ConfigFormat::BGRX5551)
};
static ARGB1555: &Format = &Format {
name: "argb1555",
vk_format: vk::Format::A1R5G5B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('A', 'R', '1', '5'),
has_alpha: true,
opaque: Some(XRGB1555),
..default(ConfigFormat::ARGB1555)
};
static XRGB1555: &Format = &Format {
name: "xrgb1555",
vk_format: vk::Format::A1R5G5B5_UNORM_PACK16,
bpp: 2,
drm: fourcc_code('X', 'R', '1', '5'),
pipewire: SPA_VIDEO_FORMAT_BGR15,
..default(ConfigFormat::XRGB1555)
};
static ARGB2101010: &Format = &Format {
name: "argb2101010",
vk_format: vk::Format::A2R10G10B10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('A', 'R', '3', '0'),
has_alpha: true,
opaque: Some(XRGB2101010),
pipewire: SPA_VIDEO_FORMAT_ARGB_210LE,
..default(ConfigFormat::ARGB2101010)
};
static XRGB2101010: &Format = &Format {
name: "xrgb2101010",
vk_format: vk::Format::A2R10G10B10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('X', 'R', '3', '0'),
pipewire: SPA_VIDEO_FORMAT_xRGB_210LE,
..default(ConfigFormat::XRGB2101010)
};
static ABGR2101010: &Format = &Format {
name: "abgr2101010",
vk_format: vk::Format::A2B10G10R10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('A', 'B', '3', '0'),
has_alpha: true,
opaque: Some(XBGR2101010),
pipewire: SPA_VIDEO_FORMAT_ABGR_210LE,
..default(ConfigFormat::ABGR2101010)
};
static XBGR2101010: &Format = &Format {
name: "xbgr2101010",
vk_format: vk::Format::A2B10G10R10_UNORM_PACK32,
bpp: 4,
drm: fourcc_code('X', 'B', '3', '0'),
pipewire: SPA_VIDEO_FORMAT_xBGR_210LE,
..default(ConfigFormat::XBGR2101010)
};
static ABGR16161616: &Format = &Format {
name: "abgr16161616",
vk_format: vk::Format::R16G16B16A16_UNORM,
bpp: 8,
drm: fourcc_code('A', 'B', '4', '8'),
has_alpha: true,
opaque: Some(XBGR16161616),
..default(ConfigFormat::ABGR16161616)
};
static XBGR16161616: &Format = &Format {
name: "xbgr16161616",
vk_format: vk::Format::R16G16B16A16_UNORM,
bpp: 8,
drm: fourcc_code('X', 'B', '4', '8'),
..default(ConfigFormat::XBGR16161616)
};
pub static ABGR16161616F: &Format = &Format {
name: "abgr16161616f",
vk_format: vk::Format::R16G16B16A16_SFLOAT,
bpp: 8,
drm: fourcc_code('A', 'B', '4', 'H'),
has_alpha: true,
opaque: Some(XBGR16161616F),
..default(ConfigFormat::ABGR16161616F)
};
static XBGR16161616F: &Format = &Format {
name: "xbgr16161616f",
vk_format: vk::Format::R16G16B16A16_SFLOAT,
bpp: 8,
drm: fourcc_code('X', 'B', '4', 'H'),
..default(ConfigFormat::XBGR16161616F)
};
static BGR161616: &Format = &Format {
name: "bgr161616",
vk_format: vk::Format::R16G16B16_UNORM,
bpp: 6,
drm: fourcc_code('B', 'G', '4', '8'),
..default(ConfigFormat::BGR161616)
};
static R16F: &Format = &Format {
name: "r16f",
vk_format: vk::Format::R16_SFLOAT,
bpp: 2,
drm: fourcc_code('R', ' ', ' ', 'H'),
..default(ConfigFormat::R16F)
};
static GR1616F: &Format = &Format {
name: "gr1616f",
vk_format: vk::Format::R16G16_SFLOAT,
bpp: 4,
drm: fourcc_code('G', 'R', ' ', 'H'),
..default(ConfigFormat::GR1616F)
};
static BGR161616F: &Format = &Format {
name: "bgr161616f",
vk_format: vk::Format::R16G16B16_SFLOAT,
bpp: 6,
drm: fourcc_code('B', 'G', 'R', 'H'),
..default(ConfigFormat::BGR161616F)
};
static R32F: &Format = &Format {
name: "r32f",
vk_format: vk::Format::R32_SFLOAT,
bpp: 4,
drm: fourcc_code('R', ' ', ' ', 'F'),
..default(ConfigFormat::R32F)
};
static GR3232F: &Format = &Format {
name: "gr3232f",
vk_format: vk::Format::R32G32_SFLOAT,
bpp: 8,
drm: fourcc_code('G', 'R', ' ', 'F'),
..default(ConfigFormat::GR3232F)
};
static BGR323232F: &Format = &Format {
name: "bgr323232f",
vk_format: vk::Format::R32G32B32_SFLOAT,
bpp: 12,
drm: fourcc_code('B', 'G', 'R', 'F'),
..default(ConfigFormat::BGR323232F)
};
static ABGR32323232F: &Format = &Format {
name: "abgr32323232f",
vk_format: vk::Format::R32G32B32A32_SFLOAT,
bpp: 16,
drm: fourcc_code('A', 'B', '8', 'F'),
has_alpha: true,
..default(ConfigFormat::ABGR32323232F)
};
pub static FORMATS: &[Format] = &[
*ARGB8888,
*XRGB8888,
*ABGR8888,
*XBGR8888,
*R8,
*GR88,
*RGB888,
*BGR888,
#[cfg(target_endian = "little")]
*RGBA4444,
#[cfg(target_endian = "little")]
*RGBX4444,
#[cfg(target_endian = "little")]
*BGRA4444,
#[cfg(target_endian = "little")]
*BGRX4444,
#[cfg(target_endian = "little")]
*RGB565,
#[cfg(target_endian = "little")]
*BGR565,
#[cfg(target_endian = "little")]
*RGBA5551,
#[cfg(target_endian = "little")]
*RGBX5551,
#[cfg(target_endian = "little")]
*BGRA5551,
#[cfg(target_endian = "little")]
*BGRX5551,
#[cfg(target_endian = "little")]
*ARGB1555,
#[cfg(target_endian = "little")]
*XRGB1555,
#[cfg(target_endian = "little")]
*ARGB2101010,
#[cfg(target_endian = "little")]
*XRGB2101010,
#[cfg(target_endian = "little")]
*ABGR2101010,
#[cfg(target_endian = "little")]
*XBGR2101010,
#[cfg(target_endian = "little")]
*ABGR16161616,
#[cfg(target_endian = "little")]
*XBGR16161616,
#[cfg(target_endian = "little")]
*ABGR16161616F,
#[cfg(target_endian = "little")]
*XBGR16161616F,
#[cfg(target_endian = "little")]
*BGR161616,
#[cfg(target_endian = "little")]
*R16F,
#[cfg(target_endian = "little")]
*GR1616F,
#[cfg(target_endian = "little")]
*BGR161616F,
#[cfg(target_endian = "little")]
*R32F,
#[cfg(target_endian = "little")]
*GR3232F,
#[cfg(target_endian = "little")]
*BGR323232F,
#[cfg(target_endian = "little")]
*ABGR32323232F,
];
pub use jay_formats::*;

View file

@ -13,9 +13,6 @@ pub type GLuint = c::c_uint;
egl_transparent!(GLeglImageOES);
pub const GL_RGBA: GLint = 0x1908;
pub const GL_RGBA8: GLenum = 0x8058;
pub const GL_BGRA_EXT: GLint = 0x80E1;
pub const GL_CLAMP_TO_EDGE: GLint = 0x812F;
pub const GL_COLOR_ATTACHMENT0: GLenum = 0x8CE0;
pub const GL_COLOR_BUFFER_BIT: GLbitfield = 0x00004000;
@ -40,7 +37,6 @@ pub const GL_TEXTURE_WRAP_T: GLenum = 0x2803;
pub const GL_TRIANGLE_STRIP: GLenum = 0x0005;
pub const GL_TRIANGLES: GLenum = 0x0004;
pub const GL_UNPACK_ROW_LENGTH_EXT: GLenum = 0x0CF2;
pub const GL_UNSIGNED_BYTE: GLint = 0x1401;
pub const GL_VERTEX_SHADER: GLenum = 0x8B31;
pub const GL_BLEND: GLenum = 0x0BE2;
pub const GL_ONE: GLenum = 1;

View file

@ -2871,7 +2871,7 @@ impl ColorTransforms {
mut color: Color,
) -> Color {
if let Some(ct) = self.get_or_create(src, dst, intent) {
color = ct.matrix * color;
color = apply_color_matrix(ct.matrix, color);
};
color
}
@ -2896,6 +2896,25 @@ impl ColorTransforms {
}
}
fn apply_color_matrix(matrix: ColorMatrix, color: Color) -> Color {
let mut rgba = color.to_array(Eotf::Linear);
let a = rgba[3];
if a < 1.0 && a > 0.0 {
for c in &mut rgba[..3] {
*c /= a;
}
}
let [r, g, b] = matrix * [rgba[0] as f64, rgba[1] as f64, rgba[2] as f64];
Color::new(
Eotf::Linear,
AlphaMode::Straight,
r as f32,
g as f32,
b as f32,
a,
)
}
#[derive(Default)]
struct EotfArgsCache {
map: AHashMap<(EotfCacheKey, bool), EotfArg>,

View file

@ -156,7 +156,8 @@ pub trait Global: GlobalBase {
true
}
fn permitted(&self, caps: ClientCaps, xwayland: bool) -> bool {
caps.contains(self.required_caps()) && (xwayland || !self.xwayland_only())
let _ = caps;
xwayland || !self.xwayland_only()
}
fn not_permitted(&self, caps: ClientCaps, xwayland: bool) -> bool {
!self.permitted(caps, xwayland)
@ -345,7 +346,7 @@ impl Globals {
}
pub fn notify_all(&self, registry: &Rc<WlRegistry>) {
let caps = registry.client.effective_caps.get();
let caps = ClientCaps::all();
let xwayland = registry.client.is_xwayland;
let globals = self.registry.lock();
macro_rules! emit {
@ -429,7 +430,7 @@ impl Globals {
}
for client in state.clients.clients.borrow().values() {
let client = &client.data;
let caps = client.effective_caps.get();
let caps = ClientCaps::all();
let xwayland = client.is_xwayland;
for global in &singletons {
if global.permitted(caps, xwayland) {

View file

@ -13,7 +13,6 @@ pub mod head_management;
pub mod hyprland_focus_grab_manager_v1;
pub mod hyprland_focus_grab_v1;
pub mod ipc;
pub mod jay_acceptor_request;
pub mod jay_client_query;
pub mod jay_color_management;
pub mod jay_compositor;

View file

@ -1,60 +0,0 @@
use {
crate::{
client::{Client, ClientError},
leaks::Tracker,
object::{Object, Version},
utils::errorfmt::ErrorFmt,
wire::{JayAcceptorRequestId, jay_acceptor_request::*},
},
std::{error::Error, rc::Rc},
thiserror::Error,
};
pub struct JayAcceptorRequest {
pub id: JayAcceptorRequestId,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
pub version: Version,
}
impl JayAcceptorRequest {
pub fn send_done(&self, name: &str) {
self.client.event(Done {
self_id: self.id,
name,
});
}
pub fn send_failed(&self, err: impl Error) {
let msg = &ErrorFmt(err).to_string();
self.client.event(Failed {
self_id: self.id,
msg,
});
}
}
impl JayAcceptorRequestRequestHandler for JayAcceptorRequest {
type Error = JayAcceptorRequestError;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
}
object_base! {
self = JayAcceptorRequest;
version = self.version;
}
impl Object for JayAcceptorRequest {}
simple_add_obj!(JayAcceptorRequest);
#[derive(Debug, Error)]
pub enum JayAcceptorRequestError {
#[error(transparent)]
ClientError(Box<ClientError>),
}
efrom!(JayAcceptorRequestError, ClientError);

View file

@ -9,7 +9,7 @@ use {
jay_client_query::{
AddAll, AddId, Comm, Destroy, Done, End, Exe, Execute, IsXwayland,
JayClientQueryRequestHandler, Pid, SandboxAppId, SandboxEngine, SandboxInstanceId,
Sandboxed, Start, Tag, Uid,
Sandboxed, Start, Uid,
},
},
},
@ -26,8 +26,6 @@ pub struct JayClientQuery {
all: Cell<bool>,
}
const TAG_SINCE: Version = Version(25);
impl JayClientQuery {
pub fn new(client: &Rc<Client>, id: JayClientQueryId, version: Version) -> Self {
Self {
@ -97,14 +95,6 @@ impl JayClientQueryRequestHandler for JayClientQuery {
instance_id,
});
}
if self.version >= TAG_SINCE
&& let Some(tag) = &client.acceptor.tag
{
self.client.event(Tag {
self_id: self.id,
tag,
});
}
self.client.event(End { self_id: self.id });
};
if self.all.get() {

View file

@ -5,7 +5,6 @@ use {
compositor::LogLevel,
globals::{Global, GlobalName},
ifs::{
jay_acceptor_request::JayAcceptorRequest,
jay_client_query::JayClientQuery,
jay_color_management::JayColorManagement,
jay_ei_session_builder::JayEiSessionBuilder,
@ -503,27 +502,6 @@ impl JayCompositorRequestHandler for JayCompositor {
Ok(())
}
fn get_tagged_acceptor(
&self,
req: GetTaggedAcceptor<'_>,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let obj = Rc::new(JayAcceptorRequest {
id: req.id,
client: self.client.clone(),
tracker: Default::default(),
version: self.version,
});
track!(self.client, obj);
self.client.add_client_obj(&obj)?;
let state = &self.client.state;
match state.tagged_acceptors.get(state, req.tag) {
Ok(d) => obj.send_done(&d),
Err(e) => obj.send_failed(e),
}
Ok(())
}
fn get_sync_file_surface(
&self,
req: GetSyncFileSurface,

Some files were not shown because too many files have changed in this diff Show more