clipboard: use a nicer error when X11/Xlib.h is missing

pull/8209/head
Delyan Angelov 2021-01-19 20:47:02 +02:00
parent 985ef52872
commit b3a4f746a2
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
1 changed files with 158 additions and 113 deletions
vlib/clipboard/x11

View File

@ -9,39 +9,60 @@ import math
#flag -lX11 #flag -lX11
#flag freebsd -I/usr/local/include #flag freebsd -I/usr/local/include
#flag freebsd -L/usr/local/lib -lX11 #flag freebsd -L/usr/local/lib -lX11
#include <X11/Xlib.h> #include <X11/Xlib.h> # Please install a package with the X11 development headers, for example: `apt-get install libx11-dev`
// X11 // X11
[typedef] [typedef]
struct C.Display struct C.Display {
}
[typedef] [typedef]
struct C.Atom struct C.Atom {
}
[typedef] [typedef]
struct C.Window struct C.Window {
}
fn C.XInitThreads() int fn C.XInitThreads() int
fn C.XCloseDisplay(d &Display) fn C.XCloseDisplay(d &Display)
fn C.XFlush(d &Display) fn C.XFlush(d &Display)
fn C.XDestroyWindow(d &Display, w C.Window) fn C.XDestroyWindow(d &Display, w C.Window)
fn C.XNextEvent(d C.Display, e &XEvent) fn C.XNextEvent(d C.Display, e &XEvent)
fn C.XSetSelectionOwner(d &Display, a C.Atom, w C.Window, time int) 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.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.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.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.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.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.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.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.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.XDeleteProperty(d &Display, w Window, property C.Atom) int
fn C.DefaultScreen() int fn C.DefaultScreen() int
fn C.RootWindow() voidptr fn C.RootWindow() voidptr
fn C.BlackPixel() voidptr fn C.BlackPixel() voidptr
fn C.WhitePixel() voidptr fn C.WhitePixel() voidptr
fn C.XFree() fn C.XFree()
fn todo_del() {} fn todo_del() {}
@ -49,7 +70,7 @@ fn todo_del(){}
[typedef] [typedef]
struct C.XSelectionRequestEvent { struct C.XSelectionRequestEvent {
mut: mut:
display &C.Display /* Display the event was read from */ display &C.Display // Display the event was read from
owner C.Window owner C.Window
requestor C.Window requestor C.Window
selection C.Atom selection C.Atom
@ -62,7 +83,7 @@ struct C.XSelectionRequestEvent{
struct C.XSelectionEvent { struct C.XSelectionEvent {
mut: mut:
@type int @type int
display &C.Display /* Display the event was read from */ display &C.Display // Display the event was read from
requestor C.Window requestor C.Window
selection C.Atom selection C.Atom
target C.Atom target C.Atom
@ -94,10 +115,12 @@ union C.XEvent{
} }
const ( 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 // all the atom types we need
// currently we only support text // currently we only support text
// in the future, maybe we can extend this // in the future, maybe we can extend this
@ -142,20 +165,22 @@ pub fn new_clipboard() &Clipboard {
// We can initialize multiple clipboard instances and use them separately // We can initialize multiple clipboard instances and use them separately
fn new_x11_clipboard(selection AtomType) &Clipboard { fn new_x11_clipboard(selection AtomType) &Clipboard {
if selection !in [.clipboard, .primary, .secondary] { 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() status := C.XInitThreads()
if status == 0 { 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() display := new_display()
if display == C.NULL { if display == C.NULL {
println("ERROR: No X Server running. Clipboard cannot be used.") println('ERROR: No X Server running. Clipboard cannot be used.')
return &Clipboard{ display: 0 mutex: sync.new_mutex() } return &Clipboard{
display: 0
mutex: sync.new_mutex()
}
} }
mut cb := &Clipboard{ mut cb := &Clipboard{
@ -187,7 +212,7 @@ fn (mut cb Clipboard) clear(){
C.XSetSelectionOwner(cb.display, cb.selection, C.Window(C.None), C.CurrentTime) C.XSetSelectionOwner(cb.display, cb.selection, C.Window(C.None), C.CurrentTime)
C.XFlush(cb.display) C.XFlush(cb.display)
cb.is_owner = false cb.is_owner = false
cb.text = "" cb.text = ''
cb.mutex.unlock() cb.mutex.unlock()
} }
@ -201,7 +226,9 @@ fn (cb &Clipboard) take_ownership(){
} }
pub fn (mut cb Clipboard) set_text(text string) bool { 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.mutex.m_lock()
cb.text = text cb.text = text
cb.is_owner = true 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 { 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 { if cb.is_owner {
return cb.text return cb.text
} }
cb.got_text = false cb.got_text = false
// Request a list of possible conversions, if we're pasting. // 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) 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 mut retries := 5
for { for {
if cb.got_text || retries == 0 {break} if cb.got_text || retries == 0 {
break
}
time.usleep(50000) time.usleep(50000)
retries-- retries--
} }
@ -238,10 +270,12 @@ fn (mut cb Clipboard) get_text() string {
fn (mut cb Clipboard) transmit_selection(xse &C.XSelectionEvent) bool { fn (mut cb Clipboard) transmit_selection(xse &C.XSelectionEvent) bool {
if xse.target == cb.get_atom(.targets) { if xse.target == cb.get_atom(.targets) {
targets := cb.get_supported_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) C.XChangeProperty(xse.display, xse.requestor, xse.property, cb.get_atom(.xa_atom),
} else if cb.is_supported_target(xse.target) && cb.is_owner && cb.text != "" { 32, C.PropModeReplace, targets.data, targets.len)
} else if cb.is_supported_target(xse.target) && cb.is_owner && cb.text != '' {
cb.mutex.m_lock() 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() cb.mutex.unlock()
} else { } else {
return false return false
@ -256,7 +290,7 @@ fn (mut cb Clipboard) start_listener(){
for { for {
C.XNextEvent(cb.display, &event) C.XNextEvent(cb.display, &event)
if unsafe { event.@type == 0 } { if unsafe { event.@type == 0 } {
println("error") println('error')
continue continue
} }
match unsafe { event.@type } { match unsafe { event.@type } {
@ -267,16 +301,20 @@ fn (mut cb Clipboard) start_listener(){
} }
} }
C.SelectionClear { 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.mutex.m_lock()
cb.is_owner = false cb.is_owner = false
cb.text = "" cb.text = ''
cb.mutex.unlock() cb.mutex.unlock()
} }
} }
C.SelectionRequest { C.SelectionRequest {
if unsafe { event.xselectionrequest.selection == cb.selection } { if unsafe { event.xselectionrequest.selection == cb.selection } {
mut xsre := &C.XSelectionRequestEvent{ display: 0 } mut xsre := &C.XSelectionRequestEvent{
display: 0
}
xsre = unsafe { &event.xselectionrequest } xsre = unsafe { &event.xselectionrequest }
mut xse := C.XSelectionEvent{ mut xse := C.XSelectionEvent{
@ -296,20 +334,25 @@ fn (mut cb Clipboard) start_listener(){
} }
} }
C.SelectionNotify { 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 } { if unsafe { event.xselection.target == cb.get_atom(.targets) && !sent_request } {
sent_request = true sent_request = true
prop := read_property(cb.display, cb.window, cb.selection) prop := read_property(cb.display, cb.window, cb.selection)
to_be_requested = cb.pick_target(prop) to_be_requested = cb.pick_target(prop)
if to_be_requested != C.Atom(0) { 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 } { } else if unsafe { event.xselection.target == to_be_requested } {
sent_request = false sent_request = false
to_be_requested = C.Atom(0) to_be_requested = C.Atom(0)
cb.mutex.m_lock() cb.mutex.m_lock()
prop := unsafe{ read_property(event.xselection.display, event.xselection.requestor, event.xselection.property) } prop := unsafe { read_property(event.xselection.display, event.xselection.requestor,
unsafe{ C.XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property) } event.xselection.property) }
unsafe { C.XDeleteProperty(event.xselection.display, event.xselection.requestor,
event.xselection.property) }
if cb.is_supported_target(prop.actual_type) { if cb.is_supported_target(prop.actual_type) {
cb.got_text = true cb.got_text = true
unsafe { unsafe {
@ -326,10 +369,7 @@ fn (mut cb Clipboard) start_listener(){
} }
} }
// Helpers // Helpers
// Initialize all the atoms we need // Initialize all the atoms we need
fn (mut cb Clipboard) intern_atoms() { fn (mut cb Clipboard) intern_atoms() {
cb.atoms << C.Atom(4) // XA_ATOM cb.atoms << C.Atom(4) // XA_ATOM
@ -354,9 +394,12 @@ fn read_property(d &C.Display, w C.Window, p C.Atom) Property {
if ret != 0 { if ret != 0 {
C.XFree(ret) 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 read_bytes *= 2
if bytes_after == 0 {break} if bytes_after == 0 {
break
}
} }
return Property{actual_type, actual_format, nitems, ret} return Property{actual_type, actual_format, nitems, ret}
} }
@ -365,16 +408,16 @@ fn read_property(d &C.Display, w C.Window, p C.Atom) Property {
fn (cb &Clipboard) pick_target(prop Property) C.Atom { 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 // The list of targets is a list of atoms, so it should have type XA_ATOM
// but it may have the type TARGETS instead. // 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 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 // This would be really broken. Targets have to be an atom list
// and applications should support this. Nevertheless, some // and applications should support this. Nevertheless, some
// seem broken (MATLAB 7, for instance), so ask for STRING // seem broken (MATLAB 7, for instance), so ask for STRING
// next instead as the lowest common denominator // next instead as the lowest common denominator
return cb.get_atom(.xa_string) return cb.get_atom(.xa_string)
} } else {
else
{
atom_list := &C.Atom(prop.data) atom_list := &C.Atom(prop.data)
mut to_be_requested := C.Atom(0) mut to_be_requested := C.Atom(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 { fn (cb &Clipboard) get_target_index(target C.Atom) int {
for i, atom in cb.get_supported_targets() { for i, atom in cb.get_supported_targets() {
if atom == target {return i} if atom == target {
return i
}
} }
return -1 return -1
} }
@ -432,8 +477,8 @@ fn new_atom(value int) &C.Atom {
fn create_xwindow(display &C.Display) C.Window { fn create_xwindow(display &C.Display) C.Window {
n := C.DefaultScreen(display) n := C.DefaultScreen(display)
return C.XCreateSimpleWindow(display, C.RootWindow(display, n), 0, 0, 1, 1, return C.XCreateSimpleWindow(display, C.RootWindow(display, n), 0, 0, 1, 1, 0, C.BlackPixel(display,
0, C.BlackPixel(display, n), C.WhitePixel(display, n)) n), C.WhitePixel(display, n))
} }
fn new_display() &C.Display { fn new_display() &C.Display {