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 freebsd -I/usr/local/include
#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
[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 {