From b3a4f746a23600955f7ee46b4df81af7fd5a684a Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Tue, 19 Jan 2021 20:47:02 +0200 Subject: [PATCH] clipboard: use a nicer error when X11/Xlib.h is missing --- vlib/clipboard/x11/clipboard.c.v | 271 ++++++++++++++++++------------- 1 file changed, 158 insertions(+), 113 deletions(-) diff --git a/vlib/clipboard/x11/clipboard.c.v b/vlib/clipboard/x11/clipboard.c.v index 944510f993..50b2029056 100644 --- a/vlib/clipboard/x11/clipboard.c.v +++ b/vlib/clipboard/x11/clipboard.c.v @@ -9,102 +9,125 @@ import math #flag -lX11 #flag freebsd -I/usr/local/include #flag freebsd -L/usr/local/lib -lX11 -#include - +#include # Please install a package with the X11 development headers, for example: `apt-get install libx11-dev` // X11 [typedef] -struct C.Display +struct C.Display { +} [typedef] -struct C.Atom +struct C.Atom { +} [typedef] -struct C.Window +struct C.Window { +} fn C.XInitThreads() int + fn C.XCloseDisplay(d &Display) + fn C.XFlush(d &Display) + fn C.XDestroyWindow(d &Display, w C.Window) + fn C.XNextEvent(d C.Display, e &XEvent) + fn C.XSetSelectionOwner(d &Display, a C.Atom, w C.Window, time int) + fn C.XGetSelectionOwner(d &Display, a C.Atom) C.Window + fn C.XChangeProperty(d &Display, requestor C.Window, property C.Atom, typ C.Atom, format int, mode int, data voidptr, nelements int) int + fn C.XSendEvent(d &Display, requestor C.Window, propogate int, mask i64, event &XEvent) + fn C.XInternAtom(d &Display, typ byteptr, only_if_exists int) C.Atom -fn C.XCreateSimpleWindow(d &Display, root C.Window, x int, y int, width u32, height u32, border_width u32, - border u64, background u64) C.Window + +fn C.XCreateSimpleWindow(d &Display, root C.Window, x int, y int, width u32, height u32, border_width u32, border u64, background u64) C.Window + fn C.XOpenDisplay(name byteptr) &C.Display + fn C.XConvertSelection(d &Display, selection C.Atom, target C.Atom, property C.Atom, requestor Window, time int) int + fn C.XSync(d &Display, discard int) int + fn C.XGetWindowProperty(d &Display, w Window, property C.Atom, offset i64, length i64, delete int, req_type C.Atom, actual_type_return &C.Atom, actual_format_return &int, nitems &u64, bytes_after_return &u64, prop_return &byteptr) int + fn C.XDeleteProperty(d &Display, w Window, property C.Atom) int + fn C.DefaultScreen() int + fn C.RootWindow() voidptr + fn C.BlackPixel() voidptr + fn C.WhitePixel() voidptr + fn C.XFree() -fn todo_del(){} +fn todo_del() {} [typedef] -struct C.XSelectionRequestEvent{ - mut: - display &C.Display /* Display the event was read from */ - owner C.Window +struct C.XSelectionRequestEvent { +mut: + display &C.Display // Display the event was read from + owner C.Window requestor C.Window selection C.Atom - target C.Atom - property C.Atom - time int + target C.Atom + property C.Atom + time int } [typedef] -struct C.XSelectionEvent{ - mut: - @type int - display &C.Display /* Display the event was read from */ +struct C.XSelectionEvent { +mut: + @type int + display &C.Display // Display the event was read from requestor C.Window selection C.Atom - target C.Atom - property C.Atom - time int + target C.Atom + property C.Atom + time int } [typedef] -struct C.XSelectionClearEvent{ - mut: - window C.Window +struct C.XSelectionClearEvent { +mut: + window C.Window selection C.Atom } [typedef] struct C.XDestroyWindowEvent { - mut: +mut: window C.Window } [typedef] -union C.XEvent{ - mut: - @type int - xdestroywindow C.XDestroyWindowEvent - xselectionclear C.XSelectionClearEvent +union C.XEvent { +mut: + @type int + xdestroywindow C.XDestroyWindowEvent + xselectionclear C.XSelectionClearEvent xselectionrequest C.XSelectionRequestEvent - xselection C.XSelectionEvent + xselection C.XSelectionEvent } const ( - atom_names = ["TARGETS", "CLIPBOARD", "PRIMARY", "SECONDARY", "TEXT", "UTF8_STRING", "text/plain", "text/html"] + 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 +// 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 + xa_atom = 0 // value 4 + xa_string = 1 // value 31 targets = 2 clipboard = 3 primary = 4 @@ -117,21 +140,21 @@ enum AtomType { pub struct Clipboard { display &C.Display - mut: - selection C.Atom //the selection atom - window C.Window - atoms []C.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 +mut: + selection C.Atom // the selection atom + window C.Window + atoms []C.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 C.Atom +struct Property { + actual_type C.Atom actual_format int - nitems u64 - data byteptr + nitems u64 + data byteptr } pub fn new_clipboard() &Clipboard { @@ -142,20 +165,22 @@ pub fn new_clipboard() &Clipboard { // We can initialize multiple clipboard instances and use them 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.") + panic('Wrong AtomType. Must be one of .primary, .secondary or .clipboard.') } - - //init x11 thread support + // 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.") + 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() } + println('ERROR: No X Server running. Clipboard cannot be used.') + return &Clipboard{ + display: 0 + mutex: sync.new_mutex() + } } mut cb := &Clipboard{ @@ -178,16 +203,16 @@ fn (cb &Clipboard) check_availability() bool { fn (mut cb Clipboard) free() { C.XDestroyWindow(cb.display, cb.window) cb.window = C.Window(C.None) - //FIX ME: program hangs when closing display - //XCloseDisplay(cb.display) + // FIX ME: program hangs when closing display + // XCloseDisplay(cb.display) } -fn (mut cb Clipboard) clear(){ +fn (mut cb Clipboard) clear() { cb.mutex.m_lock() C.XSetSelectionOwner(cb.display, cb.selection, C.Window(C.None), C.CurrentTime) C.XFlush(cb.display) cb.is_owner = false - cb.text = "" + cb.text = '' cb.mutex.unlock() } @@ -195,13 +220,15 @@ fn (cb &Clipboard) has_ownership() bool { return cb.is_owner } -fn (cb &Clipboard) take_ownership(){ +fn (cb &Clipboard) take_ownership() { C.XSetSelectionOwner(cb.display, cb.selection, cb.window, C.CurrentTime) C.XFlush(cb.display) } pub fn (mut cb Clipboard) set_text(text string) bool { - if cb.window == C.Window(C.None) {return false} + if cb.window == C.Window(C.None) { + return false + } cb.mutex.m_lock() cb.text = text cb.is_owner = true @@ -214,19 +241,24 @@ pub fn (mut cb Clipboard) set_text(text string) bool { } fn (mut cb Clipboard) get_text() string { - if cb.window == C.Window(C.None) {return ""} + if cb.window == C.Window(C.None) { + 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) + // 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 + // wait for the text to arrive mut retries := 5 for { - if cb.got_text || retries == 0 {break} + if cb.got_text || retries == 0 { + break + } time.usleep(50000) retries-- } @@ -238,10 +270,12 @@ fn (mut cb Clipboard) get_text() string { 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 != "" { + 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.m_lock() - C.XChangeProperty(xse.display, xse.requestor, xse.property, xse.target, 8, C.PropModeReplace, cb.text.str, cb.text.len) + 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 @@ -249,34 +283,38 @@ fn (mut cb Clipboard) transmit_selection(xse &C.XSelectionEvent) bool { return true } -fn (mut cb Clipboard) start_listener(){ +fn (mut cb Clipboard) start_listener() { event := C.XEvent{} mut sent_request := false mut to_be_requested := C.Atom(0) for { C.XNextEvent(cb.display, &event) - if unsafe { event.@type == 0 } { - println("error") - continue - } - match unsafe {event.@type} { + 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 + return } } C.SelectionClear { - if unsafe { event.xselectionclear.window == cb.window } && unsafe { event.xselectionclear.selection == cb.selection } { + if unsafe { event.xselectionclear.window == cb.window } && unsafe { event.xselectionclear.selection == + cb.selection } + { cb.mutex.m_lock() cb.is_owner = false - cb.text = "" + cb.text = '' cb.mutex.unlock() } } C.SelectionRequest { - if unsafe { event.xselectionrequest.selection == cb.selection } { - mut xsre := &C.XSelectionRequestEvent{ display: 0 } + if unsafe { event.xselectionrequest.selection == cb.selection } { + mut xsre := &C.XSelectionRequestEvent{ + display: 0 + } xsre = unsafe { &event.xselectionrequest } mut xse := C.XSelectionEvent{ @@ -296,24 +334,29 @@ fn (mut cb Clipboard) start_listener(){ } } C.SelectionNotify { - if unsafe { event.xselection.selection == cb.selection && event.xselection.property != C.Atom(C.None) } { + if unsafe { event.xselection.selection == cb.selection && + event.xselection.property != C.Atom(C.None) } + { 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 != C.Atom(0) { - C.XConvertSelection(cb.display, cb.selection, to_be_requested, cb.selection, cb.window, C.CurrentTime) + 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 = C.Atom(0) cb.mutex.m_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) } + 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 = byteptr(prop.data).vstring() //TODO: return byteptr to support other mimetypes + cb.text = byteptr(prop.data).vstring() // TODO: return byteptr to support other mimetypes } } cb.mutex.unlock() @@ -326,16 +369,13 @@ fn (mut cb Clipboard) start_listener(){ } } - - // Helpers - // Initialize all the atoms we need -fn (mut cb Clipboard) intern_atoms(){ - cb.atoms << C.Atom(4) //XA_ATOM - cb.atoms << C.Atom(31) //XA_STRING - for i, name in atom_names{ - only_if_exists := if i == int(AtomType.utf8_string) {1} else {0} +fn (mut cb Clipboard) intern_atoms() { + cb.atoms << C.Atom(4) // XA_ATOM + cb.atoms << C.Atom(31) // XA_STRING + for i, name in atom_names { + only_if_exists := if i == int(AtomType.utf8_string) { 1 } else { 0 } cb.atoms << C.XInternAtom(cb.display, name.str, only_if_exists) if i == int(AtomType.utf8_string) && cb.atoms[i] == C.Atom(C.None) { cb.atoms[i] = cb.get_atom(.xa_string) @@ -354,39 +394,42 @@ fn read_property(d &C.Display, w C.Window, p C.Atom) Property { if ret != 0 { C.XFree(ret) } - C.XGetWindowProperty(d, w, p, 0, read_bytes, 0, C.AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &ret) + C.XGetWindowProperty(d, w, p, 0, read_bytes, 0, C.AnyPropertyType, &actual_type, + &actual_format, &nitems, &bytes_after, &ret) read_bytes *= 2 - if bytes_after == 0 {break} + if bytes_after == 0 { + break + } } return Property{actual_type, actual_format, nitems, ret} } // Finds the best target given a local copy of a property. fn (cb &Clipboard) pick_target(prop Property) C.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 + // 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 + // 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 - { + } else { atom_list := &C.Atom(prop.data) mut to_be_requested := C.Atom(0) - //This is higher than the maximum priority. + // 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. + 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] } + target := unsafe { atom_list[i] } if cb.is_supported_target(target) { index := cb.get_target_index(target) if priority > index && index >= 0 { @@ -417,7 +460,9 @@ fn (cb &Clipboard) is_supported_target(target C.Atom) bool { fn (cb &Clipboard) get_target_index(target C.Atom) int { for i, atom in cb.get_supported_targets() { - if atom == target {return i} + if atom == target { + return i + } } return -1 } @@ -427,13 +472,13 @@ fn (cb &Clipboard) get_supported_targets() []C.Atom { } fn new_atom(value int) &C.Atom { - return unsafe {&C.Atom(value)} + return unsafe { &C.Atom(value) } } fn create_xwindow(display &C.Display) C.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)) + 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 {