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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -0
  3. data/Rakefile +120 -22
  4. data/ext/teek/extconf.rb +19 -1
  5. data/ext/teek/tcltkbridge.c +38 -2
  6. data/ext/teek/tcltkbridge.h +3 -0
  7. data/ext/teek/tkdrop.c +66 -0
  8. data/ext/teek/tkdrop.h +26 -0
  9. data/ext/teek/tkdrop_macos.m +141 -0
  10. data/ext/teek/tkdrop_win.c +232 -0
  11. data/ext/teek/tkdrop_x11.c +337 -0
  12. data/ext/teek/tkwin.c +42 -0
  13. data/lib/teek/platform.rb +29 -0
  14. data/lib/teek/version.rb +1 -1
  15. data/lib/teek.rb +49 -3
  16. data/teek.gemspec +3 -2
  17. metadata +7 -53
  18. data/sample/calculator.rb +0 -255
  19. data/sample/debug_demo.rb +0 -43
  20. data/sample/gamepad_viewer/assets/controller.png +0 -0
  21. data/sample/gamepad_viewer/gamepad_viewer.rb +0 -554
  22. data/sample/goldberg.rb +0 -1803
  23. data/sample/goldberg_helpers.rb +0 -170
  24. data/sample/optcarrot/thwaite.nes +0 -0
  25. data/sample/optcarrot/vendor/optcarrot/apu.rb +0 -856
  26. data/sample/optcarrot/vendor/optcarrot/config.rb +0 -257
  27. data/sample/optcarrot/vendor/optcarrot/cpu.rb +0 -1162
  28. data/sample/optcarrot/vendor/optcarrot/driver.rb +0 -144
  29. data/sample/optcarrot/vendor/optcarrot/mapper/cnrom.rb +0 -14
  30. data/sample/optcarrot/vendor/optcarrot/mapper/mmc1.rb +0 -105
  31. data/sample/optcarrot/vendor/optcarrot/mapper/mmc3.rb +0 -153
  32. data/sample/optcarrot/vendor/optcarrot/mapper/uxrom.rb +0 -14
  33. data/sample/optcarrot/vendor/optcarrot/nes.rb +0 -105
  34. data/sample/optcarrot/vendor/optcarrot/opt.rb +0 -168
  35. data/sample/optcarrot/vendor/optcarrot/pad.rb +0 -92
  36. data/sample/optcarrot/vendor/optcarrot/palette.rb +0 -65
  37. data/sample/optcarrot/vendor/optcarrot/ppu.rb +0 -1468
  38. data/sample/optcarrot/vendor/optcarrot/rom.rb +0 -143
  39. data/sample/optcarrot/vendor/optcarrot.rb +0 -14
  40. data/sample/optcarrot.rb +0 -354
  41. data/sample/paint/assets/bucket.png +0 -0
  42. data/sample/paint/assets/cursor.png +0 -0
  43. data/sample/paint/assets/eraser.png +0 -0
  44. data/sample/paint/assets/pencil.png +0 -0
  45. data/sample/paint/assets/spray.png +0 -0
  46. data/sample/paint/layer.rb +0 -255
  47. data/sample/paint/layer_manager.rb +0 -179
  48. data/sample/paint/paint_demo.rb +0 -837
  49. data/sample/paint/sparse_pixel_buffer.rb +0 -202
  50. data/sample/sdl2_demo.rb +0 -318
  51. data/sample/threading_demo.rb +0 -494
  52. data/sample/yam/assets/MINESWEEPER_0.png +0 -0
  53. data/sample/yam/assets/MINESWEEPER_1.png +0 -0
  54. data/sample/yam/assets/MINESWEEPER_2.png +0 -0
  55. data/sample/yam/assets/MINESWEEPER_3.png +0 -0
  56. data/sample/yam/assets/MINESWEEPER_4.png +0 -0
  57. data/sample/yam/assets/MINESWEEPER_5.png +0 -0
  58. data/sample/yam/assets/MINESWEEPER_6.png +0 -0
  59. data/sample/yam/assets/MINESWEEPER_7.png +0 -0
  60. data/sample/yam/assets/MINESWEEPER_8.png +0 -0
  61. data/sample/yam/assets/MINESWEEPER_F.png +0 -0
  62. data/sample/yam/assets/MINESWEEPER_M.png +0 -0
  63. data/sample/yam/assets/MINESWEEPER_X.png +0 -0
  64. data/sample/yam/assets/click.wav +0 -0
  65. data/sample/yam/assets/explosion.wav +0 -0
  66. data/sample/yam/assets/flag.wav +0 -0
  67. data/sample/yam/assets/music.mp3 +0 -0
  68. data/sample/yam/assets/sweep.wav +0 -0
  69. 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Teek
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.4"
5
5
  end