Initial Lime window manager
This commit is contained in:
163
test/x11-font-smoke.lisp
Normal file
163
test/x11-font-smoke.lisp
Normal file
@@ -0,0 +1,163 @@
|
||||
;;;; Opt-in isolated-X font/overlay regression, including live reload/rollback.
|
||||
;;;; Requires Xvfb, Pango/Cairo, and fonts covering Japanese and color emoji.
|
||||
;;;; Optional: bind CL-USER::*FONT-SMOKE-IMAGE-DIRECTORY* before loading to save
|
||||
;;;; bar.png and palette.png for inspection. Never uses the current DISPLAY.
|
||||
;;;; *FONT-SMOKE-FONT-OPTIONS* optionally supplies MAKE-UI-FONT-STYLE keywords.
|
||||
(require :asdf)
|
||||
(asdf:load-asd (truename (merge-pathnames "../lime.asd" *load-truename*)))
|
||||
(asdf:load-system "lime/x11")
|
||||
(defvar *font-smoke-image-directory* nil)
|
||||
(defvar *font-smoke-font-options* nil)
|
||||
|
||||
(dolist (depth '(24 16))
|
||||
(let ((server (uiop:launch-program
|
||||
(list "Xvfb" "-displayfd" "1" "-screen" "0"
|
||||
(format nil "1000x622x~D" depth) "-nolisten" "tcp")
|
||||
:output :stream :error-output :interactive))
|
||||
(backend nil) (checks 0))
|
||||
(unwind-protect
|
||||
(let* ((number (parse-integer (read-line (uiop:process-info-output server))))
|
||||
(configuration (lime.x11:make-lime-configuration
|
||||
:default-keybindings-p nil
|
||||
:bar-style (lime.x11:make-head-bar-style
|
||||
:foreground "#dbc9a6" :focused-foreground "#dbc9a6"
|
||||
:accent-color "#ffb054" :menu-label-color "#dbc9a6"
|
||||
:logo-color "#768565")
|
||||
:font (apply #'lime.x11:make-ui-font-style *font-smoke-font-options*)
|
||||
:bar-height 24 :bar-height-supplied-p t)))
|
||||
(setf backend (lime.x11:start-lime
|
||||
:display-name (format nil ":~D" number) :head-discovery :none))
|
||||
(lime.x11:apply-lime-configuration backend configuration)
|
||||
(let* ((display (lime.x11::x11-backend-display backend))
|
||||
(head (first (lime.x11:backend-head-ids backend)))
|
||||
(bar (gethash head (lime.x11::x11-backend-head-bars backend)))
|
||||
(client (xlib:create-window :parent (lime.x11::x11-backend-root backend)
|
||||
:x 0 :y 0 :width 100 :height 100))
|
||||
(labels '("café — 日本語 🎮" "👩💻 Coding and debugging"
|
||||
"مرحبا بالعالم" "é — combining accents"))
|
||||
(session (lime.ui:make-ui-session
|
||||
(loop for label in labels for id from 0 collect
|
||||
(lime.ui:make-ui-candidate :id id :label label :detail "Unicode sample")))))
|
||||
(labels ((check (truth label)
|
||||
(assert truth () "Font smoke (~D-bit): ~A" depth label)
|
||||
(incf checks))
|
||||
(check-frame (canvas window)
|
||||
(xlib:display-finish-output display)
|
||||
(check (equalp (lime.x11::ui-canvas-pixels canvas)
|
||||
(xlib:image-z-pixarray
|
||||
(xlib:get-image window :x 0 :y 0
|
||||
:width (lime.x11::ui-canvas-width canvas)
|
||||
:height (lime.x11::ui-canvas-height canvas)
|
||||
:format :z-pixmap)))
|
||||
"painted frame survives the X11 pixel upload"))
|
||||
(snapshot (canvas name)
|
||||
(when (and (= depth 24) *font-smoke-image-directory*)
|
||||
(check (zerop (lime.x11::%cairo-write-png
|
||||
(lime.x11::ui-canvas-surface canvas)
|
||||
(namestring (merge-pathnames name *font-smoke-image-directory*))))
|
||||
"PNG snapshot"))))
|
||||
(xlib:change-property client :_NET_WM_NAME
|
||||
(babel:string-to-octets (first labels) :encoding :utf-8)
|
||||
:UTF8_STRING 8)
|
||||
(lime.x11:manage-window backend client)
|
||||
(lime.x11:backend-set-head-bar-status-providers
|
||||
backend (list (lambda (&rest ignored)
|
||||
(declare (ignore ignored))
|
||||
(list (lime.x11:format-music-status '(:state :playing :title "スパイダー")
|
||||
:playing-icon "▶" :icon-foreground "#76929f")
|
||||
(lime.x11:format-volume-status '(:volume 25) :icon "♪"
|
||||
:icon-foreground "#76929f")))))
|
||||
(check-frame (lime.x11::x11-head-bar-canvas bar) (lime.x11::x11-head-bar-window bar))
|
||||
(let ((pixels (lime.x11::ui-canvas-pixels (lime.x11::x11-head-bar-canvas bar))))
|
||||
(check (loop for y below 24 always
|
||||
(loop for x below 10 always
|
||||
(= (aref pixels y x) (aref pixels 0 0)
|
||||
(aref pixels y (- 999 x)))))
|
||||
"both outer text margins remain clear"))
|
||||
;; Inspect native RGB pixels: the logo is green, not the cream
|
||||
;; used by neighboring labels (before depth-specific upload).
|
||||
(let* ((canvas (lime.x11::x11-head-bar-canvas bar))
|
||||
(data (lime.x11::%cairo-data (lime.x11::ui-canvas-surface canvas)))
|
||||
(stride (lime.x11::%cairo-stride (lime.x11::ui-canvas-surface canvas)))
|
||||
(right (lime.x11::x11-head-bar-hit-region-right
|
||||
(first (lime.x11::x11-head-bar-hit-regions bar)))))
|
||||
(check (loop for y below 24 thereis
|
||||
(loop for x from 10 below right
|
||||
for pixel = (cffi:mem-ref data :uint32 (+ (* y stride) (* x 4)))
|
||||
thereis (> (ldb (byte 8 8) pixel)
|
||||
(max (ldb (byte 8 16) pixel) (ldb (byte 8 0) pixel)))))
|
||||
"Lime logo uses its independent green foreground"))
|
||||
(snapshot (lime.x11::x11-head-bar-canvas bar) "bar.png")
|
||||
(lime.x11::%open-ui-session backend session)
|
||||
(let* ((overlay (lime.x11::x11-backend-ui-overlay backend))
|
||||
(old-bar-font (lime.x11::x11-head-bar-font bar))
|
||||
(old-font (lime.x11::x11-ui-overlay-font overlay))
|
||||
(old-canvas (lime.x11::x11-ui-overlay-canvas overlay))
|
||||
(old-height (lime.x11::x11-ui-overlay-height overlay))
|
||||
(large (lime.x11::copy-lime-configuration configuration)))
|
||||
(dolist (text labels)
|
||||
(check (zerop (lime.x11::text-metrics-unknown-glyphs
|
||||
(lime.x11::%text-metrics old-font text)))
|
||||
(format nil "installed fonts cover ~S" text)))
|
||||
(check-frame old-canvas (lime.x11::x11-ui-overlay-window overlay))
|
||||
(snapshot old-canvas "palette.png")
|
||||
(lime.x11::%render-command-palette backend)
|
||||
(check (eq old-canvas (lime.x11::x11-ui-overlay-canvas overlay)) "reuse same-size canvas")
|
||||
(check (eq old-font (lime.x11::x11-ui-overlay-font overlay)) "reuse unchanged font")
|
||||
(setf (lime.x11:lime-configuration-font large)
|
||||
(lime.x11:make-ui-font-style :family "sans-serif, Noto Color Emoji" :size 22 :weight :bold)
|
||||
(lime.x11:lime-configuration-bar-height large) 36)
|
||||
(setf (lime.x11:lime-configuration-bar-style large)
|
||||
(lime.x11:make-head-bar-style :padding-x 16))
|
||||
(lime.x11:apply-lime-configuration backend large)
|
||||
(check (= (+ 16 (lime.x11::%text-width (lime.x11::x11-head-bar-font bar) "Lime"))
|
||||
(lime.x11::x11-head-bar-hit-region-right
|
||||
(first (lime.x11::x11-head-bar-hit-regions bar))))
|
||||
"padding reload also updates click geometry")
|
||||
(check (eq session (lime.x11::x11-backend-ui-session backend)) "reload preserves open session")
|
||||
(check (eq overlay (lime.x11::x11-backend-ui-overlay backend)) "reload preserves overlay window")
|
||||
(check (> (lime.x11::x11-ui-overlay-height overlay) old-height) "font increases measured row height")
|
||||
(check (= 36 (xlib:drawable-y (lime.x11::x11-ui-overlay-window overlay))) "overlay follows new bar height")
|
||||
(check (null (lime.x11::ui-text-font-layout old-font)) "reload frees old palette font")
|
||||
(check (null (lime.x11::ui-text-font-layout old-bar-font)) "reload frees old bar font")
|
||||
(check (null (lime.x11::ui-canvas-surface old-canvas)) "resize frees old canvas")
|
||||
(check (= 22 (lime.x11:ui-font-style-size
|
||||
(lime.x11::ui-text-font-style (lime.x11::x11-ui-overlay-font overlay))))
|
||||
"palette uses configured pixel size")
|
||||
(check-frame (lime.x11::x11-ui-overlay-canvas overlay) (lime.x11::x11-ui-overlay-window overlay))
|
||||
(let ((bad (lime.x11::copy-lime-configuration large))
|
||||
(failed nil) (failed-font nil))
|
||||
(setf (lime.x11:lime-configuration-font bad) (lime.x11:make-ui-font-style :size 10)
|
||||
(lime.x11:lime-configuration-startup-hooks bad)
|
||||
(list (lambda (ignored)
|
||||
(declare (ignore ignored))
|
||||
(setf failed-font (lime.x11::x11-ui-overlay-font overlay))
|
||||
(error "Deliberate font rollback"))))
|
||||
(handler-case (lime.x11:apply-lime-configuration backend bad :run-startup-hooks-p t)
|
||||
(error () (setf failed t)))
|
||||
(check (and failed failed-font) "installation reaches deliberate rollback")
|
||||
(check (eq large (lime.x11:x11-backend-configuration backend)) "previous config restored")
|
||||
(check (null (lime.x11::ui-text-font-layout failed-font)) "rollback frees failed font")
|
||||
(check (= 22 (lime.x11:ui-font-style-size
|
||||
(lime.x11::ui-text-font-style (lime.x11::x11-head-bar-font bar))))
|
||||
"rollback restores bar font")
|
||||
(check (= 22 (lime.x11:ui-font-style-size
|
||||
(lime.x11::ui-text-font-style (lime.x11::x11-ui-overlay-font overlay))))
|
||||
"rollback restores overlay font")
|
||||
(check-frame (lime.x11::x11-ui-overlay-canvas overlay) (lime.x11::x11-ui-overlay-window overlay)))
|
||||
;; Closing and reopening frees all transient native resources.
|
||||
(lime.x11:backend-close-command-palette backend)
|
||||
(check (null (lime.x11::ui-text-font-layout (lime.x11::x11-ui-overlay-font overlay)))
|
||||
"close frees palette layout")
|
||||
(check (null (lime.x11::ui-canvas-surface (lime.x11::x11-ui-overlay-canvas overlay)))
|
||||
"close frees palette canvas")
|
||||
(lime.x11::%open-ui-session backend session)
|
||||
(check (not (eq overlay (lime.x11::x11-backend-ui-overlay backend))) "reopen builds a new overlay"))
|
||||
(lime.x11:stop-backend backend)
|
||||
(setf backend nil)
|
||||
(check (null (lime.x11::ui-text-font-layout (lime.x11::x11-head-bar-font bar))) "shutdown frees bar font")
|
||||
(check (null (lime.x11::ui-canvas-surface (lime.x11::x11-head-bar-canvas bar))) "shutdown frees bar canvas")
|
||||
(format t "~&Live X11 fonts (~D-bit): ~D checks passed.~%" depth checks))))
|
||||
(when backend (lime.x11:stop-backend backend))
|
||||
(uiop:terminate-process server)
|
||||
(uiop:wait-process server))))
|
||||
Reference in New Issue
Block a user