~shunter/wayflan

0d7c532b072da536f5d1b37059c68ec0685990ee — Samuel Hunter 10 months ago 0846d2f
Expand docstrings, getting started, API reference

This commit touches up on existing documentation, adds the section (From
Zero to Globals), and completes documentation on the define- macros and
ASDF component in the API reference.
M doc/Client-API-Reference.md => doc/Client-API-Reference.md +133 -0
@@ 4,6 4,11 @@ The wayflan-client package includes all symbols from the [wayflan
package](./API-Reference.md) plus the interface for connecting to, sending
messages to, and dispatching events from a compositor.

The macro forms **define-interface**, **define-enum**, **define-request**, and
**define-event** are all used internally by `:wayflan-client-impl`, and are
pragmatically likely not used for anything except for prototyping new protocols
within Wayflan.

## [Metaclass] __wl-interface-class__ (__standard-class__)

**Description:**


@@ 202,3 207,131 @@ Return a lambda that accepts an event as specified in **wl-proxy-hooks**.
The lambda selects the clause by matching the *event-name* with the event type.
If a clause is selected, bind the event arguments by the
*destructuring-lambda-list* and run the clause *body*.

## [Macro] **define-interface** *name () &body options*

```
option ::= (:version VERSION)
         | (:interface-name INTERFACE-NAME)
         | (:documentation DOCUMENTATION)
```

**Arguments and Values:**

- *name* -- a __symbol__. The name of the interface class.
- *version* -- a positive __integer__. Defaults to 1.
- *interface-name* -- a __string__.
- *documentation* -- a __string__. Documentation attached to the defined class

**Description:**

Define a wayland object interface as a subclass of wl-proxy.

*Version* describes the latest supported version of the interface. Proxies can only be created with a version between 1 and *version* inclusive.
*Interface-name* describes the interface's name as sent down the wire, both by __wl-registry__'s *:global* event and runtime-typed *:new-id* args.

## [Macro] **define-enum** *name () &body (entry-specifiers &rest options)*

```
entry-specifier ::= (ENTRY-NAME VALUE &key DOCUMENTATION)
option ::= (:since SINCE)
         | (:bitfield BITFIELD)
         | (:documentation DOCUMENTATION)
```

**Arguments and Values:**

- *name* -- a __symbol__.
- *entry-name* -- a __keyword__.
- *value* -- a __wl-int__ or __wl-uint__.
- *documentation* -- a __string__.
- *since* -- a positive __integer__. Defaults to 1
- *bitfield* -- a __boolean__. Defaults to __false__.

**Description:**

Defines an argument subtype that associates integer values with keyword symbols.
When a request or event argument has the type `(:int NAME)` or `(:uint NAME)`,
then either the integer value is automatically decoded into a keyword (for
events), or the keyword argument is automatically encoded into an integer (for
requests). If *bitfield* is __true__, then instead, integer values are
automatically decoded into a __list__ of keywords whose associated values' bits
are associated with the subject value (for events), and keyword list arguments
are **logior**'d into a result integer (for requests).

Define-enum defines methods for **wl-enum-value** and **wl-enum-keyword**, with
the first argument specialized as *(eql NAME)*.

## [Macro] **define-request** *(name interface opcode) &body (arg-specifiers &rest options)*

```
arg-specifier ::= (NAME &key TYPE DOCUMENTATION)
option ::= (:type REQUEST-TYPE)
         | (:since VERSION)
         | (:documentation DOCUMENTATION)
```

**Arguments and Values:**

- *name* -- a __symbol__.
- *interface* -- a __symbol__, the name of an __interface-class__.
- *opcode* -- a non-negative __integer__.
- *request-type* -- either `:destructor` or *nil*. Defaults to *nil*.
- *version* -- a positive __integer__. Defaults to 1.
- *documentation* -- a __string__.

**Description:**

Defines a function implementing the interface's request.

**Define-request** currently only supports up to one `:new-id` argument per
request. If *request-type* is `:destructor`, then both the defined function and
**destroy-proxy** sends the message and marks the proxy as destroyed.

## [Macro] **define-event** *(name interface opcode) &body (arg-specifiers &rest options)*

```
arg-specifier ::= (NAME &key TYPE DOCUMENTATION)
option ::= (:type EVENT-TYPE)
         | (:since VERSION)
         | (:documentation DOCUMENTATION)
```

**Arguments and Values:**

- *name* -- a __keyword__.
- *interface* -- a __symbol__, the name of an __interface-class__.
- *opcode* -- a non-negative __integer__.
- *event-type* -- either `:destructor` or *nil*. Defaults to *nil*.
- *version* -- a positive __integer__. Defaults to 1.
- *documentation* -- a __string__.

**Description:**

Defines an event associated with the interface and opcode. Hooks in proxies
implementing this interface may accept an event payload beginning with the
event name, followed by its arguments.

## [ASDF Component] **:wayflan-client-impl** *name &key in-package export*

**Arguments and Values:**

- *name* -- a __string designator__.
- *in-package* -- a __string designator__.
- *export* -- a __boolean__. Defaults to *nil*.

**Description:**

Generates and loads a lisp file pointing to a protocol XML document.

On __prepare-op__ and __prepare-source-op__, the client generates a cached lisp
source file with a macro that points to the component input file's pathname.
This macro generates all code required to implement the interfaces, enums,
requests, and events defined in the protocol.

The component then performs on this source file on __compile-op__ and
__load-op__ like a normal source file component.

*In-package* is required, and defines the package wherein all protocol code
lives. If *export* is __true__, then all interned symbols are exported from the
package.

M doc/Getting-Started-With-Wayflan-Client.md => doc/Getting-Started-With-Wayflan-Client.md +44 -1
@@ 1,7 1,9 @@
# Getting Started with Wayflan Client

This document target those familiar with Wayland fundamentals, and aims to
explain how each Wayland concept integrates into Wayflan.
explain how each Wayland concept integrates into Wayflan. It starts with a
refresher, and then a more in-depth look at how each Wayland concept maps to
Lisp code.

To help with brevity in the snippets, I'm assuming all forms are evaluated
under `(use-package :wayflan-client)`.


@@ 184,6 186,47 @@ functions will immediately affect all proxies it lives under:
      (wl-proxy-hooks pointer))
```

## From Zero to Globals

This code snippet connects to a Wayland server, grabs the registry, and then
listens for a global event that broadcasts a `wl_compositor` global:

```lisp
(with-open-display (display)
  (let (registry compositor)
    (setf registry (wl-display.get-registry display))
    ;; Add a closure that listens for wl_registry.global events
    (push (evlambda
            (:global (name interface version)
             (when (string= interface "wl_compositor")
               (setf compositor (wl-registry.bind
                                  registry name 'wl-compositor 1)))))
          (wl-proxy-hooks registry))

    ;; Process all events up to this point
    (wl-display-roundtrip display)

    ;; !! Use the compositor and any other globals here
    (print compositor)))
```

Alternatively instead of matching for the string name, you can match on the
interface class name instead:

```lisp
(push (evlambda
        (:global (name interface version)
         (when (find-interface-named interface)
           (case (class-name (find-interface-named interface))
             (wl-compositor
               (setf compositor (wl-registry.bind
                                  registry name 'wl-compositor 5)))
             (wl-shm
               (setf shm (wl-registry.bind
                           registry name 'wl-shm 1)))))))
      (wl-proxy-hooks registry))
```

## Marshalling Enums

In Wayflan, enums live as keywords rather than numeric values:

M src/client/asdf.lisp => src/client/asdf.lisp +3 -1
@@ 14,7 14,9 @@
(defclass wayflan-client-impl (cl-source-file)
  ((type :initform "xml")
   (in-package :initarg :in-package)
   (export :initarg :export :initform nil)))
   (export :initarg :export :initform nil))
  (:documentation
    "Generates and loads a lisp file pointing to a protocol XML document"))

(defun wayflan-scan-output-file (c)
  (uiop:merge-pathnames*

M src/client/client.lisp => src/client/client.lisp +7 -10
@@ 390,7 390,7 @@ destructuring lambda-list bound under the case's body."
    clauses :from-end t))

(defmacro define-interface (name () &body options)
  "Define a wl-proxy CLOS subclass and assign the interface's name to the interface table, accessible via #'FIND-INTERFACE-NAMED.
  "Define a wayland object interface as subclass of wl-proxy.

NAME - The name of the interface class.



@@ 398,8 398,6 @@ OPTIONS:

(:version VERSION) - Latest supported version of the interface.
(:documentation DOCSTRING) - Docstring attached to the defined class.
(:skip-defclass BOOLEAN) - If true, do not define the interface class.
                           Used by cl-autowrap when it reads wl_display.
(:interface-name STRING) - The name of the interface as listed by the wl-registry on a wl-registry-global-event."
  (%option-bind (version documentation skip-defclass interface-name) options
    `(progn


@@ 438,15 436,14 @@ OPTIONS:
        (push (car entry) result)))))

(defmacro define-enum (name () &body (entry-specifiers &rest options))
  "Define a parameter that associates each entry keyword with an index in the array.

  " Defines an argument subtype that associates integer values with keyword symbols.
NAME - The name of the enum class. Used as arguments in Wayland types :INT and :UINT.

OPTIONS:

(:since INTEGER) - The interface version since it appeared.
(:documentation DOCSTRING) - Ignored.
(:bitfield BOOLEAN) - Whether individual bits have specific meanings. If set, enums are decoded as a list of arguments, rather than a single argument."
(:bitfield BOOLEAN) - Whether individual bits have specific meanings. If set, enums are coded as a list of keywords, rather than a single keyword.
(:documentation DOCSTRING) - Ignored."
  (%option-bind (bitfield) options
    `(let ((table ',(mapcar (%slambda (argument value &key &allow-other-keys)
                              (cons argument value))


@@ 467,7 464,7 @@ OPTIONS:
       ',name)))

(defmacro define-request ((name interface opcode) &body (arg-specifiers &rest options))
  "Define a function implementing the wl request.
  "Define a function implementing the interface's request.
DEFINE-REQUEST currently only supports up to one :NEW-ID argument per request.

NAME - The name of the request function.


@@ 480,9 477,9 @@ ARG-SPECIFIERS - Each specifier takes the lambda list (name &key type documentat

OPTIONS:

(:DOCUMENTATION STRING) - Provided to the function as its docstring.
(:TYPE KEYWORD) - So far, only :DESTRUCTOR is a valid type.
(:SINCE VERSION) - Minimum interface version of the proxy object."
(:SINCE VERSION) - Minimum interface version of the proxy object.
(:DOCUMENTATION STRING) - Provided to the function as its docstring."
  (assert (>= 1 (count :new-id arg-specifiers
                       :test #'%wltype=
                       :key (curry #'%sgetf :type))))