-
Notifications
You must be signed in to change notification settings - Fork 13
/
zen.lisp
2537 lines (2254 loc) · 81.1 KB
/
zen.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;; (C) 2011 Pierre-Yves Baccou
(sb-ext:unlock-package 'sb-ext) ; to allow (defstruct gc), get-time-of-day
(require "deps" "deps.lisp")
(when (find-package 'cl-opengl)
(rename-package 'cl-opengl 'cl-opengl)) ; remove nickname "GL"
(require 'clx)
(rename-package (find-package 'xlib) "XLIB" '("X"))
(when (find-package 'gl)
(delete-package 'gl)) ; do not use clx/gl package
(when (find-package 'cl-opengl)
(rename-package 'cl-opengl 'cl-opengl '(gl))) ; restore nickname "GL", now that conflict is solved
(when (find-package 'glx)
(delete-package 'glx))
(eval-when (:compile-toplevel :load-toplevel :execute)
;
(load "packages")
;
)
(load "zengl")
(load "glx/myglx")
(load "glx/constants")
(load "data/request-opcodes") ; only for the CopyArea one
(load "data/error-spec")
(load "data/colors")
(load "region")
(rename-package 'myglx 'myglx '(glx))
(define-constant +root-id+ #x0000013c) ; to match X.Org server
(define-constant +root-width+ 1000)
(define-constant +root-height+ 800)
(define-constant +root-x+ 10)
(define-constant +root-y+ 10)
(define-constant +default-colormap+ 214)
(define-constant +visual-24+ #x21)
;;(define-constant +visual-32+ #x23)
(defvar *screen* nil)
(defvar *saved-read-target* nil)
(defvar *saved-draw-target* nil)
;; --------------------------------------------------------------------------------------------------------------
;; --------------------------------------------------------------------------------------------------------------
;; Conditions, boilerplate
;; X errors : signal conditions, send an error (and also whatever events were queued before the error occured, but :
;; note : see protocol book p 353. When a request terminates with an error, there is no partial execution, unless it was a request such as ChangeWindowAttributes.
;; In this case all the operations up to the one containing the error will be performed.
(define-condition internal-error (simple-error)
((str :initarg :str :reader internal-error-str)))
(define-condition xcondition (simple-condition)
((args :initarg :args :reader xcondition-args)))
(define-condition xreply (xcondition)
())
(define-condition xerror (xcondition simple-error)
((type :initarg :type :reader xerror-type)))
(define-condition xevent (xcondition)
((name :initarg :name)
(client :initarg :client)))
;; --------------------------------------------------------------------------------------------------------------
;; --------------------------------------------------------------------------------------------------------------
;; Requests => internal commands
;; TODO: these are (almost) templates that will need to be expanded (esp. "request")
;; (NB : what did I mean here ?)
(defvar *requests* nil)
(defvar *client* nil)
(defvar *clients* nil)
(defvar *focus-window* :PointerRoot)
(defvar *focus-revert-to* :PointerRoot)
(defvar *last-focus-change-time* 0)
(defvar *button-state* '((:Button1 nil) (:Button2 nil) (:Button3 nil) (:Button4 nil) (:Button5 nil)))
(defvar *cursor-exact-coords* '(0.0 0.0))
(defvar *cursor-window* nil)
(defvar *request-queue* (make-queue))
(defvar *reply/event-queue* (make-queue))
(defmethod is-class (arg type)
nil)
(defmacro defclass- (name parents &rest args)
`(progn
(defclass/struct ,name ,parents ,@args)
(defmethod is-class ((arg ,name) (type (eql ',name)))
t)))
(defun request (name client seqnum suppress-replies &rest args)
(apply (rest (assoc name
*requests*))
client
seqnum
suppress-replies
args))
;; Careful when using this ! as it sets *client* to nil (bad)
(defun int-request (name suppress-replies &rest args)
"internal request (execute from any thread)"
(apply #'request
name nil nil suppress-replies args))
(defun enqueue-request (name suppress-replies &rest args)
"enqueue request for execution in main thread"
(bt:with-lock-held (*request-lock*)
(enqueue (list* name nil nil suppress-replies
args)
*request-queue*))
(bt:condition-notify *request-condition*))
(defmacro with-resources-and-atoms (xargs &body body)
"Extract values from resource and atom ids, save result in same variable var."
`(progn
,@(mapcan #'(lambda (xarg)
(bind (sym class &optional (alt-vals nil alt-vals?))
xarg
(if (eql class 'xatom)
`((setf ,sym (or (get-atom ,sym)
,sym))
(unless (or (stringp ,sym)
,(when alt-vals? `(member ,sym
',alt-vals)))
(xerror :XAtom ,sym)))
`((setf ,sym (or (find-res ,sym)
,sym))
(unless (or (is-class ,sym ',class)
,(when alt-vals? `(member ,sym
',alt-vals)))
(xerror ,(make-keyword class)
,sym))))))
xargs)
,@body))
(defun reconvert (arg)
(when arg
(if (atom arg)
(xresource-id arg)
(mapcar #'reconvert
arg))))
;; TODO : can be shortened A LOT. Treat all xconditions (error, event, reply) the same way, as much as possible.
;; todo : create request struct. 3 members : function, arglist, hooks. Then *requests* will be a fixed-size hashtable of such structs
(defmacro defrequest (request xarglist &body body)
(let ((arglist (mapcar #'carat
xarglist))
(resources/atoms (intersection xarglist
'(gc window drawable pixmap picture cursor colormap font xatom)
:test #'(lambda (xarg item)
(eql (second (mklist xarg))
item)))))
(with-gensyms (gevents greply gerror)
`(let ((fun
#'(lambda (client seqnum suppress-replies &key ,@arglist)
(when client
(setf *client* client))
(if seqnum
(setf (client-processed-seqnum client)
seqnum)
(setf seqnum
(when client
(client-processed-seqnum client))))
(let (,gevents ,greply ,gerror)
(block request-block
(handler-bind
;; TODO : another condition whereby a connection closed whilst executing the request.
((xerror #'(lambda (xerror)
(setf ,gerror
xerror)
(return-from request-block)))
(xreply #'(lambda (xreply)
(setf ,greply
xreply)))
(xevent #'(lambda (xevent)
(push xevent
,gevents))))
(with-resources-and-atoms (,@resources/atoms)
,@body)))
(unless suppress-replies
(dolist (xevent (nreverse ,gevents)) ; fixme : at the moment, in a nested request, events will always be sent before those of the parent request.
(with-slots (name client args)
xevent
(send name ; this is repeated 3 times, think of factoring, within a (send) equivalent local to zen.lisp
client
(list* :sequence-number (client-processed-seqnum client)
(reconvert args)))))
(acond (,greply (send ',request
*client*
(list* :sequence-number seqnum
(reconvert (xcondition-args it)))))
(,gerror (send :Error
*client*
(list* :major-opcode (major-opcode ',request)
:minor-opcode (minor-opcode ',request)
:code (first (rassoc (xerror-type ,gerror) ; todo: evaluate from (first... ) at compile time ?
+error-codes+))
:sequence-number seqnum
(reconvert (xcondition-args it))))))))))) ; this step may not be needed for errors
(push (cons ',request
fun)
*requests*)))))
(defun reply (&rest args)
(signal 'xreply :args args))
(defun xerror (type &optional (resource 0))
(error 'xerror :type type :args (list :resource resource)))
;; ---
;; TODO : try to remove this (merge with find-input-window, for instance)
(defun first-window-selecting-any (events window &optional chain) ; downward chain builds up as we recurse
(when window
(let ((selected-events (intersection events
(remove-duplicates (mapcan #'rest
(attr-event-masks (window-attr window)))))))
(if (some #'(lambda (event)
(notany #'(lambda (win)
(member event
(attr-do-not-propagate-mask (window-attr win))))
chain))
selected-events)
window
(first-window-selecting-any events
(window-parent window)
(cons window
chain))))))
;; TODO : make it work with :msb clients ; byte-swapping for ClientMessage (complicated)
(defrequest :SendEvent ((destination window (:PointerWindow :InputFocus)) propagate event-mask event)
(setf event
(second event)) ; event is originally (:lsb data)
(unless (= 32 (length event)) (error "Bad SendEvent event length !~%"))
(setf (elt event 0)
(logior #x80
(elt event 0)))
(let* ((real-destination (case destination
(:PointerWindow *cursor-window*)
(:InputFocus (let ((focus (focus-window)))
(if (member focus
(all-parents *cursor-window*))
*cursor-window*
focus)))
(t destination)))
(clients (cond ((null event-mask)
(list (xresource-created-by real-destination)))
((null propagate) (find-clients event-mask
real-destination))
(t (or (find-clients event-mask
real-destination)
(progn
(setf real-destination (first-window-selecting-any event-mask
real-destination))
(unless (and (eql destination
:InputFocus)
(member (focus-window)
(all-parents real-destination)))
(find-clients event-mask
real-destination))))))))
;; uninteresting hack : manually reproduce a send (see transport.lisp)
(dolist (client clients)
(let ((data (replace (copy-seq event)
(encode :CARD16 :lsb (client-processed-seqnum client))
:start1 2)))
(bt:with-lock-held ((client-outbuf-lock client))
(enqueue data
(client-out-bufqueue client)))
(write-char #\a *pipe-write-stream*)))))
;; The functions below returns the clients receiving the event, and perhaps some per-client arguments. (source window, event window...)
(defun find-clients (events window)
"List clients that selected the event for this window. (Only used for some events)"
(remove-if #'(lambda (client)
(not (intersection events
(assoc client
(attr-event-masks (window-attr window))))))
*clients*))
(defun find-input-window (window event masks)
"Find first window, up the chain from 'window' , where event is selected by some client. Returns clients and window"
(when window
(with-slots (parent attr)
window
(let ((selected? (find-clients masks
window))
(propagate? (not (member event
(attr-do-not-propagate-mask attr)))))
(let ((input-window (cond (selected? window)
(propagate? (find-input-window parent
event
masks))
(t nil)))
(focus (focus-window)))
(if (and input-window
(member event
'(:KeyPress :KeyRelease))
(not (member focus
(all-parents input-window))))
focus
input-window))))))
(defvar *old-parent* nil)
;; return ((client1 eventwin1) (client2 eventwin2) ...)
(defun find-structure-clients-and-windows (window &key old-parent?)
(nconc (mapcar (rcurry #'list
window)
(find-clients '(:StructureNotify)
window))
(mapcar (rcurry #'list
(window-parent window))
(find-clients '(:SubstructureNotify)
(window-parent window)))
(when old-parent?
(mapcar (rcurry #'list
*old-parent*)
(find-clients '(:SubstructureNotify)
*old-parent*)))))
(defun substructure-redirectee (window)
(first (remove *client*
(find-clients '(:SubstructureRedirect) (window-parent window))))) ; should be a list of 0 or 1 elts
(defun resize-redirectee (window)
(first (remove *client*
(find-clients '(:ResizeRedirect) (window-parent window))))) ; idem
;; all keywords, some with default values (eg window = (window window) keyword !).
;; allowed boolean options :structure-event (StructureNotify etc), :input-event (use "propagate")
(defvar *event-fns* nil)
(eval-when (:compile-toplevel :load-toplevel :execute) ; normally (eval-when (:compile-toplevel). also :execute for testing purposes
(defvar *event-xarglists* nil))
(defun active-input-masks (button-state)
(let ((active-input-masks '(:PointerMotion :KeyPress :KeyRelease :ButtonPress :ButtonRelease :EnterWindow :LeaveWindow)))
(when (find t
button-state
:key #'second)
(push :ButtonMotion active-input-masks))
(dotimes (i 5)
(when (second (elt button-state i))
(push (elt '(:Button1Motion :Button2Motion :Button3Motion :Button4Motion :Button5Motion)
i)
active-input-masks)))
active-input-masks))
;; explain in doc by expanding an example or 2
(defmacro defevent (name xarglist &key (mode :event-mask) event-mask custom-clients masks)
(eval-when (:compile-toplevel :load-toplevel :execute) ; normally (eval-when (:compile-toplevel). also execute for testing purposes
(push (cons name
(remove 'eventwin
xarglist))
*event-xarglists*))
(let ((final-args (mapcan #'(lambda (xarg)
(bind (arg &optional default)
(mklist xarg)
`(,(make-keyword arg) (or ,arg ,default))))
xarglist)))
`(push (cons ,name
#'(lambda (&key ,@(mapcar #'carat
xarglist))
(let ((clientinfos ,(case mode
(:structure '(find-structure-clients-and-windows window))
(:structure-reparent `(find-structure-clients-and-windows window
:old-parent? t))
(:input `(when eventwin
(find-clients (intersection ',masks
(active-input-masks *button-state*))
eventwin)))
(:redirect '(list (substructure-redirectee window)))
(:resize '(list (resize-redirectee window)))
(:reply '(list *client*))
(:custom custom-clients)
(:event-mask `(find-clients (list ,event-mask)
window))))) ; 'normal' events
(dolist (clientinfo clientinfos)
(bind (client &optional eventwin)
(mklist clientinfo)
(signal 'xevent
:name ,name
:client client
:args (list ,@final-args)))))))
*event-fns*)))
;; This macro inserts all implicit args (= args absent from (event...) call), either with nil if there is a default in defevent, as in "(event :SomeEvent :parent nil)", or arg itself as in "(event :SomeEvent :x x)"
(defmacro event (name &rest args)
(let* ((grouped-args (group args 2))
(xarglist (rest (assoc name
*event-xarglists*)))
(bindings (mapcar #'(lambda (argname)
(acond ((find (make-keyword argname) ; arg given in (event... ) command ?
grouped-args
:key #'first)
(list argname (second it)))
((second (assoc argname ; find the default
(mapcar #'mklist
xarglist)))
(list argname it))
(t nil)))
(mapcar #'carat
xarglist))))
`(let* (,@(remove nil bindings))
(funcall (rest (assoc ,name
*event-fns*))
,@(mapcan #'(lambda (argname)
(list (make-keyword argname) argname))
(mapcar #'carat
xarglist))))))
;; --------------------------------------------------------------------------------------------------------------
;; 9) Events
;; FIXME : not well designed, I can't tell what event-coords is for ?
;; fix this by using the source argument, just used for construction of the others (root, eventwin, child)
;; eventwin, child, state, detail, root-x, root-y are given by the input code. The other arguments are computed here.
;; Implicit arg 'window' is set by the input code to be the source window of the event.
;; IMPLICIT args eventwin (as below) and child, also computed in event-func.
;; TODO : allow focus window, grabs
(defmacro definputevent (name (&rest other-args) &rest masks)
`(defevent ,name (source detail
(eventwin (find-input-window source ',name (intersection ',masks
(active-input-masks *button-state*))))
(root-x (floor (first *cursor-exact-coords*))) (root-y (floor (second *cursor-exact-coords*)))
(state (butmod-state))
(root (screen-root (drawable-screen source)))
(same-screen t)
(event-coords (if (null eventwin) ; used just for construction
'(0 0)
(mapcar #'-
(cursor-coords)
(screen-coordinates eventwin))))
(event-x (first event-coords))
(event-y (second event-coords))
(time (server-time))
,@other-args)
:mode :input :masks ,masks))
(definputevent :ButtonPress () :ButtonPress)
(definputevent :ButtonRelease () :ButtonRelease)
(definputevent :KeyPress () :KeyPress)
(definputevent :KeyRelease () :KeyRelease)
(definputevent :MotionNotify () :PointerMotion :ButtonMotion :Button1Motion :Button2Motion :Button3Motion :Button4Motion :Button5Motion)
;; Note 'virtual window crossing' for Enter/LeaveNotify and FocusIn/Out is not implemented.
(definputevent :EnterNotify ((focus (member (focus-window)
(all-parents eventwin)))
(mode :normal)
(child (when eventwin
(find source
(window-subwindows eventwin))))
(same-screen/focus (cond ((and focus same-screen) '(:focus :same-screen))
(focus '(:focus))
(same-screen '(:same-screen))
(t nil))))
:EnterWindow)
(definputevent :LeaveNotify ((focus (member (focus-window)
(all-parents eventwin)))
(mode :normal)
(child (when eventwin
(find source
(window-subwindows eventwin))))
(same-screen/focus (cond ((and focus same-screen) '(:focus :same-screen))
(focus '(:focus))
(same-screen '(:same-screen))
(t nil))))
:LeaveWindow)
(defevent :CirculateNotify (eventwin window place)
:mode :structure)
(defevent :ConfigureNotify
(eventwin window x y width height border-width override-redirect above-sibling)
:mode :structure)
(defevent :CreateNotify
(window parent x y width height border-width override-redirect)
:mode :custom :custom-clients (find-clients '(:SubstructureRedirect) (window-parent window)))
;; :mode :structure) ; special case, hope this works ; update : it wasn't working
(defevent :DestroyNotify (eventwin window)
:mode :structure)
(defevent :MapNotify (eventwin window (override-redirect (attr-override-redirect (window-attr window))))
:mode :structure)
(defevent :UnmapNotify (eventwin window from-configure)
:mode :structure)
(defevent :MapRequest (window (parent (window-parent window)))
:mode :redirect)
(defevent :ConfigureRequest (window (parent (window-parent window)) x y width height border-width sibling stack-mode value-mask)
:mode :redirect)
(defevent :CirculateRequest (window (parent (window-parent window)) place)
:mode :redirect)
(defevent :ResizeRequest (window width height)
:mode :resize)
;; ClientMessage : Cannot be generated by the server ! But TODO : implement byte-swapping for it in SendEvent.
(defevent :ColormapNotify (window colormap new state)
:event-mask :ColormapChange)
(defevent :Expose (window
(x 0) (y 0)
(width (drawable-width window)) (height (drawable-height window))
(count 0))
:event-mask :Exposure)
(defevent :FocusOut (window (mode :Normal) detail)
:event-mask :FocusChange)
(defevent :FocusIn (window (mode :Normal) detail)
:event-mask :FocusChange)
(defevent :GraphicsExposure (drawable
(x 0) (y 0)
(width (drawable-width drawable))
(height (drawable-height drawable))
(count 0)
major-opcode (minor-opcode 0))
:mode :reply)
(defevent :NoExposure (drawable major-opcode (minor-opcode 0))
:mode :reply)
(defevent :GravityNotify (eventwin window x y)
:mode :structure)
(defevent :Keymapnotify (window keys) ; window arg not sent out
:event-mask :KeymapState)
(defevent :MappingNotify (request (first-keycode 8) (count 248))
:mode :custom :custom-clients *clients*)
(defevent :PropertyNotify (window atom (state :NewValue) (time (server-time)))
:event-mask :PropertyChange)
(defevent :ReparentNotify (eventwin window (parent (window-parent window)) x y
(override-redirect (attr-override-redirect (window-attr window))))
:mode :structure-reparent) ;; Warning : *old-parent* must exist
(defevent :VisibilityNotify (window state)
:event-mask :VisibilityChange)
;; --------------------------------------------------------------------------------------------------------------
;; 2) Resources
;; TODO : use hashtable
;; Resource IDs are given by the clients. (but we give them a mask to help create resource IDs)
(defvar *resources* nil)
(defclass xresource ()
((id :initarg :id :accessor xresource-id)
(created-by :initarg :created-by :accessor xresource-created-by)))
(defmethod xresource-id (obj) ; for non-resources
obj)
(defun find-res (id)
(find id
*resources*
:key #'xresource-id))
;; TODO : which client was it created by ? (give created-by field to all xresources ?)
(defun create-res (id obj)
(if (find id
*resources*
:key #'xresource-id)
(xerror :IdChoice id)
(progn (setf (xresource-id obj) id
(xresource-created-by obj) *client*)
(push obj
*resources*))))
(defun destroy-res (resource)
(setf *resources*
(delete resource
*resources*)))
;; (error "Destroying unknown resource"))) ; actually not always an error.
;; --------------------------------------------------------------------------------------------------------------
;; 3) Properties, atoms
;; may add reverse hash table for performance, later.
(define-constant +initial-atoms+ 1024)
(defvar *atoms* (make-array +initial-atoms+ :adjustable t))
(let ((%n 0))
;
(defun intern-atom (name &optional only-if-exists)
(when (= %n
(length *atoms*))
(setf *atoms*
(adjust-array *atoms*
(* 2 %n))))
(aif (position name
*atoms*
:test #'equal)
(1+ it)
(unless only-if-exists
(let ((atom (incf %n)))
(setf (aref *atoms*
(1- atom))
name)
atom))))
;
(defun is-atom (atom)
(and (numberp atom)
(/= atom 0)
(<= atom %n)))
;
) ; n
(defun get-atom (atom)
(when (is-atom atom)
(aref *atoms*
(1- atom))))
(load "data/builtin-atoms.lisp")
(defrequest :InternAtom (name only-if-exists)
(let ((interned (intern-atom name
only-if-exists)))
(reply :atom (when interned
name))))
(defrequest :GetAtomName ((atom xatom))
(reply :name atom))
;;--------------------------
(defstruct property
name ; atom
format
type
(data #()))
;; consider this :
;;(defun val (property [window])
;; (rest (assoc property window)))
;; as closure or normal flet function.
(defrequest :ChangeProperty ((window window) (name xatom) (type xatom) format mode data)
(setf data
(second data)) ; because data = (endian seq)
(flet ((property-match (prop format type mode)
(or (and (eql format
(property-format prop))
(eql type
(property-type prop)))
(eql mode :Replace))))
(event :PropertyNotify :atom name)
(let ((prop (find name
(window-properties window)
:key #'property-name)))
(cond ((and prop
(property-match prop format type mode))
(setf (property-format prop) format
(property-type prop) type
(property-data prop) (ecase mode
(:Replace data)
(:Prepend (concatenate 'vector
data
(property-data prop)))
(:Append (concatenate 'vector
(property-data prop)
data)))))
(prop
(xerror :Match))
(t
(push (make-property :name name :format format :type type :data data)
(window-properties window)))))))
(defrequest :DeleteProperty ((window window) (name xatom))
(with-slots (properties)
window
(when-let ((property (find name
properties
:key #'property-name)))
(setf properties
(delete property
properties))
(event :PropertyNotify :atom name :state :Deleted))))
(defun encode-getproperty-value (value format endian)
(if (eql :lsb endian)
(coerce value 'vector)
(ecase format
(8 (coerce value
'vector))
(16 (concatenate 'vector
(mapcar (compose (curry (encoder 'CARD16)
endian)
(curry (decoder 'CARD16)
:lsb)
(rcurry #'coerce 'vector))
(group value 2))))
(32 (concatenate 'vector
(mapcar (compose (curry (encoder 'CARD32)
endian)
(curry (decoder 'CARD32)
:lsb)
(rcurry #'coerce 'vector))
(group value 4)))))))
;; value has to be encoded here rather than as usual, because encoding depends on format and my transport code doesn't support that
(defrequest :GetProperty ((window window) (name xatom) (type xatom (:AnyPropertyType))
long-offset long-length delete)
(setf long-offset (* 4 long-offset)
long-length (* 4 long-length))
(if-let ((p (find name
(window-properties window)
:key #'property-name)))
(with-fields (data (ptype type) (pformat format))
(p property)
(let* ((end (min (length data)
(+ long-offset
long-length)))
(bytes-after (- (length data)
end)))
(if (not (member type
(list ptype :AnyPropertyType)))
(reply :type ptype
:format pformat
:bytes-after (length data))
(progn
(reply :type ptype
:format pformat
:bytes-after bytes-after
:value (encode-getproperty-value (subseq data
long-offset
end)
pformat
(client-endian *client*)))
(when (and delete
(= 0 bytes-after))
(enqueue-request :DeleteProperty nil
:window window :name name))))))
(reply :type nil
:format 0
:bytes-after 0)))
(defrequest :ListProperties ((window window))
(reply :atoms (mapcar #'property-name
(window-properties window))))
;; untested
(defrequest :RotateProperties ((window window) delta names)
(let* ((names (mapcar #'get-atom
names))
(properties (mapcar #'(lambda (name)
(find name
(window-properties window)
:key #'property-name))
names)))
(when (member nil
names)
(xerror :Xatom))
(when (member nil
properties)
(xerror :Match))
(when (mod delta
(length properties))
(mapc #'(lambda (name property)
(setf (property-name property)
name)
(event :PropertyNotify :atom name))
names
(rotate-list properties
delta)))))
;; --------------------------------------------------------------------------------------------------------------
;; 8) Pixmaps
(defclass- pixmap (drawable)
framebuffer)
;; note this redundancy : there is a copy of width,height in the pixmap class and another in the framebuffer struct.
(defrequest :CreatePixmap (pid (drawable drawable) depth width height)
(if (and (/= depth 1)
(/= depth 24)) ; TODO : more useful depths allowed for pixmaps
(xerror :Value)
(create-res pid
(make-instance 'pixmap
:screen (drawable-screen drawable)
:id pid
:depth depth :width width :height height
:framebuffer (zengl:create-framebuffer width height)))))
(defrequest :FreePixmap ((pixmap pixmap))
;; this stuff belongs in zengl....
(switch-draw-target (screen-root *screen*))
(switch-read-target (screen-root *screen*))
(setf *saved-draw-target* nil)
(setf *saved-read-target* nil)
(gl:Delete-Textures (list (zengl::framebuffer-texture (pixmap-framebuffer pixmap))))
(gl:Delete-Renderbuffers (list (zengl::framebuffer-depth-st-rb (pixmap-framebuffer pixmap))))
(gl:Delete-Framebuffers (list (zengl::framebuffer-fbo (pixmap-framebuffer pixmap))))
(destroy-res pixmap))
;; Put this back in CreateWindow body if it's the only place it's used ?
(defmethod copy-pixmap (obj)
nil)
(defmethod copy-pixmap ((pixmap pixmap))
(with-slots (width height depth screen framebuffer)
pixmap
(make-instance 'pixmap
:depth screen
:id nil ;; no id, it's a server internal pixmap
:framebuffer (zengl:create-framebuffer width height
:source framebuffer)
:width width :height height)))
;; --------------------------------------------------------------------------------------------------------------
;; 1) Windows
;; TODO : maybe put the defaults elsewhere ? (in window creation routine ?)
;; TODO : add references (ie 'cweb paragraph numbers') to where these are used in the code
;; TODO : only one client can select SubstructureRedirect
(defvar *installed-colormaps* nil) ; this does almost nothing, remove it ?
(defvar *cursor-confine-window* nil)
(defstruct screen
id
root
framebuffer
glx-context) ; unused, only one screen
(defclass- attr ()
(background-pixmap nil) ; a pixmap, :ParentRelative or nil (aka :None)
(background-pixel nil)
(border-pixmap :CopyFromParent)
(border-pixel nil)
(bit-gravity :Forget)
(win-gravity :NorthWest)
(event-masks nil) ; one per client : list of the form ((client . (:event1 :event2 ...)) ...)
(do-not-propagate-mask nil) ; one per window : (:event1 :event2 ...)
(override-redirect nil)
(colormap :CopyFromParent)
(cursor nil)
(backing-store :NotUseful) (backing-planes 0) (backing-pixel 0) save-under) ; unused
(defclass- drawable (xresource)
screen
depth
width
height)
(defclass- window (drawable)
attr
mapped
viewable
visualid
x
y
border-width
parent
subwindows
input-subwindows ; todo later : support InputOnly windows, keep them separate from normal ones like so
properties
created-by
(class :InputOutput)) ; unused
;; Idea : (fsetf window-background-pixmap (compose #'attr-background-pixmap #'window-attr)), etc. Or get rid of struct attr and put everything in struct window.
;; footnote : no class InputOutput / InputOnly . InputOnly windows will not be considered as 'real' window and perhaps not even allowed.
;; update : they aren't implemented.
(defun siblings (window)
(awhen (window-parent window)
(window-subwindows it)))
(defun root-window (window)
(screen-root (drawable-screen window)))
(defmethod prep-target ((window window))
(apply #'hide-sw-cursor
(cursor-coords)))
(defmethod unprep-target ((window window))
(apply #'draw-sw-cursor
(cursor-coords)))
(defmethod prep-target ((pixmap pixmap))
)
(defmethod unprep-target ((pixmap pixmap))
)
;; needed now but should logically be lower in the file
(defmacro with-read-target ((target) &body body)
`(unwind-protect
(progn
(prep-target ,target)
(switch-read-target ,target)
,@body)
(unprep-target ,target)
(zengl:flush)))
(defmacro with-draw-target ((target &key gc) &body body)
`(unwind-protect
(progn
(prep-target ,target)
(when (switch-draw-target ,target :gc ,gc)
,@body))
(unprep-target ,target)
(zengl:flush)))
;; excludes event-mask which is a special case
(define-constant +attributes+ '(background-pixel border-pixel bit-gravity win-gravity
do-not-propagate-mask override-redirect colormap cursor backing-store save-under backing-planes backing-pixel))
(define-constant +attributes/get-attr+ (set-difference +attributes+
'(background-pixel border-pixel cursor)))
;; TODO : all the checks in CreateWindow p 138
;; window arg must be given when changing the attrs of an _existing_ window.
(defun event-selected-by-some-other-client (event attr)
(some #'(lambda (client-mask)
(member event
(rest client-mask)))
(remove *client*
(attr-event-masks attr)
:key #'first)))
;; This code closely follows the specification pp. 16-17
(defmacro change-attributes! (attr value-list &optional window)
(let ((lambda-list (mapcar #'(lambda (attr)
(list attr nil (symb attr "?")))
+attributes+)))
`(bind (&key ,@lambda-list (event-mask nil event-mask?) (background-pixmap nil background-pixmap?) (border-pixmap nil border-pixmap?))
,value-list
(when (or (and (member :SubstructureRedirect event-mask)
(event-selected-by-some-other-client :SubstructureRedirect ,attr))
(and (member :ResizeRedirect event-mask)
(event-selected-by-some-other-client :ResizeRedirect ,attr))
(and (member :ButtonPress event-mask)
(event-selected-by-some-other-client :ButtonPress ,attr)))
(xerror :Access))
(when event-mask?
(setf (attr-event-masks ,attr)
(cons (cons *client*
event-mask)
(delete *client*
(attr-event-masks ,attr)
:key #'first))))
(when (or border-pixmap? border-pixel?)
(setf (attr-border-pixmap ,attr) nil
(attr-border-pixel ,attr) nil))
(when (or background-pixmap? background-pixel?)
(setf (attr-background-pixmap ,attr) nil
(attr-background-pixel ,attr) nil))
(when-let ((pixmap (and background-pixmap?
(find-res background-pixmap))))
(if (eql 'pixmap
(type-of pixmap))
(setf (attr-background-pixmap ,attr)
pixmap)
(xerror :Pixmap)))
(when-let ((pixmap (and border-pixmap?
(find-res border-pixmap))))
(if (eql 'pixmap
(type-of pixmap))
(setf (attr-border-pixmap ,attr)
pixmap)
(xerror :Pixmap)))
,@(mapcar #'(lambda (attribute)
`(when ,(symb attribute "?")
(setf (slot-value ,attr ',attribute)
,attribute)))
+attributes+)
(when (and colormap? ,window)
(event :ColormapNotify
:new t :window ,window :colormap colormap
:state (if (member colormap *installed-colormaps*)
:Installed
:Uninstalled)))