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

View File

@ -9,102 +9,125 @@ 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() {}
[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
target C.Atom target C.Atom
property C.Atom property C.Atom
time int time int
} }
[typedef] [typedef]
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
property C.Atom property C.Atom
time int time int
} }
[typedef] [typedef]
struct C.XSelectionClearEvent{ struct C.XSelectionClearEvent {
mut: mut:
window C.Window window C.Window
selection C.Atom selection C.Atom
} }
[typedef] [typedef]
struct C.XDestroyWindowEvent { struct C.XDestroyWindowEvent {
mut: mut:
window C.Window window C.Window
} }
[typedef] [typedef]
union C.XEvent{ union C.XEvent {
mut: mut:
@type int @type int
xdestroywindow C.XDestroyWindowEvent xdestroywindow C.XDestroyWindowEvent
xselectionclear C.XSelectionClearEvent xselectionclear C.XSelectionClearEvent
xselectionrequest C.XSelectionRequestEvent xselectionrequest C.XSelectionRequestEvent
xselection C.XSelectionEvent xselection C.XSelectionEvent
} }
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
// to support other mime types // to support other mime types
enum AtomType { enum AtomType {
xa_atom = 0 //value 4 xa_atom = 0 // value 4
xa_string = 1 //value 31 xa_string = 1 // value 31
targets = 2 targets = 2
clipboard = 3 clipboard = 3
primary = 4 primary = 4
@ -117,21 +140,21 @@ enum AtomType {
pub struct Clipboard { pub struct Clipboard {
display &C.Display display &C.Display
mut: mut:
selection C.Atom //the selection atom selection C.Atom // the selection atom
window C.Window window C.Window
atoms []C.Atom atoms []C.Atom
mutex &sync.Mutex mutex &sync.Mutex
text string // text data sent or received text string // text data sent or received
got_text bool // used to confirm that we have got the text got_text bool // used to confirm that we have got the text
is_owner bool // to save selection owner state is_owner bool // to save selection owner state
} }
struct Property{ struct Property {
actual_type C.Atom actual_type C.Atom
actual_format int actual_format int
nitems u64 nitems u64
data byteptr data byteptr
} }
pub fn new_clipboard() &Clipboard { pub fn new_clipboard() &Clipboard {
@ -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{
@ -178,16 +203,16 @@ fn (cb &Clipboard) check_availability() bool {
fn (mut cb Clipboard) free() { fn (mut cb Clipboard) free() {
C.XDestroyWindow(cb.display, cb.window) C.XDestroyWindow(cb.display, cb.window)
cb.window = C.Window(C.None) cb.window = C.Window(C.None)
//FIX ME: program hangs when closing display // FIX ME: program hangs when closing display
//XCloseDisplay(cb.display) // XCloseDisplay(cb.display)
} }
fn (mut cb Clipboard) clear(){ fn (mut cb Clipboard) clear() {
cb.mutex.m_lock() cb.mutex.m_lock()
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()
} }
@ -195,13 +220,15 @@ fn (cb &Clipboard) has_ownership() bool {
return cb.is_owner 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.XSetSelectionOwner(cb.display, cb.selection, cb.window, C.CurrentTime)
C.XFlush(cb.display) C.XFlush(cb.display)
} }
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
@ -249,34 +283,38 @@ fn (mut cb Clipboard) transmit_selection(xse &C.XSelectionEvent) bool {
return true return true
} }
fn (mut cb Clipboard) start_listener(){ fn (mut cb Clipboard) start_listener() {
event := C.XEvent{} event := C.XEvent{}
mut sent_request := false mut sent_request := false
mut to_be_requested := C.Atom(0) mut to_be_requested := C.Atom(0)
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 } {
C.DestroyNotify { C.DestroyNotify {
if unsafe { event.xdestroywindow.window == cb.window } { if unsafe { event.xdestroywindow.window == cb.window } {
// we are done // we are done
return return
} }
} }
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,24 +334,29 @@ 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 {
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() cb.mutex.unlock()
@ -326,16 +369,13 @@ 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
cb.atoms << C.Atom(31) //XA_STRING cb.atoms << C.Atom(31) // XA_STRING
for i, name in atom_names{ for i, name in atom_names {
only_if_exists := if i == int(AtomType.utf8_string) {1} else {0} only_if_exists := if i == int(AtomType.utf8_string) { 1 } else { 0 }
cb.atoms << C.XInternAtom(cb.display, name.str, only_if_exists) cb.atoms << C.XInternAtom(cb.display, name.str, only_if_exists)
if i == int(AtomType.utf8_string) && cb.atoms[i] == C.Atom(C.None) { if i == int(AtomType.utf8_string) && cb.atoms[i] == C.Atom(C.None) {
cb.atoms[i] = cb.get_atom(.xa_string) 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 { 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}
} }
// Finds the best target given a local copy of a property. // Finds the best target given a local copy of a 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)
//This is higher than the maximum priority. // This is higher than the maximum priority.
mut priority := math.max_i32 mut priority := math.max_i32
for i in 0..prop.nitems { for i in 0 .. prop.nitems {
//See if this data type is allowed and of higher priority (closer to zero) // See if this data type is allowed and of higher priority (closer to zero)
//than the present one. // than the present one.
target := unsafe{ atom_list[i] } target := unsafe { atom_list[i] }
if cb.is_supported_target(target) { if cb.is_supported_target(target) {
index := cb.get_target_index(target) index := cb.get_target_index(target)
if priority > index && index >= 0 { 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 { 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
} }
@ -427,13 +472,13 @@ fn (cb &Clipboard) get_supported_targets() []C.Atom {
} }
fn new_atom(value int) &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 { 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 {