Skip to content

Add view properties#2835

Merged
ammen99 merged 6 commits intomasterfrom
properties
Oct 29, 2025
Merged

Add view properties#2835
ammen99 merged 6 commits intomasterfrom
properties

Conversation

@ammen99
Copy link
Member

@ammen99 ammen99 commented Sep 12, 2025

This PR adds a more streamlined way to attach custom data (integers, strings, etc.) to views under a given key - properties. They are meant to be used for setting metadata which is unrelated to the 'main' view state (geometry, minimized/tiled/fullscreen state, etc.), as well as custom properties that various plugins can use.

Planned properties for this first PR:

  • startup-x and startup-y to replace is_positioned in the view-mapped signal. This way Xwayland can indicate it has a custom position for the views and the place plugin should not move them. Similar could be set via IPC or any custom plugin.
  • A way to create IPC scripts which delay the map transaction, allowing IPC clients to set custom properties (and also set initial geometry, etc.)
  • A way to set properties via window-rules or similar (will be IPC only for now, PRs welcome for window-rules properties)
  • open-animation-type, open-animation-duration, close-animation, close-animation-duration for the animate plugin (fixes Per-window animations & effects / Window rules for animations & effects #1832)

Instead, we can use startup-x and startup-y properties
@ammen99 ammen99 marked this pull request as ready for review October 28, 2025 15:48
@ammen99
Copy link
Member Author

ammen99 commented Oct 28, 2025

In case anyone is wondering what this is all about:

  • This PR adds 'properties' which is nothing else but an extension of the existing custom data mechanism. With properties, we can add metadata of various types to a view (named integer/string/bool/etc properties). The most interesting feature of this PR are the animation properties, which allow us a very fine grained control over which views get what animations (you can even use it to have a random animation every time you open a new window, etc.).
  • Currently, IPC is completely async. For example, when a new view is opened, it is placed on the screen somewhere. Then we send view-mapped in the IPC client. The IPC client can then configure the view in any way it wants. However, this is not ideal as the IPC script cannot set the 'initial' geometry/etc. of the view. With this PR, IPC clients can request that Wayfire delays the map operation, until the IPC client is done setting it up.

Example IPC script showing how this works in practice:

import wayfire

s = wayfire.WayfireSocket()
# With pre-map events, Wayfire will delay mapping the view until we unblock it (or until transaction timeout happens)
s.watch(['view-pre-map'])
cnt = 0

while True:
    ev = s.read_next_event()
    # We get only view-pre-map events as those are the only events we subscribe to
    view = ev['view']
    if view['title'] == "simple-egl":
        # set spin animation for weston-simple-egl only
        s.set_view_property(view['id'], 'open-animation-type', 'spin')
    else:
        # rotate through different animations for each other view
        cnt = (cnt + 1) % 3
        if cnt == 0:
            # Use default animation settings
            pass
        if cnt == 1:
            # zoom animation for every third view, starting from the second view
            s.set_view_property(view['id'], 'open-animation-type', 'zoom')
        if cnt == 2:
            # fire animation with a custom duration for every third view, starting from the third view
            s.set_view_property(view['id'], 'open-animation-type', 'fire')
            s.set_view_property(view['id'], 'open-animation-duration', '1s circle')

    # Tell Wayfire we have finished setting up the view and it can be mapped now.
    s.unblock_view_map(view['id'])

Note: view-pre-map event is not subscribed by default when we do a socket.watch() call in pywayfire, because otherwise it would introduce a lot of delays on the Wayfire side, unless the client explicitly subscribes to view-pre-map, then it is responsible for calling unblock_view_map to avoid these delays.

@killown
Copy link
Contributor

killown commented Oct 28, 2025

In case anyone is wondering what this is all about:

* This PR adds 'properties' which is nothing else but an extension of the existing custom data mechanism. With properties, we can add metadata of various types to a view (named integer/string/bool/etc properties). The most interesting feature of this PR are the animation properties, which allow us a very fine grained control over which views get what animations (you can even use it to have a random animation every time you open a new window, etc.).

* Currently, IPC is completely async. For example, when a new view is opened, it is placed on the screen somewhere. Then we send view-mapped in the IPC client. The IPC client can then configure the view in any way it wants. However, this is not ideal as the IPC script cannot set the 'initial' geometry/etc. of the view. With this PR, IPC clients can request that Wayfire delays the map operation, until the IPC client is done setting it up.

Example IPC script showing how this works in practice:

import wayfire

s = wayfire.WayfireSocket()
# With pre-map events, Wayfire will delay mapping the view until we unblock it (or until transaction timeout happens)
s.watch(['view-pre-map'])
cnt = 0

while True:
    ev = s.read_next_event()
    # We get only view-pre-map events as those are the only events we subscribe to
    view = ev['view']
    if view['title'] == "simple-egl":
        # set spin animation for weston-simple-egl only
        s.set_view_property(view['id'], 'open-animation-type', 'spin')
    else:
        # rotate through different animations for each other view
        cnt = (cnt + 1) % 3
        if cnt == 0:
            # Use default animation settings
            pass
        if cnt == 1:
            # zoom animation for every third view, starting from the second view
            s.set_view_property(view['id'], 'open-animation-type', 'zoom')
        if cnt == 2:
            # fire animation with a custom duration for every third view, starting from the third view
            s.set_view_property(view['id'], 'open-animation-type', 'fire')
            s.set_view_property(view['id'], 'open-animation-duration', '1s circle')

    # Tell Wayfire we have finished setting up the view and it can be mapped now.
    s.unblock_view_map(view['id'])

Note: view-pre-map event is not subscribed by default when we do a socket.watch() call in pywayfire, because otherwise it would introduce a lot of delays on the Wayfire side, unless the client explicitly subscribes to view-pre-map, then it is responsible for calling unblock_view_map to avoid these delays.

how the custom property works?

In [13]: sock.set_view_property(12, "sticky", True)
Out[13]: {'result': 'ok'}

In [14]: sock.get_view(id)
Out[14]: 
{'id': 12,
 'pid': 103832,
 'title': 'IPython: home/neo',
 'app-id': 'kitty',
 'base-geometry': {'x': 0, 'y': 36, 'width': 1920, 'height': 1044},
 'parent': -1,
 'geometry': {'x': 0, 'y': 36, 'width': 1920, 'height': 1044},
 'bbox': {'x': 0, 'y': 36, 'width': 1920, 'height': 1044},
 'output-id': 1,
 'output-name': 'DP-1',
 'last-focus-timestamp': 11698151762696,
 'role': 'toplevel',
 'mapped': True,
 'layer': 'workspace',
 'tiled-edges': 15,
 'fullscreen': False,
 'minimized': False,
 'activated': True,
 'sticky': False,
 'wset-index': 1,
 'min-size': {'width': 0, 'height': 0},
 'max-size': {'width': 0, 'height': 0},
 'focusable': True,
 'type': 'toplevel'}

In [15]: sock.set_view_property(12, "hello", True)
Out[15]: {'result': 'ok'}

In [16]: sock.get_view(id)
Out[16]: 
{'id': 12,
 'pid': 103832,
 'title': 'IPython: home/neo',
 'app-id': 'kitty',
 'base-geometry': {'x': 0, 'y': 36, 'width': 1920, 'height': 1044},
 'parent': -1,
 'geometry': {'x': 0, 'y': 36, 'width': 1920, 'height': 1044},
 'bbox': {'x': 0, 'y': 36, 'width': 1920, 'height': 1044},
 'output-id': 1,
 'output-name': 'DP-1',
 'last-focus-timestamp': 11698151762696,
 'role': 'toplevel',
 'mapped': True,
 'layer': 'workspace',
 'tiled-edges': 15,
 'fullscreen': False,
 'minimized': False,
 'activated': True,
 'sticky': False,
 'wset-index': 1,
 'min-size': {'width': 0, 'height': 0},
 'max-size': {'width': 0, 'height': 0},
 'focusable': True,
 'type': 'toplevel'}

@ammen99
Copy link
Member Author

ammen99 commented Oct 28, 2025

The properties are not visible over IPC, do you need that? I could add it but the issue is that properties can store any type of value, so you can't easily serialize them to json.

@ammen99
Copy link
Member Author

ammen99 commented Oct 28, 2025

Also it would be easy to add a query for a particular property (for example it is easy to query for a property with a name 'open-animation-type' and value type 'string'), if that is enough for your use-case.

@ammen99
Copy link
Member Author

ammen99 commented Oct 28, 2025

Ah, also @killown, properties is not the same as what you see in the json list. What you see in the json currently are fixed parts of the definition of a view, whereas properties are like random attached metadata, not part of the view as seen in the json description.

@killown
Copy link
Contributor

killown commented Oct 28, 2025

Also it would be easy to add a query for a particular property (for example it is easy to query for a property with a name 'open-animation-type' and value type 'string'), if that is enough for your use-case.

My use case would be something like view['icon'] = "some_icon", so the IPC broadcast sends this view data to subscribers, eliminating the need to query for an icon. Not only that, I am sure I would have much more use cases, but this one I picked randomly

Ah, also @killown, properties is not the same as what you see in the json list. What you see in the json currently are fixed parts of the definition of a view, whereas properties are like random attached metadata, not part of the view as seen in the json description.

if I understood well, your PR is not specifically about custom data yet xd

@ammen99
Copy link
Member Author

ammen99 commented Oct 28, 2025

Ah, also @killown, properties is not the same as what you see in the json list. What you see in the json currently are fixed parts of the definition of a view, whereas properties are like random attached metadata, not part of the view as seen in the json description.

if I understood well, your PR is not specifically about custom data yet xd

In the C++ API, you can set a property as a combination of name and any value type. Just the issue is that if we expose this over IPC, we are limited to what JSON can provide.

My use case would be something like view['icon'] = "some_icon", so the IPC broadcast sends this view data to subscribers, eliminating the need to query for an icon. Not only that, I am sure I would have much more use cases, but this one I picked randomly

Transferring icons over json can be quite inefficient, that would essentially be binary data which we pack directly into json as a string. It would also not make sense to broadcast the icon on every IPC query, as most clients wouldn't care about the icon. However I assume we can add a query like get_view_property, then on the ipc-rules side, we can check whether there is a property like this with the common types (int/string/bool/double), if yes, then we return that. Do you think this would be enough for you?

@killown
Copy link
Contributor

killown commented Oct 28, 2025

Ah, also @killown, properties is not the same as what you see in the json list. What you see in the json currently are fixed parts of the definition of a view, whereas properties are like random attached metadata, not part of the view as seen in the json description.

if I understood well, your PR is not specifically about custom data yet xd

In the C++ API, you can set a property as a combination of name and any value type. Just the issue is that if we expose this over IPC, we are limited to what JSON can provide.

My use case would be something like view['icon'] = "some_icon", so the IPC broadcast sends this view data to subscribers, eliminating the need to query for an icon. Not only that, I am sure I would have much more use cases, but this one I picked randomly

Transferring icons over json can be quite inefficient, that would essentially be binary data which we pack directly into json as a string. It would also not make sense to broadcast the icon on every IPC query, as most clients wouldn't care about the icon. However I assume we can add a query like get_view_property, then on the ipc-rules side, we can check whether there is a property like this with the common types (int/string/bool/double), if yes, then we return that. Do you think this would be enough for you?

As we discussed earlier in matrix, no worries, I was just curious how the custom data from your PR works. All the IPC features Wayfire currently offers are more than enough!

@ammen99 ammen99 merged commit a779562 into master Oct 29, 2025
8 checks passed
@ammen99 ammen99 deleted the properties branch October 29, 2025 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-window animations & effects / Window rules for animations & effects

2 participants

Comments