Added SIGCHLD handling to avoid zombie processes.
[python-purple] / purple.pyx
1 #
2 #  Copyright (c) 2008 INdT - Instituto Nokia de Tecnologia
3 #
4 #  This file is part of python-purple.
5 #
6 #  python-purple is free software: you can redistribute it and/or modify
7 #  it under the terms of the GNU General Public License as published by
8 #  the Free Software Foundation, either version 3 of the License, or
9 #  (at your option) any later version.
10 #
11 #  python-purple is distributed in the hope that it will be useful,
12 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #  GNU General Public License for more details.
15 #
16 #  You should have received a copy of the GNU General Public License
17 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 cimport purple
21
22 cdef extern from "c_purple.h":
23     glib.guint glib_input_add(glib.gint fd, eventloop.PurpleInputCondition condition, eventloop.PurpleInputFunction function, glib.gpointer data)
24
25 import ecore
26 import signal
27
28 cdef glib.GHashTable *c_ui_info
29
30 c_ui_info = NULL
31
32 cdef char *c_ui_name
33 cdef char *c_ui_version
34 cdef char *c_ui_website
35 cdef char *c_ui_dev_website
36
37 cdef account.PurpleAccountUiOps c_account_ui_ops
38 cdef blist.PurpleBlistUiOps c_blist_ui_ops
39 cdef connection.PurpleConnectionUiOps c_conn_ui_ops
40 cdef conversation.PurpleConversationUiOps c_conv_ui_ops
41 cdef core.PurpleCoreUiOps c_core_ui_ops
42 cdef eventloop.PurpleEventLoopUiOps c_eventloop_ui_ops
43 #cdef ft.PurpleXferUiOps c_ft_ui_ops
44 cdef notify.PurpleNotifyUiOps c_notify_ui_ops
45 #cdef privacy.PurplePrivacyUiOps c_privacy_ui_ops
46 cdef request.PurpleRequestUiOps c_request_ui_ops
47 #cdef roomlist.PurpleRoomlistUiOps c_rlist_ui_ops
48
49 include "account_cbs.pxd"
50 include "blist_cbs.pxd"
51 include "connection_cbs.pxd"
52 include "conversation_cbs.pxd"
53 #include "xfer_cbs.pxd"
54 include "notify_cbs.pxd"
55 #include "privacy_cbs.pxd"
56 include "request_cbs.pxd"
57 #include "roomlist_cbs.pxd"
58 include "signal_cbs.pxd"
59
60 include "util.pxd"
61
62 cdef class Purple:
63     '''Purple class.
64
65     @param ui_name ID of the UI using the purple.
66         This should be a unique ID, registered with the purple team.
67     @param ui_version UI version.
68     @param ui_website UI website.
69     @param ui_dev_website UI development website.
70     @param debug_enabled True to enable debug messages.
71     @param default_path Custom settings directory
72     '''
73
74
75     def __init__(self, ui_name, ui_version, ui_website, ui_dev_website, \
76             debug_enabled=None, default_path=None):
77
78         global c_ui_name
79         global c_ui_version
80         global c_ui_website
81         global c_ui_dev_website
82
83         c_ui_name = ui_name
84         c_ui_version = ui_version
85         c_ui_website = ui_website
86         c_ui_dev_website = ui_dev_website
87
88         if debug_enabled:
89             debug.purple_debug_set_enabled(debug_enabled)
90
91         if default_path:
92             util.purple_util_set_user_dir(default_path)
93
94         # adds glib iteration inside ecore main loop
95         ecore.timer_add(0.001, self.__glib_iteration_when_idle)
96
97         # libpurple's built-in DNS resolution forks processes to perform
98         # blocking lookups without blocking the main process.  It does not
99         # handle SIGCHLD itself, so if the UI does not you quickly get an army
100         # of zombie subprocesses marching around.
101         signal.signal(signal.SIGCHLD, signal.SIG_IGN)
102
103     def destroy(self):
104         core.purple_core_quit()
105
106     def __get_ui_name(self):
107         '''Returns the UI name.
108
109         @return UI name.
110         '''
111
112         global c_ui_name
113         return str(c_ui_name)
114     ui_name = property(__get_ui_name)
115
116     cdef void __core_ui_ops_ui_prefs_init(self):
117         debug.purple_debug_info("core_ui_ops", "%s", "ui_prefs_init\n")
118         prefs.purple_prefs_load()
119
120         prefs.purple_prefs_add_none("/carman")
121
122     cdef void __core_ui_ops_debug_init(self):
123         debug.purple_debug_info("core_ui_ops", "%s", "debug_ui_init\n")
124         pass
125
126     cdef void __core_ui_ops_ui_init(self):
127         debug.purple_debug_info("core_ui_ops", "%s", "ui_init\n")
128
129         account.purple_accounts_set_ui_ops(&c_account_ui_ops)
130         connection.purple_connections_set_ui_ops(&c_conn_ui_ops)
131         blist.purple_blist_set_ui_ops(&c_blist_ui_ops)
132         conversation.purple_conversations_set_ui_ops(&c_conv_ui_ops)
133         notify.purple_notify_set_ui_ops(&c_notify_ui_ops)
134         #privacy.purple_privacy_set_ui_ops(&c_privacy_ui_ops)
135         request.purple_request_set_ui_ops(&c_request_ui_ops)
136         #ft.purple_xfers_set_ui_ops(&c_ft_ui_ops)
137         #roomlist.purple_roomlist_set_ui_ops(&c_rlist_ui_ops)
138
139     cdef void __core_ui_ops_quit(self):
140         debug.purple_debug_info("core_ui_ops", "%s", "quit\n")
141
142         account.purple_accounts_set_ui_ops(NULL)
143         connection.purple_connections_set_ui_ops(NULL)
144         blist.purple_blist_set_ui_ops(NULL)
145         conversation.purple_conversations_set_ui_ops(NULL)
146         notify.purple_notify_set_ui_ops(NULL)
147         #privacy.purple_privacy_set_ui_ops(NULL)
148         request.purple_request_set_ui_ops(NULL)
149         #ft.purple_xfers_set_ui_ops(NULL)
150         #roomlist.purple_roomlist_set_ui_ops(NULL)
151
152         if self.c_ui_info:
153             glib.g_hash_table_destroy(<glib.GHashTable *> self.c_ui_info)
154
155     cdef glib.GHashTable *__core_ui_ops_get_ui_info(self):
156         global c_ui_info
157         global c_ui_name
158         global c_ui_version
159         global c_ui_website
160         global c_ui_dev_website
161
162         if c_ui_info == NULL:
163             c_ui_info = glib.g_hash_table_new(glib.g_str_hash, \
164                     glib.g_str_equal)
165
166             glib.g_hash_table_insert(c_ui_info, "name", c_ui_name)
167             glib.g_hash_table_insert(c_ui_info, "version", c_ui_version)
168             glib.g_hash_table_insert(c_ui_info, "website", c_ui_website)
169             glib.g_hash_table_insert(c_ui_info, "dev_website", c_ui_dev_website)
170         return c_ui_info
171
172     def __glib_iteration_when_idle(self):
173         glib.g_main_context_iteration(NULL, False)
174         return True
175
176     def purple_init(self):
177         '''Initializes the purple.
178
179         This will setup preferences for all the core subsystems.
180         '''
181
182         global c_ui_name
183
184         c_account_ui_ops.notify_added = notify_added
185         c_account_ui_ops.status_changed = status_changed
186         c_account_ui_ops.request_add = request_add
187         c_account_ui_ops.request_authorize = request_authorize
188         c_account_ui_ops.close_account_request = close_account_request
189
190         c_blist_ui_ops.new_list = new_list
191         c_blist_ui_ops.new_node = new_node
192         c_blist_ui_ops.show = show
193         c_blist_ui_ops.update = update
194         c_blist_ui_ops.remove = remove
195         c_blist_ui_ops.destroy = destroy
196         c_blist_ui_ops.set_visible = set_visible
197         c_blist_ui_ops.request_add_buddy = request_add_buddy
198         c_blist_ui_ops.request_add_chat = request_add_chat
199         c_blist_ui_ops.request_add_group = request_add_group
200
201         c_conn_ui_ops.connect_progress = connect_progress
202         c_conn_ui_ops.connected = connected
203         c_conn_ui_ops.disconnected = disconnected
204         c_conn_ui_ops.notice = notice
205         c_conn_ui_ops.report_disconnect = report_disconnect
206         c_conn_ui_ops.network_connected = network_connected
207         c_conn_ui_ops.network_disconnected = network_disconnected
208         c_conn_ui_ops.report_disconnect_reason = report_disconnect_reason
209
210         c_conv_ui_ops.create_conversation = create_conversation
211         c_conv_ui_ops.destroy_conversation = destroy_conversation
212         c_conv_ui_ops.write_chat = write_chat
213         c_conv_ui_ops.write_im = write_im
214         c_conv_ui_ops.write_conv = write_conv
215         c_conv_ui_ops.chat_add_users = chat_add_users
216         c_conv_ui_ops.chat_rename_user = chat_rename_user
217         c_conv_ui_ops.chat_remove_users = chat_remove_users
218         c_conv_ui_ops.chat_update_user = chat_update_user
219         c_conv_ui_ops.present = present
220         c_conv_ui_ops.has_focus = has_focus
221         c_conv_ui_ops.custom_smiley_add = custom_smiley_add
222         c_conv_ui_ops.custom_smiley_write = custom_smiley_write
223         c_conv_ui_ops.custom_smiley_close = custom_smiley_close
224         c_conv_ui_ops.send_confirm = send_confirm
225
226         c_notify_ui_ops.notify_message = notify_message
227         c_notify_ui_ops.notify_email = notify_email
228         c_notify_ui_ops.notify_emails = notify_emails
229         c_notify_ui_ops.notify_formatted = notify_formatted
230         c_notify_ui_ops.notify_searchresults = notify_searchresults
231         c_notify_ui_ops.notify_searchresults_new_rows = notify_searchresults_new_rows
232         c_notify_ui_ops.notify_userinfo = notify_userinfo
233         c_notify_ui_ops.notify_uri = notify_uri
234         c_notify_ui_ops.close_notify = close_notify
235
236         c_request_ui_ops.request_input = request_input
237         c_request_ui_ops.request_choice = request_choice
238         c_request_ui_ops.request_action = request_action
239         c_request_ui_ops.request_fields = request_fields
240         c_request_ui_ops.request_file = request_file
241         c_request_ui_ops.close_request = close_request
242         c_request_ui_ops.request_folder = request_folder
243
244         c_core_ui_ops.ui_prefs_init = <void (*)()> self.__core_ui_ops_ui_prefs_init
245         c_core_ui_ops.debug_ui_init = <void (*)()> self.__core_ui_ops_debug_init
246         c_core_ui_ops.ui_init = <void (*)()> self.__core_ui_ops_ui_init
247         c_core_ui_ops.quit = <void (*)()> self.__core_ui_ops_quit
248         c_core_ui_ops.get_ui_info = <glib.GHashTable* (*)()> self.__core_ui_ops_get_ui_info
249
250         c_eventloop_ui_ops.timeout_add = glib.g_timeout_add
251         c_eventloop_ui_ops.timeout_remove = glib.g_source_remove
252         c_eventloop_ui_ops.input_add = glib_input_add
253         c_eventloop_ui_ops.input_remove = glib.g_source_remove
254         c_eventloop_ui_ops.input_get_error = NULL
255         c_eventloop_ui_ops.timeout_add_seconds = NULL
256
257         core.purple_core_set_ui_ops(&c_core_ui_ops)
258         eventloop.purple_eventloop_set_ui_ops(&c_eventloop_ui_ops)
259
260         # initialize purple core
261         ret = core.purple_core_init(c_ui_name)
262         if ret is False:
263             debug.purple_debug_fatal("main", "%s", "libpurple " \
264                                        "initialization failed.\n")
265             return False
266
267         # check if there is another instance of libpurple running
268         if core.purple_core_ensure_single_instance() == False:
269             debug.purple_debug_fatal("main", "%s", "Another instance of " \
270                                       "libpurple is already running.\n")
271             core.purple_core_quit()
272             return False
273
274         # create and load the buddy list
275         blist.purple_set_blist(blist.purple_blist_new())
276         blist.purple_blist_load()
277
278         # load pounces
279         pounce.purple_pounces_load()
280
281         return ret
282
283     def add_callback(self, type, name, callback):
284         '''Adds a callback with given name inside callback's type.
285
286         @param type     Callback type (e.g. "account")
287         @param name     Callback name (e.g. "notify-added")
288         @param callback Callback to be called
289         '''
290
291         global account_cbs
292         global blist_cbs
293         global connection_cbs
294         global conversation_cbs
295         global notify_cbs
296         global request_cbs
297
298         { "account": account_cbs,
299           "blist": blist_cbs,
300           "connection": connection_cbs,
301           "conversation": conversation_cbs,
302           "notify": notify_cbs,
303           "request": request_cbs }[type][name] = callback
304
305     def signal_connect(self, name=None, cb=None):
306         '''Connects a signal handler to a signal for a particular object.
307         Take care not to register a handler function twice. Purple will
308         not correct any mistakes for you in this area.
309
310         @param name Name of the signal to connect.
311         @param cb Callback function.
312         '''
313
314         cdef int handle
315         cdef plugin.PurplePlugin *jabber
316
317         if name is None:
318             return
319
320         jabber = prpl.purple_find_prpl("prpl-jabber")
321         if jabber == NULL:
322             return
323
324         global signal_cbs
325         signal_cbs[name] = cb
326
327         if name == "signed-on":
328             signals.purple_signal_connect(
329                     connection.purple_connections_get_handle(),
330                     "signed-on", &handle,
331                     <signals.PurpleCallback> signal_signed_on_cb, NULL)
332         elif name == "signed-off":
333             signals.purple_signal_connect(
334                     connection.purple_connections_get_handle(),
335                     "signed-off", &handle,
336                     <signals.PurpleCallback> signal_signed_off_cb, NULL)
337         elif name == "connection-error":
338             signals.purple_signal_connect(
339                     connection.purple_connections_get_handle(),
340                     "connection-error", &handle,
341                     <signals.PurpleCallback> signal_connection_error_cb, NULL)
342         elif name == "buddy-signed-on":
343             signals.purple_signal_connect(
344                     blist.purple_blist_get_handle(),
345                     "buddy-signed-on", &handle,
346                     <signals.PurpleCallback> signal_buddy_signed_on_cb, NULL)
347         elif name == "buddy-signed-off":
348             signals.purple_signal_connect(
349                     blist.purple_blist_get_handle(),
350                     "buddy-signed-off", &handle,
351                     <signals.PurpleCallback> signal_buddy_signed_off_cb, NULL)
352         elif name == "receiving-im-msg":
353             signals.purple_signal_connect(
354                     conversation.purple_conversations_get_handle(),
355                     "receiving-im-msg", &handle,
356                     <signals.PurpleCallback> signal_receiving_im_msg_cb, NULL)
357         elif name == "jabber-receiving-xmlnode":
358             signals.purple_signal_connect(
359                     jabber, "jabber-receiving-xmlnode", &handle,
360                     <signals.PurpleCallback> jabber_receiving_xmlnode_cb, NULL)
361
362     def accounts_get_all(self):
363         '''Returns a list of all accounts.
364
365         @return A list of all accounts.
366         '''
367
368         cdef glib.GList *iter
369         cdef account.PurpleAccount *acc
370         cdef char *username
371         cdef char *protocol_id
372
373         iter = account.purple_accounts_get_all()
374         account_list = []
375
376         while iter:
377             acc = <account.PurpleAccount *> iter.data
378
379             if <account.PurpleAccount *>acc:
380                 username = <char *> account.purple_account_get_username(acc)
381                 protocol_id = <char *> account.purple_account_get_protocol_id(acc)
382
383                 if username != NULL and protocol_id != NULL:
384                     account_list.append(Account(username, \
385                             Protocol(protocol_id), self))
386             iter = iter.next
387
388         return account_list
389
390     def accounts_get_all_active(self):
391         '''Returns a list of all enabled accounts.
392
393         @return A list of all enabled accounts.
394         '''
395
396         cdef glib.GList *iter
397         cdef account.PurpleAccount *acc
398         cdef char *username
399         cdef char *protocol_id
400
401         #FIXME: The list is owned by the caller, and must be g_list_free()d
402         #       to avoid leaking the nodes.
403
404         iter = account.purple_accounts_get_all_active()
405         account_list = []
406
407         while iter:
408             acc = <account.PurpleAccount *> iter.data
409
410             if <account.PurpleAccount *>acc:
411                 username = <char *> account.purple_account_get_username(acc)
412                 protocol_id = <char *> account.purple_account_get_protocol_id(acc)
413
414                 if username != NULL and protocol_id != NULL:
415                     account_list.append(Account(username, \
416                             Protocol(protocol_id), self))
417             iter = iter.next
418
419         return account_list
420
421     def protocols_get_all(self):
422         '''Returns a list of all protocols.
423
424         @return A list of all protocols.
425         '''
426
427         cdef glib.GList *iter
428         cdef plugin.PurplePlugin *pp
429
430         iter = plugin.purple_plugins_get_protocols()
431         protocol_list = []
432         while iter:
433             pp = <plugin.PurplePlugin*> iter.data
434             if pp.info and pp.info.name:
435                 protocol_list.append(Protocol(pp.info.id))
436             iter = iter.next
437         return protocol_list
438
439     def call_action(self, i):
440         __call_action(i)
441
442 include "protocol.pyx"
443 #include "plugin.pyx"
444 include "proxy.pyx"
445 #include "protocol.pyx"
446 include "account.pyx"
447 include "buddy.pyx"
448 #include "connection.pyx"
449 include "conversation.pyx"