Added SIGCHLD handling to avoid zombie processes.
[python-purple] / nullclient-ecore.py
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 import etk
21 import ecore
22 import purple
23
24 # The information below is needed by libpurple
25 __NAME__ = "nullclient-ecore"
26 __VERSION__ = "0.1"
27 __WEBSITE__ = "N/A"
28 __DEV_WEBSITE__ = "N/A"
29
30 class MainWindow(object):
31     def __init__(self):
32         # Connect button callback
33         self.__connect_button_cb = None
34
35         # Send button callback
36         self.__send_button_cb = None
37
38         # New account callback
39         self.__new_account_cb = None
40
41         # Exit callback
42         self.__exit_cb = None
43
44     def __login_window_cb(self, pointer):
45         # Password entry
46         self.login_password = etk.Entry()
47
48         # Confirm login button
49         confirm_login_bt = etk.Button(label='Ok')
50         confirm_login_bt.on_clicked(self.__connect_button_clicked)
51
52         # Login VBox
53         vbox = etk.VBox()
54         vbox.append(self.login_password, etk.VBox.START, etk.VBox.FILL, 0)
55         vbox.append(confirm_login_bt, etk.VBox.END, etk.VBox.NONE, 0)
56
57         # Login window
58         self.login_window = etk.Window(title='Password', \
59                 size_request=(190, 80), child=vbox)
60         self.login_window.show_all()
61
62     def __connect_button_clicked(self, pointer):
63         # Call connection button callback
64         if self.__connect_button_cb:
65             self.__connect_button_cb(self.login_password.text)
66         self.login_window.destroy()
67
68     def __send_button_clicked(self, pointer):
69         # Get selected buddy name from buddy list
70         bname = self.blist.selected_rows[0][0]
71
72         # Get message from command entry
73         msg = self.cmd_entry.text
74
75         if bname and msg != '':
76             # Call send button callback
77             if self.__send_button_cb:
78                 self.__send_button_cb(bname, msg)
79
80         # Clear text from command entry
81         self.cmd_entry.text = ''
82
83     def __new_account_button_clicked(self, pointer):
84         # Username entry
85         self.login_username = etk.Entry()
86
87         # Confirm username button
88         confirm_username_bt = etk.Button(label='Ok')
89         confirm_username_bt.on_clicked(self.add_account)
90
91         # Username VBox
92         vbox = etk.VBox()
93         vbox.append(self.login_username, etk.VBox.START, etk.VBox.FILL, 0)
94         vbox.append(confirm_username_bt, etk.VBox.END, etk.VBox.NONE, 0)
95
96         # Username window
97         self.username_window = etk.Window(title='Username', \
98                 size_request=(190, 80), child=vbox)
99         self.username_window.show_all()
100
101     def __create_accounts_list(self):
102         # Accounts list
103         self.accslistmodel = etk.ListModel()
104         self.accslist = etk.List(model=self.accslistmodel, \
105                 columns=[(10, etk.TextRenderer(slot=0), False)], \
106                 selectable=True, animated_changes=True)
107
108         #Appending accounts list to VBox
109         vbox = etk.VBox()
110         vbox.append(self.accslist, etk.VBox.START, etk.VBox.EXPAND_FILL, 0)
111         return vbox
112
113     def __create_buddies_list(self):
114         # Buddies list
115         self.blistmodel = etk.ListModel()
116         self.blist = etk.List(model=self.blistmodel, \
117                 columns=[(10, etk.TextRenderer(slot=0), False)], \
118                 selectable=True, animated_changes=True)
119
120         # Appending buddies list to VBox
121         vbox = etk.VBox()
122         vbox.append(self.blist, etk.VBox.START, etk.VBox.EXPAND_FILL, 0)
123         return vbox
124
125     def __create_buttons_bar(self):
126         # Send button
127         send_button = etk.Button(label='Send')
128         send_button.on_clicked(self.__send_button_clicked)
129
130         # Connect button
131         conn_button = etk.Button(label='Connect')
132         conn_button.on_clicked(self.__login_window_cb)
133
134         # New account button
135         new_acc_button = etk.Button(label='New Account')
136         new_acc_button.on_clicked(self.__new_account_button_clicked)
137
138         # Appending all buttons to HBox
139         hbox = etk.HBox(homogeneous=False)
140         hbox.append(send_button, etk.HBox.START, etk.HBox.NONE, 0)
141         hbox.append(conn_button, etk.HBox.START, etk.HBox.NONE, 0)
142         hbox.append(new_acc_button, etk.HBox.START, etk.HBox.NONE, 0)
143         return hbox
144
145     def __create_command_entry_box(self):
146         # Command entry box
147         self.cmd_entry = etk.Entry()
148         self.cmd_label = etk.Label(text='Type your message: ')
149
150         # appending command entry and label to HBox
151         hbox = etk.HBox(homogeneous=False)
152         hbox.append(self.cmd_label, etk.HBox.START, \
153                 etk.HBox.START, 0)
154         hbox.append(self.cmd_entry, etk.HBox.START, \
155                 etk.HBox.EXPAND_FILL, 0)
156         return hbox
157
158     def __create_text_area(self):
159         # Text area (shows buddy messages)
160         self.txt_area = etk.Label()
161         self.txt_area.text = '<br>Nullclient-Ecore<br> '
162
163         # Appending text area to VBox
164         vbox = etk.VBox()
165         vbox.append(self.txt_area, etk.VBox.START, etk.VBox.EXPAND_FILL, 0)
166         return vbox
167
168     def __create_main_panel(self):
169         # Text box
170         txt_vbox = self.__create_text_area()
171
172         # Buddies list
173         bdd_vbox = self.__create_buddies_list()
174
175         # Accounts list
176         acc_vbox = self.__create_accounts_list()
177
178         # Appending text area, buddies list and accounts list to HBox
179         hbox = etk.HBox()
180         hbox.append(txt_vbox, etk.HBox.START, etk.HBox.EXPAND_FILL, 0)
181         hbox.append(bdd_vbox, etk.HBox.END, etk.HBox.EXPAND_FILL, 0)
182         hbox.append(acc_vbox, etk.HBox.END, etk.HBox.EXPAND_FILL, 0)
183         return hbox
184
185     def __create_main_box(self):
186         # Main panel
187         panel_hbox = self.__create_main_panel()
188
189         # Command entry
190         cmd_hbox = self.__create_command_entry_box()
191
192         # Buttons Bar
193         btn_hbox = self.__create_buttons_bar()
194
195         # Connection status
196         self.status = etk.Label(text='Connection status')
197
198         # Main VBox
199         vbox = etk.VBox(homogeneous=False)
200         vbox.append(panel_hbox, etk.VBox.START, etk.VBox.EXPAND_FILL, 0)
201         vbox.append(cmd_hbox, etk.VBox.END, etk.VBox.FILL, 0)
202         vbox.append(btn_hbox, etk.VBox.END, etk.VBox.NONE, 5)
203         vbox.append(self.status, etk.VBox.END, etk.VBox.FILL, 0)
204         return vbox
205
206     def get_selected_account(self):
207         # Catch selected account from accounts list
208         try:
209             account = self.accslist.selected_rows[0][0]
210             if account:
211                 return account
212             else:
213                 return None
214         except:
215             return None
216
217     def add_buddy(self, name):
218         # Adds a new buddy into buddy list
219         if [name] not in self.blistmodel.elements:
220             self.blistmodel.append([name])
221
222     def remove_buddy(self, name):
223         # Removes a buddy from buddy list
224         self.blistmodel.remove([name])
225
226     def add_account(self, pointer):
227         # Adds a new account into accounts list
228         if [self.login_username.text] not in self.accslistmodel.elements:
229             self.accslistmodel.append([self.login_username.text])
230         self.username_window.destroy()
231         self.window.show_all()
232
233     def add_connection_button_cb(self, cb):
234         if callable(cb):
235             self.__connect_button_cb = cb
236
237     def add_new_account_cb(self, cb):
238         if callable(cb):
239             self.__new_account_cb = cb
240
241     def add_send_button_cb(self, cb):
242         if callable(cb):
243             self.__send_button_cb = cb
244
245     def add_exit_cb(self, cb):
246         if callable(cb):
247             self.__exit_cb = cb
248
249     def init_window(self):
250         # Main box
251         main_box = self.__create_main_box()
252
253         # Main Window
254         self.window = etk.Window(title='Nullclient-Ecore', \
255                 size_request=(600, 600), child=main_box)
256         self.window.on_destroyed(self.__exit_cb)
257         self.window.show_all()
258
259     def show(self):
260         if self.window:
261             self.window.show_all()
262
263 class NullClient(object):
264     def __init__(self):
265         # Sets initial parameters
266         self.core = purple.Purple(__NAME__, __VERSION__, __WEBSITE__, \
267                 __DEV_WEBSITE__, debug_enabled=True, default_path='/tmp')
268         self.account = None
269         self.buddies = {}
270         self.conversations = {}
271         self.protocol = purple.Protocol('prpl-jabber')
272         self.window = MainWindow()
273
274         # Adds libpurple core callbacks
275
276         # Updates buddy list
277         self.core.add_callback('blist', 'update', \
278                 self.__update_blist_cb)
279
280         # Updates connection progress
281         self.core.add_callback('connection', 'connect-progress', \
282                 self.__connection_progress_cb)
283
284         # Activates when an account is connected
285         self.core.add_callback('connection', 'connected', \
286                 self.__connected_cb)
287
288         # Activates when an account is disconnected
289         self.core.add_callback('connection', 'disconnected', \
290                 self.__disconected_cb)
291
292         # Activates when a message is sent or received from conversation
293         self.core.add_callback('conversation', 'write-im', \
294                 self.__write_im_cb)
295
296         # Signal when account signed on
297         self.core.signal_connect('signed-on', self.__signed_on_cb)
298
299         # Signal when account signed off
300         self.core.signal_connect('signed-off', self.__signed_off_cb)
301
302         # Signal when buddy signed on
303         self.core.signal_connect('buddy-signed-on', self.__buddy_signed_on_cb)
304
305         # Signed when buddy signed off
306         self.core.signal_connect('buddy-signed-off', self.__buddy_signed_off_cb)
307
308         # Adds UI callbacks
309         self.window.add_connection_button_cb(self.connect)
310         self.window.add_send_button_cb(self.send_message)
311         self.window.add_exit_cb(self.exit)
312
313         # Initializes libpurple
314         self.core.purple_init()
315
316         # Initializes UI
317         self.window.init_window()
318
319     def __update_blist_cb(self, type, name=None, alias=None):
320         if self.account and name and type == 2:
321             if not self.buddies.has_key(name):
322                 self.buddies[name] = purple.Buddy(name, self.account)
323             if self.buddies[name].online:
324                 self.window.add_buddy(name)
325
326     def __connection_progress_cb(self, text, step, step_count):
327         if self.window:
328             self.window.status.text = text
329
330     def __connected_cb(self, *data):
331         if self.window:
332             self.window.status.text = 'Connected'
333
334     def __disconected_cb():
335         if self.window:
336             self.window.status.text = 'Disconnected'
337
338     def __write_im_cb(self, username, name, alias, message, flags):
339         if self.window:
340             if 'SEND' == flags:
341                 self.window.txt_area.text += username + ": " + message + "<br> "
342             elif alias:
343                 self.window.txt_area.text += alias + ": " + message + "<br> "
344             else:
345                 self.window.txt_area.text += name + ": " + message + "<br> "
346             self.window.show()
347
348     def __signed_on_cb(self, username, protocol_id):
349         if self.window:
350             self.window.txt_area += 'Signed on: %s (%s)' % (username, protocol_id)
351             self.window.show()
352
353     def __signed_off_cb(self, username, protocol_id):
354         if self.window:
355             self.window.txt_area += 'Signed off: %s (%s)' % (username, protocol_id)
356             self.window.show()
357
358     def __buddy_signed_on_cb(self, name, alias):
359         if self.window:
360             self.window.txt_area += 'Buddy signed on: %s (%s)' % (name, alias)
361             self.window.show()
362
363     def __buddy_signed_off_cb(self, name, alias):
364         if self.buddies.has_key(name):
365             del self.buddies[name]
366
367         if self.window:
368             self.window.txt_area += 'Buddy signed off: %s (%s)' % (name, alias)
369             self.window.remove_buddy(name)
370             self.window.show()
371
372     def connect(self, password):
373         username = self.window.get_selected_account()
374         if username and password:
375             self.account = purple.Account(username, self.protocol, self.core)
376             if not self.account.exists:
377                 self.account.new()
378                 info = {}
379                 info['connect_server'] = 'talk.google.com'
380                 info['port'] = '443'
381                 info['old_ssl'] = True
382                 self.account.set_protocol_options(info)
383
384             self.account.set_password(password)
385             self.account.set_enabled(True)
386
387     def send_message(self, name, message):
388         print name, message
389         if not self.conversations.has_key(name):
390             self.conversations[name] = purple.Conversation('IM', self.account, name)
391             self.conversations[name].new()
392
393         self.conversations[name].im_send(message)
394
395     def exit(self, pointer):
396         ecore.main_loop_quit()
397
398 if __name__ == '__main__':
399     client = NullClient()
400
401     # Initializes ecore mainloop
402     ecore.main_loop_begin()