Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,16 @@
|
||||
## Description
|
||||
|
||||
`clipboard` provides access to the platform's clipboard mechanism.
|
||||
You can use it to read from the system clipboard, and write to it
|
||||
from your applications.
|
||||
|
||||
## Examples
|
||||
|
||||
```v
|
||||
import clipboard
|
||||
|
||||
fn main() {
|
||||
mut c := clipboard.new()
|
||||
println(c.get_text())
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
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 frees its resources.
|
||||
pub fn (mut cb Clipboard) destroy() {
|
||||
unsafe {
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
module clipboard
|
||||
|
||||
import clipboard.dummy
|
||||
|
||||
// Clipboard represents a system clipboard.
|
||||
//
|
||||
// System "copy" and "paste" actions utilize the clipboard for temporary storage.
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
module clipboard
|
||||
|
||||
#include <libkern/OSAtomic.h>
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#flag -framework Cocoa
|
||||
#include "@VEXEROOT/vlib/clipboard/clipboard_darwin.m"
|
||||
|
||||
// Clipboard represents a system clipboard.
|
||||
//
|
||||
// System "copy" and "paste" actions utilize the clipboard for temporary storage.
|
||||
@[heap]
|
||||
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) &u8
|
||||
|
||||
fn C.darwin_set_pasteboard_text(voidptr, string) bool
|
||||
|
||||
fn new_clipboard() &Clipboard {
|
||||
cb := &Clipboard{
|
||||
pb: C.darwin_new_pasteboard() // pb
|
||||
}
|
||||
return cb
|
||||
}
|
||||
|
||||
// check_availability returns true if the clipboard is ready to be used.
|
||||
pub fn (cb &Clipboard) check_availability() bool {
|
||||
return cb.pb != C.NULL
|
||||
}
|
||||
|
||||
// clear empties the clipboard contents.
|
||||
pub fn (mut cb Clipboard) clear() {
|
||||
cb.foo = 0
|
||||
cb.set_text('')
|
||||
//#[cb->pb clearContents];
|
||||
}
|
||||
|
||||
// free releases all memory associated with the clipboard instance.
|
||||
pub fn (mut cb Clipboard) free() {
|
||||
cb.foo = 0
|
||||
// nothing to free
|
||||
}
|
||||
|
||||
// has_ownership returns true if the contents of the clipboard were created by this clipboard instance.
|
||||
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()
|
||||
|
||||
// set_text transfers `text` to the system clipboard.
|
||||
// This is often associated with a *copy* action (`Cmd` + `C`).
|
||||
pub fn (mut cb Clipboard) set_text(text string) bool {
|
||||
return C.darwin_set_pasteboard_text(cb.pb, text)
|
||||
}
|
||||
|
||||
// get_text retrieves the contents of the system clipboard.
|
||||
// This is often associated with a *paste* action (`Cmd` + `V`).
|
||||
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(&u8(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.')
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
module clipboard
|
||||
|
||||
import clipboard.x11
|
||||
|
||||
// Clipboard represents a system clipboard.
|
||||
//
|
||||
// System "copy" and "paste" actions utilize the clipboard for temporary storage.
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
module clipboard
|
||||
|
||||
import clipboard.dummy
|
||||
|
||||
// Clipboard represents a system clipboard.
|
||||
//
|
||||
// System "copy" and "paste" actions utilize the clipboard for temporary storage.
|
||||
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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
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 = unsafe { nil } // LPCWSTR
|
||||
lpsz_class_name &u16 = unsafe { nil }
|
||||
h_icon_sm &u16 = unsafe { nil }
|
||||
}
|
||||
|
||||
fn C.RegisterClassEx(class &WndClassEx) i32
|
||||
|
||||
fn C.GetClipboardOwner() C.HWND
|
||||
|
||||
fn C.CreateWindowEx(dwExStyle i64, lpClassName &u16, lpWindowName &u16, dwStyle i64, x i32, y i32, nWidth i32,
|
||||
nHeight i32, 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) i32
|
||||
|
||||
fn C.DestroyWindow(hwnd C.HWND)
|
||||
|
||||
// Clipboard represents a system clipboard.
|
||||
//
|
||||
// System "copy" and "paste" actions utilize the clipboard for temporary storage.
|
||||
@[heap]
|
||||
pub struct Clipboard {
|
||||
max_retries int
|
||||
retry_delay int
|
||||
mut:
|
||||
hwnd voidptr
|
||||
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
|
||||
}
|
||||
if C.OpenClipboard(cb.hwnd) > 0 {
|
||||
return true
|
||||
}
|
||||
last_error = C.GetLastError()
|
||||
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: unsafe { 0 }
|
||||
h_icon_sm: unsafe { 0 }
|
||||
}
|
||||
if C.RegisterClassEx(voidptr(&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 == unsafe { nil } {
|
||||
println('Error creating window!')
|
||||
}
|
||||
cb.hwnd = voidptr(hwnd)
|
||||
return cb
|
||||
}
|
||||
|
||||
// check_availability returns true if the clipboard is ready to be used.
|
||||
pub fn (cb &Clipboard) check_availability() bool {
|
||||
return cb.hwnd != unsafe { nil }
|
||||
}
|
||||
|
||||
// has_ownership returns true if the contents of the clipboard were created by this clipboard instance.
|
||||
pub fn (cb &Clipboard) has_ownership() bool {
|
||||
return voidptr(C.GetClipboardOwner()) == cb.hwnd
|
||||
}
|
||||
|
||||
// clear empties the clipboard contents.
|
||||
pub fn (mut cb Clipboard) clear() {
|
||||
if !cb.get_clipboard_lock() {
|
||||
return
|
||||
}
|
||||
C.EmptyClipboard()
|
||||
C.CloseClipboard()
|
||||
cb.foo = 0
|
||||
}
|
||||
|
||||
// free releases all memory associated with the clipboard instance.
|
||||
pub fn (mut cb Clipboard) free() {
|
||||
C.DestroyWindow(cb.hwnd)
|
||||
cb.foo = 0
|
||||
}
|
||||
|
||||
const cp_utf8 = 65001
|
||||
|
||||
// the string.to_wide doesn't work with SetClipboardData, don't know why
|
||||
fn to_wide(text string) C.HGLOBAL {
|
||||
len_required := C.MultiByteToWideChar(cp_utf8, C.MB_ERR_INVALID_CHARS, voidptr(text.str),
|
||||
|
||||
text.len + 1, C.NULL, 0)
|
||||
buf := C.GlobalAlloc(C.GMEM_MOVEABLE, i64(sizeof(u16)) * len_required)
|
||||
if buf != unsafe { nil } {
|
||||
mut locked := &u16(C.GlobalLock(buf))
|
||||
C.MultiByteToWideChar(cp_utf8, C.MB_ERR_INVALID_CHARS, voidptr(text.str), text.len + 1,
|
||||
locked, len_required)
|
||||
unsafe {
|
||||
locked[len_required - 1] = u16(0)
|
||||
}
|
||||
C.GlobalUnlock(buf)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// set_text transfers `text` to the system clipboard.
|
||||
// This is often associated with a *copy* action (`Ctrl` + `C`).
|
||||
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) == unsafe { nil } {
|
||||
println('SetClipboardData: Failed.')
|
||||
C.CloseClipboard()
|
||||
C.GlobalFree(buf)
|
||||
return false
|
||||
}
|
||||
}
|
||||
// CloseClipboard appears to change the sequence number...
|
||||
C.CloseClipboard()
|
||||
return true
|
||||
}
|
||||
|
||||
// get_text retrieves the contents of the system clipboard.
|
||||
// This is often associated with a *paste* action (`Ctrl` + `V`).
|
||||
pub fn (mut cb Clipboard) get_text() string {
|
||||
cb.foo = 0
|
||||
if !cb.get_clipboard_lock() {
|
||||
return ''
|
||||
}
|
||||
defer {
|
||||
C.CloseClipboard()
|
||||
}
|
||||
h_data := C.GetClipboardData(C.CF_UNICODETEXT)
|
||||
if h_data == unsafe { nil } {
|
||||
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.')
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
module dummy
|
||||
|
||||
// Clipboard represents a system clipboard.
|
||||
//
|
||||
// System "copy" and "paste" actions utilize the clipboard for temporary storage.
|
||||
@[heap]
|
||||
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{}
|
||||
}
|
||||
|
||||
// set_text transfers `text` to the system clipboard.
|
||||
// This is often associated with a *copy* action (`Ctrl` + `C`).
|
||||
pub fn (mut cb Clipboard) set_text(text string) bool {
|
||||
cb.text = text
|
||||
cb.is_owner = true
|
||||
cb.got_text = true
|
||||
return true
|
||||
}
|
||||
|
||||
// get_text retrieves the contents of the system clipboard.
|
||||
// This is often associated with a *paste* action (`Ctrl` + `V`).
|
||||
pub fn (mut cb Clipboard) get_text() string {
|
||||
return cb.text
|
||||
}
|
||||
|
||||
// clear empties the clipboard contents.
|
||||
pub fn (mut cb Clipboard) clear() {
|
||||
cb.text = ''
|
||||
cb.is_owner = false
|
||||
}
|
||||
|
||||
// free releases all memory associated with the clipboard instance.
|
||||
pub fn (mut cb Clipboard) free() {
|
||||
}
|
||||
|
||||
// has_ownership returns true if the contents of the clipboard were created by this clipboard instance.
|
||||
pub fn (cb &Clipboard) has_ownership() bool {
|
||||
return cb.is_owner
|
||||
}
|
||||
|
||||
// check_availability returns true if the clipboard is ready to be used.
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
// 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
|
||||
|
||||
$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 headers BEFORE any type definitions to avoid incomplete type errors
|
||||
#preinclude <X11/Xlib.h>
|
||||
#preinclude <X11/Xatom.h>
|
||||
#preinclude <X11/Xutil.h>
|
||||
|
||||
// X11 types are defined by X11 headers
|
||||
// For V check mode, we need to define C.Display
|
||||
@[typedef]
|
||||
pub struct C.Display {
|
||||
pub mut:
|
||||
_ int // opaque - actual definition from X11 headers
|
||||
}
|
||||
|
||||
// Local type aliases for convenience
|
||||
type Window = u64
|
||||
type Atom = u64
|
||||
|
||||
// Forward declarations for V type checking (actual types from X11 headers)
|
||||
@[typedef]
|
||||
pub struct C.XSelectionEvent {
|
||||
pub mut:
|
||||
type int
|
||||
display voidptr
|
||||
requestor Window
|
||||
selection Atom
|
||||
target Atom
|
||||
property Atom
|
||||
time int
|
||||
}
|
||||
|
||||
@[typedef]
|
||||
pub struct C.XSelectionClearEvent {
|
||||
pub mut:
|
||||
window Window
|
||||
selection Atom
|
||||
}
|
||||
|
||||
@[typedef]
|
||||
pub struct C.XSelectionRequestEvent {
|
||||
pub mut:
|
||||
display voidptr
|
||||
owner Window
|
||||
requestor Window
|
||||
selection Atom
|
||||
target Atom
|
||||
property Atom
|
||||
time int
|
||||
}
|
||||
|
||||
@[typedef]
|
||||
pub struct C.XDestroyWindowEvent {
|
||||
pub mut:
|
||||
window Window
|
||||
}
|
||||
|
||||
fn C.XInitThreads() i32
|
||||
|
||||
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 i32)
|
||||
|
||||
fn C.XGetSelectionOwner(d &C.Display, a Atom) Window
|
||||
|
||||
fn C.XChangeProperty(d &C.Display, requestor Window, property Atom, typ Atom, format i32, mode i32, data voidptr,
|
||||
nelements i32) i32
|
||||
|
||||
fn C.XSendEvent(d &C.Display, requestor Window, propagate i32, mask i64, event &C.XEvent)
|
||||
|
||||
fn C.XInternAtom(d &C.Display, typ &u8, only_if_exists i32) Atom
|
||||
|
||||
fn C.XCreateSimpleWindow(d &C.Display, root Window, x i32, y i32, width u32, height u32, border_width u32,
|
||||
border u64, background u64) Window
|
||||
|
||||
fn C.XOpenDisplay(name &u8) &C.Display
|
||||
|
||||
fn C.XConvertSelection(d &C.Display, selection Atom, target Atom, property Atom, requestor Window, time i32) i32
|
||||
|
||||
fn C.XSync(d &C.Display, discard i32) i32
|
||||
|
||||
fn C.XGetWindowProperty(d &C.Display, w Window, property Atom, offset i64, length i64, delete i32, req_type Atom,
|
||||
actual_type_return &Atom, actual_format_return &int, nitems &u64, bytes_after_return &u64, prop_return &&u8) i32
|
||||
|
||||
fn C.XDeleteProperty(d &C.Display, w Window, property Atom) i32
|
||||
|
||||
fn C.DefaultScreen(display &C.Display) i32
|
||||
|
||||
fn C.RootWindow(display &C.Display, screen_number i32) Window
|
||||
|
||||
fn C.BlackPixel(display &C.Display, screen_number i32) u32
|
||||
|
||||
fn C.WhitePixel(display &C.Display, screen_number i32) u32
|
||||
|
||||
fn C.XFree(data voidptr)
|
||||
|
||||
// X11 event type constants
|
||||
pub const C.DestroyNotify int
|
||||
pub const C.SelectionClear int
|
||||
pub const C.SelectionRequest int
|
||||
pub const C.SelectionNotify int
|
||||
pub const C.PropertyNotify int // X11 property/selection constants
|
||||
|
||||
pub const C.CurrentTime int
|
||||
pub const C.PropModeReplace int
|
||||
pub const C.PropertyChangeMask i64
|
||||
|
||||
fn todo_del() {}
|
||||
|
||||
// X11 event types are defined in vlib/x/x11/x11.v
|
||||
// XEvent union is defined here since it's used by clipboard
|
||||
|
||||
@[typedef]
|
||||
union C.XEvent {
|
||||
pub 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']
|
||||
const atom_types = [AtomType.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
|
||||
}
|
||||
|
||||
@[heap]
|
||||
pub struct Clipboard {
|
||||
display &C.Display = unsafe { nil }
|
||||
mut:
|
||||
selection Atom // the selection atom
|
||||
window Window
|
||||
atoms []Atom
|
||||
mutex &sync.Mutex = sync.new_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 &u8 = unsafe { nil }
|
||||
}
|
||||
|
||||
// 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: unsafe { nil }
|
||||
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
|
||||
spawn cb.start_listener()
|
||||
return cb
|
||||
}
|
||||
|
||||
// check_availability returns `true` if the clipboard is available for use.
|
||||
pub fn (cb &Clipboard) check_availability() bool {
|
||||
return cb.display != C.NULL
|
||||
}
|
||||
|
||||
// free releases the clipboard resources.
|
||||
pub fn (mut cb Clipboard) free() {
|
||||
C.XDestroyWindow(cb.display, cb.window)
|
||||
cb.window = Window(0)
|
||||
// FIXME: program hangs when closing display
|
||||
// XCloseDisplay(cb.display)
|
||||
}
|
||||
|
||||
// clear clears the clipboard (sets it to an empty string).
|
||||
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()
|
||||
}
|
||||
|
||||
// has_ownership returns `true` if the `Clipboard` has the content ownership.
|
||||
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
|
||||
}
|
||||
|
||||
// get_text returns the current entry as a `string` from the clipboard.
|
||||
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 {
|
||||
unsafe {
|
||||
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 {
|
||||
time.sleep(1 * time.millisecond)
|
||||
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 {
|
||||
unsafe {
|
||||
if event.xselectionclear.window == cb.window
|
||||
&& event.xselectionclear.selection == cb.selection {
|
||||
cb.mutex.lock()
|
||||
cb.is_owner = false
|
||||
cb.text = ''
|
||||
cb.mutex.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
C.SelectionRequest {
|
||||
unsafe {
|
||||
if event.xselectionrequest.selection == cb.selection {
|
||||
xsre := &event.xselectionrequest
|
||||
|
||||
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 = Atom(0)
|
||||
}
|
||||
C.XSendEvent(cb.display, xse.requestor, 0, C.PropertyChangeMask,
|
||||
voidptr(&xse))
|
||||
C.XFlush(cb.display)
|
||||
}
|
||||
}
|
||||
}
|
||||
C.SelectionNotify {
|
||||
unsafe {
|
||||
if event.xselection.selection == cb.selection
|
||||
&& event.xselection.property != Atom(0) {
|
||||
if 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 event.xselection.target == to_be_requested {
|
||||
sent_request = false
|
||||
to_be_requested = Atom(0)
|
||||
cb.mutex.lock()
|
||||
prop := read_property(event.xselection.display,
|
||||
event.xselection.requestor, event.xselection.property)
|
||||
C.XDeleteProperty(event.xselection.display, event.xselection.requestor,
|
||||
event.xselection.property)
|
||||
if cb.is_supported_target(prop.actual_type) {
|
||||
cb.got_text = true
|
||||
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 atom_names {
|
||||
atom_type := atom_types[i]
|
||||
only_if_exists := if atom_type == .utf8_string { 1 } else { 0 }
|
||||
mut atom := C.XInternAtom(cb.display, &char(name.str), only_if_exists)
|
||||
if atom_type == .utf8_string && atom == Atom(0) {
|
||||
atom = cb.get_atom(.xa_string)
|
||||
}
|
||||
cb.atoms << atom
|
||||
}
|
||||
}
|
||||
|
||||
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 := &u8(unsafe { nil })
|
||||
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 := int(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 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)
|
||||
}
|
||||
Reference in new issue
Block a user