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
|
// Currently there is only X11 Selections support and no way to handle Wayland
// but since Wayland isn't extremely adopted, we are covering almost all Linux distros.
module x11
import time
import sync
import math
$if freebsd {
#flag -I/usr/local/include
#flag -L/usr/local/lib
} $else $if openbsd {
#flag -I/usr/X11R6/include
#flag -L/usr/X11R6/lib
}
#flag -lX11
#include <X11/Xlib.h> # Please install a package with the X11 development headers, for example: `apt-get install libx11-dev`
// X11
[typedef]
struct C.Display {
}
type Window = u64
type Atom = u64
fn C.XInitThreads() int
fn C.XCloseDisplay(d &C.Display)
fn C.XFlush(d &C.Display)
fn C.XDestroyWindow(d &C.Display, w Window)
fn C.XNextEvent(d &C.Display, e &C.XEvent)
fn C.XSetSelectionOwner(d &C.Display, a Atom, w Window, time int)
fn C.XGetSelectionOwner(d &C.Display, a Atom) Window
fn C.XChangeProperty(d &C.Display, requestor Window, property Atom, typ Atom, format int, mode int, data voidptr, nelements int) int
fn C.XSendEvent(d &C.Display, requestor Window, propogate int, mask i64, event &C.XEvent)
fn C.XInternAtom(d &C.Display, typ &byte, only_if_exists int) Atom
fn C.XCreateSimpleWindow(d &C.Display, root Window, x int, y int, width u32, height u32, border_width u32, border u64, background u64) Window
fn C.XOpenDisplay(name &byte) &C.Display
fn C.XConvertSelection(d &C.Display, selection Atom, target Atom, property Atom, requestor Window, time int) int
fn C.XSync(d &C.Display, discard int) int
fn C.XGetWindowProperty(d &C.Display, w Window, property Atom, offset i64, length i64, delete int, req_type Atom, actual_type_return &Atom, actual_format_return &int, nitems &u64, bytes_after_return &u64, prop_return &&byte) int
fn C.XDeleteProperty(d &C.Display, w Window, property Atom) int
fn C.DefaultScreen(display &C.Display) int
fn C.RootWindow(display &C.Display, screen_number int) Window
fn C.BlackPixel(display &C.Display, screen_number int) u32
fn C.WhitePixel(display &C.Display, screen_number int) u32
fn C.XFree(data voidptr)
fn todo_del() {}
[typedef]
struct C.XSelectionRequestEvent {
mut:
display &C.Display // Display the event was read from
owner Window
requestor Window
selection Atom
target Atom
property Atom
time int
}
[typedef]
struct C.XSelectionEvent {
mut:
@type int
display &C.Display // Display the event was read from
requestor Window
selection Atom
target Atom
property Atom
time int
}
[typedef]
struct C.XSelectionClearEvent {
mut:
window Window
selection Atom
}
[typedef]
struct C.XDestroyWindowEvent {
mut:
window Window
}
[typedef]
union C.XEvent {
mut:
@type int
xdestroywindow C.XDestroyWindowEvent
xselectionclear C.XSelectionClearEvent
xselectionrequest C.XSelectionRequestEvent
xselection C.XSelectionEvent
}
const (
atom_names = ['TARGETS', 'CLIPBOARD', 'PRIMARY', 'SECONDARY', 'TEXT', 'UTF8_STRING', 'text/plain',
'text/html',
]
)
// UNSUPPORTED TYPES: MULTIPLE, INCR, TIMESTAMP, image/bmp, image/jpeg, image/tiff, image/png
// all the atom types we need
// currently we only support text
// in the future, maybe we can extend this
// to support other mime types
enum AtomType {
xa_atom = 0 // value 4
xa_string = 1 // value 31
targets = 2
clipboard = 3
primary = 4
secondary = 5
text = 6
utf8_string = 7
text_plain = 8
text_html = 9
}
pub struct Clipboard {
display &C.Display
mut:
selection Atom // the selection atom
window Window
atoms []Atom
mutex &sync.Mutex
text string // text data sent or received
got_text bool // used to confirm that we have got the text
is_owner bool // to save selection owner state
}
struct Property {
actual_type Atom
actual_format int
nitems u64
data &byte
}
// new_clipboard returns a new `Clipboard` instance allocated on the heap.
// The `Clipboard` resources can be released with `free()`
pub fn new_clipboard() &Clipboard {
return new_x11_clipboard(.clipboard)
}
// new_x11_clipboard initializes a new clipboard of the given selection type.
// Multiple clipboard instance types can be initialized and used separately.
fn new_x11_clipboard(selection AtomType) &Clipboard {
if selection !in [.clipboard, .primary, .secondary] {
panic('Wrong AtomType. Must be one of .primary, .secondary or .clipboard.')
}
// init x11 thread support
status := C.XInitThreads()
if status == 0 {
println('WARN: this system does not support threads; clipboard will cause the program to lock.')
}
display := new_display()
if display == C.NULL {
println('ERROR: No X Server running. Clipboard cannot be used.')
return &Clipboard{
display: 0
mutex: sync.new_mutex()
}
}
mut cb := &Clipboard{
display: display
window: create_xwindow(display)
mutex: sync.new_mutex()
}
cb.intern_atoms()
cb.selection = cb.get_atom(selection)
// start the listener on another thread or
// we will be locked and will have to hard exit
go cb.start_listener()
return cb
}
pub fn (cb &Clipboard) check_availability() bool {
return cb.display != C.NULL
}
pub fn (mut cb Clipboard) free() {
C.XDestroyWindow(cb.display, cb.window)
cb.window = Window(0)
// FIX ME: program hangs when closing display
// XCloseDisplay(cb.display)
}
pub fn (mut cb Clipboard) clear() {
cb.mutex.@lock()
C.XSetSelectionOwner(cb.display, cb.selection, Window(0), C.CurrentTime)
C.XFlush(cb.display)
cb.is_owner = false
cb.text = ''
cb.mutex.unlock()
}
pub fn (cb &Clipboard) has_ownership() bool {
return cb.is_owner
}
fn (cb &Clipboard) take_ownership() {
C.XSetSelectionOwner(cb.display, cb.selection, cb.window, C.CurrentTime)
C.XFlush(cb.display)
}
// set_text stores `text` in the system clipboard.
pub fn (mut cb Clipboard) set_text(text string) bool {
if cb.window == Window(0) {
return false
}
cb.mutex.@lock()
cb.text = text
cb.is_owner = true
cb.take_ownership()
C.XFlush(cb.display)
cb.mutex.unlock()
// sleep a little bit
time.sleep(1 * time.millisecond)
return cb.is_owner
}
pub fn (mut cb Clipboard) get_text() string {
if cb.window == Window(0) {
return ''
}
if cb.is_owner {
return cb.text
}
cb.got_text = false
// Request a list of possible conversions, if we're pasting.
C.XConvertSelection(cb.display, cb.selection, cb.get_atom(.targets), cb.selection,
cb.window, C.CurrentTime)
// wait for the text to arrive
mut retries := 5
for {
if cb.got_text || retries == 0 {
break
}
time.sleep(50 * time.millisecond)
retries--
}
return cb.text
}
// transmit_selection is crucial to handling all the different data types.
// If we ever support other mimetypes they should be handled here.
fn (mut cb Clipboard) transmit_selection(xse &C.XSelectionEvent) bool {
if xse.target == cb.get_atom(.targets) {
targets := cb.get_supported_targets()
C.XChangeProperty(xse.display, xse.requestor, xse.property, cb.get_atom(.xa_atom),
32, C.PropModeReplace, targets.data, targets.len)
} else if cb.is_supported_target(xse.target) && cb.is_owner && cb.text != '' {
cb.mutex.@lock()
C.XChangeProperty(xse.display, xse.requestor, xse.property, xse.target, 8, C.PropModeReplace,
cb.text.str, cb.text.len)
cb.mutex.unlock()
} else {
return false
}
return true
}
fn (mut cb Clipboard) start_listener() {
event := C.XEvent{}
mut sent_request := false
mut to_be_requested := Atom(0)
for {
C.XNextEvent(cb.display, &event)
if unsafe { event.@type == 0 } {
println('error')
continue
}
match unsafe { event.@type } {
C.DestroyNotify {
if unsafe { event.xdestroywindow.window == cb.window } {
// we are done
return
}
}
C.SelectionClear {
if unsafe { event.xselectionclear.window == cb.window } && unsafe {
event.xselectionclear.selection == cb.selection
} {
cb.mutex.@lock()
cb.is_owner = false
cb.text = ''
cb.mutex.unlock()
}
}
C.SelectionRequest {
if unsafe { event.xselectionrequest.selection == cb.selection } {
mut xsre := &C.XSelectionRequestEvent{
display: 0
}
xsre = unsafe { &event.xselectionrequest }
mut xse := C.XSelectionEvent{
@type: C.SelectionNotify // 31
display: xsre.display
requestor: xsre.requestor
selection: xsre.selection
time: xsre.time
target: xsre.target
property: xsre.property
}
if !cb.transmit_selection(&xse) {
xse.property = new_atom(0)
}
C.XSendEvent(cb.display, xse.requestor, 0, C.PropertyChangeMask, voidptr(&xse))
C.XFlush(cb.display)
}
}
C.SelectionNotify {
if unsafe {
event.xselection.selection == cb.selection
&& event.xselection.property != Atom(0)
} {
if unsafe { event.xselection.target == cb.get_atom(.targets) && !sent_request } {
sent_request = true
prop := read_property(cb.display, cb.window, cb.selection)
to_be_requested = cb.pick_target(prop)
if to_be_requested != Atom(0) {
C.XConvertSelection(cb.display, cb.selection, to_be_requested,
cb.selection, cb.window, C.CurrentTime)
}
} else if unsafe { event.xselection.target == to_be_requested } {
sent_request = false
to_be_requested = Atom(0)
cb.mutex.@lock()
prop := unsafe {
read_property(event.xselection.display, event.xselection.requestor,
event.xselection.property)
}
unsafe {
C.XDeleteProperty(event.xselection.display, event.xselection.requestor,
event.xselection.property)
}
if cb.is_supported_target(prop.actual_type) {
cb.got_text = true
unsafe {
cb.text = prop.data.vstring() // TODO: return byteptr to support other mimetypes
}
}
cb.mutex.unlock()
}
}
}
C.PropertyNotify {}
else {}
}
}
}
/*
* Helpers
*/
// intern_atoms initializes all the atoms we need.
fn (mut cb Clipboard) intern_atoms() {
cb.atoms << Atom(4) // XA_ATOM
cb.atoms << Atom(31) // XA_STRING
for i, name in x11.atom_names {
only_if_exists := if i == int(AtomType.utf8_string) { 1 } else { 0 }
cb.atoms << C.XInternAtom(cb.display, &char(name.str), only_if_exists)
if i == int(AtomType.utf8_string) && cb.atoms[i] == Atom(0) {
cb.atoms[i] = cb.get_atom(.xa_string)
}
}
}
fn read_property(d &C.Display, w Window, p Atom) Property {
actual_type := Atom(0)
actual_format := 0
nitems := u64(0)
bytes_after := u64(0)
ret := &byte(0)
mut read_bytes := 1024
for {
if ret != 0 {
C.XFree(ret)
}
C.XGetWindowProperty(d, w, p, 0, read_bytes, 0, 0, &actual_type, &actual_format,
&nitems, &bytes_after, &ret)
read_bytes *= 2
if bytes_after == 0 {
break
}
}
return Property{actual_type, actual_format, nitems, ret}
}
// pick_target finds the best target given a local copy of a property.
fn (cb &Clipboard) pick_target(prop Property) Atom {
// The list of targets is a list of atoms, so it should have type XA_ATOM
// but it may have the type TARGETS instead.
if (prop.actual_type != cb.get_atom(.xa_atom) && prop.actual_type != cb.get_atom(.targets))
|| prop.actual_format != 32 {
// This would be really broken. Targets have to be an atom list
// and applications should support this. Nevertheless, some
// seem broken (MATLAB 7, for instance), so ask for STRING
// next instead as the lowest common denominator
return cb.get_atom(.xa_string)
} else {
atom_list := &Atom(voidptr(prop.data))
mut to_be_requested := Atom(0)
// This is higher than the maximum priority.
mut priority := math.max_i32
for i in 0 .. prop.nitems {
// See if this data type is allowed and of higher priority (closer to zero)
// than the present one.
target := unsafe { atom_list[i] }
if cb.is_supported_target(target) {
index := cb.get_target_index(target)
if priority > index && index >= 0 {
priority = index
to_be_requested = target
}
}
}
return to_be_requested
}
}
fn (cb &Clipboard) get_atoms(types ...AtomType) []Atom {
mut atoms := []Atom{}
for typ in types {
atoms << cb.atoms[typ]
}
return atoms
}
fn (cb &Clipboard) get_atom(typ AtomType) Atom {
return cb.atoms[typ]
}
fn (cb &Clipboard) is_supported_target(target Atom) bool {
return cb.get_target_index(target) >= 0
}
fn (cb &Clipboard) get_target_index(target Atom) int {
for i, atom in cb.get_supported_targets() {
if atom == target {
return i
}
}
return -1
}
fn (cb &Clipboard) get_supported_targets() []Atom {
return cb.get_atoms(AtomType.utf8_string, .xa_string, .text, .text_plain, .text_html)
}
fn new_atom(value int) &Atom {
return unsafe { &Atom(&u64(u64(value))) }
}
fn create_xwindow(display &C.Display) Window {
n := C.DefaultScreen(display)
return C.XCreateSimpleWindow(display, C.RootWindow(display, n), 0, 0, 1, 1, 0, C.BlackPixel(display,
n), C.WhitePixel(display, n))
}
fn new_display() &C.Display {
return C.XOpenDisplay(C.NULL)
}
// new_primary returns a new X11 `PRIMARY` type `Clipboard` instance allocated on the heap.
// Please note: new_primary only works on X11 based systems.
pub fn new_primary() &Clipboard {
return new_x11_clipboard(.primary)
}
|