In order for our surface to be displayed on the screen, we need to assign it a role. There is no uniform mechanism for assigning roles in Wayland; each role has its own way to assign it to a surface. For example, to give a surface the role of a cursor, we use
wl_pointer.set_cursor request, passing the surface as an argument.
For some other roles it's more complicated because those roles come with some additional functionality that needs to be implemented should a surface take that role. The role we're going to use in order to make a surface into a desktop-style window is shell surface.
One way to think about it is to imagine that shell surface is a subclass of surface that has additional functionality implemented on it. This subclassing is done in a somewhat interesting way: you call additional methods on a new object of type
wl_shell_surface. You create that object with
wl_shell.get_shell_surface request, which gives the surface the role of a shell surface.
Another way to think about it is that the surface (that has the role of a shell surface) and its shell surface are indeed two different objects, two distinct, but related, entities. The shell surface, in this case, represents a window, and the surface is responsible for its content.
wl_shell_surface has a lot of functionality, like handling resizes, but we won't go into detail about it. That's because
wl_shell_surface are deprecated and superseded by xdg_sell protocol extension. The only reason we're using
wl_shell_surface here is because we want to stick to core Wayland protocol for now.
Put this into the code before attaching the buffer:
struct wl_shell_surface *shell_surface = wl_shell_get_shell_surface(shell, surface);