nullclient-ecore: move away the fdhandler re-usage introduced in 96e2dc7
[nullclient-ecore.git] / nullclient-ecore.c
1 /*
2  * nullclient-ecore.c - an ecore libpurple client test.
3  * Copyright (C) 2009, Marco Trevisan (TreviƱo) <mail@3v1n0.net>
4  * --
5  *
6  * Pidgin is the legal property of its developers, whose names are too numerous
7  * to list here.  Please refer to the COPYRIGHT file distributed with this
8  * source distribution.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
23  *
24  */
25
26 #include "purple.h"
27
28 #include <Ecore.h>
29 #include <eina_list.h>
30
31 #include <signal.h>
32 #include <string.h>
33 #include <unistd.h>
34
35 // Client related settings
36 #define CUSTOM_USER_DIRECTORY  "/dev/null"
37 #define CUSTOM_PLUGIN_PATH     ""
38 #define PLUGIN_SAVE_PREF       "/purple/nullclient-ecore/plugins/saved"
39 #define UI_ID                  "nullclient-ecore"
40
41
42 /*** Event uiops ***/
43 static guint loopID = 0;
44 Eina_List *timerList = NULL;
45 Eina_List *inputList = NULL;
46
47 typedef struct _timerRef {
48         Ecore_Timer *timer;
49         guint id;
50 } timerRef;
51
52 typedef struct _fdRef {
53         PurpleInputFunction function;
54         Ecore_Fd_Handler_Flags condition;
55         Ecore_Fd_Handler *fdh;
56         guint id;
57         void *data;
58 } fdRef;
59
60 static guint ecore_purple_timeout_add(guint interval, GSourceFunc function, gpointer data) {
61         timerRef *ref = calloc(1, sizeof(timerRef));
62         double timer_interval = ((double) interval)/1000;
63         
64         ref->timer = ecore_timer_add(timer_interval, function, data);
65         ref->id = ++loopID;
66         timerList = eina_list_append(timerList, ref);
67         
68         return ref->id;
69 }
70
71 static gboolean ecore_purple_timeout_remove(guint handle) {
72         timerRef *ref;
73         Eina_List *tlist;
74         
75         EINA_LIST_FOREACH(timerList, tlist, ref) {
76                 if (ref->id == handle) {
77                         if (ref->timer)
78                                 ecore_timer_del(ref->timer);
79                         timerList = eina_list_remove(timerList, ref);
80                         free(ref);
81                         return TRUE;
82                 }
83         }
84         
85         return FALSE;
86 }
87
88 static int ecore_purple_input_cb(void *data, Ecore_Fd_Handler *fd_handler) {
89         fdRef *ref = data;
90         PurpleInputCondition purple_cond = 0;
91
92         if (ref->condition & ECORE_FD_READ)
93                 purple_cond |= PURPLE_INPUT_READ;
94
95         if (ref->condition & ECORE_FD_WRITE)
96                 purple_cond |= PURPLE_INPUT_WRITE;
97
98         if (ref->function && fd_handler) {
99                 ref->function(ref->data, ecore_main_fd_handler_fd_get(fd_handler), purple_cond);
100                 return 1;
101         } else
102                 return 0;
103 }
104
105 static guint ecore_purple_input_add(int fd, PurpleInputCondition condition,
106                                                          PurpleInputFunction func, gpointer user_data) {
107         Ecore_Fd_Handler_Flags fdflags = 0;
108         fdRef *ref;
109         
110         if (fd < 0)
111                 return ++loopID;
112
113         if (condition & PURPLE_INPUT_READ)
114                 fdflags |= ECORE_FD_READ;
115
116         if (condition & PURPLE_INPUT_WRITE)
117                 fdflags |= ECORE_FD_WRITE;
118
119         ref = calloc(1, sizeof(fdRef));
120         ref->fdh = ecore_main_fd_handler_add(fd, fdflags, ecore_purple_input_cb, ref, NULL, NULL);
121         ref->id = ++loopID;
122         ref->data = user_data;
123         ref->function = func;
124         inputList = eina_list_append(inputList, ref);
125
126         return ref->id;
127 }
128
129 static gboolean ecore_purple_input_remove(guint handle) {
130         fdRef *ref;
131         Eina_List *tlist;
132         
133         EINA_LIST_FOREACH(inputList, tlist, ref) {
134                 if (ref && ref->id == handle) {
135                         if (ref->fdh)
136                                 ecore_main_fd_handler_del(ref->fdh);
137                         inputList = eina_list_remove(inputList, ref);
138                         free(ref);
139                         return TRUE;
140                 }
141         }
142         
143         return FALSE;
144 }
145
146 static PurpleEventLoopUiOps ecore_eventloops =
147 {
148         ecore_purple_timeout_add,
149         ecore_purple_timeout_remove,
150         ecore_purple_input_add,
151         ecore_purple_input_remove,
152         NULL,
153         NULL,
154         NULL,
155         NULL,
156         NULL
157 };
158 /*** End of the eventloop functions. ***/
159
160 /*** Conversation uiops ***/
161 static void
162 null_write_conv(PurpleConversation *conv, const char *who, const char *alias,
163                         const char *message, PurpleMessageFlags flags, time_t mtime)
164 {
165         const char *name;
166         if (alias && *alias)
167                 name = alias;
168         else if (who && *who)
169                 name = who;
170         else
171                 name = NULL;
172
173         printf("(%s) %s %s: %s\n", purple_conversation_get_name(conv),
174                         purple_utf8_strftime("(%H:%M:%S)", localtime(&mtime)),
175                         name, message);
176 }
177
178 static PurpleConversationUiOps null_conv_uiops = 
179 {
180         NULL,                      /* create_conversation  */
181         NULL,                      /* destroy_conversation */
182         NULL,                      /* write_chat           */
183         NULL,                      /* write_im             */
184         null_write_conv,           /* write_conv           */
185         NULL,                      /* chat_add_users       */
186         NULL,                      /* chat_rename_user     */
187         NULL,                      /* chat_remove_users    */
188         NULL,                      /* chat_update_user     */
189         NULL,                      /* present              */
190         NULL,                      /* has_focus            */
191         NULL,                      /* custom_smiley_add    */
192         NULL,                      /* custom_smiley_write  */
193         NULL,                      /* custom_smiley_close  */
194         NULL,                      /* send_confirm         */
195         NULL,
196         NULL,
197         NULL,
198         NULL
199 };
200
201 static void
202 null_ui_init(void)
203 {
204         /**
205          * This should initialize the UI components for all the modules. Here we
206          * just initialize the UI for conversations.
207          */
208         purple_conversations_set_ui_ops(&null_conv_uiops);
209
210 }
211
212 static PurpleCoreUiOps null_core_uiops =
213 {
214         NULL,
215         NULL,
216         null_ui_init,
217         NULL,
218         NULL,
219         NULL,
220         NULL,
221         NULL
222 };
223
224 static void
225 init_libpurple(void)
226 {
227         /* Set a custom user directory (optional) */
228         purple_util_set_user_dir(CUSTOM_USER_DIRECTORY);
229
230         /* We do not want any debugging for now to keep the noise to a minimum. */
231         purple_debug_set_enabled(TRUE);
232
233         /* Set the core-uiops, which is used to
234          *      - initialize the ui specific preferences.
235          *      - initialize the debug ui.
236          *      - initialize the ui components for all the modules.
237          *      - uninitialize the ui components for all the modules when the core terminates.
238          */
239         purple_core_set_ui_ops(&null_core_uiops);
240
241         /* Set the uiops for the eventloop. */
242         purple_eventloop_set_ui_ops(&ecore_eventloops);
243
244         /* Set path to search for plugins. The core (libpurple) takes care of loading the
245          * core-plugins, which includes the protocol-plugins. So it is not essential to add
246          * any path here, but it might be desired, especially for ui-specific plugins. */
247         purple_plugins_add_search_path(CUSTOM_PLUGIN_PATH);
248
249         /* Now that all the essential stuff has been set, let's try to init the core. It's
250          * necessary to provide a non-NULL name for the current ui to the core. This name
251          * is used by stuff that depends on this ui, for example the ui-specific plugins. */
252         if (!purple_core_init(UI_ID)) {
253                 /* Initializing the core failed. Terminate. */
254                 fprintf(stderr,
255                                 "libpurple initialization failed. Dumping core.\n"
256                                 "Please report this!\n");
257                 abort();
258         }
259
260         /* Create and load the buddylist. */
261         purple_set_blist(purple_blist_new());
262         purple_blist_load();
263
264         /* Load the preferences. */
265         purple_prefs_load();
266
267         /* Load the desired plugins. The client should save the list of loaded plugins in
268          * the preferences using purple_plugins_save_loaded(PLUGIN_SAVE_PREF) */
269         purple_plugins_load_saved(PLUGIN_SAVE_PREF);
270
271         /* Load the pounces. */
272         purple_pounces_load();
273 }
274
275 static void
276 signed_on(PurpleConnection *gc, gpointer null)
277 {
278         PurpleBlistNode *node;
279         
280         PurpleAccount *account = purple_connection_get_account(gc);
281         printf("Account connected: %s %s\n", account->username, account->protocol_id);
282 }
283
284 static void loopListsfree(void) {
285         fdRef *fref;
286         timerRef *tref;
287         Eina_List *tlist;
288         
289         EINA_LIST_FOREACH(timerList, tlist, fref) {
290                 if (fref && fref->fdh)
291                         ecore_main_fd_handler_del(fref->fdh);
292                 timerList = eina_list_remove(timerList, fref);
293                 free(fref);
294         }
295         
296         EINA_LIST_FOREACH(inputList, tlist, tref) {
297                 if (tref && tref->timer)
298                         ecore_timer_del(tref->timer);
299                 inputList = eina_list_remove(inputList, tref);
300                 free(tref);
301         }
302         
303         eina_list_free(timerList);
304         eina_list_free(inputList);
305 }
306
307 static void signed_off(PurpleConnection *gc, gpointer null)
308 {
309         printf("Connection is gone :(...\n ");
310         ecore_main_loop_quit();
311 }
312
313 static void
314 connect_to_signals_for_demonstration_purposes_only(void)
315 {
316         static int handle;
317         purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
318                                 PURPLE_CALLBACK(signed_on), NULL);
319                                 
320         static int handle2;
321         purple_signal_connect(purple_connections_get_handle(), "signed-off", &handle2,
322                                 PURPLE_CALLBACK(signed_off), NULL);
323 }
324
325 int main(int argc, char *argv[])
326 {
327         const char *prpl;
328         char name[128];
329         char *password;
330         PurpleAccount *account;
331         PurpleSavedStatus *status;
332         char *res;
333
334         /* libpurple's built-in DNS resolution forks processes to perform
335          * blocking lookups without blocking the main process.  It does not
336          * handle SIGCHLD itself, so if the UI does not you quickly get an army
337          * of zombie subprocesses marching around.
338          */
339         signal(SIGCHLD, SIG_IGN);
340         
341         ecore_init();
342         eina_list_init();
343
344         init_libpurple();
345
346         printf("libpurple initialized.\n");
347
348         int i, num;
349         GList *iter;
350         Eina_List *names = NULL;
351         iter = purple_plugins_get_protocols();
352         for (i = 0; iter; iter = iter->next) {
353                 PurplePlugin *plugin = iter->data;
354                 PurplePluginInfo *info = plugin->info;
355                 if (info && info->name) {
356                         printf("\t%d: %s\n", i++, info->name);
357                         names = eina_list_append(names, info->id);
358                 }
359         }
360         
361         printf("Select the protocol [0-%d]: ", i-1);
362         res = fgets(name, sizeof(name), stdin);
363         if (!res) {
364                 fprintf(stderr, "Failed to gets protocol selection.");
365                 abort();
366         }
367         sscanf(name, "%d", &num);
368         prpl = eina_list_nth(names, num);
369
370         printf("Username: ");
371         res = fgets(name, sizeof(name), stdin);
372         if (!res) {
373                 fprintf(stderr, "Failed to read user name.");
374                 abort();
375         }
376         name[strlen(name) - 1] = 0;  /* strip the \n at the end */
377
378         /* Create the account */
379         account = purple_account_new(name, prpl);
380
381         /* Get the password for the account */
382         password = getpass("Password: ");
383         purple_account_set_password(account, password);
384
385         /* It's necessary to enable the account first. */
386         purple_account_set_enabled(account, UI_ID, TRUE);
387
388         /* Now, to connect the account(s), create a status and activate it. */
389         status = purple_savedstatus_new(NULL, PURPLE_STATUS_INVISIBLE);
390         purple_savedstatus_activate(status);
391
392         connect_to_signals_for_demonstration_purposes_only();
393
394         ecore_main_loop_begin();
395
396         ecore_main_loop_quit();
397         
398         eina_list_shutdown();
399         
400         return 0;
401 }