From 429545585a112b219c822a9d643d42a005646f28 Mon Sep 17 00:00:00 2001 From: shtirlic Date: Sat, 3 Jun 2023 21:18:02 +0700 Subject: [PATCH] Update to upstream tinywl implementation --- cmd/tinywl/main.go | 9 +- cmd/tinywl/server.go | 907 +++++++++++--------- cmd/tinywl/toplevel.go | 24 - wlroots/backend.go | 93 ++ wlroots/buffer.go | 49 ++ wlroots/compositor.go | 283 +++++++ wlroots/cursor.go | 192 +++++ wlroots/input_device.go | 88 ++ wlroots/keyboard.go | 103 +++ wlroots/listener_manager.go | 155 ++++ wlroots/log.go | 64 ++ wlroots/output.go | 420 ++++++++++ wlroots/renderer.go | 56 ++ wlroots/scene.go | 346 ++++++++ wlroots/seat.go | 125 +++ wlroots/server_decoration.go | 73 ++ wlroots/wlroots.go | 1539 ++-------------------------------- wlroots/xcursor.go | 44 + wlroots/xdg_shell.go | 258 ++++++ wlroots/xwayland.go | 120 +++ xkb/xkb.go | 4 +- 21 files changed, 3072 insertions(+), 1880 deletions(-) delete mode 100644 cmd/tinywl/toplevel.go create mode 100644 wlroots/backend.go create mode 100644 wlroots/buffer.go create mode 100644 wlroots/compositor.go create mode 100644 wlroots/cursor.go create mode 100644 wlroots/input_device.go create mode 100644 wlroots/keyboard.go create mode 100644 wlroots/listener_manager.go create mode 100644 wlroots/log.go create mode 100644 wlroots/output.go create mode 100644 wlroots/renderer.go create mode 100644 wlroots/scene.go create mode 100644 wlroots/seat.go create mode 100644 wlroots/server_decoration.go create mode 100644 wlroots/xcursor.go create mode 100644 wlroots/xdg_shell.go create mode 100644 wlroots/xwayland.go diff --git a/cmd/tinywl/main.go b/cmd/tinywl/main.go index 2fc780f..18ab570 100644 --- a/cmd/tinywl/main.go +++ b/cmd/tinywl/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "log/slog" "os" "os/exec" "runtime" @@ -11,7 +12,9 @@ import ( ) var ( - command = flag.String("s", "", "startup command") + command = flag.String("s", "", "startup command") + programLevel = new(slog.LevelVar) // Info by default + ) func fatal(msg string, err error) { @@ -26,9 +29,13 @@ func init() { } func main() { + // set global logger with custom options + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}))) + flag.Parse() // set up logging + // programLevel.Set(slog.LevelDebug) wlroots.OnLog(wlroots.LogImportanceDebug, nil) // start the server diff --git a/cmd/tinywl/server.go b/cmd/tinywl/server.go index c0b5cef..87b7ccb 100644 --- a/cmd/tinywl/server.go +++ b/cmd/tinywl/server.go @@ -1,7 +1,9 @@ package main import ( + "container/list" "fmt" + "log/slog" "os" "time" @@ -25,8 +27,8 @@ type Server struct { scene wlroots.Scene sceneLayout wlroots.SceneOutputLayout - xdgShell wlroots.XDGShell - topLevels []*TopLevel + xdgShell wlroots.XDGShell + topLevelList list.List cursor wlroots.Cursor cursorMgr wlroots.XCursorManager @@ -34,315 +36,299 @@ type Server struct { seat wlroots.Seat keyboards []*Keyboard cursorMode CursorMode - grabbedTopLevel *TopLevel + grabbedTopLevel *wlroots.XDGTopLevel grabX, grabY float64 grabGeobox wlroots.GeoBox resizeEdges wlroots.Edges outputLayout wlroots.OutputLayout - outputs []wlroots.Output } type Keyboard struct { dev wlroots.InputDevice } -func NewServer() (s *Server, err error) { - s = new(Server) - - /* The Wayland display is managed by libwayland. It handles accepting - * clients from the Unix socket, manging Wayland globals, and so on. */ - s.display = wlroots.NewDisplay() - - /* The backend is a wlroots feature which abstracts the underlying input and - * output hardware. The autocreate option will choose the most suitable - * backend based on the current environment, such as opening an X11 window - * if an X11 server is running. */ - s.backend, err = wlroots.NewBackend(s.display) - if err != nil { - return nil, err +func (s *Server) inTopLevel(topLevel *wlroots.XDGTopLevel) *list.Element { + for e := s.topLevelList.Front(); e != nil; e = e.Next() { + if *e.Value.(*wlroots.XDGTopLevel) == *topLevel { + return e + } } + return nil +} - /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user - * can also specify a renderer using the WLR_RENDERER env var. - * The renderer is responsible for defining the various pixel formats it - * supports for shared memory, this configures that for clients. */ - s.renderer, err = wlroots.NewRenderer(s.backend) - if err != nil { - return nil, err +func (s *Server) moveFrontTopLevel(topLevel *wlroots.XDGTopLevel) { + slog.Debug("moveFrontTopLevel", "s.topLevelList.Len()", s.topLevelList.Len()) + e := s.inTopLevel(topLevel) + if e != nil { + slog.Debug("moveFrontTopLevel", "topLevel", topLevel) + s.topLevelList.MoveToFront(e) } - s.renderer.InitDisplay(s.display) + slog.Debug("moveFrontTopLevel", "s.topLevelList.Len()", s.topLevelList.Len()) +} - /* Autocreates an allocator for us. - * The allocator is the bridge between the renderer and the backend. It - * handles the buffer creation, allowing wlroots to render onto the - * screen */ - s.allocator, err = wlroots.NewAllocator(s.backend, s.renderer) - if err != nil { - return nil, err +func (s *Server) removeTopLevel(topLevel *wlroots.XDGTopLevel) { + slog.Debug("removeTopLevel", "s.topLevelList.Len()", s.topLevelList.Len()) + e := s.inTopLevel(topLevel) + if e != nil { + slog.Debug("removeTopLevel", "topLevel", topLevel) + s.topLevelList.Remove(e) } - - /* This creates some hands-off wlroots interfaces. The compositor is - * necessary for clients to allocate surfaces, the subcompositor allows to - * assign the role of subsurfaces to surfaces and the data device manager - * handles the clipboard. Each of these wlroots interfaces has room for you - * to dig your fingers in and play with their behavior if you want. Note that - * the clients cannot set the selection directly without compositor approval, - * see the handling of the request_set_selection event below.*/ - wlroots.NewCompositor(s.display, 5, s.renderer) - wlroots.NewSubCompositor(s.display) - wlroots.NewDataDeviceManager(s.display) - - /* Creates an output layout, which a wlroots utility for working with an - * arrangement of screens in a physical layout. */ - s.outputLayout = wlroots.NewOutputLayout() - - /* Configure a listener to be notified when new outputs are available on the - * backend. */ - s.backend.OnNewOutput(s.handleNewOutput) - - /* Create a scene graph. This is a wlroots abstraction that handles all - * rendering and damage tracking. All the compositor author needs to do - * is add things that should be rendered to the scene graph at the proper - * positions and then call wlr_scene_output_commit() to render a frame if - * necessary. - */ - s.scene = wlroots.NewScene() - s.sceneLayout = s.scene.AttachOutputLayout(s.outputLayout) - - /* Set up xdg-shell version 3. The xdg-shell is a Wayland protocol which is - * used for application windows. For more detail on shells, refer to - * https://drewdevault.com/2018/07/29/Wayland-shells.html. - */ - s.xdgShell = wlroots.NewXDGShell(s.display, 3) - s.xdgShell.OnNewSurface(s.handleNewXDGSurface) - - /* - * Creates a cursor, which is a wlroots utility for tracking the cursor - * image shown on screen. - */ - s.cursor = wlroots.NewCursor() - s.cursor.AttachOutputLayout(s.outputLayout) - - /* Creates an xcursor manager, another wlroots utility which loads up - * Xcursor themes to source cursor images from and makes sure that cursor - * images are available at all scale factors on the screen (necessary for - * HiDPI support). */ - s.cursorMgr = wlroots.NewXCursorManager("", 24) - - /* - * wlr_cursor *only* displays an image on screen. It does not move around - * when the pointer moves. However, we can attach input devices to it, and - * it will generate aggregate events for all of them. In these events, we - * can choose how we want to process them, forwarding them to clients and - * moving the cursor around. More detail on this process is described in - * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html. - * - * And more comments are sprinkled throughout the notify functions above. - */ - - s.cursorMode = CursorModePassThrough - s.cursor.OnMotion(s.handleCursorMotion) - s.cursor.OnMotionAbsolute(s.handleCursorMotionAbsolute) - s.cursor.OnButton(s.handleCursorButton) - s.cursor.OnAxis(s.handleCursorAxis) - s.cursor.OnFrame(s.handleCursorFrame) - s.cursorMgr.Load(1) - - /* - * Configures a seat, which is a single "seat" at which a user sits and - * operates the computer. This conceptually includes up to one keyboard, - * pointer, touch, and drawing tablet device. We also rig up a listener to - * let us know when new input devices are available on the backend. - */ - s.backend.OnNewInput(s.handleNewInput) - s.seat = wlroots.NewSeat(s.display, "seat0") - s.seat.OnSetCursorRequest(s.handleSetCursorRequest) - - return + slog.Debug("removeTopLevel", "s.topLevelList.Len()", s.topLevelList.Len()) } -func (s *Server) Start() (err error) { - - var socket string - /* Add a Unix socket to the Wayland display. */ - if socket, err = s.display.AddSocketAuto(); err != nil { - s.backend.Destroy() +func (s *Server) focusTopLevel(topLevel *wlroots.XDGTopLevel, surface *wlroots.Surface) { + /* Note: this function only deals with keyboard focus. */ + if topLevel == nil { return } - - /* Start the backend. This will enumerate outputs and inputs, become the DRM - * master, etc */ - if err = s.backend.Start(); err != nil { - s.backend.Destroy() - s.display.Destroy() + prevSurface := s.seat.KeyboardState().FocusedSurface() + slog.Debug("focusTopLevel", "prev surface:", prevSurface) + slog.Debug("focusTopLevel", "current surface:", *surface) + if prevSurface == *surface { + /* Don't re-focus an already focused surface. */ return } - /* Set the WAYLAND_DISPLAY environment variable to our socket and run the - * startup command if requested. */ - if err = os.Setenv("WAYLAND_DISPLAY", socket); err != nil { - return + if !prevSurface.Nil() { + /* + * Deactivate the previously focused surface. This lets the client know + * it no longer has focus and the client will repaint accordingly, e.g. + * stop displaying a caret. + */ + prevTopLevel, err := prevSurface.XDGTopLevel() + if err == nil { + prevTopLevel.SetActivated(false) + } } - return + /* Move the toplevel to the front */ + topLevel.Base().SceneTree().Node().RaiseToTop() + slog.Debug("focusTopLevel", "s.topLevelList.Len()", s.topLevelList.Len()) + slog.Debug("focusTopLevel", "topLevel", topLevel) + s.moveFrontTopLevel(topLevel) + slog.Debug("focusTopLevel", "s.topLevelList.Len()", s.topLevelList.Len()) + /* Activate the new surface */ + topLevel.SetActivated(true) + /* + * Tell the seat to have the keyboard enter this surface. wlroots will keep + * track of this and automatically send key events to the appropriate + * clients without additional work on your part. + */ + s.seat.NotifyKeyboardEnter(topLevel.Base().Surface(), s.seat.Keyboard()) } -func (s *Server) Run() error { +func (s *Server) handleNewPointer(dev wlroots.InputDevice) { + /* We don't do anything special with pointers. All of our pointer handling + * is proxied through wlr_cursor. On another compositor, you might take this + * opportunity to do libinput configuration on the device to set + * acceleration, etc. */ + s.cursor.AttachInputDevice(dev) +} - /* Run the Wayland event loop. This does not return until you exit the - * compositor. Starting the backend rigged up all of the necessary event - * loop configuration to listen to libinput events, DRM events, generate - * frame events at the refresh rate, and so on. */ - s.display.Run() +func (s *Server) handleKey(keyboard wlroots.Keyboard, time uint32, keyCode uint32, updateState bool, state wlroots.KeyState) { + /* This event is raised when a key is pressed or released. */ - /* Once s.display.Run() returns, we destroy all clients then shut down the - * server. */ - s.display.DestroyClients() - s.scene.Tree().Node().Destroy() - s.cursorMgr.Destroy() - s.outputLayout.Destroy() - s.display.Destroy() - return nil -} + // translate libinput keycode to xkbcommon and obtain keysyms + syms := keyboard.XKBState().Syms(xkb.KeyCode(keyCode + 8)) -func (s *Server) topLevelAt(lx float64, ly float64) (*TopLevel, wlroots.Surface, float64, float64) { - for i := len(s.topLevels) - 1; i >= 0; i-- { - topLevel := s.topLevels[i] - surface, sx, sy := topLevel.XDGSurface().SurfaceAt(lx-topLevel.X, ly-topLevel.Y) - if !surface.Nil() { - return topLevel, surface, sx, sy + handled := false + modifiers := keyboard.Modifiers() + if (modifiers&wlroots.KeyboardModifierAlt != 0) && state == wlroots.KeyStatePressed { + /* If alt is held down and this button was _pressed_, we attempt to + * process it as a compositor keybinding. */ + for _, sym := range syms { + handled = s.handleKeyBinding(sym) } } - return nil, wlroots.Surface{}, 0, 0 + if !handled { + /* Otherwise, we pass it along to the client. */ + s.seat.SetKeyboard(keyboard.Base()) + s.seat.NotifyKeyboardKey(time, keyCode, state) + } } -func (s *Server) renderView(output wlroots.Output, topLevel *TopLevel) { - topLevel.XDGSurface().Walk(func(surface wlroots.Surface, sx int, sy int) { - texture := surface.Texture() - if texture.Nil() { - return - } +func (s *Server) handleNewKeyboard(dev wlroots.InputDevice) { + keyboard := dev.Keyboard() - ox, oy := s.outputLayout.Coords(output) - ox += topLevel.X + float64(sx) - oy += topLevel.Y + float64(sy) + /* We need to prepare an XKB keymap and assign it to the keyboard. This + * assumes the defaults (e.g. layout = "us"). */ + context := xkb.NewContext(xkb.KeySymFlagNoFlags) + keymap := context.KeyMap() + keyboard.SetKeymap(keymap) + keymap.Destroy() + context.Destroy() + keyboard.SetRepeatInfo(25, 600) - scale := output.Scale() - state := surface.CurrentState() - transform := wlroots.OutputTransformInvert(state.Transform()) + /* Here we set up listeners for keyboard events. */ + keyboard.OnModifiers(func(keyboard wlroots.Keyboard) { + /* This event is raised when a modifier key, such as shift or alt, is + * pressed. We simply communicate this to the client. */ + s.seat.SetKeyboard(dev) + s.seat.NotifyKeyboardModifiers(keyboard) + }) + keyboard.OnKey(s.handleKey) - box := wlroots.GeoBox{ - X: int(ox * float64(scale)), - Y: int(oy * float64(scale)), - Width: int(float32(state.Width()) * scale), - Height: int(float32(state.Height()) * scale), - } + s.seat.SetKeyboard(dev) - var matrix wlroots.Matrix - transformMatrix := output.TransformMatrix() - matrix.ProjectBox(&box, transform, 0, &transformMatrix) + /* And add the keyboard to our list of keyboards */ + s.keyboards = append(s.keyboards, &Keyboard{dev: dev}) +} - s.renderer.RenderTextureWithMatrix(texture, &matrix, 1) +func (s *Server) handleNewInput(dev wlroots.InputDevice) { + /* This event is raised by the backend when a new input device becomes + * available. */ + switch dev.Type() { + case wlroots.InputDeviceTypePointer: + s.handleNewPointer(dev) + case wlroots.InputDeviceTypeKeyboard: + s.handleNewKeyboard(dev) + } - surface.SendFrameDone(time.Now()) - }) + /* We need to let the wlr_seat know what our capabilities are, which is + * communicated to the client. In TinyWL we always have a cursor, even if + * there are no pointer devices, so we always include that capability. */ + caps := wlroots.SeatCapabilityPointer + if len(s.keyboards) > 0 { + caps |= wlroots.SeatCapabilityKeyboard + } + s.seat.SetCapabilities(caps) } -func (s *Server) focusTopLevel(topLevel *TopLevel, surface wlroots.Surface) { - /* Note: this function only deals with keyboard focus. */ - if topLevel == nil { - return +func (s *Server) topLevelAt(lx float64, ly float64) (*wlroots.XDGTopLevel, *wlroots.Surface, float64, float64) { + /* This returns the topmost node in the scene at the given layout coords. + * We only care about surface nodes as we are specifically looking for a + * surface in the surface tree of a tinywl_toplevel. */ + + node, sx, sy := s.scene.Tree().Node().At(lx, ly) + + if node.Nil() || node.Type() != wlroots.SceneNodeBuffer { + return nil, nil, 0, 0 } - prevSurface := s.seat.KeyboardState().FocusedSurface() - if prevSurface == surface { - /* Don't re-focus an already focused surface. */ - return + sceneSurface := node.SceneBuffer().SceneSurface() + slog.Debug("topLevelAt", "sceneSurface:", sceneSurface) + if sceneSurface.Nil() { + return nil, nil, 0, 0 } + surface := sceneSurface.Surface() + slog.Debug("topLevelAt", "surface:", surface) - if !prevSurface.Nil() { - /* - * Deactivate the previously focused surface. This lets the client know - * it no longer has focus and the client will repaint accordingly, e.g. - * stop displaying a caret. - */ - prev := prevSurface.XDGSurface() - prev.TopLevelSetActivated(false) - } + /* Find the node corresponding to the tinywl_toplevel at the root of this + * surface tree, it is the only one for which we set the data field. */ - /* Move the toplevel to the front */ - topLevel.SceneTree.Node().RaiseToTop() + topLevel := surface.XDGSurface().TopLevel() + slog.Debug("topLevelAt", "topLevel", topLevel) + slog.Debug("topLevelAt", "s.topLevelList.Len()", s.topLevelList.Len()) - for i := len(s.topLevels) - 1; i >= 0; i-- { - if s.topLevels[i] == topLevel { - s.topLevels = append(s.topLevels[:i], s.topLevels[i+1:]...) - s.topLevels = append(s.topLevels, topLevel) - break - } + if s.inTopLevel(&topLevel) != nil { + return &topLevel, &surface, sx, sy + } else { + return nil, &surface, sx, sy } - /* Activate the new surface */ - topLevel.XDGSurface().TopLevelSetActivated(true) - /* - * Tell the seat to have the keyboard enter this surface. wlroots will keep - * track of this and automatically send key events to the appropriate - * clients without additional work on your part. - */ - s.seat.NotifyKeyboardEnter(topLevel.Surface(), s.seat.Keyboard()) } func (s *Server) handleNewFrame(output wlroots.Output) { - output.AttachRender() + /* This function is called every time an output is ready to display a frame, + * generally at the output's refresh rate (e.g. 60Hz). */ - width, height := output.EffectiveResolution() - s.renderer.Begin(output, width, height) - s.renderer.Clear(&wlroots.Color{R: 0.3, G: 0.3, B: 0.3, A: 1.0}) + sOut, err := s.scene.SceneOutput(output) + if err != nil { + return + } - // render all of the topLevels - for _, view := range s.topLevels { - if !view.Mapped { - continue - } + /* Render the scene if needed and commit the output */ + sOut.Commit() + sOut.SendFrameDone(time.Now()) +} - s.renderView(output, view) - } +func (s *Server) handleOutputRequestState(output wlroots.Output, state wlroots.OutputState) { + /* This function is called when the backend requests a new state for + * the output. For example, Wayland and X11 backends request a new mode + * when the output window is resized. */ + slog.Debug("handleRequestState", "output", output, "state", state) + output.CommitState(state) +} - output.RenderSoftwareCursors() - s.renderer.End() - output.Commit() +func (s *Server) handleOuptuDestroy(output wlroots.Output) { + slog.Debug("handleDestroy", "output", output) } func (s *Server) handleNewOutput(output wlroots.Output) { + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + + /* Configures the output created by the backend to use our allocator + * and our renderer. Must be done once, before commiting the output */ output.InitRender(s.allocator, s.renderer) /* The output may be disabled, switch it on. */ - var oState wlroots.OutputState + oState := wlroots.NewOutputState() oState.StateInit() - oState.StateSetEnabled() + oState.StateSetEnabled(true) /* Some backends don't have modes. DRM+KMS does, and we need to set a mode * before we can use the output. The mode is a tuple of (width, height, * refresh rate), and each monitor supports only a specific set of modes. We * just pick the monitor's preferred mode, a more sophisticated compositor * would let the user configure it. */ - mode := output.PrefferedMode() - output.SetMode(mode) - output.Enable(true) - if !output.Commit() { - return + mode, err := output.PrefferedMode() + if err == nil { + oState.SetMode(mode) } + /* Atomically applies the new output state. */ + output.CommitState(oState) + oState.Finish() + + /* Sets up a listener for the frame event. */ output.OnFrame(s.handleNewFrame) - s.outputLayout.AddOutputAuto(output) - output.SetTitle(fmt.Sprintf("tinywl (go-wlroots) - %s", output.Name())) + + /* Sets up a listener for the state request event. */ + output.OnRequestState(s.handleOutputRequestState) + + /* Sets up a listener for the destroy event. */ + output.OnDestroy(s.handleOuptuDestroy) + + /* Adds this to the output layout. The add_auto function arranges outputs + * from left-to-right in the order they appear. A more sophisticated + * compositor would let the user configure the arrangement of outputs in the + * layout. + * + * The output layout utility automatically adds a wl_output global to the + * display, which Wayland clients can see to find out information about the + * output (such as DPI, scale factor, manufacturer, etc). + */ + lOutput := s.outputLayout.AddOutputAuto(output) + sceneOutput := s.scene.NewOutput(output) + s.sceneLayout.AddOutput(lOutput, sceneOutput) + + err = output.SetTitle(fmt.Sprintf("tinywl (go-wlroots) - %s", output.Name())) + if err != nil { + return + } } func (s *Server) handleCursorMotion(dev wlroots.InputDevice, time uint32, dx float64, dy float64) { + /* This event is forwarded by the cursor when a pointer emits a _relative_ + * pointer motion event (i.e. a delta) */ + + /* The cursor doesn't move unless we tell it to. The cursor automatically + * handles constraining the motion to the output layout, as well as any + * special configuration applied for the specific input device which + * generated the event. You can pass NULL for the device if you want to move + * the cursor around without any input. */ s.cursor.Move(dev, dx, dy) s.processCursorMotion(time) } func (s *Server) handleCursorMotionAbsolute(dev wlroots.InputDevice, time uint32, x float64, y float64) { + /* This event is forwarded by the cursor when a pointer emits an _absolute_ + * motion event, from 0..1 on each axis. This happens, for example, when + * wlroots is running under a Wayland window rather than KMS+DRM, and you + * move the mouse over the window. You could enter the window from any edge, + * so we have to warp the mouse there. There is also some hardware which + * emits these events. */ s.cursor.WarpAbsolute(dev, x, y) s.processCursorMotion(time) } @@ -357,14 +343,15 @@ func (s *Server) processCursorMotion(time uint32) { return } - // if not, find the view below the cursor and send the event to that - view, surface, sx, sy := s.topLevelAt(s.cursor.X(), s.cursor.Y()) - if view == nil { - // if there is no view, set the default cursor image - // s.cursorMgr.SetCursorImage(s.cursor, "left_ptr") + /* Otherwise, find the toplevel under the pointer and send the event along. */ + topLevel, surface, sx, sy := s.topLevelAt(s.cursor.X(), s.cursor.Y()) + if topLevel == nil { + /* If there's no toplevel under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * around the screen, not over any toplevels. */ + s.cursor.SetXCursor(s.cursorMgr, "default") } - - if !surface.Nil() { + if surface != nil { /* * Send pointer enter and motion events. * @@ -376,7 +363,7 @@ func (s *Server) processCursorMotion(time uint32) { * the surface has already has pointer focus or if the client is already * aware of the coordinates passed. */ - s.seat.NotifyPointerEnter(surface, sx, sy) + s.seat.NotifyPointerEnter(*surface, sx, sy) s.seat.NotifyPointerMotion(time, sx, sy) } else { /* Clear pointer focus so future button events and such are not sent to @@ -385,26 +372,36 @@ func (s *Server) processCursorMotion(time uint32) { } } -func (s *Server) processCursorMove(time uint32) { - s.grabbedTopLevel.X = s.cursor.X() - s.grabX - s.grabbedTopLevel.Y = s.cursor.Y() - s.grabY - s.grabbedTopLevel.SceneTree.Node().SetPosition(s.grabbedTopLevel.X, s.grabbedTopLevel.Y) +func (s *Server) processCursorMove(_ uint32) { + /* Move the grabbed toplevel to the new position. */ + s.grabbedTopLevel.Base().SceneTree().Node().SetPosition(s.cursor.X()-s.grabX, s.cursor.Y()-s.grabY) } -func (s *Server) processCursorResize(time uint32) { - borderX := s.cursor.X() - s.grabX - borderY := s.cursor.Y() - s.grabY - // x := s.grabbedTopLevel.X - // y := s.grabbedTopLevel.Y +func (s *Server) processCursorResize(_ uint32) { + /* + * Resizing the grabbed toplevel can be a little bit complicated, because we + * could be resizing from any corner or edge. This not only resizes the + * toplevel on one or two axes, but can also move the toplevel if you resize + * from the top or left edges (or top-left corner). + * + * Note that some shortcuts are taken here. In a more fleshed-out + * compositor, you'd wait for the client to prepare a buffer at the new + * size, then commit any movement that was prepared. + */ + + // borderX := s.cursor.X() - s.grabX + // borderY := s.cursor.Y() - s.grabY + borderX := s.cursor.X() + borderY := s.cursor.Y() nLeft := s.grabGeobox.X - nRight := s.grabGeobox.X - s.grabGeobox.Width + nRight := s.grabGeobox.X + s.grabGeobox.Width nTop := s.grabGeobox.Y - nBottom := s.grabGeobox.Y - s.grabGeobox.Height + nBottom := s.grabGeobox.Y + s.grabGeobox.Height if s.resizeEdges&wlroots.EdgeTop != 0 { nTop = int(borderY) if nTop >= nBottom { - nTop = nBottom + 1 + nTop = nBottom - 1 } } else if s.resizeEdges&wlroots.EdgeBottom != 0 { nBottom = int(borderY) @@ -416,7 +413,7 @@ func (s *Server) processCursorResize(time uint32) { if s.resizeEdges&wlroots.EdgeLeft != 0 { nLeft = int(borderX) if nLeft >= nRight { - nLeft = nRight + 1 + nLeft = nRight - 1 } } else if s.resizeEdges&wlroots.EdgeRight != 0 { nRight = int(borderX) @@ -427,129 +424,32 @@ func (s *Server) processCursorResize(time uint32) { nWidth := nRight - nLeft nHeight := nBottom - nTop - s.grabbedTopLevel.XDGSurface().TopLevelSetSize(uint32(nWidth), uint32(nHeight)) + s.grabbedTopLevel.Base().TopLevelSetSize(uint32(nWidth), uint32(nHeight)) } -func (s *Server) handleSetCursorRequest(client wlroots.SeatClient, surface wlroots.Surface, serial uint32, hotspotX int32, hotspotY int32) { +func (s *Server) handleSetCursorRequest(client wlroots.SeatClient, surface wlroots.Surface, _ uint32, hotspotX int32, hotspotY int32) { + /* This event is raised by the seat when a client provides a cursor image */ + focusedClient := s.seat.PointerState().FocusedClient() + + /* This can be sent by any client, so we check to make sure this one is + * actually has pointer focus first. */ if focusedClient == client { + /* Once we've vetted the client, we can tell the cursor to use the + * provided surface as the cursor image. It will set the hardware cursor + * on the output that it's currently on and continue to do so as the + * cursor moves between outputs. */ s.cursor.SetSurface(surface, hotspotX, hotspotY) } } -func (s *Server) handleNewPointer(dev wlroots.InputDevice) { - s.cursor.AttachInputDevice(dev) -} - -func (s *Server) handleNewKeyboard(dev wlroots.InputDevice) { - keyboard := dev.Keyboard() - - /* We need to prepare an XKB keymap and assign it to the keyboard. This - * assumes the defaults (e.g. layout = "us"). */ - context := xkb.NewContext() - keymap := context.KeyMap() - keyboard.SetKeymap(keymap) - keymap.Destroy() - context.Destroy() - keyboard.SetRepeatInfo(25, 600) - - /* Here we set up listeners for keyboard events. */ - keyboard.OnKey(func(keyboard wlroots.Keyboard, time uint32, keyCode uint32, updateState bool, state wlroots.KeyState) { - // translate libinput keycode to xkbcommon and obtain keysyms - syms := keyboard.XKBState().Syms(xkb.KeyCode(keyCode + 8)) - - var handled bool - modifiers := keyboard.Modifiers() - if (modifiers&wlroots.KeyboardModifierAlt != 0) && state == wlroots.KeyStatePressed { - for _, sym := range syms { - handled = s.handleKeyBinding(sym) - } - } - - if !handled { - s.seat.SetKeyboard(dev) - s.seat.NotifyKeyboardKey(time, keyCode, state) - } - }) - - keyboard.OnModifiers(func(keyboard wlroots.Keyboard) { - s.seat.SetKeyboard(dev) - s.seat.NotifyKeyboardModifiers(keyboard) - }) - - s.seat.SetKeyboard(dev) - - /* And add the keyboard to our list of keyboards */ - s.keyboards = append(s.keyboards, &Keyboard{dev: dev}) -} - -func (s *Server) handleNewInput(dev wlroots.InputDevice) { - switch dev.Type() { - case wlroots.InputDeviceTypePointer: - s.handleNewPointer(dev) - case wlroots.InputDeviceTypeKeyboard: - s.handleNewKeyboard(dev) - } - - /* We need to let the wlr_seat know what our capabilities are, which is - * communicated to the client. In TinyWL we always have a cursor, even if - * there are no pointer devices, so we always include that capability. */ - caps := wlroots.SeatCapabilityPointer - if len(s.keyboards) > 0 { - caps |= wlroots.SeatCapabilityKeyboard - } - s.seat.SetCapabilities(caps) -} - -func (s *Server) handleNewXDGSurface(xdgSurface wlroots.XDGSurface) { - /* This event is raised when wlr_xdg_shell receives a new xdg xdgSurface from a - * client, either a toplevel (application window) or popup. */ - - if xdgSurface.Role() == wlroots.XDGSurfaceRolePopup { - // pTree := xdgSurface.Popup().Parent().SceneTree() - // xdgSurface.SetData(pTree) - return - } - - topLevel := NewTopLevel(xdgSurface) - topLevel.xdgTopLevel = xdgSurface.TopLevel() - topLevel.SceneTree = s.scene.Tree().XDGSurfaceCreate(topLevel.xdgTopLevel.Base()) - xdgSurface.SetData(topLevel.SceneTree) - xdgSurface.OnMap(func(surface wlroots.XDGSurface) { - topLevel.Mapped = true - s.focusTopLevel(topLevel, surface.Surface()) - }) - xdgSurface.OnUnmap(func(surface wlroots.XDGSurface) { - topLevel.Mapped = false - }) - xdgSurface.OnDestroy(func(surface wlroots.XDGSurface) { - // TODO: keep track of topLevels some other way - for i := range s.topLevels { - if s.topLevels[i] == topLevel { - s.topLevels = append(s.topLevels[:i], s.topLevels[i+1:]...) - break - } - } - }) - - toplevel := xdgSurface.TopLevel() - toplevel.OnRequestMove(func(client wlroots.SeatClient, serial uint32) { - s.beginInteractive(topLevel, CursorModeMove, 0) - }) - toplevel.OnRequestResize(func(client wlroots.SeatClient, serial uint32, edges wlroots.Edges) { - s.beginInteractive(topLevel, CursorModeResize, edges) - }) - - s.topLevels = append(s.topLevels, topLevel) -} - func (s *Server) resetCursorMode() { /* Reset the cursor mode to passthrough. */ s.cursorMode = CursorModePassThrough s.grabbedTopLevel = nil } -func (s *Server) handleCursorButton(dev wlroots.InputDevice, time uint32, button uint32, state wlroots.ButtonState) { +func (s *Server) handleCursorButton(_ wlroots.InputDevice, time uint32, button uint32, state wlroots.ButtonState) { /* This event is forwarded by the cursor when a pointer emits a button * event. */ @@ -561,14 +461,14 @@ func (s *Server) handleCursorButton(dev wlroots.InputDevice, time uint32, button s.resetCursorMode() } else { topLevel, surface, _, _ := s.topLevelAt(s.cursor.X(), s.cursor.Y()) - if topLevel != nil { - /* Focus that client if the button was _pressed_ */ - s.focusTopLevel(topLevel, surface) - } + slog.Debug("handleCursorButton", "surface", surface) + slog.Debug("handleCursorButton", "topLevel", topLevel) + /* Focus that client if the button was _pressed_ */ + s.focusTopLevel(topLevel, surface) } } -func (s *Server) handleCursorAxis(dev wlroots.InputDevice, time uint32, source wlroots.AxisSource, orientation wlroots.AxisOrientation, delta float64, deltaDiscrete int32) { +func (s *Server) handleCursorAxis(_ wlroots.InputDevice, time uint32, source wlroots.AxisSource, orientation wlroots.AxisOrientation, delta float64, deltaDiscrete int32) { /* This event is forwarded by the cursor when a pointer emits an axis event, * for example when you move the scroll wheel. */ @@ -587,52 +487,283 @@ func (s *Server) handleCursorFrame() { } func (s *Server) handleKeyBinding(sym xkb.KeySym) bool { + /* + * Here we handle compositor keybindings. This is when the compositor is + * processing keys, rather than passing them on to the client for its own + * processing. + * + * This function assumes Alt is held down. + */ switch sym { case xkb.KeySymEscape: s.display.Terminate() case xkb.KeySymF1: - if len(s.topLevels) < 2 { + /* Cycle to the next toplevel */ + if s.topLevelList.Len() < 2 { break } - - i := len(s.topLevels) - 1 - focusedView := s.topLevels[i] - nextView := s.topLevels[i-1] - - // move the focused view to the back of the view list - s.topLevels = append(s.topLevels[:i], s.topLevels[i+1:]...) - s.topLevels = append([]*TopLevel{focusedView}, s.topLevels...) - // focus the next view - s.focusTopLevel(nextView, nextView.Surface()) + nextView := s.topLevelList.Front().Next().Value.(*wlroots.XDGTopLevel) + nextSurface := nextView.Base().Surface() + s.focusTopLevel(nextView, &nextSurface) default: return false } - return true } -func (s *Server) beginInteractive(topLevel *TopLevel, mode CursorMode, edges wlroots.Edges) { +func (s *Server) handleMapXDGToplevel(xdgSurface wlroots.XDGSurface) { + /* Called when the surface is mapped, or ready to display on-screen. */ + + topLevel := xdgSurface.TopLevel() + surface := xdgSurface.Surface() + slog.Debug("handleMapXDGToplevel", "topLevel", topLevel) + slog.Debug("handleMapXDGToplevel", "s.topLevelList.Len()", s.topLevelList.Len()) + s.topLevelList.PushFront(&topLevel) + slog.Debug("handleMapXDGToplevel", "s.topLevelList.Len()", s.topLevelList.Len()) + s.focusTopLevel(&topLevel, &surface) + slog.Debug("handleMapXDGToplevel", "s.topLevelList.Len()", s.topLevelList.Len()) +} + +func (s *Server) handleUnMapXDGToplevel(xdgSurface wlroots.XDGSurface) { + /* Called when the surface is unmapped, and should no longer be shown. */ + + /* Reset the cursor mode if the grabbed toplevel was unmapped. */ + topLevel := xdgSurface.TopLevel() + if s.grabbedTopLevel != nil && topLevel == *s.grabbedTopLevel { + s.resetCursorMode() + } + s.removeTopLevel(&topLevel) +} +func (s *Server) handleNewXDGSurface(xdgSurface wlroots.XDGSurface) { + /* This event is raised when wlr_xdg_shell receives a new xdg xdgSurface from a + * client, either a toplevel (application window) or popup. */ + + if xdgSurface.Role() == wlroots.XDGSurfaceRolePopup { + + parent := xdgSurface.Popup().Parent() + if parent.Nil() { + panic("xdgSurface popup parent is nil") + } + xdgSurface.SetData(parent.XDGSurface().SceneTree().NewXDGSurface(xdgSurface)) + return + } + if xdgSurface.Role() != wlroots.XDGSurfaceRoleTopLevel { + panic("xdgSurface role is not XDGSurfaceRoleTopLevel") + } + + xdgSurface.SetData(s.scene.Tree().NewXDGSurface(xdgSurface.TopLevel().Base())) + xdgSurface.OnMap(s.handleMapXDGToplevel) + xdgSurface.OnUnmap(s.handleUnMapXDGToplevel) + xdgSurface.OnDestroy(func(surface wlroots.XDGSurface) {}) + + toplevel := xdgSurface.TopLevel() + toplevel.OnRequestMove(func(client wlroots.SeatClient, serial uint32) { + s.beginInteractive(&toplevel, CursorModeMove, 0) + }) + toplevel.OnRequestResize(func(client wlroots.SeatClient, serial uint32, edges wlroots.Edges) { + s.beginInteractive(&toplevel, CursorModeResize, edges) + }) +} + +func (s *Server) beginInteractive(topLevel *wlroots.XDGTopLevel, mode CursorMode, edges wlroots.Edges) { /* This function sets up an interactive move or resize operation, where the * compositor stops propegating pointer events to clients and instead * consumes them itself, to move or resize windows. */ - if topLevel.Surface() != s.seat.PointerState().FocusedSurface() { + if topLevel.Base().Surface() != s.seat.PointerState().FocusedSurface() { /* Deny move/resize requests from unfocused clients. */ return } - s.grabbedTopLevel = topLevel s.cursorMode = mode - box := topLevel.XDGSurface().Geometry() if mode == CursorModeMove { - s.grabX = s.cursor.X() - topLevel.X - s.grabY = s.cursor.Y() - topLevel.Y + s.grabX = s.cursor.X() - float64(topLevel.Base().SceneTree().Node().X()) + s.grabY = s.cursor.Y() - float64(topLevel.Base().SceneTree().Node().Y()) } else { - s.grabX = s.cursor.X() + float64(box.X) - s.grabY = s.cursor.Y() + float64(box.Y) + box := topLevel.Base().Geometry() + r := func() int { + if edges&wlroots.EdgeRight != 0 { + return box.Width + } else { + return 0 + } + }() + b := func() int { + if edges&wlroots.EdgeBottom != 0 { + return box.Height + } else { + return 0 + } + }() + borderX := (topLevel.Base().SceneTree().Node().X() + box.X) + r + borderY := (topLevel.Base().SceneTree().Node().Y() + box.Y) + b + s.grabX = s.cursor.X() + float64(borderX) + s.grabY = s.cursor.Y() + float64(borderY) + s.grabGeobox = box + s.grabGeobox.X += topLevel.Base().SceneTree().Node().X() + s.grabGeobox.Y += topLevel.Base().SceneTree().Node().Y() + + s.resizeEdges = edges + } +} + +func NewServer() (s *Server, err error) { + s = new(Server) + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, manging Wayland globals, and so on. */ + s.display = wlroots.NewDisplay() + + /* The backend is a wlroots feature which abstracts the underlying input and + * output hardware. The autocreate option will choose the most suitable + * backend based on the current environment, such as opening an X11 window + * if an X11 server is running. */ + s.backend, err = s.display.BackendAutocreate() + if err != nil { + return nil, err + } + + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ + s.renderer, err = s.backend.RendererAutoCreate() + if err != nil { + return nil, err + } + s.renderer.InitDisplay(s.display) + + /* Autocreates an allocator for us. + * The allocator is the bridge between the renderer and the backend. It + * handles the buffer creation, allowing wlroots to render onto the + * screen */ + s.allocator, err = s.backend.AllocatorAutocreate(s.renderer) + if err != nil { + return nil, err + } + + /* This creates some hands-off wlroots interfaces. The compositor is + * necessary for clients to allocate surfaces, the subcompositor allows to + * assign the role of subsurfaces to surfaces and the data device manager + * handles the clipboard. Each of these wlroots interfaces has room for you + * to dig your fingers in and play with their behavior if you want. Note that + * the clients cannot set the selection directly without compositor approval, + * see the handling of the request_set_selection event below.*/ + s.display.CompositorCreate(5, s.renderer) + s.display.SubCompositorCreate() + s.display.DataDeviceManagerCreate() + + /* Creates an output layout, which a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + s.outputLayout = wlroots.NewOutputLayout() + + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + s.backend.OnNewOutput(s.handleNewOutput) + + /* Create a scene graph. This is a wlroots abstraction that handles all + * rendering and damage tracking. All the compositor author needs to do + * is add things that should be rendered to the scene graph at the proper + * positions and then call wlr_scene_output_commit() to render a frame if + * necessary. + */ + s.scene = wlroots.NewScene() + s.sceneLayout = s.scene.AttachOutputLayout(s.outputLayout) + + /* Set up xdg-shell version 3. The xdg-shell is a Wayland protocol which is + * used for application windows. For more detail on shells, refer to + * https://drewdevault.com/2018/07/29/Wayland-shells.html. + */ + s.topLevelList.Init() + s.xdgShell = s.display.XDGShellCreate(3) + s.xdgShell.OnNewSurface(s.handleNewXDGSurface) + + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. + */ + s.cursor = wlroots.NewCursor() + s.cursor.AttachOutputLayout(s.outputLayout) + + /* Creates an xcursor manager, another wlroots utility which loads up + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). */ + s.cursorMgr = wlroots.NewXCursorManager("", 24) + + /* + * wlr_cursor *only* displays an image on screen. It does not move around + * when the pointer moves. However, we can attach input devices to it, and + * it will generate aggregate events for all of them. In these events, we + * can choose how we want to process them, forwarding them to clients and + * moving the cursor around. More detail on this process is described in + * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html. + * + * And more comments are sprinkled throughout the notify functions above. + */ + s.cursorMode = CursorModePassThrough + s.cursor.OnMotion(s.handleCursorMotion) + s.cursor.OnMotionAbsolute(s.handleCursorMotionAbsolute) + s.cursor.OnButton(s.handleCursorButton) + s.cursor.OnAxis(s.handleCursorAxis) + s.cursor.OnFrame(s.handleCursorFrame) + s.cursorMgr.Load(1) + + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ + s.backend.OnNewInput(s.handleNewInput) + s.seat = s.display.SeatCreate("seat0") + s.seat.OnSetCursorRequest(s.handleSetCursorRequest) + + return +} + +func (s *Server) Start() (err error) { + + var socket string + /* Add a Unix socket to the Wayland display. */ + if socket, err = s.display.AddSocketAuto(); err != nil { + s.backend.Destroy() + return } - s.resizeEdges = edges - s.grabGeobox = box + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if err = s.backend.Start(); err != nil { + s.backend.Destroy() + s.display.Destroy() + return + } + + /* Set the WAYLAND_DISPLAY environment variable to our socket and run the + * startup command if requested. */ + if err = os.Setenv("WAYLAND_DISPLAY", socket); err != nil { + return + } + + slog.Info(fmt.Sprintf("Running Wayland compositor on WAYLAND_DISPLAY=%s", socket)) + return +} + +func (s *Server) Run() error { + + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event + * loop configuration to listen to libinput events, DRM events, generate + * frame events at the refresh rate, and so on. */ + s.display.Run() + + /* Once s.display.Run() returns, we destroy all clients then shut down the + * server. */ + s.display.DestroyClients() + s.scene.Tree().Node().Destroy() + s.cursorMgr.Destroy() + s.outputLayout.Destroy() + s.display.Destroy() + return nil } diff --git a/cmd/tinywl/toplevel.go b/cmd/tinywl/toplevel.go deleted file mode 100644 index 70fcb92..0000000 --- a/cmd/tinywl/toplevel.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import "github.com/swaywm/go-wlroots/wlroots" - -type TopLevel struct { - surface wlroots.XDGSurface - Mapped bool - X float64 - Y float64 - xdgTopLevel wlroots.XDGTopLevel - SceneTree wlroots.SceneTree -} - -func NewTopLevel(surface wlroots.XDGSurface) *TopLevel { - return &TopLevel{surface: surface} -} - -func (v *TopLevel) Surface() wlroots.Surface { - return v.surface.Surface() -} - -func (v *TopLevel) XDGSurface() wlroots.XDGSurface { - return v.surface -} diff --git a/wlroots/backend.go b/wlroots/backend.go new file mode 100644 index 0000000..b862d12 --- /dev/null +++ b/wlroots/backend.go @@ -0,0 +1,93 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import ( + "errors" + "unsafe" +) + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +// #include +// #include +import "C" + +type Backend struct { + p *C.struct_wlr_backend +} + +func (b Backend) Destroy() { + C.wlr_backend_destroy(b.p) +} + +func (b Backend) OnDestroy(cb func(Backend)) { + man.add(unsafe.Pointer(b.p), &b.p.events.destroy, func(unsafe.Pointer) { + cb(b) + }) +} + +func (b Backend) Start() error { + if !C.wlr_backend_start(b.p) { + return errors.New("can't start backend") + } + + return nil +} + +func (b Backend) OnNewOutput(cb func(Output)) { + man.add(unsafe.Pointer(b.p), &b.p.events.new_output, func(data unsafe.Pointer) { + output := wrapOutput(data) + man.track(unsafe.Pointer(output.p), &output.p.events.destroy) + cb(output) + }) +} + +func (b Backend) OnNewInput(cb func(InputDevice)) { + man.add(unsafe.Pointer(b.p), &b.p.events.new_input, func(data unsafe.Pointer) { + dev := wrapInputDevice(data) + man.add(unsafe.Pointer(dev.p), &dev.p.events.destroy, func(data unsafe.Pointer) { + // delete the wlr_input_device + man.delete(unsafe.Pointer(dev.p)) + }) + cb(dev) + }) +} + +func (b Backend) AllocatorAutocreate(r Renderer) (Allocator, error) { + p := C.wlr_allocator_autocreate(b.p, r.p) + if p == nil { + return Allocator{}, errors.New("failed to wlr_allocator") + } + man.track(unsafe.Pointer(p), &p.events.destroy) + return Allocator{p: p}, nil +} + +func (b Backend) NewAllocator(r Renderer) (Allocator, error) { + return b.AllocatorAutocreate(r) +} + +func (b Backend) RendererAutoCreate() (Renderer, error) { + p := C.wlr_renderer_autocreate(b.p) + if p == nil { + return Renderer{}, errors.New("failed to create wlr_renderer") + } + man.track(unsafe.Pointer(p), &p.events.destroy) + return Renderer{p: p}, nil +} + +func (b Backend) NewRenderer() (Renderer, error) { + return b.RendererAutoCreate() +} + +type Allocator struct { + p *C.struct_wlr_allocator +} + +func (s Allocator) Nil() bool { + return s.p == nil +} diff --git a/wlroots/buffer.go b/wlroots/buffer.go new file mode 100644 index 0000000..aec1070 --- /dev/null +++ b/wlroots/buffer.go @@ -0,0 +1,49 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +import "C" + +/** + * A buffer containing pixel data. + * + * A buffer has a single producer (the party who created the buffer) and + * multiple consumers (parties reading the buffer). When all consumers are done + * with the buffer, it gets released and can be re-used by the producer. When + * the producer and all consumers are done with the buffer, it gets destroyed. + */ +type Buffer struct { + p *C.struct_wlr_buffer +} + +/** + * Unreference the buffer. This function should be called by producers when + * they are done with the buffer. + */ +func (b Buffer) Drop() { + C.wlr_buffer_drop(b.p) +} + +/** + * Lock the buffer. This function should be called by consumers to make + * sure the buffer can be safely read from. Once the consumer is done with the + * buffer, they should call wlr_buffer_unlock(). + */ +func (b Buffer) Lock() Buffer { + p := C.wlr_buffer_lock(b.p) + return Buffer{p: p} +} + +/** + * Unlock the buffer. This function should be called by consumers once they are + * done with the buffer. + */ +func (b Buffer) Unlock() { + C.wlr_buffer_unlock(b.p) +} diff --git a/wlroots/compositor.go b/wlroots/compositor.go new file mode 100644 index 0000000..f3ee3a7 --- /dev/null +++ b/wlroots/compositor.go @@ -0,0 +1,283 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import ( + "errors" + "time" + "unsafe" + + "golang.org/x/sys/unix" +) + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +// #include +// #include +// #include +// #include +// #include +import "C" + +type Compositor struct { + p *C.struct_wlr_compositor +} + +func (c Compositor) OnDestroy(cb func(Compositor)) { + man.add(unsafe.Pointer(c.p), &c.p.events.destroy, func(unsafe.Pointer) { + cb(c) + }) +} + +type SubCompositor struct { + p *C.struct_wlr_subcompositor +} + +func (c SubCompositor) OnDestroy(cb func(SubCompositor)) { + man.add(unsafe.Pointer(c.p), &c.p.events.destroy, func(unsafe.Pointer) { + cb(c) + }) +} + +type SurfaceType uint32 + +const ( + SurfaceTypeNone SurfaceType = iota + SurfaceTypeXDG + SurfaceTypeXWayland +) + +type Surface struct { + p *C.struct_wlr_surface +} + +/** + * Map the surface. If the surface is already mapped, this is no-op. + * + * This function must only be used by surface role implementations. + */ +func (s Surface) Map() { + C.wlr_surface_map(s.p) +} + +/** + * Unmap the surface. If the surface is already unmapped, this is no-op. + * + * This function must only be used by surface role implementations. + */ +func (s Surface) Unmap() { + C.wlr_surface_unmap(s.p) +} + +/** + * Whether or not this surface currently has an attached buffer. A surface has + * an attached buffer when it commits with a non-null buffer in its pending + * state. A surface will not have a buffer if it has never committed one, has + * committed a null buffer, or something went wrong with uploading the buffer. + */ +func (s Surface) HasBuffer() bool { + return bool(C.wlr_surface_has_buffer(s.p)) +} + +/** + * Get the texture of the buffer currently attached to this surface. Returns + * NULL if no buffer is currently attached or if something went wrong with + * uploading the buffer. + */ +func (s Surface) Texture() Texture { + p := C.wlr_surface_get_texture(s.p) + return Texture{p} +} + +/** + * Get the root of the subsurface tree for this surface. Can return NULL if + * a surface in the tree has been destroyed. + */ +func (s Surface) RootSurface() Surface { + p := C.wlr_surface_get_root_surface(s.p) + return Surface{p} +} + +/** + * Notify the client that the surface has entered an output. + * + * This is a no-op if the surface has already entered the output. + */ +func (s Surface) SendEnter(o Output) { + C.wlr_surface_send_enter(s.p, o.p) +} + +/** + * Notify the client that the surface has left an output. + * + * This is a no-op if the surface has already left the output. + */ +func (s Surface) SendLeave(o Output) { + C.wlr_surface_send_leave(s.p, o.p) +} + +func (s Surface) Nil() bool { + return s.p == nil +} + +func (s Surface) OnDestroy(cb func(Surface)) { + man.add(unsafe.Pointer(s.p), &s.p.events.destroy, func(unsafe.Pointer) { + cb(s) + }) +} + +func (s Surface) Type() SurfaceType { + if C.wlr_xdg_surface_try_from_wlr_surface(s.p) != nil { + return SurfaceTypeXDG + } else if C.wlr_xwayland_surface_try_from_wlr_surface(s.p) != nil { + return SurfaceTypeXWayland + } + return SurfaceTypeNone +} + +func (s Surface) SurfaceAt(sx float64, sy float64) (surface Surface, subX float64, subY float64) { + var csubX, csubY C.double + p := C.wlr_surface_surface_at(s.p, C.double(sx), C.double(sy), &csubX, &csubY) + return Surface{p: p}, float64(csubX), float64(csubY) +} + +func (s Surface) CurrentState() SurfaceState { + return SurfaceState{p: s.p.current} +} + +func (s Surface) Walk(visit func()) { + panic("not implemented") +} + +/** + * Complete the queued frame callbacks for this surface. + * + * This will send an event to the client indicating that now is a good time to + * draw its next frame. + */ +func (s Surface) SendFrameDone(when time.Time) { + // we ignore the returned error; the only possible error is + // ERANGE, when timespec on a platform has int32 precision, but + // our time requires 64 bits. This should not occur. + t, _ := unix.TimeToTimespec(when) + C.wlr_surface_send_frame_done(s.p, (*C.struct_timespec)(unsafe.Pointer(&t))) +} + +func (s Surface) XDGSurface() XDGSurface { + p := C.wlr_xdg_surface_try_from_wlr_surface(s.p) + return XDGSurface{p: p} +} + +func (s Surface) XDGTopLevel() (XDGTopLevel, error) { + p := C.wlr_xdg_toplevel_try_from_wlr_surface(s.p) + if p == nil { + return XDGTopLevel{}, errors.New("no xdg top level") + } + return XDGTopLevel{p: p}, nil +} + +/** + * Get a struct wlr_xwayland_surface from a struct wlr_surface. + * + * If the surface hasn't been created by Xwayland or has no X11 window + * associated, NULL is returned. + */ +func (s Surface) XWaylandSurface() XWaylandSurface { + p := C.wlr_xwayland_surface_try_from_wlr_surface(s.p) + return XWaylandSurface{p: p} +} + +type SurfaceStateField uint32 + +const ( + SurfaceStateBuffer SurfaceStateField = C.WLR_SURFACE_STATE_BUFFER + SurfaceStateSurfaceDamage SurfaceStateField = C.WLR_SURFACE_STATE_SURFACE_DAMAGE + SurfaceStateBufferDamage SurfaceStateField = C.WLR_SURFACE_STATE_BUFFER_DAMAGE + SurfaceStateOpaqueRegion SurfaceStateField = C.WLR_SURFACE_STATE_OPAQUE_REGION + SurfaceStateInputRegion SurfaceStateField = C.WLR_SURFACE_STATE_INPUT_REGION + SurfaceStateTransform SurfaceStateField = C.WLR_SURFACE_STATE_TRANSFORM + SurfaceStateScale SurfaceStateField = C.WLR_SURFACE_STATE_SCALE + SurfaceStateFrameCallbackList SurfaceStateField = C.WLR_SURFACE_STATE_FRAME_CALLBACK_LIST + SurfaceStateViewport SurfaceStateField = C.WLR_SURFACE_STATE_VIEWPORT + SurfaceStateOffset SurfaceStateField = C.WLR_SURFACE_STATE_OFFSET +) + +type SurfaceState struct { + p C.struct_wlr_surface_state +} + +func (s SurfaceState) Commited() SurfaceStateField { + return SurfaceStateField(s.p.committed) +} + +func (s SurfaceState) Buffer() Buffer { + return Buffer{p: s.p.buffer} +} + +func (s SurfaceState) Scale() int { + return int(s.p.scale) +} + +// relative to previous position +func (s SurfaceState) DX() int { + return int(s.p.dx) +} + +// relative to previous position +func (s SurfaceState) DY() int { + return int(s.p.dy) +} + +// in surface-local coordinates +func (s SurfaceState) Width() int { + return int(s.p.width) +} + +// in surface-local coordinates +func (s SurfaceState) Height() int { + return int(s.p.height) +} + +func (s SurfaceState) BufferWidth() int { + return int(s.p.buffer_width) +} + +func (s SurfaceState) BufferHeight() int { + return int(s.p.buffer_height) +} + +func (s SurfaceState) Transform() uint32 { + return uint32(s.p.transform) +} + +type SurfaceRole struct { + p *C.struct_wlr_surface_role +} + +func (s SurfaceRole) Name() string { + return C.GoString(s.p.name) +} + +/** + * If true, the role isn't represented by any object. + * For example, this applies to cursor surfaces. + */ +func (s SurfaceRole) NoObject() bool { + return bool(s.p.no_object) +} + +type SurfaceOutput struct { + p *C.struct_wlr_surface_output +} + +func (s SurfaceOutput) Surface() Surface { + return Surface{p: s.p.surface} +} + +func (s SurfaceOutput) Output() Output { + return Output{p: s.p.output} +} diff --git a/wlroots/cursor.go b/wlroots/cursor.go new file mode 100644 index 0000000..3036a02 --- /dev/null +++ b/wlroots/cursor.go @@ -0,0 +1,192 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import "unsafe" + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +// #include +// #include +// #include +import "C" + +/** + * wlr_cursor implements the behavior of the "cursor", that is, the image on the + * screen typically moved about with a mouse or so. It provides tracking for + * this in global coordinates, and integrates with struct wlr_output, + * struct wlr_output_layout, and struct wlr_input_device. You can use it to + * abstract multiple input devices over a single cursor, constrain cursor + * movement to the usable area of a struct wlr_output_layout and communicate + * position updates to the hardware cursor, constrain specific input devices to + * specific outputs or regions of the screen, and so on. + */ +type Cursor struct { + p *C.struct_wlr_cursor +} + +func NewCursor() Cursor { + p := C.wlr_cursor_create() + return Cursor{p: p} +} + +func (c Cursor) Destroy() { + C.wlr_cursor_destroy(c.p) + man.delete(unsafe.Pointer(c.p)) +} + +func (c Cursor) X() float64 { + return float64(c.p.x) +} + +func (c Cursor) Y() float64 { + return float64(c.p.y) +} + +/** + * Uses the given layout to establish the boundaries and movement semantics of + * this cursor. Cursors without an output layout allow infinite movement in any + * direction and do not support absolute input events. + */ +func (c Cursor) AttachOutputLayout(layout OutputLayout) { + C.wlr_cursor_attach_output_layout(c.p, layout.p) +} + +/** + * Attaches this cursor to the given output, which must be among the outputs in + * the current output_layout for this cursor. This call is invalid for a cursor + * without an associated output layout. + */ +func (c Cursor) MapToOutput(output Output) { + C.wlr_cursor_map_to_output(c.p, output.p) +} + +/** + * Maps all input from a specific input device to a given output. The input + * device must be attached to this cursor and the output must be among the + * outputs in the attached output layout. + */ +func (c Cursor) MapInputToOutput(input InputDevice, output Output) { + C.wlr_cursor_map_input_to_output(c.p, input.p, output.p) +} + +/** + * Maps this cursor to an arbitrary region on the associated + * struct wlr_output_layout. + */ +func (c Cursor) MapToRegion(box GeoBox) { + C.wlr_cursor_map_to_region(c.p, box.p) +} + +/** + * Maps inputs from this input device to an arbitrary region on the associated + * struct wlr_output_layout. + */ +func (c Cursor) MapInputToRegion(dev InputDevice, box GeoBox) { + C.wlr_cursor_map_input_to_region(c.p, dev.p, box.p) +} + +/** + * Attaches this input device to this cursor. The input device must be one of: + * + * - WLR_INPUT_DEVICE_POINTER + * - WLR_INPUT_DEVICE_TOUCH + * - WLR_INPUT_DEVICE_TABLET_TOOL + */ +func (c Cursor) AttachInputDevice(dev InputDevice) { + C.wlr_cursor_attach_input_device(c.p, dev.p) +} + +func (c Cursor) DetachInputDevice(dev InputDevice) { + C.wlr_cursor_detach_input_device(c.p, dev.p) +} + +/** + * Set the cursor image from an XCursor theme. + * + * The image will be loaded from the struct wlr_xcursor_manager. + */ +func (c Cursor) SetXCursor(cm XCursorManager, name string) { + C.wlr_cursor_set_xcursor(c.p, cm.p, C.CString(name)) +} + +/** + * Move the cursor in the direction of the given x and y layout coordinates. If + * one coordinate is NAN, it will be ignored. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + */ +func (c Cursor) Move(dev InputDevice, dx float64, dy float64) { + C.wlr_cursor_move(c.p, dev.p, C.double(dx), C.double(dy)) +} + +/** + * Warp the cursor to the given x and y in absolute 0..1 coordinates. If the + * given point is out of the layout boundaries or constraints, the closest point + * will be used. If one coordinate is NAN, it will be ignored. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + */ +func (c Cursor) WarpAbsolute(dev InputDevice, x float64, y float64) { + C.wlr_cursor_warp_absolute(c.p, dev.p, C.double(x), C.double(y)) +} + +/** + * Set the cursor surface. The surface can be committed to update the cursor + * image. The surface position is subtracted from the hotspot. A NULL surface + * commit hides the cursor. + */ +func (c Cursor) SetSurface(surface Surface, hotspotX int32, hotspotY int32) { + C.wlr_cursor_set_surface(c.p, surface.p, C.int32_t(hotspotX), C.int32_t(hotspotY)) +} + +/** + * Hide the cursor image. + */ +func (c Cursor) UnsetImage() { + C.wlr_cursor_unset_image(c.p) +} + +func (c Cursor) OnMotion(cb func(dev InputDevice, time uint32, dx float64, dy float64)) { + man.add(unsafe.Pointer(c.p), &c.p.events.motion, func(data unsafe.Pointer) { + event := (*C.struct_wlr_pointer_motion_event)(data) + dev := InputDevice{p: &event.pointer.base} + cb(dev, uint32(event.time_msec), float64(event.delta_x), float64(event.delta_y)) + }) +} + +func (c Cursor) OnMotionAbsolute(cb func(dev InputDevice, time uint32, x float64, y float64)) { + man.add(unsafe.Pointer(c.p), &c.p.events.motion_absolute, func(data unsafe.Pointer) { + event := (*C.struct_wlr_pointer_motion_absolute_event)(data) + dev := InputDevice{p: &event.pointer.base} + cb(dev, uint32(event.time_msec), float64(event.x), float64(event.y)) + }) +} + +func (c Cursor) OnButton(cb func(dev InputDevice, time uint32, button uint32, state ButtonState)) { + man.add(unsafe.Pointer(c.p), &c.p.events.button, func(data unsafe.Pointer) { + event := (*C.struct_wlr_pointer_button_event)(data) + dev := InputDevice{p: &event.pointer.base} + cb(dev, uint32(event.time_msec), uint32(event.button), ButtonState(event.state)) + }) +} + +func (c Cursor) OnAxis(cb func(dev InputDevice, time uint32, source AxisSource, orientation AxisOrientation, delta float64, deltaDiscrete int32)) { + man.add(unsafe.Pointer(c.p), &c.p.events.axis, func(data unsafe.Pointer) { + event := (*C.struct_wlr_pointer_axis_event)(data) + dev := InputDevice{p: &event.pointer.base} + cb(dev, uint32(event.time_msec), AxisSource(event.source), AxisOrientation(event.orientation), float64(event.delta), int32(event.delta_discrete)) + }) +} + +func (c Cursor) OnFrame(cb func()) { + man.add(unsafe.Pointer(c.p), &c.p.events.frame, func(data unsafe.Pointer) { + cb() + }) +} diff --git a/wlroots/input_device.go b/wlroots/input_device.go new file mode 100644 index 0000000..7c047ce --- /dev/null +++ b/wlroots/input_device.go @@ -0,0 +1,88 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import ( + "fmt" + "unsafe" +) + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +// #include +// #include +import "C" + +type ( + InputDeviceType uint32 + ButtonState uint32 + AxisSource uint32 + AxisOrientation uint32 +) + +var inputDeviceNames = []string{ + InputDeviceTypeKeyboard: "keyboard", + InputDeviceTypePointer: "pointer", + InputDeviceTypeTouch: "touch", + InputDeviceTypeTabletTool: "tablet tool", + InputDeviceTypeTabletPad: "tablet pad", +} + +const ( + InputDeviceTypeKeyboard InputDeviceType = C.WLR_INPUT_DEVICE_KEYBOARD + InputDeviceTypePointer InputDeviceType = C.WLR_INPUT_DEVICE_POINTER + InputDeviceTypeTouch InputDeviceType = C.WLR_INPUT_DEVICE_TOUCH + InputDeviceTypeTabletTool InputDeviceType = C.WLR_INPUT_DEVICE_TABLET_TOOL + InputDeviceTypeTabletPad InputDeviceType = C.WLR_INPUT_DEVICE_TABLET_PAD + InputDeviceTypeSwitch InputDeviceType = C.WLR_INPUT_DEVICE_SWITCH + + ButtonStateReleased ButtonState = C.WLR_BUTTON_RELEASED + ButtonStatePressed ButtonState = C.WLR_BUTTON_PRESSED + + AxisSourceWheel AxisSource = C.WLR_AXIS_SOURCE_WHEEL + AxisSourceFinger AxisSource = C.WLR_AXIS_SOURCE_FINGER + AxisSourceContinuous AxisSource = C.WLR_AXIS_SOURCE_CONTINUOUS + AxisSourceWheelTilt AxisSource = C.WLR_AXIS_SOURCE_WHEEL_TILT + + AxisOrientationVertical AxisOrientation = C.WLR_AXIS_ORIENTATION_VERTICAL + AxisOrientationHorizontal AxisOrientation = C.WLR_AXIS_ORIENTATION_HORIZONTAL +) + +type InputDevice struct { + p *C.struct_wlr_input_device +} + +func (d InputDevice) OnDestroy(cb func(InputDevice)) { + man.add(unsafe.Pointer(d.p), &d.p.events.destroy, func(unsafe.Pointer) { + cb(d) + }) +} + +func (d InputDevice) Type() InputDeviceType { return InputDeviceType(d.p._type) } +func (d InputDevice) Vendor() int { return int(d.p.vendor) } +func (d InputDevice) Product() int { return int(d.p.product) } +func (d InputDevice) Name() string { return C.GoString(d.p.name) } + +func validateInputDeviceType(d InputDevice, fn string, req InputDeviceType) { + if typ := d.Type(); typ != req { + if int(typ) >= len(inputDeviceNames) { + panic(fmt.Sprintf("%s called on input device of type %d", fn, typ)) + } else { + panic(fmt.Sprintf("%s called on input device of type %s", fn, inputDeviceNames[typ])) + } + } +} + +func (d InputDevice) Keyboard() Keyboard { + validateInputDeviceType(d, "Keyboard", InputDeviceTypeKeyboard) + p := *(*unsafe.Pointer)(unsafe.Pointer(&d.p)) + return Keyboard{p: (*C.struct_wlr_keyboard)(p)} +} + +func wrapInputDevice(p unsafe.Pointer) InputDevice { + return InputDevice{p: (*C.struct_wlr_input_device)(p)} +} diff --git a/wlroots/keyboard.go b/wlroots/keyboard.go new file mode 100644 index 0000000..47047b3 --- /dev/null +++ b/wlroots/keyboard.go @@ -0,0 +1,103 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import ( + "unsafe" + + "github.com/swaywm/go-wlroots/xkb" +) + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +// #include +// #include +import "C" + +type ( + KeyState uint32 + KeyboardModifier uint32 + KeyboardModifiers uint32 + KeyboardLed uint32 +) + +const ( + KeyboardLedCount = C.WLR_LED_COUNT + KeyboardModifierCount = C.WLR_MODIFIER_COUNT + KeyboardKeysCap = C.WLR_KEYBOARD_KEYS_CAP + KeyStateReleased KeyState = C.WL_KEYBOARD_KEY_STATE_RELEASED + KeyStatePressed KeyState = C.WL_KEYBOARD_KEY_STATE_PRESSED + + KeyboardModifierShift KeyboardModifier = C.WLR_MODIFIER_SHIFT + KeyboardModifierCaps KeyboardModifier = C.WLR_MODIFIER_CAPS + KeyboardModifierCtrl KeyboardModifier = C.WLR_MODIFIER_CTRL + KeyboardModifierAlt KeyboardModifier = C.WLR_MODIFIER_ALT + KeyboardModifierMod2 KeyboardModifier = C.WLR_MODIFIER_MOD2 + KeyboardModifierMod3 KeyboardModifier = C.WLR_MODIFIER_MOD3 + KeyboardModifierLogo KeyboardModifier = C.WLR_MODIFIER_LOGO + KeyboardModifierMod5 KeyboardModifier = C.WLR_MODIFIER_MOD5 + + KeyboardLedNumLock KeyboardLed = C.WLR_LED_NUM_LOCK + KeyboardLedCapsLock KeyboardLed = C.WLR_LED_CAPS_LOCK + KeyboardLedScrollLock KeyboardLed = C.WLR_LED_SCROLL_LOCK +) + +/** + * Get a struct wlr_keyboard from a struct wlr_input_device. + * + * Asserts that the input device is a keyboard. + */ +type Keyboard struct { + p *C.struct_wlr_keyboard +} + +func (k Keyboard) SetKeymap(keymap xkb.Keymap) { + C.wlr_keyboard_set_keymap(k.p, (*C.struct_xkb_keymap)(keymap.Ptr())) +} + +func (k Keyboard) RepeatInfo() (rate int32, delay int32) { + return int32(k.p.repeat_info.rate), int32(k.p.repeat_info.delay) +} + +func (k Keyboard) SetRepeatInfo(rate int32, delay int32) { + C.wlr_keyboard_set_repeat_info(k.p, C.int32_t(rate), C.int32_t(delay)) +} + +func (k Keyboard) Base() InputDevice { + return InputDevice{p: &k.p.base} +} + +func (k Keyboard) XKBState() xkb.State { + return xkb.WrapState(unsafe.Pointer(k.p.xkb_state)) +} + +func (k Keyboard) Leds() int { + return int(k.p.leds) +} + +func (k Keyboard) Modifiers() KeyboardModifier { + return KeyboardModifier(C.wlr_keyboard_get_modifiers(k.p)) +} + +func (k Keyboard) OnModifiers(cb func(keyboard Keyboard)) { + man.add(unsafe.Pointer(k.p), &k.p.events.modifiers, func(data unsafe.Pointer) { + cb(k) + }) +} + +func (k Keyboard) OnDestroy(cb func(keyboard Keyboard)) { + man.add(unsafe.Pointer(k.p), &k.p.base.events.destroy, func(data unsafe.Pointer) { + cb(k) + }) +} + +func (k Keyboard) OnKey(cb func(keyboard Keyboard, time uint32, keyCode uint32, updateState bool, state KeyState)) { + man.add(unsafe.Pointer(k.p), &k.p.events.key, func(data unsafe.Pointer) { + event := (*C.struct_wlr_keyboard_key_event)(data) + cb(k, uint32(event.time_msec), uint32(event.keycode), bool(event.update_state), KeyState(event.state)) + }) +} diff --git a/wlroots/listener_manager.go b/wlroots/listener_manager.go new file mode 100644 index 0000000..24f3251 --- /dev/null +++ b/wlroots/listener_manager.go @@ -0,0 +1,155 @@ +package wlroots + +import ( + "sync" + "time" + "unsafe" +) + +// This whole mess has to exist for a number of reasons: +// +// 1. We need to allocate all instances of wl_listener on the heap as storing Go +// pointers in C after a cgo call returns is not allowed. +// +// 2. The wlroots library implicitly destroys objects when wl_display is +// destroyed. So, we need to keep track of all objects (and their listeners) +// manually and listen for the destroy signal to be able to free everything. +// +// 3 (TODO). As we're keeping track of all objects anyway, we might as well +// store a Go pointer to the wrapper struct along with them in order to be able +// to pass the same Go pointer through callbacks every time. This will also +// allow calling runtime.SetFinalizer on some of them to clean them up early +// when the GC notices it has gone out of scope. +// +// Send help. + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +// #include +// +// void _wl_listener_cb(struct wl_listener *listener, void *data); +// +// static inline void _wl_listener_set_cb(struct wl_listener *listener) { +// listener->notify = &_wl_listener_cb; +// } +// +import "C" + +type EventLoop struct { + p *C.struct_wl_event_loop +} + +func (evl EventLoop) OnDestroy(cb func(EventLoop)) { + l := man.add(unsafe.Pointer(evl.p), nil, func(data unsafe.Pointer) { + cb(evl) + }) + C.wl_event_loop_add_destroy_listener(evl.p, l.p) +} + +func (evl EventLoop) Fd() uintptr { + return uintptr(C.wl_event_loop_get_fd(evl.p)) +} + +func (evl EventLoop) Dispatch(timeout time.Duration) { + var d int + if timeout >= 0 { + d = int(timeout / time.Millisecond) + } else { + d = -1 + } + C.wl_event_loop_dispatch(evl.p, C.int(d)) +} + +type ( + listenerCallback func(data unsafe.Pointer) +) + +type manager struct { + mutex sync.RWMutex + objects map[unsafe.Pointer][]*listener + listeners map[*C.struct_wl_listener]*listener +} + +type listener struct { + p *C.struct_wl_listener + s *C.struct_wl_signal + cbs []listenerCallback +} + +var ( + man = &manager{ + objects: map[unsafe.Pointer][]*listener{}, + listeners: map[*C.struct_wl_listener]*listener{}, + } +) + +//export _wl_listener_cb +func _wl_listener_cb(listener *C.struct_wl_listener, data unsafe.Pointer) { + man.mutex.RLock() + l := man.listeners[listener] + man.mutex.RUnlock() + for _, cb := range l.cbs { + cb(data) + } +} + +func (m *manager) add(p unsafe.Pointer, signal *C.struct_wl_signal, cb listenerCallback) *listener { + m.mutex.Lock() + defer m.mutex.Unlock() + + // if a listener for this object and signal already exists, add the callback + // to the existing listener + if signal != nil { + for _, l := range m.objects[p] { + if l.s != nil && l.s == signal { + l.cbs = append(l.cbs, cb) + return l + } + } + } + + lp := (*C.struct_wl_listener)(C.calloc(C.sizeof_struct_wl_listener, 1)) + C._wl_listener_set_cb(lp) + if signal != nil { + C.wl_signal_add((*C.struct_wl_signal)(signal), lp) + } + + l := &listener{ + p: lp, + s: signal, + cbs: []listenerCallback{cb}, + } + m.listeners[lp] = l + m.objects[p] = append(m.objects[p], l) + + return l +} + +func (m *manager) has(p unsafe.Pointer) bool { + m.mutex.RLock() + _, found := m.objects[p] + m.mutex.RUnlock() + return found +} + +func (m *manager) delete(p unsafe.Pointer) { + m.mutex.Lock() + defer m.mutex.Unlock() + + for _, l := range m.objects[p] { + delete(m.listeners, l.p) + + // remove the listener from the signal + C.wl_list_remove(&l.p.link) + + // free the listener + C.free(unsafe.Pointer(l.p)) + } + + delete(m.objects, p) +} + +func (m *manager) track(p unsafe.Pointer, destroySignal *C.struct_wl_signal) { + m.add(p, destroySignal, func(data unsafe.Pointer) { m.delete(p) }) +} diff --git a/wlroots/log.go b/wlroots/log.go new file mode 100644 index 0000000..895d665 --- /dev/null +++ b/wlroots/log.go @@ -0,0 +1,64 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +// #include +// #include +// +// +// void _wlr_log_cb(enum wlr_log_importance importance, char *msg); +// +// +// static inline void _wlr_log_inner_cb(enum wlr_log_importance importance, const char *fmt, va_list args) { +// char *msg = NULL; +// if (vasprintf(&msg, fmt, args) == -1) { +// return; +// } +// +// _wlr_log_cb(importance, msg); +// free(msg); +// } +// +// +// static inline void _wlr_log_set_cb(enum wlr_log_importance verbosity, bool is_set) { +// wlr_log_init(verbosity, is_set ? &_wlr_log_inner_cb : NULL); +// } +// +// +import "C" + +type ( + LogImportance uint32 + LogFunc func(importance LogImportance, msg string) +) + +const ( + LogImportanceSilent LogImportance = C.WLR_SILENT + LogImportanceError LogImportance = C.WLR_ERROR + LogImportanceInfo LogImportance = C.WLR_INFO + LogImportanceDebug LogImportance = C.WLR_DEBUG +) + +var ( + onLog LogFunc +) + +//export _wlr_log_cb +func _wlr_log_cb(importance LogImportance, msg *C.char) { + if onLog != nil { + onLog(importance, C.GoString(msg)) + } +} + +func OnLog(verbosity LogImportance, cb LogFunc) { + C._wlr_log_set_cb(C.enum_wlr_log_importance(verbosity), cb != nil) + onLog = cb +} diff --git a/wlroots/output.go b/wlroots/output.go new file mode 100644 index 0000000..44926de --- /dev/null +++ b/wlroots/output.go @@ -0,0 +1,420 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import ( + "errors" + "unsafe" +) + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +// #include +// #include +import "C" + +/** + * A compositor output region. This typically corresponds to a monitor that + * displays part of the compositor space. + * + * The `frame` event will be emitted when it is a good time for the compositor + * to submit a new frame. + * + * To render a new frame, compositors should call wlr_output_attach_render(), + * render and call wlr_output_commit(). No rendering should happen outside a + * `frame` event handler or before wlr_output_attach_render(). + */ +type Output struct { + p *C.struct_wlr_output +} + +func wrapOutput(p unsafe.Pointer) Output { + return Output{p: (*C.struct_wlr_output)(p)} +} + +func (o Output) Name() string { + return C.GoString(o.p.name) +} + +func (o Output) Scale() float32 { + return float32(o.p.scale) +} + +func (o Output) TransformMatrix() Matrix { + var matrix Matrix + matrix.fromC(&o.p.transform_matrix) + return matrix +} + +func (o Output) OnFrame(cb func(Output)) { + man.add(unsafe.Pointer(o.p), &o.p.events.frame, func(data unsafe.Pointer) { + cb(o) + }) +} + +func (o Output) OnRequestState(cb func(Output, OutputState)) { + man.add(unsafe.Pointer(o.p), &o.p.events.request_state, func(data unsafe.Pointer) { + cb(o, OutputState{p: (*C.struct_wlr_output_state)(data)}) + }) +} + +func (o Output) OnDestroy(cb func(Output)) { + man.add(unsafe.Pointer(o.p), &o.p.events.destroy, func(data unsafe.Pointer) { + cb(o) + }) +} + +func (o Output) RenderSoftwareCursors() { + C.wlr_output_render_software_cursors(o.p, nil) +} + +/** + * Computes the transformed output resolution. + */ +func (o Output) TransformedResolution() (int, int) { + var width, height C.int + C.wlr_output_transformed_resolution(o.p, &width, &height) + return int(width), int(height) +} + +/** + * Computes the transformed and scaled output resolution. + */ +func (o Output) EffectiveResolution() (int, int) { + var width, height C.int + C.wlr_output_effective_resolution(o.p, &width, &height) + return int(width), int(height) +} + +/** + * Attach the renderer's buffer to the output. Compositors must call this + * function before rendering. After they are done rendering, they should call + * wlr_output_commit() to submit the new frame. The output needs to be + * enabled. + * + * If non-NULL, `buffer_age` is set to the drawing buffer age in number of + * frames or -1 if unknown. This is useful for damage tracking. + * + * If the compositor decides not to render after calling this function, it + * must call wlr_output_rollback(). + */ +func (o Output) AttachRender() (int, error) { + var bufferAge C.int + if !C.wlr_output_attach_render(o.p, &bufferAge) { + return 0, errors.New("can't make output context current") + } + + return int(bufferAge), nil +} + +/** + * Schedule a done event. + * + * This is intended to be used by wl_output add-on interfaces. + */ +func (o Output) ScheduleDone() { + C.wlr_output_schedule_done(o.p) +} + +func (o Output) Destroy() { + C.wlr_output_destroy(o.p) +} + +/** + * Test whether the pending output state would be accepted by the backend. If + * this function returns true, wlr_output_commit() can only fail due to a + * runtime error. + * + * This function doesn't mutate the pending state. + */ +func (o Output) Test() bool { + return bool(C.wlr_output_test(o.p)) +} + +/** + * Commit the pending output state. If wlr_output_attach_render() has been + * called, the pending frame will be submitted for display and a `frame` event + * will be scheduled. + * + * On failure, the pending changes are rolled back. + */ +func (o Output) Commit() bool { + return bool(C.wlr_output_commit(o.p)) +} + +/** + * Discard the pending output state. + */ +func (o Output) Rollback() { + C.wlr_output_rollback(o.p) +} + +/** + * Test whether this output state would be accepted by the backend. If this + * function returns true, wlr_output_commit_state() will only fail due to a + * runtime error. This function does not change the current state of the + * output. + */ +func (o Output) TestState(s OutputState) bool { + return bool(C.wlr_output_test_state(o.p, s.p)) +} + +/** + * Attempts to apply the state to this output. This function may fail for any + * reason and return false. If failed, none of the state would have been applied, + * this function is atomic. If the commit succeeded, true is returned. + * + * Note: wlr_output_state_finish() would typically be called after the state + * has been committed. + */ +func (o Output) CommitState(s OutputState) bool { + return bool(C.wlr_output_commit_state(o.p, s.p)) +} + +/** + * Manually schedules a `frame` event. If a `frame` event is already pending, + * it is a no-op. + */ +func (o Output) ScheduleFrame() { + C.wlr_output_schedule_frame(o.p) +} + +func (o Output) Modes() []OutputMode { + // TODO: figure out what to do with this ridiculous for loop + // perhaps this can be refactored into a less ugly hack that uses reflection + var modes []OutputMode + var mode *C.struct_wlr_output_mode + for mode := (*C.struct_wlr_output_mode)(unsafe.Pointer(uintptr(unsafe.Pointer(o.p.modes.next)) - unsafe.Offsetof(mode.link))); &mode.link != &o.p.modes; mode = (*C.struct_wlr_output_mode)(unsafe.Pointer(uintptr(unsafe.Pointer(mode.link.next)) - unsafe.Offsetof(mode.link))) { + modes = append(modes, OutputMode{p: mode}) + } + + return modes +} + +/** + * Sets the output mode. The output needs to be enabled. + * + * Mode is double-buffered state, see wlr_output_commit(). + */ +func (o Output) SetMode(mode OutputMode) { + C.wlr_output_set_mode(o.p, mode.p) +} + +/** + * Returns the preferred mode for this output. If the output doesn't support + * modes, returns NULL. + */ +func (o Output) PrefferedMode() (OutputMode, error) { + mode := C.wlr_output_preferred_mode(o.p) + if mode == nil { + return OutputMode{}, errors.New("no preferred mode") + } + return OutputMode{p: mode}, nil +} + +/** + * Sets a custom mode on the output. + * + * When the output advertises fixed modes, custom modes are not guaranteed to + * work correctly, they may result in visual artifacts. If a suitable fixed mode + * is available, compositors should prefer it and use wlr_output_set_mode() + * instead of custom modes. + * + * Setting `refresh` to zero lets the backend pick a preferred value. The + * output needs to be enabled. + * + * Custom mode is double-buffered state, see wlr_output_commit(). + */ +func (o Output) SetCustomMode(width int, height int, refresh int) { + C.wlr_output_set_custom_mode(o.p, C.int(width), C.int(height), C.int(refresh)) +} + +/** + * Enables or disables adaptive sync (ie. variable refresh rate) on this + * output. On some backends, this is just a hint and may be ignored. + * Compositors can inspect `wlr_output.adaptive_sync_status` to query the + * effective status. Backends that don't support adaptive sync will reject + * the output commit. + * + * When enabled, compositors can submit frames a little bit later than the + * deadline without dropping a frame. + * + * Adaptive sync is double-buffered state, see wlr_output_commit(). + */ +func (o Output) EnableAdaptiveSync(enable bool) { + C.wlr_output_enable_adaptive_sync(o.p, C.bool(enable)) +} + +/** + * Sets a scale for the output. + * + * Scale is double-buffered state, see wlr_output_commit(). + */ +func (o Output) SetScale(scale float32) { + C.wlr_output_set_scale(o.p, C.float(scale)) +} + +/** + * Set the output name. + * + * Output names are subject to the following rules: + * + * - Each output name must be unique. + * - The name cannot change after the output has been advertised to clients. + * + * For more details, see the protocol documentation for wl_output.name. + */ +func (o Output) SetName(name string) { + C.wlr_output_set_name(o.p, C.CString(name)) +} + +func (o Output) SetDescription(desc string) { + C.wlr_output_set_description(o.p, C.CString(desc)) +} + +/** + * Enables or disables the output. A disabled output is turned off and doesn't + * emit `frame` events. + * + * Whether an output is enabled is double-buffered state, see + * wlr_output_commit(). + */ +func (o Output) Enable(enable bool) { + C.wlr_output_enable(o.p, C.bool(enable)) +} + +func (o Output) Enabled() bool { + return bool(o.p.enabled) +} + +func (o Output) Refresh() int { + return int(o.p.refresh) +} + +func (o Output) CreateGlobal() { + C.wlr_output_create_global(o.p) +} + +func (o Output) DestroyGlobal() { + C.wlr_output_destroy_global(o.p) +} + +func (o Output) SetTitle(title string) error { + if C.wlr_output_is_wl(o.p) { + C.wlr_wl_output_set_title(o.p, C.CString(title)) + } else if C.wlr_output_is_x11(o.p) { + C.wlr_x11_output_set_title(o.p, C.CString(title)) + } else { + return errors.New("this output type cannot have a title") + } + + return nil +} + +/** + * Initialize the output's rendering subsystem with the provided allocator and + * renderer. After initialization, this function may invoked again to reinitialize + * the allocator and renderer to different values. + * + * Call this function prior to any call to wlr_output_attach_render(), + * wlr_output_commit() or wlr_output_cursor_create(). + * + * The buffer capabilities of the provided must match the capabilities of the + * output's backend. Returns false otherwise. + */ +func (o Output) InitRender(a Allocator, r Renderer) bool { + return bool(C.wlr_output_init_render(o.p, a.p, r.p)) +} + +/** + * Holds the double-buffered output state. + */ +type OutputState struct { + p *C.struct_wlr_output_state +} + +func NewOutputState() OutputState { + return OutputState{p: &C.struct_wlr_output_state{}} +} + +func (os OutputState) StateInit() { + C.wlr_output_state_init(os.p) +} + +func (os OutputState) StateSetEnabled(enabled bool) { + C.wlr_output_state_set_enabled(os.p, C.bool(enabled)) +} + +func (os OutputState) SetMode(mode OutputMode) { + C.wlr_output_state_set_mode(os.p, mode.p) +} + +func (os OutputState) Finish() { + C.wlr_output_state_finish(os.p) +} + +func OutputTransformInvert(transform uint32) uint32 { + return uint32(C.wlr_output_transform_invert(C.enum_wl_output_transform(transform))) +} + +type OutputMode struct { + p *C.struct_wlr_output_mode +} + +func (m OutputMode) Width() int { + return int(m.p.width) +} + +func (m OutputMode) Height() int { + return int(m.p.height) +} + +// mHz +func (m OutputMode) Refresh() int { + return int(m.p.refresh) +} + +func (m OutputMode) Preferred() bool { + return bool(m.p.preferred) +} + +func (m OutputMode) PictureAspectRatio() OutputModeAspectRatio { + return OutputModeAspectRatio(m.p.picture_aspect_ratio) +} + +type OutputModeAspectRatio uint32 + +const ( + OutputModeAspectRatio_None OutputModeAspectRatio = C.WLR_OUTPUT_MODE_ASPECT_RATIO_NONE + OutputModeAspectRatio_4_3 OutputModeAspectRatio = C.WLR_OUTPUT_MODE_ASPECT_RATIO_4_3 + OutputModeAspectRatio_16_9 OutputModeAspectRatio = C.WLR_OUTPUT_MODE_ASPECT_RATIO_16_9 + OutputModeAspectRatio_64_27 OutputModeAspectRatio = C.WLR_OUTPUT_MODE_ASPECT_RATIO_64_27 + OutputModeAspectRatio_256_135 OutputModeAspectRatio = C.WLR_OUTPUT_MODE_ASPECT_RATIO_256_135 +) + +type OutputStateField uint32 + +const ( + OutputState_BUFFER OutputStateField = C.WLR_OUTPUT_STATE_BUFFER + OutputState_DAMAGE OutputStateField = C.WLR_OUTPUT_STATE_DAMAGE + OutputState_MODE OutputStateField = C.WLR_OUTPUT_STATE_MODE + OutputState_ENABLED OutputStateField = C.WLR_OUTPUT_STATE_ENABLED + OutputState_SCALE OutputStateField = C.WLR_OUTPUT_STATE_SCALE + OutputState_TRANSFORM OutputStateField = C.WLR_OUTPUT_STATE_TRANSFORM + OutputState_ADAPTIVE_SYNC_ENABLED OutputStateField = C.WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED + OutputState_GAMMA_LUT OutputStateField = C.WLR_OUTPUT_STATE_GAMMA_LUT + OutputState_RENDER_FORMAT OutputStateField = C.WLR_OUTPUT_STATE_RENDER_FORMAT + OutputState_SUBPIXEL OutputStateField = C.WLR_OUTPUT_STATE_SUBPIXEL + OutputState_LAYERS OutputStateField = C.WLR_OUTPUT_STATE_LAYERS +) + +type OutputSdaptiveSyncStatus uint32 + +const ( + OutputSdaptiveSync_Disabled OutputSdaptiveSyncStatus = C.WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED + OutputSdaptiveSync_Enabled OutputSdaptiveSyncStatus = C.WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED +) diff --git a/wlroots/renderer.go b/wlroots/renderer.go new file mode 100644 index 0000000..610efd7 --- /dev/null +++ b/wlroots/renderer.go @@ -0,0 +1,56 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import "unsafe" + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +import "C" + +type Renderer struct { + p *C.struct_wlr_renderer +} + +func (r Renderer) Destroy() { + C.wlr_renderer_destroy(r.p) +} + +func (r Renderer) OnDestroy(cb func(Renderer)) { + man.add(unsafe.Pointer(r.p), &r.p.events.destroy, func(unsafe.Pointer) { + cb(r) + }) +} + +func (r Renderer) InitDisplay(display Display) { + C.wlr_renderer_init_wl_display(r.p, display.p) +} + +func (r Renderer) Begin(output Output, width int, height int) { + C.wlr_renderer_begin(r.p, C.uint(width), C.uint(height)) +} + +func (r Renderer) Clear(color *Color) { + c := color.toC() + C.wlr_renderer_clear(r.p, &c[0]) +} + +func (r Renderer) End() { + C.wlr_renderer_end(r.p) +} + +func (r Renderer) RenderTextureWithMatrix(texture Texture, matrix *Matrix, alpha float32) { + m := matrix.toC() + C.wlr_render_texture_with_matrix(r.p, texture.p, &m[0], C.float(alpha)) +} + +func (r Renderer) RenderRect(box *GeoBox, color *Color, projection *Matrix) { + b := box.toC() + c := color.toC() + pm := projection.toC() + C.wlr_render_rect(r.p, &b, &c[0], &pm[0]) +} diff --git a/wlroots/scene.go b/wlroots/scene.go new file mode 100644 index 0000000..82ea5dc --- /dev/null +++ b/wlroots/scene.go @@ -0,0 +1,346 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import ( + "errors" + "time" + "unsafe" + + "golang.org/x/sys/unix" +) + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +import "C" + +/** + * The scene-graph API provides a declarative way to display surfaces. The + * compositor creates a scene, adds surfaces, then renders the scene on + * outputs. + * + * The scene-graph API only supports basic 2D composition operations (like the + * KMS API or the Wayland protocol does). For anything more complicated, + * compositors need to implement custom rendering logic. + */ + +/** The root scene-graph node. */ +type Scene struct { + p *C.struct_wlr_scene +} + +func (s Scene) OutputCreate(o Output) SceneOutput { + p := C.wlr_scene_output_create(s.p, o.p) + return SceneOutput{p: p} +} + +func (s Scene) NewOutput(o Output) SceneOutput { + return s.OutputCreate(o) +} + +/** + * Create a new scene-graph. + */ + +func NewScene() Scene { + p := C.wlr_scene_create() + return Scene{p: p} +} + +func (s Scene) AttachOutputLayout(layout OutputLayout) SceneOutputLayout { + p := C.wlr_scene_attach_output_layout(s.p, layout.p) + return SceneOutputLayout{p: p} +} + +func (s Scene) SceneOutput(o Output) (SceneOutput, error) { + p := C.wlr_scene_get_scene_output(s.p, o.p) + if p != nil { + return SceneOutput{p: p}, nil + } + return SceneOutput{}, errors.New(" output hasn't been added to the scene-graph") +} + +func (s Scene) Tree() SceneTree { + return SceneTree{p: &s.p.tree} +} + +/** A scene-graph node displaying a single surface. */ +type SceneSurface struct { + p *C.struct_wlr_scene_surface +} + +func (s SceneSurface) Nil() bool { + return s.p == nil +} + +func (s SceneSurface) Surface() Surface { + return Surface{p: s.p.surface} +} + +/** A sub-tree in the scene-graph. */ +type SceneTree struct { + p *C.struct_wlr_scene_tree +} + +func (st SceneTree) Nil() bool { + return st.p == nil +} + +/** + * Add a node displaying nothing but its children. + */ +func (parent SceneTree) NewSceneTree() SceneTree { + p := C.wlr_scene_tree_create(parent.p) + return SceneTree{p: p} +} + +/** + * Add a node displaying a single surface to the scene-graph. + * + * The child sub-surfaces are ignored. + * + * wlr_surface_send_enter() and wlr_surface_send_leave() will be called + * automatically based on the position of the surface and outputs in + * the scene. + */ + +func (parent SceneTree) NewSurface(surface Surface) SceneSurface { + p := C.wlr_scene_surface_create(parent.p, surface.p) + return SceneSurface{p: p} +} + +func (st SceneTree) Node() SceneNode { + return SceneNode{p: (*C.struct_wlr_scene_node)(&st.p.node)} +} + +/** + * Add a node displaying an xdg_surface and all of its sub-surfaces to the + * scene-graph. + * + * The origin of the returned scene-graph node will match the top-left corner + * of the xdg_surface window geometry. + */ +func (st SceneTree) XDGSurfaceCreate(s XDGSurface) SceneTree { + p := C.wlr_scene_xdg_surface_create(st.p, s.p) + return SceneTree{p: p} +} +func (st SceneTree) NewXDGSurface(s XDGSurface) SceneTree { + return st.XDGSurfaceCreate(s) +} + +func (parent SceneTree) BufferCreate(b Buffer) SceneBuffer { + p := C.wlr_scene_buffer_create(parent.p, b.p) + return SceneBuffer{p: p} +} +func (parent SceneTree) NewBuffer(b Buffer) SceneBuffer { + return parent.BufferCreate(b) +} + +/** A scene-graph node displaying a solid-colored rectangle */ +type SceneRect struct { + p *C.struct_wlr_scene_rect +} + +/** A scene-graph node displaying a buffer */ +type SceneBuffer struct { + p *C.struct_wlr_scene_buffer +} + +/** + * If this buffer is backed by a surface, then the struct wlr_scene_surface is + * returned. If not, NULL will be returned. + */ +func (sb SceneBuffer) SceneSurface() SceneSurface { + p := C.wlr_scene_surface_try_from_buffer(sb.p) + return SceneSurface{p: p} +} + +/** + * Calls the buffer's frame_done signal. + */ +func (sb SceneBuffer) SendFrameDone(when time.Time) { + t, _ := unix.TimeToTimespec(when) + C.wlr_scene_buffer_send_frame_done(sb.p, (*C.struct_timespec)(unsafe.Pointer(&t))) +} + +func (sb SceneBuffer) SetBuffer(b Buffer) { + C.wlr_scene_buffer_set_buffer(sb.p, b.p) +} + +/** A viewport for an output in the scene-graph */ +type SceneOutput struct { + p *C.struct_wlr_scene_output +} + +func (s SceneOutput) Commit() { + C.wlr_scene_output_commit(s.p, nil) +} + +func (s SceneOutput) Destroy() { + C.wlr_scene_output_destroy(s.p) +} + +func (s SceneOutput) SendFrameDone(when time.Time) { + t, _ := unix.TimeToTimespec(when) + C.wlr_scene_output_send_frame_done(s.p, (*C.struct_timespec)(unsafe.Pointer(&t))) +} + +type SceneTimer struct { + p *C.struct_wlr_scene_timer +} + +type SceneOutputLayout struct { + p *C.struct_wlr_scene_output_layout +} + +func (sol SceneOutputLayout) AddOutput(l OutputLayoutOutput, o SceneOutput) { + C.wlr_scene_output_layout_add_output(sol.p, l.p, o.p) +} + +type SceneNodeType uint32 + +const ( + SceneNodeTree SceneNodeType = C.WLR_SCENE_NODE_TREE + SceneNodeRect SceneNodeType = C.WLR_SCENE_NODE_RECT + SceneNodeBuffer SceneNodeType = C.WLR_SCENE_NODE_BUFFER +) + +/** A node is an object in the scene. */ +type SceneNode struct { + p *C.struct_wlr_scene_node +} + +/** + * If this node represents a wlr_scene_tree, that tree will be returned. It + * is not legal to feed a node that does not represent a wlr_scene_tree. + */ +func (sn SceneNode) SceneTree() SceneTree { + p := C.wlr_scene_tree_from_node(sn.p) + return SceneTree{p: p} +} + +/** + * If this node represents a wlr_scene_rect, that rect will be returned. It + * is not legal to feed a node that does not represent a wlr_scene_rect. + */ +func (sn SceneNode) SceneRect() SceneRect { + p := C.wlr_scene_rect_from_node(sn.p) + return SceneRect{p: p} +} + +/** + * If this node represents a wlr_scene_buffer, that buffer will be returned. It + * is not legal to feed a node that does not represent a wlr_scene_buffer. + */ +func (sn SceneNode) SceneBuffer() SceneBuffer { + p := C.wlr_scene_buffer_from_node(sn.p) + return SceneBuffer{p: p} +} + +/** + * Immediately destroy the scene-graph node. + */ +func (sn SceneNode) Destroy() { + C.wlr_scene_node_destroy(sn.p) +} + +/** + * Move the node below all of its sibling nodes. + */ +func (sn SceneNode) LowerToBottom() { + C.wlr_scene_node_lower_to_bottom(sn.p) +} + +/** + * Move the node right above the specified sibling. + * Asserts that node and sibling are distinct and share the same parent. + */ +func (sn SceneNode) PlaceAbove(sib SceneNode) { + C.wlr_scene_node_place_above(sn.p, sib.p) +} + +/** + * Move the node right below the specified sibling. + * Asserts that node and sibling are distinct and share the same parent. + */ +func (sn SceneNode) PlaceBellow(sib SceneNode) { + C.wlr_scene_node_place_below(sn.p, sib.p) +} + +/** + * Move the node above all of its sibling nodes. + */ +func (sn SceneNode) RaiseToTop() { + C.wlr_scene_node_raise_to_top(sn.p) +} + +/** + * Move the node to another location in the tree. + */ +func (sn SceneNode) Reparent(tree SceneTree) { + C.wlr_scene_node_reparent(sn.p, tree.p) +} + +/** + * Enable or disable this node. If a node is disabled, all of its children are + * implicitly disabled as well. + */ +func (sn SceneNode) SetEnabled(enabled bool) { + C.wlr_scene_node_set_enabled(sn.p, C.bool(enabled)) +} + +/** + * Set the position of the node relative to its parent. + */ +func (sn SceneNode) SetPosition(x float64, y float64) { + C.wlr_scene_node_set_position(sn.p, C.int(x), C.int(y)) +} + +/** + * Find the topmost node in this scene-graph that contains the point at the + * given layout-local coordinates. (For surface nodes, this means accepting + * input events at that point.) Returns the node and coordinates relative to the + * returned node, or NULL if no node is found at that location. + */ +func (sn SceneNode) At(x float64, y float64) (SceneNode, float64, float64) { + var lx *C.double = new(C.double) + var ly *C.double = new(C.double) + p := C.wlr_scene_node_at(sn.p, C.double(x), C.double(y), lx, ly) + return SceneNode{p: p}, float64(*lx), float64(*ly) +} + +func (sn SceneNode) Nil() bool { + return sn.p == nil +} + +func (sn SceneNode) Type() SceneNodeType { + return SceneNodeType(sn.p._type) +} + +func (sn SceneNode) Parent() SceneTree { + return SceneTree{p: sn.p.parent} +} + +// relative to parent +func (sn SceneNode) X() int { + return int(sn.p.x) +} + +// relative to parent +func (sn SceneNode) Y() int { + return int(sn.p.y) +} + +func (sn SceneNode) SetData(tree SceneTree) { + sn.p.data = unsafe.Pointer(tree.p) +} + +func (x SceneNode) SceneTreeFromData() SceneTree { + // slog.Debug("XDGSurface SceneTree(): x.p", x.p) + // slog.Debug("XDGSurface SceneTree(): x.p.data", x.p.data) + return SceneTree{p: (*C.struct_wlr_scene_tree)(x.p.data)} +} diff --git a/wlroots/seat.go b/wlroots/seat.go new file mode 100644 index 0000000..5d1b678 --- /dev/null +++ b/wlroots/seat.go @@ -0,0 +1,125 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import "unsafe" + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +import "C" + +type Seat struct { + p *C.struct_wlr_seat +} + +type SeatClient struct { + p *C.struct_wlr_seat_client +} + +type SeatKeyboardState struct { + s C.struct_wlr_seat_keyboard_state +} + +type SeatPointerState struct { + s C.struct_wlr_seat_pointer_state +} + +type SeatCapability uint32 + +const ( + SeatCapabilityPointer SeatCapability = C.WL_SEAT_CAPABILITY_POINTER + SeatCapabilityKeyboard SeatCapability = C.WL_SEAT_CAPABILITY_KEYBOARD + SeatCapabilityTouch SeatCapability = C.WL_SEAT_CAPABILITY_TOUCH +) + +func (s Seat) Destroy() { + C.wlr_seat_destroy(s.p) +} + +func (s Seat) OnDestroy(cb func(Seat)) { + man.add(unsafe.Pointer(s.p), &s.p.events.destroy, func(unsafe.Pointer) { + cb(s) + }) +} + +func (s Seat) OnSetCursorRequest(cb func(client SeatClient, surface Surface, serial uint32, hotspotX int32, hotspotY int32)) { + man.add(unsafe.Pointer(s.p), &s.p.events.request_set_cursor, func(data unsafe.Pointer) { + event := (*C.struct_wlr_seat_pointer_request_set_cursor_event)(data) + client := SeatClient{p: event.seat_client} + surface := Surface{p: event.surface} + cb(client, surface, uint32(event.serial), int32(event.hotspot_x), int32(event.hotspot_y)) + }) +} + +func (s Seat) SetCapabilities(caps SeatCapability) { + C.wlr_seat_set_capabilities(s.p, C.uint32_t(caps)) +} + +func (s Seat) SetKeyboard(dev InputDevice) { + C.wlr_seat_set_keyboard(s.p, dev.Keyboard().p) +} + +func (s Seat) NotifyPointerButton(time uint32, button uint32, state ButtonState) { + C.wlr_seat_pointer_notify_button(s.p, C.uint32_t(time), C.uint32_t(button), uint32(state)) +} + +func (s Seat) NotifyPointerAxis(time uint32, orientation AxisOrientation, delta float64, deltaDiscrete int32, source AxisSource) { + C.wlr_seat_pointer_notify_axis(s.p, C.uint32_t(time), C.enum_wlr_axis_orientation(orientation), C.double(delta), C.int32_t(deltaDiscrete), C.enum_wlr_axis_source(source)) +} + +func (s Seat) NotifyPointerEnter(surface Surface, sx float64, sy float64) { + C.wlr_seat_pointer_notify_enter(s.p, surface.p, C.double(sx), C.double(sy)) +} + +func (s Seat) NotifyPointerMotion(time uint32, sx float64, sy float64) { + C.wlr_seat_pointer_notify_motion(s.p, C.uint32_t(time), C.double(sx), C.double(sy)) +} + +func (s Seat) NotifyPointerFrame() { + C.wlr_seat_pointer_notify_frame(s.p) +} + +func (s Seat) NotifyKeyboardEnter(surface Surface, k Keyboard) { + C.wlr_seat_keyboard_notify_enter(s.p, surface.p, &k.p.keycodes[0], k.p.num_keycodes, &k.p.modifiers) +} + +func (s Seat) NotifyKeyboardModifiers(k Keyboard) { + C.wlr_seat_keyboard_notify_modifiers(s.p, &k.p.modifiers) +} + +func (s Seat) NotifyKeyboardKey(time uint32, keyCode uint32, state KeyState) { + C.wlr_seat_keyboard_notify_key(s.p, C.uint32_t(time), C.uint32_t(keyCode), C.uint32_t(state)) +} + +func (s Seat) ClearPointerFocus() { + C.wlr_seat_pointer_clear_focus(s.p) +} + +func (s Seat) Keyboard() Keyboard { + p := C.wlr_seat_get_keyboard(s.p) + return Keyboard{p: p} +} + +func (s Seat) KeyboardState() SeatKeyboardState { + return SeatKeyboardState{s: s.p.keyboard_state} +} + +func (s Seat) PointerState() SeatPointerState { + return SeatPointerState{s: s.p.pointer_state} +} + +func (s SeatKeyboardState) FocusedSurface() Surface { + return Surface{p: s.s.focused_surface} +} + +func (s SeatPointerState) FocusedSurface() Surface { + return Surface{p: s.s.focused_surface} +} + +func (s SeatPointerState) FocusedClient() SeatClient { + return SeatClient{p: s.s.focused_client} +} diff --git a/wlroots/server_decoration.go b/wlroots/server_decoration.go new file mode 100644 index 0000000..4689302 --- /dev/null +++ b/wlroots/server_decoration.go @@ -0,0 +1,73 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import ( + "unsafe" +) + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +import "C" + +type ServerDecorationManagerMode uint32 + +const ( + ServerDecorationManagerModeNone ServerDecorationManagerMode = C.WLR_SERVER_DECORATION_MANAGER_MODE_NONE + ServerDecorationManagerModeClient ServerDecorationManagerMode = C.WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT + ServerDecorationManagerModeServer ServerDecorationManagerMode = C.WLR_SERVER_DECORATION_MANAGER_MODE_SERVER +) + +type ServerDecorationManager struct { + p *C.struct_wlr_server_decoration_manager +} + +type ServerDecoration struct { + p *C.struct_wlr_server_decoration +} + +func NewServerDecorationManager(display Display) ServerDecorationManager { + p := C.wlr_server_decoration_manager_create(display.p) + man.track(unsafe.Pointer(p), &p.events.destroy) + return ServerDecorationManager{p: p} +} + +func (m ServerDecorationManager) OnDestroy(cb func(ServerDecorationManager)) { + man.add(unsafe.Pointer(m.p), &m.p.events.destroy, func(unsafe.Pointer) { + cb(m) + }) +} + +func (m ServerDecorationManager) SetDefaultMode(mode ServerDecorationManagerMode) { + C.wlr_server_decoration_manager_set_default_mode(m.p, C.uint32_t(mode)) +} + +func (m ServerDecorationManager) OnNewMode(cb func(ServerDecorationManager, ServerDecoration)) { + man.add(unsafe.Pointer(m.p), &m.p.events.new_decoration, func(data unsafe.Pointer) { + dec := ServerDecoration{ + p: (*C.struct_wlr_server_decoration)(data), + } + man.track(unsafe.Pointer(dec.p), &dec.p.events.destroy) + cb(m, dec) + }) +} + +func (d ServerDecoration) OnDestroy(cb func(ServerDecoration)) { + man.add(unsafe.Pointer(d.p), &d.p.events.destroy, func(unsafe.Pointer) { + cb(d) + }) +} + +func (d ServerDecoration) OnMode(cb func(ServerDecoration)) { + man.add(unsafe.Pointer(d.p), &d.p.events.mode, func(unsafe.Pointer) { + cb(d) + }) +} + +func (d ServerDecoration) Mode() ServerDecorationManagerMode { + return ServerDecorationManagerMode(d.p.mode) +} diff --git a/wlroots/wlroots.go b/wlroots/wlroots.go index 8c8697f..1be045b 100644 --- a/wlroots/wlroots.go +++ b/wlroots/wlroots.go @@ -1,377 +1,29 @@ package wlroots -import "C" import ( "errors" - "fmt" - "sync" - "time" "unsafe" - - "golang.org/x/sys/unix" - - "github.com/swaywm/go-wlroots/xkb" ) -// #include -// #include +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE // #include // #include // #include // #include -// #include -// #include -// #include -// #include -// #include // #include +// #include // #include -// #include -// #include -// #include // #include -// #include -// #include -// #include -// #include // #include // #include -// #include // #include -// #include // #include // #include // #include // #include -// #include -// -// void _wlr_log_cb(enum wlr_log_importance importance, char *msg); -// -// static inline void _wlr_log_inner_cb(enum wlr_log_importance importance, const char *fmt, va_list args) { -// char *msg = NULL; -// if (vasprintf(&msg, fmt, args) == -1) { -// return; -// } -// -// _wlr_log_cb(importance, msg); -// free(msg); -// } -// -// static inline void _wlr_log_set_cb(enum wlr_log_importance verbosity, bool is_set) { -// wlr_log_init(verbosity, is_set ? &_wlr_log_inner_cb : NULL); -// } -// -// void _wlr_xdg_surface_for_each_cb(struct wlr_surface *surface, int sx, int sy, void *data); -// -// static inline void _wlr_xdg_surface_for_each_surface(struct wlr_xdg_surface *surface, void *user_data) { -// wlr_xdg_surface_for_each_surface(surface, &_wlr_xdg_surface_for_each_cb, user_data); -// } -// -// void _wl_listener_cb(struct wl_listener *listener, void *data); -// -// static inline void _wl_listener_set_cb(struct wl_listener *listener) { -// listener->notify = &_wl_listener_cb; -// } -// -// #cgo pkg-config: wlroots wayland-server -// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE import "C" -type XWayland struct { - p *C.struct_wlr_xwayland -} - -type XWaylandSurface struct { - p *C.struct_wlr_xwayland_surface -} - -func NewXWayland(display Display, compositor Compositor, lazy bool) XWayland { - p := C.wlr_xwayland_create(display.p, compositor.p, C.bool(lazy)) - return XWayland{p: p} -} - -func (x XWayland) Destroy() { - C.wlr_xwayland_destroy(x.p) -} - -func (x XWayland) OnNewSurface(cb func(XWaylandSurface)) { - man.add(unsafe.Pointer(x.p), &x.p.events.new_surface, func(data unsafe.Pointer) { - surface := XWaylandSurface{p: (*C.struct_wlr_xwayland_surface)(data)} - man.track(unsafe.Pointer(surface.p), &surface.p.events.destroy) - man.add(unsafe.Pointer(surface.p.surface), &surface.p.surface.events.destroy, func(data unsafe.Pointer) { - man.delete(unsafe.Pointer(surface.p.surface)) - }) - cb(surface) - }) -} - -func (x XWayland) SetCursor(img XCursorImage) { - C.wlr_xwayland_set_cursor(x.p, img.p.buffer, img.p.width*4, img.p.width, img.p.height, C.int32_t(img.p.hotspot_x), C.int32_t(img.p.hotspot_y)) -} - -func (s XWaylandSurface) Surface() Surface { - return Surface{p: s.p.surface} -} - -func (s XWaylandSurface) Geometry() GeoBox { - return GeoBox{ - X: int(s.p.x), - Y: int(s.p.y), - Width: int(s.p.width), - Height: int(s.p.height), - } -} - -func (s XWaylandSurface) Configure(x int16, y int16, width uint16, height uint16) { - C.wlr_xwayland_surface_configure(s.p, C.int16_t(x), C.int16_t(y), C.uint16_t(width), C.uint16_t(height)) -} - -func (s XWaylandSurface) OnMap(cb func(XWaylandSurface)) { - man.add(unsafe.Pointer(s.p), &s.p.surface.events._map, func(data unsafe.Pointer) { - cb(s) - }) -} - -func (s XWaylandSurface) OnUnmap(cb func(XWaylandSurface)) { - man.add(unsafe.Pointer(s.p), &s.p.surface.events.unmap, func(data unsafe.Pointer) { - cb(s) - }) -} - -func (s XWaylandSurface) OnDestroy(cb func(XWaylandSurface)) { - man.add(unsafe.Pointer(s.p), &s.p.events.destroy, func(data unsafe.Pointer) { - cb(s) - }) -} - -func (s XWaylandSurface) OnRequestMove(cb func(surface XWaylandSurface)) { - man.add(unsafe.Pointer(s.p), &s.p.events.request_move, func(data unsafe.Pointer) { - cb(s) - }) -} - -func (s XWaylandSurface) OnRequestResize(cb func(surface XWaylandSurface, edges Edges)) { - man.add(unsafe.Pointer(s.p), &s.p.events.request_resize, func(data unsafe.Pointer) { - event := (*C.struct_wlr_xwayland_resize_event)(data) - cb(s, Edges(event.edges)) - }) -} - -func (s XWaylandSurface) OnRequestConfigure(cb func(surface XWaylandSurface, x int16, y int16, width uint16, height uint16)) { - man.add(unsafe.Pointer(s.p), &s.p.events.request_configure, func(data unsafe.Pointer) { - event := (*C.struct_wlr_xwayland_surface_configure_event)(data) - cb(s, int16(event.x), int16(event.y), uint16(event.width), uint16(event.height)) - }) -} - -type XDGSurfaceRole uint32 - -const ( - XDGSurfaceRoleNone XDGSurfaceRole = C.WLR_XDG_SURFACE_ROLE_NONE - XDGSurfaceRoleTopLevel XDGSurfaceRole = C.WLR_XDG_SURFACE_ROLE_TOPLEVEL - XDGSurfaceRolePopup XDGSurfaceRole = C.WLR_XDG_SURFACE_ROLE_POPUP -) - -var ( - // TODO: guard this with a mutex - xdgSurfaceWalkers = map[*C.struct_wlr_xdg_surface]XDGSurfaceWalkFunc{} - xdgSurfaceWalkersMutex sync.RWMutex -) - -type XDGShell struct { - p *C.struct_wlr_xdg_shell -} - -type XDGSurface struct { - p *C.struct_wlr_xdg_surface - data *C.struct_wlr_scene_tree -} - -type XDGPopup struct { - p *C.struct_wlr_xdg_popup -} - -type XDGSurfaceWalkFunc func(surface Surface, sx int, sy int) - -type XDGTopLevel struct { - p *C.struct_wlr_xdg_toplevel -} - -func NewXDGShell(display Display, version int) XDGShell { - p := C.wlr_xdg_shell_create(display.p, C.uint(version)) - man.track(unsafe.Pointer(p), &p.events.destroy) - return XDGShell{p: p} -} - -func (s XDGShell) OnDestroy(cb func(XDGShell)) { - man.add(unsafe.Pointer(s.p), &s.p.events.destroy, func(unsafe.Pointer) { - cb(s) - }) -} - -func (s XDGShell) OnNewSurface(cb func(XDGSurface)) { - man.add(unsafe.Pointer(s.p), &s.p.events.new_surface, func(data unsafe.Pointer) { - surface := XDGSurface{p: (*C.struct_wlr_xdg_surface)(data)} - man.add(unsafe.Pointer(surface.p), &surface.p.events.destroy, func(data unsafe.Pointer) { - man.delete(unsafe.Pointer(surface.p)) - man.delete(unsafe.Pointer(surface.TopLevel().p)) - }) - man.add(unsafe.Pointer(surface.p.surface), &surface.p.surface.events.destroy, func(data unsafe.Pointer) { - man.delete(unsafe.Pointer(surface.p.surface)) - }) - cb(surface) - }) -} - -//export _wlr_xdg_surface_for_each_cb -func _wlr_xdg_surface_for_each_cb(surface *C.struct_wlr_surface, sx C.int, sy C.int, data unsafe.Pointer) { - xdgSurfaceWalkersMutex.RLock() - cb := xdgSurfaceWalkers[(*C.struct_wlr_xdg_surface)(data)] - xdgSurfaceWalkersMutex.RUnlock() - if cb != nil { - cb(Surface{p: surface}, int(sx), int(sy)) - } -} - -func (s XDGSurface) Nil() bool { - return s.p == nil -} - -func (s XDGSurface) Walk(visit XDGSurfaceWalkFunc) { - xdgSurfaceWalkersMutex.Lock() - xdgSurfaceWalkers[s.p] = visit - xdgSurfaceWalkersMutex.Unlock() - - C._wlr_xdg_surface_for_each_surface(s.p, unsafe.Pointer(s.p)) - - xdgSurfaceWalkersMutex.Lock() - delete(xdgSurfaceWalkers, s.p) - xdgSurfaceWalkersMutex.Unlock() -} - -func (s XDGSurface) Role() XDGSurfaceRole { - return XDGSurfaceRole(s.p.role) -} - -func (s XDGSurface) TopLevel() XDGTopLevel { - p := *(*unsafe.Pointer)(unsafe.Pointer(&s.p.anon0[0])) - return XDGTopLevel{p: (*C.struct_wlr_xdg_toplevel)(p)} -} - -func (s XDGSurface) TopLevelSetActivated(activated bool) { - C.wlr_xdg_toplevel_set_activated(s.TopLevel().p, C.bool(activated)) -} - -func (s XDGSurface) TopLevelSetSize(width uint32, height uint32) { - C.wlr_xdg_toplevel_set_size(s.TopLevel().p, C.int(width), C.int(height)) -} - -func (s XDGSurface) TopLevelSetTiled(edges Edges) { - C.wlr_xdg_toplevel_set_tiled(s.TopLevel().p, C.uint(edges)) -} - -func (s XDGSurface) SendClose() { - C.wlr_xdg_toplevel_send_close(s.TopLevel().p) -} - -func (s XDGSurface) SceneTree() SceneTree { - return SceneTree{p: (*C.struct_wlr_scene_tree)(s.p.data)} -} - -func (s XDGSurface) Ping() { - C.wlr_xdg_surface_ping(s.p) -} - -func (s XDGSurface) Surface() Surface { - return Surface{p: s.p.surface} -} - -func (s XDGSurface) SurfaceAt(sx float64, sy float64) (surface Surface, subX float64, subY float64) { - var csubX, csubY C.double - p := C.wlr_xdg_surface_surface_at(s.p, C.double(sx), C.double(sy), &csubX, &csubY) - return Surface{p: p}, float64(csubX), float64(csubY) -} - -func (s XDGSurface) SetData(tree SceneTree) { - s.data = tree.p -} - -func (s XDGSurface) OnMap(cb func(XDGSurface)) { - man.add(unsafe.Pointer(s.p), &s.p.surface.events._map, func(data unsafe.Pointer) { - cb(s) - }) -} - -func (s XDGSurface) OnUnmap(cb func(XDGSurface)) { - man.add(unsafe.Pointer(s.p), &s.p.surface.events.unmap, func(data unsafe.Pointer) { - cb(s) - }) -} - -func (s XDGSurface) OnDestroy(cb func(XDGSurface)) { - man.add(unsafe.Pointer(s.p), &s.p.events.destroy, func(data unsafe.Pointer) { - cb(s) - }) -} - -func (s XDGSurface) OnPingTimeout(cb func(XDGSurface)) { - man.add(unsafe.Pointer(s.p), &s.p.events.ping_timeout, func(data unsafe.Pointer) { - cb(s) - }) -} - -func (s XDGSurface) OnNewPopup(cb func(XDGSurface, XDGPopup)) { - man.add(unsafe.Pointer(s.p), &s.p.events.ping_timeout, func(data unsafe.Pointer) { - popup := XDGPopup{p: (*C.struct_wlr_xdg_popup)(data)} - cb(s, popup) - }) -} - -func (s XDGSurface) Geometry() GeoBox { - var cb C.struct_wlr_box - C.wlr_xdg_surface_get_geometry(s.p, &cb) - - var b GeoBox - b.fromC(&cb) - return b -} - -func (t XDGTopLevel) OnRequestMove(cb func(client SeatClient, serial uint32)) { - man.add(unsafe.Pointer(t.p), &t.p.events.request_move, func(data unsafe.Pointer) { - event := (*C.struct_wlr_xdg_toplevel_move_event)(data) - client := SeatClient{p: event.seat} - cb(client, uint32(event.serial)) - }) -} - -func (t XDGTopLevel) OnRequestResize(cb func(client SeatClient, serial uint32, edges Edges)) { - man.add(unsafe.Pointer(t.p), &t.p.events.request_resize, func(data unsafe.Pointer) { - event := (*C.struct_wlr_xdg_toplevel_resize_event)(data) - client := SeatClient{p: event.seat} - cb(client, uint32(event.serial), Edges(event.edges)) - }) -} - -func (s XDGTopLevel) Nil() bool { - return s.p == nil -} - -func (t XDGTopLevel) Title() string { - return C.GoString(t.p.title) -} - -func (s XDGTopLevel) Base() XDGSurface { - return XDGSurface{p: (*C.struct_wlr_xdg_surface)(s.p.base)} -} - -type XCursor struct { - p *C.struct_wlr_xcursor -} - -type XCursorImage struct { - p *C.struct_wlr_xcursor_image -} - type XCursorManager struct { p *C.struct_wlr_xcursor_manager } @@ -389,28 +41,6 @@ func (m XCursorManager) Load(scale float32) { C.wlr_xcursor_manager_load(m.p, C.float(scale)) } -func (c XCursor) Image(i int) XCursorImage { - n := c.ImageCount() - slice := (*[1 << 30]*C.struct_wlr_xcursor_image)(unsafe.Pointer(c.p.images))[:n:n] - return XCursorImage{p: slice[i]} -} - -func (c XCursor) Images() []XCursorImage { - images := make([]XCursorImage, 0, c.ImageCount()) - for i := 0; i < cap(images); i++ { - images = append(images, c.Image(i)) - } - return images -} - -func (c XCursor) ImageCount() int { - return int(c.p.image_count) -} - -func (c XCursor) Name() string { - return C.GoString(c.p.name) -} - type Edges uint32 const ( @@ -433,423 +63,18 @@ func (t Texture) Nil() bool { return t.p == nil } -type SurfaceType uint32 - -const ( - SurfaceTypeNone SurfaceType = iota - SurfaceTypeXDG - SurfaceTypeXWayland -) - -type Surface struct { - p *C.struct_wlr_surface -} - -type SurfaceState struct { - s C.struct_wlr_surface_state -} - -func (s Surface) Nil() bool { - return s.p == nil -} - -func (s Surface) OnDestroy(cb func(Surface)) { - man.add(unsafe.Pointer(s.p), &s.p.events.destroy, func(unsafe.Pointer) { - cb(s) - }) -} - -func (s Surface) Type() SurfaceType { - if C.wlr_xdg_surface_try_from_wlr_surface(s.p) != nil { - return SurfaceTypeXDG - } else if C.wlr_xwayland_surface_try_from_wlr_surface(s.p) != nil { - return SurfaceTypeXWayland - } - - return SurfaceTypeNone -} - -func (s Surface) SurfaceAt(sx float64, sy float64) (surface Surface, subX float64, subY float64) { - var csubX, csubY C.double - p := C.wlr_surface_surface_at(s.p, C.double(sx), C.double(sy), &csubX, &csubY) - return Surface{p: p}, float64(csubX), float64(csubY) -} - -func (s Surface) Texture() Texture { - p := C.wlr_surface_get_texture(s.p) - return Texture{p: p} -} - -func (s Surface) CurrentState() SurfaceState { - return SurfaceState{s: s.p.current} -} - -func (s Surface) Walk(visit func()) { - panic("not implemented") -} - -func (s Surface) SendFrameDone(when time.Time) { - // we ignore the returned error; the only possible error is - // ERANGE, when timespec on a platform has int32 precision, but - // our time requires 64 bits. This should not occur. - t, _ := unix.TimeToTimespec(when) - C.wlr_surface_send_frame_done(s.p, (*C.struct_timespec)(unsafe.Pointer(&t))) -} - -func (s Surface) XDGSurface() XDGSurface { - p := C.wlr_xdg_surface_try_from_wlr_surface(s.p) - return XDGSurface{p: p} -} - -func (s Surface) XWaylandSurface() XWaylandSurface { - p := C.wlr_xwayland_surface_try_from_wlr_surface(s.p) - return XWaylandSurface{p: p} -} - -func (s SurfaceState) Width() int { - return int(s.s.width) -} - -func (s SurfaceState) Height() int { - return int(s.s.height) -} - -func (s SurfaceState) Transform() uint32 { - return uint32(s.s.transform) -} - -type Seat struct { - p *C.struct_wlr_seat -} - -type SeatClient struct { - p *C.struct_wlr_seat_client -} - -type SeatKeyboardState struct { - s C.struct_wlr_seat_keyboard_state -} - -type SeatPointerState struct { - s C.struct_wlr_seat_pointer_state -} - -type SeatCapability uint32 - -const ( - SeatCapabilityPointer SeatCapability = C.WL_SEAT_CAPABILITY_POINTER - SeatCapabilityKeyboard SeatCapability = C.WL_SEAT_CAPABILITY_KEYBOARD - SeatCapabilityTouch SeatCapability = C.WL_SEAT_CAPABILITY_TOUCH -) - -func NewSeat(display Display, name string) Seat { - s := C.CString(name) - p := C.wlr_seat_create(display.p, s) - C.free(unsafe.Pointer(s)) - man.track(unsafe.Pointer(p), &p.events.destroy) - return Seat{p: p} -} - -func (s Seat) Destroy() { - C.wlr_seat_destroy(s.p) -} - -func (s Seat) OnDestroy(cb func(Seat)) { - man.add(unsafe.Pointer(s.p), &s.p.events.destroy, func(unsafe.Pointer) { - cb(s) - }) -} - -func (s Seat) OnSetCursorRequest(cb func(client SeatClient, surface Surface, serial uint32, hotspotX int32, hotspotY int32)) { - man.add(unsafe.Pointer(s.p), &s.p.events.request_set_cursor, func(data unsafe.Pointer) { - event := (*C.struct_wlr_seat_pointer_request_set_cursor_event)(data) - client := SeatClient{p: event.seat_client} - surface := Surface{p: event.surface} - cb(client, surface, uint32(event.serial), int32(event.hotspot_x), int32(event.hotspot_y)) - }) -} - -func (s Seat) SetCapabilities(caps SeatCapability) { - C.wlr_seat_set_capabilities(s.p, C.uint32_t(caps)) -} - -func (s Seat) SetKeyboard(dev InputDevice) { - C.wlr_seat_set_keyboard(s.p, dev.Keyboard().p) -} - -func (s Seat) NotifyPointerButton(time uint32, button uint32, state ButtonState) { - C.wlr_seat_pointer_notify_button(s.p, C.uint32_t(time), C.uint32_t(button), uint32(state)) -} - -func (s Seat) NotifyPointerAxis(time uint32, orientation AxisOrientation, delta float64, deltaDiscrete int32, source AxisSource) { - C.wlr_seat_pointer_notify_axis(s.p, C.uint32_t(time), C.enum_wlr_axis_orientation(orientation), C.double(delta), C.int32_t(deltaDiscrete), C.enum_wlr_axis_source(source)) -} - -func (s Seat) NotifyPointerEnter(surface Surface, sx float64, sy float64) { - C.wlr_seat_pointer_notify_enter(s.p, surface.p, C.double(sx), C.double(sy)) -} - -func (s Seat) NotifyPointerMotion(time uint32, sx float64, sy float64) { - C.wlr_seat_pointer_notify_motion(s.p, C.uint32_t(time), C.double(sx), C.double(sy)) -} - -func (s Seat) NotifyPointerFrame() { - C.wlr_seat_pointer_notify_frame(s.p) -} - -func (s Seat) NotifyKeyboardEnter(surface Surface, k Keyboard) { - C.wlr_seat_keyboard_notify_enter(s.p, surface.p, &k.p.keycodes[0], k.p.num_keycodes, &k.p.modifiers) -} - -func (s Seat) NotifyKeyboardModifiers(k Keyboard) { - C.wlr_seat_keyboard_notify_modifiers(s.p, &k.p.modifiers) -} - -func (s Seat) NotifyKeyboardKey(time uint32, keyCode uint32, state KeyState) { - C.wlr_seat_keyboard_notify_key(s.p, C.uint32_t(time), C.uint32_t(keyCode), C.uint32_t(state)) -} - -func (s Seat) ClearPointerFocus() { - C.wlr_seat_pointer_clear_focus(s.p) -} - -func (s Seat) Keyboard() Keyboard { - p := C.wlr_seat_get_keyboard(s.p) - return Keyboard{p: p} -} - -func (s Seat) KeyboardState() SeatKeyboardState { - return SeatKeyboardState{s: s.p.keyboard_state} -} - -func (s Seat) PointerState() SeatPointerState { - return SeatPointerState{s: s.p.pointer_state} -} - -func (s SeatKeyboardState) FocusedSurface() Surface { - return Surface{p: s.s.focused_surface} -} - -func (s SeatPointerState) FocusedSurface() Surface { - return Surface{p: s.s.focused_surface} -} - -func (s SeatPointerState) FocusedClient() SeatClient { - return SeatClient{p: s.s.focused_client} -} - -type Renderer struct { - p *C.struct_wlr_renderer -} - -func (r Renderer) Destroy() { - C.wlr_renderer_destroy(r.p) -} - -func (r Renderer) OnDestroy(cb func(Renderer)) { - man.add(unsafe.Pointer(r.p), &r.p.events.destroy, func(unsafe.Pointer) { - cb(r) - }) -} - -func (r Renderer) InitDisplay(display Display) { - C.wlr_renderer_init_wl_display(r.p, display.p) -} - -func (r Renderer) Begin(output Output, width int, height int) { - C.wlr_renderer_begin(r.p, C.uint(width), C.uint(height)) -} - -func (r Renderer) Clear(color *Color) { - c := color.toC() - C.wlr_renderer_clear(r.p, &c[0]) -} - -func (r Renderer) End() { - C.wlr_renderer_end(r.p) -} - -func (r Renderer) RenderTextureWithMatrix(texture Texture, matrix *Matrix, alpha float32) { - m := matrix.toC() - C.wlr_render_texture_with_matrix(r.p, texture.p, &m[0], C.float(alpha)) -} - -func (r Renderer) RenderRect(box *GeoBox, color *Color, projection *Matrix) { - b := box.toC() - c := color.toC() - pm := projection.toC() - C.wlr_render_rect(r.p, &b, &c[0], &pm[0]) -} - -type Output struct { - p *C.struct_wlr_output -} - -type OutputMode struct { - p *C.struct_wlr_output_mode -} - -func wrapOutput(p unsafe.Pointer) Output { - return Output{p: (*C.struct_wlr_output)(p)} -} - -func (o Output) OnDestroy(cb func(Output)) { - man.add(unsafe.Pointer(o.p), &o.p.events.destroy, func(unsafe.Pointer) { - cb(o) - }) -} - -func (o Output) Name() string { - return C.GoString(o.p.name) -} - -func (o Output) Scale() float32 { - return float32(o.p.scale) -} - -func (o Output) TransformMatrix() Matrix { - var matrix Matrix - matrix.fromC(&o.p.transform_matrix) - return matrix -} - -func (o Output) OnFrame(cb func(Output)) { - man.add(unsafe.Pointer(o.p), &o.p.events.frame, func(data unsafe.Pointer) { - cb(o) - }) -} - -func (o Output) RenderSoftwareCursors() { - C.wlr_output_render_software_cursors(o.p, nil) -} - -func (o Output) TransformedResolution() (int, int) { - var width, height C.int - C.wlr_output_transformed_resolution(o.p, &width, &height) - return int(width), int(height) -} - -func (o Output) EffectiveResolution() (int, int) { - var width, height C.int - C.wlr_output_effective_resolution(o.p, &width, &height) - return int(width), int(height) -} - -func (o Output) AttachRender() (int, error) { - var bufferAge C.int - if !C.wlr_output_attach_render(o.p, &bufferAge) { - return 0, errors.New("can't make output context current") - } - - return int(bufferAge), nil -} - -func (o Output) Rollback() { - C.wlr_output_rollback(o.p) -} - -func (o Output) CreateGlobal() { - C.wlr_output_create_global(o.p) -} - -func (o Output) DestroyGlobal() { - C.wlr_output_destroy_global(o.p) -} - -func (o Output) ScheduleDone() { - C.wlr_output_schedule_done(o.p) -} - -func (o Output) Destroy() { - C.wlr_output_destroy(o.p) -} - -func (o Output) Test() bool { - return bool(C.wlr_output_test(o.p)) -} - -func (o Output) Commit() bool { - return bool(C.wlr_output_commit(o.p)) -} - -func (o Output) Modes() []OutputMode { - // TODO: figure out what to do with this ridiculous for loop - // perhaps this can be refactored into a less ugly hack that uses reflection - var modes []OutputMode - var mode *C.struct_wlr_output_mode - for mode := (*C.struct_wlr_output_mode)(unsafe.Pointer(uintptr(unsafe.Pointer(o.p.modes.next)) - unsafe.Offsetof(mode.link))); &mode.link != &o.p.modes; mode = (*C.struct_wlr_output_mode)(unsafe.Pointer(uintptr(unsafe.Pointer(mode.link.next)) - unsafe.Offsetof(mode.link))) { - modes = append(modes, OutputMode{p: mode}) - } - - return modes -} - -func (o Output) SetMode(mode OutputMode) { - C.wlr_output_set_mode(o.p, mode.p) -} - -func (o Output) PrefferedMode() OutputMode { - mode := C.wlr_output_preferred_mode(o.p) - return OutputMode{p: mode} -} - -func (o Output) SetCustomMode(width int, height int, refresh int) { - C.wlr_output_set_custom_mode(o.p, C.int(width), C.int(height), C.int(refresh)) -} - -func (o Output) EnableAdaptiveSync(enable bool) { - C.wlr_output_enable_adaptive_sync(o.p, C.bool(enable)) -} - -func (o Output) SetScale(scale float32) { - C.wlr_output_set_scale(o.p, C.float(scale)) -} - -func (o Output) SetName(name string) { - C.wlr_output_set_name(o.p, C.CString(name)) -} - -func (o Output) SetDescription(desc string) { - C.wlr_output_set_description(o.p, C.CString(desc)) -} - -func (o Output) Enable(enable bool) { - C.wlr_output_enable(o.p, C.bool(enable)) -} - -func (o Output) SetTitle(title string) error { - if C.wlr_output_is_wl(o.p) { - C.wlr_wl_output_set_title(o.p, C.CString(title)) - } else if C.wlr_output_is_x11(o.p) { - C.wlr_x11_output_set_title(o.p, C.CString(title)) - } else { - return errors.New("this output type cannot have a title") - } - - return nil -} - -func (o Output) InitRender(a Allocator, r Renderer) bool { - return bool(C.wlr_output_init_render(o.p, a.p, r.p)) -} - -type OutputState struct { - state C.struct_wlr_output_state -} - -func (os OutputState) StateInit() { - C.wlr_output_state_init(&os.state) -} - -func (os OutputState) StateSetEnabled() { - C.wlr_output_state_set_enabled(&os.state, true) -} - type OutputLayout struct { p *C.struct_wlr_output_layout } +type OutputLayoutOutput struct { + p *C.struct_wlr_output_layout_output +} func NewOutputLayout() OutputLayout { + return OutputLayoutCerate() +} + +func OutputLayoutCerate() OutputLayout { p := C.wlr_output_layout_create() man.track(unsafe.Pointer(p), &p.events.destroy) return OutputLayout{p: p} @@ -859,156 +84,15 @@ func (l OutputLayout) Destroy() { C.wlr_output_layout_destroy(l.p) } -func (l OutputLayout) AddOutputAuto(output Output) { - C.wlr_output_layout_add_auto(l.p, output.p) +func (l OutputLayout) AddOutputAuto(output Output) OutputLayoutOutput { + p := C.wlr_output_layout_add_auto(l.p, output.p) + return OutputLayoutOutput{p: p} } func (l OutputLayout) Coords(output Output) (x float64, y float64) { var ox, oy C.double C.wlr_output_layout_output_coords(l.p, output.p, &ox, &oy) - return float64(ox), float64(oy) -} - -func OutputTransformInvert(transform uint32) uint32 { - return uint32(C.wlr_output_transform_invert(C.enum_wl_output_transform(transform))) -} - -func (m OutputMode) Width() int32 { - return int32(m.p.width) -} - -func (m OutputMode) Height() int32 { - return int32(m.p.height) -} - -func (m OutputMode) RefreshRate() int32 { - return int32(m.p.refresh) -} - -type SceneNodeType uint32 - -const ( - SceneNodeTree SceneNodeType = C.WLR_SCENE_NODE_TREE - SceneNodeRect SceneNodeType = C.WLR_SCENE_NODE_RECT - SceneNodeBuffer SceneNodeType = C.WLR_SCENE_NODE_BUFFER -) - -type SceneNode struct { - p *C.struct_wlr_scene_node -} - -type Scene struct { - p *C.struct_wlr_scene -} - -type SceneSurface struct { - p *C.struct_wlr_scene_surface -} - -type SceneTree struct { - p *C.struct_wlr_scene_tree -} - -type SceneRect struct { - p *C.struct_wlr_scene_rect -} - -type SceneBuffer struct { - p *C.struct_wlr_scene_buffer -} - -type SceneOutput struct { - p *C.struct_wlr_scene_output -} - -type SceneTimer struct { - p *C.struct_wlr_scene_timer -} - -type SceneOutputLayout struct { - p *C.struct_wlr_scene_output_layout -} - -func (sn SceneNode) SceneTree(st SceneTree) { - st.p = C.wlr_scene_tree_from_node(sn.p) - return -} - -func (sn SceneNode) SceneRect() (sr SceneRect) { - sr.p = C.wlr_scene_rect_from_node(sn.p) - return -} - -func (sn SceneNode) SceneBuffer() (sb SceneBuffer) { - sb.p = C.wlr_scene_buffer_from_node(sn.p) - return -} - -func (sn SceneNode) Destroy() { - C.wlr_scene_node_destroy(sn.p) -} - -func (sn SceneNode) SetEnabled(enabled bool) { - C.wlr_scene_node_set_enabled(sn.p, C.bool(enabled)) -} - -func (sn SceneNode) SetPosition(x float64, y float64) { - C.wlr_scene_node_set_position(sn.p, C.int(x), C.int(y)) -} - -func (sn SceneNode) RaiseToTop() { - C.wlr_scene_node_raise_to_top(sn.p) -} - -/** - * Create a new scene-graph. - */ - -func NewScene() Scene { - p := C.wlr_scene_create() - return Scene{p: p} -} - -func (s Scene) AttachOutputLayout(layout OutputLayout) SceneOutputLayout { - p := C.wlr_scene_attach_output_layout(s.p, layout.p) - return SceneOutputLayout{p: p} -} - -/** - * Add a node displaying nothing but its children. - */ - -func (parent SceneTree) NewSceneTree() SceneTree { - p := C.wlr_scene_tree_create(parent.p) - return SceneTree{p: p} -} - -/** - * Add a node displaying a single surface to the scene-graph. - * - * The child sub-surfaces are ignored. - * - * wlr_surface_send_enter() and wlr_surface_send_leave() will be called - * automatically based on the position of the surface and outputs in - * the scene. - */ - -func (parent SceneTree) NewSurface(surface Surface) SceneSurface { - p := C.wlr_scene_surface_create(parent.p, surface.p) - return SceneSurface{p: p} -} - -func (s Scene) Tree() SceneTree { - return SceneTree{p: &s.p.tree} -} - -func (st SceneTree) Node() SceneNode { - return SceneNode{p: (*C.struct_wlr_scene_node)(&st.p.node)} -} - -func (st SceneTree) XDGSurfaceCreate(s XDGSurface) SceneTree { - p := C.wlr_scene_xdg_surface_create(st.p, s.p) - return SceneTree{p: p} + return float64(ox), float64(oy) } type Matrix [9]float32 @@ -1035,216 +119,102 @@ func (m *Matrix) fromC(cm *[9]C.float) { } } -type ( - LogImportance uint32 - LogFunc func(importance LogImportance, msg string) -) - -const ( - LogImportanceSilent LogImportance = C.WLR_SILENT - LogImportanceError LogImportance = C.WLR_ERROR - LogImportanceInfo LogImportance = C.WLR_INFO - LogImportanceDebug LogImportance = C.WLR_DEBUG -) - -var ( - onLog LogFunc -) - -//export _wlr_log_cb -func _wlr_log_cb(importance LogImportance, msg *C.char) { - if onLog != nil { - onLog(importance, C.GoString(msg)) - } -} - -func OnLog(verbosity LogImportance, cb LogFunc) { - C._wlr_log_set_cb(C.enum_wlr_log_importance(verbosity), cb != nil) - onLog = cb -} - -type ( - KeyState uint32 - KeyboardModifier uint32 -) - -const ( - KeyStateReleased KeyState = C.WL_KEYBOARD_KEY_STATE_RELEASED - KeyStatePressed KeyState = C.WL_KEYBOARD_KEY_STATE_PRESSED - - KeyboardModifierShift KeyboardModifier = C.WLR_MODIFIER_SHIFT - KeyboardModifierCaps KeyboardModifier = C.WLR_MODIFIER_CAPS - KeyboardModifierCtrl KeyboardModifier = C.WLR_MODIFIER_CTRL - KeyboardModifierAlt KeyboardModifier = C.WLR_MODIFIER_ALT - KeyboardModifierMod2 KeyboardModifier = C.WLR_MODIFIER_MOD2 - KeyboardModifierMod3 KeyboardModifier = C.WLR_MODIFIER_MOD3 - KeyboardModifierLogo KeyboardModifier = C.WLR_MODIFIER_LOGO - KeyboardModifierMod5 KeyboardModifier = C.WLR_MODIFIER_MOD5 -) - -type Keyboard struct { - p *C.struct_wlr_keyboard -} - -func (k Keyboard) SetKeymap(keymap xkb.Keymap) { - C.wlr_keyboard_set_keymap(k.p, (*C.struct_xkb_keymap)(keymap.Ptr())) -} - -func (k Keyboard) RepeatInfo() (rate int32, delay int32) { - return int32(k.p.repeat_info.rate), int32(k.p.repeat_info.delay) -} - -func (k Keyboard) SetRepeatInfo(rate int32, delay int32) { - C.wlr_keyboard_set_repeat_info(k.p, C.int32_t(rate), C.int32_t(delay)) -} - -func (k Keyboard) XKBState() xkb.State { - return xkb.WrapState(unsafe.Pointer(k.p.xkb_state)) -} - -func (k Keyboard) Modifiers() KeyboardModifier { - return KeyboardModifier(C.wlr_keyboard_get_modifiers(k.p)) +type DMABuf struct { + p *C.struct_wlr_linux_dmabuf_v1 } -func (k Keyboard) OnModifiers(cb func(keyboard Keyboard)) { - man.add(unsafe.Pointer(k.p), &k.p.events.modifiers, func(data unsafe.Pointer) { - cb(k) - }) +func NewDMABuf(display Display, renderer Renderer) DMABuf { + p := C.wlr_linux_dmabuf_v1_create_with_renderer(display.p, 4, renderer.p) + man.track(unsafe.Pointer(p), &p.events.destroy) + return DMABuf{p: p} } -func (k Keyboard) OnKey(cb func(keyboard Keyboard, time uint32, keyCode uint32, updateState bool, state KeyState)) { - man.add(unsafe.Pointer(k.p), &k.p.events.key, func(data unsafe.Pointer) { - event := (*C.struct_wlr_keyboard_key_event)(data) - cb(k, uint32(event.time_msec), uint32(event.keycode), bool(event.update_state), KeyState(event.state)) +func (b DMABuf) OnDestroy(cb func(DMABuf)) { + man.add(unsafe.Pointer(b.p), &b.p.events.destroy, func(unsafe.Pointer) { + cb(b) }) } -type ( - InputDeviceType uint32 - ButtonState uint32 - AxisSource uint32 - AxisOrientation uint32 -) - -var inputDeviceNames = []string{ - InputDeviceTypeKeyboard: "keyboard", - InputDeviceTypePointer: "pointer", - InputDeviceTypeTouch: "touch", - InputDeviceTypeTabletTool: "tablet tool", - InputDeviceTypeTabletPad: "tablet pad", +type Display struct { + p *C.struct_wl_display } -const ( - InputDeviceTypeKeyboard InputDeviceType = C.WLR_INPUT_DEVICE_KEYBOARD - InputDeviceTypePointer InputDeviceType = C.WLR_INPUT_DEVICE_POINTER - InputDeviceTypeTouch InputDeviceType = C.WLR_INPUT_DEVICE_TOUCH - InputDeviceTypeTabletTool InputDeviceType = C.WLR_INPUT_DEVICE_TABLET_TOOL - InputDeviceTypeTabletPad InputDeviceType = C.WLR_INPUT_DEVICE_TABLET_PAD - InputDeviceTypeSwitch InputDeviceType = C.WLR_INPUT_DEVICE_SWITCH - - ButtonStateReleased ButtonState = C.WLR_BUTTON_RELEASED - ButtonStatePressed ButtonState = C.WLR_BUTTON_PRESSED - - AxisSourceWheel AxisSource = C.WLR_AXIS_SOURCE_WHEEL - AxisSourceFinger AxisSource = C.WLR_AXIS_SOURCE_FINGER - AxisSourceContinuous AxisSource = C.WLR_AXIS_SOURCE_CONTINUOUS - AxisSourceWheelTilt AxisSource = C.WLR_AXIS_SOURCE_WHEEL_TILT - - AxisOrientationVertical AxisOrientation = C.WLR_AXIS_ORIENTATION_VERTICAL - AxisOrientationHorizontal AxisOrientation = C.WLR_AXIS_ORIENTATION_HORIZONTAL -) - -type InputDevice struct { - p *C.struct_wlr_input_device +func DisplayCreate() { + NewDisplay() } -func (d InputDevice) OnDestroy(cb func(InputDevice)) { - man.add(unsafe.Pointer(d.p), &d.p.events.destroy, func(unsafe.Pointer) { - cb(d) +func NewDisplay() Display { + p := C.wl_display_create() + d := Display{p: p} + d.OnDestroy(func(Display) { + man.delete(unsafe.Pointer(p)) }) + return d } -func (d InputDevice) Type() InputDeviceType { return InputDeviceType(d.p._type) } -func (d InputDevice) Vendor() int { return int(d.p.vendor) } -func (d InputDevice) Product() int { return int(d.p.product) } -func (d InputDevice) Name() string { return C.GoString(d.p.name) } - -// func (d InputDevice) Width() float64 { return float64(d.p.width_mm) } -// func (d InputDevice) Height() float64 { return float64(d.p.height_mm) } -// func (d InputDevice) OutputName() string { return C.GoString(d.p.output_name) } +func (d Display) NewBackend() (Backend, error) { + return d.BackendAutocreate() +} -func validateInputDeviceType(d InputDevice, fn string, req InputDeviceType) { - if typ := d.Type(); typ != req { - if int(typ) >= len(inputDeviceNames) { - panic(fmt.Sprintf("%s called on input device of type %d", fn, typ)) - } else { - panic(fmt.Sprintf("%s called on input device of type %s", fn, inputDeviceNames[typ])) - } +func (d Display) BackendAutocreate() (Backend, error) { + p := C.wlr_backend_autocreate(d.p, nil) + if p == nil { + return Backend{}, errors.New("failed to create wlr_backend") } + man.track(unsafe.Pointer(p), &p.events.destroy) + return Backend{p: p}, nil } -func (d InputDevice) Keyboard() Keyboard { - validateInputDeviceType(d, "Keyboard", InputDeviceTypeKeyboard) - p := *(*unsafe.Pointer)(unsafe.Pointer(&d.p)) - return Keyboard{p: (*C.struct_wlr_keyboard)(p)} +func (d Display) NewSubCompositor() SubCompositor { + return d.SubCompositorCreate() } -func wrapInputDevice(p unsafe.Pointer) InputDevice { - return InputDevice{p: (*C.struct_wlr_input_device)(p)} +func (d Display) SubCompositorCreate() SubCompositor { + p := C.wlr_subcompositor_create(d.p) + man.track(unsafe.Pointer(p), &p.events.destroy) + return SubCompositor{p: p} } -type DMABuf struct { - p *C.struct_wlr_linux_dmabuf_v1 +func (d Display) NewCompositor(version int, renderer Renderer) Compositor { + return d.CompositorCreate(version, renderer) } -func NewDMABuf(display Display, renderer Renderer) DMABuf { - p := C.wlr_linux_dmabuf_v1_create_with_renderer(display.p, 4, renderer.p) +func (d Display) CompositorCreate(version int, renderer Renderer) Compositor { + p := C.wlr_compositor_create(d.p, C.uint(version), renderer.p) man.track(unsafe.Pointer(p), &p.events.destroy) - return DMABuf{p: p} -} - -func (b DMABuf) OnDestroy(cb func(DMABuf)) { - man.add(unsafe.Pointer(b.p), &b.p.events.destroy, func(unsafe.Pointer) { - cb(b) - }) + return Compositor{p: p} } -type EventLoop struct { - p *C.struct_wl_event_loop +func (d Display) NewDataDeviceManager() DataDeviceManager { + return d.DataDeviceManagerCreate() } -func (evl EventLoop) OnDestroy(cb func(EventLoop)) { - l := man.add(unsafe.Pointer(evl.p), nil, func(data unsafe.Pointer) { - cb(evl) - }) - C.wl_event_loop_add_destroy_listener(evl.p, l.p) +func (d Display) DataDeviceManagerCreate() DataDeviceManager { + p := C.wlr_data_device_manager_create(d.p) + man.track(unsafe.Pointer(p), &p.events.destroy) + return DataDeviceManager{p: p} } -func (evl EventLoop) Fd() uintptr { - return uintptr(C.wl_event_loop_get_fd(evl.p)) +func (d Display) NewSeat(name string) Seat { + return d.SeatCreate(name) } -func (evl EventLoop) Dispatch(timeout time.Duration) { - var d int - if timeout >= 0 { - d = int(timeout / time.Millisecond) - } else { - d = -1 - } - C.wl_event_loop_dispatch(evl.p, C.int(d)) +func (d Display) SeatCreate(name string) Seat { + s := C.CString(name) + p := C.wlr_seat_create(d.p, s) + C.free(unsafe.Pointer(s)) + man.track(unsafe.Pointer(p), &p.events.destroy) + return Seat{p: p} } -type Display struct { - p *C.struct_wl_display +func (d Display) NewXDGShell(version int) XDGShell { + return d.XDGShellCreate(version) } -func NewDisplay() Display { - p := C.wl_display_create() - d := Display{p: p} - d.OnDestroy(func(Display) { - man.delete(unsafe.Pointer(p)) - }) - return d +func (d Display) XDGShellCreate(version int) XDGShell { + p := C.wlr_xdg_shell_create(d.p, C.uint(version)) + man.track(unsafe.Pointer(p), &p.events.destroy) + return XDGShell{p: p} } func (d Display) Destroy() { @@ -1292,192 +262,16 @@ func (d Display) DestroyClients() { C.wl_display_destroy_clients(d.p) } -type ServerDecorationManagerMode uint32 - -const ( - ServerDecorationManagerModeNone ServerDecorationManagerMode = C.WLR_SERVER_DECORATION_MANAGER_MODE_NONE - ServerDecorationManagerModeClient ServerDecorationManagerMode = C.WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT - ServerDecorationManagerModeServer ServerDecorationManagerMode = C.WLR_SERVER_DECORATION_MANAGER_MODE_SERVER -) - -type ServerDecorationManager struct { - p *C.struct_wlr_server_decoration_manager -} - -type ServerDecoration struct { - p *C.struct_wlr_server_decoration -} - -func NewServerDecorationManager(display Display) ServerDecorationManager { - p := C.wlr_server_decoration_manager_create(display.p) - man.track(unsafe.Pointer(p), &p.events.destroy) - return ServerDecorationManager{p: p} -} - -func (m ServerDecorationManager) OnDestroy(cb func(ServerDecorationManager)) { - man.add(unsafe.Pointer(m.p), &m.p.events.destroy, func(unsafe.Pointer) { - cb(m) - }) -} - -func (m ServerDecorationManager) SetDefaultMode(mode ServerDecorationManagerMode) { - C.wlr_server_decoration_manager_set_default_mode(m.p, C.uint32_t(mode)) -} - -func (m ServerDecorationManager) OnNewMode(cb func(ServerDecorationManager, ServerDecoration)) { - man.add(unsafe.Pointer(m.p), &m.p.events.new_decoration, func(data unsafe.Pointer) { - dec := ServerDecoration{ - p: (*C.struct_wlr_server_decoration)(data), - } - man.track(unsafe.Pointer(dec.p), &dec.p.events.destroy) - cb(m, dec) - }) -} - -func (d ServerDecoration) OnDestroy(cb func(ServerDecoration)) { - man.add(unsafe.Pointer(d.p), &d.p.events.destroy, func(unsafe.Pointer) { - cb(d) - }) -} - -func (d ServerDecoration) OnMode(cb func(ServerDecoration)) { - man.add(unsafe.Pointer(d.p), &d.p.events.mode, func(unsafe.Pointer) { - cb(d) - }) -} - -func (d ServerDecoration) Mode() ServerDecorationManagerMode { - return ServerDecorationManagerMode(d.p.mode) -} - type DataDeviceManager struct { p *C.struct_wlr_data_device_manager } -func NewDataDeviceManager(display Display) DataDeviceManager { - p := C.wlr_data_device_manager_create(display.p) - man.track(unsafe.Pointer(p), &p.events.destroy) - return DataDeviceManager{p: p} -} - func (m DataDeviceManager) OnDestroy(cb func(DataDeviceManager)) { man.add(unsafe.Pointer(m.p), &m.p.events.destroy, func(unsafe.Pointer) { cb(m) }) } -type Cursor struct { - p *C.struct_wlr_cursor -} - -func NewCursor() Cursor { - p := C.wlr_cursor_create() - return Cursor{p: p} -} - -func (c Cursor) Destroy() { - C.wlr_cursor_destroy(c.p) - man.delete(unsafe.Pointer(c.p)) -} - -func (c Cursor) X() float64 { - return float64(c.p.x) -} - -func (c Cursor) Y() float64 { - return float64(c.p.y) -} - -func (c Cursor) AttachOutputLayout(layout OutputLayout) { - C.wlr_cursor_attach_output_layout(c.p, layout.p) -} - -func (c Cursor) AttachInputDevice(dev InputDevice) { - C.wlr_cursor_attach_input_device(c.p, dev.p) -} - -func (c Cursor) Move(dev InputDevice, dx float64, dy float64) { - C.wlr_cursor_move(c.p, dev.p, C.double(dx), C.double(dy)) -} - -func (c Cursor) WarpAbsolute(dev InputDevice, x float64, y float64) { - C.wlr_cursor_warp_absolute(c.p, dev.p, C.double(x), C.double(y)) -} - -func (c Cursor) SetSurface(surface Surface, hotspotX int32, hotspotY int32) { - C.wlr_cursor_set_surface(c.p, surface.p, C.int32_t(hotspotX), C.int32_t(hotspotY)) -} - -func (c Cursor) OnMotion(cb func(dev InputDevice, time uint32, dx float64, dy float64)) { - man.add(unsafe.Pointer(c.p), &c.p.events.motion, func(data unsafe.Pointer) { - event := (*C.struct_wlr_pointer_motion_event)(data) - dev := InputDevice{p: &event.pointer.base} - cb(dev, uint32(event.time_msec), float64(event.delta_x), float64(event.delta_y)) - }) -} - -func (c Cursor) OnMotionAbsolute(cb func(dev InputDevice, time uint32, x float64, y float64)) { - man.add(unsafe.Pointer(c.p), &c.p.events.motion_absolute, func(data unsafe.Pointer) { - event := (*C.struct_wlr_pointer_motion_absolute_event)(data) - dev := InputDevice{p: &event.pointer.base} - cb(dev, uint32(event.time_msec), float64(event.x), float64(event.y)) - }) -} - -func (c Cursor) OnButton(cb func(dev InputDevice, time uint32, button uint32, state ButtonState)) { - man.add(unsafe.Pointer(c.p), &c.p.events.button, func(data unsafe.Pointer) { - event := (*C.struct_wlr_pointer_button_event)(data) - dev := InputDevice{p: &event.pointer.base} - cb(dev, uint32(event.time_msec), uint32(event.button), ButtonState(event.state)) - }) -} - -func (c Cursor) OnAxis(cb func(dev InputDevice, time uint32, source AxisSource, orientation AxisOrientation, delta float64, deltaDiscrete int32)) { - man.add(unsafe.Pointer(c.p), &c.p.events.axis, func(data unsafe.Pointer) { - event := (*C.struct_wlr_pointer_axis_event)(data) - dev := InputDevice{p: &event.pointer.base} - cb(dev, uint32(event.time_msec), AxisSource(event.source), AxisOrientation(event.orientation), float64(event.delta), int32(event.delta_discrete)) - }) -} - -func (c Cursor) OnFrame(cb func()) { - man.add(unsafe.Pointer(c.p), &c.p.events.frame, func(data unsafe.Pointer) { - cb() - }) -} - -type Compositor struct { - p *C.struct_wlr_compositor -} - -func NewCompositor(display Display, version int, renderer Renderer) Compositor { - p := C.wlr_compositor_create(display.p, C.uint(version), renderer.p) - man.track(unsafe.Pointer(p), &p.events.destroy) - return Compositor{p: p} -} - -func (c Compositor) OnDestroy(cb func(Compositor)) { - man.add(unsafe.Pointer(c.p), &c.p.events.destroy, func(unsafe.Pointer) { - cb(c) - }) -} - -type SubCompositor struct { - p *C.struct_wlr_subcompositor -} - -func NewSubCompositor(display Display) SubCompositor { - p := C.wlr_subcompositor_create(display.p) - man.track(unsafe.Pointer(p), &p.events.destroy) - return SubCompositor{p: p} -} - -func (c SubCompositor) OnDestroy(cb func(SubCompositor)) { - man.add(unsafe.Pointer(c.p), &c.p.events.destroy, func(unsafe.Pointer) { - cb(c) - }) -} - type Color struct { R, G, B, A float32 } @@ -1499,6 +293,7 @@ func (c *Color) toC() [4]C.float { } type GeoBox struct { + p *C.struct_wlr_box X, Y, Width, Height int } @@ -1524,189 +319,3 @@ func (b *GeoBox) fromC(cb *C.struct_wlr_box) { b.Width = int(cb.width) b.Height = int(cb.height) } - -type Backend struct { - p *C.struct_wlr_backend -} - -func NewBackend(display Display) (Backend, error) { - p := C.wlr_backend_autocreate(display.p, nil) - if p == nil { - return Backend{}, errors.New("failed to create wlr_backend") - } - man.track(unsafe.Pointer(p), &p.events.destroy) - return Backend{p: p}, nil -} - -func (b Backend) Destroy() { - C.wlr_backend_destroy(b.p) -} - -func (b Backend) OnDestroy(cb func(Backend)) { - man.add(unsafe.Pointer(b.p), &b.p.events.destroy, func(unsafe.Pointer) { - cb(b) - }) -} - -func (b Backend) Start() error { - if !C.wlr_backend_start(b.p) { - return errors.New("can't start backend") - } - - return nil -} - -func (b Backend) OnNewOutput(cb func(Output)) { - man.add(unsafe.Pointer(b.p), &b.p.events.new_output, func(data unsafe.Pointer) { - output := wrapOutput(data) - man.track(unsafe.Pointer(output.p), &output.p.events.destroy) - cb(output) - }) -} - -func (b Backend) OnNewInput(cb func(InputDevice)) { - man.add(unsafe.Pointer(b.p), &b.p.events.new_input, func(data unsafe.Pointer) { - dev := wrapInputDevice(data) - man.add(unsafe.Pointer(dev.p), &dev.p.events.destroy, func(data unsafe.Pointer) { - // delete the wlr_input_device - man.delete(unsafe.Pointer(dev.p)) - }) - cb(dev) - }) -} - -func NewAllocator(b Backend, r Renderer) (Allocator, error) { - p := C.wlr_allocator_autocreate(b.p, r.p) - if p == nil { - return Allocator{}, errors.New("failed to wlr_allocator") - } - man.track(unsafe.Pointer(p), &p.events.destroy) - return Allocator{p: p}, nil -} - -func NewRenderer(b Backend) (Renderer, error) { - p := C.wlr_renderer_autocreate(b.p) - if p == nil { - return Renderer{}, errors.New("failed to create wlr_renderer") - } - man.track(unsafe.Pointer(p), &p.events.destroy) - return Renderer{p: p}, nil -} - -type Allocator struct { - p *C.struct_wlr_allocator -} - -func (s Allocator) Nil() bool { - return s.p == nil -} - -// This whole mess has to exist for a number of reasons: -// -// 1. We need to allocate all instances of wl_listener on the heap as storing Go -// pointers in C after a cgo call returns is not allowed. -// -// 2. The wlroots library implicitly destroys objects when wl_display is -// destroyed. So, we need to keep track of all objects (and their listeners) -// manually and listen for the destroy signal to be able to free everything. -// -// 3 (TODO). As we're keeping track of all objects anyway, we might as well -// store a Go pointer to the wrapper struct along with them in order to be able -// to pass the same Go pointer through callbacks every time. This will also -// allow calling runtime.SetFinalizer on some of them to clean them up early -// when the GC notices it has gone out of scope. -// -// Send help. - -type ( - listenerCallback func(data unsafe.Pointer) -) - -type manager struct { - mutex sync.RWMutex - objects map[unsafe.Pointer][]*listener - listeners map[*C.struct_wl_listener]*listener -} - -type listener struct { - p *C.struct_wl_listener - s *C.struct_wl_signal - cbs []listenerCallback -} - -var ( - man = &manager{ - objects: map[unsafe.Pointer][]*listener{}, - listeners: map[*C.struct_wl_listener]*listener{}, - } -) - -//export _wl_listener_cb -func _wl_listener_cb(listener *C.struct_wl_listener, data unsafe.Pointer) { - man.mutex.RLock() - l := man.listeners[listener] - man.mutex.RUnlock() - for _, cb := range l.cbs { - cb(data) - } -} - -func (m *manager) add(p unsafe.Pointer, signal *C.struct_wl_signal, cb listenerCallback) *listener { - m.mutex.Lock() - defer m.mutex.Unlock() - - // if a listener for this object and signal already exists, add the callback - // to the existing listener - if signal != nil { - for _, l := range m.objects[p] { - if l.s != nil && l.s == signal { - l.cbs = append(l.cbs, cb) - return l - } - } - } - - lp := (*C.struct_wl_listener)(C.calloc(C.sizeof_struct_wl_listener, 1)) - C._wl_listener_set_cb(lp) - if signal != nil { - C.wl_signal_add((*C.struct_wl_signal)(signal), lp) - } - - l := &listener{ - p: lp, - s: signal, - cbs: []listenerCallback{cb}, - } - m.listeners[lp] = l - m.objects[p] = append(m.objects[p], l) - - return l -} - -func (m *manager) has(p unsafe.Pointer) bool { - m.mutex.RLock() - _, found := m.objects[p] - m.mutex.RUnlock() - return found -} - -func (m *manager) delete(p unsafe.Pointer) { - m.mutex.Lock() - defer m.mutex.Unlock() - - for _, l := range m.objects[p] { - delete(m.listeners, l.p) - - // remove the listener from the signal - C.wl_list_remove(&l.p.link) - - // free the listener - C.free(unsafe.Pointer(l.p)) - } - - delete(m.objects, p) -} - -func (m *manager) track(p unsafe.Pointer, destroySignal *C.struct_wl_signal) { - m.add(p, destroySignal, func(data unsafe.Pointer) { m.delete(p) }) -} diff --git a/wlroots/xcursor.go b/wlroots/xcursor.go new file mode 100644 index 0000000..9fdf1de --- /dev/null +++ b/wlroots/xcursor.go @@ -0,0 +1,44 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import "unsafe" + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +// #include +import "C" + +type XCursor struct { + p *C.struct_wlr_xcursor +} + +type XCursorImage struct { + p *C.struct_wlr_xcursor_image +} + +func (c XCursor) Image(i int) XCursorImage { + n := c.ImageCount() + slice := (*[1 << 30]*C.struct_wlr_xcursor_image)(unsafe.Pointer(c.p.images))[:n:n] + return XCursorImage{p: slice[i]} +} + +func (c XCursor) Images() []XCursorImage { + images := make([]XCursorImage, 0, c.ImageCount()) + for i := 0; i < cap(images); i++ { + images = append(images, c.Image(i)) + } + return images +} + +func (c XCursor) ImageCount() int { + return int(c.p.image_count) +} + +func (c XCursor) Name() string { + return C.GoString(c.p.name) +} diff --git a/wlroots/xdg_shell.go b/wlroots/xdg_shell.go new file mode 100644 index 0000000..25e3f10 --- /dev/null +++ b/wlroots/xdg_shell.go @@ -0,0 +1,258 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import ( + "log/slog" + "sync" + "unsafe" +) + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +// +// void _wlr_xdg_surface_for_each_cb(struct wlr_surface *surface, int sx, int sy, void *data); +// static inline void _wlr_xdg_surface_for_each_surface(struct wlr_xdg_surface *surface, void *user_data) { +// wlr_xdg_surface_for_each_surface(surface, &_wlr_xdg_surface_for_each_cb, user_data); +// } +import "C" + +type XDGSurfaceRole uint32 + +const ( + XDGSurfaceRoleNone XDGSurfaceRole = C.WLR_XDG_SURFACE_ROLE_NONE + XDGSurfaceRoleTopLevel XDGSurfaceRole = C.WLR_XDG_SURFACE_ROLE_TOPLEVEL + XDGSurfaceRolePopup XDGSurfaceRole = C.WLR_XDG_SURFACE_ROLE_POPUP +) + +var ( + xdgSurfaceWalkers = map[*C.struct_wlr_xdg_surface]XDGSurfaceWalkFunc{} + xdgSurfaceWalkersMutex sync.RWMutex +) + +type XDGShell struct { + p *C.struct_wlr_xdg_shell +} + +type XDGPopup struct { + p *C.struct_wlr_xdg_popup +} + +func (x XDGPopup) Parent() Surface { + return Surface{p: x.p.parent} +} + +func (x XDGPopup) Seat() Seat { + return Seat{p: x.p.seat} +} + +func (x XDGPopup) Base() XDGSurface { + return XDGSurface{p: x.p.base} +} + +type XDGSurfaceWalkFunc func(surface Surface, sx int, sy int) + +func (s XDGShell) Version() int { + return int(s.p.version) +} + +func (s XDGShell) PingTimeout() int { + return int(s.p.ping_timeout) +} + +func (s XDGShell) OnDestroy(cb func(XDGShell)) { + man.add(unsafe.Pointer(s.p), &s.p.events.destroy, func(unsafe.Pointer) { + cb(s) + }) +} + +func (s XDGShell) OnNewSurface(cb func(XDGSurface)) { + man.add(unsafe.Pointer(s.p), &s.p.events.new_surface, func(data unsafe.Pointer) { + surface := XDGSurface{p: (*C.struct_wlr_xdg_surface)(data)} + man.add(unsafe.Pointer(surface.p), &surface.p.events.destroy, func(data unsafe.Pointer) { + man.delete(unsafe.Pointer(surface.p)) + man.delete(unsafe.Pointer(surface.TopLevel().p)) + }) + man.add(unsafe.Pointer(surface.p.surface), &surface.p.surface.events.destroy, func(data unsafe.Pointer) { + man.delete(unsafe.Pointer(surface.p.surface)) + }) + cb(surface) + }) +} + +//export _wlr_xdg_surface_for_each_cb +func _wlr_xdg_surface_for_each_cb(surface *C.struct_wlr_surface, sx C.int, sy C.int, data unsafe.Pointer) { + xdgSurfaceWalkersMutex.RLock() + cb := xdgSurfaceWalkers[(*C.struct_wlr_xdg_surface)(data)] + xdgSurfaceWalkersMutex.RUnlock() + if cb != nil { + cb(Surface{p: surface}, int(sx), int(sy)) + } +} + +type XDGSurface struct { + p *C.struct_wlr_xdg_surface +} + +func (x XDGSurface) Nil() bool { + return x.p == nil +} + +func (x XDGSurface) Walk(visit XDGSurfaceWalkFunc) { + xdgSurfaceWalkersMutex.Lock() + xdgSurfaceWalkers[x.p] = visit + xdgSurfaceWalkersMutex.Unlock() + + C._wlr_xdg_surface_for_each_surface(x.p, unsafe.Pointer(x.p)) + + xdgSurfaceWalkersMutex.Lock() + delete(xdgSurfaceWalkers, x.p) + xdgSurfaceWalkersMutex.Unlock() +} + +/** + * The lifetime-bound role of the xdg_surface. WLR_XDG_SURFACE_ROLE_NONE + * if the role was never set. + */ +func (x XDGSurface) Role() XDGSurfaceRole { + return XDGSurfaceRole(x.p.role) +} + +func (x XDGSurface) Popup() XDGPopup { + p := *(*unsafe.Pointer)(unsafe.Pointer(&x.p.anon0[0])) + return XDGPopup{p: (*C.struct_wlr_xdg_popup)(p)} +} +func (x XDGSurface) TopLevel() XDGTopLevel { + p := *(*unsafe.Pointer)(unsafe.Pointer(&x.p.anon0[0])) + return XDGTopLevel{p: (*C.struct_wlr_xdg_toplevel)(p)} +} + +func (x XDGSurface) TopLevelSetActivated(activated bool) { + C.wlr_xdg_toplevel_set_activated(x.TopLevel().p, C.bool(activated)) +} + +func (x XDGSurface) TopLevelSetSize(width uint32, height uint32) { + C.wlr_xdg_toplevel_set_size(x.TopLevel().p, C.int(width), C.int(height)) +} + +func (x XDGSurface) TopLevelSetTiled(edges Edges) { + C.wlr_xdg_toplevel_set_tiled(x.TopLevel().p, C.uint(edges)) +} + +func (x XDGSurface) SendClose() { + C.wlr_xdg_toplevel_send_close(x.TopLevel().p) +} + +func (x XDGSurface) SceneTree() SceneTree { + slog.Debug("XDGSurface SceneTree()", "x.p", x.p) + slog.Debug("XDGSurface SceneTree()", "x.p.data", x.p.data) + return SceneTree{p: (*C.struct_wlr_scene_tree)(x.p.data)} +} + +func (x XDGSurface) Ping() { + C.wlr_xdg_surface_ping(x.p) +} + +func (x XDGSurface) Surface() Surface { + return Surface{p: x.p.surface} +} + +func (x XDGSurface) SurfaceAt(sx float64, sy float64) (surface Surface, subX float64, subY float64) { + var csubX, csubY C.double + p := C.wlr_xdg_surface_surface_at(x.p, C.double(sx), C.double(sy), &csubX, &csubY) + return Surface{p: p}, float64(csubX), float64(csubY) +} + +func (x XDGSurface) SetData(tree SceneTree) { + x.p.data = unsafe.Pointer(tree.p) + slog.Debug("XDGSurface SetData", "x.p", x.p) + slog.Debug("XDGSurface SetData", "x.data:", x.p.data) +} + +func (x XDGSurface) OnMap(cb func(XDGSurface)) { + man.add(unsafe.Pointer(x.p), &x.p.surface.events._map, func(data unsafe.Pointer) { + cb(x) + }) +} + +func (x XDGSurface) OnUnmap(cb func(XDGSurface)) { + man.add(unsafe.Pointer(x.p), &x.p.surface.events.unmap, func(data unsafe.Pointer) { + cb(x) + }) +} + +func (x XDGSurface) OnDestroy(cb func(XDGSurface)) { + man.add(unsafe.Pointer(x.p), &x.p.events.destroy, func(data unsafe.Pointer) { + cb(x) + }) +} + +func (x XDGSurface) OnPingTimeout(cb func(XDGSurface)) { + man.add(unsafe.Pointer(x.p), &x.p.events.ping_timeout, func(data unsafe.Pointer) { + cb(x) + }) +} + +func (x XDGSurface) OnNewPopup(cb func(XDGSurface, XDGPopup)) { + man.add(unsafe.Pointer(x.p), &x.p.events.ping_timeout, func(data unsafe.Pointer) { + popup := XDGPopup{p: (*C.struct_wlr_xdg_popup)(data)} + cb(x, popup) + }) +} + +func (x XDGSurface) Geometry() GeoBox { + var cb C.struct_wlr_box + C.wlr_xdg_surface_get_geometry(x.p, &cb) + + var b GeoBox + b.fromC(&cb) + return b +} + +type XDGTopLevel struct { + p *C.struct_wlr_xdg_toplevel +} + +func (t XDGTopLevel) OnRequestMove(cb func(client SeatClient, serial uint32)) { + man.add(unsafe.Pointer(t.p), &t.p.events.request_move, func(data unsafe.Pointer) { + event := (*C.struct_wlr_xdg_toplevel_move_event)(data) + client := SeatClient{p: event.seat} + cb(client, uint32(event.serial)) + }) +} + +func (t XDGTopLevel) OnRequestResize(cb func(client SeatClient, serial uint32, edges Edges)) { + man.add(unsafe.Pointer(t.p), &t.p.events.request_resize, func(data unsafe.Pointer) { + event := (*C.struct_wlr_xdg_toplevel_resize_event)(data) + client := SeatClient{p: event.seat} + cb(client, uint32(event.serial), Edges(event.edges)) + }) +} + +func (t XDGTopLevel) Nil() bool { + return t.p == nil +} + +func (t XDGTopLevel) Title() string { + return C.GoString(t.p.title) +} + +func (t XDGTopLevel) AppId() string { + return C.GoString(t.p.app_id) +} + +func (t XDGTopLevel) Parent() XDGTopLevel { + return XDGTopLevel{p: t.p.parent} +} + +func (t XDGTopLevel) Base() XDGSurface { + return XDGSurface{p: t.p.base} +} + +func (t XDGTopLevel) SetActivated(activated bool) { + C.wlr_xdg_toplevel_set_activated(t.p, C.bool(activated)) +} diff --git a/wlroots/xwayland.go b/wlroots/xwayland.go new file mode 100644 index 0000000..34c10af --- /dev/null +++ b/wlroots/xwayland.go @@ -0,0 +1,120 @@ +package wlroots + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +import "unsafe" + +// #cgo pkg-config: wlroots wayland-server +// #cgo CFLAGS: -D_GNU_SOURCE -DWLR_USE_UNSTABLE +// #include +// #include +// #include +// #include +import "C" + +type XWayland struct { + p *C.struct_wlr_xwayland +} + +/** + * An Xwayland user interface component. It has an absolute position in + * layout-local coordinates. + * + * The inner struct wlr_surface is valid once the associate event is emitted. + * Compositors can set up e.g. map and unmap listeners at this point. The + * struct wlr_surface becomes invalid when the dissociate event is emitted. + */ +type XWaylandSurface struct { + p *C.struct_wlr_xwayland_surface +} + +func (x XWayland) Destroy() { + C.wlr_xwayland_destroy(x.p) +} + +func (x XWayland) OnNewSurface(cb func(XWaylandSurface)) { + man.add(unsafe.Pointer(x.p), &x.p.events.new_surface, func(data unsafe.Pointer) { + surface := XWaylandSurface{p: (*C.struct_wlr_xwayland_surface)(data)} + man.track(unsafe.Pointer(surface.p), &surface.p.events.destroy) + man.add(unsafe.Pointer(surface.p.surface), &surface.p.surface.events.destroy, func(data unsafe.Pointer) { + man.delete(unsafe.Pointer(surface.p.surface)) + }) + cb(surface) + }) +} + +func (x XWayland) SetCursor(img XCursorImage) { + C.wlr_xwayland_set_cursor(x.p, img.p.buffer, img.p.width*4, img.p.width, img.p.height, C.int32_t(img.p.hotspot_x), C.int32_t(img.p.hotspot_y)) +} + +func (s XWaylandSurface) Activate(activated bool) { + C.wlr_xwayland_surface_activate(s.p, C.bool(activated)) +} + +func (s XWaylandSurface) Surface() Surface { + return Surface{p: s.p.surface} +} + +func (s XWaylandSurface) Geometry() GeoBox { + return GeoBox{ + X: int(s.p.x), + Y: int(s.p.y), + Width: int(s.p.width), + Height: int(s.p.height), + } +} + +func (s XWaylandSurface) Configure(x int16, y int16, width uint16, height uint16) { + C.wlr_xwayland_surface_configure(s.p, C.int16_t(x), C.int16_t(y), C.uint16_t(width), C.uint16_t(height)) +} + +func (s XWaylandSurface) OnMap(cb func(XWaylandSurface)) { + man.add(unsafe.Pointer(s.p), &s.p.surface.events._map, func(data unsafe.Pointer) { + cb(s) + }) +} + +func (s XWaylandSurface) OnUnmap(cb func(XWaylandSurface)) { + man.add(unsafe.Pointer(s.p), &s.p.surface.events.unmap, func(data unsafe.Pointer) { + cb(s) + }) +} + +func (s XWaylandSurface) OnDestroy(cb func(XWaylandSurface)) { + man.add(unsafe.Pointer(s.p), &s.p.events.destroy, func(data unsafe.Pointer) { + cb(s) + }) +} + +func (s XWaylandSurface) OnRequestMove(cb func(surface XWaylandSurface)) { + man.add(unsafe.Pointer(s.p), &s.p.events.request_move, func(data unsafe.Pointer) { + cb(s) + }) +} + +func (s XWaylandSurface) OnRequestResize(cb func(surface XWaylandSurface, edges Edges)) { + man.add(unsafe.Pointer(s.p), &s.p.events.request_resize, func(data unsafe.Pointer) { + event := (*C.struct_wlr_xwayland_resize_event)(data) + cb(s, Edges(event.edges)) + }) +} + +func (s XWaylandSurface) OnRequestConfigure(cb func(surface XWaylandSurface, x int16, y int16, width uint16, height uint16)) { + man.add(unsafe.Pointer(s.p), &s.p.events.request_configure, func(data unsafe.Pointer) { + event := (*C.struct_wlr_xwayland_surface_configure_event)(data) + cb(s, int16(event.x), int16(event.y), uint16(event.width), uint16(event.height)) + }) +} + +/** Create an Xwayland server and XWM. + * + * The server supports a lazy mode in which Xwayland is only started when a + * client tries to connect. + */ +func (d Display) XWaylandCreate(compositor Compositor, lazy bool) XWayland { + p := C.wlr_xwayland_create(d.p, compositor.p, C.bool(lazy)) + return XWayland{p: p} +} diff --git a/xkb/xkb.go b/xkb/xkb.go index a515a4a..c157a05 100644 --- a/xkb/xkb.go +++ b/xkb/xkb.go @@ -32,8 +32,8 @@ func WrapState(p unsafe.Pointer) State { return State{p: (*C.struct_xkb_state)(p)} } -func NewContext() Context { - p := C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS) +func NewContext(ksf KeySymFlags) Context { + p := C.xkb_context_new(uint32(ksf)) return Context{p: p} }