Mercurial > pidgin
view HACKING @ 11205:567998f226c1
[gaim-migrate @ 13333]
Christian Muise, was this change intentional?
committer: Tailor Script <tailor@pidgin.im>
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Mon, 08 Aug 2005 03:14:57 +0000 |
parents | 364a2ef907ae |
children | 9f793948154a |
line wrap: on
line source
The majority of the below was written by Eric Warmenhoven way back in antiquity. I have taken the liberty of attempting to PARTIALLY update it. I still think its helpful, but use it at your own risk. --Luke A lot of people have tried to hack gaim, but haven't been able to because the code is just so horrid. Well, the code isn't getting better anytime soon (I hate GNU indent), so to help all you would-be hackers help out gaim, here's a brief tutorial on how gaim works. I'll quickly describe the logical flow of things, then what you'll find in each of the source files. As an added bonus, I'll try and describe as best I can how multiple connections and multiple protocols work. Depending on how much I want to avoid my final tomorrow I may even describe other parts of gaim that I particularly want to brag about. Hopefully that's enough to get most of you going. If you don't know how event-driven programs work, stop right now. Gaim uses GTK+'s main loop (actually GLib's but I won't talk about how GTK works) and uses GLib functions for timeouts and socket notification. If you don't know GTK+ you should go learn that first. If you're going to hack gaim, PLEASE, PLEASE PLEASE PLEASE send patches against the absolute latest CVS. I get really annoyed when I get patches against the last released version, especially since I don't usually have a copy of it on my computer, and gaim tends to change a lot between versions. (I sometimes get annoyed when they're against CVS from 3 days ago, but can't complain because it's usually my fault that I haven't looked at the patch yet.) To get gaim from CVS (if you haven't already), run the following commands: $ export CVSROOT=:pserver:anonymous@cvs.sourceforge.net:/cvsroot/gaim $ cvs login (hit enter as the password) $ cvs co gaim (you'll see it getting all of the files) $ cd gaim $ ./autogen.sh You'll now have your normal gaim tree with ./configure and all (which ./autogen.sh takes the liberty of running for you). (If you want to make your life really simple, learn how CVS works. CVS is your friend.) To make a patch, just edit the files right there in that tree (don't bother with two trees, or even two copies of the same file). Then when you're ready to make your patch, simply run 'cvs diff -u >my.patch' and post it on sf.net/projects/gaim in the patches section. Some Documentation is available on the Gaim api if you run the command $make docs after running ./configure (or ./autogen.sh). You will need doxygen and graphiz dot to generate these docs. CODING STYLE ============ Coding styles are like assholes, everyone has one and no one likes anyone elses. This is mine and if you want me to accept a patch from you without getting annoyed you'll follow this coding style. :) It would probably just be easier for me to include CodingStyle from the linux kernel source. Tab indents. I *HATE* 2-space indents, and I strongly dislike 8-space indents. Use a tab character. I'm likely to refuse a patch if it has 2-space indents. K&R style for braces. Braces always go on the same line as the if, etc. that they're associated with; the only exception is functions. Braces for else statements should have both braces on the same line as the else (i.e. "} else {"). No functionOrVariableNamesLikeThis. Save it for Java. Underscores are your friend. "tmp" is an excellent variable name. Hungarian style will not be tolerated. Go back to Microsoft. I have a 105-char wide Eterm. Deal with it. NO goto. I'm very likely to refuse a patch if it makes use of goto. If you feel the need to use goto, you need to rethink your design and flow. PROGRAM FLOW (just about every function name from here on down is wrong. ============ but many of the ideas still apply under different names.) Before gaim does anything you can see, it initializes itself, which is mostly just reading ~/.gaim/*.xml (handled by the functions in prefs.[ch]) and parsing command-line options. It then draws the login window by calling show_login, and waits for input. At the login window, when "Accounts" is clicked, account_editor() is called. This then displays all of the users and various information about them. (Don't ask about what happens when "Sign On" is called. It's quite hackish. The only reason the login window is there anymore is to make it more palatable to people so used to WinAIM that they can't accept anything else.) When the "Sign on/off" button is clicked, serv_login is passed the username and the password for the account. If the password length is zero (the password field is a character array rather than pointer so it will not be NULL) then the Signon callback will prompt for the password before calling serv_login. serv_login then signs in the user using the appropriate protocol. After you're signed in, Gaim draws the buddy list by calling show_buddy_list. Assuming the user has a buddy list (all buddy list functions are controlled by list.c; when you sign on do_import is called and that loads the locally saved list), the protocol calls gaim_prpl_got functions, which set the information in the appropriate struct buddy and then passes it off to set_buddy. set_buddy is responsible for a lot of stuff, but most of it is done implicitly. It's responsible for the sounds (which is just a call to play_sound), but the biggest thing it does is call new_group_show and new_buddy_show if necessary. There's only one group_show per group name, even between connections, and only one buddy_show per group_show per buddy name, even between connections. (If that's not confusing enough, wait until I really start describing how the buddy list works.) New connections happen the exact same way as described above. Each gaim_account can have one gaim_connection associated with it. gaim_account and gaim_connection both have a protocol field. This is kind of confusing: gaim, except for the account editor screen and when the user signs on, ignores the user's protocl field, and only uses the connection's protocol field. You can change the connection's protocol field once it's created and been assigned a PRPL to use to change certain behavior (Oscar does this because it handles both AIM and ICQ). I'll talk about the gaim_connection struct more later. When the user opens a new conversation window, new_conversation is called. That's easy enough. If there isn't a conversation with the person already open (checked by calling find_conversation), show_conv is called to create the new window. All sorts of neat things happen there, but it's mostly drawing the window. show_conv is the best place to edit the UI. That's pretty much it for the quick tutorial. I know it wasn't much but it's enough to get you started. Make sure you know GTK+ before you get too involved. Most of the back-end stuff is pretty basic; most of gaim is GTK+. SOURCE FILES (this should probly be utterly removed) ============ about.c: Not much to say here, just a few basic functions. account.[ch]: This controls the GaimAccount struct, which stores information on each account a user registers with gaim. Usernames, pass- words, user info, alias, user specific options, and everything else controlled from within the account editor (and then some) are handled via this code. accountopt.[ch]: Api and implemenation for account options. I'm not precisely sure how this meshes with account.[ch] away.c: This takes care of most of the away stuff: setting the away message (do_away_message); coming back (do_im_back); drawing the away window; etc. Away messages work really oddly due to multiple connections and multiple protocols; I think there are really only two or three people who know how it works and I don't think any of us know why it works that way. blist.[ch]: This takes care of the buddy list backend, the blist.xml file, importing old buddy list files, and related things like finding buddies and groups. buddies, contacts, and groups are controlled from these files. buddy_chat.c: This takes care of the buddy chat stuff. This used to be a lot bigger until the chat and IM windows got merged in the code. Now it mostly just takes care of chat-specific stuff, like ignoring people and keeping track of who's in the room. This is also where the chat window is created. conversation.c: This is where most of the functions dealing with the IM and chat windows are hidden. It tries to abstract things as much as possible, but doesn't do a very good job. This is also where things like "Enter sends" and "Ctrl-{B/I/U/S}" options get carried out (look for send_callback). The chat and IM toolbar (with the B/I/U/S buttons) are both built from the same function, build_conv_toolbar. core.c: This is the start of what will become the main() for gaim-core. gtkdialogs.c: A massive file with a lot of little utility functions. This is where all of those little dialog windows are created. Things like the warn dialog and the add buddy dialog are here. Not all of the dialogs in gaim are in this file, though. But most of them are. This is also where do_import is housed, to import buddy lists. (The actual buddy list parsing code is in util.c for winaim lists and buddy.c for gaim's own lists.) gtkimhtml.c: This is gaim's HTML widget. It replaced the old widget, GtkHtml (which was different than GNOME's GtkHTML). It's self-contained (it doesn't use any of gaim's code) and is actually a separate project from gaim (but is maintained by Eric). idle.c: This file used to be entirely #if 0'd out of existance. However, thanks to some very generous people who submitted patches, this takes care of reporting idle time (imagine that). It's a pretty straight-forward file. This also takes care of the auto-away stuff. gtkmain.c: This is where the main() function is. It takes care of a lot of the initialization stuff, and showing the buddy list or account editor. md5.c: Oscar, Yahoo, and MSN all require md5 hashing, so better to put it in the core than have the same thing in three different places. module.c: This contains all of the plugin code, except for the UI. This is what actually loads the plugins, makes sure they're valid, has the code for setting up plugin event handlers, and contains the plugin_event method that gaim calls on events. prefs.c: Read the documentation on this file. This handles the backend side of prefs. proxy.c: Adam (of libfaim glory) got bored one day and rewrote this file, so now everything actually works. The main function is proxy_connect, which figures out which proxy you want to use (if you want to use one at all) and passes off the data to the appropriate function. This file should be pretty straight-forward. Except I STRONGLY suspect that time has broken this file. prpl.c: This file is what lets gaim dynamically load protocols, sort of. All of the actual dlopen(), dlsym() stuff is in module.c. But this contains all of the functions that the protocol plugin needs to call, and manages all of the protocols. It's a pretty simple file actually. server.c: This is where all of the differentiation between the different protocols is done. Nearly everything that's network related goes through here at one point or another. This has good things like serv_send_im. Most of it should be pretty self-explanatory. sound.c: The main function in this file is play_sound, which plays one of 8 (maybe 9?) sounds based on preferences. All that the rest of the code should have to do is call play_sound(BUDDY_ARRIVE), for example, and this file will take care of determining if a sound should be played and which file should be played. util.c: There's not really a lot of cohesion to this file; it's just a lot of stuff that happened to be thrown into it for no apparent reason. None of it is particularly tasty; it's all just utility functions. Just like the name says. plugins/ticker/gtkticker.c: Syd, our resident GTK+ God, wrote a GtkWidget, GtkTicker. This is that widget. It's cool, and it's tiny. This is actually a really good example widget for those of you looking to write your own. plugins/ticker/ticker.c: Syd is just so cool. I really can't get over it. He let me come visit him at Netscape one day, and I got to see all of their toys (don't worry, I'm under an NDA). Anyway, this file is for the buddy ticker. This is also a damn cool file because it's got all of the functions that you'd want right up at the top. Someday I want to be as cool as Syd. For the PRPLs, the only protocol whose "main" gaim file isn't the same as the name of the protocol is ICQ; for that it's gaim_icq.c. But ICQ is deprecated and you should be using Oscar for ICQ anyway. PLUGINS (read the plugins howto, this is really out of date) ======= OK, so you want to load a plugin. You go through whatever UI (you can read all about the UI in plugins.c or whereever). You finally get to load_plugin, the meat of the plugins stuff (plugins can actually call load_plugin themselves to load other plugins). load_plugin is passed the full path to the plugin you want to load (e.g. /usr/local/lib/gaim/irc.so). load_plugin does a few things with that filename. The first is to see if you've already loaded that plugin. If you have, load_plugin unloads the one that is currently loaded. You might wonder why; it's because the same plugin can't be loaded twice. If you call g_module_open on a filename twice, both times it will return the same pointer, and both times increment the reference count on the GModule * that it returns. This means you really do have the same plugin twice, which fucks up the callback system to no end. So it's better that you can only have it loaded once at any given time. Now that we're assured that we don't have this particular plugin loaded yet, we better load it. g_module_open, baby. Much more portable than dlopen(). In fact, for Linux it actually is the equivalent of dlopen() (you can read the gmodule source and see for yourself). There's only one quirk. It always logically ORs the options you pass with RTLD_GLOBAL, which means that plugins share symbols. I haven't figured out yet if this means just functions or variables too; but in either case make every function and variable in your plugin static except for gaim_plugin_*(), name(), and description(). It's good coding practice anyway. So, assuming we didn't get NULL back from g_module_open, we then make sure it's a valid gaim plugin by looking for and calling gaim_plugin_init, courtesy g_module_symbol (g_module_symbol is actually what's portable about gmodule as opposed to dl*; some BSD's require '_' prepended to symbol names and g_module_symbol guarantees we do The Right Thing). Assuming we've found gaim_plugin_init and it hasn't returned non-NULL to us, we then add it to our list of plugins and go merrily about our way. So when do the callbacks happen?! plugin_event, baby, plugin_event. Any time you want to trigger a plugin event simply call plugin_even with the parameters to be passed to any event handlers and you're set. plugin_event then makes sure that any plugins waiting for the event get passed the arguments properly and passes it on to perl. Speaking of perl. If you really want to know how this works, you're better off reading X-Chat's documentation of it, because it's better than what I could provide. MULTIPLE CONNECTIONS AND PRPLS ============================== OK, let's start with the basics. There are users. Each user is contained in an gaim_account struct, and kept track of in the gaim_accounts GSList. Each gaim_account has certain features: a username, a password, and user_info. It also has certain options, and the protocol it uses to sign on (kept as an int which is #define'd in prpl.h). Now then, there are protocols that gaim knows about. Each protocol is in a prpl struct and kept track of in the protocols GSList. The way the management of the protocols is, there will only ever be one prpl per numeric protocol. Each prpl defines a basic set of functions: login, logout, send_im, etc. The prpl is responsible not only for handling these functions, but also for calling the appropriate prpl_got functions It handles each of these on a per-account basis. So why's it called a PRPL? It stands for PRotocol PLugin. That means that it's possible to dynamically add new protocols to gaim. However, all protocols must be implemented the same way: by using a prpl struct and being loaded, regardless of whether they are static or dynamic. Here's how struct gaim_connection fits into all of this. At some point the User (capitalized to indicate a person and not a name) will try to sign on one of Their users. serv_login is then called for that user. It searches for the prpl that is assigned to that user, and calls that prpl's login function, passing it the gaim_account struct that is attempting to sign on. The prpl is then responsible for seeing that the gaim_connection is created (by calling new_gaim_connection), and registering it as being online (by calling account_online and passing it the gaim_account and gaim_connection structs). At that point, the gaim_account and gaim_connection structs have pointers to each other, and the gaim_connection struct has a pointer to the prpl struct that it is using. The gaim_connections are stored in the connections GSList. The way connection management works is, there will always only be one gaim_connection per user, and the prpl that the gaim_connection uses will be constant for the gaim_connection's life. So at certain points the User is going to want to do certain things, like send a message. They must send the message on a connection. So the UI figures out which gaim_connection the User want to send a message on (for our example), and calls serv_send_im, telling it which gaim_connection to use, and the necessary information (who to send it to, etc). The serv_ function then calls the handler of the prpl of the connection for that event (that was way too many prepositions). OK, each prpl has a send_im function. Each connection has a prpl. so you call gc->prpl->send_im and pass it the connection and all the necessary info. And that's how things get done. I hope some of that made sense. Looking back at it it makes absolutely no sense to me. Thank god I wrote the code; otherwise I'm sure I'd be lost. WRITING PRPLS ============= Start off with a protocol that you want to implement; make sure it has a number defined in prpl.h. If it doesn't, talk to Rob or Eric about adding it. *NEVER* use an unassigned number, not even for testing or personal use. It's possible that number will be used later by something else and that would cause quite a few head-scratchers. Start off with the following boiler plate: static struct prpl *my_protocol = NULL; void newproto_init(struct prpl *ret) { ret->protocol = PROTO_NEWPROTO; my_protocol = ret; } #ifndef STATIC char *gaim_plugin_init(GModule *handle) { load_protocol(newproto_init, sizeof(struct prpl)); return NULL; } void gaim_plugin_remove() { struct prpl *p = find_prpl(PROTO_NEWPROTO); if (p == my_protocol) unload_protocol(p); } char *name() { return "New Protocol"; } char *description() { return PRPL_DESC("New Protocol"); } #endif Replace all NEWPROTO things with your protocol name (e.g. PROTO_OSCAR instead of PROTO_NEWPROTO, oscar_init instead of newproto_init). Then populate your struct prpl; the most important function is actually name(), because without it, Gaim will most likely segfault. The second most important function is login(). Not all functions need to be implemented. There should be absolutely *ZERO* GTK+ in the PRPLs. PRPLs should *NEVER* say what the UI *looks* like, only what information needs to be there. There's currently an effort to get the GTK+ that is contained in the PRPLs directory out of there. If you submit a patch that adds GTK+ to those directories it's very likely to be refused, unless if I'm in a good mood and decide to relocate things for you. That's not likely. You're probably wondering how you can do certain things without GTK+. Well, you're just going to have to make do. Rely on the UI, that's why it's there. A PRPL should have absolutely ZERO interaction with the user, it should all be handled by the UI. Don't use the _options variables at all. The core should take care of all of that. There are several proto_opt fields that you can use on a per-user basis. Check out existing protocols for more details.