diff --git a/siwin.nimble b/siwin.nimble index 0e8fe23..e0ca686 100644 --- a/siwin.nimble +++ b/siwin.nimble @@ -30,7 +30,6 @@ feature "dev": requires "sdl2" requires "https://github.com/planetis-m/vulkan#b223dc9" - when fileExists("src/siwin/build_utils/tasks.nim"): include "src/siwin/build_utils/tasks.nim" diff --git a/src/siwin/platforms/any/window.nim b/src/siwin/platforms/any/window.nim index 624939f..d51238d 100644 --- a/src/siwin/platforms/any/window.nim +++ b/src/siwin/platforms/any/window.nim @@ -145,6 +145,37 @@ type constraintAdjustment*: set[PopupConstraintAdjustment] reactive*: bool + LayerSurfaceLayer* {.siwin_enum.} = enum + lslBackground + lslBottom + lslTop + lslOverlay + + LayerSurfaceAnchor* {.siwin_enum.} = enum + lsaTop + lsaBottom + lsaLeft + lsaRight + + LayerSurfaceKeyboardMode* {.siwin_enum.} = enum + lskNone + lskExclusive + lskOnDemand + + LayerSurfaceMargins* = object + top*: int32 + right*: int32 + bottom*: int32 + left*: int32 + + LayerSurfaceConfig* = object + layer*: LayerSurfaceLayer + anchors*: set[LayerSurfaceAnchor] + margins*: LayerSurfaceMargins + exclusiveZone*: int32 + keyboardMode*: LayerSurfaceKeyboardMode + namespace*: string + WindowVisualCapability* {.siwin_enum.} = enum wvcBackdropBlur wvcBackdropBlurRegion diff --git a/src/siwin/platforms/wayland/siwinGlobals.nim b/src/siwin/platforms/wayland/siwinGlobals.nim index 86f2fd4..5f895ab 100644 --- a/src/siwin/platforms/wayland/siwinGlobals.nim +++ b/src/siwin/platforms/wayland/siwinGlobals.nim @@ -6,9 +6,14 @@ import ./[libwayland, protocol, bitfields, libdecor] type WaylandExtensionNotFound* = object of CatchableError + WaylandOutput* = object + registryName*: uint32 + output*: Wl_output + SiwinGlobalsWayland* = ref SiwinGlobalsWaylandObj SiwinGlobalsWaylandObj* = object of SiwinGlobals seatEventsInitialized*: bool + seatCapabilitiesChanged*: proc(globals: SiwinGlobalsWayland) dataDeviceManagerEventsInitialized*: bool display*: WlDisplay @@ -26,6 +31,7 @@ type viewporter*: Wp_viewporter fractionalScaleManager*: Wp_fractional_scale_manager_v1 xdgToplevelIconManager*: Xdg_toplevel_icon_manager_v1 + outputs*: seq[WaylandOutput] serverDecorationManager*: Zxdg_decoration_manager_v1 plasmaShell*: Org_kde_plasma_shell @@ -118,6 +124,8 @@ proc initRegistryCallbacks(globals: SiwinGlobalsWayland) = globals.seatCapabilities = globals.seatCapabilities.typeof.default globals.seat.onCapabilities: globals.seatCapabilities = capabilities.asBitfield + if not globals.seatCapabilitiesChanged.isNil: + globals.seatCapabilitiesChanged(globals) discard wl_display_roundtrip globals.display @@ -155,6 +163,9 @@ proc initRegistryCallbacks(globals: SiwinGlobalsWayland) = addRegistry Wp_cursor_shape_manager_v1: globals.cursorShapeManager = binded + addRegistry Wl_output: + globals.outputs.add WaylandOutput(registryName: name, output: binded) + proc isWaylandAvailable*: bool = proc isSocket(filename: string): bool = @@ -208,6 +219,12 @@ proc newWaylandGlobals*(): SiwinGlobalsWayland = if interfaceString == targetIface: callback(globals.registry, name, version) + globals.registry.onGlobal_remove: + for idx in countdown(globals.outputs.high, 0): + if globals.outputs[idx].registryName == name: + release globals.outputs[idx].output + globals.outputs.delete(idx) + proc roundtrip*(globals: SiwinGlobalsWayland) = discard wl_display_roundtrip globals.display diff --git a/src/siwin/platforms/wayland/window.nim b/src/siwin/platforms/wayland/window.nim index 7e17450..14587a2 100644 --- a/src/siwin/platforms/wayland/window.nim +++ b/src/siwin/platforms/wayland/window.nim @@ -13,6 +13,7 @@ privateAccess Window type ScreenWayland* = ref object of Screen id: cint + output: Wl_output WindowWaylandKind* {.pure.} = enum XdgSurface @@ -22,6 +23,7 @@ type WindowWayland* = ref WindowWaylandObj WindowWaylandObj* = object of Window globals: SiwinGlobalsWayland + screen: ScreenWayland surface: Wl_surface xdgSurface: Xdg_surface xdgToplevel: Xdg_toplevel @@ -63,6 +65,7 @@ type lastTextEntered: string lastPressedKeyTime: Time lastKeyRepeatedTime: Time + initialConfigureReceived: bool bufferScaleFactor: int32 fractionalScaleFactor: float32 popupRepositionToken: uint32 @@ -247,16 +250,19 @@ proc refreshKeyboardModifiers(window: WindowWayland) = method swapBuffers(window: WindowWayland) {.base.} = discard proc screenCountWayland*(globals: SiwinGlobalsWayland): int32 = - ## todo - 1 + globals.outputs.len.int32 proc screenWayland*(globals: SiwinGlobalsWayland, number: int32): ScreenWayland = new result if number notin 0.. 0 and h > 0: @@ -1157,12 +1165,15 @@ var cursor: Wp_cursor_shape_device_v1 var cursor_surface: CursorWayland proc initSeatEvents*(globals: SiwinGlobalsWayland) = - if globals.seatEventsInitialized: return - globals.seatEventsInitialized = true + if not globals.seatEventsInitialized: + globals.seatEventsInitialized = true + globals.seatCapabilitiesChanged = proc(updatedGlobals: SiwinGlobalsWayland) = + updatedGlobals.initSeatEvents() if globals.seat == nil: return - if `WlSeat / Capability`.`pointer` in globals.seatCapabilities: + if `WlSeat / Capability`.`pointer` in globals.seatCapabilities and + globals.seat_pointer == nil: globals.seat_pointer = globals.seat.get_pointer cursor_surface = globals.loadBuiltinCursor(arrow) @@ -1302,7 +1313,8 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = return - if `WlSeat / Capability`.keyboard in globals.seatCapabilities: + if `WlSeat / Capability`.keyboard in globals.seatCapabilities and + globals.seat_keyboard == nil: globals.seat_keyboard = globals.seat.get_keyboard # Default repeat values; compositor-provided repeat_info overrides these. globals.seat_keyboard_repeatSettings = (rate: 25, delay: 600) @@ -1409,7 +1421,7 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = globals.seat_keyboard_repeatSettings = (rate: rate, delay: delay) - if globals.tabletManager != nil: + if globals.tabletManager != nil and globals.seat_tablet == nil: globals.seat_tablet = globals.tabletManager.get_tablet_seat(globals.seat) globals.seat_tablet.onTool_added: @@ -1727,6 +1739,7 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool window.xdgToplevel = window.xdgSurface.get_toplevel window.xdgSurface.onConfigure: + window.initialConfigureReceived = true window.xdgSurface.ack_configure(serial) redraw window @@ -1774,6 +1787,7 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool destroy positioner window.xdgSurface.onConfigure: + window.initialConfigureReceived = true window.xdgSurface.ack_configure(serial) redraw window @@ -1792,9 +1806,10 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool window.resize(ivec2(width, height)) of LayerSurface: + expectExtension window.globals.layerShell window.layerShellSurface = window.globals.layerShell.get_layer_surface( window.surface, - Wl_output(proxy: Wl_proxy(raw: nil)), + window.screen.output, `Zwlr_layer_shell_v1/Layer`(window.layer.int), window.namespace.cstring ) @@ -1802,6 +1817,7 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool window.redraw() window.layerShellSurface.onConfigure: + window.initialConfigureReceived = true window.layerShellSurface.ack_configure(serial) window.resize(ivec2(width.int32, height.int32)) @@ -1898,6 +1914,19 @@ proc setExclusiveZone*(window: WindowWayland, zone: int32) = window.layerShellSurface.set_exclusive_zone(zone) +proc setMargins*(window: WindowWayland, margins: LayerSurfaceMargins) = + if window.layerShellSurface == nil: + raise newException( + ValueError, + "Attempt to set margins when layer shell surface hasn't been initialized." & + "\nHint: Construct this window with newLayerSurfaceWindowWayland()." + ) + + window.layerShellSurface.set_margin( + margins.top, margins.right, margins.bottom, margins.left + ) + + proc constructClipboardContent*( data: sink string, kind: ClipboardContentKind, mimeType: string ): ClipboardContent = @@ -2111,6 +2140,24 @@ method `content=`*(clipboard: ClipboardWayland, content: ClipboardConvertableCon discard wl_display_roundtrip clipboard.globals.display +proc configureSurface*(window: WindowWayland) = + ## Commit the role setup and wait for the compositor's initial configure. + ## + ## An xdg_surface must acknowledge its initial configure before a renderer + ## attaches its first buffer. Vulkan renderers may create and present a + ## swapchain before the caller reaches `firstStep`, so Vulkan window setup + ## calls this before exposing the surface. + window.surface.commit() + while true: + if window.globals.libdecorCtx != nil: + discard libdecor_dispatch(window.globals.libdecorCtx, 0) + let eventCount = wl_display_roundtrip(window.globals.display) + if eventCount < 0: + raise newException(RoundtripFailed, "wl_display_roundtrip() returned " & $eventCount) + if window.initialConfigureReceived: + break + + method firstStep*(window: WindowWayland, makeVisible = true) = if makeVisible: window.visible = true @@ -2118,10 +2165,7 @@ method firstStep*(window: WindowWayland, makeVisible = true) = # window.m_pos = window.handle.geometry.pos # window.mouse.pos = cursor().pos - window.m_pos - window.surface.commit() - if window.globals.libdecorCtx != nil: - discard libdecor_dispatch(window.globals.libdecorCtx, 0) - discard wl_display_roundtrip window.globals.display + window.configureSurface() if window.opened: window.eventsHandler.onResize.pushEvent ResizeEvent(window: window, size: window.size, initial: true) window.lastTickTime = getTime() @@ -2227,6 +2271,55 @@ proc newSoftwareRenderingWindowWayland*( result.title = title if not resizable: result.resizable = false +proc newLayerSurfaceWindowWayland*( + globals: SiwinGlobalsWayland, + size = ivec2(1280, 32), + title = "", + screen: ScreenWayland, + config: LayerSurfaceConfig, + transparent = false, +): WindowWaylandSoftwareRendering = + new result + result.globals = globals + result.softwarePresentEnabled = true + result.kind = WindowWaylandKind.LayerSurface + result.layer = Layer(config.layer.ord) + result.namespace = config.namespace + result.basicInitWindow(size, screen) + result.setupWindow( + fullscreen = false, + frameless = true, + transparent = transparent, + size = size, + class = title, + ) + + var anchors: uint32 + for anchor in config.anchors: + anchors = anchors or (1'u32 shl anchor.ord) + result.layerShellSurface.set_anchor( + cast[`Zwlr_layer_surface_v1/Anchor`](anchors) + ) + result.setMargins(config.margins) + result.setExclusiveZone(config.exclusiveZone) + result.setKeyboardInteractivity( + LayerInteractivityMode(config.keyboardMode.ord) + ) + + let + requestedWidth = + if {lsaLeft, lsaRight} <= config.anchors: 0'u32 else: size.x.uint32 + requestedHeight = + if {lsaTop, lsaBottom} <= config.anchors: 0'u32 else: size.y.uint32 + result.layerShellSurface.set_size(requestedWidth, requestedHeight) + result.buffer = result.globals.create( + result.globals.shm, + result.bufferSize(size), + (if transparent: argb8888 else: xrgb8888), + bufferCount = 2, + ) + result.title = title + proc newPopupWindowWayland*( globals: SiwinGlobalsWayland, parent: WindowWayland, diff --git a/src/siwin/platforms/wayland/windowVulkan.nim b/src/siwin/platforms/wayland/windowVulkan.nim index 94f6d6c..74b9422 100644 --- a/src/siwin/platforms/wayland/windowVulkan.nim +++ b/src/siwin/platforms/wayland/windowVulkan.nim @@ -53,6 +53,7 @@ proc initVulkanWindow( window.basicInitWindow size, screen window.setupWindow fullscreen, frameless, transparent, size, class + window.configureSurface() # window.eglContext = newOpenglContext(window.surface.proxy.raw, size.x, size.y) # makeCurrent window.eglContext diff --git a/src/siwin/window.nim b/src/siwin/window.nim index de21084..eb97e22 100644 --- a/src/siwin/window.nim +++ b/src/siwin/window.nim @@ -148,6 +148,37 @@ when not siwin_use_lib: elif defined(macosx): result = newPopupWindowCocoa(parent.WindowCocoa, placement, transparent, grab) + proc newLayerSurfaceWindow*( + globals: SiwinGlobals, + size = ivec2(1280, 32), + title = "", + screen: int32 = -1, + config: LayerSurfaceConfig, + transparent = false, + ): Window = + when defined(linux) or defined(bsd): + if globals of SiwinGlobalsWayland: + let waylandGlobals = globals.SiwinGlobalsWayland + result = waylandGlobals.newLayerSurfaceWindowWayland( + size = size, + title = title, + screen = + if screen == -1: + waylandGlobals.defaultScreenWayland() + else: + waylandGlobals.screenWayland(screen), + config = config, + transparent = transparent, + ) + else: + raise SiwinPlatformSupportDefect.newException( + "Layer-shell surfaces require the Wayland platform" + ) + else: + raise SiwinPlatformSupportDefect.newException( + "Layer-shell surfaces require Wayland" + ) + when defined(android): proc loadExtensions*() =