Commit Diff


commit - /dev/null
commit + 25e295bcb3df3888109fa1dae32b464bec6c9db1
blob - /dev/null
blob + b3eb2214468db5701c4530cb5474013d078a0bb4 (mode 644)
--- /dev/null
+++ net.entf.graphics.asd
@@ -0,0 +1,26 @@
+(defsystem "net.entf.graphics"
+  :depends-on ("net.entf.graphics/window"))
+
+(defsystem "net.entf.graphics/window"
+  :pathname #P"window/"
+  :depends-on ("net.entf.graphics/window/wayland")
+  :components ((:file "package")))
+
+(defsystem "net.entf.graphics/window/protocol"
+  :pathname #P"window/"
+  :depends-on ("bordeaux-threads")
+  :components ((:file "protocol")))
+
+(defsystem "net.entf.graphics/window/wayland"
+  :pathname #P"window/wayland/"
+  :depends-on ("net.entf.graphics/window/protocol"
+               "wayflan"
+               "alexandria"
+               "posix-shm")
+  :components ((:file "package")
+               (:file "protocols"
+                :depends-on ("package"))
+               (:file "wayland"
+                :depends-on ("package" "protocols"))
+               (:module "wayland-protocols"
+                :components ((:static-file "xdg-decoration-unstable-v1.xml")))))
blob - /dev/null
blob + 55cfcbfee52285a3836a0d8635ab7b36900351f1 (mode 644)
--- /dev/null
+++ window/package.lisp
@@ -0,0 +1,5 @@
+(uiop:define-package #:net.entf.graphics/window/all
+  (:use #:cl #:net.entf.graphics/window/wayland)
+  (:nicknames #:net.entf.graphics/window)
+  (:use-reexport #:net.entf.graphics/window/protocol))
+(in-package #:net.entf.graphics/window/all)
blob - /dev/null
blob + d440e6f1889a0ac60ae50c468e0d5d73d372ac17 (mode 644)
--- /dev/null
+++ window/protocol.lisp
@@ -0,0 +1,46 @@
+(uiop:define-package #:net.entf.graphics/window/protocol
+  (:use #:cl)
+  (:import-from #:bordeaux-threads)
+  (:export
+   #:backend
+   #:start-event-loop
+   #:stop-event-loop
+   #:window
+   #:window-title
+   #:window-app-id
+   #:window-size
+   #:make-window))
+(in-package #:net.entf.graphics/window/protocol)
+
+(defclass backend ()
+  ((event-loop-thread
+    :accessor event-loop-thread)))
+
+(defgeneric start-event-loop (backend &key blocking?))
+
+(defmethod start-event-loop :around ((backend backend) &key blocking?)
+  (if blocking?
+      (prog1
+          (setf (event-loop-thread backend) (bt2:current-thread))
+        (call-next-method backend :blocking? blocking?))
+      (bt2:make-thread (lambda ()
+                         (start-event-loop backend :blocking? t))
+                       :name (format nil "net.entf.graphics/window event loop for ~A" backend))))
+
+(defgeneric stop-event-loop (backend))
+
+(defclass window ()
+  ((title
+    :initarg :title
+    :accessor window-title)
+   (app-id
+    :initarg :app-id
+    :accessor window-app-id)
+   (size
+    :initarg :size
+    :accessor window-size)))
+
+(defgeneric make-window (backend &key title app-id size &allow-other-keys))
+
+(defgeneric window-minimize (window))
+(defgeneric window-maximize (window))
blob - /dev/null
blob + b9a66ff9c07951326ffc2d1af78d50adfe53092b (mode 644)
--- /dev/null
+++ window/wayland/package.lisp
@@ -0,0 +1,11 @@
+(defpackage #:net.entf.graphics/window/wayland/protocols
+  (:use #:cl #:wayflan-client.xdg-shell))
+
+(defpackage #:net.entf.graphics/window/wayland
+  (:use #:cl #:net.entf.graphics/window/protocol
+        #:wayflan-client #:wayflan-client.xdg-shell
+        #:net.entf.graphics/window/wayland/protocols)
+  (:local-nicknames (#:a #:alexandria)
+                    (#:shm #:posix-shm))
+  (:export
+   #:make-wayland-backend))
blob - /dev/null
blob + 77cb1f0b7311cddc05070c019b6c57134305ed7c (mode 644)
--- /dev/null
+++ window/wayland/protocols.lisp
@@ -0,0 +1,5 @@
+(in-package #:net.entf.graphics/window/wayland/protocols)
+
+(xyz.shunter.wayflan.client.scanner:wl-include
+ '("net.entf.graphics/window/wayland" "wayland-protocols" "xdg-decoration-unstable-v1.xml")
+ :export t)
blob - /dev/null
blob + c175cadf7d7e7578e796af037bc281621888bf8c (mode 644)
--- /dev/null
+++ window/wayland/wayland-protocols/xdg-decoration-unstable-v1.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_decoration_unstable_v1">
+  <copyright>
+    Copyright © 2018 Simon Ser
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <interface name="zxdg_decoration_manager_v1" version="1">
+    <description summary="window decoration manager">
+      This interface allows a compositor to announce support for server-side
+      decorations.
+
+      A window decoration is a set of window controls as deemed appropriate by
+      the party managing them, such as user interface components used to move,
+      resize and change a window's state.
+
+      A client can use this protocol to request being decorated by a supporting
+      compositor.
+
+      If compositor and client do not negotiate the use of a server-side
+      decoration using this protocol, clients continue to self-decorate as they
+      see fit.
+
+      Warning! The protocol described in this file is experimental and
+      backward incompatible changes may be made. Backward compatible changes
+      may be added together with the corresponding interface version bump.
+      Backward incompatible changes are done by bumping the version number in
+      the protocol and interface names and resetting the interface version.
+      Once the protocol is to be declared stable, the 'z' prefix and the
+      version number in the protocol and interface names are removed and the
+      interface version number is reset.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the decoration manager object">
+        Destroy the decoration manager. This doesn't destroy objects created
+        with the manager.
+      </description>
+    </request>
+
+    <request name="get_toplevel_decoration">
+      <description summary="create a new toplevel decoration object">
+        Create a new decoration object associated with the given toplevel.
+
+        Creating an xdg_toplevel_decoration from an xdg_toplevel which has a
+        buffer attached or committed is a client error, and any attempts by a
+        client to attach or manipulate a buffer prior to the first
+        xdg_toplevel_decoration.configure event must also be treated as
+        errors.
+      </description>
+      <arg name="id" type="new_id" interface="zxdg_toplevel_decoration_v1"/>
+      <arg name="toplevel" type="object" interface="xdg_toplevel"/>
+    </request>
+  </interface>
+
+  <interface name="zxdg_toplevel_decoration_v1" version="1">
+    <description summary="decoration object for a toplevel surface">
+      The decoration object allows the compositor to toggle server-side window
+      decorations for a toplevel surface. The client can request to switch to
+      another mode.
+
+      The xdg_toplevel_decoration object must be destroyed before its
+      xdg_toplevel.
+    </description>
+
+    <enum name="error">
+      <entry name="unconfigured_buffer" value="0"
+        summary="xdg_toplevel has a buffer attached before configure"/>
+      <entry name="already_constructed" value="1"
+        summary="xdg_toplevel already has a decoration object"/>
+      <entry name="orphaned" value="2"
+        summary="xdg_toplevel destroyed before the decoration object"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the decoration object">
+        Switch back to a mode without any server-side decorations at the next
+        commit.
+      </description>
+    </request>
+
+    <enum name="mode">
+      <description summary="window decoration modes">
+        These values describe window decoration modes.
+      </description>
+      <entry name="client_side" value="1"
+        summary="no server-side window decoration"/>
+      <entry name="server_side" value="2"
+        summary="server-side window decoration"/>
+    </enum>
+
+    <request name="set_mode">
+      <description summary="set the decoration mode">
+        Set the toplevel surface decoration mode. This informs the compositor
+        that the client prefers the provided decoration mode.
+
+        After requesting a decoration mode, the compositor will respond by
+        emitting an xdg_surface.configure event. The client should then update
+        its content, drawing it without decorations if the received mode is
+        server-side decorations. The client must also acknowledge the configure
+        when committing the new content (see xdg_surface.ack_configure).
+
+        The compositor can decide not to use the client's mode and enforce a
+        different mode instead.
+
+        Clients whose decoration mode depend on the xdg_toplevel state may send
+        a set_mode request in response to an xdg_surface.configure event and wait
+        for the next xdg_surface.configure event to prevent unwanted state.
+        Such clients are responsible for preventing configure loops and must
+        make sure not to send multiple successive set_mode requests with the
+        same decoration mode.
+      </description>
+      <arg name="mode" type="uint" enum="mode" summary="the decoration mode"/>
+    </request>
+
+    <request name="unset_mode">
+      <description summary="unset the decoration mode">
+        Unset the toplevel surface decoration mode. This informs the compositor
+        that the client doesn't prefer a particular decoration mode.
+
+        This request has the same semantics as set_mode.
+      </description>
+    </request>
+
+    <event name="configure">
+      <description summary="notify a decoration mode change">
+        The configure event configures the effective decoration mode. The
+        configured state should not be applied immediately. Clients must send an
+        ack_configure in response to this event. See xdg_surface.configure and
+        xdg_surface.ack_configure for details.
+
+        A configure event can be sent at any time. The specified mode must be
+        obeyed by the client.
+      </description>
+      <arg name="mode" type="uint" enum="mode" summary="the decoration mode"/>
+    </event>
+  </interface>
+</protocol>
blob - /dev/null
blob + 5612987d3d155605ad94c4016b166d716da55c77 (mode 644)
--- /dev/null
+++ window/wayland/wayland.lisp
@@ -0,0 +1,167 @@
+(in-package #:net.entf.graphics/window/wayland)
+
+(defclass wayland-backend (backend)
+  ((stop-requested?
+    :initform nil)
+   display
+   registry
+   compositor
+   xdg-wm-base
+   shm
+   (decoration-manager
+    :initform nil)))
+
+(defun make-wayland-backend ()
+  (make-instance 'wayland-backend))
+
+(defmethod queue-event ((backend wayland-backend) event-func)
+  (with-slots (display)
+      backend
+    (let ((callback (wl-display.sync display)))
+      (push (lambda (&rest event)
+              (declare (ignore event))
+              (destroy-proxy callback)
+              (funcall event-func))
+            (wl-proxy-hooks callback)))))
+
+(defmethod handle-registry ((backend wayland-backend) event-type &rest event-data)
+  (with-slots (registry compositor xdg-wm-base shm decoration-manager)
+      backend
+    (ecase event-type
+      (:global
+       (format t "global: ~{~A ~}~%" event-data)
+       (a:when-let* ((interface (find-interface-named (second event-data)))
+                     (interface-class (class-name interface)))
+         (labels ((bind (version)
+                    (wl-registry.bind registry (first event-data) interface-class version)))
+           (format t "Interface found: ~A~%" interface-class)
+           (case interface-class
+             (wl-compositor
+              (setf compositor (bind 4)))
+             (xdg-wm-base
+              (setf xdg-wm-base (bind 2)))
+             (wl-shm
+              (setf shm (bind 1)))
+             (zxdg-decoration-manager-v1
+              (setf decoration-manager (bind 1))))))))))
+
+(defmethod start-event-loop ((backend wayland-backend) &key blocking?)
+  (declare (ignore blocking?))
+  (with-slots (stop-requested? registry)
+      backend
+    (setf stop-requested? nil)
+    (with-open-display (display)
+      (setf (slot-value backend 'display) display)
+      (setf registry (wl-display.get-registry display))
+      (push (a:curry 'handle-registry backend) (wl-proxy-hooks registry))
+      (wl-display-roundtrip display)
+      (loop while (not stop-requested?)
+            do (wl-display-dispatch-event display))
+      (format t "event loop shutdown~%"))))
+
+(defmethod stop-event-loop ((backend wayland-backend))
+  (with-slots (display stop-requested?)
+      backend
+    (queue-event backend (lambda ()
+                           (setf stop-requested? t)))))
+
+(defclass wayland-shm-buffer ()
+  ((width
+    :initarg :width)
+   (height
+    :initarg :height)
+   length
+   shm
+   mmap
+   buffer))
+
+(defmethod allocate-shm ((shm-buffer wayland-shm-buffer) wl-shm)
+  (with-slots (shm mmap buffer width height length)
+      shm-buffer
+    (let ((stride (* width 4)))
+      (setf length (* stride height))
+      (setf shm (shm:open-shm* :direction :io))
+      (shm:truncate-shm shm length)
+      (setf mmap (shm:mmap-shm shm length))
+      (with-proxy (pool (wl-shm.create-pool wl-shm (shm:shm-fd shm) length))
+        (setf buffer (wl-shm-pool.create-buffer pool 0 width height stride :xrgb8888))))))
+
+(defmethod free-shm-buffer ((shm-buffer wayland-shm-buffer))
+  (with-slots (mmap shm length buffer)
+      shm-buffer
+    (shm:munmap mmap length)
+    (shm:close-shm shm)
+    (wl-buffer.destroy buffer)))
+
+(defclass wayland-window (window)
+  ((backend
+    :initarg :backend)
+   (buffer
+    :initform nil)
+   surface
+   xdg-surface
+   xdg-toplevel
+   (xdg-decoration
+    :initform nil)))
+
+(defmethod allocate-buffer ((window wayland-window))
+  (with-slots (backend buffer)
+      window
+    (with-slots (shm)
+        backend
+      (when buffer
+        (free-shm-buffer buffer))
+      (setf buffer (make-instance 'wayland-shm-buffer
+                                  :width (first (window-size window))
+                                  :height (second (window-size window))))
+      (allocate-shm buffer shm))))
+
+(defmethod make-window ((backend wayland-backend)
+                        &key
+                          (title "New Window")
+                          (app-id (format nil "net.entf.graphics-~A" (+ (random 99) 100)))
+                          (requested-size (list 300 300))
+                        &allow-other-keys)
+  (with-slots (compositor xdg-wm-base decoration-manager)
+      backend
+    (let ((window (make-instance 'wayland-window
+                                 :backend backend
+                                 :title title
+                                 :app-id app-id
+                                 :size requested-size)))
+      (with-slots (buffer surface xdg-surface xdg-toplevel xdg-decoration)
+          window
+        (setf surface (wl-compositor.create-surface compositor)
+              xdg-surface (xdg-wm-base.get-xdg-surface xdg-wm-base surface)
+              xdg-toplevel (xdg-surface.get-toplevel xdg-surface))
+        (push (evlambda
+                (:configure
+                 (serial)
+                 (xdg-surface.ack-configure xdg-surface serial)
+                 (let ((buffer (allocate-buffer window)))
+                   (wl-surface.attach surface buffer 0 0)
+                   (wl-surface.commit surface))))
+              (wl-proxy-hooks xdg-surface))
+        (push (evlambda
+                (:configure
+                 (new-width new-height states)
+                 (declare (ignore states))
+                 (if (or (zerop new-width) (zerop new-height))
+                     (setf (window-size window) requested-size)
+                     (setf (window-size window) (list new-width new-height))))
+                (:close
+                 ()
+                 (when xdg-decoration
+                   (zxdg-toplevel-decoration-v1.destroy xdg-decoration))
+                 (xdg-toplevel.destroy xdg-toplevel)
+                 (xdg-surface.destroy xdg-surface)
+                 (free-shm-buffer buffer)))
+              (wl-proxy-hooks xdg-toplevel))
+        (xdg-toplevel.set-title xdg-toplevel title)
+        (xdg-toplevel.set-app-id xdg-toplevel app-id)
+        (when decoration-manager
+          (setf xdg-decoration (zxdg-decoration-manager-v1.get-toplevel-decoration
+                                decoration-manager xdg-toplevel))
+          (zxdg-toplevel-decoration-v1.set-mode xdg-decoration :server-side))
+        (wl-surface.commit surface))
+      window)))