teek 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +21 -0
- data/Rakefile +120 -22
- data/ext/teek/extconf.rb +19 -1
- data/ext/teek/tcltkbridge.c +38 -2
- data/ext/teek/tcltkbridge.h +3 -0
- data/ext/teek/tkdrop.c +66 -0
- data/ext/teek/tkdrop.h +26 -0
- data/ext/teek/tkdrop_macos.m +141 -0
- data/ext/teek/tkdrop_win.c +232 -0
- data/ext/teek/tkdrop_x11.c +337 -0
- data/ext/teek/tkwin.c +42 -0
- data/lib/teek/platform.rb +29 -0
- data/lib/teek/version.rb +1 -1
- data/lib/teek.rb +49 -3
- data/teek.gemspec +3 -2
- metadata +7 -53
- data/sample/calculator.rb +0 -255
- data/sample/debug_demo.rb +0 -43
- data/sample/gamepad_viewer/assets/controller.png +0 -0
- data/sample/gamepad_viewer/gamepad_viewer.rb +0 -554
- data/sample/goldberg.rb +0 -1803
- data/sample/goldberg_helpers.rb +0 -170
- data/sample/optcarrot/thwaite.nes +0 -0
- data/sample/optcarrot/vendor/optcarrot/apu.rb +0 -856
- data/sample/optcarrot/vendor/optcarrot/config.rb +0 -257
- data/sample/optcarrot/vendor/optcarrot/cpu.rb +0 -1162
- data/sample/optcarrot/vendor/optcarrot/driver.rb +0 -144
- data/sample/optcarrot/vendor/optcarrot/mapper/cnrom.rb +0 -14
- data/sample/optcarrot/vendor/optcarrot/mapper/mmc1.rb +0 -105
- data/sample/optcarrot/vendor/optcarrot/mapper/mmc3.rb +0 -153
- data/sample/optcarrot/vendor/optcarrot/mapper/uxrom.rb +0 -14
- data/sample/optcarrot/vendor/optcarrot/nes.rb +0 -105
- data/sample/optcarrot/vendor/optcarrot/opt.rb +0 -168
- data/sample/optcarrot/vendor/optcarrot/pad.rb +0 -92
- data/sample/optcarrot/vendor/optcarrot/palette.rb +0 -65
- data/sample/optcarrot/vendor/optcarrot/ppu.rb +0 -1468
- data/sample/optcarrot/vendor/optcarrot/rom.rb +0 -143
- data/sample/optcarrot/vendor/optcarrot.rb +0 -14
- data/sample/optcarrot.rb +0 -354
- data/sample/paint/assets/bucket.png +0 -0
- data/sample/paint/assets/cursor.png +0 -0
- data/sample/paint/assets/eraser.png +0 -0
- data/sample/paint/assets/pencil.png +0 -0
- data/sample/paint/assets/spray.png +0 -0
- data/sample/paint/layer.rb +0 -255
- data/sample/paint/layer_manager.rb +0 -179
- data/sample/paint/paint_demo.rb +0 -837
- data/sample/paint/sparse_pixel_buffer.rb +0 -202
- data/sample/sdl2_demo.rb +0 -318
- data/sample/threading_demo.rb +0 -494
- data/sample/yam/assets/MINESWEEPER_0.png +0 -0
- data/sample/yam/assets/MINESWEEPER_1.png +0 -0
- data/sample/yam/assets/MINESWEEPER_2.png +0 -0
- data/sample/yam/assets/MINESWEEPER_3.png +0 -0
- data/sample/yam/assets/MINESWEEPER_4.png +0 -0
- data/sample/yam/assets/MINESWEEPER_5.png +0 -0
- data/sample/yam/assets/MINESWEEPER_6.png +0 -0
- data/sample/yam/assets/MINESWEEPER_7.png +0 -0
- data/sample/yam/assets/MINESWEEPER_8.png +0 -0
- data/sample/yam/assets/MINESWEEPER_F.png +0 -0
- data/sample/yam/assets/MINESWEEPER_M.png +0 -0
- data/sample/yam/assets/MINESWEEPER_X.png +0 -0
- data/sample/yam/assets/click.wav +0 -0
- data/sample/yam/assets/explosion.wav +0 -0
- data/sample/yam/assets/flag.wav +0 -0
- data/sample/yam/assets/music.mp3 +0 -0
- data/sample/yam/assets/sweep.wav +0 -0
- data/sample/yam/yam.rb +0 -587
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/* tkdrop_win.c - Windows file drop target via OLE IDropTarget
|
|
2
|
+
*
|
|
3
|
+
* Uses the OLE drag-and-drop API (RegisterDragDrop/IDropTarget) for
|
|
4
|
+
* flexible drop support. Extracts file paths from CF_HDROP format and
|
|
5
|
+
* generates <<DropFile>> virtual events.
|
|
6
|
+
*
|
|
7
|
+
* Based on tkdnd (https://github.com/petasis/tkdnd) as reference.
|
|
8
|
+
* See THIRD_PARTY_NOTICES for attribution.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include <tcl.h>
|
|
12
|
+
#include <tk.h>
|
|
13
|
+
#include "tkdrop.h"
|
|
14
|
+
|
|
15
|
+
#ifdef _WIN32
|
|
16
|
+
|
|
17
|
+
#define COBJMACROS
|
|
18
|
+
#include <initguid.h>
|
|
19
|
+
#include <windows.h>
|
|
20
|
+
#include <ole2.h>
|
|
21
|
+
#include <shlobj.h>
|
|
22
|
+
#include <shellapi.h>
|
|
23
|
+
#include "tkPlatDecls.h"
|
|
24
|
+
|
|
25
|
+
/* --------------------------------------------------------- */
|
|
26
|
+
/* IDropTarget implementation in C via COM vtable */
|
|
27
|
+
/* --------------------------------------------------------- */
|
|
28
|
+
|
|
29
|
+
typedef struct TeekDropTarget {
|
|
30
|
+
IDropTargetVtbl *lpVtbl;
|
|
31
|
+
LONG ref_count;
|
|
32
|
+
Tcl_Interp *interp;
|
|
33
|
+
char *widget_path;
|
|
34
|
+
HWND hwnd;
|
|
35
|
+
} TeekDropTarget;
|
|
36
|
+
|
|
37
|
+
static HRESULT STDMETHODCALLTYPE
|
|
38
|
+
tdt_QueryInterface(IDropTarget *self, REFIID riid, void **ppv)
|
|
39
|
+
{
|
|
40
|
+
if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IDropTarget)) {
|
|
41
|
+
*ppv = self;
|
|
42
|
+
IDropTarget_AddRef(self);
|
|
43
|
+
return S_OK;
|
|
44
|
+
}
|
|
45
|
+
*ppv = NULL;
|
|
46
|
+
return E_NOINTERFACE;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static ULONG STDMETHODCALLTYPE
|
|
50
|
+
tdt_AddRef(IDropTarget *self)
|
|
51
|
+
{
|
|
52
|
+
TeekDropTarget *tdt = (TeekDropTarget *)self;
|
|
53
|
+
return InterlockedIncrement(&tdt->ref_count);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static ULONG STDMETHODCALLTYPE
|
|
57
|
+
tdt_Release(IDropTarget *self)
|
|
58
|
+
{
|
|
59
|
+
TeekDropTarget *tdt = (TeekDropTarget *)self;
|
|
60
|
+
LONG count = InterlockedDecrement(&tdt->ref_count);
|
|
61
|
+
if (count == 0) {
|
|
62
|
+
free(tdt->widget_path);
|
|
63
|
+
free(tdt);
|
|
64
|
+
}
|
|
65
|
+
return count;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Check if the drag contains files */
|
|
69
|
+
static BOOL
|
|
70
|
+
has_file_data(IDataObject *pDataObj)
|
|
71
|
+
{
|
|
72
|
+
FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
73
|
+
return IDataObject_QueryGetData(pDataObj, &fmt) == S_OK;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static HRESULT STDMETHODCALLTYPE
|
|
77
|
+
tdt_DragEnter(IDropTarget *self, IDataObject *pDataObj, DWORD grfKeyState,
|
|
78
|
+
POINTL pt, DWORD *pdwEffect)
|
|
79
|
+
{
|
|
80
|
+
if (has_file_data(pDataObj)) {
|
|
81
|
+
*pdwEffect = DROPEFFECT_COPY;
|
|
82
|
+
} else {
|
|
83
|
+
*pdwEffect = DROPEFFECT_NONE;
|
|
84
|
+
}
|
|
85
|
+
return S_OK;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static HRESULT STDMETHODCALLTYPE
|
|
89
|
+
tdt_DragOver(IDropTarget *self, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
|
|
90
|
+
{
|
|
91
|
+
*pdwEffect = DROPEFFECT_COPY;
|
|
92
|
+
return S_OK;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static HRESULT STDMETHODCALLTYPE
|
|
96
|
+
tdt_DragLeave(IDropTarget *self)
|
|
97
|
+
{
|
|
98
|
+
return S_OK;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static HRESULT STDMETHODCALLTYPE
|
|
102
|
+
tdt_Drop(IDropTarget *self, IDataObject *pDataObj, DWORD grfKeyState,
|
|
103
|
+
POINTL pt, DWORD *pdwEffect)
|
|
104
|
+
{
|
|
105
|
+
TeekDropTarget *tdt = (TeekDropTarget *)self;
|
|
106
|
+
FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
107
|
+
STGMEDIUM stg;
|
|
108
|
+
HRESULT hr;
|
|
109
|
+
|
|
110
|
+
*pdwEffect = DROPEFFECT_NONE;
|
|
111
|
+
|
|
112
|
+
hr = IDataObject_GetData(pDataObj, &fmt, &stg);
|
|
113
|
+
if (FAILED(hr)) return hr;
|
|
114
|
+
|
|
115
|
+
HDROP hDrop = (HDROP)GlobalLock(stg.hGlobal);
|
|
116
|
+
if (!hDrop) {
|
|
117
|
+
ReleaseStgMedium(&stg);
|
|
118
|
+
return E_FAIL;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
UINT count = DragQueryFileW(hDrop, 0xFFFFFFFF, NULL, 0);
|
|
122
|
+
UINT i;
|
|
123
|
+
|
|
124
|
+
/* Build a Tcl list of all dropped file paths */
|
|
125
|
+
Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
|
|
126
|
+
Tcl_IncrRefCount(listObj);
|
|
127
|
+
|
|
128
|
+
for (i = 0; i < count; i++) {
|
|
129
|
+
WCHAR wpath[MAX_PATH];
|
|
130
|
+
if (DragQueryFileW(hDrop, i, wpath, MAX_PATH) == 0) continue;
|
|
131
|
+
|
|
132
|
+
/* Convert wide string to UTF-8 for Tcl */
|
|
133
|
+
int utf8_len = WideCharToMultiByte(CP_UTF8, 0, wpath, -1,
|
|
134
|
+
NULL, 0, NULL, NULL);
|
|
135
|
+
if (utf8_len <= 0) continue;
|
|
136
|
+
|
|
137
|
+
char *utf8 = (char *)malloc(utf8_len);
|
|
138
|
+
if (!utf8) continue;
|
|
139
|
+
|
|
140
|
+
WideCharToMultiByte(CP_UTF8, 0, wpath, -1, utf8, utf8_len, NULL, NULL);
|
|
141
|
+
Tcl_ListObjAppendElement(NULL, listObj,
|
|
142
|
+
Tcl_NewStringObj(utf8, -1));
|
|
143
|
+
free(utf8);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Generate single <<DropFile>> event with all paths as a Tcl list */
|
|
147
|
+
Tcl_Obj *script = Tcl_ObjPrintf(
|
|
148
|
+
"event generate %s <<DropFile>> -data {%s}",
|
|
149
|
+
tdt->widget_path, Tcl_GetString(listObj));
|
|
150
|
+
Tcl_IncrRefCount(script);
|
|
151
|
+
Tcl_EvalObjEx(tdt->interp, script, TCL_EVAL_GLOBAL);
|
|
152
|
+
Tcl_DecrRefCount(script);
|
|
153
|
+
Tcl_DecrRefCount(listObj);
|
|
154
|
+
|
|
155
|
+
GlobalUnlock(stg.hGlobal);
|
|
156
|
+
ReleaseStgMedium(&stg);
|
|
157
|
+
*pdwEffect = DROPEFFECT_COPY;
|
|
158
|
+
return S_OK;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* COM vtable */
|
|
162
|
+
static IDropTargetVtbl teek_drop_vtbl = {
|
|
163
|
+
tdt_QueryInterface,
|
|
164
|
+
tdt_AddRef,
|
|
165
|
+
tdt_Release,
|
|
166
|
+
tdt_DragEnter,
|
|
167
|
+
tdt_DragOver,
|
|
168
|
+
tdt_DragLeave,
|
|
169
|
+
tdt_Drop
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/* --------------------------------------------------------- */
|
|
173
|
+
|
|
174
|
+
int
|
|
175
|
+
teek_register_drop_target(Tcl_Interp *interp, Tk_Window tkwin,
|
|
176
|
+
const char *widget_path)
|
|
177
|
+
{
|
|
178
|
+
Drawable drawable = Tk_WindowId(tkwin);
|
|
179
|
+
if (!drawable) {
|
|
180
|
+
Tcl_SetResult(interp, "window has no native handle", TCL_STATIC);
|
|
181
|
+
return TCL_ERROR;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
HWND hwnd = Tk_GetHWND(drawable);
|
|
185
|
+
if (!hwnd) {
|
|
186
|
+
Tcl_SetResult(interp, "could not get HWND", TCL_STATIC);
|
|
187
|
+
return TCL_ERROR;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
HRESULT hr = OleInitialize(NULL);
|
|
191
|
+
if (FAILED(hr)) {
|
|
192
|
+
Tcl_SetResult(interp, "OleInitialize failed", TCL_STATIC);
|
|
193
|
+
return TCL_ERROR;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
TeekDropTarget *tdt = (TeekDropTarget *)malloc(sizeof(TeekDropTarget));
|
|
197
|
+
if (!tdt) {
|
|
198
|
+
Tcl_SetResult(interp, "out of memory", TCL_STATIC);
|
|
199
|
+
return TCL_ERROR;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
tdt->lpVtbl = &teek_drop_vtbl;
|
|
203
|
+
tdt->ref_count = 1;
|
|
204
|
+
tdt->interp = interp;
|
|
205
|
+
tdt->widget_path = strdup(widget_path);
|
|
206
|
+
tdt->hwnd = hwnd;
|
|
207
|
+
|
|
208
|
+
hr = RegisterDragDrop(hwnd, (IDropTarget *)tdt);
|
|
209
|
+
if (FAILED(hr)) {
|
|
210
|
+
free(tdt->widget_path);
|
|
211
|
+
free(tdt);
|
|
212
|
+
if (hr == DRAGDROP_E_ALREADYREGISTERED) {
|
|
213
|
+
return TCL_OK; /* Already registered */
|
|
214
|
+
}
|
|
215
|
+
Tcl_SetResult(interp, "RegisterDragDrop failed", TCL_STATIC);
|
|
216
|
+
return TCL_ERROR;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return TCL_OK;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#else
|
|
223
|
+
|
|
224
|
+
int
|
|
225
|
+
teek_register_drop_target(Tcl_Interp *interp, Tk_Window tkwin,
|
|
226
|
+
const char *widget_path)
|
|
227
|
+
{
|
|
228
|
+
Tcl_SetResult(interp, "file drop not supported on this platform", TCL_STATIC);
|
|
229
|
+
return TCL_ERROR;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#endif /* _WIN32 */
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/* tkdrop_x11.c - X11 file drop target via XDND protocol
|
|
2
|
+
*
|
|
3
|
+
* Implements the XDND (version 5) drop target protocol for receiving
|
|
4
|
+
* file drops from X11 desktop environments. Handles XdndEnter,
|
|
5
|
+
* XdndPosition, XdndDrop client messages and selection transfer.
|
|
6
|
+
*
|
|
7
|
+
* Based on tkdnd (https://github.com/petasis/tkdnd) as reference.
|
|
8
|
+
* See THIRD_PARTY_NOTICES for attribution.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include <tcl.h>
|
|
12
|
+
#include <tk.h>
|
|
13
|
+
#include <X11/Xlib.h>
|
|
14
|
+
#include <X11/Xatom.h>
|
|
15
|
+
#include <string.h>
|
|
16
|
+
#include <stdlib.h>
|
|
17
|
+
#include <ctype.h>
|
|
18
|
+
#include "tkdrop.h"
|
|
19
|
+
|
|
20
|
+
#define XDND_VERSION 5
|
|
21
|
+
|
|
22
|
+
/* Per-window drop target state */
|
|
23
|
+
typedef struct {
|
|
24
|
+
Tcl_Interp *interp;
|
|
25
|
+
char *widget_path;
|
|
26
|
+
Tk_Window tkwin;
|
|
27
|
+
Display *display;
|
|
28
|
+
Window window;
|
|
29
|
+
/* XDND atoms (cached) */
|
|
30
|
+
Atom xdnd_aware;
|
|
31
|
+
Atom xdnd_enter;
|
|
32
|
+
Atom xdnd_position;
|
|
33
|
+
Atom xdnd_status;
|
|
34
|
+
Atom xdnd_drop;
|
|
35
|
+
Atom xdnd_finished;
|
|
36
|
+
Atom xdnd_selection;
|
|
37
|
+
Atom xdnd_type_list;
|
|
38
|
+
Atom xdnd_action_copy;
|
|
39
|
+
Atom text_uri_list;
|
|
40
|
+
Atom teek_drop_prop;
|
|
41
|
+
/* State during drag */
|
|
42
|
+
Window source_window;
|
|
43
|
+
int has_uri_list;
|
|
44
|
+
} TeekXdndState;
|
|
45
|
+
|
|
46
|
+
/* Decode a %XX hex escape in a URI, return decoded char or -1 on error */
|
|
47
|
+
static int
|
|
48
|
+
hex_decode(const char *s)
|
|
49
|
+
{
|
|
50
|
+
int hi, lo;
|
|
51
|
+
hi = s[0];
|
|
52
|
+
lo = s[1];
|
|
53
|
+
if (hi >= '0' && hi <= '9') hi -= '0';
|
|
54
|
+
else if (hi >= 'a' && hi <= 'f') hi = hi - 'a' + 10;
|
|
55
|
+
else if (hi >= 'A' && hi <= 'F') hi = hi - 'A' + 10;
|
|
56
|
+
else return -1;
|
|
57
|
+
if (lo >= '0' && lo <= '9') lo -= '0';
|
|
58
|
+
else if (lo >= 'a' && lo <= 'f') lo = lo - 'a' + 10;
|
|
59
|
+
else if (lo >= 'A' && lo <= 'F') lo = lo - 'A' + 10;
|
|
60
|
+
else return -1;
|
|
61
|
+
return (hi << 4) | lo;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Convert a file:// URI to a filesystem path in-place.
|
|
65
|
+
* Strips the file:// prefix and decodes %XX escapes.
|
|
66
|
+
* Returns pointer to the path within the buffer, or NULL if not a file URI. */
|
|
67
|
+
static char *
|
|
68
|
+
uri_to_path(char *uri)
|
|
69
|
+
{
|
|
70
|
+
char *src, *dst;
|
|
71
|
+
|
|
72
|
+
/* Strip file:// prefix */
|
|
73
|
+
if (strncmp(uri, "file://", 7) == 0) {
|
|
74
|
+
src = uri + 7;
|
|
75
|
+
/* Skip optional hostname (file://localhost/path -> /path) */
|
|
76
|
+
if (*src != '/' && strncmp(src, "localhost", 9) == 0) {
|
|
77
|
+
src += 9;
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
return NULL;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* URL-decode in place */
|
|
84
|
+
dst = src;
|
|
85
|
+
while (*src) {
|
|
86
|
+
if (*src == '%' && src[1] && src[2]) {
|
|
87
|
+
int ch = hex_decode(src + 1);
|
|
88
|
+
if (ch >= 0) {
|
|
89
|
+
*dst++ = (char)ch;
|
|
90
|
+
src += 3;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
*dst++ = *src++;
|
|
95
|
+
}
|
|
96
|
+
*dst = '\0';
|
|
97
|
+
|
|
98
|
+
return uri + 7 + (uri[7] != '/') * 9; /* return start of decoded path */
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Send XdndStatus response to source */
|
|
102
|
+
static void
|
|
103
|
+
send_xdnd_status(TeekXdndState *st, int accept)
|
|
104
|
+
{
|
|
105
|
+
XClientMessageEvent msg;
|
|
106
|
+
memset(&msg, 0, sizeof(msg));
|
|
107
|
+
msg.type = ClientMessage;
|
|
108
|
+
msg.display = st->display;
|
|
109
|
+
msg.window = st->source_window;
|
|
110
|
+
msg.message_type = st->xdnd_status;
|
|
111
|
+
msg.format = 32;
|
|
112
|
+
msg.data.l[0] = st->window; /* target window */
|
|
113
|
+
msg.data.l[1] = accept ? 1 : 0; /* bit 0 = accept */
|
|
114
|
+
msg.data.l[2] = 0; /* empty rectangle */
|
|
115
|
+
msg.data.l[3] = 0;
|
|
116
|
+
msg.data.l[4] = accept ? st->xdnd_action_copy : 0;
|
|
117
|
+
|
|
118
|
+
XSendEvent(st->display, st->source_window, False, NoEventMask,
|
|
119
|
+
(XEvent *)&msg);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Send XdndFinished to source */
|
|
123
|
+
static void
|
|
124
|
+
send_xdnd_finished(TeekXdndState *st, int success)
|
|
125
|
+
{
|
|
126
|
+
XClientMessageEvent msg;
|
|
127
|
+
memset(&msg, 0, sizeof(msg));
|
|
128
|
+
msg.type = ClientMessage;
|
|
129
|
+
msg.display = st->display;
|
|
130
|
+
msg.window = st->source_window;
|
|
131
|
+
msg.message_type = st->xdnd_finished;
|
|
132
|
+
msg.format = 32;
|
|
133
|
+
msg.data.l[0] = st->window;
|
|
134
|
+
msg.data.l[1] = success ? 1 : 0;
|
|
135
|
+
msg.data.l[2] = success ? st->xdnd_action_copy : 0;
|
|
136
|
+
|
|
137
|
+
XSendEvent(st->display, st->source_window, False, NoEventMask,
|
|
138
|
+
(XEvent *)&msg);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Process dropped data (text/uri-list) and generate a single <<DropFile>> event */
|
|
142
|
+
static void
|
|
143
|
+
process_uri_list(TeekXdndState *st, const char *data, unsigned long len)
|
|
144
|
+
{
|
|
145
|
+
char *buf = (char *)malloc(len + 1);
|
|
146
|
+
if (!buf) return;
|
|
147
|
+
memcpy(buf, data, len);
|
|
148
|
+
buf[len] = '\0';
|
|
149
|
+
|
|
150
|
+
/* Build a Tcl list of all dropped file paths */
|
|
151
|
+
Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
|
|
152
|
+
Tcl_IncrRefCount(listObj);
|
|
153
|
+
|
|
154
|
+
/* text/uri-list: one URI per line, \r\n separated, # lines are comments */
|
|
155
|
+
char *line = buf;
|
|
156
|
+
while (line && *line) {
|
|
157
|
+
char *eol = strstr(line, "\r\n");
|
|
158
|
+
if (eol) *eol = '\0';
|
|
159
|
+
|
|
160
|
+
/* Skip comments and empty lines */
|
|
161
|
+
if (*line && *line != '#') {
|
|
162
|
+
char *uri_copy = strdup(line);
|
|
163
|
+
if (uri_copy) {
|
|
164
|
+
char *path = uri_to_path(uri_copy);
|
|
165
|
+
if (path && *path) {
|
|
166
|
+
Tcl_ListObjAppendElement(NULL, listObj,
|
|
167
|
+
Tcl_NewStringObj(path, -1));
|
|
168
|
+
}
|
|
169
|
+
free(uri_copy);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
line = eol ? eol + 2 : NULL;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Generate single <<DropFile>> event with all paths as a Tcl list */
|
|
177
|
+
Tcl_Obj *script = Tcl_ObjPrintf(
|
|
178
|
+
"event generate %s <<DropFile>> -data {%s}",
|
|
179
|
+
st->widget_path, Tcl_GetString(listObj));
|
|
180
|
+
Tcl_IncrRefCount(script);
|
|
181
|
+
Tcl_EvalObjEx(st->interp, script, TCL_EVAL_GLOBAL);
|
|
182
|
+
Tcl_DecrRefCount(script);
|
|
183
|
+
Tcl_DecrRefCount(listObj);
|
|
184
|
+
|
|
185
|
+
free(buf);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Tk event handler for ClientMessage and SelectionNotify */
|
|
189
|
+
static int
|
|
190
|
+
xdnd_event_handler(ClientData clientData, XEvent *eventPtr)
|
|
191
|
+
{
|
|
192
|
+
TeekXdndState *st = (TeekXdndState *)clientData;
|
|
193
|
+
|
|
194
|
+
if (eventPtr->type == ClientMessage) {
|
|
195
|
+
XClientMessageEvent *cm = &eventPtr->xclient;
|
|
196
|
+
|
|
197
|
+
if (cm->message_type == st->xdnd_enter) {
|
|
198
|
+
st->source_window = (Window)cm->data.l[0];
|
|
199
|
+
int version = (cm->data.l[1] >> 24) & 0xFF;
|
|
200
|
+
if (version > XDND_VERSION) return 0;
|
|
201
|
+
|
|
202
|
+
/* Check if text/uri-list is among offered types */
|
|
203
|
+
st->has_uri_list = 0;
|
|
204
|
+
int more_than_3 = cm->data.l[1] & 1;
|
|
205
|
+
|
|
206
|
+
if (more_than_3) {
|
|
207
|
+
/* Fetch XdndTypeList property from source */
|
|
208
|
+
Atom type;
|
|
209
|
+
int format;
|
|
210
|
+
unsigned long count, remaining;
|
|
211
|
+
unsigned char *prop_data = NULL;
|
|
212
|
+
|
|
213
|
+
XGetWindowProperty(st->display, st->source_window,
|
|
214
|
+
st->xdnd_type_list, 0, 1024, False, XA_ATOM,
|
|
215
|
+
&type, &format, &count, &remaining, &prop_data);
|
|
216
|
+
|
|
217
|
+
if (prop_data) {
|
|
218
|
+
Atom *types = (Atom *)prop_data;
|
|
219
|
+
unsigned long i;
|
|
220
|
+
for (i = 0; i < count; i++) {
|
|
221
|
+
if (types[i] == st->text_uri_list) {
|
|
222
|
+
st->has_uri_list = 1;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
XFree(prop_data);
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
/* Types are in data.l[2..4] */
|
|
230
|
+
int i;
|
|
231
|
+
for (i = 2; i <= 4; i++) {
|
|
232
|
+
if ((Atom)cm->data.l[i] == st->text_uri_list) {
|
|
233
|
+
st->has_uri_list = 1;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return 1;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (cm->message_type == st->xdnd_position) {
|
|
242
|
+
send_xdnd_status(st, st->has_uri_list);
|
|
243
|
+
return 1;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (cm->message_type == st->xdnd_drop) {
|
|
247
|
+
if (st->has_uri_list) {
|
|
248
|
+
Time timestamp = (Time)cm->data.l[2];
|
|
249
|
+
XConvertSelection(st->display, st->xdnd_selection,
|
|
250
|
+
st->text_uri_list, st->teek_drop_prop,
|
|
251
|
+
st->window, timestamp);
|
|
252
|
+
} else {
|
|
253
|
+
send_xdnd_finished(st, 0);
|
|
254
|
+
}
|
|
255
|
+
return 1;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (eventPtr->type == SelectionNotify) {
|
|
260
|
+
XSelectionEvent *sel = &eventPtr->xselection;
|
|
261
|
+
if (sel->property == st->teek_drop_prop) {
|
|
262
|
+
Atom type;
|
|
263
|
+
int format;
|
|
264
|
+
unsigned long count, remaining;
|
|
265
|
+
unsigned char *data = NULL;
|
|
266
|
+
|
|
267
|
+
XGetWindowProperty(st->display, st->window,
|
|
268
|
+
st->teek_drop_prop, 0, 65536, True, AnyPropertyType,
|
|
269
|
+
&type, &format, &count, &remaining, &data);
|
|
270
|
+
|
|
271
|
+
if (data && count > 0) {
|
|
272
|
+
process_uri_list(st, (const char *)data, count);
|
|
273
|
+
}
|
|
274
|
+
if (data) XFree(data);
|
|
275
|
+
|
|
276
|
+
send_xdnd_finished(st, 1);
|
|
277
|
+
return 1;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* Tk generic event handler wrapper */
|
|
285
|
+
static int
|
|
286
|
+
xdnd_generic_handler(ClientData clientData, XEvent *eventPtr)
|
|
287
|
+
{
|
|
288
|
+
return xdnd_event_handler(clientData, eventPtr);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
int
|
|
292
|
+
teek_register_drop_target(Tcl_Interp *interp, Tk_Window tkwin,
|
|
293
|
+
const char *widget_path)
|
|
294
|
+
{
|
|
295
|
+
Display *display = Tk_Display(tkwin);
|
|
296
|
+
Window window = Tk_WindowId(tkwin);
|
|
297
|
+
|
|
298
|
+
if (!display || !window) {
|
|
299
|
+
Tcl_SetResult(interp, "window has no X11 display/id", TCL_STATIC);
|
|
300
|
+
return TCL_ERROR;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
TeekXdndState *st = (TeekXdndState *)calloc(1, sizeof(TeekXdndState));
|
|
304
|
+
if (!st) {
|
|
305
|
+
Tcl_SetResult(interp, "out of memory", TCL_STATIC);
|
|
306
|
+
return TCL_ERROR;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
st->interp = interp;
|
|
310
|
+
st->widget_path = strdup(widget_path);
|
|
311
|
+
st->tkwin = tkwin;
|
|
312
|
+
st->display = display;
|
|
313
|
+
st->window = window;
|
|
314
|
+
|
|
315
|
+
/* Cache atoms */
|
|
316
|
+
st->xdnd_aware = Tk_InternAtom(tkwin, "XdndAware");
|
|
317
|
+
st->xdnd_enter = Tk_InternAtom(tkwin, "XdndEnter");
|
|
318
|
+
st->xdnd_position = Tk_InternAtom(tkwin, "XdndPosition");
|
|
319
|
+
st->xdnd_status = Tk_InternAtom(tkwin, "XdndStatus");
|
|
320
|
+
st->xdnd_drop = Tk_InternAtom(tkwin, "XdndDrop");
|
|
321
|
+
st->xdnd_finished = Tk_InternAtom(tkwin, "XdndFinished");
|
|
322
|
+
st->xdnd_selection = Tk_InternAtom(tkwin, "XdndSelection");
|
|
323
|
+
st->xdnd_type_list = Tk_InternAtom(tkwin, "XdndTypeList");
|
|
324
|
+
st->xdnd_action_copy = Tk_InternAtom(tkwin, "XdndActionCopy");
|
|
325
|
+
st->text_uri_list = Tk_InternAtom(tkwin, "text/uri-list");
|
|
326
|
+
st->teek_drop_prop = Tk_InternAtom(tkwin, "TeekDropData");
|
|
327
|
+
|
|
328
|
+
/* Set XdndAware property (version 5) */
|
|
329
|
+
Atom version = XDND_VERSION;
|
|
330
|
+
XChangeProperty(display, window, st->xdnd_aware, XA_ATOM, 32,
|
|
331
|
+
PropModeReplace, (unsigned char *)&version, 1);
|
|
332
|
+
|
|
333
|
+
/* Register generic event handler for ClientMessage + SelectionNotify */
|
|
334
|
+
Tk_CreateGenericHandler(xdnd_generic_handler, (ClientData)st);
|
|
335
|
+
|
|
336
|
+
return TCL_OK;
|
|
337
|
+
}
|
data/ext/teek/tkwin.c
CHANGED
|
@@ -101,6 +101,47 @@ interp_get_root_coords(VALUE self, VALUE window_path)
|
|
|
101
101
|
return rb_ary_new_from_args(2, INT2NUM(x), INT2NUM(y));
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/* ---------------------------------------------------------
|
|
105
|
+
* Interp#window_geometry(window_path)
|
|
106
|
+
*
|
|
107
|
+
* Get a window's screen position and interior size in one call.
|
|
108
|
+
*
|
|
109
|
+
* Arguments:
|
|
110
|
+
* window_path - Tk window path (e.g., ".", ".settings")
|
|
111
|
+
*
|
|
112
|
+
* Returns [root_x, root_y, width, height].
|
|
113
|
+
*
|
|
114
|
+
* root_x/root_y are absolute screen coordinates (Tk_GetRootCoords).
|
|
115
|
+
* width/height are the interior size excluding any border (Tk_Width/Tk_Height).
|
|
116
|
+
* --------------------------------------------------------- */
|
|
117
|
+
|
|
118
|
+
static VALUE
|
|
119
|
+
interp_window_geometry(VALUE self, VALUE window_path)
|
|
120
|
+
{
|
|
121
|
+
struct tcltk_interp *tip = get_interp(self);
|
|
122
|
+
Tk_Window mainWin;
|
|
123
|
+
Tk_Window tkwin;
|
|
124
|
+
int x, y;
|
|
125
|
+
|
|
126
|
+
StringValue(window_path);
|
|
127
|
+
|
|
128
|
+
mainWin = Tk_MainWindow(tip->interp);
|
|
129
|
+
if (!mainWin) {
|
|
130
|
+
rb_raise(eTclError, "Tk not initialized (no main window)");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
tkwin = Tk_NameToWindow(tip->interp, StringValueCStr(window_path), mainWin);
|
|
134
|
+
if (!tkwin) {
|
|
135
|
+
rb_raise(eTclError, "window not found: %s", StringValueCStr(window_path));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Tk_GetRootCoords(tkwin, &x, &y);
|
|
139
|
+
|
|
140
|
+
return rb_ary_new_from_args(4,
|
|
141
|
+
INT2NUM(x), INT2NUM(y),
|
|
142
|
+
INT2NUM(Tk_Width(tkwin)), INT2NUM(Tk_Height(tkwin)));
|
|
143
|
+
}
|
|
144
|
+
|
|
104
145
|
/* ---------------------------------------------------------
|
|
105
146
|
* Interp#coords_to_window(root_x, root_y)
|
|
106
147
|
*
|
|
@@ -223,6 +264,7 @@ Init_tkwin(VALUE cInterp)
|
|
|
223
264
|
{
|
|
224
265
|
rb_define_method(cInterp, "user_inactive_time", interp_user_inactive_time, 0);
|
|
225
266
|
rb_define_method(cInterp, "get_root_coords", interp_get_root_coords, 1);
|
|
267
|
+
rb_define_method(cInterp, "window_geometry", interp_window_geometry, 1);
|
|
226
268
|
rb_define_method(cInterp, "coords_to_window", interp_coords_to_window, 2);
|
|
227
269
|
rb_define_method(cInterp, "native_window_handle", interp_native_window_handle, 1);
|
|
228
270
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Standalone platform detection — no dependencies on the rest of Teek.
|
|
4
|
+
# Safe to require from extconf.rb or any context where the full gem
|
|
5
|
+
# isn't loaded yet.
|
|
6
|
+
|
|
7
|
+
module Teek
|
|
8
|
+
class Platform
|
|
9
|
+
def initialize(platform = RUBY_PLATFORM)
|
|
10
|
+
@platform = platform.freeze
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def darwin? = @platform.include?('darwin')
|
|
14
|
+
def linux? = @platform.include?('linux')
|
|
15
|
+
def windows? = !!(@platform =~ /mingw|mswin|cygwin/)
|
|
16
|
+
|
|
17
|
+
def to_s
|
|
18
|
+
if darwin? then 'darwin'
|
|
19
|
+
elsif windows? then 'windows'
|
|
20
|
+
elsif linux? then 'linux'
|
|
21
|
+
else @platform
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.platform
|
|
27
|
+
@platform ||= Platform.new
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/teek/version.rb
CHANGED