Skip to content

global plugins#1674

Merged
ammen99 merged 12 commits intomasterfrom
global-plugins
Jan 7, 2023
Merged

global plugins#1674
ammen99 merged 12 commits intomasterfrom
global-plugins

Conversation

@ammen99
Copy link
Member

@ammen99 ammen99 commented Jan 4, 2023

Currently, plugins necessarily have one instance per output. While this makes sense for many plugins, it is an artificial restriction which is sometimes bypassed with the singleton plugin class. Nonetheless, it would make far more sense to have a single instance of each plugin, and then have the plugins create per-output objects if they need to do so. This makes a lot of sense especially with plugins like IPC, idle or oswitch. Not to mention that things like bindings are currently artifically constrained to a single output, but they should actually be global state, just like every other input state.

This PR aims to do the following:

  • Remove the old grab interfaces, which are per-output. Instead, plugins can use the input-grab helper.
  • Make plugins have one global instance instead of an instance per-output. There should also be a helper for plugins which prefer the old behavior (to begin with, that include basically all plugins).
  • Revamp bindings. They should work globally and not just per-output.
  • Make Wayfire's default bindings optional #1510

Fixes #1320

This commit removes the old input grab interface. In its place, plugins
can use the input-grab helper in plugins/common, or reimplement that
logic if they need to customize it in any way.

Fixes #1320.
Each plugin now gets one instance by default. Plugins that want to have per-output tracking can do so by using the per-output-plugin helpers. The API remains almost the same as in previous versions.
@ammen99 ammen99 marked this pull request as ready for review January 5, 2023 18:15
@ammen99
Copy link
Member Author

ammen99 commented Jan 5, 2023

In this PR I have refactored the existing IPC used for tests in a more general-purpose IPC framework. A demo plugin utilizing it is the demo-ipc.cpp. Unfortunately, there is nothing too exciting for users as of yet. Anyway, how it works:

  1. There is an ipc method repository which contains callbacks for various IPC calls which can be made. Multiple plugins can register callbacks here. The idea is that eventually most plugins would provide their actions (currently activator callbacks) via this IPC method repository.
  2. The ipc plugin itself creates the ipc socket and manages incoming connections.

Currently, there is a demo ipc plugin which is a proof-of-concept of how this works together. It provides just 4 IPC calls - watch (register the IPC client so that it can get view-mapped events over IPC), get view info, get output info, set view geometry. The IPC client in itself, together with the message parsing logic (in python, but could be any other language) can be found below. What it does is center each view when it becomes mapped, essentially place's center mode (but it also sets the view size to half the output size, even though that can obviously be changed by everybody).

import socket
import json as js

def get_msg_template():
    # Create generic message template
    message = {}
    message["data"] = {}
    return message

class WayfireSocket:
    def __init__(self, socket_name: str):
        self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        self.client.connect(socket_name)

    def read_exact(self, n):
        response = bytes()
        while n > 0:
            read_this_time = self.client.recv(n)
            if not read_this_time:
                raise Exception("Failed to read anything from the socket!")
            n -= len(read_this_time)
            response += read_this_time

        return response

    def read_message(self):
        rlen = int.from_bytes(self.read_exact(4), byteorder="little")
        response_message = self.read_exact(rlen)
        return js.loads(response_message)

    def send_json(self, msg):
        data = js.dumps(msg).encode('utf8')
        header = len(data).to_bytes(4, byteorder="little")
        self.client.send(header)
        self.client.send(data)
        return self.read_message()

    def watch(self):
        message = get_msg_template()
        message["method"] = "demo-ipc/watch"
        return self.send_json(message)

    def get_view_info(self, id):
        message = get_msg_template()
        message["method"] = "demo-ipc/view-info"
        message["data"]["id"] = id
        return self.send_json(message)

    def get_output_info(self, id):
        message = get_msg_template()
        message["method"] = "demo-ipc/output-info"
        message["data"]["id"] = id
        return self.send_json(message)

    def set_view_geometry(self, id, x, y, w, h):
        message = get_msg_template()
        message["method"] = "demo-ipc/view-set-geometry"
        message["data"]["id"] = id
        message["data"]["geometry"] = {}
        message["data"]["geometry"]["x"] = x
        message["data"]["geometry"]["y"] = y
        message["data"]["geometry"]["width"] = w
        message["data"]["geometry"]["height"] = h
        return self.send_json(message)

inbound_socket = WayfireSocket('/tmp/wayfire.socket')
outbound_socket = WayfireSocket('/tmp/wayfire.socket')

inbound_socket.watch()
while True:
    msg = inbound_socket.read_message()

    output = msg["view"]["output"]
    output_info = outbound_socket.get_output_info(output)
    print(output_info)

    geometry = {}

    w = output_info["info"]["geometry"]["width"]
    h = output_info["info"]["geometry"]["height"]
    print(outbound_socket.set_view_geometry(msg["view"]["id"], w // 4, h // 4, w // 2, h // 2))

The long-term plans are to add many more commands in a dedicated IPC plugin, the current one is just a demo to ensure that what I imagine is feasible.

PS. The ipc plugin by default sets the Wayfire socket to /tmp/wayfire-<wayland display name>.socket, however, that can be overridden with the _WAYFIRE_SOCKET env var before starting Wayfire. The python test client currently hardcodes the expected /tmp/wayfire.socket socket path.

PS 2: The demo client currently uses 2 sockets, one for listening to events, one for sending commands to Wayfire. This is done to avoid mixing replies and events from Wayfire.

@ammen99
Copy link
Member Author

ammen99 commented Jan 5, 2023

Porting guide for plugins:

  • The grab interfaces have been removed. Input grabs can be done with the input grab helper from wayfire/plugins/common/input-grab.hpp. See bedbeb7 for more details.
  • Plugins now have single instance (as opposed to a per-output instance) by default. To get the old behavior, plugins should now derive from per_output_plugin_instance_t defined in wayfire/per-output-plugin.hpp and then use DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t<your_plugin_class_here>). See fe3a36f for more details.
  • The grab_interface field was completely removed from the plugin interface. Plugins that need to use activation (e. output->activate_plugin()) can instead create a wf::plugin_activation_data_t member and use that struct to activate themselves. For example fe3a36f#diff-ec11d3b1cb8b17c685647070cfdccd7f3003b59b99b3f56f75e2f7aabfe500bfR122-R127 and the subsequent rename 11a5e45

@ammen99 ammen99 merged commit b8b8486 into master Jan 7, 2023
@ammen99 ammen99 deleted the global-plugins branch January 7, 2023 07:28
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.

Remove/refactor grab APIs

1 participant

Comments