diff options
Diffstat (limited to 'v_windows/v/vlib/clipboard/x11/clipboard.c.v')
-rw-r--r-- | v_windows/v/vlib/clipboard/x11/clipboard.c.v | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/v_windows/v/vlib/clipboard/x11/clipboard.c.v b/v_windows/v/vlib/clipboard/x11/clipboard.c.v new file mode 100644 index 0000000..d5a4656 --- /dev/null +++ b/v_windows/v/vlib/clipboard/x11/clipboard.c.v @@ -0,0 +1,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) +} |