Monorepo for Aesthetic.Computer aesthetic.computer

ac-os/cl: scaffold Common Lisp edition of AC Native OS

Phase 1 scaffolding:
- ASDF system definition + packages
- Syscalls (CFFI bindings for open/close/ioctl/mmap/mount)
- Color (ARGB32 pack/blend)
- Framebuffer (foreign-memory pixel buffer)
- DRM display (libdrm bindings, connector/CRTC setup stubs)
- Graphics (wipe, ink, plot, line, box, circle — Bresenham + midpoint)
- Input (evdev polling, struct input_event)
- Audio (ALSA PCM, voice mixing thread, synth oscillators)
- Config (minimal JSON parser for USB config.json)
- Main loop (60fps frame sync, demo color cycle)
- Build script (sb-ext:save-lisp-and-die standalone binary)

Uses SBCL + CFFI + bordeaux-threads. Targets the same initramfs
slot as the C binary — init script doesn't change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+1197
+36
fedac/native/cl/ac-native.asd
··· 1 + ;;; AC Native OS — Common Lisp Edition 2 + ;;; ASDF system definition 3 + 4 + (defsystem "ac-native" 5 + :description "Aesthetic Computer Native OS runtime" 6 + :version "0.1.0" 7 + :author "Aesthetic Computer" 8 + :license "MIT" 9 + :depends-on ("cffi" "bordeaux-threads" "alexandria") 10 + :serial t 11 + :components 12 + ((:file "packages") 13 + ;; Core infrastructure 14 + (:file "util") 15 + (:file "syscalls") 16 + ;; Display 17 + (:file "color") 18 + (:file "framebuffer") 19 + (:file "drm-constants") 20 + (:file "drm-display") 21 + ;; Graphics 22 + (:file "graph") 23 + (:file "font-data") 24 + (:file "font") 25 + ;; Input 26 + (:file "input-keycodes") 27 + (:file "input") 28 + ;; Audio 29 + (:file "alsa-bindings") 30 + (:file "audio-synth") 31 + (:file "audio") 32 + ;; Main 33 + (:file "config") 34 + (:file "main") 35 + ;; Build 36 + (:file "build")))
+40
fedac/native/cl/alsa-bindings.lisp
··· 1 + ;;; ALSA PCM bindings via CFFI 2 + 3 + (in-package :ac-native.alsa) 4 + 5 + (cffi:define-foreign-library libasound 6 + (:unix "libasound.so.2")) 7 + 8 + (cffi:use-foreign-library libasound) 9 + 10 + ;;; PCM 11 + (cffi:defcfun ("snd_pcm_open" pcm-open%) :int 12 + (pcm :pointer) (name :string) (stream :int) (mode :int)) 13 + 14 + (defun pcm-open (name &key (stream 0) (mode 0)) 15 + "Open an ALSA PCM device. Returns (values pcm-handle error-code)." 16 + (cffi:with-foreign-object (handle :pointer) 17 + (let ((err (pcm-open% handle name stream mode))) 18 + (values (if (zerop err) (cffi:mem-ref handle :pointer) nil) err)))) 19 + 20 + (cffi:defcfun ("snd_pcm_close" pcm-close) :int (pcm :pointer)) 21 + (cffi:defcfun ("snd_pcm_prepare" pcm-prepare) :int (pcm :pointer)) 22 + (cffi:defcfun ("snd_pcm_writei" pcm-writei) :long 23 + (pcm :pointer) (buffer :pointer) (size :unsigned-long)) 24 + (cffi:defcfun ("snd_pcm_recover" pcm-recover) :int 25 + (pcm :pointer) (err :int) (silent :int)) 26 + 27 + ;;; Simplified setup 28 + (cffi:defcfun ("snd_pcm_set_params" pcm-set-params) :int 29 + (pcm :pointer) (format :int) (access :int) (channels :unsigned-int) 30 + (rate :unsigned-int) (soft-resample :int) (latency :unsigned-int)) 31 + 32 + ;;; Constants 33 + (defconstant +snd-pcm-stream-playback+ 0) 34 + (defconstant +snd-pcm-stream-capture+ 1) 35 + (defconstant +snd-pcm-format-s16-le+ 2) 36 + (defconstant +snd-pcm-format-float-le+ 14) 37 + (defconstant +snd-pcm-access-rw-interleaved+ 3) 38 + 39 + ;;; Error strings 40 + (cffi:defcfun ("snd_strerror" snd-strerror) :string (errnum :int))
+53
fedac/native/cl/audio-synth.lisp
··· 1 + ;;; Audio synthesis — oscillators, envelopes 2 + 3 + (in-package :ac-native.audio) 4 + 5 + (defconstant +twopi+ (* 2.0d0 pi)) 6 + 7 + (declaim (inline osc-sine osc-square osc-triangle osc-sawtooth)) 8 + 9 + (defun osc-sine (phase) 10 + (declare (optimize (speed 3) (safety 0)) (type double-float phase)) 11 + (sin phase)) 12 + 13 + (defun osc-square (phase) 14 + (declare (optimize (speed 3) (safety 0)) (type double-float phase)) 15 + (if (< (mod phase +twopi+) pi) 1.0d0 -1.0d0)) 16 + 17 + (defun osc-triangle (phase) 18 + (declare (optimize (speed 3) (safety 0)) (type double-float phase)) 19 + (let ((p (mod (/ phase +twopi+) 1.0d0))) 20 + (if (< p 0.5d0) 21 + (- (* 4.0d0 p) 1.0d0) 22 + (- 3.0d0 (* 4.0d0 p))))) 23 + 24 + (defun osc-sawtooth (phase) 25 + (declare (optimize (speed 3) (safety 0)) (type double-float phase)) 26 + (- (* 2.0d0 (mod (/ phase +twopi+) 1.0d0)) 1.0d0)) 27 + 28 + (defstruct voice 29 + "A synthesizer voice." 30 + (active nil :type boolean) 31 + (osc-type 0 :type fixnum) ; 0=sine 1=square 2=tri 3=saw 4=noise 32 + (phase 0.0d0 :type double-float) 33 + (phase-inc 0.0d0 :type double-float) 34 + (volume 0.7d0 :type double-float) 35 + (pan 0.0d0 :type double-float) 36 + (attack 0.01d0 :type double-float) 37 + (decay 0.1d0 :type double-float) 38 + (duration 0.0d0 :type double-float) ; 0 = infinite 39 + (elapsed 0.0d0 :type double-float) 40 + (fade 1.0d0 :type double-float) 41 + (id 0 :type fixnum)) 42 + 43 + (defun generate-sample (v) 44 + "Generate one sample from voice V." 45 + (declare (optimize (speed 3) (safety 0)) 46 + (type voice v)) 47 + (case (voice-osc-type v) 48 + (0 (osc-sine (voice-phase v))) 49 + (1 (osc-square (voice-phase v))) 50 + (2 (osc-triangle (voice-phase v))) 51 + (3 (osc-sawtooth (voice-phase v))) 52 + (4 (- (random 2.0d0) 1.0d0)) ; white noise 53 + (t 0.0d0)))
+137
fedac/native/cl/audio.lisp
··· 1 + ;;; Audio subsystem — ALSA output + voice mixing 2 + 3 + (in-package :ac-native.audio) 4 + 5 + (defconstant +max-voices+ 32) 6 + (defconstant +sample-rate+ 48000) 7 + 8 + (defstruct ac-audio 9 + "Audio subsystem state." 10 + (pcm nil) ; ALSA PCM handle 11 + (voices (make-array +max-voices+ :initial-element nil)) 12 + (thread nil) 13 + (running nil :type boolean) 14 + (next-id 1 :type fixnum) 15 + (lock (bordeaux-threads:make-lock "audio"))) 16 + 17 + (defun audio-init () 18 + "Initialize ALSA audio output and start the mixing thread." 19 + (multiple-value-bind (pcm err) (ac-native.alsa:pcm-open "default") 20 + (unless pcm 21 + (ac-native.util:ac-log "[audio] failed to open: ~A~%" 22 + (ac-native.alsa:snd-strerror err)) 23 + ;; Try hw:0,0 24 + (multiple-value-setq (pcm err) (ac-native.alsa:pcm-open "hw:0,0")) 25 + (unless pcm 26 + (ac-native.util:ac-log "[audio] hw:0,0 also failed~%") 27 + (return-from audio-init nil))) 28 + 29 + ;; Set params: S16_LE, stereo, 48kHz, 50ms latency 30 + (let ((ret (ac-native.alsa:pcm-set-params 31 + pcm 32 + ac-native.alsa:+snd-pcm-format-s16-le+ 33 + ac-native.alsa:+snd-pcm-access-rw-interleaved+ 34 + 2 +sample-rate+ 1 50000))) 35 + (unless (zerop ret) 36 + (ac-native.util:ac-log "[audio] set-params failed: ~A~%" 37 + (ac-native.alsa:snd-strerror ret)) 38 + (ac-native.alsa:pcm-close pcm) 39 + (return-from audio-init nil))) 40 + 41 + (let ((audio (make-ac-audio :pcm pcm :running t))) 42 + ;; Init voice slots 43 + (dotimes (i +max-voices+) 44 + (setf (aref (ac-audio-voices audio) i) (make-voice))) 45 + 46 + ;; Start audio thread 47 + (setf (ac-audio-thread audio) 48 + (bordeaux-threads:make-thread 49 + (lambda () (audio-thread-fn audio)) 50 + :name "audio")) 51 + 52 + (ac-native.util:ac-log "[audio] OK (48kHz stereo)~%") 53 + audio))) 54 + 55 + (defun audio-thread-fn (audio) 56 + "Audio mixing thread — fills PCM buffer." 57 + (let* ((period-frames 256) 58 + (buf-samples (* period-frames 2)) ; stereo 59 + (buf (cffi:foreign-alloc :int16 :count buf-samples))) 60 + (unwind-protect 61 + (loop while (ac-audio-running audio) do 62 + ;; Mix all active voices 63 + (dotimes (f period-frames) 64 + (let ((mix-l 0.0d0) (mix-r 0.0d0)) 65 + (bordeaux-threads:with-lock-held ((ac-audio-lock audio)) 66 + (dotimes (i +max-voices+) 67 + (let ((v (aref (ac-audio-voices audio) i))) 68 + (when (voice-active v) 69 + (let* ((s (generate-sample v)) 70 + (vol (* (voice-volume v) (voice-fade v))) 71 + (l-gain (if (<= (voice-pan v) 0) 1.0d0 72 + (- 1.0d0 (* (voice-pan v) 0.6d0)))) 73 + (r-gain (if (>= (voice-pan v) 0) 1.0d0 74 + (+ 1.0d0 (* (voice-pan v) 0.6d0))))) 75 + (incf mix-l (* s vol l-gain)) 76 + (incf mix-r (* s vol r-gain)) 77 + ;; Advance phase 78 + (incf (voice-phase v) (voice-phase-inc v)) 79 + (incf (voice-elapsed v) (/ 1.0d0 +sample-rate+)) 80 + ;; Check duration 81 + (when (and (> (voice-duration v) 0) 82 + (> (voice-elapsed v) (voice-duration v))) 83 + (setf (voice-active v) nil))))))) 84 + ;; Clamp and write S16LE stereo 85 + (let ((sl (max -32767 (min 32767 (round (* mix-l 32767))))) 86 + (sr (max -32767 (min 32767 (round (* mix-r 32767)))))) 87 + (setf (cffi:mem-aref buf :int16 (* f 2)) sl) 88 + (setf (cffi:mem-aref buf :int16 (1+ (* f 2))) sr)))) 89 + ;; Write to ALSA 90 + (let ((ret (ac-native.alsa:pcm-writei (ac-audio-pcm audio) buf period-frames))) 91 + (when (< ret 0) 92 + (ac-native.alsa:pcm-recover (ac-audio-pcm audio) (truncate ret) 0)))) 93 + ;; Cleanup 94 + (cffi:foreign-free buf)))) 95 + 96 + (defun audio-synth (audio &key (type 0) (tone 440.0) (volume 0.7) (duration 0) 97 + (pan 0.0) (attack 0.01) (decay 0.1)) 98 + "Start a synth voice. Returns voice ID." 99 + (bordeaux-threads:with-lock-held ((ac-audio-lock audio)) 100 + ;; Find free slot 101 + (let ((slot (dotimes (i +max-voices+ 0) 102 + (unless (voice-active (aref (ac-audio-voices audio) i)) 103 + (return i)))) 104 + (id (incf (ac-audio-next-id audio)))) 105 + (let ((v (aref (ac-audio-voices audio) slot))) 106 + (setf (voice-active v) t 107 + (voice-osc-type v) type 108 + (voice-phase v) 0.0d0 109 + (voice-phase-inc v) (/ (* +twopi+ (coerce tone 'double-float)) +sample-rate+) 110 + (voice-volume v) (coerce volume 'double-float) 111 + (voice-pan v) (coerce pan 'double-float) 112 + (voice-attack v) (coerce attack 'double-float) 113 + (voice-decay v) (coerce decay 'double-float) 114 + (voice-duration v) (coerce duration 'double-float) 115 + (voice-elapsed v) 0.0d0 116 + (voice-fade v) 1.0d0 117 + (voice-id v) id)) 118 + id))) 119 + 120 + (defun audio-synth-kill (audio id) 121 + "Kill a synth voice by ID." 122 + (bordeaux-threads:with-lock-held ((ac-audio-lock audio)) 123 + (dotimes (i +max-voices+) 124 + (let ((v (aref (ac-audio-voices audio) i))) 125 + (when (and (voice-active v) (= (voice-id v) id)) 126 + (setf (voice-active v) nil) 127 + (return)))))) 128 + 129 + (defun audio-destroy (audio) 130 + "Shutdown audio." 131 + (when audio 132 + (setf (ac-audio-running audio) nil) 133 + (when (ac-audio-thread audio) 134 + (bordeaux-threads:join-thread (ac-audio-thread audio))) 135 + (when (ac-audio-pcm audio) 136 + (ac-native.alsa:pcm-close (ac-audio-pcm audio))) 137 + (ac-native.util:ac-log "[audio] destroyed~%")))
+13
fedac/native/cl/build.lisp
··· 1 + ;;; Build script — produce standalone ac-native binary 2 + 3 + (in-package :ac-native.build) 4 + 5 + (defun build (&optional (output "ac-native")) 6 + "Save a standalone executable." 7 + (format t "Building AC Native OS (Common Lisp)...~%") 8 + (format t " Output: ~A~%" output) 9 + (sb-ext:save-lisp-and-die output 10 + :toplevel #'ac-native:main 11 + :executable t 12 + :compression t 13 + :purify t))
+40
fedac/native/cl/color.lisp
··· 1 + ;;; Color — ARGB32 pixel operations 2 + 3 + (in-package :ac-native.color) 4 + 5 + (defstruct (color (:constructor make-color (&key (r 0) (g 0) (b 0) (a 255)))) 6 + (r 0 :type (unsigned-byte 8)) 7 + (g 0 :type (unsigned-byte 8)) 8 + (b 0 :type (unsigned-byte 8)) 9 + (a 255 :type (unsigned-byte 8))) 10 + 11 + (declaim (inline color-pack-argb32)) 12 + (defun color-pack-argb32 (c) 13 + "Pack a color struct into a 32-bit ARGB value." 14 + (declare (optimize (speed 3) (safety 0))) 15 + (logior (ash (color-a c) 24) 16 + (ash (color-r c) 16) 17 + (ash (color-g c) 8) 18 + (color-b c))) 19 + 20 + (declaim (inline blend-channel)) 21 + (defun blend-channel (src dst alpha) 22 + "Blend a single channel: src over dst with alpha [0..255]." 23 + (declare (optimize (speed 3) (safety 0)) 24 + (type (unsigned-byte 8) src dst alpha)) 25 + (the (unsigned-byte 8) 26 + (ash (+ (* src alpha) (* dst (- 255 alpha)) 128) -8))) 27 + 28 + (defun color-blend (src dst) 29 + "Alpha-blend src over dst, return packed ARGB32." 30 + (declare (optimize (speed 3) (safety 0))) 31 + (let ((sa (color-a src))) 32 + (if (= sa 255) 33 + (color-pack-argb32 src) 34 + (if (zerop sa) 35 + (color-pack-argb32 dst) 36 + (logior 37 + #xFF000000 38 + (ash (blend-channel (color-r src) (color-r dst) sa) 16) 39 + (ash (blend-channel (color-g src) (color-g dst) sa) 8) 40 + (blend-channel (color-b src) (color-b dst) sa))))))
+33
fedac/native/cl/config.lisp
··· 1 + ;;; Config — parse config.json from USB 2 + 3 + (in-package :ac-native.config) 4 + 5 + (defstruct config 6 + (handle "unknown" :type string) 7 + (piece nil :type (or null string)) 8 + (email nil :type (or null string))) 9 + 10 + (defun load-config (&optional (path "/mnt/config.json")) 11 + "Load config from USB. Returns a config struct." 12 + (if (probe-file path) 13 + (let ((text (with-open-file (s path :direction :input) 14 + (let ((buf (make-string (file-length s)))) 15 + (read-sequence buf s) 16 + buf)))) 17 + ;; Minimal JSON parsing — just extract "handle" and "piece" 18 + (make-config 19 + :handle (or (json-extract text "handle") "unknown") 20 + :piece (json-extract text "piece") 21 + :email (json-extract text "email"))) 22 + (make-config))) 23 + 24 + (defun json-extract (text key) 25 + "Extract a string value for KEY from a JSON text. Very minimal." 26 + (let* ((needle (format nil "\"~A\"" key)) 27 + (pos (search needle text))) 28 + (when pos 29 + (let* ((colon (position #\: text :start (+ pos (length needle)))) 30 + (quote1 (and colon (position #\" text :start (1+ colon)))) 31 + (quote2 (and quote1 (position #\" text :start (1+ quote1))))) 32 + (when (and quote1 quote2) 33 + (subseq text (1+ quote1) quote2))))))
+91
fedac/native/cl/drm-constants.lisp
··· 1 + ;;; DRM/KMS constants and struct definitions 2 + 3 + (in-package :ac-native.drm) 4 + 5 + ;;; DRM ioctl numbers (from <drm.h> and <drm_mode.h>) 6 + (defconstant +drm-ioctl-base+ #x64) ; 'd' 7 + 8 + ;;; libdrm function bindings (higher-level than raw ioctls) 9 + (cffi:define-foreign-library libdrm 10 + (:unix "libdrm.so.2")) 11 + 12 + (cffi:use-foreign-library libdrm) 13 + 14 + ;; drmModeRes *drmModeGetResources(int fd) 15 + (cffi:defcfun ("drmModeGetResources" drm-mode-get-resources) :pointer 16 + (fd :int)) 17 + 18 + ;; void drmModeFreeResources(drmModeRes *ptr) 19 + (cffi:defcfun ("drmModeFreeResources" drm-mode-free-resources) :void 20 + (ptr :pointer)) 21 + 22 + ;; drmModeConnector *drmModeGetConnector(int fd, uint32_t id) 23 + (cffi:defcfun ("drmModeGetConnector" drm-mode-get-connector) :pointer 24 + (fd :int) (id :uint32)) 25 + 26 + ;; void drmModeFreeConnector(drmModeConnector *ptr) 27 + (cffi:defcfun ("drmModeFreeConnector" drm-mode-free-connector) :void 28 + (ptr :pointer)) 29 + 30 + ;; drmModeEncoder *drmModeGetEncoder(int fd, uint32_t id) 31 + (cffi:defcfun ("drmModeGetEncoder" drm-mode-get-encoder) :pointer 32 + (fd :int) (id :uint32)) 33 + 34 + ;; void drmModeFreeEncoder(drmModeEncoder *ptr) 35 + (cffi:defcfun ("drmModeFreeEncoder" drm-mode-free-encoder) :void 36 + (ptr :pointer)) 37 + 38 + ;; int drmModeSetCrtc(int fd, uint32_t crtc_id, uint32_t fb_id, 39 + ;; uint32_t x, uint32_t y, uint32_t *connectors, 40 + ;; int count, drmModeModeInfo *mode) 41 + (cffi:defcfun ("drmModeSetCrtc" drm-mode-set-crtc) :int 42 + (fd :int) (crtc-id :uint32) (fb-id :uint32) 43 + (x :uint32) (y :uint32) (connectors :pointer) 44 + (count :int) (mode :pointer)) 45 + 46 + ;; int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth, 47 + ;; uint8_t bpp, uint32_t pitch, uint32_t bo_handle, uint32_t *buf_id) 48 + (cffi:defcfun ("drmModeAddFB" drm-mode-add-fb) :int 49 + (fd :int) (width :uint32) (height :uint32) (depth :uint8) 50 + (bpp :uint8) (pitch :uint32) (bo-handle :uint32) (buf-id :pointer)) 51 + 52 + ;; int drmModeRmFB(int fd, uint32_t fb_id) 53 + (cffi:defcfun ("drmModeRmFB" drm-mode-rm-fb) :int 54 + (fd :int) (fb-id :uint32)) 55 + 56 + ;; int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id, 57 + ;; uint32_t flags, void *user_data) 58 + (cffi:defcfun ("drmModePageFlip" drm-mode-page-flip) :int 59 + (fd :int) (crtc-id :uint32) (fb-id :uint32) 60 + (flags :uint32) (user-data :pointer)) 61 + 62 + (defconstant +drm-mode-page-flip-event+ 1) 63 + 64 + ;;; DRM dumb buffer ioctls 65 + (cffi:defcstruct drm-mode-create-dumb 66 + (height :uint32) 67 + (width :uint32) 68 + (bpp :uint32) 69 + (flags :uint32) 70 + ;; output 71 + (handle :uint32) 72 + (pitch :uint32) 73 + (size :uint64)) 74 + 75 + (cffi:defcstruct drm-mode-map-dumb 76 + (handle :uint32) 77 + (pad :uint32) 78 + (offset :uint64)) 79 + 80 + (cffi:defcstruct drm-mode-destroy-dumb 81 + (handle :uint32)) 82 + 83 + ;;; DRM_IOCTL_MODE_CREATE_DUMB = DRM_IOWR(0xB2, struct drm_mode_create_dumb) 84 + ;;; These are computed from the ioctl macros 85 + (defconstant +drm-ioctl-mode-create-dumb+ #xC02064B2) 86 + (defconstant +drm-ioctl-mode-map-dumb+ #xC01064B3) 87 + (defconstant +drm-ioctl-mode-destroy-dumb+ #xC00464B4) 88 + 89 + ;;; Connector status 90 + (defconstant +drm-mode-connected+ 1) 91 + (defconstant +drm-mode-disconnected+ 2)
+105
fedac/native/cl/drm-display.lisp
··· 1 + ;;; DRM display — Linux DRM/KMS for direct framebuffer access 2 + 3 + (in-package :ac-native.drm) 4 + 5 + (defstruct display 6 + "DRM display state." 7 + (fd -1 :type fixnum) 8 + (width 0 :type fixnum) 9 + (height 0 :type fixnum) 10 + (crtc-id 0 :type (unsigned-byte 32)) 11 + (connector-id 0 :type (unsigned-byte 32)) 12 + ;; Double-buffered dumb buffers 13 + (fb-ids (make-array 2 :element-type '(unsigned-byte 32) :initial-element 0)) 14 + (bo-handles (make-array 2 :element-type '(unsigned-byte 32) :initial-element 0)) 15 + (maps (make-array 2 :initial-element (cffi:null-pointer))) 16 + (buf-sizes (make-array 2 :element-type 'fixnum :initial-element 0)) 17 + (pitches (make-array 2 :element-type 'fixnum :initial-element 0)) 18 + (back 0 :type fixnum) ; which buffer is back (0 or 1) 19 + (mode-ptr (cffi:null-pointer) :type cffi:foreign-pointer)) 20 + 21 + (defun try-open-drm () 22 + "Try /dev/dri/card0, card1. Return fd or -1." 23 + (loop for card in '("/dev/dri/card0" "/dev/dri/card1" "/dev/dri/card2") 24 + for fd = (ac-native.syscalls:sys-open card 25 + (logior ac-native.syscalls:+o-rdwr+ 26 + ac-native.syscalls:+o-cloexec+) 27 + 0) 28 + when (>= fd 0) do 29 + (ac-native.util:ac-log "[drm] opened ~A (fd ~D)~%" card fd) 30 + (return fd) 31 + finally (return -1))) 32 + 33 + (defun create-dumb-buffer (fd width height) 34 + "Create a DRM dumb buffer. Returns (values handle pitch size) or nil." 35 + (cffi:with-foreign-object (req '(:struct drm-mode-create-dumb)) 36 + (cffi:foreign-funcall "memset" :pointer req :int 0 37 + :unsigned-long (cffi:foreign-type-size '(:struct drm-mode-create-dumb)) 38 + :pointer) 39 + (setf (cffi:foreign-slot-value req '(:struct drm-mode-create-dumb) 'height) height) 40 + (setf (cffi:foreign-slot-value req '(:struct drm-mode-create-dumb) 'width) width) 41 + (setf (cffi:foreign-slot-value req '(:struct drm-mode-create-dumb) 'bpp) 32) 42 + (let ((ret (ac-native.syscalls:sys-ioctl fd +drm-ioctl-mode-create-dumb+ :pointer req))) 43 + (if (zerop ret) 44 + (values 45 + (cffi:foreign-slot-value req '(:struct drm-mode-create-dumb) 'handle) 46 + (cffi:foreign-slot-value req '(:struct drm-mode-create-dumb) 'pitch) 47 + (cffi:foreign-slot-value req '(:struct drm-mode-create-dumb) 'size)) 48 + nil)))) 49 + 50 + (defun map-dumb-buffer (fd handle size) 51 + "Mmap a dumb buffer. Returns foreign pointer or null." 52 + (cffi:with-foreign-object (req '(:struct drm-mode-map-dumb)) 53 + (cffi:foreign-funcall "memset" :pointer req :int 0 54 + :unsigned-long (cffi:foreign-type-size '(:struct drm-mode-map-dumb)) 55 + :pointer) 56 + (setf (cffi:foreign-slot-value req '(:struct drm-mode-map-dumb) 'handle) handle) 57 + (let ((ret (ac-native.syscalls:sys-ioctl fd +drm-ioctl-mode-map-dumb+ :pointer req))) 58 + (if (zerop ret) 59 + (let ((offset (cffi:foreign-slot-value req '(:struct drm-mode-map-dumb) 'offset))) 60 + (ac-native.syscalls:sys-mmap 61 + (cffi:null-pointer) size 62 + (logior ac-native.syscalls:+prot-read+ ac-native.syscalls:+prot-write+) 63 + ac-native.syscalls:+map-shared+ 64 + fd offset)) 65 + (cffi:null-pointer))))) 66 + 67 + (defun drm-init () 68 + "Initialize DRM display. Returns a display struct or nil." 69 + (let ((fd (try-open-drm))) 70 + (when (< fd 0) 71 + (ac-native.util:ac-log "[drm] no DRM device found~%") 72 + (return-from drm-init nil)) 73 + 74 + ;; Get resources 75 + (let ((res (drm-mode-get-resources fd))) 76 + (when (cffi:null-pointer-p res) 77 + (ac-native.util:ac-log "[drm] drmModeGetResources failed~%") 78 + (ac-native.syscalls:sys-close fd) 79 + (return-from drm-init nil)) 80 + 81 + ;; Find first connected connector 82 + ;; TODO: iterate connectors, find encoder, find CRTC 83 + ;; For now, return a placeholder 84 + (ac-native.util:ac-log "[drm] resources obtained, setting up display...~%") 85 + 86 + ;; Placeholder — real implementation needs connector/encoder/CRTC setup 87 + ;; This is the most hardware-specific part 88 + (drm-mode-free-resources res) 89 + 90 + (let ((disp (make-display :fd fd :width 1366 :height 768))) 91 + ;; TODO: actually detect resolution from connector modes 92 + disp)))) 93 + 94 + (defun drm-present (display screen scale) 95 + "Copy the render framebuffer to the DRM back buffer and flip." 96 + (declare (ignore display screen scale)) 97 + ;; TODO: implement fb-copy-scaled to mmap'd buffer + page flip 98 + ) 99 + 100 + (defun drm-destroy (display) 101 + "Clean up DRM resources." 102 + (when display 103 + (when (>= (display-fd display) 0) 104 + (ac-native.syscalls:sys-close (display-fd display)) 105 + (ac-native.util:ac-log "[drm] closed~%"))))
+8
fedac/native/cl/font-data.lisp
··· 1 + ;;; Font bitmap data — placeholder, will be generated from C headers 2 + 3 + (in-package :ac-native.font) 4 + 5 + ;; 8x8 font — 95 printable ASCII characters (space through ~) 6 + ;; Each character is 8 bytes (8 rows of 8 pixels) 7 + ;; TODO: port from fedac/native/src/font-8x8.h 8 + (defvar *font-8x8* (make-array (* 95 8) :element-type '(unsigned-byte 8) :initial-element 0))
+30
fedac/native/cl/font.lisp
··· 1 + ;;; Font rendering — bitmap font draw/measure 2 + 3 + (in-package :ac-native.font) 4 + 5 + (defun font-init () 6 + "Load font data. Called once at startup." 7 + ;; TODO: populate *font-8x8* from header data 8 + ) 9 + 10 + (defun font-draw (graph text x y &key (size 1)) 11 + "Draw TEXT at (X, Y) using the 8x8 bitmap font." 12 + (declare (ignore graph text x y size)) 13 + ;; TODO: implement 14 + ) 15 + 16 + (defun font-measure (text &key (size 1)) 17 + "Return the pixel width of TEXT in the 8x8 font." 18 + (declare (ignore size)) 19 + (* (length text) 8)) 20 + 21 + (defun font-draw-matrix (graph text x y size) 22 + "Draw TEXT using the MatrixChunky8 font." 23 + (declare (ignore graph text x y size)) 24 + ;; TODO: implement 25 + ) 26 + 27 + (defun font-measure-matrix (text size) 28 + "Return pixel width of TEXT in MatrixChunky8." 29 + (declare (ignore size)) 30 + (* (length text) 10))
+95
fedac/native/cl/framebuffer.lisp
··· 1 + ;;; Framebuffer — foreign-memory pixel buffer for DRM/fbdev 2 + 3 + (in-package :ac-native.framebuffer) 4 + 5 + (defstruct framebuffer 6 + "A 2D pixel buffer backed by CFFI foreign memory (for DRM mmap compat)." 7 + (pixels (cffi:null-pointer) :type cffi:foreign-pointer) 8 + (width 0 :type fixnum) 9 + (height 0 :type fixnum) 10 + (stride 0 :type fixnum) ; in pixels (= width for our buffers) 11 + (owned t :type boolean)) ; if t, we allocated pixels and must free 12 + 13 + (defun fb-create (width height) 14 + "Allocate a new framebuffer with WIDTH x HEIGHT pixels (ARGB32)." 15 + (let* ((stride width) 16 + (nbytes (* stride height 4)) 17 + (ptr (cffi:foreign-alloc :uint32 :count (* stride height) :initial-element 0))) 18 + (make-framebuffer :pixels ptr :width width :height height 19 + :stride stride :owned t))) 20 + 21 + (defun fb-destroy (fb) 22 + "Free the framebuffer's pixel memory (if we own it)." 23 + (when (and fb (framebuffer-owned fb) 24 + (not (cffi:null-pointer-p (framebuffer-pixels fb)))) 25 + (cffi:foreign-free (framebuffer-pixels fb)) 26 + (setf (framebuffer-pixels fb) (cffi:null-pointer)))) 27 + 28 + (declaim (inline fb-pixel-offset)) 29 + (defun fb-pixel-offset (fb x y) 30 + (declare (optimize (speed 3) (safety 0)) 31 + (type fixnum x y) 32 + (type framebuffer fb)) 33 + (the fixnum (+ x (* y (framebuffer-stride fb))))) 34 + 35 + (defun fb-clear (fb color-u32) 36 + "Fill the entire framebuffer with a packed ARGB32 color." 37 + (declare (optimize (speed 3) (safety 0)) 38 + (type (unsigned-byte 32) color-u32)) 39 + (let* ((n (* (framebuffer-stride fb) (framebuffer-height fb))) 40 + (ptr (framebuffer-pixels fb))) 41 + (dotimes (i n) 42 + (setf (cffi:mem-aref ptr :uint32 i) color-u32)))) 43 + 44 + (declaim (inline fb-put-pixel)) 45 + (defun fb-put-pixel (fb x y color-u32) 46 + "Write a pixel (no blending, no bounds check in optimized mode)." 47 + (declare (optimize (speed 3) (safety 0)) 48 + (type fixnum x y) 49 + (type (unsigned-byte 32) color-u32) 50 + (type framebuffer fb)) 51 + (when (and (>= x 0) (< x (framebuffer-width fb)) 52 + (>= y 0) (< y (framebuffer-height fb))) 53 + (setf (cffi:mem-aref (framebuffer-pixels fb) :uint32 54 + (fb-pixel-offset fb x y)) 55 + color-u32))) 56 + 57 + (defun fb-blend-pixel (fb x y src-color) 58 + "Alpha-blend a color onto the framebuffer at (x, y)." 59 + (declare (optimize (speed 3) (safety 0)) 60 + (type fixnum x y)) 61 + (when (and (>= x 0) (< x (framebuffer-width fb)) 62 + (>= y 0) (< y (framebuffer-height fb))) 63 + (let* ((off (fb-pixel-offset fb x y)) 64 + (dst-u32 (cffi:mem-aref (framebuffer-pixels fb) :uint32 off)) 65 + (dst (make-color :r (ldb (byte 8 16) dst-u32) 66 + :g (ldb (byte 8 8) dst-u32) 67 + :b (ldb (byte 8 0) dst-u32) 68 + :a 255))) 69 + (setf (cffi:mem-aref (framebuffer-pixels fb) :uint32 off) 70 + (color-blend src-color dst))))) 71 + 72 + (defun fb-copy-scaled (src dst-ptr dst-w dst-h dst-stride scale) 73 + "Copy SRC framebuffer to a foreign memory region at DST-PTR, scaling up by SCALE. 74 + Used by DRM present to blit the small render buffer to the display." 75 + (declare (optimize (speed 3) (safety 0)) 76 + (type fixnum dst-w dst-h dst-stride scale) 77 + (type framebuffer src)) 78 + (let ((sw (framebuffer-width src)) 79 + (sh (framebuffer-height src)) 80 + (sp (framebuffer-pixels src))) 81 + (dotimes (sy sh) 82 + (dotimes (sx sw) 83 + (let ((pixel (cffi:mem-aref sp :uint32 (+ sx (* sy sw))))) 84 + ;; Replicate pixel into scale x scale block 85 + (dotimes (dy scale) 86 + (let ((oy (* (+ (* (+ (* sy scale) dy) 1) 0) 1))) ; row offset placeholder 87 + (declare (ignore oy)) 88 + (let ((row (+ (* sy scale) dy))) 89 + (when (< row dst-h) 90 + (dotimes (dx scale) 91 + (let ((col (+ (* sx scale) dx))) 92 + (when (< col dst-w) 93 + (setf (cffi:mem-aref dst-ptr :uint32 94 + (+ col (* row dst-stride))) 95 + pixel)))))))))))))
+106
fedac/native/cl/graph.lisp
··· 1 + ;;; Graphics primitives — wipe, ink, plot, line, box, circle 2 + 3 + (in-package :ac-native.graph) 4 + 5 + (defstruct graph 6 + "Immediate-mode 2D graphics context." 7 + (fb nil :type (or null ac-native.framebuffer:framebuffer)) 8 + (screen nil :type (or null ac-native.framebuffer:framebuffer)) 9 + (ink-color (make-color :r 255 :g 255 :b 255 :a 255) :type color)) 10 + 11 + (defun graph-wipe (g color) 12 + "Clear the screen with COLOR." 13 + (fb-clear (graph-fb g) (color-pack-argb32 color))) 14 + 15 + (defun graph-ink (g color) 16 + "Set the current drawing color." 17 + (setf (graph-ink-color g) color)) 18 + 19 + (defun graph-plot (g x y) 20 + "Plot a single pixel at (X, Y) using the current ink." 21 + (let ((c (graph-ink-color g))) 22 + (if (= (color-a c) 255) 23 + (fb-put-pixel (graph-fb g) x y (color-pack-argb32 c)) 24 + (fb-blend-pixel (graph-fb g) x y c)))) 25 + 26 + (defun graph-line (g x0 y0 x1 y1) 27 + "Draw a line from (X0,Y0) to (X1,Y1) using Bresenham's algorithm." 28 + (declare (optimize (speed 3) (safety 1)) 29 + (type fixnum x0 y0 x1 y1)) 30 + (let ((dx (abs (- x1 x0))) 31 + (dy (- (abs (- y1 y0)))) 32 + (sx (if (< x0 x1) 1 -1)) 33 + (sy (if (< y0 y1) 1 -1))) 34 + (let ((err (+ dx dy)) 35 + (x x0) (y y0)) 36 + (declare (type fixnum err x y)) 37 + (loop 38 + (graph-plot g x y) 39 + (when (and (= x x1) (= y y1)) (return)) 40 + (let ((e2 (* 2 err))) 41 + (when (>= e2 dy) 42 + (incf err dy) 43 + (incf x sx)) 44 + (when (<= e2 dx) 45 + (incf err dx) 46 + (incf y sy))))))) 47 + 48 + (defun graph-box (g x y w h &optional (filled t)) 49 + "Draw a rectangle. If FILLED, fill it; otherwise draw outline." 50 + (declare (optimize (speed 3) (safety 1)) 51 + (type fixnum x y w h)) 52 + (let ((c (graph-ink-color g)) 53 + (fb (graph-fb g))) 54 + (if filled 55 + ;; Filled box — hot path, optimize 56 + (let ((c32 (color-pack-argb32 c)) 57 + (opaque (= (color-a c) 255))) 58 + (dotimes (row h) 59 + (let ((py (+ y row))) 60 + (when (and (>= py 0) (< py (fb-height fb))) 61 + (dotimes (col w) 62 + (let ((px (+ x col))) 63 + (when (and (>= px 0) (< px (fb-width fb))) 64 + (if opaque 65 + (fb-put-pixel fb px py c32) 66 + (fb-blend-pixel fb px py c))))))))) 67 + ;; Outline 68 + (progn 69 + (graph-line g x y (+ x w -1) y) 70 + (graph-line g x (+ y h -1) (+ x w -1) (+ y h -1)) 71 + (graph-line g x y x (+ y h -1)) 72 + (graph-line g (+ x w -1) y (+ x w -1) (+ y h -1)))))) 73 + 74 + (defun graph-circle (g cx cy r &optional (filled t)) 75 + "Draw a circle using the midpoint algorithm." 76 + (declare (optimize (speed 3) (safety 1)) 77 + (type fixnum cx cy r)) 78 + (if filled 79 + ;; Filled circle — draw horizontal spans 80 + (let ((x 0) (y r) (d (- 1 r))) 81 + (declare (type fixnum x y d)) 82 + (loop while (<= x y) do 83 + (graph-line g (- cx x) (+ cy y) (+ cx x) (+ cy y)) 84 + (graph-line g (- cx x) (- cy y) (+ cx x) (- cy y)) 85 + (graph-line g (- cx y) (+ cy x) (+ cx y) (+ cy x)) 86 + (graph-line g (- cx y) (- cy x) (+ cx y) (- cy x)) 87 + (incf x) 88 + (if (< d 0) 89 + (incf d (+ (* 2 x) 1)) 90 + (progn (decf y) (incf d (+ (* 2 (- x y)) 1)))))) 91 + ;; Outline circle 92 + (let ((x 0) (y r) (d (- 1 r))) 93 + (declare (type fixnum x y d)) 94 + (loop while (<= x y) do 95 + (graph-plot g (+ cx x) (+ cy y)) 96 + (graph-plot g (- cx x) (+ cy y)) 97 + (graph-plot g (+ cx x) (- cy y)) 98 + (graph-plot g (- cx x) (- cy y)) 99 + (graph-plot g (+ cx y) (+ cy x)) 100 + (graph-plot g (- cx y) (+ cy x)) 101 + (graph-plot g (+ cx y) (- cy x)) 102 + (graph-plot g (- cx y) (- cy x)) 103 + (incf x) 104 + (if (< d 0) 105 + (incf d (+ (* 2 x) 1)) 106 + (progn (decf y) (incf d (+ (* 2 (- x y)) 1))))))))
+33
fedac/native/cl/input-keycodes.lisp
··· 1 + ;;; Linux input keycodes 2 + 3 + (in-package :ac-native.input) 4 + 5 + (defconstant +key-esc+ 1) 6 + (defconstant +key-1+ 2) 7 + (defconstant +key-backspace+ 14) 8 + (defconstant +key-tab+ 15) 9 + (defconstant +key-enter+ 28) 10 + (defconstant +key-leftctrl+ 29) 11 + (defconstant +key-a+ 30) 12 + (defconstant +key-space+ 57) 13 + (defconstant +key-f1+ 59) 14 + (defconstant +key-up+ 103) 15 + (defconstant +key-left+ 105) 16 + (defconstant +key-right+ 106) 17 + (defconstant +key-down+ 108) 18 + (defconstant +key-home+ 102) 19 + (defconstant +key-end+ 107) 20 + (defconstant +key-power+ 116) 21 + 22 + ;;; Event types 23 + (defconstant +ev-key+ 1) 24 + (defconstant +ev-rel+ 2) 25 + (defconstant +ev-abs+ 3) 26 + 27 + ;;; struct input_event (24 bytes on x86-64) 28 + (cffi:defcstruct input-event 29 + (tv-sec :long) 30 + (tv-usec :long) 31 + (type :uint16) 32 + (code :uint16) 33 + (value :int32))
+64
fedac/native/cl/input.lisp
··· 1 + ;;; Input — evdev keyboard/mouse/touch 2 + 3 + (in-package :ac-native.input) 4 + 5 + (defstruct ac-input 6 + "Input subsystem state." 7 + (fds (make-array 16 :element-type 'fixnum :initial-element -1)) 8 + (fd-count 0 :type fixnum) 9 + (display-w 0 :type fixnum) 10 + (display-h 0 :type fixnum) 11 + (pixel-scale 1 :type fixnum)) 12 + 13 + (defstruct event 14 + "An input event." 15 + (type :none :type keyword) ; :key-down :key-up :touch :draw :lift 16 + (key nil) ; key name string 17 + (code 0 :type fixnum) ; raw keycode 18 + (x 0 :type fixnum) 19 + (y 0 :type fixnum)) 20 + 21 + (defun input-init (display-w display-h pixel-scale) 22 + "Open all /dev/input/event* devices." 23 + (let ((input (make-ac-input :display-w display-w :display-h display-h 24 + :pixel-scale pixel-scale))) 25 + ;; Scan /dev/input/ for event devices 26 + (loop for i from 0 to 15 27 + for path = (format nil "/dev/input/event~D" i) 28 + for fd = (ac-native.syscalls:sys-open path 29 + (logior ac-native.syscalls:+o-rdonly+ 30 + ac-native.syscalls:+o-nonblock+) 31 + 0) 32 + when (>= fd 0) do 33 + (setf (aref (ac-input-fds input) (ac-input-fd-count input)) fd) 34 + (incf (ac-input-fd-count input)) 35 + (ac-native.util:ac-log "[input] opened ~A (fd ~D)~%" path fd)) 36 + (ac-native.util:ac-log "[input] ~D event devices~%" (ac-input-fd-count input)) 37 + input)) 38 + 39 + (defun input-poll (input) 40 + "Poll all input devices, return a list of events." 41 + (let ((events nil)) 42 + (dotimes (i (ac-input-fd-count input)) 43 + (let ((fd (aref (ac-input-fds input) i))) 44 + (cffi:with-foreign-object (ev '(:struct input-event)) 45 + (loop for n = (ac-native.syscalls:sys-read 46 + fd ev (cffi:foreign-type-size '(:struct input-event))) 47 + while (= n (cffi:foreign-type-size '(:struct input-event))) do 48 + (let ((type (cffi:foreign-slot-value ev '(:struct input-event) 'type)) 49 + (code (cffi:foreign-slot-value ev '(:struct input-event) 'code)) 50 + (value (cffi:foreign-slot-value ev '(:struct input-event) 'value))) 51 + (when (= type +ev-key+) 52 + (push (make-event 53 + :type (if (plusp value) :key-down :key-up) 54 + :code code) 55 + events))))))) 56 + (nreverse events))) 57 + 58 + (defun input-destroy (input) 59 + "Close all input fds." 60 + (when input 61 + (dotimes (i (ac-input-fd-count input)) 62 + (let ((fd (aref (ac-input-fds input) i))) 63 + (when (>= fd 0) 64 + (ac-native.syscalls:sys-close fd))))))
+85
fedac/native/cl/main.lisp
··· 1 + ;;; Main entry point — AC Native OS (Common Lisp edition) 2 + 3 + (in-package :ac-native) 4 + 5 + (defvar *running* t "Main loop flag.") 6 + 7 + (defun compute-pixel-scale (display-w) 8 + "Compute pixel scale targeting ~300px wide." 9 + (let ((target (max 1 (min 16 (floor display-w 300))))) 10 + ;; Prefer clean divisors within ±3 11 + (loop for delta from 0 to 3 do 12 + (let ((s (+ target delta))) 13 + (when (and (>= s 1) (<= s 16) 14 + (zerop (mod display-w s))) 15 + (return-from compute-pixel-scale s))) 16 + (let ((s (- target delta))) 17 + (when (and (>= s 1) 18 + (zerop (mod display-w s))) 19 + (return-from compute-pixel-scale s)))) 20 + target)) 21 + 22 + (defun mount-minimal-fs () 23 + "Mount essential pseudo-filesystems (PID 1 only)." 24 + (ac-native.syscalls:sys-mount "proc" "/proc" "proc") 25 + (ac-native.syscalls:sys-mount "sysfs" "/sys" "sysfs") 26 + (ac-native.syscalls:sys-mount "devtmpfs" "/dev" "devtmpfs") 27 + (ac-log "mounted proc, sysfs, devtmpfs~%")) 28 + 29 + (defun main () 30 + "AC Native OS entry point." 31 + (ac-log "~%═══════════════════════════════════════~%") 32 + (ac-log " AC Native OS (Common Lisp)~%") 33 + (ac-log " SBCL ~A~%" (lisp-implementation-version)) 34 + (ac-log "═══════════════════════════════════════~%~%") 35 + 36 + ;; PID 1 duties 37 + (when (= (ac-native.syscalls:sys-getpid) 1) 38 + (mount-minimal-fs)) 39 + 40 + ;; Init display 41 + (let ((display (ac-native.drm:drm-init))) 42 + (unless display 43 + (ac-log "FATAL: no display~%") 44 + (return-from main 1)) 45 + 46 + (let* ((dw (ac-native.drm:display-width display)) 47 + (dh (ac-native.drm:display-height display)) 48 + (scale (compute-pixel-scale dw)) 49 + (sw (floor dw scale)) 50 + (sh (floor dh scale)) 51 + (screen (fb-create sw sh)) 52 + (graph (make-graph :fb screen :screen screen))) 53 + 54 + (ac-log "display: ~Dx~D scale: ~D screen: ~Dx~D~%" 55 + dw dh scale sw sh) 56 + 57 + ;; Main loop 58 + (setf *running* t) 59 + (unwind-protect 60 + (loop while *running* do 61 + ;; TODO: input-poll 62 + ;; TODO: js-call-act 63 + ;; TODO: js-call-sim 64 + 65 + ;; Demo: cycle background color 66 + (let* ((t-ms (monotonic-time-ms)) 67 + (r (floor (+ 128 (* 127 (sin (* t-ms 0.001)))))) 68 + (g (floor (+ 128 (* 127 (sin (* t-ms 0.0013)))))) 69 + (b (floor (+ 128 (* 127 (sin (* t-ms 0.0017))))))) 70 + (graph-wipe graph (make-color :r r :g g :b b)) 71 + ;; Draw a box in the center 72 + (graph-ink graph (make-color :r 255 :g 255 :b 255 :a 200)) 73 + (graph-box graph (- (floor sw 2) 20) (- (floor sh 2) 20) 40 40) 74 + ;; Draw a circle 75 + (graph-ink graph (make-color :r 255 :g 100 :b 50)) 76 + (graph-circle graph (floor sw 2) (floor sh 2) 15)) 77 + 78 + ;; Present 79 + (ac-native.drm:drm-present display screen scale) 80 + (frame-sync-60fps)) 81 + 82 + ;; Cleanup 83 + (fb-destroy screen) 84 + (ac-native.drm:drm-destroy display) 85 + (ac-log "shutdown complete~%")))))
+73
fedac/native/cl/packages.lisp
··· 1 + ;;; Package definitions for AC Native OS 2 + 3 + (defpackage :ac-native.util 4 + (:use :cl) 5 + (:export #:ac-log #:monotonic-time-ms #:frame-sync-60fps)) 6 + 7 + (defpackage :ac-native.syscalls 8 + (:use :cl :cffi) 9 + (:export #:sys-open #:sys-close #:sys-read #:sys-write 10 + #:sys-ioctl #:sys-mmap #:sys-munmap 11 + #:sys-mount #:sys-umount 12 + #:sys-reboot #:sys-poweroff 13 + #:+o-rdonly+ #:+o-rdwr+ #:+o-nonblock+ #:+o-cloexec+ 14 + #:+prot-read+ #:+prot-write+ #:+map-shared+ 15 + #:+clock-monotonic+)) 16 + 17 + (defpackage :ac-native.color 18 + (:use :cl) 19 + (:export #:make-color #:color-r #:color-g #:color-b #:color-a 20 + #:color-pack-argb32 #:color-blend)) 21 + 22 + (defpackage :ac-native.framebuffer 23 + (:use :cl :cffi :ac-native.color) 24 + (:export #:make-framebuffer #:fb-create #:fb-destroy 25 + #:fb-width #:fb-height #:fb-stride #:fb-pixels 26 + #:fb-clear #:fb-put-pixel #:fb-blend-pixel 27 + #:fb-copy-scaled)) 28 + 29 + (defpackage :ac-native.drm 30 + (:use :cl :cffi :ac-native.syscalls :ac-native.framebuffer) 31 + (:export #:drm-init #:drm-destroy #:drm-present #:drm-flip 32 + #:display-width #:display-height)) 33 + 34 + (defpackage :ac-native.graph 35 + (:use :cl :ac-native.color :ac-native.framebuffer) 36 + (:export #:make-graph #:graph-wipe #:graph-ink 37 + #:graph-plot #:graph-line #:graph-box #:graph-circle)) 38 + 39 + (defpackage :ac-native.font 40 + (:use :cl :ac-native.graph :ac-native.framebuffer) 41 + (:export #:font-init #:font-draw #:font-measure 42 + #:font-draw-matrix #:font-measure-matrix)) 43 + 44 + (defpackage :ac-native.input 45 + (:use :cl :cffi :ac-native.syscalls) 46 + (:export #:input-init #:input-destroy #:input-poll 47 + #:make-event #:event-type #:event-key #:event-x #:event-y)) 48 + 49 + (defpackage :ac-native.alsa 50 + (:use :cl :cffi) 51 + (:export #:pcm-open #:pcm-close #:pcm-writei #:pcm-prepare 52 + #:pcm-set-params #:mixer-set-capture-volume)) 53 + 54 + (defpackage :ac-native.audio 55 + (:use :cl :cffi :bordeaux-threads 56 + :ac-native.alsa :ac-native.util) 57 + (:export #:audio-init #:audio-destroy 58 + #:audio-synth #:audio-synth-kill 59 + #:audio-sample-play #:audio-sample-load-data)) 60 + 61 + (defpackage :ac-native.config 62 + (:use :cl) 63 + (:export #:load-config #:config-handle #:config-piece)) 64 + 65 + (defpackage :ac-native 66 + (:use :cl :ac-native.util :ac-native.color :ac-native.framebuffer 67 + :ac-native.drm :ac-native.graph :ac-native.font 68 + :ac-native.input :ac-native.audio :ac-native.config) 69 + (:export #:main)) 70 + 71 + (defpackage :ac-native.build 72 + (:use :cl) 73 + (:export #:build))
+72
fedac/native/cl/syscalls.lisp
··· 1 + ;;; Linux syscall bindings via CFFI 2 + 3 + (in-package :ac-native.syscalls) 4 + 5 + ;;; File operations 6 + (cffi:defcfun ("open" sys-open) :int 7 + (pathname :string) (flags :int) (mode :int)) 8 + 9 + (cffi:defcfun ("close" sys-close) :int (fd :int)) 10 + 11 + (cffi:defcfun ("read" sys-read) :long 12 + (fd :int) (buf :pointer) (count :unsigned-long)) 13 + 14 + (cffi:defcfun ("write" sys-write) :long 15 + (fd :int) (buf :pointer) (count :unsigned-long)) 16 + 17 + (cffi:defcfun ("ioctl" sys-ioctl) :int 18 + (fd :int) (request :unsigned-long) &rest) 19 + 20 + ;;; Memory mapping 21 + (cffi:defcfun ("mmap" sys-mmap) :pointer 22 + (addr :pointer) (length :unsigned-long) (prot :int) 23 + (flags :int) (fd :int) (offset :long)) 24 + 25 + (cffi:defcfun ("munmap" sys-munmap) :int 26 + (addr :pointer) (length :unsigned-long)) 27 + 28 + ;;; Filesystem 29 + (cffi:defcfun ("mount" %mount) :int 30 + (source :string) (target :string) (fstype :string) 31 + (flags :unsigned-long) (data :pointer)) 32 + 33 + (defun sys-mount (source target fstype &optional (flags 0)) 34 + (%mount source target fstype flags (cffi:null-pointer))) 35 + 36 + (cffi:defcfun ("umount" sys-umount) :int (target :string)) 37 + 38 + ;;; System control 39 + (cffi:defcfun ("reboot" %reboot) :int (cmd :int)) 40 + 41 + (defconstant +reboot-power-off+ #x4321FEDC) 42 + (defconstant +reboot-restart+ #x01234567) 43 + 44 + (defun sys-poweroff () 45 + ;; sync first 46 + (cffi:foreign-funcall "sync" :void) 47 + (%reboot +reboot-power-off+)) 48 + 49 + (defun sys-reboot () 50 + (cffi:foreign-funcall "sync" :void) 51 + (%reboot +reboot-restart+)) 52 + 53 + ;;; Process 54 + (cffi:defcfun ("getpid" sys-getpid) :int) 55 + (cffi:defcfun ("fork" sys-fork) :int) 56 + (cffi:defcfun ("execvp" sys-execvp) :int 57 + (file :string) (argv :pointer)) 58 + (cffi:defcfun ("waitpid" sys-waitpid) :int 59 + (pid :int) (status :pointer) (options :int)) 60 + 61 + ;;; Constants 62 + (defconstant +o-rdonly+ 0) 63 + (defconstant +o-wronly+ 1) 64 + (defconstant +o-rdwr+ 2) 65 + (defconstant +o-nonblock+ #o4000) 66 + (defconstant +o-cloexec+ #o2000000) 67 + 68 + (defconstant +prot-read+ 1) 69 + (defconstant +prot-write+ 2) 70 + (defconstant +map-shared+ 1) 71 + 72 + (defconstant +clock-monotonic+ 1)
+83
fedac/native/cl/util.lisp
··· 1 + ;;; Utility functions — logging, timing 2 + 3 + (in-package :ac-native.util) 4 + 5 + (defvar *log-stream* *error-output* 6 + "Where ac-log writes. Rebound to a file stream when USB is mounted.") 7 + 8 + (defvar *log-file* nil "Open file stream for USB log, or nil.") 9 + 10 + (defun ac-log (fmt &rest args) 11 + "Log a message to stderr and optionally to USB log file." 12 + (let ((msg (apply #'format nil fmt args))) 13 + (write-string msg *error-output*) 14 + (force-output *error-output*) 15 + (when *log-file* 16 + (write-string msg *log-file*) 17 + (force-output *log-file*)))) 18 + 19 + (defun open-usb-log (path) 20 + "Open a log file on the USB drive (append mode)." 21 + (setf *log-file* (open path :direction :output 22 + :if-exists :append 23 + :if-does-not-exist :create))) 24 + 25 + (defun close-usb-log () 26 + (when *log-file* 27 + (close *log-file*) 28 + (setf *log-file* nil))) 29 + 30 + ;;; Monotonic clock 31 + 32 + (cffi:defcfun ("clock_gettime" %clock-gettime) :int 33 + (clk-id :int) 34 + (tp :pointer)) 35 + 36 + (defconstant +clock-monotonic+ 1) 37 + 38 + (defun monotonic-time-ms () 39 + "Return monotonic time in milliseconds as a double-float." 40 + (cffi:with-foreign-object (ts '(:struct timespec)) 41 + (%clock-gettime +clock-monotonic+ ts) 42 + (let ((sec (cffi:foreign-slot-value ts '(:struct timespec) 'tv-sec)) 43 + (nsec (cffi:foreign-slot-value ts '(:struct timespec) 'tv-nsec))) 44 + (+ (* sec 1000.0d0) (/ nsec 1000000.0d0))))) 45 + 46 + (cffi:defcstruct timespec 47 + (tv-sec :long) 48 + (tv-nsec :long)) 49 + 50 + (cffi:defcfun ("clock_nanosleep" %clock-nanosleep) :int 51 + (clk-id :int) 52 + (flags :int) 53 + (request :pointer) 54 + (remain :pointer)) 55 + 56 + (defconstant +timer-abstime+ 1) 57 + 58 + (defvar *frame-target-ns* (floor 1000000000 60) 59 + "Nanoseconds per frame at 60fps.") 60 + 61 + (defvar *next-frame-sec* 0) 62 + (defvar *next-frame-nsec* 0) 63 + 64 + (defun frame-sync-60fps () 65 + "Sleep until the next 60fps frame boundary. Call once per frame." 66 + (incf *next-frame-nsec* *frame-target-ns*) 67 + (when (>= *next-frame-nsec* 1000000000) 68 + (decf *next-frame-nsec* 1000000000) 69 + (incf *next-frame-sec*)) 70 + ;; Initialize on first call 71 + (when (zerop *next-frame-sec*) 72 + (cffi:with-foreign-object (ts '(:struct timespec)) 73 + (%clock-gettime +clock-monotonic+ ts) 74 + (setf *next-frame-sec* 75 + (cffi:foreign-slot-value ts '(:struct timespec) 'tv-sec)) 76 + (setf *next-frame-nsec* 77 + (cffi:foreign-slot-value ts '(:struct timespec) 'tv-nsec)) 78 + (return-from frame-sync-60fps))) 79 + ;; Sleep until target time 80 + (cffi:with-foreign-object (ts '(:struct timespec)) 81 + (setf (cffi:foreign-slot-value ts '(:struct timespec) 'tv-sec) *next-frame-sec*) 82 + (setf (cffi:foreign-slot-value ts '(:struct timespec) 'tv-nsec) *next-frame-nsec*) 83 + (%clock-nanosleep +clock-monotonic+ +timer-abstime+ ts (cffi:null-pointer))))