-
Notifications
You must be signed in to change notification settings - Fork 6
/
xcb-cursor.el
432 lines (400 loc) · 19.1 KB
/
xcb-cursor.el
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
;;; xcb-cursor.el --- Port of Xcursor -*- lexical-binding: t -*-
;; Copyright (C) 2015-2024 Free Software Foundation, Inc.
;; Author: Chris Feng <chris.w.feng@gmail.com>
;; This file is part of GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This library is a port of Xcursor in Xlib, and roughly corresponds to the
;; xcb/util-cursor project.
;; Usage tips:
;; + Do not forget to call `xcb:cursor:init' for _every_ connection using this
;; library.
;; + The only useful method in this library is `xcb:cursor:load-cursor', which
;; loads a cursor by its name (e.g. "left_ptr"), in the following order:
;; 1. themed cursor
;; 2. inherited themed cursor
;; 3. standard X cursor
;; Todo:
;; + Add legacy support for RENDER.
;; + Cursor should be set per screen (only the first is used right now).
;; + Move codes corresponding to xcb/util-renderutil or xcb/util-image
;; elsewhere.
;; References:
;; + Xcursor(3).
;; + xcb/util-cursor (git://anongit.freedesktop.org/xcb/util-cursor)
;; + xcb/util-renderutil (git://anongit.freedesktop.org/xcb/util-renderutil)
;; + xcb/util-image (git://anongit.freedesktop.org/xcb/util-image)
;;; Code:
(eval-when-compile (require 'cl-lib))
(require 'xcb)
(require 'xcb-render)
;; FIXME: check if resource manager really works
(cl-defmethod xcb:cursor:init ((obj xcb:connection))
"Initialize Xcursor for connection OBJ."
;; Initialize resource manager
(let* ((root (slot-value (car (slot-value (xcb:get-setup obj) 'roots))
'root))
(rm (xcb:+request-unchecked+reply obj
(make-instance 'xcb:GetProperty
:delete 0 :window root
:property xcb:Atom:RESOURCE_MANAGER
:type xcb:Atom:STRING
:long-offset 0
:long-length 16384))) ;FIXME: xcb/util-cursor
(rm (split-string
(decode-coding-string
(apply #'unibyte-string (append (slot-value rm 'value) nil))
'iso-latin-1)
"\n"))
theme size dpi)
(dolist (i rm)
(pcase (replace-regexp-in-string "^\\(\\S-+\\)" "\\1" i)
("Xcursor.theme"
(setq theme
(replace-regexp-in-string "^[^:]+:\\s-*\\(.+$\\)" "\\1" i)))
("Xcursor.size"
(setq size
(string-to-number
(replace-regexp-in-string "^[^:]+:\\s-*\\(.+$\\)" "\\1" i))))
("Xft.dpi"
(setq dpi
(string-to-number
(replace-regexp-in-string "^[^:]+:\\s-*\\(.+$\\)" "\\1" i))))))
;; Get cursor size from XCURSOR_SIZE environment variable
(let ((default-size (getenv "XCURSOR_SIZE")))
(when default-size
(setq default-size (string-to-number default-size)))
(setq size (or default-size size)))
;; Alternatives
(when (and (not size) dpi)
(setq size (/ (* dpi 16) 72))) ;FIXME: xcb/util-cursor
(unless size
(setq size
;; FIXME: xcb/util-cursor
(/ (min (x-display-pixel-width) (x-display-pixel-height)) 48)))
;; Save default values
(let ((plist (plist-get (slot-value obj 'extra-plist) 'cursor)))
(setq plist (plist-put plist 'theme theme)
plist (plist-put plist 'size size))
(setf (slot-value obj 'extra-plist)
(plist-put (slot-value obj 'extra-plist) 'cursor plist))))
;; Initialize render extension
(if (= 0 (slot-value (xcb:get-extension-data obj 'xcb:render) 'present))
(error "[XELB:CURSOR] Render extension is not supported by this server")
(with-slots (minor-version)
(xcb:+request-unchecked+reply obj
(make-instance 'xcb:render:QueryVersion
:client-major-version 0 :client-minor-version 8))
(if (> 8 minor-version)
(error "[XELB:CURSOR] Render version 0.8 is not supported")
(let* ((formats
(slot-value (xcb:+request-unchecked+reply obj
(make-instance 'xcb:render:QueryPictFormats))
'formats))
(format (catch 'break
(dolist (i formats)
(with-slots (type depth direct) i
(with-slots (red-shift red-mask
green-shift green-mask
blue-shift blue-mask
alpha-shift alpha-mask)
direct
;; FIXME: xcb/util-renderutil
(when (and (= type xcb:render:PictType:Direct)
(= depth 32)
(= red-shift 16) (= red-mask #xFF)
(= green-shift 8) (= green-mask #xFF)
(= blue-shift 0) (= blue-mask #xFF)
(= alpha-shift 24)
(= alpha-mask #xFF))
(throw 'break i)))))))
(plist (plist-get (slot-value obj 'extra-plist) 'cursor)))
(setf (slot-value obj 'extra-plist)
(plist-put (slot-value obj 'extra-plist) 'cursor
(plist-put plist 'pict-format format))))))))
(defsubst xcb:cursor:-get-path ()
"Return a list of cursor paths."
(let ((path (getenv "XCURSOR_PATH")))
(if path
(split-string path ":" t)
'("~/.icons"
"/usr/share/icons"
"/usr/share/pixmaps"
"/usr/X11R6/lib/X11/icons"))))
(defun xcb:cursor:-get-theme-inherits (file)
"Return the inherited themes in a index.theme file FILE."
(let ((lines (with-temp-buffer
(insert-file-contents file)
(split-string (buffer-string) "\n" t))))
(catch 'break
(dolist (line lines)
(when (string-match "^Inherits\\s-*=\\s-*" line)
(throw 'break
(split-string (replace-regexp-in-string "^[^=]+=\\(.*\\)$"
"\\1" line)
"[;, \t\n]+" t)))))))
(defsubst xcb:cursor:-shape->id (name)
"Return the standard Xcursor font for cursor named NAME."
;; Standard X cursor fonts are defined in Emacs
(intern-soft (concat "x-pointer-" (replace-regexp-in-string "_" "-" name))))
(defun xcb:cursor:-find-file (theme name &optional skip)
"Return the file for cursor named NAME in theme THEME, or nil if not found."
(catch 'return
;; Skip searched themes
(when (memq theme skip)
(throw 'return nil))
;; Give up when supplied "core" theme and a valid cursor name
(when (and (string= "core" theme) (xcb:cursor:-shape->id name))
(throw 'return nil))
(let ((path (xcb:cursor:-get-path))
file)
;; 1. try THEME/cursors/NAME in each cursor path
(dolist (i path)
(setq file (concat i "/" theme "/cursors/" name))
(when (file-readable-p file)
(throw 'return file)))
;; 2. try "Inherits=" key in "index.theme"
(dolist (i path)
(setq file (concat i "/" theme "/index.theme"))
(when (file-readable-p file)
(cl-pushnew theme skip)
;; try all inherited themes
(dolist (j (xcb:cursor:-get-theme-inherits file))
(setq file (xcb:cursor:-find-file j name skip))
(when file
(throw 'return file))
(cl-pushnew j skip)))))
nil))
(defconst xcb:cursor:-file-magic-lsb "Xcur"
"The magic number for little-endian Xcursor file.")
(defconst xcb:cursor:-file-magic-msb "rucX"
"The magic number for big-endian Xcursor file.")
(defclass xcb:cursor:-file-header (xcb:-struct)
((magic :type xcb:CARD32)
(header :type xcb:CARD32)
(version :type xcb:CARD32)
(ntoc :type xcb:CARD32)) ;redundant, required for calculating TOC bytes
:documentation "Xcursor file header.")
(defclass xcb:cursor:-file-header-toc (xcb:-struct)
((ntoc :type xcb:CARD32) ;redundant slot
(toc :type xcb:-ignore)
(toc~ :initform '(name toc type xcb:cursor:-file-toc
size (xcb:-fieldref 'ntoc))
:type xcb:-list))
:documentation "The TOC field in Xcursor file header.")
(defclass xcb:cursor:-file-toc (xcb:-struct)
((type :type xcb:CARD32)
(subtype :type xcb:CARD32)
(position :type xcb:CARD32))
:documentation "Xcursor file TOC entry.")
(defclass xcb:cursor:-file-chunk-header (xcb:-struct)
((header :type xcb:CARD32)
(type :type xcb:CARD32)
(subtype :type xcb:CARD32)
(version :type xcb:CARD32)
(width :type xcb:CARD32) ;redundant, required for calculating image bytes
(height :type xcb:CARD32)) ;redundant, required for calculating image bytes
:documentation "Xcursor file chunk header.")
(defconst xcb:cursor:-file-chunk-image-header 36
"Header value of image-type chunk in Xcursor file.")
(defconst xcb:cursor:-file-chunk-image-type 4294770690.
"Type of image-type chunk in Xcursor file.")
(defconst xcb:cursor:-file-chunk-image-version 1
"Version of image-type chunk in Xcursor file.")
(defclass xcb:cursor:-file-chunk-image (xcb:-struct)
((width :type xcb:CARD32) ;<= #x7FFF, redundant
(height :type xcb:CARD32) ;<= #x7FFF, redundant
(xhot :type xcb:CARD32) ;<= width
(yhot :type xcb:CARD32) ;<= height
(delay :type xcb:CARD32) ;in ms
(pixels :type xcb:-ignore)
(pixels~ :initform '(name pixels type xcb:CARD32
size (* (xcb:-fieldref 'width)
(xcb:-fieldref 'height)))
:type xcb:-list))
:documentation "Image-type chunk in Xcursor file.")
(cl-defmethod xcb:cursor:-parse-file ((obj xcb:connection) path)
"Parse an Xcursor file named PATH."
(catch 'return
(let ((data (let ((coding-system-for-read 'binary))
(with-temp-buffer
(set-buffer-multibyte nil)
(insert-file-contents path) (buffer-string))))
xcb:lsb ;override global byte order
best-size chunks
magic file-header file-header-toc chunk-header chunk)
;; Determine byte order
(setq magic (substring data 0 4))
(if (string= xcb:cursor:-file-magic-lsb magic)
(setq xcb:lsb t) ;LSB first
(if (string= xcb:cursor:-file-magic-msb magic)
(setq xcb:lsb nil) ;MSB first
(throw 'return nil)))
(setq file-header (make-instance 'xcb:cursor:-file-header))
;;
(xcb:unmarshal file-header (substring data 0 16))
;; FIXME: checks
(setq file-header-toc (make-instance 'xcb:cursor:-file-header-toc))
(xcb:unmarshal file-header-toc
(substring data 12 (+ 16 (* 12 (slot-value file-header
'ntoc)))))
(with-slots (toc) file-header-toc
(let ((target (plist-get
(plist-get (slot-value obj 'extra-plist) 'cursor)
'size)))
(catch 'break
(dolist (i toc)
(with-slots (type subtype) i
(when (= type xcb:cursor:-file-chunk-image-type)
(when (= target subtype)
(setq best-size target)
(throw 'break nil))
(when (or (not best-size)
(> (abs (- target best-size))
(abs (- target subtype))))
(setq best-size subtype)))))))
;; Collect chunks fitting this size
(setq chunk-header (make-instance 'xcb:cursor:-file-chunk-header))
(dolist (i toc)
(with-slots (type subtype position) i
(when (and (= type xcb:cursor:-file-chunk-image-type)
(= subtype best-size))
(xcb:unmarshal chunk-header (substring data position
(+ position 24)))
;; Validate the header of this chunk
(with-slots (header type subtype version) chunk-header
(when (or (/= header xcb:cursor:-file-chunk-image-header)
(/= type xcb:cursor:-file-chunk-image-type)
(/= subtype best-size)
(/= version xcb:cursor:-file-chunk-image-version))
(throw 'return nil)))
;; Parse this chunk
(setq chunk (make-instance 'xcb:cursor:-file-chunk-image))
(xcb:unmarshal chunk (substring data (+ position 16)
(+ position 36
(* 4
(slot-value chunk-header
'width)
(slot-value chunk-header
'height)))))
(setq chunks (nconc chunks (list chunk))))))
(list xcb:lsb chunks)))))
(cl-defmethod xcb:cursor:-load-cursor ((obj xcb:connection) file)
"Load a cursor file FILE."
(let* ((images (xcb:cursor:-parse-file obj file))
(lsb (car images))
(images (cadr images))
(root (slot-value (car (slot-value (xcb:get-setup obj) 'roots))
'root))
(picture (xcb:generate-id obj))
(pict-format (slot-value
(plist-get
(plist-get (slot-value obj 'extra-plist) 'cursor)
'pict-format)
'id))
pixmap gc cursors cursor last-width last-height)
(dolist (image images)
(with-slots (width height xhot yhot delay pixels) image
(when (or (not pixmap) (/= last-width width) (/= last-height height))
(if pixmap
(progn (xcb:+request obj (make-instance 'xcb:FreePixmap
:pixmap pixmap))
(xcb:+request obj (make-instance 'xcb:FreeGC :gc gc)))
(setq pixmap (xcb:generate-id obj)
gc (xcb:generate-id obj)))
(xcb:+request obj (make-instance 'xcb:CreatePixmap
:depth 32 :pid pixmap :drawable root
:width width :height height))
(xcb:+request obj (make-instance 'xcb:CreateGC
:cid gc :drawable pixmap
:value-mask 0))
(setq last-width width
last-height height))
(xcb:+request obj (make-instance 'xcb:PutImage
:format xcb:ImageFormat:ZPixmap
:drawable pixmap
:gc gc
:width width
:height height
:dst-x 0
:dst-y 0
:left-pad 0
:depth 32
:data (with-temp-buffer
(set-buffer-multibyte nil)
(mapconcat
(if lsb #'xcb:-pack-u4-lsb
#'xcb:-pack-u4)
pixels []))))
(xcb:+request obj (make-instance 'xcb:render:CreatePicture
:pid picture
:drawable pixmap
:format pict-format
:value-mask 0))
(setq cursor (xcb:generate-id obj)
cursors (nconc cursors
(list (make-instance 'xcb:render:ANIMCURSORELT
:cursor cursor
:delay delay))))
(xcb:+request obj (make-instance 'xcb:render:CreateCursor
:cid cursor
:source picture
:x xhot :y yhot))
(xcb:+request obj (make-instance 'xcb:render:FreePicture
:picture picture))))
(xcb:+request obj (make-instance 'xcb:FreePixmap :pixmap pixmap))
(xcb:+request obj (make-instance 'xcb:FreeGC :gc gc))
(xcb:flush obj)
(if (= 1 (length cursors))
;; Non-animated cursor
(slot-value (car cursors) 'cursor)
;; Animated cursor
(setq cursor (xcb:generate-id obj))
(xcb:+request obj (make-instance 'xcb:render:CreateAnimCursor
:cid cursor
:cursors (vconcat cursors)))
(dolist (i cursors)
(xcb:+request obj (make-instance 'xcb:FreeCursor
:cursor (slot-value i 'cursor))))
(xcb:flush obj)
cursor)))
(cl-defmethod xcb:cursor:load-cursor ((obj xcb:connection) name)
"Return a cursor whose name is NAME."
(let* ((theme (or (plist-get
(plist-get (slot-value obj 'extra-plist) 'cursor) 'theme)
"default"))
(file (xcb:cursor:-find-file theme name)))
(if file
(xcb:cursor:-load-cursor obj file)
;; Fallback to standard X cursors
(let ((pointer (xcb:cursor:-shape->id name))
(cursor xcb:Cursor:None)
font)
(when (boundp pointer)
(setq pointer (symbol-value pointer)
font (xcb:generate-id obj)
cursor (xcb:generate-id obj))
(xcb:+request obj
(make-instance 'xcb:OpenFont
:fid font :name-len (length "cursor")
:name "cursor"))
(xcb:+request obj
(make-instance 'xcb:CreateGlyphCursor
:cid cursor :source-font font :mask-font font
:source-char pointer :mask-char (1+ pointer)
:fore-red 0 :fore-green 0 :fore-blue 0
:back-red #xFFFF :back-green #xFFFF
:back-blue #xFFFF))
(xcb:flush obj))
cursor))))
(provide 'xcb-cursor)
;;; xcb-cursor.el ends here