view HACKING @ 1176:02af8c5b9b24

[gaim-migrate @ 1186] I am dumb. committer: Tailor Script <tailor@pidgin.im>
author Rob Flynn <gaim@robflynn.com>
date Thu, 30 Nov 2000 09:34:07 +0000
parents 4416ead31db7
children 72692c70317e
line wrap: on
line source

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, 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. Hopefully that's enough to get most of you going.

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.gaim.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
$ ./gen

You'll now have your normal gaim tree with ./configure and all. (If you
want to make your life really simple, learn how CVS works. CVS is your
friend.)

There's one little thing that's just a pet peeve, and it's really stupid.
In ./configure there's an --enable-debug option. This does two things:
compiles with -Wall, and prints debugging information to stdout. The
debugging information is printed to the debug window (which can be turned
on in the preferences) whether or not --enable-debug was selected. Most
of the information that's printed is useless anyway though; so the
--enable-debug option really doesn't do a whole lot.

This file was last modified by $Author: warmenhoven $ on $Date: 2000-11-16 02:35:58 -0500 (Thu, 16 Nov 2000) $.


PROGRAM FLOW
============

Before gaim does anything you can see, it initializes itself, which is
mostly just reading .gaimrc (handled by the functions in gaimrc.c) 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. If the user clicks the "Signon" button instead, serv_login is called.

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. We'll assume TOC for the rest of this
discussion; Oscar has a lot of bad hacks to get it working that I don't
even want to think about.

After you're signed in (I'll skip that discussion - I doubt many people
are going to change the login process, since it pretty much just follows
PROTOCOL), Gaim draws the buddy list by calling show_buddy_list, and
waits for input from two places: the server and the user. The first place
it gets input from after signon is usually the server, when the server
tells Gaim which buddies are signed on.

When there is information ready to be read from the server, toc_callback
is called (by GDK) to parse the incoming information. On an UPDATE,
serv_got_update is called, which takes care of things like notifying
conversation windows of the update if need be; notifying the plugins;
and finally, calling set_buddy.

New connections happen the exact same way as described above. Each aim_user
can have one gaim_connection associated with it. aim_user and gaim_connection
both have a protocol field; gaim_connection's should be constant once it is
set in the appropriate (protocol)_login function. There are lots of details
that are connected with multiple connections that are best explained by
reading the code.

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
============

about.c:
  Not much to say here, just a few basic functions.

aim.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. Watch out for bad Oscar
  sign in hacks.

away.c:
  This takes care of most of the away stuff: setting the away message
  (do_im_away); coming back (do_im_back); drawing the away window;
  etc. To be honest I haven't looked at this file in months.

browser.c:
  Code for opening a browser window. Most of the code is trying to deal
  with Netscape. The most important function here is open_url. Have fun.

buddy.c:
  This takes care of not only nearly everything buddy-related (the buddy
  list, the permit/deny lists, and the window), but also a lot of the
  code flow and util functions. Look for good things like find_buddy,
  set_buddy, and signoff() here.

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.

dialogs.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.

gaimrc.c:
  This controls everything about the .gaimrc file. There's not really much
  to say about it; this is probably one of the better designed and easier
  to follow files in gaim. The important functions are towards the bottom.

gnome_applet_mgr.c:
  A hideous creation from the days before I started working on gaim. Most
  of it works, but it has functionsLikeThis. I hate looking at this
  file, but I'm too lazy to change the functions. The best functions
  are things like set_applet_draw_open, whose sole purpose is to set a
  global variable to TRUE. [ note 8/22/00 - I finally changed this file. ]

gtkhtml.c:
  This is really just one big hack. It started off as an HTML widget that
  was written for Gnome as far as I can tell. The current version is
  huge, requires way too many libs, and is too hard to upgrade to. But
  we've managed to hack this poor old version into basically what we
  need it for. I recommend not looking at this file if you want to save
  your sanity.

gtkticker.c:
  Syd, our resident GTK God, wrote a GtkWidget, GtkTicker. This is that
  widget. It's cool, and it's tiny.

html.c:
  Don't ask my why this is called html.c. Most of it is just grab_url,
  which does like the name says; it downloads a URL to show in the
  GtkHTML widget.  http.c would be a more appropriate name, but that's OK.

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.

multi.c:
  This is the file that tries to take care of most of the major issues
  with multiple connections. The best function in here by far is the
  account_editor(). auto_login() is also in here (I'm just reading
  multi.h now...); auto_login has problems. Someone please fix it.
  account_editor is really the only function that the UI needs to be
  concerned with. If you want to remove multiconnectivity from gaim,
  all you would really need to do is comment out any lines that make
  reference to this function (there are only two - one in aim.c and one
  in buddy.c). Or you could just run ./configure --disable-multi.

oscar.c:
  One big hack of copied code. This is supposed to be the libfaim tie-in
  in gaim. Most of it is just copied straight from faimtest, the small
  program that comes with libfaim. I'm not even sure how half of it works,
  if that makes you feel any better.

perl.c:
  This was basically copied straight from X-Chat through the power of
  the GPL.  Perl is the biggest, most confusing piece of C code I've ever
  seen in my life (and keep in mind I'm a gaim hacker). I have a basic
  idea of what's going on in it, but I couldn't tell you exactly. The
  top half sets up perl and tells it what's going on and the bottom half
  implements the AIM module.

plugins.c:
  This is the "plugin plug", as the file states. This file is probably
  the only file in all of gaim that at the top has all of the functions
  and global and static variables named out for you. It makes reading
  it a little easier, but not by much. A lot of the code in here deals
  with the plugin window rather than the plugins themselves.

prefs.c:
  The important function in here is build_prefs, but the most useful
  function is gaim_button. build_prefs draws the window, and calls
  gaim_button probably 30 or 40 times. (I don't really wanna run grep
  | wc to count.) This is where you add the toggle button for gaim
  preferences. It's very simple, and if you look at a couple of the
  calls to gaim_button you'll figure it out right away.

prpl.c:
  This file is what lets gaim dynamically load protocols, sort of. All of
  the actual dlopen(), dlsym() stuff is in plugins.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.

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.

rvous.c:
  This was originally going to be the stuff for all of the Buddy Icon
  and Voice Chat stuff, but I got really sick of protocol hacking really
  quick.  Now it only houses the file transfer stuff, which only works
  for TOC.

server.c:
  This is where all of the differentiation between TOC and Oscar 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.

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.

toc.c:
  This handles everything TOC-related, including parsing gaim's buddy
  list.  Most of this file is toc_callback, which parses the incoming
  information from the server. I really don't like TOC though.

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.


MULTIPLE CONNECTIONS AND PRPLS
==============================

OK, let's start with the basics. There are users. Each user is contained
in an aim_user struct, and kept track of in the aim_users GList (GSList?).
Each aim_user 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). The way the management of the users
works is, there will (hopefully) only be one user for a given screenname/
protocol pair (i.e. you may have two user warmenhoven's, but they'll both
have a different protocol number).

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 aim_user 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 aim_user and gaim_connection structs). At
that point, the aim_user 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.