| 1 | module clipboard |
| 2 | |
| 3 | import time |
| 4 | |
| 5 | #include <windows.h> |
| 6 | #flag -luser32 |
| 7 | |
| 8 | struct WndClassEx { |
| 9 | cb_size u32 |
| 10 | style u32 |
| 11 | lpfn_wnd_proc voidptr |
| 12 | cb_cls_extra int |
| 13 | cb_wnd_extra int |
| 14 | h_instance C.HINSTANCE |
| 15 | h_icon C.HICON |
| 16 | h_cursor C.HCURSOR |
| 17 | hbr_background C.HBRUSH |
| 18 | lpsz_menu_name &u16 = unsafe { nil } // LPCWSTR |
| 19 | lpsz_class_name &u16 = unsafe { nil } |
| 20 | h_icon_sm &u16 = unsafe { nil } |
| 21 | } |
| 22 | |
| 23 | fn C.RegisterClassEx(class &WndClassEx) i32 |
| 24 | |
| 25 | fn C.GetClipboardOwner() C.HWND |
| 26 | |
| 27 | fn C.CreateWindowEx(dwExStyle i64, lpClassName &u16, lpWindowName &u16, dwStyle i64, x i32, y i32, nWidth i32, |
| 28 | nHeight i32, hWndParent i64, hMenu voidptr, h_instance voidptr, lpParam voidptr) C.HWND |
| 29 | |
| 30 | // fn C.MultiByteToWideChar(CodePage u32, dw_flags u16, lpMultiByteStr byteptr, cbMultiByte int, lpWideCharStr u16, cchWideChar int) int |
| 31 | fn C.EmptyClipboard() |
| 32 | |
| 33 | fn C.CloseClipboard() |
| 34 | |
| 35 | fn C.GlobalAlloc(uFlag u32, size i64) C.HGLOBAL |
| 36 | |
| 37 | fn C.GlobalFree(buf C.HGLOBAL) |
| 38 | |
| 39 | fn C.GlobalLock(buf C.HGLOBAL) voidptr |
| 40 | |
| 41 | fn C.GlobalUnlock(buf C.HGLOBAL) bool |
| 42 | |
| 43 | fn C.SetClipboardData(uFormat u32, data voidptr) C.HANDLE |
| 44 | |
| 45 | fn C.GetClipboardData(uFormat u32) C.HANDLE |
| 46 | |
| 47 | fn C.DefWindowProc(hwnd C.HWND, msg u32, wParam C.WPARAM, lParam C.LPARAM) C.LRESULT |
| 48 | |
| 49 | fn C.SetLastError(error i64) |
| 50 | |
| 51 | fn C.OpenClipboard(hwnd C.HWND) i32 |
| 52 | |
| 53 | fn C.DestroyWindow(hwnd C.HWND) |
| 54 | |
| 55 | // Clipboard represents a system clipboard. |
| 56 | // |
| 57 | // System "copy" and "paste" actions utilize the clipboard for temporary storage. |
| 58 | @[heap] |
| 59 | pub struct Clipboard { |
| 60 | max_retries int |
| 61 | retry_delay int |
| 62 | mut: |
| 63 | hwnd voidptr |
| 64 | foo int // TODO: remove |
| 65 | } |
| 66 | |
| 67 | fn (cb &Clipboard) get_clipboard_lock() bool { |
| 68 | mut retries := cb.max_retries |
| 69 | mut last_error := u32(0) |
| 70 | for { |
| 71 | retries-- |
| 72 | if retries < 0 { |
| 73 | break |
| 74 | } |
| 75 | if C.OpenClipboard(cb.hwnd) > 0 { |
| 76 | return true |
| 77 | } |
| 78 | last_error = C.GetLastError() |
| 79 | if last_error != u32(C.ERROR_ACCESS_DENIED) { |
| 80 | return false |
| 81 | } |
| 82 | time.sleep(cb.retry_delay * time.second) |
| 83 | } |
| 84 | C.SetLastError(last_error) |
| 85 | return false |
| 86 | } |
| 87 | |
| 88 | fn new_clipboard() &Clipboard { |
| 89 | mut cb := &Clipboard{ |
| 90 | max_retries: 5 |
| 91 | retry_delay: 5 |
| 92 | } |
| 93 | class_name := 'clipboard' |
| 94 | wndclass := WndClassEx{ |
| 95 | cb_size: sizeof(WndClassEx) |
| 96 | lpfn_wnd_proc: voidptr(&C.DefWindowProc) |
| 97 | lpsz_class_name: class_name.to_wide() |
| 98 | lpsz_menu_name: unsafe { 0 } |
| 99 | h_icon_sm: unsafe { 0 } |
| 100 | } |
| 101 | if C.RegisterClassEx(voidptr(&wndclass)) == 0 |
| 102 | && C.GetLastError() != u32(C.ERROR_CLASS_ALREADY_EXISTS) { |
| 103 | println('Failed registering class.') |
| 104 | } |
| 105 | hwnd := C.CreateWindowEx(0, wndclass.lpsz_class_name, wndclass.lpsz_class_name, 0, 0, 0, 0, 0, |
| 106 | C.HWND_MESSAGE, C.NULL, C.NULL, C.NULL) |
| 107 | if hwnd == unsafe { nil } { |
| 108 | println('Error creating window!') |
| 109 | } |
| 110 | cb.hwnd = voidptr(hwnd) |
| 111 | return cb |
| 112 | } |
| 113 | |
| 114 | // check_availability returns true if the clipboard is ready to be used. |
| 115 | pub fn (cb &Clipboard) check_availability() bool { |
| 116 | return cb.hwnd != unsafe { nil } |
| 117 | } |
| 118 | |
| 119 | // has_ownership returns true if the contents of the clipboard were created by this clipboard instance. |
| 120 | pub fn (cb &Clipboard) has_ownership() bool { |
| 121 | return voidptr(C.GetClipboardOwner()) == cb.hwnd |
| 122 | } |
| 123 | |
| 124 | // clear empties the clipboard contents. |
| 125 | pub fn (mut cb Clipboard) clear() { |
| 126 | if !cb.get_clipboard_lock() { |
| 127 | return |
| 128 | } |
| 129 | C.EmptyClipboard() |
| 130 | C.CloseClipboard() |
| 131 | cb.foo = 0 |
| 132 | } |
| 133 | |
| 134 | // free releases all memory associated with the clipboard instance. |
| 135 | pub fn (mut cb Clipboard) free() { |
| 136 | C.DestroyWindow(cb.hwnd) |
| 137 | cb.foo = 0 |
| 138 | } |
| 139 | |
| 140 | const cp_utf8 = 65001 |
| 141 | |
| 142 | // the string.to_wide doesn't work with SetClipboardData, don't know why |
| 143 | fn to_wide(text string) C.HGLOBAL { |
| 144 | len_required := C.MultiByteToWideChar(cp_utf8, C.MB_ERR_INVALID_CHARS, voidptr(text.str), |
| 145 | |
| 146 | text.len + 1, C.NULL, 0) |
| 147 | buf := C.GlobalAlloc(C.GMEM_MOVEABLE, i64(sizeof(u16)) * len_required) |
| 148 | if buf != unsafe { nil } { |
| 149 | mut locked := &u16(C.GlobalLock(buf)) |
| 150 | C.MultiByteToWideChar(cp_utf8, C.MB_ERR_INVALID_CHARS, voidptr(text.str), text.len + 1, |
| 151 | locked, len_required) |
| 152 | unsafe { |
| 153 | locked[len_required - 1] = u16(0) |
| 154 | } |
| 155 | C.GlobalUnlock(buf) |
| 156 | } |
| 157 | return buf |
| 158 | } |
| 159 | |
| 160 | // set_text transfers `text` to the system clipboard. |
| 161 | // This is often associated with a *copy* action (`Ctrl` + `C`). |
| 162 | pub fn (mut cb Clipboard) set_text(text string) bool { |
| 163 | cb.foo = 0 |
| 164 | buf := to_wide(text) |
| 165 | if !cb.get_clipboard_lock() { |
| 166 | C.GlobalFree(buf) |
| 167 | return false |
| 168 | } else { |
| 169 | // EmptyClipboard must be called to properly update clipboard ownership |
| 170 | C.EmptyClipboard() |
| 171 | if C.SetClipboardData(C.CF_UNICODETEXT, buf) == unsafe { nil } { |
| 172 | println('SetClipboardData: Failed.') |
| 173 | C.CloseClipboard() |
| 174 | C.GlobalFree(buf) |
| 175 | return false |
| 176 | } |
| 177 | } |
| 178 | // CloseClipboard appears to change the sequence number... |
| 179 | C.CloseClipboard() |
| 180 | return true |
| 181 | } |
| 182 | |
| 183 | // get_text retrieves the contents of the system clipboard. |
| 184 | // This is often associated with a *paste* action (`Ctrl` + `V`). |
| 185 | pub fn (mut cb Clipboard) get_text() string { |
| 186 | cb.foo = 0 |
| 187 | if !cb.get_clipboard_lock() { |
| 188 | return '' |
| 189 | } |
| 190 | defer { |
| 191 | C.CloseClipboard() |
| 192 | } |
| 193 | h_data := C.GetClipboardData(C.CF_UNICODETEXT) |
| 194 | if h_data == unsafe { nil } { |
| 195 | return '' |
| 196 | } |
| 197 | str := unsafe { string_from_wide(&u16(C.GlobalLock(C.HGLOBAL(h_data)))) } |
| 198 | C.GlobalUnlock(C.HGLOBAL(h_data)) |
| 199 | return str |
| 200 | } |
| 201 | |
| 202 | // new_primary returns a new X11 `PRIMARY` type `Clipboard` instance allocated on the heap. |
| 203 | // Please note: new_primary only works on X11 based systems. |
| 204 | pub fn new_primary() &Clipboard { |
| 205 | panic('Primary clipboard is not supported on non-Linux systems.') |
| 206 | } |
| 207 | |