qulogic/pidgin
Clone
Summary
Browse
Changes
Graph
Allow localization of the xmpp console plugin
2007-01-25, Mark Doliner
52bd37e96ada
Allow localization of the xmpp console plugin
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 SVN. 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 SVN 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
SVN
(
if
you
haven
't already),
run the following commands:
$ svn co https://svn.sourceforge.net/svnroot/gaim/trunk gaim
$ 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 SVN works. SVN 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
'svn diff > 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.