gaim/gaim

A blurb.
oldstatus
2005-08-22, Mark Doliner
bc21999240ae
Parents c306cd26d61c
Children f1a51edc017c
A blurb.
  • +454 -0
    HACKING
  • --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/HACKING Mon Aug 22 17:59:14 2005 -0400
    @@ -0,0 +1,454 @@
    +Lots of this is pretty grossly out of date...
    +Some of it might still be useful. For coding style, your
    +best bet is to browse through some of the files in src and
    +emulate what you see there.
    +--Mark
    +
    +
    +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
    +serv_got_update, which sets 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.
    +
    +main.c:
    + This is where the main() function is. It takes care of a lot of the
    + initialization stuff, and showing the login window. It's pretty tiny
    + and there's not really much to edit in it. This has some of the most
    + pointless functions, like gaim_setup, which optionally turns off sounds
    + on signon. A lot of this file should actually be part of other files.
    +
    +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 and
    + serv_got_update. 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 serv_got functions
    +(e.g. serv_got_update when a buddy comes online/goes offline/goes
    +idle/etc). It handles each of these on a per-connection 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.