blob: b22006a65d941cb3d473bcf869bcee83e51a314f [file] [log] [blame] [edit]
;;;; libbrlapi - A library providing access to braille terminals for applications.
;;;;
;;;; Copyright (C) 2006-2023 by Mario Lang <mlang@delysid.org>
;;;;
;;;; libbrlapi comes with ABSOLUTELY NO WARRANTY.
;;;;
;;;; This is free software, placed under the terms of the
;;;; GNU Lesser General Public License, as published by the Free Software
;;;; Foundation; either version 2.1 of the License, or (at your option) any
;;;; later version. Please see the file LICENSE-LGPL for details.
;;;;
;;;; Web Page: http://brltty.app/
;;;;
;;;; This software is maintained by Dave Mielke <dave@mielke.cc>.
(eval-when (:compile-toplevel)
(declaim (optimize (safety 3) (debug 3))))
(in-package :brlapi)
;;;; * The DISPLAY class
(defclass display ()
((handle :initarg :handle :reader connection-handle)
(fd :initarg :fd :reader file-descriptor)
(auth :initarg :auth :reader server-auth)
(host :initarg :host :reader server-host)
(tty :initform nil :reader entered-tty)))
(defmethod property-list ((obj display))
"Return various properties as a lsit of three-element lists.
The sublist for each returned property contains, in order, its name, its value, and its format string."
(list*
(list "version" (multiple-value-list (library-version)) "~{~D~^.~}")
(if (is-connected obj)
(multiple-value-bind (width height) (display-size obj)
(list
(list "host" (server-host obj) "~A")
(list "auth" (server-auth obj) "~A")
(list "fd" (file-descriptor obj) "~D")
(list "driver" (driver-name obj) "~A")
(list "model" (model-identifier obj) "~A")
(list "width" width "~D")
(list "height" height "~D")
)
)
)
)
)
(defmethod print-object ((obj display) stream)
(print-unreadable-object (obj stream :type t)
(format stream "~{~A~^ ~}"
(loop
for (name value format) in (property-list obj) by #'cdr
append (list (format nil "~A=~@?" name format value))
)
)
)
)
;;;; * Error handling
(defctype brlapi-code :int)
(define-condition brlapi-error (error)
((text :initarg :text :reader brlapi-error-text))
(:report (lambda (c stream)
(format stream "libbrlapi function returned ~A"
(brlapi-error-text c))))
(:documentation "Signalled when a libbrlapi function answers with -1."))
(defmethod error-message ()
"The message for the most recent error."
(foreign-funcall "brlapi_strerror"
:pointer (foreign-funcall "brlapi_error_location" :pointer)
:string)
)
(defmethod translate-from-foreign (value (name (eql 'brlapi-code)))
"Raise a BRLAPI-ERROR if VALUE, a brlapi-code, is -1."
(declare (integer value))
(if (eql value -1)
(error 'brlapi-error :text (error-message))
value))
;;;; * Connection management
(defmethod is-connected ((obj display))
(and
obj
(let
((fd (file-descriptor obj)))
(and fd (numberp fd) (not (< fd 0)))
)
)
)
(defcstruct settings
"Connection setting structure."
(auth :string)
(host :string))
(defun open-connection (&key auth host)
"Open a new connection to BRLTTY on HOST usng AUTH for authorization.
Return a DISPLAY object which can further be used to interact with BRLTTY."
(with-foreign-object (settings 'settings)
(setf (foreign-slot-value settings 'settings 'auth)
(if (stringp auth) auth (null-pointer))
(foreign-slot-value settings 'settings 'host)
(if (stringp host) host (null-pointer)))
(let* ((handle (foreign-alloc :char :count (foreign-funcall "brlapi_getHandleSize" :int)))
(fd (foreign-funcall "brlapi__openConnection"
:pointer handle
:pointer settings
:pointer settings
brlapi-code))
(display (make-instance 'display :handle handle :fd fd
:auth (foreign-slot-value settings 'settings 'auth)
:host (foreign-slot-value settings 'settings 'host))))
#+sbcl (sb-ext:finalize display (lambda ()
(foreign-funcall "brlapi__closeConnection" :pointer handle :void)
(foreign-free handle)))
display)))
(defmethod close-connection ((obj display))
(foreign-funcall "brlapi__closeConnection" :pointer (connection-handle obj) :void)
(setf (slot-value obj 'fd) nil)
(foreign-free (connection-handle obj))
(setf (slot-value obj 'handle) nil))
;;;; * Querying the display
(defmethod library-version ()
"Return the version of the API as multiple values (major, minor, revision)."
(with-foreign-objects ((major :int) (minor :int) (revision :int))
(foreign-funcall "brlapi_getLibraryVersion"
:pointer major :pointer minor :pointer revision
:void
)
(values (mem-ref major :int) (mem-ref minor :int) (mem-ref revision :int))
)
)
(defmethod driver-name ((obj display))
"Return the currently used driver name."
(with-foreign-pointer-as-string ((str str-size) 64)
(foreign-funcall "brlapi__getDriverName" :pointer (connection-handle obj)
:string str :int str-size brlapi-code)))
(defmethod model-identifier ((obj display))
"Return the currently used display model."
(with-foreign-pointer-as-string ((str str-size) 64)
(foreign-funcall "brlapi__getModelIdentifier" :pointer (connection-handle obj)
:string str :int str-size brlapi-code)))
(defmethod display-size ((obj display))
"Return the dimensions of DISPLAY as multiple values.
The first value represents the x dimension and the second the y dimension."
(with-foreign-objects ((x :int) (y :int))
(foreign-funcall "brlapi__getDisplaySize" :pointer (connection-handle obj) :pointer x :pointer y brlapi-code)
(values (mem-ref x :int) (mem-ref y :int))))
;;;; * TTY mode
(defmethod enter-tty-mode ((obj display) tty &optional (driver ""))
(declare (integer tty))
(declare (string driver))
(setf (slot-value obj 'tty) (foreign-funcall "brlapi__enterTtyMode" :pointer (connection-handle obj) :int tty :string driver brlapi-code)))
(defmethod leave-tty-mode ((obj display))
(foreign-funcall "brlapi__leaveTtyMode" :pointer (connection-handle obj) brlapi-code)
(setf (slot-value obj 'tty) nil))
;;;; * Output
(defmethod write-text ((obj display) text &key (cursor -1))
"Write TEXT (a string) to the braille display."
(declare (string text))
(declare (integer cursor))
(if (eql (foreign-funcall "brlapi__writeText"
:pointer (connection-handle obj)
:int cursor :string text
brlapi-code)
0)
text))
(defbitfield (dots :uint8)
(:dot1 #x01) :dot2 :dot3 :dot4 :dot5 :dot6 :dot7 :dot8)
(defmethod write-dots ((obj display) &rest dots-list)
"Write the given dots list to the display."
(with-foreign-object (dots 'dots (display-size obj))
(loop for i below (min (display-size obj) (length dots-list))
do (setf (mem-aref dots 'dots i)
(foreign-bitfield-value 'dots (nth i dots-list))))
(loop for i from (length dots-list) below (display-size obj)
do (setf (mem-aref dots 'dots i) 0))
(foreign-funcall "brlapi__writeDots" :pointer (connection-handle obj) :pointer dots brlapi-code)))
(defcstruct write-struct
(display-number :int)
(region-begin :int)
(region-size :int)
(text :pointer)
(attr-and :pointer)
(attr-or :pointer)
(cursor :int)
(charset :string))
(defmethod write-region ((obj display) text &key (begin 1) size (cursor -1) (display-number -1)
(charset "") attr-and attr-or)
"Update a specific region of the braille display and apply and/or masks."
(let ((size (or size (min (display-size obj)
(max (length text)
(length attr-and)
(length attr-or))))))
(with-foreign-objects ((ws 'write-struct)
(txt :string (1+ size))
(attra 'dots size)
(attro 'dots size))
(loop for i below size
do (setf (mem-aref txt :uint8 i) (char-code #\SPACE)))
(setf (mem-aref txt :uint8 size) 0)
(loop for i below (min size (length text))
do (setf (mem-aref txt :uint8 i) (char-code (aref text i))))
(loop for i below size
do (setf (mem-aref attra :uint8 i) #XFF (mem-aref attro :uint8 i) 0))
(loop for i below (min size (length attr-and))
do (setf (mem-aref attra :uint8 i) (foreign-bitfield-value 'dots (nth i attr-and))))
(loop for i below (min size (length attr-or))
do (setf (mem-aref attro :uint8 i) (foreign-bitfield-value 'dots (nth i attr-or))))
(setf (foreign-slot-value ws 'write-struct 'display-number) display-number
(foreign-slot-value ws 'write-struct 'cursor) cursor
(foreign-slot-value ws 'write-struct 'region-begin) begin
(foreign-slot-value ws 'write-struct 'region-size) size
(foreign-slot-value ws 'write-struct 'charset) charset
(foreign-slot-value ws 'write-struct 'text) txt
(foreign-slot-value ws 'write-struct 'attr-or) attro
(foreign-slot-value ws 'write-struct 'attr-and) attra)
(eql (foreign-funcall "brlapi__write" :pointer (connection-handle obj) :pointer ws brlapi-code) 0))))
;;;; * Input
(defctype key-code :uint64)
(defmethod read-key ((obj display) &optional block)
(with-foreign-object (key 'key-code)
(case (foreign-funcall "brlapi__readKey" :pointer (connection-handle obj) :boolean block :pointer key brlapi-code)
(0 nil)
(1 (mem-ref key 'key-code)))))
(defmethod read-key-with-timeout ((obj display) &optional (timeout -1))
(with-foreign-object (key 'key-code)
(case (foreign-funcall "brlapi__readKeyWithTimeout" :pointer (connection-handle obj) :int (* timeout 1000) :pointer key brlapi-code)
(0 nil)
(1 (mem-ref key 'key-code)))))
(defcstruct expanded-key-code
"A key code broken down into its individual fields."
(type :int)
(command :int)
(argument :int)
(flags :int)
)
(defcstruct key-code-description
"The description of a key code."
(type-name :string)
(command-name :string)
(argument-value :int)
(flag-count :int)
(flag-names :string :count 32)
(field-values expanded-key-code)
)
(defun expand-key-code (code)
"Return the individual fields of a key code as a list of numeric values.
The list contains, in order, the type, command, argument, and flags."
(with-foreign-object (expansion 'expanded-key-code)
(foreign-funcall "brlapi_expandKeyCode" key-code code :pointer expansion :int)
(with-foreign-slots
((type command argument flags) expansion expanded-key-code)
(list type command argument flags)
)
)
)
(defun describe-key-code (code)
"Return the individual fields of the key code as a list.
The list contains:
0: type (a string)
1: command (a string)
2: argument (a non-negative integer)
3: flags (a list of strings)"
(with-foreign-object (description 'key-code-description)
(foreign-funcall "brlapi_describeKeyCode" key-code code :pointer description :int)
(with-foreign-slots
((type-name command-name argument-value flag-count flag-names) description key-code-description)
(list
type-name command-name argument-value
(loop
for index below flag-count
collect (mem-aref (foreign-slot-pointer description 'key-code-description 'flag-names) :string index))
)
)
)
)
;;;; * Example usage
(defun example (&optional (tty -1))
"A basic example."
(let ((display (open-connection)))
(enter-tty-mode display tty)
(write-text display "Press any key to continue...")
(apply #'format t "; Command: ~A, argument: ~D, flags: ~D"
(multiple-value-list (expand-key-code (read-key display t))))
(leave-tty-mode display)
(close-connection display)))