diff options
author | Indrajith K L | 2022-12-03 17:00:20 +0530 |
---|---|---|
committer | Indrajith K L | 2022-12-03 17:00:20 +0530 |
commit | f5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch) | |
tree | 2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/vlib/clipboard | |
download | cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.tar.gz cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.tar.bz2 cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.zip |
Diffstat (limited to 'v_windows/v/vlib/clipboard')
-rw-r--r-- | v_windows/v/vlib/clipboard/clipboard.v | 37 | ||||
-rw-r--r-- | v_windows/v/vlib/clipboard/clipboard_android.c.v | 15 | ||||
-rw-r--r-- | v_windows/v/vlib/clipboard/clipboard_darwin.c.v | 70 | ||||
-rw-r--r-- | v_windows/v/vlib/clipboard/clipboard_darwin.m | 23 | ||||
-rw-r--r-- | v_windows/v/vlib/clipboard/clipboard_default.c.v | 15 | ||||
-rw-r--r-- | v_windows/v/vlib/clipboard/clipboard_solaris.c.v | 15 | ||||
-rw-r--r-- | v_windows/v/vlib/clipboard/clipboard_test.v | 29 | ||||
-rw-r--r-- | v_windows/v/vlib/clipboard/clipboard_windows.c.v | 186 | ||||
-rw-r--r-- | v_windows/v/vlib/clipboard/dummy/dummy_clipboard.v | 49 | ||||
-rw-r--r-- | v_windows/v/vlib/clipboard/x11/clipboard.c.v | 501 |
10 files changed, 940 insertions, 0 deletions
diff --git a/v_windows/v/vlib/clipboard/clipboard.v b/v_windows/v/vlib/clipboard/clipboard.v new file mode 100644 index 0000000..d82289b --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard.v @@ -0,0 +1,37 @@ +module clipboard + +// new returns a new `Clipboard` instance allocated on the heap. +// The `Clipboard` resources can be released with `free()` +pub fn new() &Clipboard { + return new_clipboard() +} + +// copy copies `text` into the clipboard. +pub fn (mut cb Clipboard) copy(text string) bool { + return cb.set_text(text) +} + +// paste returns current entry as a `string` from the clipboard. +pub fn (mut cb Clipboard) paste() string { + return cb.get_text() +} + +// clear_all clears the clipboard. +pub fn (mut cb Clipboard) clear_all() { + cb.clear() +} + +// destroy destroys the clipboard and free it's resources. +pub fn (mut cb Clipboard) destroy() { + cb.free() +} + +// check_ownership returns `true` if the `Clipboard` has the content ownership. +pub fn (cb Clipboard) check_ownership() bool { + return cb.has_ownership() +} + +// is_available returns `true` if the clipboard is available for use. +pub fn (cb &Clipboard) is_available() bool { + return cb.check_availability() +} diff --git a/v_windows/v/vlib/clipboard/clipboard_android.c.v b/v_windows/v/vlib/clipboard/clipboard_android.c.v new file mode 100644 index 0000000..eb1e6b8 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_android.c.v @@ -0,0 +1,15 @@ +module clipboard + +import clipboard.dummy + +pub type Clipboard = dummy.Clipboard + +fn new_clipboard() &Clipboard { + return dummy.new_clipboard() +} + +// 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 dummy.new_primary() +} diff --git a/v_windows/v/vlib/clipboard/clipboard_darwin.c.v b/v_windows/v/vlib/clipboard/clipboard_darwin.c.v new file mode 100644 index 0000000..13c9ee8 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_darwin.c.v @@ -0,0 +1,70 @@ +module clipboard + +#include <libkern/OSAtomic.h> +#include <Cocoa/Cocoa.h> +#flag -framework Cocoa +#include "@VEXEROOT/vlib/clipboard/clipboard_darwin.m" + +pub struct Clipboard { + pb voidptr + last_cb_serial i64 +mut: + foo int // TODO remove, for mut hack +} + +fn C.darwin_new_pasteboard() voidptr + +fn C.darwin_get_pasteboard_text(voidptr) &byte + +fn C.darwin_set_pasteboard_text(voidptr, string) bool + +fn new_clipboard() &Clipboard { + cb := &Clipboard{ + pb: C.darwin_new_pasteboard() // pb + } + return cb +} + +pub fn (cb &Clipboard) check_availability() bool { + return cb.pb != C.NULL +} + +pub fn (mut cb Clipboard) clear() { + cb.foo = 0 + cb.set_text('') + //#[cb->pb clearContents]; +} + +pub fn (mut cb Clipboard) free() { + cb.foo = 0 + // nothing to free +} + +pub fn (cb &Clipboard) has_ownership() bool { + if cb.last_cb_serial == 0 { + return false + } + //#return [cb->pb changeCount] == cb->last_cb_serial; + return false +} + +fn C.OSAtomicCompareAndSwapLong() + +pub fn (mut cb Clipboard) set_text(text string) bool { + return C.darwin_set_pasteboard_text(cb.pb, text) +} + +pub fn (mut cb Clipboard) get_text() string { + cb.foo = 0 + if isnil(cb.pb) { + return '' + } + utf8_clip := C.darwin_get_pasteboard_text(cb.pb) + return unsafe { tos_clone(&byte(utf8_clip)) } +} + +// 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 { + panic('Primary clipboard is not supported on non-Linux systems.') +} diff --git a/v_windows/v/vlib/clipboard/clipboard_darwin.m b/v_windows/v/vlib/clipboard/clipboard_darwin.m new file mode 100644 index 0000000..cabc744 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_darwin.m @@ -0,0 +1,23 @@ +//NSPasteboard* darwin_new_pasteboard() { +void* darwin_new_pasteboard() { + return (__bridge void*) [NSPasteboard generalPasteboard]; +} + +char* darwin_get_pasteboard_text(void* pb) { + NSString *ns_clip = [((__bridge NSPasteboard*)pb) stringForType:NSStringPboardType]; //NSPasteboardTypeString + if (ns_clip == nil) { + return ""; + } + return [ns_clip UTF8String]; +} + +bool darwin_set_pasteboard_text(void* _pb, string text) { + NSPasteboard* pb = (__bridge NSPasteboard*) _pb; + NSString *ns_clip = [[ NSString alloc ] initWithBytesNoCopy:text.str length:text.len encoding:NSUTF8StringEncoding freeWhenDone: false]; + [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; + bool ret = [pb setString:ns_clip forType:NSStringPboardType]; + //[ns_clip release]; + int serial = [pb changeCount]; + //OSAtomicCompareAndSwapLong(cb.last_cb_serial, serial, &cb.last_cb_serial); + return ret; +} diff --git a/v_windows/v/vlib/clipboard/clipboard_default.c.v b/v_windows/v/vlib/clipboard/clipboard_default.c.v new file mode 100644 index 0000000..d8f65ce --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_default.c.v @@ -0,0 +1,15 @@ +module clipboard + +import clipboard.x11 + +pub type Clipboard = x11.Clipboard + +fn new_clipboard() &Clipboard { + return x11.new_clipboard() +} + +// 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 x11.new_primary() +} diff --git a/v_windows/v/vlib/clipboard/clipboard_solaris.c.v b/v_windows/v/vlib/clipboard/clipboard_solaris.c.v new file mode 100644 index 0000000..eb1e6b8 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_solaris.c.v @@ -0,0 +1,15 @@ +module clipboard + +import clipboard.dummy + +pub type Clipboard = dummy.Clipboard + +fn new_clipboard() &Clipboard { + return dummy.new_clipboard() +} + +// 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 dummy.new_primary() +} diff --git a/v_windows/v/vlib/clipboard/clipboard_test.v b/v_windows/v/vlib/clipboard/clipboard_test.v new file mode 100644 index 0000000..6c97f44 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_test.v @@ -0,0 +1,29 @@ +import clipboard + +fn run_test(is_primary bool) { + mut cb := if is_primary { clipboard.new_primary() } else { clipboard.new() } + if !cb.is_available() { + return + } + assert cb.check_ownership() == false + assert cb.copy('I am a good boy!') == true + // assert cb.check_ownership() == true TODO + assert cb.paste() == 'I am a good boy!' + cb.clear_all() + assert cb.paste().len <= 0 + cb.destroy() +} + +fn test_primary() { + $if linux || freebsd { + // run_test(true) + return + } +} + +fn test_clipboard() { + $if linux || freebsd { + return + } + run_test(false) +} diff --git a/v_windows/v/vlib/clipboard/clipboard_windows.c.v b/v_windows/v/vlib/clipboard/clipboard_windows.c.v new file mode 100644 index 0000000..9ae42d1 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_windows.c.v @@ -0,0 +1,186 @@ +module clipboard + +import time + +#include <windows.h> +#flag -luser32 + +struct WndClassEx { + cb_size u32 + style u32 + lpfn_wnd_proc voidptr + cb_cls_extra int + cb_wnd_extra int + h_instance C.HINSTANCE + h_icon C.HICON + h_cursor C.HCURSOR + hbr_background C.HBRUSH + lpsz_menu_name &u16 // LPCWSTR + lpsz_class_name &u16 + h_icon_sm &u16 +} + +fn C.RegisterClassEx(class &WndClassEx) int + +fn C.GetClipboardOwner() &C.HWND + +fn C.CreateWindowEx(dwExStyle i64, lpClassName &u16, lpWindowName &u16, dwStyle i64, x int, y int, nWidth int, nHeight int, hWndParent i64, hMenu voidptr, h_instance voidptr, lpParam voidptr) &C.HWND + +// fn C.MultiByteToWideChar(CodePage u32, dw_flags u16, lpMultiByteStr byteptr, cbMultiByte int, lpWideCharStr u16, cchWideChar int) int +fn C.EmptyClipboard() + +fn C.CloseClipboard() + +fn C.GlobalAlloc(uFlag u32, size i64) C.HGLOBAL + +fn C.GlobalFree(buf C.HGLOBAL) + +fn C.GlobalLock(buf C.HGLOBAL) voidptr + +fn C.GlobalUnlock(buf C.HGLOBAL) bool + +fn C.SetClipboardData(uFormat u32, data voidptr) C.HANDLE + +fn C.GetClipboardData(uFormat u32) C.HANDLE + +fn C.DefWindowProc(hwnd C.HWND, msg u32, wParam C.WPARAM, lParam C.LPARAM) C.LRESULT + +fn C.SetLastError(error i64) + +fn C.OpenClipboard(hwnd C.HWND) int + +fn C.DestroyWindow(hwnd C.HWND) + +struct Clipboard { + max_retries int + retry_delay int +mut: + hwnd C.HWND + foo int // TODO remove +} + +fn (cb &Clipboard) get_clipboard_lock() bool { + mut retries := cb.max_retries + mut last_error := u32(0) + for { + retries-- + if retries < 0 { + break + } + last_error = C.GetLastError() + if C.OpenClipboard(cb.hwnd) > 0 { + return true + } else if last_error != u32(C.ERROR_ACCESS_DENIED) { + return false + } + time.sleep(cb.retry_delay * time.second) + } + C.SetLastError(last_error) + return false +} + +fn new_clipboard() &Clipboard { + mut cb := &Clipboard{ + max_retries: 5 + retry_delay: 5 + } + class_name := 'clipboard' + wndclass := WndClassEx{ + cb_size: sizeof(WndClassEx) + lpfn_wnd_proc: voidptr(&C.DefWindowProc) + lpsz_class_name: class_name.to_wide() + lpsz_menu_name: 0 + h_icon_sm: 0 + } + if C.RegisterClassEx(&wndclass) == 0 && C.GetLastError() != u32(C.ERROR_CLASS_ALREADY_EXISTS) { + println('Failed registering class.') + } + hwnd := C.CreateWindowEx(0, wndclass.lpsz_class_name, wndclass.lpsz_class_name, 0, + 0, 0, 0, 0, C.HWND_MESSAGE, C.NULL, C.NULL, C.NULL) + if hwnd == C.NULL { + println('Error creating window!') + } + cb.hwnd = hwnd + return cb +} + +pub fn (cb &Clipboard) check_availability() bool { + return cb.hwnd != C.HWND(C.NULL) +} + +pub fn (cb &Clipboard) has_ownership() bool { + return C.GetClipboardOwner() == cb.hwnd +} + +pub fn (mut cb Clipboard) clear() { + if !cb.get_clipboard_lock() { + return + } + C.EmptyClipboard() + C.CloseClipboard() + cb.foo = 0 +} + +pub fn (mut cb Clipboard) free() { + C.DestroyWindow(cb.hwnd) + cb.foo = 0 +} + +// the string.to_wide doesn't work with SetClipboardData, don't know why +fn to_wide(text string) C.HGLOBAL { + len_required := C.MultiByteToWideChar(C.CP_UTF8, C.MB_ERR_INVALID_CHARS, text.str, + text.len + 1, C.NULL, 0) + buf := C.GlobalAlloc(C.GMEM_MOVEABLE, i64(sizeof(u16)) * len_required) + if buf != C.HGLOBAL(C.NULL) { + mut locked := &u16(C.GlobalLock(buf)) + C.MultiByteToWideChar(C.CP_UTF8, C.MB_ERR_INVALID_CHARS, text.str, text.len + 1, + locked, len_required) + unsafe { + locked[len_required - 1] = u16(0) + } + C.GlobalUnlock(buf) + } + return buf +} + +pub fn (mut cb Clipboard) set_text(text string) bool { + cb.foo = 0 + buf := to_wide(text) + if !cb.get_clipboard_lock() { + C.GlobalFree(buf) + return false + } else { + // EmptyClipboard must be called to properly update clipboard ownership + C.EmptyClipboard() + if C.SetClipboardData(C.CF_UNICODETEXT, buf) == C.HANDLE(C.NULL) { + println('SetClipboardData: Failed.') + C.CloseClipboard() + C.GlobalFree(buf) + return false + } + } + // CloseClipboard appears to change the sequence number... + C.CloseClipboard() + return true +} + +pub fn (mut cb Clipboard) get_text() string { + cb.foo = 0 + if !cb.get_clipboard_lock() { + return '' + } + h_data := C.GetClipboardData(C.CF_UNICODETEXT) + if h_data == C.HANDLE(C.NULL) { + C.CloseClipboard() + return '' + } + str := unsafe { string_from_wide(&u16(C.GlobalLock(C.HGLOBAL(h_data)))) } + C.GlobalUnlock(C.HGLOBAL(h_data)) + return str +} + +// 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 { + panic('Primary clipboard is not supported on non-Linux systems.') +} diff --git a/v_windows/v/vlib/clipboard/dummy/dummy_clipboard.v b/v_windows/v/vlib/clipboard/dummy/dummy_clipboard.v new file mode 100644 index 0000000..a3f4f35 --- /dev/null +++ b/v_windows/v/vlib/clipboard/dummy/dummy_clipboard.v @@ -0,0 +1,49 @@ +module dummy + +pub struct Clipboard { +mut: + 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 +} + +// 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 &Clipboard{} +} + +// 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 &Clipboard{} +} + +pub fn (mut cb Clipboard) set_text(text string) bool { + cb.text = text + cb.is_owner = true + cb.got_text = true + return true +} + +pub fn (mut cb Clipboard) get_text() string { + return cb.text +} + +pub fn (mut cb Clipboard) clear() { + cb.text = '' + cb.is_owner = false +} + +pub fn (mut cb Clipboard) free() { +} + +pub fn (cb &Clipboard) has_ownership() bool { + return cb.is_owner +} + +pub fn (cb &Clipboard) check_availability() bool { + // This is a dummy clipboard implementation, + // which can be always used, although it does not do much... + return true +} 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) +} |