#!/usr/bin/env python3
#
# Fibonacci spiral layout for river, implemented in simple python. Reading this
# code should help you get a basic understanding of how to use river-layout to
# create a basic layout generator.
#
# This depends on pywayland: https://github.com/flacjacket/pywayland/
#
# Q: Wow, this looks complicated!
# A: For simple layouts, you really only need to care about what's in the
#    layout_handle_layout_demand() function. And the rest isn't as complicated
#    as it looks.
#
# Q: The script runs but nothing happens! How can I see this layout?
# A: Once started, to set this layout as default use the command:
#    riverctl default-layout layout.py

import mmap
import time
from pywayland.client import Display
from pywayland.protocol.wayland import WlOutput
try:
    from pywayland.protocol.river_layout_v3 import RiverLayoutManagerV3
except:
    river_layout_help = """
    Your pywayland package does not have bindings for river-layout-v3.
    You can generate the bindings with the following command:
         python3 -m pywayland.scanner -i /usr/share/wayland/wayland.xml river-layout-v3.xml
    It is recommended to use a virtual environment to avoid modifying your
    system-wide python installation, See: https://docs.python.org/3/library/venv.html
    """
    print(river_layout_help)
    quit()

layout_manager = None
outputs = []
loop = True

def layout_handle_layout_demand(layout, view_count, usable_w, usable_h, tags, serial):
    x = 0
    y = 0
    w = usable_w
    h = usable_h
    for i in range(0, view_count - 1):
        if i % 2 == 0:
            w //= 2
            if i % 4 == 2:
                layout.push_view_dimensions(x + w, y, w, h, serial)
            else:
                layout.push_view_dimensions(x, y, w, h, serial)
                x += w
        else:
            h //= 2
            if i % 4 == 3:
                layout.push_view_dimensions(x, y + h, w, h, serial)
            else:
                layout.push_view_dimensions(x, y, w, h, serial)
                y += h
    layout.push_view_dimensions(x, y, w, h, serial)

    # Committing the layout means telling the server that your code is done
    # laying out windows. Make sure you have pushed exactly the right amount of
    # view dimensions, a mismatch is a fatal protocol error.
    #
    # You also have to provide a layout name. This is a user facing string that
    # the server can forward to status bars. You can use it to tell the user
    # which layout is currently in use. You could also add some status
    # information status information about your layout, which is what we do here.
    layout.commit(f"{view_count} windows laid out by python", serial)

def layout_handle_namespace_in_use(layout):
    # Oh no, the namespace we choose is already used by another client! All we
    # can do now is destroy the layout object. Because we are lazy, we just
    # abort and let our cleanup mechanism destroy it. A more sophisticated
    # client could instead destroy only the one single affected layout object
    # and recover from this mishap. Writing such a client is left as an exercise
    # for the reader.
    print("Namespace already in use!")
    global loop
    loop = False

class Output(object):
    def __init__(self):
        self.output = None
        self.layout = None
        self.id = None

    def destroy(self):
        if self.layout is not None:
            self.layout.destroy()
        if self.output is not None:
            self.output.destroy()

    def configure(self):
        global layout_manager
        if self.layout is None and layout_manager is not None:
            # We need to set a namespace, which is used to identify our layout.
            self.layout = layout_manager.get_layout(self.output, "layout.py")
            self.layout.user_data = self
            self.layout.dispatcher["layout_demand"] = layout_handle_layout_demand
            self.layout.dispatcher["namespace_in_use"] = layout_handle_namespace_in_use

def registry_handle_global(registry, id, interface, version):
    global layout_manager
    global output
    if interface == 'river_layout_manager_v3':
        layout_manager = registry.bind(id, RiverLayoutManagerV3, version)
    elif interface == 'wl_output':
        output = Output()
        output.output = registry.bind(id, WlOutput, version)
        output.id = id
        output.configure()
        outputs.append(output)

def registry_handle_global_remove(registry, id):
    for output in outputs:
        if output.id == id:
            output.destroy()
            outputs.remove(output)

display = Display()
display.connect()

registry = display.get_registry()
registry.dispatcher["global"] = registry_handle_global
registry.dispatcher["global_remove"] = registry_handle_global_remove

display.dispatch(block=True)
display.roundtrip()

if layout_manager is None:
    print("No layout_manager, aborting")
    quit()

for output in outputs:
    output.configure()

while loop and display.dispatch(block=True) != -1:
    pass

# Destroy outputs
for output in outputs:
    output.destroy()
    outputs.remove(output)

display.disconnect()
