Haskell wlroots bindings

In the project I shared you can see the dependencies for tinywl in the shelll.nix, and also the Makefile.shared.

If you comment out some of them you can see how it is interacting by the build errors generated.

TInywl is using wlroots and also wayland-scanner, wayland-protocols and wayland itself.

So it seems its designed to run the scanner on the protocol each time its built to ensure compatibility. So I guess that means we should create bindings based on the wayland XML eventually, however I think for now we can focus on bindings for wlroots and simply leave the wayland stuff on the c side of the equation until we have the wlroots bindings working.

So some good news everyone.

Here Is a screen grab of tiny-wlhs working using the wlhs log bindings. You can see here I open the window with st but you can pass any application name and it will open the window.

When I went to find the wlhs binding for wlr_log it wasnt implemented likely because of the C function being variadic. So I hacked a quick solution and created a PR with the change to wlhs here

So what I did so far is

  • Convert tiny-wl to a shared library and link it into a haskell project
  • Rename then refactor and finally remove the main function to be implemented in haskell
  • Added my fork of wlhs as a submodule and included it into tiny-wlhs cabal
  • Created a shell.nix with an explicit list of all the dependencies
  • Linked wlhs into tiny-wlhs, imported it and then implement logging using the wlhs log bindings.

You can see for yourself or take it for a test run here

Going forward there is obviously a lot to do. My cadence will probably remain at a few hours each weekend.

Already there is some tech debt emerging, for example wlhs uses wlroots 0.17.1 while tiny-wlhs uses 0.17.3 this is simply because wlhs uses a flake and tiny-wlhs uses nix shell. So in the near future tiny-wlhs needs a nix flake to replace the nix shell and lock in the decency versions and align with wlhs.

Going forward I will continue to add wlhs bindings to tiny-wlhs one header at a time. In this way we can sign off on each and be confident they work at least for tinywl. Anything that doesnt work or is missing I will try to address myself and failing that report back.

If anyone can help out it would be greatly appreciated, besides the quality of life tooling mentioned, there is documentation and it would be great to know the bindings creation for wlhs is ongoing. Also its not difficult to try and use the bindings with tiny-wlhs so anyone interested can try this independently or we could form a group to take one set of bindings at a time. Regardless Iā€™m more than happy to help, guide or direct anyone interested all levels are welcome!

4 Likes

Amazing work @l-Shane-l! This is exciting to see.

To summarise what youā€™ve done here:

  • Rewrote TinyWLā€™s original main function in Haskell
  • Refactored TinyWL such that the rest of the code can be called externally as needed
  • Dynamically linked it with Haskell main calling into the remainder of TinyWL in C

So if my understanding is correct, you havenā€™t actually modified wlhs at all (except for adding one more binding): youā€™ve just made it much easier to test what we have. (Which is important!)

Yep, I wasnā€™t sure what to do about variadic functions. This solution is adequate, but I donā€™t like merging PRs until weā€™ve found a proper solution to the problem.

(Of course, maintainership may be taken over by someone else, in which case theyā€™re free to do whatever they want.)

1 Like

Yes, so sorry if its not clear but the vast majority of the tiny-wlhs is currently just ffi calls to the refactored c tinywl embedded in the project.

The intention is to reduce this C code function by function using the wlhs bindings, the project can be regarded as a success when there is no longer and C present in the project. My goal was to provide a way to iterate and test and also to simulate the needs of waymonad. My hope is it will offer some direction as well as a simple way to determine progress.

With regard the PR I didnt expect it to be merged, but if its ok with you I would like tocontinue to make PRā€™s with what is essentially place holder bindings. Its a nice way to keep track of what is needed for tinywl. Also Im happy to take part in any design discussions on the PRā€™s. It really take the pressure of me if I can just PR place holder bindings otherwise I would need to invest a lot of time into the design.

So basically going forward my plan is to isolate functions into single FFI to tinywl and then try to replace them with wlhs. I am working on the assumption a lot of challenges and issues will pop up when trying to integrate the bindings, I also think its a really good way to get some of the dopamine from seeing something happen on screen.

Oh wow, this thread is active again! Glad to see it. I thought it was over when I watched the last PR fizzle out :frowning:. Iā€™m definitely still interested though!

The only caveat is that when I left off, I felt a bit disparaged looking at just how much we may need to bind to in order to get a complete window manager (moving past just TinyWL). I started to toy with a new bindings generator. I can generate some basic stuff, so that may help with momentum. That being said, itā€™s currently in a bit of a rough state. I can try to clean it up and publish the WIP, if anyone is interested or willing to help.

@l-Shane-l, that looks like some really great work so far! I think thatā€™s the right idea, using a C base so that we have something operational as we eat away at it with the bindings.

Anyways, Iā€™ll keep an eye on this thread from now on, and try to put in some more work when Iā€™m free this weekend. Iā€™m still really hoping that we can make this project happen.

2 Likes

I for one am definitely interested and would love to help out.

This is so true, especially because its volunteer based the pace is going to be slow. However, at the same time there is no time pressure or deadline, its more about making consistent progress. For me at least, I try to view the journey and not the destination, focusing on getting excited about all that will be learned on the way.

3 Likes

Take a look at the tutorial if you want to see how Iā€™m using it. Like I said, itā€™s in a rough state, so anything is appreciated.

1 Like

This is really cool, I will try it out this week. When I started I was also looking into getting the headers into a ast. I looked at language-c but didnt pursue it further.

So this weekend I spent more time on tiny-wlhs, there is no great success to talk about unfortunately but I will give an update because I think will provide some insight on the project.

So currently in the Main.hs we have this line

initSuccess <- FFI.c_server_init server

Which calls this FFI

foreign import ccall "server_init" c_server_init :: Ptr TinyWLServer -> IO Bool

which calls this c function in the tinywl shared lib

bool server_init(struct tinywl_server *server) {
    wlr_log_init(WLR_DEBUG, NULL);
    
    if (!initialize_wl_display(server)) return false;
    if (!initialize_backend(server)) return false;
    if (!initialize_renderer(server)) return false;
    if (!initialize_allocator(server)) return false;

    initialize_compositor(server);
    initialize_output_layout(server);
    initialize_scene(server);
    initialize_xdg_shell(server);
    initialize_cursor(server);
    initialize_seat(server);

    return true;
}

so focusing on initialize_compositor

void initialize_compositor(struct tinywl_server *server) {
    /* wlr_compositor_create(server->wl_display, 5, server->renderer); */
    wlr_subcompositor_create(server->wl_display);
    wlr_data_device_manager_create(server->wl_display);
}

the intention is to comment out wlr_compositor_create and instead call it from wlhs

Unfortunately wlr_compositor_create does not exist in wlhs it should be in WLR/Types/Compositor.hsc

I will go over this later but first a slight detour on the topic of opaque pointers. In order to keep tiny-wlhs as simple as possible I have left all structs on the C side of the equation with opaque pointers being used to manage them on the Haskell side. Then to interact I created getters and setters in hsc files.

So that required creating a header file for tinywl in the tinywl share library. Then this code


#include "tinywl.h"


type TinyWLServerPtr = Ptr TinyWLServer

-- Getter for wl_display
getWlDisplay :: TinyWLServerPtr -> IO (Ptr ())
getWlDisplay ptr = #{peek struct tinywl_server, wl_display} ptr

-- Getter for renderer
getRenderer :: TinyWLServerPtr -> IO (Ptr ())
getRenderer ptr = #{peek struct tinywl_server, renderer} ptr

-- Getter for backend
getBackend :: TinyWLServerPtr -> IO (Ptr ())
getBackend ptr = #{peek struct tinywl_server, backend} ptr

-- Setter for cursor_mode
setCursorMode :: TinyWLServerPtr -> CInt -> IO ()
setCursorMode ptr mode = #{poke struct tinywl_server, cursor_mode} ptr mode

-- Getter for cursor_mode
getCursorMode :: TinyWLServerPtr -> IO CInt
getCursorMode ptr = #{peek struct tinywl_server, cursor_mode} ptr

which i crudely tested in Main.hs like this


    initSuccess <- FFI.c_server_init server
    if initSuccess
        then do
            wlr_log WLR_INFO "Server initialized successfully"
            wlDisplay <- Server.getWlDisplay server
            renderer <- Server.getRenderer server
            backend <- Server.getBackend server
            
            putStrLn $ "wl_display pointer: " ++ show wlDisplay
            putStrLn $ "renderer pointer: " ++ show renderer
            putStrLn $ "backend pointer: " ++ show backend

So originally I was creating Haskell implementations of the server struct but this led to an unending recursive process of header definitions that lead to the Wayland protocols. These protocols are designed to create headers at compile time using Wayland scanner.

So to take this path we would need a Haskell xml scanner for the Wayland protocols to be run at compile time, or to simply draw the line on the nested Haskell implementation of structs and use opaque at some arbitrary level(likely the Wayland protocol headers).

The is why I decided to leave the struct on the C side as much as possible.

So now returning to the need for wlr_compositor_create in wlhs this is what the c function looks like

 struct wlr_compositor *wlr_compositor_create(struct wl_display *display,
		uint32_t version, struct wlr_renderer *renderer) {
	assert(version <= COMPOSITOR_VERSION);

	struct wlr_compositor *compositor = calloc(1, sizeof(*compositor));
	if (!compositor) {
		return NULL;
	}

	compositor->global = wl_global_create(display, &wl_compositor_interface,
		version, compositor, compositor_bind);
	if (!compositor->global) {
		free(compositor);
		return NULL;
	}

	wl_signal_init(&compositor->events.new_surface);
	wl_signal_init(&compositor->events.destroy);
	wl_list_init(&compositor->renderer_destroy.link);

	compositor->display_destroy.notify = compositor_handle_display_destroy;
	wl_display_add_destroy_listener(display, &compositor->display_destroy);

	wlr_compositor_set_renderer(compositor, renderer);

	return compositor;
}

So I think for

	struct wlr_compositor *compositor = calloc(1, sizeof(*compositor));
	if (!compositor) {
		return NULL;
	}

It makes the sense to create a function in hsc to just create this and return a pointer.

For the rest of the function I believe all the wl_* functions exist in wlhs and are also pointers like this

foreign import capi "wayland-server-core.h wl_signal_init"
    wl_signal_init :: Ptr WL_signal -> IO ()

So to try to wrap this long message up I think there is some valuable information here.

1 - wlhs does need to have Wayland headers, this can be from wayland-scanner run at compile time, the files manually uploaded or a Haskell wml parser but they are needed.

2 - I think there would be a lot of value in writing down design decisions somewhere, like what is the default approach for structs, or opaque pointers. It might be worth having a discussion on what to leave on the C side and what to implement in Haskell. There are clear benefits for the Haskell implementation (the whole reason we are doing this) but it comes at a cost, both in time to implement and likely in run time performance.

3 - wlr_compositor_create needs to be implemented in wlhs. Further on this, with the Wayland protocols there are signals and events etc its not clear if just creating a pointer to these is the best approach. We may want to implement these in Haskell.

4 - This is subjective and my opinion but when I try to use the bindings I really see the limitations in the automatic generators. I really get the appeal but I think even if we had something that could magically convert every header there would still be questions about what should be bound and what should be implemented in Haskell.

Anyway Ill probably try to create a wlhs implementation for wlr_compositor_create next week, hopefully someone else will beat me to it :slight_smile:

5 Likes

I can help pretty quickly with the nix stuff, probably this weekend as Iā€™m on call at work. annoyingly, Iā€™m stuck on mac for a bit so itā€™s been tougher to get a dev environment that can load the wayland packages together, so will need to fix a machine before I can help out with the WM itself.

Yep, I wasnā€™t sure what to do about variadic functions. This solution is adequate, but I donā€™t like merging PRs until weā€™ve found a proper solution to the problem.

how heterogenous are the types of the varargs? if theyā€™re fairly similar, we can probably construct maps on the Haskell side and (un)pack those maps before passing them through the FFI layer. iirc, llvm-hs does something similar. if theyā€™re very heterogenous, I canā€™t think of a better option than a type level assoc list that tracks the types inside a value level map, or something equivalent to that. itā€™s a little fiddly to get past the compiler but doable.

1 Like

I think that it might be best to continue on the C side for a little while, and then work towards the Haskell bindings. The part of this project which necessitates Haskell, as far as Iā€™m concerned, is user config and contrib. I think that it may be best to choose a feature to start working towards, keybinding configuration for example, and then start to work on the bindings necessary to implement an XMonad-like interface for keyconfig.

1 Like

Help with the nix would be great. Moving from shell to a flake would definitely be an improvement for example. Fortunately for the time being @bullishOnFunctional added niv to the project so we can actually pin versions.

Yes I agree. Its hard enough to make any progress using simple ffi calls and opaque pointers.

As it is I think already too much is being done in tiny-wlhs, rather than explaining this its easier to show it.

So like I mentioned before my goal was to move

struct wlr_compositor *wlr_compositor_create(struct wl_display *display, 
               uint32_t version, struct wlr_renderer *renderer) { 
       assert(version <= COMPOSITOR_VERSION); 

       struct wlr_compositor *compositor = calloc(1, sizeof(*compositor)); 
       if (!compositor) { 
               return NULL; 
       } 

       compositor->global = wl_global_create(display, &wl_compositor_interface, 
               version, compositor, compositor_bind); 
       if (!compositor->global) { 
               free(compositor); 
               return NULL; 
       } 

       wl_signal_init(&compositor->events.new_surface); 
       wl_signal_init(&compositor->events.destroy); 
       wl_list_init(&compositor->renderer_destroy.link); 

       compositor->display_destroy.notify = compositor_handle_display_destroy; 
       wl_display_add_destroy_listener(display, &compositor->display_destroy); 

       wlr_compositor_set_renderer(compositor, renderer); 

       return compositor; 
} 

From where it is currently being called (its in wlroot and being called by tinywl.c).

tinywl.c just makes a function call to wlroots, for us ideally we would do a similat call to wlhs but unfortunately its not implemented in wlhs yet.

the other aspect is the function requires data to be passed to it.

The approach I took is

wlDisplay <- Server.getWlDisplay server
            renderer <- Server.getRenderer server
            backend <- Server.getBackend server
            _ <- FFI.c_wlr_compositor_create wlDisplay 5 renderer

so I just created some getters on the haskell side

-- Getter for renderer
getRenderer :: TinyWLServerPtr -> IO (Ptr WLR_renderer)
getRenderer ptr = #{peek struct tinywl_server, renderer} ptr

-- Getter for backend
getBackend :: TinyWLServerPtr -> IO (Ptr WlrBackend)
getBackend ptr = #{peek struct tinywl_server, backend} ptr

in this case types like WlrBackend are just created in tiny-wlhs but really should be imported from wlhs (as far as I know only some like WLR_renderer currently exist in wlhs)

So then finally I implemented this FFI

foreign import ccall "wlr_compositor_create" c_wlr_compositor_create :: Ptr WlDisplay -> CUInt -> Ptr WLR_renderer -> IO (Ptr WlrCompositor)

again here the types should ideally imported from wlhs and this whole function should probably be implemented in wlhs.

So this implementation works but is just a stepping stone towards how this should actually be done.

The types and ffi really should be in wlhs. However even beyond that implementing getters and setters in tiny-wlhs doesnt feel good.

I think using opaque pointers makes a lot of sense and removes a lot of complexity it probably even runs faster.

However the aim of tinywl is to provide something simple to test the bindings its not intended to be a library. Equally putting a lot of custom code into wlhs for managing opaque pointers doesnt make much sense.

There are other concerns for example within wlr_compostitor_create there are initialization of signals and events, these are currently on the C side of the build but probably need to be implemented in haskell or at least interfaced with via haskell.

All together the complexity is hard to effectively manage, its not enough to simply move everything from C to haskell, the goal is to provide a simple and performant interface for haskellers to create wayland compositors.

I need to think on this but there is probably a way to simplify all this. Taking the approach of leaving as much complexity in the C implementation already available seems to make a lot of sense, but it makes me wonder if tiny-wlhs should have a second purpose of being a library that can be used to create wayland compositors in haskell. If so then this would require a lot of thought about what abstractions to use and how to expose them to a user.

I think its fair to assume any haskeller who is trying to use wayland does not care about pointers or structs, they just want to create what they need in a simple, type safe and performant way. So then to me it seems like a lot of the complexity of working with wayland (protocol scanners etc) can be abstracted away and simply implement in C and interfaced via haskell.

Just some thoughts but I would love to hear what other people thing?

Hi everyone. Hopefully this isnā€™t too much to ask but could someone whoā€™s involved give a short summary on the progress?

2 Likes

Instead of being a library, how about we just try to grow tiny-wlhs as an application? As you say:

I think that it would be a lot more productive to just forget all of that for now, and focus our efforts on e.g. implementing a StackSet (XMonadā€™s primary data type for representing the set of all windows) in tinywl/tiny-wlhs.

But to be clear, yes, I think that Iā€™m fully on board with this.

Going back, this much Iā€™m a lot less clear on. At first it seems almost obvious that we would eventually want to be able to peek and poke structs from the Haskell side, but, well, would we actually? Maybe youā€™re onto something. Working with structs in Haskell is unpleasant, and using mutable and immutable data to define a boundary line may actually be quite pragmatic. I definitely like this idea enough to want to try it out for a bit.

2 Likes

Progress on what scale? From zero to working compositor, almost nothing concrete. We have a much better understanding of the problem space than we had a year ago, but still, thereā€™s much to work out and the total effort thatā€™s been put in has been fairly modest for a project of this scale.

2 Likes

Iā€™d really like to contribute to this project, since I have so much free time. But I donā€™t know anything about wlroots and Iā€™m still learning haskell. Can anyone guide me on where to start learning?

4 Likes

If your new to the concepts I would recommend starting with trying to get this running on your system.

https://github.com/l-Shane-l/tiny-wlhs

It has wlroots, wlhs and a starter compositor in the repo. When you successfully run it you will have something play around with. If you run into any problems just create issues in the repo, you can also point out parts of the readme that are unclear or confusing and recommend changes.

Once your set up you can pick a c function from tinywl.c and try to move the implementation to haskell, you will know you are successful if the changes compile and the compositor works. Any changes or updates you make you can create a PR Iā€™m happy to review these and provide guidance if your unsure.

If you dont want to work with the C you could pick something else for example there is currently no test coverage and readme can always use help. We could also probably add tooling like HLS, hlint etc into the nix setup.

1 Like

Ok so I refactored the project to be more of a library with an example implementation in src called ExampleCompositor.hs

I broke the library into smaller parts, currently Server and Compositor and in these I structured it as FFI, Types and the Server.hs or Compositor.hs file respectively.

This has the advantage on being easier to reason about. So for example you can look at the FFI to see exactly what C is being called.

Im quite happy with the refactor I think its a lot clearer what is going on and a lot easier to reason about and work with, this might be my own bias so im open to feedback and changes.

2 Likes