mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
vlib: add a clipboard module (Windows, macOS, X)
This commit is contained in:
parent
3c03051bcf
commit
200fcd41ce
45
vlib/clipboard/clipboard.v
Normal file
45
vlib/clipboard/clipboard.v
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
module clipboard
|
||||||
|
|
||||||
|
// create a new clipboard
|
||||||
|
pub fn new() &Clipboard {
|
||||||
|
return new_clipboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy some text into the clipboard
|
||||||
|
pub fn (cb mut Clipboard) copy(text string) bool {
|
||||||
|
return cb.set_text(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the text from the clipboard
|
||||||
|
pub fn (cb mut Clipboard) paste() string {
|
||||||
|
return cb.get_text()
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear the clipboard
|
||||||
|
pub fn (cb mut Clipboard) clear_all() {
|
||||||
|
cb.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// destroy the clipboard
|
||||||
|
pub fn (cb mut Clipboard) destroy() {
|
||||||
|
cb.free()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we own the clipboard
|
||||||
|
pub fn (cb mut Clipboard) check_ownership() bool {
|
||||||
|
return cb.has_ownership()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if clipboard can be used
|
||||||
|
pub fn (cb &Clipboard) is_available() bool {
|
||||||
|
return cb.check_availability()
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new PRIMARY clipboard (only supported on Linux)
|
||||||
|
pub fn new_primary() &Clipboard {
|
||||||
|
$if linux {
|
||||||
|
return new_x11_clipboard(.primary)
|
||||||
|
} $else {
|
||||||
|
panic("Primary clipboard is not supported on non-Linux systems.")
|
||||||
|
}
|
||||||
|
}
|
66
vlib/clipboard/clipboard_darwin.v
Normal file
66
vlib/clipboard/clipboard_darwin.v
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
module clipboard
|
||||||
|
|
||||||
|
#include <libkern/OSAtomic.h>
|
||||||
|
#include <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
#flag -framework Cocoa
|
||||||
|
|
||||||
|
struct Clipboard {
|
||||||
|
pb voidptr
|
||||||
|
last_cb_serial i64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_clipboard() &Clipboard{
|
||||||
|
mut pb := voidptr(0)
|
||||||
|
#pb = [NSPasteboard generalPasteboard];
|
||||||
|
cb := &Clipboard{
|
||||||
|
pb: pb
|
||||||
|
}
|
||||||
|
return cb
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) check_availability() bool {
|
||||||
|
return cb.pb != C.NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) clear(){
|
||||||
|
#[cb->pb clearContents];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) free(){
|
||||||
|
//nothing to free
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (cb &Clipboard) set_text(text string) bool {
|
||||||
|
#NSString *ns_clip;
|
||||||
|
mut ret := false
|
||||||
|
|
||||||
|
#ns_clip = [[ NSString alloc ] initWithBytesNoCopy:text.str length:text.len encoding:NSUTF8StringEncoding freeWhenDone: false];
|
||||||
|
#[cb->pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
|
||||||
|
#ret = [cb->pb setString:ns_clip forType:NSStringPboardType];
|
||||||
|
#[ns_clip release];
|
||||||
|
|
||||||
|
mut serial := 0
|
||||||
|
#serial = [cb->pb changeCount];
|
||||||
|
C.OSAtomicCompareAndSwapLong(cb.last_cb_serial, serial, &cb.last_cb_serial)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) get_text() string {
|
||||||
|
#NSString *ns_clip;
|
||||||
|
mut utf8_clip := byteptr(0)
|
||||||
|
|
||||||
|
#ns_clip = [cb->pb stringForType:NSStringPboardType]; //NSPasteboardTypeString
|
||||||
|
#if (ns_clip == nil) {
|
||||||
|
# return tos3(""); //in case clipboard is empty
|
||||||
|
#}
|
||||||
|
|
||||||
|
#utf8_clip = [ns_clip UTF8String];
|
||||||
|
return string(utf8_clip)
|
||||||
|
}
|
418
vlib/clipboard/clipboard_linux.v
Normal file
418
vlib/clipboard/clipboard_linux.v
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
// 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 clipboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
time
|
||||||
|
sync
|
||||||
|
math
|
||||||
|
)
|
||||||
|
|
||||||
|
#flag -lX11
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
|
||||||
|
// X11
|
||||||
|
struct C.Display
|
||||||
|
struct C.Atom
|
||||||
|
struct C.Window
|
||||||
|
fn C.XInitThreads() int
|
||||||
|
fn C.XCloseDisplay(d &Display)
|
||||||
|
fn C.XFlush(d &Display)
|
||||||
|
fn C.XDestroyWindow(d &Display, w Window)
|
||||||
|
fn C.XNextEvent(d Display, e &XEvent)
|
||||||
|
fn C.XSetSelectionOwner(d &Display, a Atom, w Window, time int)
|
||||||
|
fn C.XGetSelectionOwner(d &Display, a Atom) Window
|
||||||
|
fn C.XChangeProperty(d &Display, requestor Window, property Atom, typ Atom, format int, mode int, data voidptr, nelements int) int
|
||||||
|
fn C.XSendEvent(d &Display, requestor Window, propogate int, mask i64, event &XEvent)
|
||||||
|
fn C.XInternAtom(d &Display, typ byteptr, only_if_exists int) Atom
|
||||||
|
fn C.XCreateSimpleWindow(d &Display, root Window, x int, y int, width u32, height u32, border_width u32, border u64, background u64) Window
|
||||||
|
fn C.XOpenDisplay(name byteptr) &Display
|
||||||
|
fn C.XConvertSelection(d &Display, selection Atom, target Atom, property Atom, requestor Window, time int) int
|
||||||
|
fn C.XSync(d &Display, discard int) int
|
||||||
|
fn C.XGetWindowProperty(d &Display, w Window, property Atom, offset i64, length i64, delete int, req_type Atom, actual_type_return &Atom, actual_format_return &int, nitems &i64, bytes_after_return &i64, prop_return &byteptr) int
|
||||||
|
fn C.XDeleteProperty(d &Display, w Window, property Atom) int
|
||||||
|
struct C.XSelectionRequestEvent{
|
||||||
|
mut:
|
||||||
|
selection Atom
|
||||||
|
display &Display /* Display the event was read from */
|
||||||
|
owner Window
|
||||||
|
requestor Window
|
||||||
|
target Atom
|
||||||
|
property Atom
|
||||||
|
time int
|
||||||
|
}
|
||||||
|
struct C.XSelectionEvent{
|
||||||
|
mut:
|
||||||
|
@type int
|
||||||
|
selection Atom
|
||||||
|
display &Display /* Display the event was read from */
|
||||||
|
requestor Window
|
||||||
|
target Atom
|
||||||
|
property Atom
|
||||||
|
time int
|
||||||
|
}
|
||||||
|
struct C.XSelectionClearEvent{
|
||||||
|
mut:
|
||||||
|
window Window
|
||||||
|
selection Atom
|
||||||
|
}
|
||||||
|
struct C.XDestroyWindowEvent {
|
||||||
|
mut:
|
||||||
|
window Window
|
||||||
|
}
|
||||||
|
struct C.XEvent{
|
||||||
|
mut:
|
||||||
|
@type int
|
||||||
|
xselectionrequest XSelectionRequestEvent
|
||||||
|
xselection XSelectionEvent
|
||||||
|
xselectionclear XSelectionClearEvent
|
||||||
|
xdestroywindow XDestroyWindowEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
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 atom_type {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Clipboard {
|
||||||
|
display &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 int
|
||||||
|
data byteptr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_clipboard() &Clipboard {
|
||||||
|
return new_x11_clipboard(.clipboard)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a new clipboard of the given selection type.
|
||||||
|
// We can initialize multiple clipboard instances and use them separately
|
||||||
|
fn new_x11_clipboard(selection atom_type) &Clipboard {
|
||||||
|
if !(selection in [.clipboard, .primary, .secondary]) {
|
||||||
|
panic("Wrong atom_type. Must be one of .primary, .secondary or .clipboard.")
|
||||||
|
}
|
||||||
|
|
||||||
|
//init x11 thread support
|
||||||
|
status := 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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) check_availability() bool {
|
||||||
|
return cb.display != C.NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb mut Clipboard) free() {
|
||||||
|
XDestroyWindow(cb.display, cb.window)
|
||||||
|
cb.window = Window(C.None)
|
||||||
|
//FIX ME: program hangs when closing display
|
||||||
|
//XCloseDisplay(cb.display)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb mut Clipboard) clear(){
|
||||||
|
cb.mutex.lock()
|
||||||
|
XSetSelectionOwner(cb.display, cb.selection, Window(C.None), C.CurrentTime)
|
||||||
|
XFlush(cb.display)
|
||||||
|
cb.is_owner = false
|
||||||
|
cb.text = ""
|
||||||
|
cb.mutex.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) has_ownership() bool {
|
||||||
|
return cb.is_owner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) take_ownership(){
|
||||||
|
XSetSelectionOwner(cb.display, cb.selection, cb.window, C.CurrentTime)
|
||||||
|
XFlush(cb.display)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb mut Clipboard) set_text(text string) bool {
|
||||||
|
if cb.window == Window(C.None) {return false}
|
||||||
|
mut ret := false
|
||||||
|
cb.mutex.lock()
|
||||||
|
cb.text = text
|
||||||
|
cb.is_owner = true
|
||||||
|
cb.take_ownership()
|
||||||
|
XFlush(cb.display)
|
||||||
|
cb.mutex.unlock()
|
||||||
|
// sleep a little bit
|
||||||
|
time.sleep(1)
|
||||||
|
return cb.is_owner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb mut Clipboard) get_text() string {
|
||||||
|
if cb.window == 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.
|
||||||
|
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.usleep(50000)
|
||||||
|
retries--
|
||||||
|
}
|
||||||
|
return cb.text
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function is crucial to handling all the different data types
|
||||||
|
// if we ever support other mimetypes they should be handled here
|
||||||
|
fn (cb mut Clipboard) transmit_selection(xse &XSelectionEvent) bool {
|
||||||
|
if xse.target == cb.get_atom(.targets) {
|
||||||
|
targets := cb.get_supported_targets()
|
||||||
|
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()
|
||||||
|
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 (cb mut Clipboard) start_listener(){
|
||||||
|
event := XEvent{}
|
||||||
|
mut sent_request := false
|
||||||
|
mut to_be_requested := Atom(0)
|
||||||
|
for {
|
||||||
|
XNextEvent(cb.display, &event)
|
||||||
|
if (event.@type == 0) {
|
||||||
|
println("error")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
match event.@type {
|
||||||
|
C.DestroyNotify {
|
||||||
|
if event.xdestroywindow.window == cb.window {
|
||||||
|
return // we are done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
C.SelectionClear {
|
||||||
|
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 {
|
||||||
|
if event.xselectionrequest.selection == cb.selection {
|
||||||
|
mut xsre := &XSelectionRequestEvent{}
|
||||||
|
xsre = &event.xselectionrequest
|
||||||
|
|
||||||
|
mut xse := 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(C.None)
|
||||||
|
}
|
||||||
|
XSendEvent(cb.display, xse.requestor, 0, C.PropertyChangeMask, &xse)
|
||||||
|
XFlush(cb.display)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
C.SelectionNotify {
|
||||||
|
if event.xselection.selection == cb.selection && event.xselection.property != Atom(C.None) {
|
||||||
|
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) {
|
||||||
|
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)
|
||||||
|
XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property)
|
||||||
|
if cb.is_supported_target(prop.actual_type) {
|
||||||
|
cb.got_text = true
|
||||||
|
cb.text = string(prop.data) //TODO: return byteptr to support other mimetypes
|
||||||
|
}
|
||||||
|
cb.mutex.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
C.PropertyNotify {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
// Initialize all the atoms we need
|
||||||
|
fn (cb mut Clipboard) intern_atoms(){
|
||||||
|
cb.atoms << Atom(4) //XA_ATOM
|
||||||
|
cb.atoms << Atom(31) //XA_STRING
|
||||||
|
for i, name in atom_names{
|
||||||
|
only_if_exists := if i == int(atom_type.utf8_string) {1} else {0}
|
||||||
|
cb.atoms << XInternAtom(cb.display, name.str, only_if_exists)
|
||||||
|
if i == int(atom_type.utf8_string) && cb.atoms[i] == Atom(C.None) {
|
||||||
|
cb.atoms[i] = cb.get_atom(.xa_string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_property(d &Display, w Window, p Atom) Property {
|
||||||
|
actual_type := Atom(0)
|
||||||
|
actual_format := 0
|
||||||
|
nitems := 0
|
||||||
|
bytes_after := 0
|
||||||
|
ret := byteptr(0)
|
||||||
|
mut read_bytes := 1024
|
||||||
|
for {
|
||||||
|
if(ret != 0){
|
||||||
|
C.XFree(ret)
|
||||||
|
}
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
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) 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(prop.data)
|
||||||
|
|
||||||
|
mut to_be_requested := Atom(0)
|
||||||
|
|
||||||
|
//This is higher than the maximum priority.
|
||||||
|
mut priority := math.max_i32
|
||||||
|
|
||||||
|
supported_targets := cb.get_supported_targets()
|
||||||
|
|
||||||
|
for i := 0; i < prop.nitems; i++ {
|
||||||
|
//See if this data type is allowed and of higher priority (closer to zero)
|
||||||
|
//than the present one.
|
||||||
|
|
||||||
|
if cb.is_supported_target(atom_list[i]) {
|
||||||
|
index := cb.get_target_index(atom_list[i])
|
||||||
|
if(priority > index && index >= 0)
|
||||||
|
{
|
||||||
|
priority = index
|
||||||
|
to_be_requested = atom_list[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return to_be_requested
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) get_atoms(types ...atom_type) []Atom {
|
||||||
|
mut atoms := []Atom
|
||||||
|
for typ in types {
|
||||||
|
atoms << cb.atoms[typ]
|
||||||
|
}
|
||||||
|
return atoms
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) get_atom(typ atom_type) 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(atom_type.utf8_string, .xa_string, .text, .text_plain, .text_html)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_atom(value int) &Atom {
|
||||||
|
mut atom := &Atom{}
|
||||||
|
atom = value
|
||||||
|
return atom
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_xwindow(display &Display) Window {
|
||||||
|
N := int(C.DefaultScreen(display))
|
||||||
|
return XCreateSimpleWindow(display, C.RootWindow(display, N), 0, 0, 1, 1, 0, C.BlackPixel(display, N), C.WhitePixel(display, N))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_display() &Display {
|
||||||
|
return XOpenDisplay(C.NULL)
|
||||||
|
}
|
||||||
|
|
23
vlib/clipboard/clipboard_test.v
Normal file
23
vlib/clipboard/clipboard_test.v
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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
|
||||||
|
assert cb.paste() == "I am a good boy!"
|
||||||
|
cb.clear_all()
|
||||||
|
assert cb.paste().len <= 0
|
||||||
|
cb.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_primary(){
|
||||||
|
$if linux {
|
||||||
|
run_test(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_clipboard(){
|
||||||
|
run_test(false)
|
||||||
|
}
|
147
vlib/clipboard/clipboard_windows.v
Normal file
147
vlib/clipboard/clipboard_windows.v
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
module clipboard
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
struct C.HWND
|
||||||
|
struct C.WPARAM
|
||||||
|
struct C.LPARAM
|
||||||
|
struct C.LRESULT
|
||||||
|
struct C.HGLOBAL
|
||||||
|
struct C.WNDCLASSEX {
|
||||||
|
cbSize int
|
||||||
|
lpfnWndProc voidptr
|
||||||
|
lpszClassName &u16
|
||||||
|
}
|
||||||
|
fn C.RegisterClassEx(class WNDCLASSEX) int
|
||||||
|
fn C.GetClipboardOwner() &HWND
|
||||||
|
fn C.CreateWindowEx(dwExStyle i64, lpClassName &u16, lpWindowName &u16, dwStyle i64, x int, y int, nWidth int, nHeight int, hWndParent i64, hMenu voidptr, hInstance voidptr, lpParam voidptr) &HWND
|
||||||
|
fn C.MultiByteToWideChar(CodePage u32, dwFlags u16, lpMultiByteStr byteptr, cbMultiByte int, lpWideCharStr u16, cchWideChar int) int
|
||||||
|
fn C.EmptyClipboard()
|
||||||
|
fn C.CloseClipboard()
|
||||||
|
fn C.GlobalAlloc(uFlag u32, size i64) HGLOBAL
|
||||||
|
fn C.GlobalFree(buf HGLOBAL)
|
||||||
|
fn C.GlobalLock(buf HGLOBAL)
|
||||||
|
fn C.GlobalUnlock(buf HGLOBAL)
|
||||||
|
fn C.SetClipboardData(uFormat u32, data voidptr) C.HANDLE
|
||||||
|
fn C.GetClipboardData(uFormat u32) C.HANDLE
|
||||||
|
fn C.DefWindowProc(hwnd HWND, msg u32, wParam WPARAM, lParam LPARAM) LRESULT
|
||||||
|
fn C.SetLastError(error i64)
|
||||||
|
fn C.OpenClipboard(hwnd HWND) int
|
||||||
|
fn C.DestroyWindow(hwnd HWND)
|
||||||
|
|
||||||
|
struct Clipboard {
|
||||||
|
max_retries int
|
||||||
|
retry_delay int
|
||||||
|
mut:
|
||||||
|
hwnd HWND
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = GetLastError()
|
||||||
|
if OpenClipboard(cb.hwnd) > 0 {
|
||||||
|
return true
|
||||||
|
} else if last_error != u32(C.ERROR_ACCESS_DENIED) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
time.sleep(cb.retry_delay)
|
||||||
|
}
|
||||||
|
SetLastError(last_error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_clipboard() &Clipboard {
|
||||||
|
mut cb := &Clipboard {
|
||||||
|
max_retries: 5
|
||||||
|
retry_delay: 5
|
||||||
|
}
|
||||||
|
wndclass := WNDCLASSEX{
|
||||||
|
cbSize: sizeof(WNDCLASSEX)
|
||||||
|
lpfnWndProc: voidptr(&DefWindowProc)
|
||||||
|
lpszClassName: "clipboard".to_wide()
|
||||||
|
}
|
||||||
|
if RegisterClassEx(&wndclass) <= 0 && GetLastError() != u32(C.ERROR_CLASS_ALREADY_EXISTS) {
|
||||||
|
println("Failed registering class.")
|
||||||
|
}
|
||||||
|
hwnd := CreateWindowEx(0, wndclass.lpszClassName, wndclass.lpszClassName, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) check_availability() bool {
|
||||||
|
return cb.hwnd != HWND(C.NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) has_ownership() bool {
|
||||||
|
return GetClipboardOwner() == cb.hwnd
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) clear() {
|
||||||
|
if !cb.get_clipboard_lock() {return}
|
||||||
|
EmptyClipboard()
|
||||||
|
CloseClipboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) free(){
|
||||||
|
DestroyWindow(cb.hwnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the string.to_wide doesn't work with SetClipboardData, don't know why
|
||||||
|
fn to_wide(text string) &HGLOBAL {
|
||||||
|
len_required := MultiByteToWideChar(C.CP_UTF8, C.MB_ERR_INVALID_CHARS, text.str, text.len + 1, C.NULL, 0)
|
||||||
|
buf := GlobalAlloc(C.GMEM_MOVEABLE, sizeof(u16) * len_required)
|
||||||
|
if buf != HGLOBAL(C.NULL) {
|
||||||
|
mut locked := &u16(GlobalLock(buf))
|
||||||
|
MultiByteToWideChar(C.CP_UTF8, C.MB_ERR_INVALID_CHARS, text.str, text.len + 1, locked, len_required)
|
||||||
|
locked[len_required - 1] = u16(0)
|
||||||
|
GlobalUnlock(buf)
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) set_text(text string) bool {
|
||||||
|
buf := to_wide(text)
|
||||||
|
if !cb.get_clipboard_lock() {
|
||||||
|
GlobalFree(buf)
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
/* EmptyClipboard must be called to properly update clipboard ownership */
|
||||||
|
EmptyClipboard()
|
||||||
|
if SetClipboardData(C.CF_UNICODETEXT, buf) == HANDLE(C.NULL) {
|
||||||
|
println("SetClipboardData: Failed.")
|
||||||
|
CloseClipboard()
|
||||||
|
GlobalFree(buf)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* CloseClipboard appears to change the sequence number... */
|
||||||
|
CloseClipboard()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (cb &Clipboard) get_text() string {
|
||||||
|
if !cb.get_clipboard_lock() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
h_data := GetClipboardData(C.CF_UNICODETEXT)
|
||||||
|
if h_data == HANDLE(C.NULL) {
|
||||||
|
CloseClipboard()
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
str := string_from_wide(&u16(GlobalLock(h_data)))
|
||||||
|
GlobalUnlock(h_data)
|
||||||
|
return str
|
||||||
|
}
|
@ -297,7 +297,7 @@ fn (p mut Parser) parse(pass Pass) {
|
|||||||
}
|
}
|
||||||
p.fgenln('\n')
|
p.fgenln('\n')
|
||||||
p.builtin_mod = p.mod == 'builtin'
|
p.builtin_mod = p.mod == 'builtin'
|
||||||
p.can_chash = p.mod=='ui' || p.mod == 'darwin'// TODO tmp remove
|
p.can_chash = p.mod in ['ui','darwin','clipboard']// TODO tmp remove
|
||||||
// Import pass - the first and the smallest pass that only analyzes imports
|
// Import pass - the first and the smallest pass that only analyzes imports
|
||||||
// if we are a building module get the full module name from v.mod
|
// if we are a building module get the full module name from v.mod
|
||||||
fq_mod := if p.pref.build_mode == .build_module && p.v.mod.ends_with(p.mod) {
|
fq_mod := if p.pref.build_mode == .build_module && p.v.mod.ends_with(p.mod) {
|
||||||
|
Loading…
Reference in New Issue
Block a user