Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+16
View File
@@ -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())
}
```
+39
View File
@@ -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.')
}
+23
View File
@@ -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()
}
+29
View File
@@ -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
}
+528
View File
@@ -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)
}