| |
1 /** |
| |
2 * @file tcl_signals.c Gaim Tcl signal API |
| |
3 * |
| |
4 * gaim |
| |
5 * |
| |
6 * Copyright (C) 2003 Ethan Blanton <eblanton@cs.purdue.edu> |
| |
7 * |
| |
8 * This program is free software; you can redistribute it and/or modify |
| |
9 * it under the terms of the GNU General Public License as published by |
| |
10 * the Free Software Foundation; either version 2 of the License, or |
| |
11 * (at your option) any later version. |
| |
12 * |
| |
13 * This program is distributed in the hope that it will be useful, |
| |
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| |
16 * GNU General Public License for more details. |
| |
17 * |
| |
18 * You should have received a copy of the GNU General Public License |
| |
19 * along with this program; if not, write to the Free Software |
| |
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
21 */ |
| |
22 #include <tcl.h> |
| |
23 #include <stdarg.h> |
| |
24 |
| |
25 #include "tcl_gaim.h" |
| |
26 |
| |
27 #include "internal.h" |
| |
28 #include "connection.h" |
| |
29 #include "conversation.h" |
| |
30 #include "signals.h" |
| |
31 #include "debug.h" |
| |
32 #include "value.h" |
| |
33 #include "core.h" |
| |
34 |
| |
35 static GList *tcl_callbacks; |
| |
36 |
| |
37 static void *tcl_signal_callback(va_list args, struct tcl_signal_handler *handler); |
| |
38 static Tcl_Obj *new_cb_namespace (void); |
| |
39 |
| |
40 void tcl_signal_init() |
| |
41 { |
| |
42 tcl_callbacks = NULL; |
| |
43 } |
| |
44 |
| |
45 void tcl_signal_handler_free(struct tcl_signal_handler *handler) |
| |
46 { |
| |
47 if (handler == NULL) |
| |
48 return; |
| |
49 |
| |
50 Tcl_DecrRefCount(handler->signal); |
| |
51 if (handler->namespace) |
| |
52 Tcl_DecrRefCount(handler->namespace); |
| |
53 g_free(handler); |
| |
54 } |
| |
55 |
| |
56 void tcl_signal_cleanup(Tcl_Interp *interp) |
| |
57 { |
| |
58 GList *cur; |
| |
59 struct tcl_signal_handler *handler; |
| |
60 |
| |
61 for (cur = tcl_callbacks; cur != NULL; cur = g_list_next(cur)) { |
| |
62 handler = cur->data; |
| |
63 if (handler->interp == interp) { |
| |
64 tcl_signal_handler_free(handler); |
| |
65 cur->data = NULL; |
| |
66 } |
| |
67 } |
| |
68 tcl_callbacks = g_list_remove_all(tcl_callbacks, NULL); |
| |
69 } |
| |
70 |
| |
71 gboolean tcl_signal_connect(struct tcl_signal_handler *handler) |
| |
72 { |
| |
73 GString *proc; |
| |
74 |
| |
75 gaim_signal_get_values(handler->instance, |
| |
76 Tcl_GetString(handler->signal), |
| |
77 &handler->returntype, &handler->nargs, |
| |
78 &handler->argtypes); |
| |
79 |
| |
80 tcl_signal_disconnect(handler->interp, Tcl_GetString(handler->signal), |
| |
81 handler->interp); |
| |
82 |
| |
83 if (!gaim_signal_connect_vargs(handler->instance, |
| |
84 Tcl_GetString(handler->signal), |
| |
85 (void *)handler->interp, |
| |
86 GAIM_CALLBACK(tcl_signal_callback), |
| |
87 (void *)handler)) |
| |
88 return FALSE; |
| |
89 |
| |
90 handler->namespace = new_cb_namespace (); |
| |
91 Tcl_IncrRefCount(handler->namespace); |
| |
92 proc = g_string_new(""); |
| |
93 g_string_append_printf(proc, "namespace eval %s { proc cb { %s } { %s } }", |
| |
94 Tcl_GetString(handler->namespace), |
| |
95 Tcl_GetString(handler->args), |
| |
96 Tcl_GetString(handler->proc)); |
| |
97 if (Tcl_Eval(handler->interp, proc->str) != TCL_OK) { |
| |
98 Tcl_DecrRefCount(handler->namespace); |
| |
99 g_string_free(proc, TRUE); |
| |
100 return FALSE; |
| |
101 } |
| |
102 g_string_free(proc, TRUE); |
| |
103 |
| |
104 tcl_callbacks = g_list_append(tcl_callbacks, (gpointer)handler); |
| |
105 |
| |
106 return TRUE; |
| |
107 } |
| |
108 |
| |
109 void tcl_signal_disconnect(void *instance, const char *signal, Tcl_Interp *interp) |
| |
110 { |
| |
111 GList *cur; |
| |
112 struct tcl_signal_handler *handler; |
| |
113 gboolean found = FALSE; |
| |
114 GString *cmd; |
| |
115 |
| |
116 for (cur = tcl_callbacks; cur != NULL; cur = g_list_next(cur)) { |
| |
117 handler = cur->data; |
| |
118 if (handler->interp == interp && handler->instance == instance |
| |
119 && !strcmp(signal, Tcl_GetString(handler->signal))) { |
| |
120 gaim_signal_disconnect(instance, signal, handler->interp, |
| |
121 GAIM_CALLBACK(tcl_signal_callback)); |
| |
122 cmd = g_string_sized_new(64); |
| |
123 g_string_printf(cmd, "namespace delete %s", |
| |
124 Tcl_GetString(handler->namespace)); |
| |
125 Tcl_EvalEx(interp, cmd->str, -1, TCL_EVAL_GLOBAL); |
| |
126 tcl_signal_handler_free(handler); |
| |
127 g_string_free(cmd, TRUE); |
| |
128 cur->data = NULL; |
| |
129 found = TRUE; |
| |
130 break; |
| |
131 } |
| |
132 } |
| |
133 if (found) |
| |
134 tcl_callbacks = g_list_remove_all(tcl_callbacks, NULL); |
| |
135 } |
| |
136 |
| |
137 static GaimStringref *ref_type(GaimSubType type) |
| |
138 { |
| |
139 switch (type) { |
| |
140 case GAIM_SUBTYPE_ACCOUNT: |
| |
141 return GaimTclRefAccount; |
| |
142 case GAIM_SUBTYPE_CONNECTION: |
| |
143 return GaimTclRefConnection; |
| |
144 case GAIM_SUBTYPE_CONVERSATION: |
| |
145 return GaimTclRefConversation; |
| |
146 case GAIM_SUBTYPE_PLUGIN: |
| |
147 return GaimTclRefPlugin; |
| |
148 case GAIM_SUBTYPE_STATUS: |
| |
149 return GaimTclRefStatus; |
| |
150 case GAIM_SUBTYPE_XFER: |
| |
151 return GaimTclRefXfer; |
| |
152 default: |
| |
153 return NULL; |
| |
154 } |
| |
155 } |
| |
156 |
| |
157 static void *tcl_signal_callback(va_list args, struct tcl_signal_handler *handler) |
| |
158 { |
| |
159 GString *name, *val; |
| |
160 GaimBlistNode *node; |
| |
161 int error, i; |
| |
162 void *retval = NULL; |
| |
163 Tcl_Obj *cmd, *arg, *result; |
| |
164 void **vals; /* Used for inout parameters */ |
| |
165 char ***strs; |
| |
166 |
| |
167 vals = g_new0(void *, handler->nargs); |
| |
168 strs = g_new0(char **, handler->nargs); |
| |
169 name = g_string_sized_new(32); |
| |
170 val = g_string_sized_new(32); |
| |
171 |
| |
172 cmd = Tcl_NewListObj(0, NULL); |
| |
173 Tcl_IncrRefCount(cmd); |
| |
174 |
| |
175 arg = Tcl_DuplicateObj(handler->namespace); |
| |
176 Tcl_AppendStringsToObj(arg, "::cb", NULL); |
| |
177 Tcl_ListObjAppendElement(handler->interp, cmd, arg); |
| |
178 |
| |
179 for (i = 0; i < handler->nargs; i++) { |
| |
180 if (gaim_value_is_outgoing(handler->argtypes[i])) |
| |
181 g_string_printf(name, "%s::arg%d", |
| |
182 Tcl_GetString(handler->namespace), i); |
| |
183 |
| |
184 switch(gaim_value_get_type(handler->argtypes[i])) { |
| |
185 case GAIM_TYPE_UNKNOWN: /* What? I guess just pass the word ... */ |
| |
186 /* treat this as a pointer, but complain first */ |
| |
187 gaim_debug(GAIM_DEBUG_ERROR, "tcl", "unknown GaimValue type %d\n", |
| |
188 gaim_value_get_type(handler->argtypes[i])); |
| |
189 case GAIM_TYPE_POINTER: |
| |
190 case GAIM_TYPE_OBJECT: |
| |
191 case GAIM_TYPE_BOXED: |
| |
192 /* These are all "pointer" types to us */ |
| |
193 if (gaim_value_is_outgoing(handler->argtypes[i])) |
| |
194 gaim_debug_error("tcl", "pointer types do not currently support outgoing arguments\n"); |
| |
195 arg = gaim_tcl_ref_new(GaimTclRefPointer, va_arg(args, void *)); |
| |
196 break; |
| |
197 case GAIM_TYPE_BOOLEAN: |
| |
198 if (gaim_value_is_outgoing(handler->argtypes[i])) { |
| |
199 vals[i] = va_arg(args, gboolean *); |
| |
200 Tcl_LinkVar(handler->interp, name->str, |
| |
201 (char *)&vals[i], TCL_LINK_BOOLEAN); |
| |
202 arg = Tcl_NewStringObj(name->str, -1); |
| |
203 } else { |
| |
204 arg = Tcl_NewBooleanObj(va_arg(args, gboolean)); |
| |
205 } |
| |
206 break; |
| |
207 case GAIM_TYPE_CHAR: |
| |
208 case GAIM_TYPE_UCHAR: |
| |
209 case GAIM_TYPE_SHORT: |
| |
210 case GAIM_TYPE_USHORT: |
| |
211 case GAIM_TYPE_INT: |
| |
212 case GAIM_TYPE_UINT: |
| |
213 case GAIM_TYPE_LONG: |
| |
214 case GAIM_TYPE_ULONG: |
| |
215 case GAIM_TYPE_ENUM: |
| |
216 /* I should really cast these individually to |
| |
217 * preserve as much information as possible ... |
| |
218 * but heh */ |
| |
219 if (gaim_value_is_outgoing(handler->argtypes[i])) { |
| |
220 vals[i] = va_arg(args, int *); |
| |
221 Tcl_LinkVar(handler->interp, name->str, |
| |
222 vals[i], TCL_LINK_INT); |
| |
223 arg = Tcl_NewStringObj(name->str, -1); |
| |
224 } else { |
| |
225 arg = Tcl_NewIntObj(va_arg(args, int)); |
| |
226 } |
| |
227 case GAIM_TYPE_INT64: |
| |
228 case GAIM_TYPE_UINT64: |
| |
229 /* Tcl < 8.4 doesn't have wide ints, so we have ugly |
| |
230 * ifdefs in here */ |
| |
231 if (gaim_value_is_outgoing(handler->argtypes[i])) { |
| |
232 vals[i] = (void *)va_arg(args, gint64 *); |
| |
233 #if (TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 4) |
| |
234 Tcl_LinkVar(handler->interp, name->str, |
| |
235 vals[i], TCL_LINK_WIDE_INT); |
| |
236 #else |
| |
237 /* This is going to cause weirdness at best, |
| |
238 * but what do you want ... we're losing |
| |
239 * precision */ |
| |
240 Tcl_LinkVar(handler->interp, name->str, |
| |
241 vals[i], TCL_LINK_INT); |
| |
242 #endif /* Tcl >= 8.4 */ |
| |
243 arg = Tcl_NewStringObj(name->str, -1); |
| |
244 } else { |
| |
245 #if (TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 4) |
| |
246 arg = Tcl_NewWideIntObj(va_arg(args, gint64)); |
| |
247 #else |
| |
248 arg = Tcl_NewIntObj((int)va_arg(args, int)); |
| |
249 #endif /* Tcl >= 8.4 */ |
| |
250 } |
| |
251 break; |
| |
252 case GAIM_TYPE_STRING: |
| |
253 if (gaim_value_is_outgoing(handler->argtypes[i])) { |
| |
254 strs[i] = va_arg(args, char **); |
| |
255 if (strs[i] == NULL || *strs[i] == NULL) { |
| |
256 vals[i] = ckalloc(1); |
| |
257 *(char *)vals[i] = '\0'; |
| |
258 } else { |
| |
259 vals[i] = ckalloc(strlen(*strs[i]) + 1); |
| |
260 strcpy(vals[i], *strs[i]); |
| |
261 } |
| |
262 Tcl_LinkVar(handler->interp, name->str, |
| |
263 (char *)&vals[i], TCL_LINK_STRING); |
| |
264 arg = Tcl_NewStringObj(name->str, -1); |
| |
265 } else { |
| |
266 arg = Tcl_NewStringObj(va_arg(args, char *), -1); |
| |
267 } |
| |
268 break; |
| |
269 case GAIM_TYPE_SUBTYPE: |
| |
270 switch (gaim_value_get_subtype(handler->argtypes[i])) { |
| |
271 case GAIM_SUBTYPE_UNKNOWN: |
| |
272 gaim_debug(GAIM_DEBUG_ERROR, "tcl", "subtype unknown\n"); |
| |
273 case GAIM_SUBTYPE_ACCOUNT: |
| |
274 case GAIM_SUBTYPE_CONNECTION: |
| |
275 case GAIM_SUBTYPE_CONVERSATION: |
| |
276 case GAIM_SUBTYPE_STATUS: |
| |
277 case GAIM_SUBTYPE_PLUGIN: |
| |
278 case GAIM_SUBTYPE_XFER: |
| |
279 if (gaim_value_is_outgoing(handler->argtypes[i])) |
| |
280 gaim_debug_error("tcl", "pointer subtypes do not currently support outgoing arguments\n"); |
| |
281 arg = gaim_tcl_ref_new(ref_type(gaim_value_get_subtype(handler->argtypes[i])), va_arg(args, void *)); |
| |
282 break; |
| |
283 case GAIM_SUBTYPE_BLIST: |
| |
284 case GAIM_SUBTYPE_BLIST_BUDDY: |
| |
285 case GAIM_SUBTYPE_BLIST_GROUP: |
| |
286 case GAIM_SUBTYPE_BLIST_CHAT: |
| |
287 /* We're going to switch again for code-deduping */ |
| |
288 if (gaim_value_is_outgoing(handler->argtypes[i])) |
| |
289 node = *va_arg(args, GaimBlistNode **); |
| |
290 else |
| |
291 node = va_arg(args, GaimBlistNode *); |
| |
292 switch (node->type) { |
| |
293 case GAIM_BLIST_GROUP_NODE: |
| |
294 arg = Tcl_NewListObj(0, NULL); |
| |
295 Tcl_ListObjAppendElement(handler->interp, arg, |
| |
296 Tcl_NewStringObj("group", -1)); |
| |
297 Tcl_ListObjAppendElement(handler->interp, arg, |
| |
298 Tcl_NewStringObj(((GaimGroup *)node)->name, -1)); |
| |
299 break; |
| |
300 case GAIM_BLIST_CONTACT_NODE: |
| |
301 /* g_string_printf(val, "contact {%s}", Contact Name? ); */ |
| |
302 arg = Tcl_NewStringObj("contact", -1); |
| |
303 break; |
| |
304 case GAIM_BLIST_BUDDY_NODE: |
| |
305 arg = Tcl_NewListObj(0, NULL); |
| |
306 Tcl_ListObjAppendElement(handler->interp, arg, |
| |
307 Tcl_NewStringObj("buddy", -1)); |
| |
308 Tcl_ListObjAppendElement(handler->interp, arg, |
| |
309 Tcl_NewStringObj(((GaimBuddy *)node)->name, -1)); |
| |
310 Tcl_ListObjAppendElement(handler->interp, arg, |
| |
311 gaim_tcl_ref_new(GaimTclRefAccount, |
| |
312 ((GaimBuddy *)node)->account)); |
| |
313 break; |
| |
314 case GAIM_BLIST_CHAT_NODE: |
| |
315 arg = Tcl_NewListObj(0, NULL); |
| |
316 Tcl_ListObjAppendElement(handler->interp, arg, |
| |
317 Tcl_NewStringObj("chat", -1)); |
| |
318 Tcl_ListObjAppendElement(handler->interp, arg, |
| |
319 Tcl_NewStringObj(((GaimChat *)node)->alias, -1)); |
| |
320 Tcl_ListObjAppendElement(handler->interp, arg, |
| |
321 gaim_tcl_ref_new(GaimTclRefAccount, |
| |
322 ((GaimChat *)node)->account)); |
| |
323 break; |
| |
324 case GAIM_BLIST_OTHER_NODE: |
| |
325 arg = Tcl_NewStringObj("other", -1); |
| |
326 break; |
| |
327 } |
| |
328 break; |
| |
329 } |
| |
330 } |
| |
331 Tcl_ListObjAppendElement(handler->interp, cmd, arg); |
| |
332 } |
| |
333 |
| |
334 /* Call the friggin' procedure already */ |
| |
335 if ((error = Tcl_EvalObjEx(handler->interp, cmd, TCL_EVAL_GLOBAL)) != TCL_OK) { |
| |
336 gaim_debug(GAIM_DEBUG_ERROR, "tcl", "error evaluating callback: %s\n", |
| |
337 Tcl_GetString(Tcl_GetObjResult(handler->interp))); |
| |
338 } else { |
| |
339 result = Tcl_GetObjResult(handler->interp); |
| |
340 /* handle return values -- strings and words only */ |
| |
341 if (handler->returntype) { |
| |
342 if (gaim_value_get_type(handler->returntype) == GAIM_TYPE_STRING) { |
| |
343 retval = (void *)g_strdup(Tcl_GetString(result)); |
| |
344 } else { |
| |
345 if ((error = Tcl_GetIntFromObj(handler->interp, result, (int *)&retval)) != TCL_OK) { |
| |
346 gaim_debug(GAIM_DEBUG_ERROR, "tcl", "Error retrieving procedure result: %s\n", |
| |
347 Tcl_GetString(Tcl_GetObjResult(handler->interp))); |
| |
348 retval = NULL; |
| |
349 } |
| |
350 } |
| |
351 } |
| |
352 } |
| |
353 |
| |
354 /* And finally clean up */ |
| |
355 for (i = 0; i < handler->nargs; i++) { |
| |
356 g_string_printf(name, "%s::arg%d", |
| |
357 Tcl_GetString(handler->namespace), i); |
| |
358 if (gaim_value_is_outgoing(handler->argtypes[i]) |
| |
359 && gaim_value_get_type(handler->argtypes[i]) != GAIM_TYPE_SUBTYPE) |
| |
360 Tcl_UnlinkVar(handler->interp, name->str); |
| |
361 |
| |
362 /* We basically only have to deal with strings on the |
| |
363 * way out */ |
| |
364 switch (gaim_value_get_type(handler->argtypes[i])) { |
| |
365 case GAIM_TYPE_STRING: |
| |
366 if (gaim_value_is_outgoing(handler->argtypes[i])) { |
| |
367 if (vals[i] != NULL && *(char **)vals[i] != NULL) { |
| |
368 g_free(*strs[i]); |
| |
369 *strs[i] = g_strdup(vals[i]); |
| |
370 } |
| |
371 ckfree(vals[i]); |
| |
372 } |
| |
373 break; |
| |
374 default: |
| |
375 /* nothing */ |
| |
376 ; |
| |
377 } |
| |
378 } |
| |
379 |
| |
380 g_string_free(name, TRUE); |
| |
381 g_string_free(val, TRUE); |
| |
382 g_free(vals); |
| |
383 g_free(strs); |
| |
384 |
| |
385 return retval; |
| |
386 } |
| |
387 |
| |
388 static Tcl_Obj *new_cb_namespace () |
| |
389 { |
| |
390 static int cbnum; |
| |
391 char name[32]; |
| |
392 |
| |
393 g_snprintf (name, sizeof(name), "::gaim::_callback::cb_%d", cbnum++); |
| |
394 return Tcl_NewStringObj (name, -1); |
| |
395 } |