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