jay-compositor 1.15.0

The Jay compositor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# Exposing Internals in the Debugfs

This chapter is for Jay developers. It describes how to make compositor state
visible in the filesystem that `jay debugfs` mounts (see
[`jay debugfs`](cli.md#jay-debugfs)).

The filesystem is not written by hand. Each directory is declared in a small
language at the top of a Rust file. A code generator turns the declaration into
a Rust trait, and the compositor state is exposed by implementing that trait.

## Workflow

1. Create a file whose name ends in `_g_fuse.rs` somewhere below `src/`, and
   add it as a module of its parent. By convention the file is called
   `<name>_dfs_g_fuse.rs` and sits in the directory of the type it describes.
   The file must start with a `/* ... */` comment that contains the
   declarations.
2. Run the generator:

   ```shell
   ~$ ./codegen/codegen.sh
   ```

   It requires a nightly `rustfmt`. For every `_g_fuse.rs` file it
   - appends, or updates, an `include!` block between the markers
     `// FUSE GENERATED START` and `// FUSE GENERATED STOP`,
   - writes a description of the directories to
     `build/fuse/generated/m_<hash>.rs`, where `<hash>` is derived from the
     path of the file,
   - updates the list of all such descriptions in `build/fuse/generated.rs`.

   Commit all of these files. Descriptions of deleted `_g_fuse.rs` files are
   removed.
3. `build.rs` reads the descriptions and writes the Rust code into `OUT_DIR`.
   The `include!` block pulls it into the file as a module named `generated`.
4. Implement the generated traits in the same file.

Run the generator again after every change to the declarations.

A minimal file looks like this:

```rust
/*
dir wl_buffer {
    @inherit dfs_object,
    width: reg,
    color: reg (opt),
}
 */
use crate::ifs::wl_buffer::WlBuffer;
use crate::ifs::wl_buffer::wl_buffer_dfs_g_fuse::generated::wl_buffer;
use crate::utils::str_fmt::StrCtx;
use crate::utils::str_fmt::StrFmt;

impl wl_buffer::Dir for WlBuffer {
    fn read_width(&self, buf: &mut String, ctx: &StrCtx<'_>) {
        self.width.str_fmt(buf, ctx);
    }

    fn has_color(&self) -> bool {
        self.color.is_some()
    }

    fn read_color(&self, buf: &mut String, ctx: &StrCtx<'_>) {
        if let Some(v) = &self.color {
            v.str_fmt(buf, ctx);
        }
    }
}

// FUSE GENERATED START
include!(concat!(
    env!("OUT_DIR"),
    "/fuse/m_8b91a6dac50768623cab19ffe8ee101561f7aa63e020634152381b318f7e2326.rs",
));
// FUSE GENERATED STOP
```

## Declarations

A file contains any number of directories:

```
dir <name> (<attributes>) {
    <entry>,
    <entry>,
    @inherit <other dir>,
}
```

The attribute list is optional. Directory attributes:

`abstract`
: The directory is never shown on its own. Only its trait is generated. It is
  used as the target of `@inherit`.

`global`
: The directory is generated into `crate::utils::fuse::fuse_globals` instead of
  the module of the file, so that other files can inherit from it. Global
  directory names must be unique across the crate. The global abstract
  directories `dfs_object`, `dfs_node`, `dfs_toplevel_node`, `dfs_global`, and
  `dfs_backend` contain the files shared by all Wayland objects, tree nodes,
  toplevels, globals, and backends.

Each entry has the form `<name>: <type> (<attributes>)`. The name is the file
name and must be unique in the directory, including inherited entries. The
types are:

`reg`
: A regular file. Its contents are produced by `read_<name>`.

`link`
: A symbolic link. Its target is produced by `readlink_<name>`.

`view`
: A subdirectory, or any other inode, whose behavior is defined by a type that
  implements `FuseView`. The type is chosen in the implementation through the
  associated type `View<Name>`.

`custom`
: An arbitrary inode returned by `get_<name>`. This is used to embed a directory
  that is implemented elsewhere, for example the directory of a tree node.

Entry attributes:

`opt`
: The entry does not always exist.

`other`
: Only for `view`. The view is implemented by a different object than the
  directory itself. That object is returned by `get_<name>`.

`key = <n>`
: Only for `view` and `custom`. The inode is created with the fixed key `n`.
  See [Keys](#keys).

`no_timeout`
: Overrides the entry timeout. See [Entry Timeouts](#entry-timeouts).

`@inherit <dir>` adds all entries of another directory, which is either defined
in the same file or global.

## The Generated Trait

For each directory `<dir>` the generator creates a module
`generated::<dir>`, which contains a trait `Dir` and, unless the directory is
abstract, a type `View` that implements `FuseView<T>` for every `T: Dir`.
`Dir` requires `GetLiveness` (see [Liveness](#liveness)). If the directory
inherits from other directories, their traits are supertraits of `Dir` and
their entries do not appear in `Dir` itself.

The methods depend on the type and attributes of each entry:

`reg`
: `fn read_<name>(&self, buf: &mut String, ctx: &StrCtx<'_>)`

`link`
: `fn readlink_<name>(&self, depth: u64, buf: &mut String)`

`view`
: `type View<Name>: FuseView<Self>`

  Without `key`, additionally `fn keyof_<name>(&self, key: u64) -> u64`.

`view (other)`
: `type Base<Name>: GetLiveness`, `type View<Name>: FuseView<Self::Base<Name>>`,
  and `fn get_<name>(self: &Rc<Self>, key: u64) -> Rc<Self::Base<Name>>`

  Without `key`, additionally `fn keyof_<name>(&self, key: u64) -> u64`.

`custom`
: `fn get_<name>(self: &Rc<Self>, key: u64) -> FuseInodeWithKey`

`custom (key = n)`
: `fn get_<name>(self: &Rc<Self>, key: u64) -> Rc<dyn FuseInode>`

`<Name>` is the entry name in camel case.

`opt` adds the following:

- For `reg` and `link`: `fn has_<name>(&self) -> bool`.
- For `view` without `other`: `fn has_<name>(&self, key: u64) -> bool`.
- For `view (other)` and `custom`: `get_<name>` returns an `Option`.

The entry exists if `has_<name>` returns true or `get_<name>` returns `Some`.
`read_<name>` and `readlink_<name>` can still be called after `has_<name>` has
returned false, because the state can change between the two calls. They must
then write nothing and must not panic.

A `view` without `other` is implemented by the same object as the directory.
This is how a directory is split into subdirectories. For example, the `pending`
subdirectory of a surface is declared as `pending: view (key = 0)` and is
implemented by `WlSurface` itself:

```rust
impl wl_surface::Dir for WlSurface {
    type ViewPending = pending::View;
    // ...
}

impl pending::Dir for WlSurface {
    // ...
}
```

## Keys

Every inode consists of an object and a 64-bit key. The key lets one object back
several inodes. For example, the directory that describes a Wayland object
without dedicated debugfs support is implemented by the `Client` and uses the
object ID as the key.

Most directories ignore the key and are created with key 0. Entries with
`key = <n>` use that key. For other `view` entries, `keyof_<name>` maps the key
of the directory to the key of the child. The `key` argument of the generated
methods is the key of the directory.

## Entry Timeouts

The kernel caches the result of looking up a name. The generator sets the cache
duration from the entry:

- Entries that always exist, that is `reg`, `link`, and `view` with a fixed key
  and without `opt` or `other`, are cached without a time limit.
- All other entries are cached for 100 milliseconds.

`no_timeout` makes an entry cached without a time limit, even if it is
optional. Use it when the existence of the entry is decided when the object is
created and never changes. `wl_buffer` uses it for `dmabuf_device`, which
exists only for buffers created from a dmabuf.

The contents of files are not cached.

## Liveness

The filesystem identifies an inode by the address of its object, the Rust type
of its view, its key, and its parent and name. The inode cache holds a `Weak`
reference to the object, which keeps the address reserved, so it cannot be
reused while the inode is cached.

Dead inodes are found by a separate thread, which cannot access the `Weak`.
Instead, every object that backs an inode has a `Liveness`, which is updated
atomically when the object is dropped. The thread uses it to find inodes whose
`Weak` can no longer be upgraded. The compositor thread then removes those
inodes from the cache and drops the `Weak`.

Add a field and derive `GetLiveness`:

```rust
#[derive(GetLiveness)]
pub struct WlBuffer {
    // ...
    liveness: Liveness,
}
```

Initialize it with `Default::default()`. If the field has a different name,
annotate it with `#[liveness]`.

## Connecting a Directory

A directory is visible only if an existing directory refers to it. To turn an
`Rc<T>` into an inode, wrap it in a view:

```rust
use crate::utils::fuse::fuse_inode::FuseInodeExt;
use crate::utils::type_view::TypeViewExt1;

self.tv_wrap_rc::<wl_buffer::View>().without_key()
```

This returns a `FuseInodeWithKey`, which is what `custom` entries and several
of the collection views below expect.

Wayland objects appear in `clients/<client>/objects/<id>`. By default the
`Object` derive shows a directory with the ID, interface, and version. To
replace it, add `#[debugfs]` to the struct and implement `ObjectDebugfs`:

```rust
#[derive(Object, GetLiveness)]
#[debugfs]
pub struct WlBuffer {
    // ...
}

impl ObjectDebugfs for WlBuffer {
    fn object_debugfs(self: Rc<Self>, _client: &Rc<Client>) -> FuseInodeWithKey {
        self.debugfs()
    }
}
```

where `debugfs` is a function in the `_g_fuse.rs` file that performs the
wrapping shown above. The directory should inherit `dfs_object`, which is
implemented for all objects.

Tree nodes are connected through `Node::node_debugfs`. Directories of other
types need a `view (other)` or `custom` entry in a directory that is already
connected, for example in the `root` directory in
`src/state/state_dfs_g_fuse.rs`.

## Links

Link targets are relative paths. `depth` is the depth of the link below the root
of the filesystem, so a target that starts with `depth - 1` times `../` is
relative to the root. `src/dfs/dfs_helpers.rs` contains functions that write
such targets:

- `format_object_link` links to a Wayland object of a client.
- `format_client_link`, `format_output_link`, and `format_workspace_link` link
  to a client, an output, or a workspace.
- `write_root_link` writes only the prefix.

```rust
fn readlink_viewport(&self, depth: u64, buf: &mut String) {
    if let Some(obj) = self.viewporter.get() {
        format_object_link(buf, depth, self.client.id, obj.id());
    }
}
```

Prefer a link over a file that contains a flag or an ID when the target has a
directory of its own. An optional link carries the same information as a
`has_*` file.

## Formatting Values

`read_*` methods write through the `StrFmt` trait. It supports two formats:
human-readable text, which is used when the filesystem is mounted, and JSON,
which is used by `jay --json debugfs snapshot`. The format is available as
`ctx.fmt`. In the human-readable format a newline is appended to every file
automatically.

`StrFmt` is implemented for integers, floats, `bool`, `str`, `Option`, slices,
arrays, pairs, `Rc`, `Box`, and many compositor types. An `Option`
that is `None` is written as `nil` or `null`.

Enums implement `StaticText` to give each variant a name, and `read_*` writes
`self.value.get().text()`. Names are lower case with underscores.

Structs derive `StrFmt`. Each field is written as `name: value` in the
human-readable format and as a JSON object otherwise. Two attributes change the
output:

- `#[str_fmt(skip)]` on a field omits it.
- `#[str_fmt(transparent)]` on the struct writes the only field that is not
  skipped as if it were the whole struct. This is used for newtypes.

```rust
#[derive(StrFmt)]
#[str_fmt(transparent)]
pub struct ColorMatrix<To = Local, From = Local>(
    pub [[F64; 4]; 3],
    #[str_fmt(skip)] PhantomData<(To, From)>,
);
```

To write values that do not exist as a struct, such as a width and a height
stored separately, declare a local struct that derives `StrFmt` and write an
instance of it. Do not implement `StrFmt` by hand for structs.

## Collection Views

`src/utils/fuse/fuse_views.rs` and `src/dfs/dfs_helpers.rs` contain `FuseView`
implementations that are used as the `View<Name>` type of `view` entries. Each
of them is configured by a marker type `V` that implements a trait:

`FuseReg<V>`
: A regular file. `V` implements `FuseRegView`.

`FuseLink<V>`
: A symbolic link. `V` implements `FuseLinkView`.

`IterDir<V>`
: One entry per element produced by `iter`, looked up by `get`. `V` implements
  `IterDirView`.

`IterDirKeyed<V>`
: Like `IterDir`, but each element has its own key. `V` implements
  `IterDirKeyedView`.

`IterDirDyn<V>`
: Like `IterDir`, but each element is an arbitrary `FuseInodeWithKey`. `V`
  implements `IterDirDynView`.

`CopyHashMapDir<V>`, `CopyHashMapDir2<V>`
: The values of a `CopyHashMap`. `V` implements `CopyHashMapDirView` or
  `CopyHashMapDir2View`.

`HashMapDir<V>`
: The values of a `HashMap`. `V` implements `HashMapDirView`.

`BinarySearchMapDir<V>`
: The values of a binary search map. `V` implements `BinarySearchMapDirView`.

`DevTDir<V>`
: Devices named `major:minor`. `V` implements `DevTDirView`.

`DfsObjectLinkDir<V>`, `DfsCopyHashMapObjectLinkDir<V>`
: Links to Wayland objects of one client. `V` implements `DfsObjectLinkDirView`
  or `DfsCopyHashMapObjectLinkDirView`.

`ClientObjectDir<V>`
: Links to Wayland objects of several clients, named `client:object`. `V`
  implements `ClientObjectDirView`.

The elements of these collections must implement `GetLiveness`.

A generated `View` can also be used as the element view of a collection. For
example, `IterDirKeyed<ColorDescriptions>` in `src/state/state_dfs_g_fuse.rs`
lists all color descriptions as regular files named after their IDs.

## Stability

The layout of the filesystem is not a stable interface. Entries can be renamed
or removed freely.