aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas White <taw@bitwiz.org.uk>2009-07-17 12:46:27 +0100
committerThomas White <taw@bitwiz.org.uk>2009-07-17 12:46:27 +0100
commit9ae0abe3414ea26f83fe3e01a37c3cd4819a82b9 (patch)
treed9e87a4b4bc035132a7e93b71c97ba90f257faec
Initial import
-rw-r--r--.gitignore28
-rw-r--r--AUTHORS21
-rw-r--r--COPYING279
-rw-r--r--ChangeLog434
-rw-r--r--INSTALL182
-rw-r--r--Makefile.am12
-rw-r--r--NEWS41
-rw-r--r--README76
-rw-r--r--TODO67
-rwxr-xr-xautogen.sh6
-rw-r--r--configure.ac60
-rw-r--r--data/Makefile.am4
-rw-r--r--data/away-blocked.xpm48
-rw-r--r--data/away.xpm51
-rw-r--r--data/hourglass.pngbin0 -> 16978 bytes
-rw-r--r--data/icon.pngbin0 -> 11835 bytes
-rw-r--r--data/imwindow.ui32
-rw-r--r--data/mainwindow.ui57
-rw-r--r--data/no_avatar.pngbin0 -> 10844 bytes
-rw-r--r--data/occ-blocked.xpm48
-rw-r--r--data/occ.xpm51
-rw-r--r--data/offline-blocked.xpm48
-rw-r--r--data/offline.xpm51
-rw-r--r--data/online-blocked.xpm48
-rw-r--r--data/online.xpm51
-rw-r--r--data/paper.pngbin0 -> 331527 bytes
-rw-r--r--src/Makefile.am9
-rw-r--r--src/about.c81
-rw-r--r--src/about.h30
-rw-r--r--src/accountwindow.c206
-rw-r--r--src/accountwindow.h30
-rw-r--r--src/addcontact.c299
-rw-r--r--src/addcontact.h32
-rw-r--r--src/avatars.c207
-rw-r--r--src/avatars.h36
-rw-r--r--src/blockwindow.c0
-rw-r--r--src/blockwindow.h0
-rw-r--r--src/contactlist.c901
-rw-r--r--src/contactlist.h70
-rw-r--r--src/debug.h33
-rw-r--r--src/error.c121
-rw-r--r--src/error.h32
-rw-r--r--src/filetrans.c57
-rw-r--r--src/filetrans.h30
-rw-r--r--src/fonttrans.c132
-rw-r--r--src/fonttrans.h30
-rw-r--r--src/gtk-ink.c297
-rw-r--r--src/gtk-ink.h68
-rw-r--r--src/ink.c159
-rw-r--r--src/ink.h68
-rw-r--r--src/listcache.c615
-rw-r--r--src/listcache.h41
-rw-r--r--src/listcleanup.c88
-rw-r--r--src/listcleanup.h30
-rw-r--r--src/main.c64
-rw-r--r--src/main.h34
-rw-r--r--src/mainwindow.c1383
-rw-r--r--src/mainwindow.h61
-rw-r--r--src/messagewindow.c1854
-rw-r--r--src/messagewindow.h118
-rw-r--r--src/mime.c242
-rw-r--r--src/mime.h35
-rw-r--r--src/msngenerics.c71
-rw-r--r--src/msngenerics.h47
-rw-r--r--src/msninvite.c32
-rw-r--r--src/msninvite.h32
-rw-r--r--src/msnp11chl.c175
-rw-r--r--src/msnp11chl.h48
-rw-r--r--src/msnp2p.c1325
-rw-r--r--src/msnp2p.h36
-rw-r--r--src/msnprotocol.c1692
-rw-r--r--src/msnprotocol.h44
-rw-r--r--src/options.c1051
-rw-r--r--src/options.h73
-rw-r--r--src/prefswindow.c921
-rw-r--r--src/prefswindow.h30
-rw-r--r--src/reversewindow.c0
-rw-r--r--src/reversewindow.h0
-rw-r--r--src/routines.c800
-rw-r--r--src/routines.h53
-rw-r--r--src/sbprotocol.c1271
-rw-r--r--src/sbprotocol.h46
-rw-r--r--src/sbsessions.c597
-rw-r--r--src/sbsessions.h140
-rw-r--r--src/statusicons.c116
-rw-r--r--src/statusicons.h32
-rw-r--r--src/twnauth.c218
-rw-r--r--src/twnauth.h31
-rw-r--r--src/xml.c186
-rw-r--r--src/xml.h35
-rw-r--r--tuxmessenger.desktop9
91 files changed, 18299 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bcea043
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,28 @@
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+config.h
+config.h.in
+config.h.in~
+config.log
+config.status
+config
+configure
+libesv/.deps
+libesv/.libs
+libesv/Makefile
+libesv/Makefile.in
+libesv/esvseries.lo
+libesv/esvseries.o
+libesv/libesv.la
+libtool
+src/.deps
+src/.libs
+src/Makefile
+src/Makefile.in
+src/*.o
+src/tuxmessenger
+stamp-h
+stamp-h1
+stamp-h.in
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..856d0ef
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,21 @@
+Copyright (C) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+
+Thanks are due to:
+
+ Andrew Millar
+ Thomas Arbesser-Rastburg
+ (for testing the client itself, especially on various non-x86/Linux platforms).
+
+ Matthew Mayer
+ James Bell
+ (for assistance in testing various features).
+
+ Barnaby Gray
+ (for permission to use certain graphics, though not included since version 2.0.0d).
+
+ ZoRoNaX
+ bugy
+ (for massively helping me with Display Pictures and MSNSLP file transfer).
+
+ Hiroyuki Yamamoto
+ (for some UI routines borrowed from the (wonderful) email client "Sylpheed").
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d74542f
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,279 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+-------------------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..14e20f5
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,434 @@
+14th April 2007
+ * Make the Close button in the About box work.
+ * Added contact adjust menu.
+ * Veto three-way session creation when invisible.
+ * Finally fix the problem with contact labels extending past the border of the
+ button.
+
+8th January 2006
+ * Fixed a crash bug when another user adds you.
+
+28th December 2005
+ # Released 2.0.12
+
+21st December 2005
+ * Plumbing for IM window configurability.
+ * Plumbed in 'wget' configurability including checking and auto-configuration.
+ * Fixed a silly colour handing bug.
+
+19th December 2005
+ * Show a message in IM windows when the other end drops offline.
+
+30th November 2005
+ * Don't re-plug sessions into windows when reporting dropped messages
+ due to session timing out when no IM window open.
+
+25th November 2005
+ * Correctly report "Signed in elsewhere" scenario.
+
+22nd November 2005
+ * Fix "Failed Challenge" problem (at last!).
+ # Released 2.0.11
+ * Added missing stdio.h includes.
+ # Released 2.0.11.1
+ * Added missing initialisations for localfont and ofont when options
+ give NULL values. D'oh.
+ # Released 2.0.11.2
+
+13th October 2005
+ * Fix problem with TypingUser reception close to sessions closing.
+
+9th October 2005
+ * Invalidate cache after changing login username.
+ * Complain after closing account details window if no username set.
+ * Fix teeny problem with status changing.
+ * Nice-ify the sizes of the entry widgets when changing name/message.
+ * Widen the border around message windows (makes them look less nasty
+ when maximised).
+ * Added preliminary List Cleanup stuff.
+
+30th September 2005
+ * De-flibbled.
+
+28th September 2005
+ * Properly negotiate protocol version, IE listen to the reply.
+ * Fix the "Newly added contact appears to be offline" bug.
+ * Sort out various other contact list issues.
+
+23rd September 2005
+ * Disable the Ink widget in message windows as well as the text entry.
+
+22nd September 2005
+ * Add "Nudge" and "Ink" to message window menus.
+ * Use en-dashes instead of hyphens next to friendly name in main window.
+ * Upgrade to MSNP12 (no changes other than VER).
+ * Fix a bug where avatars weren't retrieved properly.
+ * Make message window scrollbar "stick" to bottom of conversation.
+
+21st September 2005
+ * Only give one "Connection problems" warning at a time.
+ * Leave non-ready sessions on 217 error.
+ * Send cached messages in the correct order.
+ * Make "Message not sent" notifications actually work.
+ * Send and receive nudges.
+
+16th September 2005
+ * More bugfixes.
+
+10th September 2005
+ * Various bugfixes.
+ # Released 2.0.10
+
+6th September 2005
+ * Cache and later send messages sent before sessions are ready.
+
+5th September 2005
+ * Separate SB sessions and IM windows.
+ * Delete SB sessions that aren't ready after 60 seconds.
+ * Delete SB sessions and disable IM windows when Offline or Invisible.
+ * Fixed a colour processing bug on received messages.
+
+1st September 2005
+ * Actually ignore write attempts to hidden IM windows.
+
+16th July 2005
+ * Use override font for contacts if selected.
+ # Released 2.0.9
+
+15th July 2005
+ * Give windows an icon.
+
+12th July 2005
+ * Finishing touches to sending own fonts.
+
+11th July 2005
+ * Set and send own fonts.
+
+10th July 2005
+ * Tidy up Account Details window.
+ * Make overriding colours work.
+
+7th July 2005
+ * Set own colours per window.
+ * Save default local colour to config file.
+
+6th July 2005
+ * See text colours of other users.
+ * Set own text colours (only via default at the moment).
+
+5th July 2005
+ * Add layout of preferences window.
+ # Released 2.0.8
+
+1st July 2005
+ * Center text in "Sign In" button.
+
+26th June 2005
+ * Don't try to create a new UIContact for someone if they already have
+ one.
+ * Removed --no-check-certificate again (doesn't work with my copy
+ of wget..)
+
+17th June 2005
+ * Added --no-check-certificate to wget's options during the login.
+ (Fixed a failed authentication under certain circumstances).
+ - Will make this optional later.
+
+15th June 2005
+ * Keypad Enter sends messages.
+ * Fix silly logic bug introduced yesterday (changing online status).
+
+14th June 2005
+ * Prevent opening of IM windows while Invisible.
+ * Remember to remove Added Contact window when responding.
+ * Store "soft" (menubar-only) options in config file.
+
+13th June 2005
+ * Don't lose the CSM when re-requesting list (Consequence of not
+ loading the cache unless it's up to date).
+ * Reject obviously invalid new contacts.
+ # Released 2.0.6
+ * Fixed a big fat crash when people not on your contact list invite
+ you to a conversation.
+ * Remember to urldecode new contact friendlynames.
+ * Fixed a crash connected with being added by new contacts.
+ (Indirection operator silliness in contactlist.c)
+ # Released 2.0.7
+
+12th June 2005
+ * Nice-ify error/warning dialogs.
+
+11th June 2005
+ * Add "Add Contact" system.
+ * Respond to other users adding you to their lists.
+
+10th June 2005
+ * Don't load list cache unless SYN shows it's up-to-date.
+
+7th June 2005
+ * Don't break the "Hide offline contacts" option when
+ signing out.
+ * Don't display Join and Leave messages for two-way conversations.
+ Will find a better way to (optionally) show session
+ disconnection.
+
+6th June 2005
+ * Hide offline contacts option.
+
+3rd June 2005
+ * Hopefully fixed the Ink crash - to be tested.
+ * Fix overzealous assertion in messagewindow_addtext_system:
+ receiving NAK might result in this being called on
+ a hidden session.
+ # Released 2.0.5
+
+30th May 2005
+ * Second attempt at fixing XML entity silliness.
+ # Released 2.0.0d-003
+ * Remove overzealous assertion from contactlist_friendlyname.
+ (Allows RNG handler to fall back on RNG's friendlyname
+ if there isn't one in the contact list already).
+ # Released 2.0.0d-004
+
+29th May 2005
+ * Attempt to fix some XML entity silliness.
+ # Released 2.0.0d-002
+
+27th May 2005
+ * Comment out GTK-2.6.0-isms when only 2.4.x is available.
+ * Allow user to sign out before signing in is complete.
+ # Released 2.0.0d-001
+ * Fixed GTK-2.4.x compatability so that it actually works.
+ * Display a warning message if NAK is received.
+
+26th May 2005
+ * Fix crash when someone unknown gets added to a conversation.
+
+19th May 2005
+ * Make accelerator keys work for IM windows.
+ * Fix lame-isms in twnauth.c
+
+17th May 2005
+ * Fixed bugs with multipacket reassembly.
+ * Fix silly bug where NOT syntax wasn't properly read.
+ * Sorted out port numbers so they're all changed to default sensible values if
+ the server tries to ask for connection on port zero.
+
+13th May 2005
+ * Cache CSM and 'reinforce' it to the server at sign-in.
+
+12th May 2005
+ * Add status menu to menu bar.
+ * Provide more information in contact list tooltips.
+
+10th May 2005
+ * Change avatar display system so that transparency works.
+ * Fix problem where status ComboBox was sometimes not updated to "Appear Offline"
+ after signing in.
+
+8th May 2005
+ * Tweak capabilities code (now MSNC4, GIF ink (not ISF), multipacketing).
+ * Store CSM for local user as UBX block rather than as raw string.
+ * Don't decode entities in UBX data (let Pango do it later).
+ * Fix MSNP11 challenge zero-padding so that it actually works.
+
+7th May 2005
+ * Reveal CSM bar if hidden when trying to set CSM.
+
+6th May 2005
+ * See MSNP11 Custom Status Messages.
+ * Awareness of (but not parsing) of NOT (notifications).
+ * Hopefully fix MSNP11 challenge.
+
+4th May 2005
+ * Upgraded to MSNP11.
+ * Fix crash when local avatar file didn't exist.
+ * Tweak sbsessions_trycreate logic to avoid randomly (but rarely) opening
+ IM windows prematurely.
+
+3rd May 2005
+ * Fix packing problems with status bars in IM windows.
+ * Add 10-second delay before deleting MSNP2P records (catch straggling ACKs).
+
+1st May 2005
+ * Receive Ink messages (but not decode them yet).
+
+30th April 2005
+ * Tidy up MIME processor a bit (const-clean, stop at end of headers, handle
+ various line-end conventions).
+
+28th April 2005
+ * Reconstruction of received multipacketed messages.
+
+26th April 2005
+ * Add ability to hide avatars in IM windows.
+
+24th April 2005
+ * Fix multipacketing Chunk numbers so it works.
+
+23rd April 2005
+ * Confirm when quitting.
+ * Sign out before quitting.
+ * Close IM windows with Conversation->Close menu item.
+ * Add "About" box.
+ * Add ability to send multi-packeted messages.
+ * Implement "Change Screen Name" from Tools menu.
+
+22nd April 2005
+ * Make the Account Details window work.
+
+21st April 2005
+ * Don't try to update combobox with new user status when CHG is received.
+ (Forcing the combobox to change generates a signal as if it had
+ been changed by the user, resulting in a potential loop)
+ * Separate account details into separate window.
+ * Implement most of the "Connection" menu functions.
+
+20th April 2005
+ * Add preliminary menu bar to main window.
+ * Replace button with combobox for setting online status.
+ * Add preliminary menu bars to message windows.
+
+19th April 2005
+ * Add unknown users who RNG to the TL as early as possible (avoid not knowing the
+ friendly name shortly afterwards when it's required).
+ * Remove fixed widget sizes from contact list window.
+
+18th April 2005
+ * Fix Muppet Bug where the return value of read() was added to a buffer offset.
+ (read() can return -1).
+
+10th April 2005
+ * Don't fix vertical size for status bars.
+
+9th April 2005
+ * Small fixes.
+
+8th April 2005
+ * Generally Sort Out list caching.
+
+7th April 2005
+ * Add drag-and-dropping of users to add them to SB sessions.
+ * Fix various DP problems.
+ * Drag one user onto another to get a three-way conversation.
+
+6th April 2005
+ * (Hopefully) fix msnp2p_abortall() so it actually works.
+ * Add system to change local friendly name.
+ * Save options to file on "Apply".
+
+1th March 2005
+ * Fix bug caused by leaving a multi-way session.
+
+7th March 2005
+ * Delete sessions if IM window is closed before session is "ready".
+ * "const" cleanups for options stuff.
+ * Sign out if username is changed from Configuration window.
+
+5th February 2005
+ * Read options from ~/.tuxmessenger/config file.
+
+31st January 2005
+ * "Pull the plug" forcibly if socket doesn't respond when trying to
+ disconnect.
+ * Don't include password in debug output (unless requested by #define).
+ * Send pings - hence keep connection alive and detect when it breaks.
+ (Auto-reconnect to follow at a later date).
+
+30th January 2005
+ * "Kick" display pictures based on data hash rather than user name (allows
+ for two users having identical pictures).
+ * Delete and reinitiate download of a picture if it's corrupted.
+ * Implement little-endianising routines (simply a call to a GLib macro).
+
+29th January 2005
+ * Prevent Shift, Ctrl, Alt and Meta keys from invoking TypingUser.
+
+28th January 2005
+ * Fix crash when cleaning up "half-open" sessions.
+
+26th January 2005
+ * Remove assert() for list number - allowing for future expansion.
+ * Clean up "half-open" sessions on NS disconnection - prevents "wrong user
+ joining" problems.
+
+22nd January 2005
+ * Further MSNP2P cleanups, particularly endian-ness. Picture receive from
+ aMSN now works.
+
+21st January 2005
+ * ACTUALLY don't start conflicting DP receive transfers.
+ * MSNP2P cleanups.
+
+20th January 2005
+ * Trivial fix to MSNP2P ACK-generator. Need to test this with aMSN...
+
+19th January 2005
+ * Prevent msnp2p_compare_savefilename from dereferencing NULL.
+ * Generate a proper GUID for the Call-ID field when downloading DPs.
+
+18th January 2005
+ * Switched to GTK2. Doesn't look quite as nice on my system, but nothing
+ that can't be fixed with theming. Best long-term investment, since
+ GTK2 has far superior support for graphics and internationalization.
+
+17th January 2005
+ * Further memory leak fixes.
+ * Fixed a confusing SB session tracking problem (neg_trid not initialised
+ to zero for remotely-started sessions).
+ * Refuse to start a new DP receive transfer while would write to the same
+ filename as one already in progress.
+
+16th January 2005
+ * Fix lots of little memory leaks.
+ * Tidy up debug messages from sbsessions_find_username().
+
+15th January 2005
+ * No longer closes SB sessions straight away when the window is closed at
+ this end. No longer open message windows straight away when an
+ SB session is created from the other end.
+
+14th January 2005
+ * Fixed serious problem with socket code (DS/NS and SB: write buffer
+ offset wasn't updated in *_writeable!).
+
+13th January 2005
+ * Continued MSNP2P/MSNSLP tidy-up. Solved *very* nasty problem to do
+ with name "endian.h" conflicting (via automake) with a system
+ header.
+
+12th January 2005
+ * Receiving of other users' DPs.
+ * Tidy-up of MSNP2P/MSNSLP code.
+
+11th January 2005
+ * Check number of received bytes isn't negative before adding it to the
+ buffer offset (!).
+ * "Lint" session (with -W -Wall -ansi -pedantic) to clean things up a little.
+ * Compare usernames case-insensitively to allow for MSN servers converting
+ usernames to lower-case.
+ * Sending of local user's DP.
+
+10th January 2005
+ * Cancel "TypingUser" callback when a message comes through (don't just
+ reset status bar).
+
+9th January 2005
+ * Receive "TypingUser" control - see when other users are typing.
+
+6th January 2005
+ * Create ~/.tuxmessenger and contents if not already present.
+ * Put graphics in /usr/local/share (etc) on "make install".
+ * Fixed stupid initialisation bugs in low-level SB protocol code.
+ * Altered main window size.
+
+5th January 2005
+ * Fixed silly double-free bug with contact->dpobject.
+ * Added sending of x-clientcaps control,
+ * Added sending User-Agent string with normal messages.
+
+23rd December 2004
+ * Transferred to GNU Autotools (autoconf, automake etc).
+
+September 2004 - January 2005
+ * Rewrite from scratch.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..b42a17a
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,182 @@
+Basic Installation
+==================
+
+ These are generic installation instructions.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation. It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions. Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, a file
+`config.cache' that saves the results of its tests to speed up
+reconfiguring, and a file `config.log' containing compiler output
+(useful mainly for debugging `configure').
+
+ If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release. If at some point `config.cache'
+contains results you don't want to keep, you may remove or edit it.
+
+ The file `configure.in' is used to create `configure' by a program
+called `autoconf'. You only need `configure.in' if you want to change
+it or regenerate `configure' using a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+ 1. `cd' to the directory containing the package's source code and type
+ `./configure' to configure the package for your system. If you're
+ using `csh' on an old version of System V, you might need to type
+ `sh ./configure' instead to prevent `csh' from trying to execute
+ `configure' itself.
+
+ Running `configure' takes awhile. While running, it prints some
+ messages telling which features it is checking for.
+
+ 2. Type `make' to compile the package.
+
+ 3. Optionally, type `make check' to run any self-tests that come with
+ the package.
+
+ 4. Type `make install' to install the programs and any data files and
+ documentation.
+
+ 5. You can remove the program binaries and object files from the
+ source code directory by typing `make clean'. To also remove the
+ files that `configure' created (so you can compile the package for
+ a different kind of computer), type `make distclean'. There is
+ also a `make maintainer-clean' target, but that is intended mainly
+ for the package's developers. If you use it, you may have to get
+ all sorts of other programs in order to regenerate files that came
+ with the distribution.
+
+Compilers and Options
+=====================
+
+ Some systems require unusual options for compilation or linking that
+the `configure' script does not know about. You can give `configure'
+initial values for variables by setting them in the environment. Using
+a Bourne-compatible shell, you can do that on the command line like
+this:
+ CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
+
+Or on systems that have the `env' program, you can do it like this:
+ env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
+
+Compiling For Multiple Architectures
+====================================
+
+ You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory. To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+ If you have to use a `make' that does not supports the `VPATH'
+variable, you have to compile the package for one architecture at a time
+in the source code directory. After you have installed the package for
+one architecture, use `make distclean' before reconfiguring for another
+architecture.
+
+Installation Names
+==================
+
+ By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc. You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'.
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+give `configure' the option `--exec-prefix=PATH', the package will use
+PATH as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+ In addition, if you use an unusual directory layout you can give
+options like `--bindir=PATH' to specify different values for particular
+kinds of files. Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+ If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+ Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System). The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+ For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+ There may be some features `configure' can not figure out
+automatically, but needs to determine by the type of host the package
+will run on. Usually `configure' can figure that out, but if it prints
+a message saying it can not guess the host type, give it the
+`--host=TYPE' option. TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name with three fields:
+ CPU-COMPANY-SYSTEM
+
+See the file `config.sub' for the possible values of each field. If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the host type.
+
+ If you are building compiler tools for cross-compiling, you can also
+use the `--target=TYPE' option to select the type of system they will
+produce code for and the `--build=TYPE' option to select the type of
+system on which you are compiling the package.
+
+Sharing Defaults
+================
+
+ If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists. Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Operation Controls
+==================
+
+ `configure' recognizes the following options to control how it
+operates.
+
+`--cache-file=FILE'
+ Use and save the results of the tests in FILE instead of
+ `./config.cache'. Set FILE to `/dev/null' to disable caching, for
+ debugging `configure'.
+
+`--help'
+ Print a summary of the options to `configure', and exit.
+
+`--quiet'
+`--silent'
+`-q'
+ Do not print messages saying which checks are being made. To
+ suppress all normal output, redirect it to `/dev/null' (any error
+ messages will still be shown).
+
+`--srcdir=DIR'
+ Look for the package's source code in directory DIR. Usually
+ `configure' can determine that directory automatically.
+
+`--version'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`configure' also accepts some other, not widely useful, options.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..49f8c92
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,12 @@
+EXTRA_DIST = configure \
+ src/about.h src/listcache.h src/mainwindow.h src/msnprotocol.h src/sbprotocol.h \
+ src/addcontact.h src/contactlist.h src/listcleanup.h src/messagewindow.h src/accountwindow.h \
+ src/options.h src/sbsessions.h src/twnauth.h src/error.h src/debug.h src/reversewindow.h \
+ src/msngenerics.h src/routines.h src/statusicons.h src/avatars.h src/mime.h src/xml.h \
+ src/main.h src/msninvite.h src/msnp2p.h src/prefswindow.h src/blockwindow.h \
+ src/ink.h src/msnp11chl.h src/gtk-ink.h src/fonttrans.h src/filetrans.h \
+ data/online.xpm data/away.xpm data/occ.xpm data/offline.xpm \
+ data/online-blocked.xpm data/away-blocked.xpm data/occ-blocked.xpm data/offline-blocked.xpm \
+ data/no_avatar.png data/hourglass.png data/icon.png data/mainwindow.ui data/imwindow.ui \
+ data/paper.png
+SUBDIRS = src data
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..535048a
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,41 @@
+Version 2.0.13
+--------------
+* Bugfixes as usual.
+* The contact list context menus have been added.
+
+Version 2.0.12
+--------------
+* The "wget" plumbing has finally been done - you can now ask TM2 to automatically
+ work out the right configuration for this tricky part.
+* Various other configuration options have been 'plugged in'.
+* The odd bugfix.
+
+Version 2.0.11
+--------------
+* Lots of bugfixes and tweaks have been made.
+* Message windows now let you draw handwritten messages. Can't send or
+ receive them yet, though. That'll take significantly longer.
+* "Nudges" are now supported.
+* The preliminary bits of "List Cleanup" have been done.
+
+Version 2.0.10
+--------------
+* Massive changes to the way sessions and IM windows talk to each other. It is
+ now possible to open a message window and send a message immediately,
+ without worry about waiting for the session to connect first.
+
+Version 2.0.9
+-------------
+* Much work has been done on the use of fonts in coversations. You can now
+ set your own font and text colour, override contacts' fonts and colours,
+ and see the colours your contacts use. You can't see their fonts yet,
+ though.
+* TM's windows now have an icon.
+
+Version 2.0.8
+-------------
+* The "menubar-only" options are now saved.
+* IM windows are now prevented from being opened while you are "Invisible".
+* Small bugfix connected with being added by other people.
+* Keypad Enter now sends messages.
+* Various other small fixes and improvements.
diff --git a/README b/README
new file mode 100644
index 0000000..664c80f
--- /dev/null
+++ b/README
@@ -0,0 +1,76 @@
+---------------------------------------------------------------------------
+
+TuxMessenger
+ - a GTK+-based client for Microsoft MSN Messenger service
+
+ Version 2.0.10
+
+By Thomas White <taw27@srcf.ucam.org>
+
+---------------------------------------------------------------------------
+
+Copyright (c) Thomas White, 2002-2005, except where otherwise stated.
+Please see the "AUTHORS" file for a full list of credits and thanks.
+
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991.
+
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this package; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ 02111-1307, USA.
+
+--------------------------------------------------------------------------
+
+Installation follows the standard procedure for installing programs from source.
+See the "INSTALL" file for the ugly details, otherwise follow these simple steps
+at a command prompt.
+
+1) Unpack the source code tarball: tar -xzvf tuxmessenger-2.0.0d.tar.gz
+
+2) Change to the top level of the source tree: cd tuxmessenger-2.0.0d
+
+3) Run "./configure" to check the build environment. You will need various
+ libraries installed, including their C header files ("Development"
+ packages). You will be told if something is missing.
+
+4) Type "make" to compile the program.
+
+5) Become root, then type "make install".
+
+6) Type "tuxmessenger" to run.
+
+Should you wish to remove the program, simply "make uninstall". You may also
+wish to erase TuxMessenger's configuration files: "rm -rf ~/.tuxmessenger".
+
+Unlike with other MSN clients, you do not need to mess around with SSL or
+other cryptography libraries. TuxMessenger invokes "wget" to do the same
+job. Accordingly, you'll need a copy of "wget" which can retrieve "https:"
+URLs. To test if a suitable wget is installed or not, type the following
+at a shell prompt:
+
+ wget https://www.google.com/
+
+If all is well, the last line of the output produced should contain
+"`index.html' saved". If you get an "Unsupported scheme." message,
+you have wget installed but it doesn't understand https. In this case,
+you'll have to get hold of a suitable "wget". This *might* mean compiling
+one yourself.
+
+Whatever "wget" you use, you'll need to tell TuxMessenger about it's
+location in the filesystem. To find out the relevant location, type this
+in a shell:
+
+ which wget
+
+If you've had to find a special wget for this, make a note of the location
+you installed it to. Enter the location information under the "General" tab
+in the Preferences window.
+
+Problems / questions / suggestions / bug reports to taw27@srcf.ucam.org
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..4edef96
--- /dev/null
+++ b/TODO
@@ -0,0 +1,67 @@
+Blatent Bugs
+------------
+
+* Big fat crash on receiving a P2P Ink message.
+* Big fat crash for CSCA :/
+* Failed assertion (messagewindow.c:1241) on DnDing a user to a "dead" multi-way conversation
+
+Annoyances
+----------
+
+* There's a repeating MSNP2P packet with Flags=4 from the Official Client
+ after it receives the display picture from TM2.
+ - "Waiting for response".
+* Signedness issues.
+
+To Do List
+----------
+
+* "List Cleanup".
+ - UI parts.
+* UI for setting DP.
+* Contact list menu (Message, Send File?, Block, Unblock, Delete).
+* Allow/Block list support.
+ - "Block" message window button.
+ - Tools->Block User.
+ - Different contact list icons when blocked.
+* File transfer
+ - MSNSLP.
+ - "Synergise" MSNP2P multipacket reception.
+ - MSNFTP.
+* Change \n to \r\n in outgoing messages.
+* Invite users to conversations via Ugly Method (Conversation->Add).
+* Update message window titles when friendlynames change.
+ - Display number of participants in message window title bar.
+* Auto-reconnect.
+* IRC-style message window output.
+* Timestamp messages.
+* Sort contact list.
+
+# 2.1.0 "release".
+
+* Contact list groups (Redesign contact list UI?).
+* Emoticons.
+* Ink messages.
+* Set P4-Context friendly name
+ - Per-IM-window?
+* Useful log information window.
+
+Stuff that isn't my fault
+-------------------------
+
+* Resize main window during sign-in ---> nastiness.
+ - GTK bug?
+ - Happens with GTK 2.0.2
+ - Reported not to happen with GTK 2.2.4
+ - Doesn't happen with GTK 2.6.4
+ - Clicking on contact "Buttons" redraws them.
+* Gaim and other libgaim-based clients close the SB session
+ after completing an avatar transfer.
+* There isn't a way to recover the CSM from the server.
+
+Features That Aren't Going To Happen
+------------------------------------
+
+* Animated emoticons or avatars.
+* Networks other than MSN.
+* Custom emoticons. (?)
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..4960d71
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+aclocal
+autoconf
+autoheader
+automake -a
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..25dbbed
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,60 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT(TuxMessenger, 2.0.13, taw27@srcf.ucam.org)
+
+dnl Version number
+VERSION=AC_PACKAGE_VERSION
+
+AM_CONFIG_HEADER(config.h)
+AM_INIT_AUTOMAKE(tuxmessenger, "$VERSION")
+
+OLD_CFLAGS="$CFLAGS"
+AC_PROG_CC
+CFLAGS="$OLD_CFLAGS"
+
+AC_PROG_AWK
+AC_PROG_INSTALL
+AC_PROG_LN_S
+
+AC_HEADER_STDC
+AC_CHECK_HEADERS([fcntl.h netdb.h netinet/in.h stdlib.h string.h sys/socket.h unistd.h])
+AC_C_CONST
+AC_FUNC_MALLOC
+AC_TYPE_SIGNAL
+AC_TYPE_SIZE_T
+AC_FUNC_STAT
+AC_FUNC_REALLOC
+AC_CHECK_FUNCS([gethostbyname memmove memset socket strdup strstr strcasecmp mkdir])
+
+dnl Check for GTK+ 2.6.0 (prefered version, but not mandatory)
+AM_PATH_GTK_2_0(2.6.0,AC_DEFINE(HAVE_GTK_2_6_0, TRUE, [Have GTK 2.6.0?]),AC_MSG_WARN([
+*** Didn't find GTK 2.6.0 or above - some (insignificant) features will be disabled...]))
+
+dnl Now check for GTK+ 2.4.0 (minimum acceptable version)
+dnl This has to be done after checking for 2.6.x in order to have the flags set up.
+AM_PATH_GTK_2_0(2.4.0,,AC_MSG_ERROR([
+*** GTK+ 2.4.0 or above is required by TuxMessenger. Please make sure you have the GTK+
+*** development files installed. The latest version of GTK+ is
+*** always available at http://www.gtk.org/.]))
+
+AM_PATH_XML2(2.5.0,,AC_MSG_ERROR([
+*** Libxml is required to build TuxMessenger; Available at
+http://www.libxml.org/.]))
+
+dnl Check for OpenSSL
+AC_MSG_CHECKING([if openssl is available])
+LIBS="$LIBS -lssl -lcrypto"
+AC_TRY_LINK([#include <openssl/opensslv.h>], [ return OPENSSL_VERSION_NUMBER; ],
+ [ AC_MSG_RESULT(yes) ],
+ [ AC_MSG_RESULT(no)
+ AC_MSG_ERROR([*** Couldn't find OpenSSL ***]) ] )
+
+dnl Check for GNOME-Canvas
+AC_MSG_CHECKING([for GNOME-Canvas])
+CFLAGS="$CFLAGS `pkg-config --cflags libgnomecanvas-2.0`"
+LIBS="$LIBS `pkg-config --libs libgnomecanvas-2.0`"
+AC_TRY_LINK([#include <libgnomecanvas/libgnomecanvas.h>], ,
+ [ AC_MSG_RESULT(found) ],
+ [ AC_MSG_RESULT(not found)
+ AC_MSG_ERROR([*** Couldn't find GNOME-Canvas ***]) ] )
+
+AC_OUTPUT(Makefile src/Makefile data/Makefile)
diff --git a/data/Makefile.am b/data/Makefile.am
new file mode 100644
index 0000000..e236b17
--- /dev/null
+++ b/data/Makefile.am
@@ -0,0 +1,4 @@
+tuxmessengerdir = $(datadir)/tuxmessenger
+tuxmessenger_DATA = online.xpm away.xpm occ.xpm offline.xpm \
+ online-blocked.xpm away-blocked.xpm occ-blocked.xpm offline-blocked.xpm \
+ no_avatar.png hourglass.png icon.png mainwindow.ui imwindow.ui paper.png
diff --git a/data/away-blocked.xpm b/data/away-blocked.xpm
new file mode 100644
index 0000000..eaf183f
--- /dev/null
+++ b/data/away-blocked.xpm
@@ -0,0 +1,48 @@
+/* XPM */
+static char * away_blocked_xpm[] = {
+"16 16 29 1",
+" c None",
+". c #FF0000",
+"+ c #E8EF00",
+"@ c #E1E700",
+"# c #DADF00",
+"$ c #D3D800",
+"% c #CCD001",
+"& c #C5C801",
+"* c #BEC001",
+"= c #B7B801",
+"- c #B0B101",
+"; c #AAA902",
+"> c #A3A102",
+", c #9C9902",
+"' c #959202",
+") c #8E8A02",
+"! c #878202",
+"~ c #807A03",
+"{ c #797203",
+"] c #726B03",
+"^ c #6B6303",
+"/ c #645B03",
+"( c #5E5304",
+"_ c #574C04",
+": c #504404",
+"< c #493C04",
+"[ c #423404",
+"} c #3B2C05",
+"| c #342505",
+"..+@#$%&*=-;>,')",
+"...#$%&*=-;>,')!",
+"+...%&*=-;>,')!~",
+"@#...*=-;>,')!~{",
+"#$%...-;>,')!~{]",
+"$%&*...>,')!~{]^",
+"%&*=-...')!~{]^/",
+"&*=-;>...!~{]^/(",
+"*=-;>,'...{]^/(_",
+"=-;>,')!...^/(_:",
+"-;>,')!~{...(_:<",
+";>,')!~{]^...:<[",
+">,')!~{]^/(...[}",
+",')!~{]^/(_:...|",
+"')!~{]^/(_:<[...",
+")!~{]^/(_:<[}|.."};
diff --git a/data/away.xpm b/data/away.xpm
new file mode 100644
index 0000000..ff09dc6
--- /dev/null
+++ b/data/away.xpm
@@ -0,0 +1,51 @@
+/* XPM */
+static char * away_xpm[] = {
+"16 16 32 1",
+" c None",
+". c #F6FF00",
+"+ c #EFF700",
+"@ c #E8EF00",
+"# c #E1E700",
+"$ c #DADF00",
+"% c #D3D800",
+"& c #CCD001",
+"* c #C5C801",
+"= c #BEC001",
+"- c #B7B801",
+"; c #B0B101",
+"> c #AAA902",
+", c #A3A102",
+"' c #9C9902",
+") c #959202",
+"! c #8E8A02",
+"~ c #878202",
+"{ c #807A03",
+"] c #797203",
+"^ c #726B03",
+"/ c #6B6303",
+"( c #645B03",
+"_ c #5E5304",
+": c #574C04",
+"< c #504404",
+"[ c #493C04",
+"} c #423404",
+"| c #3B2C05",
+"1 c #342505",
+"2 c #2D1D05",
+"3 c #261505",
+".+@#$%&*=-;>,')!",
+"+@#$%&*=-;>,')!~",
+"@#$%&*=-;>,')!~{",
+"#$%&*=-;>,')!~{]",
+"$%&*=-;>,')!~{]^",
+"%&*=-;>,')!~{]^/",
+"&*=-;>,')!~{]^/(",
+"*=-;>,')!~{]^/(_",
+"=-;>,')!~{]^/(_:",
+"-;>,')!~{]^/(_:<",
+";>,')!~{]^/(_:<[",
+">,')!~{]^/(_:<[}",
+",')!~{]^/(_:<[}|",
+"')!~{]^/(_:<[}|1",
+")!~{]^/(_:<[}|12",
+"!~{]^/(_:<[}|123"};
diff --git a/data/hourglass.png b/data/hourglass.png
new file mode 100644
index 0000000..73c77f7
--- /dev/null
+++ b/data/hourglass.png
Binary files differ
diff --git a/data/icon.png b/data/icon.png
new file mode 100644
index 0000000..98984da
--- /dev/null
+++ b/data/icon.png
Binary files differ
diff --git a/data/imwindow.ui b/data/imwindow.ui
new file mode 100644
index 0000000..af3a8ec
--- /dev/null
+++ b/data/imwindow.ui
@@ -0,0 +1,32 @@
+<ui>
+ <menubar name="imwindow">
+
+ <menu name="conversation" action="ConversationAction">
+ <menuitem name="invite_user" action="InviteAction" />
+ <separator />
+ <menuitem name="mode_text" action="ModeTextAction" />
+ <menuitem name="mode_ink" action="ModeInkAction" />
+ <separator />
+ <menuitem name="close" action="CloseAction" />
+ </menu>
+
+ <menu name="view" action="ViewAction">
+ <menuitem name="font" action="FontAction" />
+ <menuitem name="irc_style" action="IRCStyleAction" />
+ <menuitem name="timestamp" action="TimeStampAction" />
+ <menuitem name="display_avatars" action="AvatarsAction" />
+ <menuitem name="emoticons" action="EmoticonsAction" />
+ </menu>
+
+ <menu name="tools" action="ToolsAction">
+ <menuitem name="nudge" action="NudgeAction" />
+ <menuitem name="block_user" action="BlockAction" />
+ <menuitem name="send_file" action="SendFileAction" />
+ </menu>
+
+ <menu name="help" action="HelpAction">
+ <menuitem name="about" action="AboutAction" />
+ </menu>
+
+ </menubar>
+</ui>
diff --git a/data/mainwindow.ui b/data/mainwindow.ui
new file mode 100644
index 0000000..b7c7ae1
--- /dev/null
+++ b/data/mainwindow.ui
@@ -0,0 +1,57 @@
+<ui>
+ <menubar name="mainwindow">
+
+ <menu name="connection" action="ConnectionAction">
+ <menuitem name="sign_in" action="SignInAction" />
+ <menuitem name="account" action="AccountAction" />
+ <separator />
+ <menuitem name="sign_out" action="SignOutAction" />
+ <menuitem name="quit" action="QuitAction" />
+ </menu>
+
+ <menu name="view" action="ViewAction">
+ <menuitem name="show_offline" action="ShowOfflineAction" />
+ <menuitem name="show_groups" action="ShowGroupsAction" />
+ <menuitem name="show_csm" action="ShowCSMAction" />
+ <separator />
+ <menuitem name="sort_alphabet" action="SortAlphaAction" />
+ <menuitem name="sort_activity" action="SortActiveAction" />
+ </menu>
+
+ <menu name="status" action="StatusAction">
+ <menuitem name="status_nln" action="StatusNLNAction" />
+ <menuitem name="status_awy" action="StatusAWYAction" />
+ <menuitem name="status_bsy" action="StatusBSYAction" />
+ <menuitem name="status_brb" action="StatusBRBAction" />
+ <menuitem name="status_phn" action="StatusPHNAction" />
+ <menuitem name="status_lun" action="StatusLUNAction" />
+ <menuitem name="status_hdn" action="StatusHDNAction" />
+ <separator />
+ <menuitem name="change_name" action="ChangeNameAction" />
+ <menuitem name="change_csm" action="ChangeCSMAction" />
+ <menuitem name="change_avatar" action="ChangeAvatarAction" />
+ </menu>
+
+ <menu name="tools" action="ToolsAction">
+ <menuitem name="add_contact" action="AddContactAction" />
+ <menuitem name="blockallow_lists" action="BlockAllowAction" />
+ <menuitem name="reverse_list" action="ReverseAction" />
+ <menuitem name="list_cleanup" action="ListCleanupAction" />
+ <separator />
+ <menuitem name="preferences" action="PreferencesAction" />
+ </menu>
+
+ <menu name="help" action="HelpAction">
+ <menuitem name="about" action="AboutAction" />
+ </menu>
+ </menubar>
+
+ <popup name="contact">
+ <menuitem name="message" action="ContactMessageAction" />
+ <menuitem name="sendfile" action="ContactSendFileAction" />
+ <menuitem name="block" action="ContactBlockAction" />
+ <menuitem name="unblock" action="ContactUnblockAction" />
+ <menuitem name="delete" action="ContactDeleteAction" />
+ </popup>
+
+</ui>
diff --git a/data/no_avatar.png b/data/no_avatar.png
new file mode 100644
index 0000000..038edc8
--- /dev/null
+++ b/data/no_avatar.png
Binary files differ
diff --git a/data/occ-blocked.xpm b/data/occ-blocked.xpm
new file mode 100644
index 0000000..c63b201
--- /dev/null
+++ b/data/occ-blocked.xpm
@@ -0,0 +1,48 @@
+/* XPM */
+static char * occ_blocked_xpm[] = {
+"16 16 29 1",
+" c None",
+". c #FF0000",
+"+ c #F00000",
+"@ c #E90000",
+"# c #E20000",
+"$ c #DB0000",
+"% c #D30101",
+"& c #CC0101",
+"* c #C50101",
+"= c #BE0101",
+"- c #B70101",
+"; c #AF0202",
+"> c #A80202",
+", c #A10202",
+"' c #9A0202",
+") c #930202",
+"! c #8C0202",
+"~ c #840303",
+"{ c #7D0303",
+"] c #760303",
+"^ c #6F0303",
+"/ c #680303",
+"( c #600404",
+"_ c #590404",
+": c #520404",
+"< c #4B0404",
+"[ c #440404",
+"} c #3C0505",
+"| c #350505",
+"..+@#$%&*=-;>,')",
+"...#$%&*=-;>,')!",
+"+...%&*=-;>,')!~",
+"@#...*=-;>,')!~{",
+"#$%...-;>,')!~{]",
+"$%&*...>,')!~{]^",
+"%&*=-...')!~{]^/",
+"&*=-;>...!~{]^/(",
+"*=-;>,'...{]^/(_",
+"=-;>,')!...^/(_:",
+"-;>,')!~{...(_:<",
+";>,')!~{]^...:<[",
+">,')!~{]^/(...[}",
+",')!~{]^/(_:...|",
+"')!~{]^/(_:<[...",
+")!~{]^/(_:<[}|.."};
diff --git a/data/occ.xpm b/data/occ.xpm
new file mode 100644
index 0000000..c88b0c6
--- /dev/null
+++ b/data/occ.xpm
@@ -0,0 +1,51 @@
+/* XPM */
+static char * occ_xpm[] = {
+"16 16 32 1",
+" c None",
+". c #FF0000",
+"+ c #F70000",
+"@ c #F00000",
+"# c #E90000",
+"$ c #E20000",
+"% c #DB0000",
+"& c #D30101",
+"* c #CC0101",
+"= c #C50101",
+"- c #BE0101",
+"; c #B70101",
+"> c #AF0202",
+", c #A80202",
+"' c #A10202",
+") c #9A0202",
+"! c #930202",
+"~ c #8C0202",
+"{ c #840303",
+"] c #7D0303",
+"^ c #760303",
+"/ c #6F0303",
+"( c #680303",
+"_ c #600404",
+": c #590404",
+"< c #520404",
+"[ c #4B0404",
+"} c #440404",
+"| c #3C0505",
+"1 c #350505",
+"2 c #2E0505",
+"3 c #270505",
+".+@#$%&*=-;>,')!",
+"+@#$%&*=-;>,')!~",
+"@#$%&*=-;>,')!~{",
+"#$%&*=-;>,')!~{]",
+"$%&*=-;>,')!~{]^",
+"%&*=-;>,')!~{]^/",
+"&*=-;>,')!~{]^/(",
+"*=-;>,')!~{]^/(_",
+"=-;>,')!~{]^/(_:",
+"-;>,')!~{]^/(_:<",
+";>,')!~{]^/(_:<[",
+">,')!~{]^/(_:<[}",
+",')!~{]^/(_:<[}|",
+"')!~{]^/(_:<[}|1",
+")!~{]^/(_:<[}|12",
+"!~{]^/(_:<[}|123"};
diff --git a/data/offline-blocked.xpm b/data/offline-blocked.xpm
new file mode 100644
index 0000000..34b6832
--- /dev/null
+++ b/data/offline-blocked.xpm
@@ -0,0 +1,48 @@
+/* XPM */
+static char * offline_blocked_xpm[] = {
+"16 16 29 1",
+" c None",
+". c #FF0000",
+"+ c #F0EFEF",
+"@ c #E9E7E7",
+"# c #E2DFDF",
+"$ c #DBD8D8",
+"% c #D3D0D0",
+"& c #CCC8C8",
+"* c #C5C0C0",
+"= c #BEB8B8",
+"- c #B7B1B1",
+"; c #AFA9A9",
+"> c #A8A1A1",
+", c #A19999",
+"' c #9A9292",
+") c #938A8A",
+"! c #8C8282",
+"~ c #847A7A",
+"{ c #7D7272",
+"] c #766B6B",
+"^ c #6F6363",
+"/ c #685B5B",
+"( c #605353",
+"_ c #594C4C",
+": c #524444",
+"< c #4B3C3C",
+"[ c #443434",
+"} c #3C2C2C",
+"| c #352525",
+"..+@#$%&*=-;>,')",
+"...#$%&*=-;>,')!",
+"+...%&*=-;>,')!~",
+"@#...*=-;>,')!~{",
+"#$%...-;>,')!~{]",
+"$%&*...>,')!~{]^",
+"%&*=-...')!~{]^/",
+"&*=-;>...!~{]^/(",
+"*=-;>,'...{]^/(_",
+"=-;>,')!...^/(_:",
+"-;>,')!~{...(_:<",
+";>,')!~{]^...:<[",
+">,')!~{]^/(...[}",
+",')!~{]^/(_:...|",
+"')!~{]^/(_:<[...",
+")!~{]^/(_:<[}|.."};
diff --git a/data/offline.xpm b/data/offline.xpm
new file mode 100644
index 0000000..81db310
--- /dev/null
+++ b/data/offline.xpm
@@ -0,0 +1,51 @@
+/* XPM */
+static char * offline_xpm[] = {
+"16 16 32 1",
+" c None",
+". c #FFFFFF",
+"+ c #F7F7F7",
+"@ c #F0EFEF",
+"# c #E9E7E7",
+"$ c #E2DFDF",
+"% c #DBD8D8",
+"& c #D3D0D0",
+"* c #CCC8C8",
+"= c #C5C0C0",
+"- c #BEB8B8",
+"; c #B7B1B1",
+"> c #AFA9A9",
+", c #A8A1A1",
+"' c #A19999",
+") c #9A9292",
+"! c #938A8A",
+"~ c #8C8282",
+"{ c #847A7A",
+"] c #7D7272",
+"^ c #766B6B",
+"/ c #6F6363",
+"( c #685B5B",
+"_ c #605353",
+": c #594C4C",
+"< c #524444",
+"[ c #4B3C3C",
+"} c #443434",
+"| c #3C2C2C",
+"1 c #352525",
+"2 c #2E1D1D",
+"3 c #271515",
+".+@#$%&*=-;>,')!",
+"+@#$%&*=-;>,')!~",
+"@#$%&*=-;>,')!~{",
+"#$%&*=-;>,')!~{]",
+"$%&*=-;>,')!~{]^",
+"%&*=-;>,')!~{]^/",
+"&*=-;>,')!~{]^/(",
+"*=-;>,')!~{]^/(_",
+"=-;>,')!~{]^/(_:",
+"-;>,')!~{]^/(_:<",
+";>,')!~{]^/(_:<[",
+">,')!~{]^/(_:<[}",
+",')!~{]^/(_:<[}|",
+"')!~{]^/(_:<[}|1",
+")!~{]^/(_:<[}|12",
+"!~{]^/(_:<[}|123"};
diff --git a/data/online-blocked.xpm b/data/online-blocked.xpm
new file mode 100644
index 0000000..743eb1d
--- /dev/null
+++ b/data/online-blocked.xpm
@@ -0,0 +1,48 @@
+/* XPM */
+static char * online_blocked_xpm[] = {
+"16 16 29 1",
+" c None",
+". c #FF0000",
+"+ c #24E723",
+"@ c #23E022",
+"# c #23D821",
+"$ c #23D120",
+"% c #22C91F",
+"& c #22C21E",
+"* c #21BA1D",
+"= c #21B31C",
+"- c #21AB1B",
+"; c #20A41A",
+"> c #209C19",
+", c #209518",
+"' c #1F8D17",
+") c #1F8616",
+"! c #1F7E15",
+"~ c #1E7614",
+"{ c #1E6F13",
+"] c #1D6712",
+"^ c #1D6011",
+"/ c #1D5810",
+"( c #1C510F",
+"_ c #1C490E",
+": c #1B420D",
+"< c #1B3A0C",
+"[ c #1B330B",
+"} c #1A2B0A",
+"| c #1A2409",
+"..+@#$%&*=-;>,')",
+"...#$%&*=-;>,')!",
+"+...%&*=-;>,')!~",
+"@#...*=-;>,')!~{",
+"#$%...-;>,')!~{]",
+"$%&*...>,')!~{]^",
+"%&*=-...')!~{]^/",
+"&*=-;>...!~{]^/(",
+"*=-;>,'...{]^/(_",
+"=-;>,')!...^/(_:",
+"-;>,')!~{...(_:<",
+";>,')!~{]^...:<[",
+">,')!~{]^/(...[}",
+",')!~{]^/(_:...|",
+"')!~{]^/(_:<[...",
+")!~{]^/(_:<[}|.."};
diff --git a/data/online.xpm b/data/online.xpm
new file mode 100644
index 0000000..dbeeb95
--- /dev/null
+++ b/data/online.xpm
@@ -0,0 +1,51 @@
+/* XPM */
+static char * online_xpm[] = {
+"16 16 32 1",
+" c None",
+". c #25F725",
+"+ c #24EF24",
+"@ c #24E723",
+"# c #23E022",
+"$ c #23D821",
+"% c #23D120",
+"& c #22C91F",
+"* c #22C21E",
+"= c #21BA1D",
+"- c #21B31C",
+"; c #21AB1B",
+"> c #20A41A",
+", c #209C19",
+"' c #209518",
+") c #1F8D17",
+"! c #1F8616",
+"~ c #1F7E15",
+"{ c #1E7614",
+"] c #1E6F13",
+"^ c #1D6712",
+"/ c #1D6011",
+"( c #1D5810",
+"_ c #1C510F",
+": c #1C490E",
+"< c #1B420D",
+"[ c #1B3A0C",
+"} c #1B330B",
+"| c #1A2B0A",
+"1 c #1A2409",
+"2 c #1A1C08",
+"3 c #191507",
+".+@#$%&*=-;>,')!",
+"+@#$%&*=-;>,')!~",
+"@#$%&*=-;>,')!~{",
+"#$%&*=-;>,')!~{]",
+"$%&*=-;>,')!~{]^",
+"%&*=-;>,')!~{]^/",
+"&*=-;>,')!~{]^/(",
+"*=-;>,')!~{]^/(_",
+"=-;>,')!~{]^/(_:",
+"-;>,')!~{]^/(_:<",
+";>,')!~{]^/(_:<[",
+">,')!~{]^/(_:<[}",
+",')!~{]^/(_:<[}|",
+"')!~{]^/(_:<[}|1",
+")!~{]^/(_:<[}|12",
+"!~{]^/(_:<[}|123"};
diff --git a/data/paper.png b/data/paper.png
new file mode 100644
index 0000000..7626e8e
--- /dev/null
+++ b/data/paper.png
Binary files differ
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..5578b7b
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,9 @@
+bin_PROGRAMS = tuxmessenger
+tuxmessenger_SOURCES = about.c listcache.c mainwindow.c msnprotocol.c sbprotocol.c \
+ addcontact.c contactlist.c listcleanup.c messagewindow.c options.c sbsessions.c twnauth.c \
+ error.c main.c msngenerics.c routines.c statusicons.c avatars.c mime.c xml.c \
+ msnp2p.c msninvite.c accountwindow.c prefswindow.c blockwindow.c reversewindow.c ink.c \
+ msnp11chl.c gtk-ink.c fonttrans.c filetrans.c
+tuxmessenger_LDADD = @LIBS@ @GTK_LIBS@ @XML_LIBS@
+AM_CFLAGS = -Wall -g @CFLAGS@ @GTK_CFLAGS@ @XML_CPPFLAGS@
+AM_CPPFLAGS = -DDATADIR=\""$(datadir)"\"
diff --git a/src/about.c b/src/about.c
new file mode 100644
index 0000000..4210f16
--- /dev/null
+++ b/src/about.c
@@ -0,0 +1,81 @@
+/*
+ * about.c
+ *
+ * The "About TuxMessenger" box
+ *
+ * (c) 2002-2007 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "debug.h"
+
+void about_open() {
+
+#ifdef HAVE_GTK_2_6_0
+ GtkWidget *window;
+ const gchar *authors[] = {
+ "Thomas White <taw27@srcf.ucam.org>",
+ "",
+ "Thanks to:",
+ "Andrew Millar",
+ "Thomas Arbesser-Rastburg",
+ "Ben Whitehead",
+ "Marielle Vonk",
+ "Siebe Tolsma",
+ "Kevin Lu",
+ "Matthew Mayer",
+ "Neill Horie",
+ "Barnaby Gray",
+ "Hiroyuki Yamamoto",
+ NULL
+ };
+
+ window = gtk_about_dialog_new();
+
+ gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(window), PACKAGE_NAME);
+ gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(window), PACKAGE_VERSION);
+ gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(window), "(c) 2002-2007 Thomas White <taw27@srcf.ucam.org>");
+ gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(window), "MSN Messenger Client");
+ gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(window), "This package is free software; you can redistribute it and/or modify\n"
+ "it under the terms of the GNU General Public License as published by\n"
+ "the Free Software Foundation; version 2 dated June, 1991.\n\n"
+ "This package is distributed in the hope that it will be useful,\n"
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
+ "GNU General Public License for more details.\n\n"
+ "You should have received a copy of the GNU General Public License\n"
+ "along with this package; if not, write to the Free Software\n"
+ "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA\n"
+ "02111-1307, USA.\n");
+ gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(window), "http://www.srcf.ucam.org/~taw27/");
+ gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(window), authors);
+
+ g_signal_connect(window, "response", G_CALLBACK(gtk_widget_destroy), NULL);
+
+ gtk_widget_show_all(window);
+#else /* HAVE_GTK_2_6_0 */
+ debug_print("AB: About window doesn't work without GTK 2.6.0 :(\n");
+#endif /* HAVE_GTK_2_6_0 */
+
+}
diff --git a/src/about.h b/src/about.h
new file mode 100644
index 0000000..ee7448d
--- /dev/null
+++ b/src/about.h
@@ -0,0 +1,30 @@
+/*
+ * about.h
+ *
+ * The "About TuxMessenger" box
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef ABOUT_H
+#define ABOUT_H
+
+extern void about_open();
+
+#endif /* ABOUT_H */
diff --git a/src/accountwindow.c b/src/accountwindow.c
new file mode 100644
index 0000000..dae37af
--- /dev/null
+++ b/src/accountwindow.c
@@ -0,0 +1,206 @@
+/*
+ * accountwindow.c
+ *
+ * The "set account details" window
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "debug.h"
+#include "options.h"
+#include "mainwindow.h"
+#include "routines.h"
+#include "error.h"
+
+static int accountwindow_isopen = 0;
+
+static int accountwindow_validate_username(GtkWidget *widget) {
+
+ options_setusername(gtk_entry_get_text(GTK_ENTRY(widget)));
+ mainwindow_kickdispatch();
+ return FALSE;
+
+}
+
+static int accountwindow_validate_password(GtkWidget *widget) {
+
+ options_setpassword(gtk_entry_get_text(GTK_ENTRY(widget)));
+ mainwindow_kickdispatch();
+ return FALSE;
+
+}
+
+static int accountwindow_validate_server(GtkWidget *widget) {
+
+ char *newserver;
+ const char *server = gtk_entry_get_text(GTK_ENTRY(widget));
+ char *hostname = routines_hostname(server);
+
+ options_sethostname(hostname);
+ free(hostname);
+ options_setport(routines_port(server));
+
+ /* options_sethostname and options_setport will change the values if they're obviously wrong. */
+ newserver = malloc(strlen(options_hostname())+6);
+ /* options_port() is "unsigned short int" so max 16384 = 5 chars */
+ sprintf(newserver, "%s:%i", options_hostname(), options_port());
+ gtk_entry_set_text(GTK_ENTRY(widget), newserver);
+ free(newserver);
+
+ mainwindow_kickdispatch();
+
+ return FALSE;
+
+}
+
+static int accountwindow_validate_remember(GtkWidget *widget) {
+
+ int remember;
+
+ g_object_get(G_OBJECT(widget), "active", &remember, NULL);
+ debug_print("AW: remember: %i\n", remember);
+ options_setrememberlogindetails(remember);
+
+ return FALSE;
+
+}
+
+static int accountwindow_closed() {
+
+ accountwindow_isopen = 0;
+ options_save();
+
+ if ( strlen(options_username()) == 0 ) {
+ error_report("You must enter a username!");
+ }
+
+ return FALSE;
+
+}
+
+void accountwindow_open() {
+
+ char *hostnameport;
+ GtkWidget *window;
+ GtkWidget *border_hbox;
+ GtkWidget *border_vbox;
+
+ GtkWidget *username;
+ GtkWidget *username_justify;
+ GtkWidget *username_hbox;
+ GtkWidget *username_spacer;
+
+ GtkWidget *password;
+ GtkWidget *password_justify;
+ GtkWidget *password_hbox;
+ GtkWidget *password_spacer;
+
+ GtkWidget *server;
+ GtkWidget *server_justify;
+ GtkWidget *server_hbox;
+ GtkWidget *server_spacer;
+
+ GtkWidget *remember;
+
+ if ( accountwindow_isopen != 0 ) {
+ debug_print("AW: Account details window already open.\n");
+ return;
+ }
+
+ window = gtk_dialog_new_with_buttons("Configure Account Details", mainwindow_gtkwindow(), 0, GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL);
+ border_hbox = gtk_hbox_new(FALSE, 0);
+ border_vbox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(border_hbox), border_vbox, FALSE, FALSE, 6);
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), border_hbox, FALSE, FALSE, 6);
+
+ /* "Username" entry box */
+ username_hbox = gtk_hbox_new(FALSE, 0);
+ username_justify = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(username_justify), gtk_label_new("Username:"), FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(username_hbox), username_justify, FALSE, FALSE, 0);
+ username = gtk_entry_new_with_max_length(255);
+ gtk_box_pack_end(GTK_BOX(username_hbox), username, FALSE, FALSE, 0);
+ username_spacer = gtk_label_new("");
+ gtk_widget_set_size_request(username_spacer, 12, -1);
+ gtk_box_pack_end(GTK_BOX(username_hbox), username_spacer, FALSE, FALSE, 0);
+ g_object_set(G_OBJECT(username), "width-chars", 32, NULL);
+ g_signal_connect(G_OBJECT(username), "focus-out-event", GTK_SIGNAL_FUNC(accountwindow_validate_username), NULL);
+ g_signal_connect(G_OBJECT(username), "activate", GTK_SIGNAL_FUNC(accountwindow_validate_username), NULL);
+ gtk_box_pack_start(GTK_BOX(border_vbox), username_hbox, FALSE, FALSE, 6);
+
+ /* "Password" entry box */
+ password_hbox = gtk_hbox_new(FALSE, 0);
+ password_justify = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(password_justify), gtk_label_new("Password:"), FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(password_hbox), password_justify, FALSE, FALSE, 0);
+ password = gtk_entry_new_with_max_length(255);
+ gtk_box_pack_end(GTK_BOX(password_hbox), password, FALSE, FALSE, 0);
+ password_spacer = gtk_label_new("");
+ gtk_widget_set_size_request(password_spacer, 12, -1);
+ gtk_box_pack_end(GTK_BOX(password_hbox), password_spacer, FALSE, FALSE, 0);
+ g_object_set(G_OBJECT(password), "width-chars", 32, NULL);
+ gtk_entry_set_visibility(GTK_ENTRY(password), FALSE);
+ g_signal_connect(G_OBJECT(password), "focus-out-event", GTK_SIGNAL_FUNC(accountwindow_validate_password), NULL);
+ g_signal_connect(G_OBJECT(password), "activate", GTK_SIGNAL_FUNC(accountwindow_validate_password), NULL);
+ gtk_box_pack_start(GTK_BOX(border_vbox), password_hbox, FALSE, FALSE, 6);
+
+ /* "Server" entry box */
+ server_hbox = gtk_hbox_new(FALSE, 0);
+ server_justify = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(server_justify), gtk_label_new("Server:"), FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(server_hbox), server_justify, FALSE, FALSE, 0);
+ server = gtk_entry_new_with_max_length(255);
+ gtk_box_pack_end(GTK_BOX(server_hbox), server, FALSE, FALSE, 0);
+ server_spacer = gtk_label_new("");
+ gtk_widget_set_size_request(server_spacer, 12, -1);
+ gtk_box_pack_end(GTK_BOX(server_hbox), server_spacer, FALSE, FALSE, 0);
+ g_object_set(G_OBJECT(server), "width-chars", 32, NULL);
+ g_signal_connect(G_OBJECT(server), "focus-out-event", GTK_SIGNAL_FUNC(accountwindow_validate_server), NULL);
+ g_signal_connect(G_OBJECT(server), "activate", GTK_SIGNAL_FUNC(accountwindow_validate_server), NULL);
+ gtk_box_pack_start(GTK_BOX(border_vbox), server_hbox, FALSE, FALSE, 6);
+
+ /* Rememer login details check-box */
+ remember = gtk_check_button_new_with_label("Remember login details");
+ gtk_box_pack_start(GTK_BOX(border_vbox), remember, FALSE, FALSE, 6);
+ g_signal_connect(G_OBJECT(remember), "toggled", GTK_SIGNAL_FUNC(accountwindow_validate_remember), NULL);
+
+ /* Fill the fields in... */
+ gtk_entry_set_text(GTK_ENTRY(username), options_username());
+ gtk_entry_set_text(GTK_ENTRY(password), options_password());
+ hostnameport = malloc(strlen(options_hostname())+6);
+ /* options_port() < 65536 because of data size - so always fits. */
+ sprintf(hostnameport, "%s:%i", options_hostname(), options_port());
+ gtk_entry_set_text(GTK_ENTRY(server), hostnameport);
+ free(hostnameport);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remember), options_rememberlogindetails());
+
+ gtk_widget_show_all(window);
+ g_signal_connect(G_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(accountwindow_closed), NULL);
+ g_signal_connect(G_OBJECT(window), "response", GTK_SIGNAL_FUNC(gtk_widget_destroy), NULL);
+ accountwindow_isopen = 1;
+
+}
diff --git a/src/accountwindow.h b/src/accountwindow.h
new file mode 100644
index 0000000..fe7cdad
--- /dev/null
+++ b/src/accountwindow.h
@@ -0,0 +1,30 @@
+/*
+ * accountwindow.h
+ *
+ * The "set account details" window
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef ACCOUNTWINDOW_H
+#define ACCOUNTWINDOW_H
+
+extern void accountwindow_open(void);
+
+#endif /* ACCOUNTWINDOW_H */
diff --git a/src/addcontact.c b/src/addcontact.c
new file mode 100644
index 0000000..91cfd8f
--- /dev/null
+++ b/src/addcontact.c
@@ -0,0 +1,299 @@
+/*
+ * addcontact.c
+ *
+ * UI parts of adding new contacts, and being added yourself.
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mainwindow.h"
+#include "debug.h"
+#include "error.h"
+#include "msnprotocol.h"
+#include "routines.h"
+#include "contactlist.h"
+
+typedef struct stru_addedwindow {
+
+ struct stru_addedwindow *next;
+ GtkWidget *window;
+ char *username;
+ GtkWidget *allow_radio;
+ GtkWidget *block_radio;
+ GtkWidget *forward_toggle;
+
+} AddedWindow;
+
+typedef struct {
+
+ GtkWidget *window;
+ GtkWidget *allow_toggle;
+ GtkWidget *username_entry;
+
+} AddContactWindow;
+
+static AddedWindow *addedwindows_list = NULL;
+
+static int addcontact_destroyed(GtkWidget *widget, AddContactWindow *item) {
+ free(item);
+ return 0;
+}
+
+static int addcontact_response(GtkWidget *widget, gint response, AddContactWindow *item) {
+
+ const char *username;
+
+ if ( response == GTK_RESPONSE_REJECT ) {
+ return 0;
+ }
+
+ if ( msnprotocol_signedin() ) {
+
+ username = gtk_entry_get_text(GTK_ENTRY(item->username_entry));
+ debug_print("AC: Adding new contact: '%s'\n", username);
+
+ if ( strstr(username, "@") == NULL ) {
+ /* Err... nope. */
+ error_report("Invalid Passport address.");
+ return 0;
+ }
+
+ msnprotocol_adduserfriendly(username, username, "FL");
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(item->allow_toggle)) ) {
+ msnprotocol_adduser(username, "AL");
+ }
+
+ } else {
+
+ error_report("Try again when you're signed in.");
+
+ }
+
+ return 0;
+
+}
+
+static int addcontact_activate(GtkWidget *widget, AddContactWindow *item) {
+
+ addcontact_response(item->window, GTK_RESPONSE_ACCEPT, item);
+ gtk_widget_destroy(item->window);
+
+ return 0;
+
+}
+
+void addcontact_open() {
+
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+ GtkWidget *icon;
+ AddContactWindow *item;
+
+ item = malloc(sizeof(AddContactWindow));
+
+ item->window = gtk_dialog_new_with_buttons("Add New Contact", mainwindow_gtkwindow(), GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ g_signal_connect(item->window, "response", G_CALLBACK(addcontact_response), item);
+ g_signal_connect_swapped(item->window, "response", G_CALLBACK(gtk_widget_destroy), item->window);
+ g_signal_connect(item->window, "destroy", G_CALLBACK(addcontact_destroyed), item);
+
+ hbox = gtk_hbox_new(FALSE, 20);
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(item->window)->vbox), hbox);
+ icon = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start(GTK_BOX(hbox), icon, TRUE, TRUE, 0);
+
+ vbox = gtk_vbox_new(FALSE, 5);
+ gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
+
+ gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new("Enter the Passport address of your new contact:"), TRUE, TRUE, 0);
+
+ item->username_entry = gtk_entry_new();
+ gtk_box_pack_start(GTK_BOX(vbox), item->username_entry, TRUE, TRUE, 0);
+ g_signal_connect(item->username_entry, "activate", G_CALLBACK(addcontact_activate), item);
+
+ item->allow_toggle = gtk_check_button_new_with_label("Allow this new contact to message you.");
+ gtk_box_pack_start(GTK_BOX(vbox), item->allow_toggle, TRUE, TRUE, 0);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item->allow_toggle), TRUE);
+
+ gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
+ gtk_widget_show_all(item->window);
+
+}
+
+static int addcontact_addedclose(GtkWidget *widget, gpointer data) {
+
+ AddedWindow *addedwindow = (AddedWindow *)data;
+ AddedWindow *previous = NULL;
+ AddedWindow *check = addedwindows_list;
+
+ debug_print("AC: Removing New Contact window for %s\n", addedwindow->username);
+
+ /* Remove from list. */
+ while ( check != NULL ) {
+ if ( check == addedwindow ) {
+ free(check->username);
+ if ( previous != NULL ) {
+ previous->next = check->next;
+ } else {
+ /* Was first on the list */
+ addedwindows_list = check->next;
+ }
+ free(check);
+ return 0;
+ }
+ previous = check;
+ check = check->next;
+ }
+
+ return 0;
+
+}
+
+static int addcontact_addedresponse(GtkWidget *widget, gint response, gpointer data) {
+
+ AddedWindow *addedwindow = (AddedWindow *)data;
+
+ if ( response == GTK_RESPONSE_REJECT ) {
+ addcontact_addedclose(widget, data);
+ return 0;
+ }
+
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(addedwindow->forward_toggle)) ) {
+ /* Add to FL. Friendlyname isn't given: it'll arrive soon on NLN. */
+ msnprotocol_adduserfriendly(addedwindow->username, addedwindow->username, "FL");
+ }
+
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(addedwindow->allow_radio)) ) {
+ /* Add to AL */
+ msnprotocol_adduser(addedwindow->username, "AL");
+ } else {
+ /* Add to BL */
+ msnprotocol_adduser(addedwindow->username, "BL");
+ }
+
+ /* Remove from PL and add to RL */
+ msnprotocol_adduser(addedwindow->username, "RL");
+ msnprotocol_remuser(addedwindow->username, "PL");
+
+ addcontact_addedclose(widget, data);
+
+ return 0;
+
+}
+
+void addcontact_added(const char *username, const char *friendlyname_coded) {
+
+ GtkWidget *label;
+ char *text;
+ AddedWindow *addedwindow;
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+ GtkWidget *icon;
+ char *friendlyname;
+
+ debug_print("AC: New contact! %s / %s\n", username, friendlyname_coded);
+
+ addedwindow = malloc(sizeof(AddedWindow));
+ addedwindow->next = addedwindows_list;
+ debug_print("AC: Next = %p\n", addedwindow->next);
+ addedwindow->username = strdup(username);
+ addedwindows_list = addedwindow;
+
+ if ( friendlyname_coded != NULL ) {
+ friendlyname = routines_urldecode(friendlyname_coded);
+ } else {
+ friendlyname = NULL;
+ }
+
+ if ( friendlyname != NULL ) {
+ text = malloc(45 + strlen(username) + strlen(friendlyname));
+ sprintf(text, "'%s' (%s) added you to his/her contact list.", friendlyname, username);
+ free(friendlyname);
+ } else {
+ text = malloc(40 + strlen(username));
+ sprintf(text, "'%s' added you to his/her contact list.", username);
+ }
+
+ addedwindow->window = gtk_dialog_new_with_buttons("New Contact", mainwindow_gtkwindow(), GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ NULL);
+
+ g_signal_connect(addedwindow->window, "response", G_CALLBACK(addcontact_addedresponse), addedwindow);
+ g_signal_connect_swapped(addedwindow->window, "response", G_CALLBACK(gtk_widget_destroy), addedwindow->window);
+ g_signal_connect(addedwindow->window, "delete_event", G_CALLBACK(addcontact_addedclose), addedwindow);
+
+ hbox = gtk_hbox_new(FALSE, 20);
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(addedwindow->window)->vbox), hbox);
+ icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start(GTK_BOX(hbox), icon, TRUE, TRUE, 0);
+
+ vbox = gtk_vbox_new(FALSE, 5);
+ gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
+
+ label = gtk_label_new(text);
+ gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
+ addedwindow->allow_radio = gtk_radio_button_new_with_label(NULL, "Add him/her to your Allow List.");
+ addedwindow->block_radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(addedwindow->allow_radio), "Block him/her.");
+ addedwindow->forward_toggle = gtk_check_button_new_with_label("Add him/her to your contact list as well.");
+ gtk_box_pack_start(GTK_BOX(vbox), addedwindow->allow_radio, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), addedwindow->block_radio, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), addedwindow->forward_toggle, TRUE, TRUE, 0);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(addedwindow->forward_toggle), TRUE);
+
+ /* Disable "Add to your list as well" option if they're already on the FL. */
+ gtk_widget_set_sensitive(GTK_WIDGET(addedwindow->forward_toggle), !contactlist_isonlist("FL", username));
+
+ gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new("If you press Cancel, you will be asked again the next time you sign in."), TRUE, TRUE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
+
+ gtk_widget_show_all(addedwindow->window);
+ free(text);
+
+}
+
+void addcontact_closeall() {
+
+ AddedWindow *item = addedwindows_list;
+
+ while ( item != NULL ) {
+
+ AddedWindow *next_item;
+
+ next_item = item->next;
+ gtk_widget_destroy(item->window);
+ debug_print("AC: Next = %p\n", next_item);
+ addcontact_addedclose(NULL, item);
+ item = next_item;
+
+ }
+
+}
diff --git a/src/addcontact.h b/src/addcontact.h
new file mode 100644
index 0000000..0e6cf41
--- /dev/null
+++ b/src/addcontact.h
@@ -0,0 +1,32 @@
+/*
+ * addcontact.h
+ *
+ * UI parts of adding new contacts, and being added yourself.
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef ADDCONTACT_H
+#define ADDCONTACT_H
+
+extern void addcontact_open(void);
+extern void addcontact_added(const char *username, const char *friendlyname);
+extern void addcontact_closeall(void);
+
+#endif /* ADDCONTACT_H */
diff --git a/src/avatars.c b/src/avatars.c
new file mode 100644
index 0000000..9bb68ac
--- /dev/null
+++ b/src/avatars.c
@@ -0,0 +1,207 @@
+/*
+ * avatars.h
+ *
+ * Handling of User Display Pictures (but not displaying them)
+ * N.B. msnp2p.c does most of the work of actually sending
+ * and receiving them on the network.
+ *
+ * (c) 2002-20045Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <openssl/sha.h>
+
+#include "sbsessions.h"
+#include "debug.h"
+#include "routines.h"
+#include "xml.h"
+#include "options.h"
+
+char *avatars_getlocalfilename(const char *name) {
+
+ char *filename;
+ char *done;
+
+ filename = malloc(strlen(name)+17);
+ strcpy(filename, "~/.tuxmessenger/");
+ strcat(filename, name);
+
+ done = routines_glob(filename);
+ free(filename);
+
+ return done;
+
+}
+
+
+static char *avatars_getfilename(const char *sha1d) {
+
+ char *filename;
+ char *filename2;
+ char *done;
+
+ filename = xml_killillegalchars(sha1d);
+
+ filename2 = malloc(strlen(filename)+25);
+ strcpy(filename2, "~/.tuxmessenger/avatars/");
+ strcat(filename2, filename);
+ free(filename);
+
+ done = routines_glob(filename2);
+ free(filename2);
+
+ return done;
+
+}
+
+/* Check if the picture for a user is already downloaded. Returns the filename if it's here. */
+char *avatars_havepicture(const char *dpobject) {
+
+ char *picture_data_hash;
+ struct stat stat_buffer;
+ struct stat *statbuf;
+ char *dpobject_decoded;
+ char *full_filename;
+
+ assert(dpobject != NULL);
+
+ dpobject_decoded = routines_urldecode(dpobject);
+ picture_data_hash = xml_getfield(dpobject_decoded, "SHA1D");
+ free(dpobject_decoded);
+ full_filename = avatars_getfilename(picture_data_hash);
+ free(picture_data_hash);
+ if ( full_filename == NULL ) {
+ return NULL;
+ }
+
+ /* Check if we already have this picture or not */
+ statbuf = &stat_buffer;
+ debug_print("AV: Looking for '%s': ", full_filename);
+ if ( stat(full_filename, statbuf) != -1 ) {
+ debug_print("found!\n");
+ return full_filename;
+ } else {
+ debug_print("not found.\n");
+ }
+ free(full_filename);
+
+ return NULL;
+
+}
+
+/* Return filename of the image to use while downloading a picture. */
+char *avatars_default_fetching() {
+
+ return avatars_getlocalfilename("wait_avatar.png");
+
+}
+
+/* Return filename of default image to use when a user has no picture. */
+char *avatars_default_none() {
+
+ return avatars_getlocalfilename("default_avatar.png");
+
+}
+
+/* Return filename of image to use for local user. */
+char *avatars_local() {
+
+ return avatars_getlocalfilename("avatar.png");
+
+}
+
+char *avatars_localobject() {
+
+ char *msnobject;
+ char *msnobject_coded;
+ char *sha1c_b64;
+ char *sha1d_b64;
+ char *sha1c;
+ char *sha1d;
+ struct stat stat_buffer;
+ struct stat *statbuf;
+ void *picture_data;
+ size_t size;
+ FILE *fh;
+ char *filename;
+
+ filename = avatars_local();
+ if ( filename == NULL ) {
+ debug_print("AV: No avatar found.\n");
+ return strdup("0");
+ }
+
+ statbuf = &stat_buffer;
+ if ( stat(filename, statbuf) == -1 ) {
+ debug_print("AV: No avatar found.\n");
+ return strdup("0");
+ }
+
+ size = (int)statbuf->st_size;
+
+ fh = fopen(filename, "r");
+ picture_data = malloc(size);
+ if ( fread(picture_data, size, 1, fh) < 0 ) {
+ debug_print("AV: Couldn't open avatar file.\n");
+ return strdup("0");
+ }
+ fclose(fh);
+ sha1d = SHA1(picture_data, size, NULL);
+ sha1d_b64 = routines_base64givenlength(sha1d, 20);
+ free(picture_data);
+
+ msnobject = malloc(256);
+ sprintf(msnobject, "Creator%sSize%iType3LocationTFR6B.tmpFriendlyAAA=SHA1D%s", options_username(), size, sha1d_b64);
+ sha1c = SHA1(msnobject, strlen(msnobject), NULL);
+ sha1c_b64 = routines_base64givenlength(sha1c, 20);
+
+ debug_print("AV: Avatar: %s - %i bytes\n", filename, size);
+ debug_print("AV: SHA1D: %s\n", sha1d_b64);
+
+ sprintf(msnobject, "<msnobj Creator=\"%s\" Size=\"%i\" Type=\"3\" Location=\"TFR6B.tmp\" Friendly=\"AAA=\" SHA1D=\"%s\" SHA1C=\"%s\"/>", options_username(), size, sha1d_b64, sha1c_b64);
+ free(sha1d_b64);
+ free(sha1c_b64);
+
+ msnobject_coded = routines_urlencode(msnobject);
+ free(msnobject);
+ free(filename);
+
+ return msnobject_coded;
+
+}
+
+char *avatars_unwrapsha1d(const char *dpobject) {
+
+ char *dpobject_decoded;
+ char *data_hash;
+
+ dpobject_decoded = routines_urldecode(dpobject);
+ data_hash = xml_getfield(dpobject_decoded, "SHA1D");
+ free(dpobject_decoded);
+
+ return data_hash;
+
+}
diff --git a/src/avatars.h b/src/avatars.h
new file mode 100644
index 0000000..d9193e4
--- /dev/null
+++ b/src/avatars.h
@@ -0,0 +1,36 @@
+/*
+ * avatars.h
+ *
+ * Handling of User Display Pictures (but not displaying them)
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef AVATARS_H
+#define AVATARS_H
+
+extern char *avatars_havepicture(const char *username);
+extern char *avatars_default_none();
+extern char *avatars_default_fetching();
+extern char *avatars_local();
+extern char *avatars_localobject();
+extern char *avatars_unwrapsha1d(const char *dpobject);
+extern char *avatars_getlocalfilename(const char *name);
+
+#endif /* AVATARS_H */
diff --git a/src/blockwindow.c b/src/blockwindow.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/blockwindow.c
diff --git a/src/blockwindow.h b/src/blockwindow.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/blockwindow.h
diff --git a/src/contactlist.c b/src/contactlist.c
new file mode 100644
index 0000000..03e2a53
--- /dev/null
+++ b/src/contactlist.c
@@ -0,0 +1,901 @@
+/*
+ * contactlist.c
+ *
+ * Contact list (FL, BL, AL and RL) data structures
+ *
+ * (c) 2002-2006 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include "debug.h"
+#include "contactlist.h"
+#include "mainwindow.h"
+#include "msngenerics.h"
+#include "sbsessions.h"
+#include "avatars.h"
+#include "xml.h"
+
+/* The record format for a contact. The same record can be referenced from more than one list. Opaque. */
+typedef struct {
+
+ char *username; /* Username */
+ char *friendlyname; /* Friendlyname */
+ OnlineState status; /* Online status */
+ unsigned int features; /* Client features (e.g. webcam) */
+ char *dpobject; /* Display Picture MSNObject */
+ ContactSource source; /* The last source the details were updated from */
+ int references; /* Number of times this contact is referenced by ContactItems */
+ UIContact *uicontact; /* src/mainwindow.c's representation of the contact */
+ char *dpsha1d; /* SHA1D field (if any) from user's DP MSNObject (if any). */
+ char *ubxdata; /* UBX data for this contact. */
+ size_t ubxdatalen; /* Length of UBX data. */
+ char *guid; /* Contact's GUID */
+
+} Contact;
+/* ... but ContactItems actually appear on the lists ... */
+
+typedef struct contactitem {
+
+ Contact *contact; /* The contact's record */
+ struct contactitem *next; /* Link to the next contact on this list */
+
+} ContactItem;
+
+static ContactItem *contactlist_forward = NULL;
+static ContactItem *contactlist_block = NULL;
+static ContactItem *contactlist_allow = NULL;
+static ContactItem *contactlist_reverse = NULL;
+static ContactItem *contactlist_pending = NULL;
+static ContactItem *contactlist_temporary = NULL;
+
+static ContactItem *contactlist_lastcontactitem(ContactItem *contact_list) {
+
+ ContactItem *contactitem = contact_list;
+
+ if ( contactitem == NULL ) {
+ return NULL;
+ }
+
+ while ( contactitem ) {
+
+ assert(contactitem != NULL);
+ assert(contactitem->contact != NULL);
+ assert(contactitem->contact->username != NULL);
+
+ if ( contactitem->next == NULL ) {
+ return contactitem;
+ } else {
+ contactitem = contactitem->next;
+ }
+
+ }
+
+ /* Should never get here */
+ debug_print("CL: Reached end of contactlist_lastcontactitem(). D'oh.\n");
+ return contactitem;
+
+}
+
+
+static ContactItem **contactlist_translatelist(const char *list) {
+
+ if ( strcmp(list, "AL") == 0 ) {
+ return &contactlist_allow;
+ } else if ( strcmp(list, "FL") == 0 ) {
+ return &contactlist_forward;
+ } else if ( strcmp(list, "BL") == 0 ) {
+ return &contactlist_block;
+ } else if ( strcmp(list, "RL") == 0 ) {
+ return &contactlist_reverse;
+ } else if ( strcmp(list, "PL") == 0 ) {
+ return &contactlist_pending;
+ } else {
+ return NULL;
+ }
+
+}
+
+/* Link a contact into a given contact list, creating the ContactItem structure */
+static void contactlist_link(ContactItem **contact_list, Contact *contact) {
+
+ ContactItem *last_contactitem;
+ ContactItem *new_contactitem;
+
+ new_contactitem = malloc(sizeof(ContactItem));
+ new_contactitem->contact = contact;
+ new_contactitem->next = NULL;
+
+ last_contactitem = contactlist_lastcontactitem(*contact_list);
+ if ( last_contactitem != NULL ) {
+ assert(last_contactitem->next == NULL);
+ last_contactitem->next = new_contactitem;
+ } else {
+ *contact_list = new_contactitem;;
+ }
+
+ contact->references++;
+
+}
+
+static ContactItem *contactlist_previous(ContactItem **contact_list, ContactItem *s_contactitem) {
+
+ ContactItem *contactitem;
+
+ contactitem = *contact_list;
+
+ while ( contactitem ) {
+
+ assert(contactitem != NULL);
+ assert(contactitem->contact != NULL);
+ assert(contactitem->contact->username != NULL);
+
+ if ( contactitem->next == s_contactitem ) {
+ return contactitem;
+ } else {
+ contactitem = contactitem->next;
+ }
+
+ }
+
+ /* Didn't find it - means it was the first item on the list. */
+ return NULL;
+
+}
+
+/* Unlink a contact from a given contact list, destroying it if there's no more references */
+static void contactlist_unlink(ContactItem **contact_list, ContactItem *contactitem) {
+
+ Contact *contact;
+ ContactItem *previous_contact;
+ ContactItem *next_contact;
+
+ contact = contactitem->contact;
+
+/* debug_print("CL: Unlinking %s\n", contact->username);*/
+
+ /* First link it out of the list. */
+ previous_contact = contactlist_previous(contact_list, contactitem);
+ next_contact = contactitem->next;
+ if ( previous_contact != NULL ) {
+ previous_contact->next = next_contact;
+ } else {
+ *contact_list = next_contact;
+ }
+ contact->references--;
+/* debug_print("CL: Reference count now %i\n", contact->references);*/
+
+ if ( (contact_list == &contactlist_forward) && (contact->uicontact != NULL) ) {
+ /* This has probably already happened. */
+ debug_print("CL: Destroying UIContact\n");
+ mainwindow_removecontact(contact->uicontact);
+ mainwindow_destroycontact(contact->uicontact);
+ }
+
+ if ( contact->references == 0 ) {
+
+ debug_print("CL: Destroying contact record for %s\n", contact->username);
+
+ /* Destroy the contact record */
+ free(contact->username);
+ if ( contact->friendlyname != NULL ) {
+ free(contact->friendlyname);
+ }
+ if ( contact->dpobject != NULL ) {
+ free(contact->dpobject);
+ }
+ if ( contact->dpsha1d != NULL ) {
+ free(contact->dpsha1d);
+ }
+ if ( contact->ubxdata != NULL ) {
+ free(contact->ubxdata);
+ }
+ if ( contact->guid != NULL ) {
+ free(contact->guid);
+ }
+ free(contact);
+
+ }
+
+ free(contactitem);
+
+}
+
+/* Locate a contact's record in a given list by username */
+static ContactItem *contactlist_findcontact(ContactItem *contact_list, const char *username) {
+
+ ContactItem *contactitem = contact_list;
+
+ if ( contactitem == NULL ) {
+ return NULL;
+ }
+
+ while ( contactitem ) {
+
+ assert(contactitem != NULL);
+ assert(contactitem->contact != NULL);
+ assert(contactitem->contact->username != NULL);
+
+ /* Case-insensitive here, like in sbsessions_find_username(). Username
+ may have different case depending on its source, since the servers
+ seem to change usernames to lower case but clients might not in
+ (e.g.) TypingUser controls. */
+ if ( strcasecmp(contactitem->contact->username, username) == 0 ) {
+ return contactitem;
+ } else {
+ contactitem = contactitem->next;
+ }
+
+ }
+
+ return NULL;
+
+}
+
+/* Locate a contact's record in a given list by GUID */
+static ContactItem *contactlist_findcontactguid(ContactItem *contact_list, const char *guid) {
+
+ ContactItem *contactitem = contact_list;
+
+ if ( contactitem == NULL ) {
+ return NULL;
+ }
+
+ while ( contactitem ) {
+
+ assert(contactitem != NULL);
+ assert(contactitem->contact != NULL);
+ assert(contactitem->contact->guid != NULL);
+
+ /* Case-insensitive */
+ if ( strcasecmp(contactitem->contact->guid, guid) == 0 ) {
+ return contactitem;
+ } else {
+ contactitem = contactitem->next;
+ }
+
+ }
+
+ return NULL;
+
+}
+
+void contactlist_removecontact(const char *list, const char *username) {
+
+ ContactItem **contact_list = contactlist_translatelist(list);
+ ContactItem *contactitem;
+
+ debug_print("CL: Removing %s from %s\n", username, list);
+
+ contactitem = contactlist_findcontact(*contact_list, username);
+ assert(contactitem != NULL);
+ contactlist_unlink(contact_list, contactitem);
+
+}
+
+void contactlist_removecontactguid(const char *list, const char *guid) {
+
+ ContactItem **contact_list = contactlist_translatelist(list);
+ ContactItem *contactitem;
+
+ debug_print("CL: Removing %s from %s\n", guid, list);
+
+ contactitem = contactlist_findcontactguid(*contact_list, guid);
+ assert(contactitem != NULL);
+ contactlist_unlink(contact_list, contactitem);
+
+}
+
+static Contact *contactlist_findcontactrecord(ContactItem *contact_list, const char *username) {
+
+ ContactItem *contactitem;
+
+ contactitem = contactlist_findcontact(contact_list, username);
+ if ( contactitem == NULL ) {
+ return NULL;
+ }
+
+ return contactitem->contact;
+
+}
+
+/* Create a new contact record */
+static Contact *contactlist_createcontact(ContactSource source, const char *username, const char *friendlyname, OnlineState status, int features, const char *dpobject, const char *guid) {
+
+ Contact *newcontact;
+
+ newcontact = malloc(sizeof(Contact));
+ assert(newcontact != NULL);
+
+ newcontact->username = strdup(username);
+ if ( friendlyname != NULL ) {
+ newcontact->friendlyname = strdup(friendlyname);
+ } else {
+ newcontact->friendlyname = NULL;
+ }
+ if ( dpobject != NULL ) {
+ newcontact->dpobject = strdup(dpobject);
+ } else {
+ newcontact->dpobject = NULL; /* Don't try to strdup(NULL) */
+ }
+ if ( guid != NULL ) {
+ newcontact->guid = strdup(guid);
+ } else {
+ newcontact->guid = NULL;
+ }
+ newcontact->source = source;
+ newcontact->uicontact = NULL; /* This is filled in if/when the contact is linked into the FL */
+ newcontact->features = features;
+ newcontact->status = status;
+ newcontact->references = 0;
+ newcontact->dpsha1d = NULL;
+ newcontact->ubxdata = NULL;
+
+ /* Caller probably means to call contactlist_link pretty soon... */
+ return newcontact;
+
+}
+
+/* Add a user to the a given list or update details if already there */
+static void contactlist_details(ContactItem **contact_list, ContactSource source, const char *username, const char *friendlyname, int features, const char *dpobject, OnlineState status, const char *guid) {
+
+ ContactItem *contact_item;
+ Contact *contact;
+ char *list_string;
+
+ assert(username != NULL);
+ assert(contact_list != NULL); /* But *contact_list is allowed to be NULL (empty list) */
+
+ if ( contact_list == &contactlist_allow ) {
+ list_string = "AL";
+ } else if ( contact_list == &contactlist_forward ) {
+ list_string = "FL";
+ } else if ( contact_list == &contactlist_block ) {
+ list_string = "BL";
+ } else if ( contact_list == &contactlist_reverse ) {
+ list_string = "RL";
+ } else if ( contact_list == &contactlist_temporary ) {
+ list_string = "TL";
+ } else if ( contact_list == &contactlist_pending ) {
+ list_string = "PL";
+ } else {
+ list_string = "??"; /* Whoops */
+ }
+
+ /* Look in all the lists to try and find the record for this user */
+ contact_item = contactlist_findcontact(contactlist_forward, username);
+ if ( contact_item == NULL ) {
+ contact_item = contactlist_findcontact(contactlist_reverse, username);
+ }
+ if ( contact_item == NULL ) {
+ contact_item = contactlist_findcontact(contactlist_allow, username);
+ }
+ if ( contact_item == NULL ) {
+ contact_item = contactlist_findcontact(contactlist_block, username);
+ }
+ if ( contact_item == NULL ) {
+ contact_item = contactlist_findcontact(contactlist_temporary, username);
+ }
+ if ( contact_item == NULL ) {
+ contact_item = contactlist_findcontact(contactlist_pending, username);
+ }
+
+ /* If they're not found, create them. */
+ if ( contact_item == NULL ) {
+
+ debug_print("CL: Creating contact record: %s / %s\n", username, friendlyname);
+ contact = contactlist_createcontact(source, username, friendlyname, status, features, dpobject, guid);
+ assert(contact != NULL); /* Shouldn't ever happen */
+
+ } else {
+
+ /* Found the contact, so check if they need to be updated */
+ contact = contact_item->contact;
+ assert(contact != NULL);
+
+ if ( contact->source <= source ) {
+
+ int dpchanged = 0;
+
+ debug_print("CL: Updating record: %s / %s\n", username, friendlyname);
+
+ /* Don't change friendlyname non-NULL to NULL */
+ if ( !((contact->friendlyname != NULL) && (friendlyname == NULL)) ) {
+ if ( contact->friendlyname != NULL ) {
+ free(contact->friendlyname);
+ }
+ if ( friendlyname != NULL ) {
+ contact->friendlyname = strdup(friendlyname);
+ } else {
+ contact->friendlyname = NULL;
+ }
+ }
+
+ /* Don't change GUID non-NULL to NULL either. In fact, this shouldn't be changing
+ anyway, but I'm not willing to assume that. */
+ if ( !((contact->guid != NULL) && (guid == NULL)) ) {
+ if ( contact->guid != NULL ) {
+ free(contact->guid);
+ }
+ if ( guid != NULL ) {
+ contact->guid = strdup(guid);
+ } else {
+ contact->guid = NULL;
+ }
+ }
+
+ /* This detects DP->no DP and No DP->DP transitions. */
+ if ( contact->dpobject != dpobject ) {
+ dpchanged = 1;
+ }
+ /* This detects DP->DP transitions and invalidates same-DP changes. */
+ if ( contact->dpobject != NULL ) {
+
+ if ( dpobject != NULL ) {
+
+ if ( strcmp(dpobject, contact->dpobject) != 0 ) {
+ dpchanged = 1;
+ } else {
+ dpchanged = 0;
+ }
+
+ }
+ free(contact->dpobject);
+ if ( contact->dpsha1d != NULL ) {
+ free(contact->dpsha1d);
+ }
+
+ }
+
+ contact->features = features;
+ if ( dpobject != NULL ) {
+ contact->dpobject = strdup(dpobject);
+ contact->dpsha1d = avatars_unwrapsha1d(dpobject);
+ } else {
+ contact->dpobject = NULL;
+ contact->dpsha1d = NULL;
+ }
+
+ if ( dpchanged ) {
+
+ /* Change of DP */
+ debug_print("CL: Display picture for %s changed.\n", username);
+ messagewindow_picturekick(username);
+
+ }
+
+ contact->source = source;
+ contact->status = status;
+
+ }
+
+ }
+
+ /* Check if they're in the right list. If not, link them into it. */
+ if ( contactlist_findcontact(*contact_list, username) == NULL ) {
+
+ assert(contact != NULL);
+ assert(contact->username != NULL);
+ debug_print("CL: Linking into %s: %s\n", list_string, contact->username);
+
+ contactlist_link(contact_list, contact);
+
+ if ( contact_list == &contactlist_forward ) {
+ /* Linking into the FL so create an UIContact for them. */
+ if ( contact->uicontact == NULL ) {
+ contact->uicontact = mainwindow_addcontact(contact->username, contact->friendlyname, status);
+ }
+ }
+
+ }
+
+ /* If an NLN, ILN or FLN (for the FL) is BEING HANDLED, and the details have been updated, sort the UI out.
+ N.B. What's being handled doesn't necessarily have anything to do with the contact's status. */
+ if ( ((source == CONTACT_SOURCE_NLN) || (source == CONTACT_SOURCE_FLN)) && (contact_list == &contactlist_forward) ) {
+ assert(contact->uicontact != NULL);
+ mainwindow_setcontactstatus(contact->uicontact, contact->username, contact->friendlyname, status);
+ }
+
+}
+
+/* Add a user to the FL or update details if already there */
+void contactlist_fldetails(ContactSource source, const char *username, const char *friendlyname, int features, const char *dpobject, OnlineState status, const char *guid) {
+ contactlist_details(&contactlist_forward, source, username, friendlyname, features, dpobject, status, guid);
+}
+
+/* Add a user to the AL or update details if already there */
+void contactlist_aldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) {
+ contactlist_details(&contactlist_allow, source, username, friendlyname, 0, NULL, status, NULL);
+}
+
+/* Add a user to the BL or update details if already there */
+void contactlist_bldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) {
+ contactlist_details(&contactlist_block, source, username, friendlyname, 0, NULL, status, NULL);
+}
+
+/* Add a user to the RL or update details if already there */
+void contactlist_rldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) {
+ contactlist_details(&contactlist_reverse, source, username, friendlyname, 0, NULL, status, NULL);
+}
+
+/* Add a user to the TL (temporary list) */
+void contactlist_tldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) {
+ contactlist_details(&contactlist_temporary, source, username, friendlyname, 0, NULL, status, NULL);
+}
+
+/* Add a user to the PL (pending list) */
+void contactlist_pldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) {
+ contactlist_details(&contactlist_pending, source, username, friendlyname, 0, NULL, status, NULL);
+}
+
+/* Do the same as messagewindow_picturekick, but identify "kickees" by DP SHA1D field. */
+void contactlist_picturekick_sha1d(const char *sha1d) {
+
+ /* Find all users who have this DP SHA1D */
+ ContactItem *contactitem = contactlist_forward;
+ if ( contactitem == NULL ) {
+ return;
+ }
+
+ while ( contactitem ) {
+
+ assert(contactitem->contact != NULL);
+ if ( contactitem->contact->dpsha1d != NULL ) {
+ if ( strcmp(contactitem->contact->dpsha1d , sha1d) == 0 ) {
+ messagewindow_picturekick(contactitem->contact->username);
+ }
+ }
+
+ contactitem = contactitem->next;
+
+ }
+
+}
+
+static void contactlist_clear_list(ContactItem **contact_list) {
+
+ ContactItem *contactitem = *contact_list;
+
+ /* Check the list isn't empty first. */
+ if ( contactitem == NULL ) {
+ debug_print("CL: List is empty\n");
+ return;
+ }
+
+ while ( contactitem ) {
+
+ ContactItem *next_contactitem = NULL;
+
+ assert(contactitem != NULL);
+ assert(contactitem->contact != NULL);
+ assert(contactitem->contact->username != NULL);
+
+ next_contactitem = contactitem->next;
+
+ contactlist_unlink(contact_list, contactitem);
+ contactitem = next_contactitem;
+
+ }
+
+}
+
+int contactlist_isonlist(const char *list, const char *username) {
+
+ ContactItem **contact_list = contactlist_translatelist(list);
+
+ if ( contactlist_findcontact(*contact_list, username) == NULL ) {
+ return 0;
+ }
+
+ return 1;
+
+}
+
+/* Called by src/msnprotocol.c to wipe the contact list at sign-out, and by src/listcache.c to invalidate the lists. */
+void contactlist_clear() {
+
+ debug_print("CL: Clearing FL\n");
+ contactlist_clear_list(&contactlist_forward);
+ debug_print("CL: Clearing RL\n");
+ contactlist_clear_list(&contactlist_reverse);
+ debug_print("CL: Clearing BL\n");
+ contactlist_clear_list(&contactlist_block);
+ debug_print("CL: Clearing AL\n");
+ contactlist_clear_list(&contactlist_allow);
+ debug_print("CL: Clearing TL\n");
+ contactlist_clear_list(&contactlist_temporary);
+ debug_print("CL: Clearing PL\n");
+ contactlist_clear_list(&contactlist_pending);
+
+ /* Check */
+ assert(contactlist_forward == NULL);
+ assert(contactlist_block == NULL);
+ assert(contactlist_allow == NULL);
+ assert(contactlist_reverse == NULL);
+ assert(contactlist_temporary == NULL);
+
+}
+
+/* Return contact data as a string. */
+static char *contactlist_getcontact(const char *list, ContactItem **item) {
+
+ char *r_contact;
+ size_t length;
+
+ if ( (*item == NULL) && (strcmp(list, "FL") == 0) ) {
+ *item = contactlist_forward;
+ } else if ( (*item == NULL) && (strcmp(list, "AL") == 0) ) {
+ *item = contactlist_allow;
+ } else if ( (*item == NULL) && (strcmp(list, "BL") == 0) ) {
+ *item = contactlist_block;
+ } else if ( (*item == NULL) && (strcmp(list, "RL") == 0) ) {
+ *item = contactlist_reverse;
+ } else if ( (*item == NULL) && (strcmp(list, "PL") == 0) ) {
+ *item = contactlist_pending;
+ } else {
+ assert(item != NULL);
+ assert(*item != NULL);
+ *item = (*item)->next;
+ }
+
+ if ( *item == NULL ) {
+ return NULL;
+ }
+
+ length = strlen((*item)->contact->username) + 1;
+ if ( (*item)->contact->friendlyname != NULL ) {
+ length += strlen((*item)->contact->friendlyname) + 1;
+ }
+ if ( (*item)->contact->guid != NULL ) {
+ length += strlen((*item)->contact->guid) + 1;
+ if ( (*item)->contact->friendlyname == NULL ) {
+ /* Whoops! GUID but no friendly name. */
+ length += 2;
+ }
+ }
+ r_contact = malloc(length);
+
+ strcpy(r_contact, (*item)->contact->username);
+ if ( (*item)->contact->friendlyname != NULL ) {
+ strcat(r_contact, " ");
+ strcat(r_contact, (*item)->contact->friendlyname);
+ }
+ if ( (*item)->contact->guid != NULL ) {
+ if ( (*item)->contact->friendlyname == NULL ) {
+ /* Shouldn't ever happen. Nasty situation. */
+ strcat(r_contact, " -");
+ }
+ strcat(r_contact, " ");
+ strcat(r_contact, (*item)->contact->guid);
+ }
+
+ return r_contact;
+
+}
+
+/* Tweak line ending before sending a line to a file. */
+static int contactlist_writeout(FILE *fh, const char *list, const char *contact) {
+
+ char *line;
+ int rval;
+
+ line = malloc(strlen(list) + strlen(contact) + 3);
+ strcpy(line, list);
+ strcat(line, " ");
+ strcat(line, contact);
+ line[strlen(line)+1] = '\0';
+ line[strlen(line)] = '\n';
+
+ rval = fputs(line, fh);
+ free(line);
+
+ if ( rval == EOF ) {
+ return -1;
+ }
+
+ return 0;
+
+}
+
+/* Dump a given list into the file handle given. */
+int contactlist_dumplist(FILE *fh, const char *list) {
+
+ ContactItem *token;
+ char *contact;
+
+ token = NULL;
+ contact = contactlist_getcontact(list, &token);
+ while ( token ) {
+
+ if ( contactlist_writeout(fh, list, contact) != 0 ) {
+ free(contact);
+ return -1;
+ }
+ free(contact);
+ contact = contactlist_getcontact(list, &token);
+
+ }
+
+ return 0;
+
+}
+
+const char *contactlist_friendlyname(const char *username) {
+
+ ContactItem *contact;
+
+ contact = contactlist_findcontact(contactlist_forward, username);
+ if ( contact == NULL ) {
+ contact = contactlist_findcontact(contactlist_reverse, username);
+ }
+ if ( contact == NULL ) {
+ contact = contactlist_findcontact(contactlist_allow, username);
+ }
+ if ( contact == NULL ) {
+ contact = contactlist_findcontact(contactlist_block, username);
+ }
+ if ( contact == NULL ) {
+ contact = contactlist_findcontact(contactlist_temporary, username);
+ }
+ if ( contact == NULL ) {
+ contact = contactlist_findcontact(contactlist_pending, username);
+ }
+
+ /* Tricky one to handle. User isn't in any of the contact lists. Caller will probably create a
+ * record in the temporary list (which isn't stored on the server or in the cache) and use
+ * that as the SPOT. */
+ if ( contact == NULL ) {
+ debug_print("CL: contactlist_friendlyname: couldn't find user data.\n");
+ return NULL;
+ }
+
+ assert(contact != NULL);
+ assert(contact->contact != NULL);
+
+ /* Don't try to free this! You may also want to urldecode it. */
+ return contact->contact->friendlyname;
+
+}
+
+/* Check if a user has a display picture or not. Return NULL or an MSNObject as appropriate. */
+const char *contactlist_haspicture(const char *username) {
+
+ Contact *contact;
+
+ contact = contactlist_findcontactrecord(contactlist_forward, username);
+ if ( contact == NULL ) {
+ return NULL; /* e.g. if user was only on TL */
+ }
+ if ( (contact->dpobject != NULL) && (strlen(contact->dpobject) > 0) ) {
+ return contact->dpobject;
+ }
+
+ return NULL;
+
+}
+
+/* Return a contact's DP SHA1D, if they have one.. */
+const char *contactlist_dpsha1d(const char *username) {
+
+ Contact *contact;
+
+ contact = contactlist_findcontactrecord(contactlist_forward, username);
+ if ( contact != NULL ) {
+ return NULL;
+ }
+
+ if ( contact->dpsha1d != NULL ) {
+ return contact->dpsha1d;
+ }
+
+ return NULL;
+
+}
+
+/* Set UBX data for a contact. */
+void contactlist_setubx(const char *username, const char *ubxdata, size_t ubxdatalen) {
+
+ Contact *contact;
+ char *newubx;
+
+ contact = contactlist_findcontactrecord(contactlist_forward, username);
+ g_return_if_fail(contact != NULL);
+
+ if ( contact->ubxdata != NULL ) {
+ free(contact->ubxdata);
+ }
+
+ newubx = malloc(ubxdatalen);
+ memcpy(newubx, ubxdata, ubxdatalen);
+ contact->ubxdata = newubx;
+ contact->ubxdatalen = ubxdatalen;
+
+ /* Kick the UI */
+ debug_print("CL: Kicking UIContact because of UBX change.\n");
+ mainwindow_setcontactstatus(contact->uicontact, contact->username, contact->friendlyname, contact->status);
+
+}
+
+/* Return the Customised Status Message for a contact. */
+char *contactlist_csm(const char *username, int replace_entities) {
+
+ Contact *contact;
+
+ contact = contactlist_findcontactrecord(contactlist_forward, username);
+ if ( contact == NULL ) {
+ return NULL;
+ }
+ if ( contact->ubxdata == NULL ) {
+ return NULL;
+ }
+
+ /* Don't translate entities - Pango will do that later. */
+ return xml_getblock(contact->ubxdata, contact->ubxdatalen, "Data", "PSM", replace_entities);
+
+}
+
+ContactListIter *contactlist_iter_new() {
+
+ ContactListIter *iter = malloc(sizeof(ContactListIter));
+ *iter = 0;
+
+ return iter;
+
+}
+
+void contactlist_iter_destroy(ContactListIter *iter) {
+ free(iter);
+}
+
+const char *contactlist_getusername(ContactListIter *iter, const char *listl) {
+
+ unsigned int i;
+ ContactItem **list = contactlist_translatelist(listl);
+ ContactItem *item = *list;
+
+ if ( list == NULL ) {
+ debug_print("CL: contactlist_getusername: invalid list.\n");
+ return NULL;
+ }
+
+ for ( i=0; i<*iter; i++ ) {
+ if ( item == NULL ) {
+ break;
+ }
+ item = item->next;
+ }
+
+ if ( item == NULL ) {
+ return NULL;
+ }
+
+ (*iter)++;
+ return item->contact->username;
+
+}
+
+int contactlist_msnc(const char *username) {
+}
diff --git a/src/contactlist.h b/src/contactlist.h
new file mode 100644
index 0000000..f36ad9b
--- /dev/null
+++ b/src/contactlist.h
@@ -0,0 +1,70 @@
+/*
+ * contactlist.h
+ *
+ * Contact list (FL, BL, AL and RL) data structures
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef CONTACTLIST_H
+#define CONTACTLIST_H
+
+#include <stdio.h>
+
+#include "mainwindow.h"
+#include "msngenerics.h"
+
+/* Sources of contact details, in ascending order of accuracy */
+typedef enum {
+ CONTACT_SOURCE_FLN, /* Details came from FLN (which provides no information) */
+ CONTACT_SOURCE_LST, /* Contact's details came from LST */
+ CONTACT_SOURCE_CACHE, /* Details came from the cache */
+ CONTACT_SOURCE_ADC, /* Details came from an ADC (asynchronous add) */
+ CONTACT_SOURCE_RNG, /* Details came from a RNG or JOI */
+ CONTACT_SOURCE_NLN /* Details came from NLN or ILN */
+} ContactSource;
+
+typedef unsigned int ContactListIter;
+
+extern void contactlist_fldetails(ContactSource source, const char *username, const char *friendlyname, int features, const char *dpobject, OnlineState status, const char *guid);
+extern void contactlist_aldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status);
+extern void contactlist_bldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status);
+extern void contactlist_rldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status);
+extern void contactlist_tldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status);
+extern void contactlist_pldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status);
+
+extern void contactlist_removecontact(const char *list, const char *username);
+extern void contactlist_removecontactguid(const char *list, const char *guid);
+
+extern void contactlist_setubx(const char *username, const char *ubxdata, size_t ubxdatalen);
+extern char *contactlist_csm(const char *username, int replace_entities);
+
+extern void contactlist_clear();
+extern int contactlist_isonlist(const char *list, const char *username);
+extern const char *contactlist_friendlyname(const char *username);
+extern const char *contactlist_haspicture(const char *username);
+extern void contactlist_picturekick_sha1d(const char *sha1d);
+extern const char *contactlist_dpsha1d(const char *username);
+
+extern int contactlist_dumplist(FILE *fh, const char *list);
+extern ContactListIter *contactlist_iter_new();
+extern const char *contactlist_getusername(ContactListIter *iter, const char *list);
+extern void contactlist_iter_destroy(ContactListIter *iter);
+
+#endif /* CONTACTLIST_H */
diff --git a/src/debug.h b/src/debug.h
new file mode 100644
index 0000000..508e739
--- /dev/null
+++ b/src/debug.h
@@ -0,0 +1,33 @@
+/*
+ * debug.h
+ *
+ * Debugging stuff
+ *
+ * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef DEBUG_H
+#define DEBUG_H
+
+/* Simple #define for now. This could be changed to a more sophisticated
+ * system later (e.g. "tuxmessenger --debug") */
+#include <stdio.h>
+#define debug_print printf
+
+#endif /* DEBUG_H */
diff --git a/src/error.c b/src/error.c
new file mode 100644
index 0000000..0388584
--- /dev/null
+++ b/src/error.c
@@ -0,0 +1,121 @@
+/*
+ * error.c
+ *
+ * Report error messages
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <string.h>
+
+#include "mainwindow.h"
+
+void error_report(const char *message) {
+
+ GtkWidget *window;
+
+ window = gtk_message_dialog_new(mainwindow_gtkwindow(), GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, message);
+
+ g_signal_connect_swapped(window, "response", G_CALLBACK(gtk_widget_destroy), window);
+ gtk_widget_show(window);
+
+}
+
+static const char *decode_error(int error_number) {
+
+ /* decode server error numbers into understandable messages
+ for the User */
+
+ switch (error_number) {
+ /* I don't pretend to know what all of these mean... */
+
+ case 200: return "Server reported Syntax Error";
+ case 201: return "Server reported Invalid Parameter";
+ case 205: return "Server reported Invalid User";
+ case 206: return "Server reported FQDN Missing";
+ case 207: return "Already logged in";
+ case 208: return "Server reported Invalid Username";
+ case 209: return "Invalid friendly name";
+ case 210: return "You have too many people on your contact list";
+ case 215: return "IGNORE"; /* Already there */
+ case 216: return "That user is not on your list";
+ case 218: return "Already in that mode";
+ case 219: return "Already in opposite list";
+ case 280: return "Switchboard request failed";
+ case 281: return "NS XFR failed";
+ case 300: return "Server reported Required Fields Missing";
+ case 302: return "Not logged in";
+ case 403: return "List unavailable";
+ case 500: return "Internal server error";
+ case 501: return "DB server error";
+ case 510: return "File operation error";
+ case 520: return "Memory allocation error";
+ case 540: return "Failed challenge";
+ case 600: return "Server busy";
+ case 601: return "Server unavailable";
+ case 602: return "Peer NS down";
+ case 603: return "DB connection error";
+ case 604: return "Service is going down";
+ case 707: return "Error creating connection";
+ case 711: return "Error with blocking write";
+ case 712: return "Session overloaded";
+ case 713: return "User too active";
+ case 714: return "Too many sessions";
+ case 715: return "Not expected";
+ case 717: return "Bad friend file";
+ case 800: return "Too many requests: slow down...";
+ case 911: return "Authentication failed";
+ case 913: return "Not allowed while Invisible";
+ case 920: return "Not accepting new users";
+ case 928: return "TWN authentication ticket rejected - sign-in failed. This is probably a bug.";
+
+ }
+
+ return "Unknown error message reported by server";
+
+}
+
+void error_report_server(int number) {
+
+ const char *error_message;
+
+ error_message = decode_error(number);
+ if ( strcmp(error_message, "IGNORE") != 0 ) {
+ error_report(error_message);
+ }
+
+}
+
+void error_message(const char *message) {
+
+ GtkWidget *window;
+
+ window = gtk_message_dialog_new(mainwindow_gtkwindow(), GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, message);
+
+ g_signal_connect_swapped(window, "response", G_CALLBACK(gtk_widget_destroy), window);
+ gtk_widget_show(window);
+
+}
diff --git a/src/error.h b/src/error.h
new file mode 100644
index 0000000..f2c7fc9
--- /dev/null
+++ b/src/error.h
@@ -0,0 +1,32 @@
+/*
+ * error.h
+ *
+ * Report error messages
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef ERROR_H
+#define ERROR_H
+
+extern void error_report(const char *message);
+extern void error_report_server(int number);
+extern void error_message(const char *message);
+
+#endif /* ERROR_H */
diff --git a/src/filetrans.c b/src/filetrans.c
new file mode 100644
index 0000000..24fa47c
--- /dev/null
+++ b/src/filetrans.c
@@ -0,0 +1,57 @@
+/*
+ * filetrans.c
+ *
+ * File transfer
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "debug.h"
+#include "sbsessions.h"
+#include "msnp2p.h"
+
+/* Offer a file to a single recipient */
+static void filetrans_offer(const char *filename, const char *username) {
+
+ SbSession *session;
+
+ debug_print("FT: Offering '%s' to '%s'\n", filename, username);
+
+ /* Find a suitable session. */
+ session = sbsessions_find_single_safe(username);
+
+ msnp2p_offerfile(session, username, filename);
+
+}
+
+/* Offer a file to multiple recipients */
+void filetrans_offer_multiple(const char *filename, char *usernames[], unsigned int num_users) {
+
+ unsigned int i;
+ for ( i=0; i<num_users; i++ ) {
+ filetrans_offer(filename, usernames[i]);
+ }
+
+}
diff --git a/src/filetrans.h b/src/filetrans.h
new file mode 100644
index 0000000..dcfc806
--- /dev/null
+++ b/src/filetrans.h
@@ -0,0 +1,30 @@
+/*
+ * filetrans.h
+ *
+ * File transfer
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef FILETRANS_H
+#define FILETRANS_H
+
+extern void filetrans_offer_multiple(const char *filename, char *usernames[], unsigned int num_users);
+
+#endif /* FILETRANS_H */
diff --git a/src/fonttrans.c b/src/fonttrans.c
new file mode 100644
index 0000000..6dbb6a3
--- /dev/null
+++ b/src/fonttrans.c
@@ -0,0 +1,132 @@
+/*
+ * fonttrans.c
+ *
+ * Translate to and from MSN-style format strings
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "routines.h"
+#include "debug.h"
+
+char *fonttrans_font_to_format(const char *font) {
+
+ char *format;
+ char *temp;
+ int family = 0;
+ int pitch = 0;
+ char *fontname;
+ int i;
+
+ debug_print("FO: Font '%s'\n", font);
+
+ format = malloc(128);
+
+ /* Turn the font name into something useful at the other end. */
+ strcpy(format, "FN=");
+ fontname = strdup(font);
+
+ /* Cut off the font size */
+ for ( i=strlen(fontname); i>0; i-- ) {
+ if ( fontname[i] == ' ' ) {
+ fontname[i] = '\0';
+ break;
+ }
+ }
+
+ /* Cut out "Bold", "Italic" etc. */
+ if ( (temp = strstr(fontname, " Bold")) ) {
+ strcpy(temp, temp+5);
+ }
+ if ( (temp = strstr(fontname, " Oblique")) ) {
+ strcpy(temp, temp+8);
+ }
+ if ( (temp = strstr(fontname, " Italic")) ) {
+ strcpy(temp, temp+7);
+ }
+ if ( (temp = strstr(fontname, " Underline")) ) {
+ strcpy(temp, temp+10);
+ }
+ if ( (temp = strstr(fontname, " Strikethrough")) ) {
+ strcpy(temp, temp+14);
+ }
+
+ temp = routines_urlencode(fontname);
+ free(fontname);
+ strncat(format, temp, 100);
+ free(temp);
+
+ /* Work out bold, underline, italic etc. */
+ strcat(format, "; EF=");
+ if ( strstr(font, " Bold ") ) {
+ strcat(format, "B");
+ }
+ if ( strstr(font, " Italic ") || strstr(font, " Oblique ") ) {
+ strcat(format, "I");
+ }
+ if ( strstr(font, " Strikethrough ") ) {
+ strcat(format, "S");
+ }
+ if ( strstr(font, " Underline ") ) {
+ strcat(format, "U");
+ }
+
+ /* Work out font family and pitch. */
+ strcat(format, "; PF=");
+ if ( strstr(font, "Serif") || strstr(font, "Palladio") || strstr(font, "Bookman") || strstr(font, "Roman") ) {
+ family = 1;
+ }
+ if ( strstr(font, "Sans") || strstr(font, "Gothic") ) {
+ family = 2;
+ }
+ if ( strstr(font, "Monospace") || strstr(font, "Mono ") || strstr(font, "System") || strstr(font, "Fixed") ) {
+ family = 3;
+ }
+ if ( strstr(font, "Script") || strstr(font, "Chancery") ) {
+ family = 4;
+ }
+ if ( strstr(font, "Comic") ) {
+ family = 5;
+ }
+
+ /* Pitch */
+ if ( strstr(font, "Mono ") || strstr(font, "Monospace") || strstr(font, "Fixed") || strstr(font, "System") ) {
+ pitch = 1;
+ } else {
+ pitch = 2;
+ }
+
+ temp = malloc(3);
+ snprintf(temp, 3, "%i%i", family, pitch);
+ strcat(format, temp);
+ free(temp);
+
+ debug_print("FO: Format '%s'\n", format);
+
+ return format;
+
+}
diff --git a/src/fonttrans.h b/src/fonttrans.h
new file mode 100644
index 0000000..2795e05
--- /dev/null
+++ b/src/fonttrans.h
@@ -0,0 +1,30 @@
+/*
+ * fonttrans.h
+ *
+ * Translate to and from MSN-style format strings
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef FONTTRANS_H
+#define FONTTRANS_H
+
+extern char *fonttrans_font_to_format(const char *font);
+
+#endif /* FONTTRANS_H */
diff --git a/src/gtk-ink.c b/src/gtk-ink.c
new file mode 100644
index 0000000..793911d
--- /dev/null
+++ b/src/gtk-ink.c
@@ -0,0 +1,297 @@
+/*
+ * gtk-ink.c
+ *
+ * GTK widget to display and collect Ink
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <math.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "gtk-ink.h"
+#include "ink.h"
+
+#define PAPER_WIDTH 640
+#define PAPER_HEIGHT 480
+
+static GtkObjectClass *parent_class = NULL;
+
+static void gtk_ink_draw_point(GtkInk *gtk_ink, GnomeCanvasGroup *root, InkPoint *prev_point, InkPoint *new_point) {
+
+ if ( prev_point == NULL ) {
+ gnome_canvas_item_new(root, gnome_canvas_ellipse_get_type(), "fill-color-gdk", gtk_ink->fgcolour, "x1", new_point->x-1, "y1", new_point->y-1, "x2", new_point->x+1, "y2", new_point->y+1, NULL);
+ } else {
+
+ GnomeCanvasPoints *points;
+ gdouble width;
+ gdouble delta;
+
+ points = gnome_canvas_points_new(2);
+ points->coords[0] = prev_point->x;
+ points->coords[1] = prev_point->y;
+ points->coords[2] = new_point->x;
+ points->coords[3] = new_point->y;
+
+ delta = sqrt((prev_point->x - new_point->x)*(prev_point->x - new_point->x) + (prev_point->y - new_point->y)*(prev_point->y - new_point->y));
+
+ /* Calibrate 'feel' here. */
+ width = (15-delta)/6.5;
+ if ( width < 0 ) {
+ width = 0;
+ }
+
+ gnome_canvas_item_new(root, gnome_canvas_line_get_type(), "fill-color-gdk", gtk_ink->fgcolour, "points", points, "width-units", width, NULL);
+ }
+
+}
+
+gboolean gtk_ink_realize(GtkWidget *widget, GtkInk *gtk_ink) {
+
+ InkStroke *stroke;
+ GnomeCanvasGroup *root;
+
+ GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
+ root = gnome_canvas_root(GNOME_CANVAS(gtk_ink));
+
+ /* Draw the (initial) pattern. */
+ stroke = gtk_ink->ink->strokes;
+ while ( stroke != NULL ) {
+
+ InkPoint *point;
+ point = stroke->points;
+
+ while ( point != NULL ) {
+
+ gtk_ink_draw_point(gtk_ink, root, point->prev, point);
+ point = point->next;
+
+ }
+ stroke = stroke->next;
+ }
+
+ return FALSE;
+
+}
+
+gboolean gtk_ink_expose_event(GtkWidget *widget, GdkEventExpose *event, GtkInk *gtk_ink) {
+
+ /* Draw the border. */
+ gtk_paint_shadow(widget->style, ((GtkLayout *)gtk_ink)->bin_window, widget->state, GTK_SHADOW_IN, &event->area, widget, NULL, 0, 0, gtk_ink->width, gtk_ink->height);
+
+ return FALSE;
+
+}
+
+static void gtk_ink_size_allocate(GtkWidget *widget, GtkAllocation *allocation, GtkInk *gtk_ink) {
+
+ gtk_ink->width = allocation->width;
+ gtk_ink->height = allocation->height;
+ gnome_canvas_set_scroll_region(GNOME_CANVAS(gtk_ink), 0, 0, allocation->width, allocation->height);
+
+ /* Sort the background out. */
+ if ( (gtk_ink->width > gtk_ink->x_bg_drawn * PAPER_WIDTH) || (gtk_ink->height > gtk_ink->y_bg_drawn * PAPER_HEIGHT) ) {
+
+ int x, y;
+ GnomeCanvasGroup *root;
+
+ root = gnome_canvas_root(GNOME_CANVAS(gtk_ink));
+
+ for ( x=gtk_ink->x_bg_drawn; x<=gtk_ink->width/PAPER_WIDTH; x++ ) {
+ for ( y=0; y<=gtk_ink->height/PAPER_HEIGHT; y++ ) {
+ if ( gtk_ink->bgpixbuf == NULL ) {
+ gnome_canvas_item_lower_to_bottom(gnome_canvas_item_new(root, gnome_canvas_rect_get_type(), "fill-color-gdk", gtk_ink->bgcolour, "x1", (gdouble)x*PAPER_WIDTH, "y1", (gdouble)y*PAPER_HEIGHT, "x2", (gdouble)(x+1)*PAPER_WIDTH, "y2", (gdouble)(y+1)*PAPER_HEIGHT, NULL));
+ } else {
+ gnome_canvas_item_lower_to_bottom(gnome_canvas_item_new(root, gnome_canvas_pixbuf_get_type(), "pixbuf", gtk_ink->bgpixbuf, "x", (gdouble)x*PAPER_WIDTH, "y", (gdouble)y*PAPER_HEIGHT, "x-in-pixels", TRUE, "y-in-pixels", TRUE, "width-set", FALSE, "height-set", FALSE, NULL));
+ }
+ }
+ }
+ gtk_ink->x_bg_drawn = (gtk_ink->width/PAPER_WIDTH)+1;
+
+ for ( x=0; x<gtk_ink->width/PAPER_WIDTH; x++ ) {
+ for ( y=gtk_ink->y_bg_drawn; y<=gtk_ink->height/PAPER_HEIGHT; y++ ) {
+ if ( gtk_ink->bgpixbuf == NULL ) {
+ gnome_canvas_item_lower_to_bottom(gnome_canvas_item_new(root, gnome_canvas_rect_get_type(), "fill-color-gdk", gtk_ink->bgcolour, "x1", (gdouble)x*PAPER_WIDTH, "y1", (gdouble)y*PAPER_HEIGHT, "x2", (gdouble)(x+1)*PAPER_WIDTH, "y2", (gdouble)(y+1)*PAPER_HEIGHT, NULL));
+ } else {
+ gnome_canvas_item_lower_to_bottom(gnome_canvas_item_new(root, gnome_canvas_pixbuf_get_type(), "pixbuf", gtk_ink->bgpixbuf, "x", (gdouble)x*PAPER_WIDTH, "y", (gdouble)y*PAPER_HEIGHT, "x-in-pixels", TRUE, "y-in-pixels", TRUE, "width-set", FALSE, "height-set", FALSE, NULL));
+ }
+ }
+ }
+ gtk_ink->y_bg_drawn = (gtk_ink->height/PAPER_HEIGHT)+1;
+
+ }
+
+}
+
+static void gtk_ink_add_point_and_draw(GtkInk *gtk_ink, double x, double y) {
+
+ InkStroke *stroke;
+ InkPoint *prev_point;
+ InkPoint *new_point;
+
+ stroke = ink_stroke_get_last(gtk_ink->ink);
+ prev_point = ink_point_get_last(stroke);
+ new_point = ink_point_add(stroke, x, y);
+
+ gtk_ink_draw_point(gtk_ink, gnome_canvas_root(GNOME_CANVAS(gtk_ink)), prev_point, new_point);
+
+}
+
+static gboolean gtk_ink_button_release(GtkWidget *widget, GdkEventButton *event, GtkInk *gtk_ink) {
+ gtk_ink->drawing = FALSE;
+ return TRUE;
+}
+
+static gboolean gtk_ink_button_press(GtkWidget *widget, GdkEventButton *event, GtkInk *gtk_ink) {
+
+ ink_stroke_new(gtk_ink->ink);
+ gtk_ink_add_point_and_draw(gtk_ink, event->x, event->y);
+ gtk_ink->drawing = TRUE;
+
+ /* Grab focus */
+ if ( !GTK_WIDGET_HAS_DEFAULT(gtk_ink) ) {
+ gtk_widget_grab_default(GTK_WIDGET(gtk_ink));
+ }
+ if ( !GTK_WIDGET_HAS_FOCUS(gtk_ink) ) {
+ gtk_widget_grab_focus(GTK_WIDGET(gtk_ink));
+ }
+
+ return TRUE; /* Claim it. */
+
+}
+
+static gboolean gtk_ink_motion(GtkWidget *widget, GdkEventMotion *event, GtkInk *gtk_ink) {
+
+ if ( gtk_ink->drawing ) {
+ gtk_ink_add_point_and_draw(gtk_ink, event->x, event->y);
+ return TRUE;
+ }
+
+ return FALSE;
+
+}
+
+static void gtk_ink_destroy(GtkObject *gtk_ink) {
+
+ /* GdkColors automagically freed. */
+ parent_class->destroy(gtk_ink);
+
+}
+
+GtkWidget *gtk_ink_new(GtkInkFlags flags, Ink *ink, GdkColor *fgcolour, GdkColor *bgcolour) {
+
+ GtkInk *gtk_ink;
+
+ gtk_ink = GTK_INK(gtk_type_new(gtk_ink_get_type()));
+
+ gnome_canvas_set_pixels_per_unit(GNOME_CANVAS(gtk_ink), 1);
+
+ gtk_signal_connect_after(GTK_OBJECT(gtk_ink), "expose_event", GTK_SIGNAL_FUNC(gtk_ink_expose_event), gtk_ink);
+ gtk_signal_connect(GTK_OBJECT(gtk_ink), "size_allocate", GTK_SIGNAL_FUNC(gtk_ink_size_allocate), gtk_ink);
+ gtk_signal_connect(GTK_OBJECT(gtk_ink), "realize", GTK_SIGNAL_FUNC(gtk_ink_realize), gtk_ink);
+
+ if ( flags & GTK_INK_FLAG_EDITABLE ) {
+ gtk_signal_connect(GTK_OBJECT(gtk_ink), "button-press-event", GTK_SIGNAL_FUNC(gtk_ink_button_press), gtk_ink);
+ gtk_signal_connect(GTK_OBJECT(gtk_ink), "button-release-event", GTK_SIGNAL_FUNC(gtk_ink_button_release), gtk_ink);
+ gtk_signal_connect(GTK_OBJECT(gtk_ink), "motion-notify-event", GTK_SIGNAL_FUNC(gtk_ink_motion), gtk_ink);
+ gtk_widget_add_events(GTK_WIDGET(gtk_ink), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK);
+ }
+ g_object_set(G_OBJECT(gtk_ink), "can-focus", TRUE, "can-default", TRUE, NULL);
+
+ /* Set up properties. */
+ gtk_ink->bgcolour = gdk_color_copy(bgcolour);
+ gtk_ink->fgcolour = gdk_color_copy(fgcolour);
+ gtk_ink->flags = flags;
+ gtk_ink->ink = ink;
+ gtk_ink->x_bg_drawn = 0;
+ gtk_ink->y_bg_drawn = 0;
+ gtk_ink->drawing = FALSE;
+
+ if ( flags & GTK_INK_FLAG_PAPER ) {
+ gtk_ink->bgpixbuf = gdk_pixbuf_new_from_file("/usr/local/share/tuxmessenger/paper.png", NULL);
+ } else {
+ gtk_ink->bgpixbuf = NULL;
+ }
+
+ return GTK_WIDGET(gtk_ink);
+
+}
+
+static GObject *gtk_ink_constructor(GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) {
+
+ GtkInkClass *class;
+ GObjectClass *p_class;
+ GObject *obj;
+
+ class = GTK_INK_CLASS(g_type_class_peek(gtk_ink_get_type()));
+ p_class = G_OBJECT_CLASS(g_type_class_peek_parent(class));
+
+ obj = p_class->constructor(type, n_construct_properties, construct_properties);
+ g_object_set(obj, "aa", TRUE, NULL);
+
+ return obj;
+
+}
+
+static void gtk_ink_class_init(GtkInkClass *class) {
+
+ GtkObjectClass *object_class;
+ GObjectClass *g_object_class;
+
+ object_class = (GtkObjectClass *) class;
+ g_object_class = G_OBJECT_CLASS(class);
+
+ object_class->destroy = gtk_ink_destroy;
+ g_object_class->constructor = gtk_ink_constructor;
+
+ parent_class = gtk_type_class(gnome_canvas_get_type());
+
+}
+
+static void gtk_ink_init(GtkInk *gtk_ink) {
+}
+
+guint gtk_ink_get_type(void) {
+
+ static guint gtk_ink_type = 0;
+
+ if ( !gtk_ink_type ) {
+
+ GtkTypeInfo gtk_ink_info = {
+ "GtkInk",
+ sizeof(GtkInk),
+ sizeof(GtkInkClass),
+ (GtkClassInitFunc) gtk_ink_class_init,
+ (GtkObjectInitFunc) gtk_ink_init,
+ NULL,
+ NULL,
+ (GtkClassInitFunc) NULL,
+ };
+ gtk_ink_type = gtk_type_unique(gnome_canvas_get_type(), &gtk_ink_info);
+
+ }
+
+ return gtk_ink_type;
+
+}
diff --git a/src/gtk-ink.h b/src/gtk-ink.h
new file mode 100644
index 0000000..9eb75d3
--- /dev/null
+++ b/src/gtk-ink.h
@@ -0,0 +1,68 @@
+/*
+ * gtk-ink.h
+ *
+ * GTK widgets to display and collect Ink
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef GTKINK_H
+#define GTKINK_H
+
+#include <gtk/gtk.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "ink.h"
+
+typedef enum {
+ GTK_INK_FLAG_EDITABLE = 1<<0, /* Editable by the user. */
+ GTK_INK_FLAG_PAPER = 1<<1, /* Draw nice paper background. */
+} GtkInkFlags;
+
+typedef struct {
+ GnomeCanvasClass parent_class;
+} GtkInkClass;
+
+typedef struct {
+
+ GnomeCanvas parent; /* Parent widget */
+
+ Ink *ink; /* Ink object this widget is associated with. */
+ GtkInkFlags flags; /* Flags for this GtkInk widget. */
+
+ int width; /* Width of drawing area. */
+ int height; /* Height of drawing area. */
+ int drawing; /* Currently drawing? */
+ GdkColor *bgcolour; /* Background colour for drawing area. */
+ GdkColor *fgcolour; /* Foreground colour for drawing area. */
+ GdkPixbuf *bgpixbuf; /* Pixbuf to use for the background. */
+
+ int x_bg_drawn;
+ int y_bg_drawn; /* Number of background tiles drawn in the x and y directions. */
+
+} GtkInk;
+
+extern guint gtk_ink_get_type(void);
+extern GtkWidget *gtk_ink_new(GtkInkFlags flags, Ink *ink, GdkColor *fgcolour, GdkColor *bgcolour);
+
+#define GTK_INK(obj) GTK_CHECK_CAST(obj, gtk_ink_get_type(), GtkInk)
+#define GTK_INK_CLASS(class) GTK_CHECK_CLASS_CAST(class, gtk_ink_get_type(), GtkInkClass)
+#define GTK_IS_INK(obj) GTK_CHECK_TYPE(obj, gtk_ink_get_type())
+
+#endif /* GTKINK_H */
diff --git a/src/ink.c b/src/ink.c
new file mode 100644
index 0000000..9655d8e
--- /dev/null
+++ b/src/ink.c
@@ -0,0 +1,159 @@
+/*
+ * ink.c
+ *
+ * Routines for handling Ink
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "ink.h"
+#include "assert.h"
+#include "routines.h"
+
+/* Create a new Ink object. */
+Ink *ink_new() {
+
+ Ink *ink = malloc(sizeof(Ink));
+
+ ink->strokes = NULL;
+ ink->last_stroke = NULL;
+
+ return ink;
+
+}
+
+static Ink *ink_new_from_isf_base64(const char *base64_isf) {
+
+ Ink *ink;
+ size_t isf_len;
+ char *isf;
+ FILE *fh = fopen("/home/weiss/isf.dat", "w");
+
+ ink = ink_new();
+ isf_len = routines_base64decode(base64_isf, &isf);
+
+ printf("'%s' (%i bytes)\n", isf, isf_len);
+ fwrite(isf, isf_len, 1, fh);
+ fclose(fh);
+
+ return ink;
+
+}
+
+/* Create a new Ink object and load it with data from an ISF string. */
+Ink *ink_new_from_isf(const char *isf) {
+
+ if ( strncmp(isf, "base64:", 7) == 0 ) {
+ return ink_new_from_isf_base64(isf+7);
+ }
+
+ /* Can't load anything else at the moment. Return a blank. */
+ return ink_new();
+
+}
+
+/* Create a new stroke in a given Ink object. */
+InkStroke *ink_stroke_new(Ink *ink) {
+
+ InkStroke *stroke = malloc(sizeof(InkStroke));
+
+ if ( ink->last_stroke != NULL ) {
+ assert(ink->last_stroke->next == NULL);
+ ink->last_stroke->next = stroke;
+ } else {
+ /* No strokes */
+ ink->strokes = stroke;
+ }
+
+ ink->last_stroke = stroke;
+
+ stroke->next = NULL;
+ stroke->points = NULL;
+ stroke->last_point = NULL;
+
+ return stroke;
+
+}
+
+/* Get last stroke - automatically creating the first one if there aren't any. */
+InkStroke *ink_stroke_get_last(Ink *ink) {
+
+ if ( ink->last_stroke != NULL ) {
+ return ink->last_stroke;
+ }
+
+ return ink_stroke_new(ink); /* Automatically becomes the last stroke. */
+
+}
+
+InkPoint *ink_point_get_last(InkStroke *stroke) {
+
+ assert(stroke != NULL);
+ return stroke->last_point; /* Might be NULL. */
+
+}
+
+InkPoint *ink_point_get_previous(InkPoint *point) {
+
+ if ( point == NULL ) {
+ return NULL;
+ }
+
+ return point->prev; /* Might be NULL. */
+
+}
+
+/* Create a new point in a given stroke. */
+InkPoint *ink_point_add(InkStroke *stroke, double x, double y) {
+
+ InkPoint *last_point = ink_point_get_last(stroke);
+ InkPoint *point = malloc(sizeof(InkPoint));
+
+ if ( last_point != NULL ) {
+ assert(last_point->next == NULL);
+ last_point->next = point;
+ point->prev = last_point;
+ } else {
+ /* No points! */
+ stroke->points = point;
+ point->prev = NULL;
+ }
+
+ stroke->last_point = point;
+
+ point->next = NULL;
+ point->x = x;
+ point->y = y;
+
+ return point;
+
+}
+
+/* Destroy an Ink object. */
+void ink_destroy(Ink *ink) {
+ free(ink);
+}
diff --git a/src/ink.h b/src/ink.h
new file mode 100644
index 0000000..4842b9a
--- /dev/null
+++ b/src/ink.h
@@ -0,0 +1,68 @@
+/*
+ * ink.h
+ *
+ * Routines for handling Ink
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef INK_H
+#define INK_H
+
+typedef struct stru_inkpoint {
+
+ struct stru_inkpoint *next; /* Next point in linked list */
+
+ double x;
+ double y;
+
+ struct stru_inkpoint *prev; /* Previous point in linked list */
+
+} InkPoint;
+
+typedef struct stru_inkstroke {
+
+ struct stru_inkstroke *next; /* Next stroke in linked list */
+ InkPoint *points; /* Linked list of points. */
+
+ InkPoint *last_point; /* Last point in list (saves rescanning) */
+
+} InkStroke;
+
+/* Opaque! */
+typedef struct {
+
+ InkStroke *strokes; /* Linked list of strokes */
+
+ InkStroke *last_stroke; /* Last stroke in list (saves rescanning) */
+
+} Ink;
+
+extern Ink *ink_new();
+extern Ink *ink_new_from_isf(const char *isf);
+extern void ink_destroy(Ink *ink);
+
+extern InkStroke *ink_stroke_new(Ink *ink);
+extern InkStroke *ink_stroke_get_last(Ink *ink);
+
+extern InkPoint *ink_point_add(InkStroke *stroke, double x, double y);
+extern InkPoint *ink_point_get_last(InkStroke *stroke);
+extern InkPoint *ink_point_get_previous(InkPoint *point);
+
+#endif /* INK_H */
diff --git a/src/listcache.c b/src/listcache.c
new file mode 100644
index 0000000..a895ff8
--- /dev/null
+++ b/src/listcache.c
@@ -0,0 +1,615 @@
+/*
+ * listcache.c
+ *
+ * Contact list caching
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "contactlist.h"
+#include "debug.h"
+#include "options.h"
+#include "routines.h"
+#include "mainwindow.h"
+#include "xml.h"
+#include "msnprotocol.h"
+#include "addcontact.h"
+#include "error.h"
+
+static char *listcache_tag = NULL;
+static char *listcache_mfn = NULL;
+static char *listcache_ubxdata = NULL;
+static unsigned int listcache_invalid = FALSE;
+
+static char *listcache_file() {
+
+ char *filename;
+ char *done;
+
+ filename = routines_glob("~/.tuxmessenger/");
+ done = malloc(strlen(filename)+11);
+ strcpy(done, filename);
+ free(filename);
+ strcat(done, "list.cache");
+
+ return done;
+
+}
+
+/* Called to record the friendly name for later caching */
+void listcache_setmfn(const char *mfn) {
+
+ if ( listcache_mfn != NULL ) {
+ free(listcache_mfn);
+ }
+
+ listcache_mfn = strdup(mfn);
+
+}
+
+const char *listcache_getmfn() {
+
+ if ( listcache_mfn == NULL ) {
+ return "";
+ }
+
+ return listcache_mfn;
+
+}
+
+void listcache_setcsm(const char *csm) {
+
+ const char *template;
+
+ if ( listcache_ubxdata != NULL ) {
+ free(listcache_ubxdata);
+ }
+
+ template = "<Data><PSM></PSM><CurrentMedia></CurrentMedia></Data>";
+ listcache_ubxdata = xml_setblock(template, strlen(template), "Data", "PSM", csm);
+
+}
+
+static char *listcache_getcsm_internal(int entities) {
+
+ char *result;
+
+ if ( listcache_ubxdata == NULL ) {
+ return strdup("");
+ }
+
+ result = xml_getblock(listcache_ubxdata, strlen(listcache_ubxdata), "Data", "PSM", entities);
+ if ( result == NULL ) {
+ return strdup("");
+ }
+
+ return result;
+
+}
+
+char *listcache_getcsm() {
+ return listcache_getcsm_internal(0);
+}
+
+char *listcache_getcsm_noentities() {
+ return listcache_getcsm_internal(1);
+}
+
+static int listcache_processline(char *line) {
+
+ char *token = routines_lindex(line, 0);
+
+ if ( strcmp(token, "FL") == 0 ) {
+
+ char *username = routines_lindex(line, 1);
+ char *friendlyname = routines_lindex(line, 2);
+ char *guid = routines_lindex(line, 3);
+ if ( username == NULL ) {
+ return -1;
+ }
+ if ( strlen(guid) == 0 ) {
+ free(guid);
+ guid = NULL;
+ }
+ if ( strlen(friendlyname) == 0 ) {
+ /* This would be a bad thing. */
+ free(friendlyname);
+ friendlyname = NULL;
+ }
+ contactlist_fldetails(CONTACT_SOURCE_CACHE, username, friendlyname, 0, NULL, ONLINE_FLN, guid);
+ free(username);
+ free(friendlyname);
+
+ } else if ( strcmp(token, "AL") == 0 ) {
+
+ char *username = routines_lindex(line, 1);
+ char *friendlyname = routines_lindex(line, 2);
+ if ( username == NULL ) {
+ return -1;
+ }
+ if ( strlen(friendlyname) == 0 ) {
+ free(friendlyname);
+ friendlyname = NULL;
+ }
+ contactlist_aldetails(CONTACT_SOURCE_CACHE, username, friendlyname, ONLINE_FLN);
+ free(username);
+ if ( friendlyname != NULL ) {
+ free(friendlyname);
+ }
+
+ } else if ( strcmp(token, "BL") == 0 ) {
+
+ char *username = routines_lindex(line, 1);
+ char *friendlyname = routines_lindex(line, 2);
+ if ( username == NULL ) {
+ return -1;
+ }
+ if ( strlen(friendlyname) == 0 ) {
+ free(friendlyname);
+ friendlyname = NULL;
+ }
+ contactlist_bldetails(CONTACT_SOURCE_CACHE, username, friendlyname, ONLINE_FLN);
+ free(username);
+ if ( friendlyname != NULL ) {
+ free(friendlyname);
+ }
+
+ } else if ( strcmp(token, "RL") == 0 ) {
+
+ char *username = routines_lindex(line, 1);
+ char *friendlyname = routines_lindex(line, 2);
+ if ( username == NULL ) {
+ return -1;
+ }
+ if ( strlen(friendlyname) == 0 ) {
+ free(friendlyname);
+ friendlyname = NULL;
+ }
+ contactlist_rldetails(CONTACT_SOURCE_CACHE, username, friendlyname, ONLINE_FLN);
+ free(username);
+ if ( friendlyname != NULL ) {
+ free(friendlyname);
+ }
+
+ } else if ( strcmp(token, "PL") == 0 ) {
+
+ char *username = routines_lindex(line, 1);
+ char *friendlyname = routines_lindex(line, 2);
+ if ( username == NULL ) {
+ return -1;
+ }
+ if ( strlen(friendlyname) == 0 ) {
+ free(friendlyname);
+ friendlyname = NULL;
+ }
+ contactlist_pldetails(CONTACT_SOURCE_CACHE, username, friendlyname, ONLINE_FLN);
+ addcontact_added(username, friendlyname);
+
+ free(username);
+ if ( friendlyname != NULL ) {
+ free(friendlyname);
+ }
+
+ } else if ( strcmp(token, "BLP") == 0 ) {
+ debug_print("LC: Got BLP from cache.\n");
+ } else if ( strcmp(token, "GTC") == 0 ) {
+ debug_print("LC: Got GTC from cache.\n");
+ } else if ( strcmp(token, "MFN") == 0 ) {
+
+ char *mfn = routines_lindex(line, 1);
+ debug_print("LC: Got MFN from cache.\n");
+ mainwindow_setmfn(mfn);
+ listcache_setmfn(mfn);
+ free(mfn);
+
+ } else if ( strcmp(token, "CSM") == 0 ) {
+
+ /* See also listcache_loadtag_internal() */
+ char *csm_text;
+ char *csm = routines_lindexend(line, 1);
+ msnprotocol_setcsm(csm);
+
+ csm_text = listcache_getcsm();
+ mainwindow_setcsm(csm_text);
+ free(csm_text);
+
+ free(csm);
+ debug_print("LC: Got CSM from cache.\n");
+
+ } else {
+ free(token);
+ return -1;
+ }
+
+ free(token);
+ return 0;
+
+}
+
+static FILE *listcache_loadtag_internal() {
+
+ FILE *fh;
+ char *line;
+ char *rval;
+ char *cachefile;
+ int done = 0;
+
+ cachefile = listcache_file();
+
+ fh = fopen(cachefile, "r");
+ free(cachefile);
+ if ( fh == NULL ) {
+ debug_print("LC: Error opening list cache file.\n");
+ return NULL;
+ }
+
+ line = malloc(1024);
+ assert(line != NULL);
+
+ /* Check this cache file is right for the username. Bin it if not... */
+ rval = fgets(line, 1023, fh);
+ if ( ferror(fh) || (rval == NULL) ) {
+ debug_print("LC: Error retrieving list cache username.\n");
+ return NULL;
+ }
+ if ( line[strlen(line)-1] == '\n' ) {
+ line[strlen(line)-1] = '\0'; /* Cut off trailing newline. */
+ }
+ debug_print("LC: list.cache file is for user '%s' ", line);
+ if ( strcmp(line, options_username()) == 0 ) {
+ debug_print("- good.\n");
+ } else {
+
+ debug_print("- wrong. Deleting...\n");
+ free(line);
+ fclose(fh);
+ cachefile = listcache_file();
+ fh = fopen(listcache_file(), "w");
+ free(cachefile);
+ return NULL;
+
+ }
+
+ /* Get the SYN tag. */
+ rval = fgets(line, 1023, fh);
+ if ( ferror(fh) || (rval == NULL) ) {
+ debug_print("LC: Error retrieving SYN tag.\n");
+ free(line);
+ fclose(fh);
+ return NULL;
+ }
+ if ( line[strlen(line)-1] == '\n' ) {
+ line[strlen(line)-1] = '\0'; /* Cut off trailing newline. */
+ }
+ if ( listcache_tag != NULL ) {
+ free(listcache_tag);
+ }
+ listcache_tag = strdup(line);
+
+ /* Because there doesn't seem to be a way to retrieve the CSM, the CSM also
+ needs to be read from the cache file at this point. Yuk. */
+ while ( rval && !done ) {
+
+ rval = fgets(line, 1023, fh);
+ if ( rval != NULL ) {
+
+ char *token;
+
+ if ( line[strlen(line)-1] == '\n' ) {
+ line[strlen(line)-1] = '\0'; /* Cut off trailing newline. */
+ }
+
+ token = routines_lindex(line, 0);
+
+ if ( strcmp(token, "CSM") == 0 ) {
+
+ /* See also listcache_processline() */
+ char *csm_text;
+ char *csm = routines_lindexend(line, 1);
+ msnprotocol_setcsm(csm);
+
+ csm_text = listcache_getcsm();
+ mainwindow_setcsm(csm_text);
+ free(csm_text);
+
+ free(csm);
+ debug_print("LC: Got CSM from cache.\n");
+ done = 1;
+
+ }
+
+ free(token);
+
+ }
+
+ }
+
+ free(line);
+
+ return fh;
+
+}
+
+char *listcache_loadtag() {
+
+ FILE *fh;
+
+ if ( listcache_invalid ) {
+ debug_print("LC: Cache invalid - not loading.\n");
+ return "0 0";
+ }
+
+ fh = listcache_loadtag_internal();
+ if ( fh != NULL ) {
+ fclose(fh);
+ return listcache_tag;
+ }
+
+ return "0 0";
+
+}
+
+/* Throws the cached contact list at contactlist.c. Returns the tag to send with SYN.
+ (Actually, this return value is never actually used...) */
+char *listcache_load() {
+
+ FILE *fh;
+ char *line;
+ char *rval = "boing";
+ int whoops = 0;
+
+ line = malloc(1024);
+ assert(line != NULL);
+
+ if ( listcache_invalid ) {
+ debug_print("LC: Cache invalid - not loading.\n");
+ return "0 0";
+ }
+
+ fh = listcache_loadtag_internal();
+ if ( fh == NULL ) {
+ return "0 0";
+ }
+
+ while ( rval && !whoops ) {
+ rval = fgets(line, 1023, fh);
+ if ( rval != NULL ) {
+ if ( line[strlen(line)-1] == '\n' ) {
+ line[strlen(line)-1] = '\0'; /* Cut off trailing newline. */
+ }
+ whoops = listcache_processline(line);
+ }
+ }
+ if ( ferror(fh) || whoops ) {
+ debug_print("LC: Error reading list cache file - re-requesting list.\n");
+ contactlist_clear();
+ return "0 0";
+ }
+
+ fclose(fh);
+ free(line);
+
+ return listcache_tag;
+
+}
+
+/* Called to record the most recent tag returned by SYN, to be saved with the list at the next listcache_save */
+void listcache_settag(const char *tag) {
+
+ if ( listcache_tag != NULL ) {
+
+ if ( strcmp(tag, listcache_tag) != 0 ) {
+ /* Tag has changed, so invalidate the lists ready for the new version.*/
+ contactlist_clear();
+ }
+
+ free(listcache_tag);
+
+ }
+
+ listcache_tag = strdup(tag);
+
+}
+
+/* Save the contact data (from contactlist.c) out to disk. */
+void listcache_save() {
+
+ FILE *fh;
+ int rval = 0;
+ char *tag_newline;
+ char *mfn;
+ char *cachefile;
+
+ cachefile = listcache_file();
+
+ fh = fopen(cachefile, "w");
+ if ( chmod(cachefile, S_IRUSR | S_IWUSR) != 0 ) {
+ error_report("Couldn't set permissions on contact list cache file!");
+ }
+ free(cachefile);
+
+ if ( fh == NULL ) {
+ debug_print("LC: Error opening list cache file.\n");
+ return;
+ }
+
+ /* Record the username */
+ tag_newline = malloc(strlen(options_username())+2);
+ assert(tag_newline != NULL);
+ strcpy(tag_newline, options_username());
+ tag_newline[strlen(tag_newline)+1] = '\0';
+ tag_newline[strlen(tag_newline)] = '\n';
+ if ( fputs(tag_newline, fh) == EOF ) {
+ rval = -1;
+ }
+ free(tag_newline);
+
+ /* Record the SYN tag */
+ tag_newline = malloc(strlen(listcache_tag)+2);
+ assert(tag_newline != NULL);
+ strcpy(tag_newline, listcache_tag);
+ tag_newline[strlen(tag_newline)+1] = '\0';
+ tag_newline[strlen(tag_newline)] = '\n';
+ if ( fputs(tag_newline, fh) == EOF ) {
+ rval = -1;
+ }
+ free(tag_newline);
+
+ /* CSM has to be saved before anything else, since listcache_loadtag_internal loads
+ the file as far as the CSM, ignoring everything else, then passes the file
+ handle to the main parser without resetting the pointer. */
+ if ( rval == 0 ) {
+
+ char *csm_text = listcache_getcsm_noentities();
+ if ( csm_text != NULL ) {
+
+ /* Record CSM */
+ char *csm = malloc(strlen(csm_text)+7);
+ strcpy(csm, "CSM ");
+ strcat(csm, csm_text);
+ csm[strlen(csm)+1] = '\0';
+ csm[strlen(csm)] = '\n';
+ if ( fputs(csm, fh) == EOF ) {
+ rval = -1;
+ }
+ free(csm);
+ free(csm_text);
+
+ }
+
+ }
+
+ if ( rval == 0 ) {
+
+ char *blp_newline;
+
+ /* Record the value of BLP */
+ blp_newline = malloc(6+strlen(options_blp()));
+ assert(blp_newline != NULL);
+
+ strcpy(blp_newline, "BLP ");
+ strcat(blp_newline, options_blp());
+ blp_newline[strlen(blp_newline)+1] = '\0';
+ blp_newline[strlen(blp_newline)] = '\n';
+ if ( fputs(blp_newline, fh) == EOF ) {
+ rval = -1;
+ }
+ free(blp_newline);
+
+ }
+
+ if ( rval == 0 ) {
+
+ char *gtc_newline;
+
+ /* Record the value of GTC */
+ gtc_newline = malloc(6+strlen(options_gtc()));
+ assert(gtc_newline != NULL);
+
+ strcpy(gtc_newline, "GTC ");
+ strcat(gtc_newline, options_gtc());
+ gtc_newline[strlen(gtc_newline)+1] = '\0';
+ gtc_newline[strlen(gtc_newline)] = '\n';
+ if ( fputs(gtc_newline, fh) == EOF ) {
+ rval = -1;
+ }
+ free(gtc_newline);
+
+ }
+
+ if ( rval == 0 ) {
+
+ if ( listcache_mfn != NULL ) {
+
+ /* Record friendly name */
+ mfn = malloc(strlen(listcache_mfn)+7);
+ strcpy(mfn, "MFN ");
+ strcat(mfn, listcache_mfn);
+ mfn[strlen(mfn)+1] = '\0';
+ mfn[strlen(mfn)] = '\n';
+ if ( fputs(mfn, fh) == EOF ) {
+ rval = -1;
+ }
+ free(mfn);
+
+ }
+
+ }
+
+ if ( rval == 0 ) {
+ rval = contactlist_dumplist(fh, "FL");
+ }
+ if ( rval == 0 ) {
+ rval = contactlist_dumplist(fh, "RL");
+ }
+ if ( rval == 0 ) {
+ rval = contactlist_dumplist(fh, "AL");
+ }
+ if ( rval == 0 ) {
+ rval = contactlist_dumplist(fh, "BL");
+ }
+ if ( rval == 0 ) {
+ rval = contactlist_dumplist(fh, "PL");
+ }
+
+ if ( rval == 0 ) {
+ debug_print("LC: Saved contacts to cache.\n");
+ } else {
+
+ char *cachefile;
+
+ debug_print("LC: Error saving contacts to cache.\n");
+ fclose(fh);
+ /* Attempt to truncate the cache file for safety next time. */
+ cachefile = listcache_file();
+ fh = fopen(cachefile, "w");
+ free(cachefile);
+
+ }
+
+ fclose(fh);
+ listcache_invalid = FALSE;
+
+}
+
+int listcache_checktag(const char *tag) {
+
+ if ( strcmp(tag, listcache_tag) == 0 ) {
+ return 1;
+ }
+
+ return 0;
+
+}
+
+/* Mark the contents of the cache as invalid, for example, after changing the local username. */
+void listcache_invalidate() {
+ listcache_invalid = TRUE;
+}
diff --git a/src/listcache.h b/src/listcache.h
new file mode 100644
index 0000000..732dd85
--- /dev/null
+++ b/src/listcache.h
@@ -0,0 +1,41 @@
+/*
+ * listcache.h
+ *
+ * Contact list caching
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef LISTCACHE_H
+#define LISTCACHE_H
+
+extern char *listcache_load(void);
+extern char *listcache_loadtag(void);
+extern void listcache_save(void);
+extern void listcache_invalidate();
+
+extern void listcache_settag(const char *tag);
+extern void listcache_setmfn(const char *mfn);
+extern const char *listcache_getmfn(void);
+
+extern void listcache_setcsm(const char *csm);
+extern char *listcache_getcsm(void);
+extern char *listcache_getcsm_noentities(void);
+
+#endif /* LISTCACHE_H */
diff --git a/src/listcleanup.c b/src/listcleanup.c
new file mode 100644
index 0000000..322ba0e
--- /dev/null
+++ b/src/listcleanup.c
@@ -0,0 +1,88 @@
+/*
+ * listcleanup.c
+ *
+ * Automatic list cleanup feature - e.g. remove unnecessary blocks
+ *
+ * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "contactlist.h"
+#include "debug.h"
+
+void listcleanup_open() {
+
+ ContactListIter *iter;
+ const char *username = "";
+
+ /* On FL but not RL (i.e. person doesn't like you) */
+ iter = contactlist_iter_new();
+ while ( username != NULL ) {
+ username = contactlist_getusername(iter, "FL");
+ if ( username != NULL ) {
+ if ( !contactlist_isonlist("RL", username) ) {
+ debug_print("LU: Hasn't added you: %s\n", username);
+ }
+ }
+ }
+ contactlist_iter_destroy(iter);
+
+ /* On RL but not FL (i.e. you're being rude) */
+ iter = contactlist_iter_new();
+ username = "";
+ while ( username != NULL ) {
+ username = contactlist_getusername(iter, "RL");
+ if ( username != NULL ) {
+ if ( !contactlist_isonlist("FL", username) ) {
+ debug_print("LU: You haven't added: %s\n", username);
+ }
+ }
+ }
+ contactlist_iter_destroy(iter);
+
+ /* On AL but not RL (i.e. pointless Allow) */
+ iter = contactlist_iter_new();
+ username = "";
+ while ( username != NULL ) {
+ username = contactlist_getusername(iter, "AL");
+ if ( username != NULL ) {
+ if ( !contactlist_isonlist("RL", username) ) {
+ debug_print("LU: Possible 'pointless allow': %s\n", username);
+ }
+ }
+ }
+ contactlist_iter_destroy(iter);
+
+ /* On BL but not RL (i.e. pointless block) */
+ iter = contactlist_iter_new();
+ username = "";
+ while ( username != NULL ) {
+ username = contactlist_getusername(iter, "BL");
+ if ( username != NULL ) {
+ if ( !contactlist_isonlist("RL", username) ) {
+ debug_print("LU: Possible 'pointless block': %s\n", username);
+ }
+ }
+ }
+ contactlist_iter_destroy(iter);
+
+}
diff --git a/src/listcleanup.h b/src/listcleanup.h
new file mode 100644
index 0000000..c982302
--- /dev/null
+++ b/src/listcleanup.h
@@ -0,0 +1,30 @@
+/*
+ * listcleanup.h
+ *
+ * Automatic list cleanup feature - e.g. remove unnecessary blocks
+ *
+ * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef LISTCLEANUP_H
+#define LISTCLEANUP_H
+
+extern void listcleanup_open();
+
+#endif /* LISTCLEANUP_H */
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..a49ba58
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,64 @@
+/*
+ * main.c
+ *
+ * The Top Level Source File
+ *
+ * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <signal.h>
+
+#include "mainwindow.h"
+#include "options.h"
+#include "debug.h"
+
+int main (int argc, char *argv[]) {
+
+ /* This makes it look so easy... */
+ gtk_init(&argc, &argv);
+ gtk_window_set_default_icon_from_file(DATADIR"/tuxmessenger/icon.png", NULL);
+
+ options_load();
+ mainwindow_open();
+ /* Prevent horrid sudden death */
+ signal(SIGPIPE, SIG_IGN);
+ gtk_main();
+
+ return 0;
+
+}
+
+char *tuxmessenger_versionstring() {
+
+#ifdef HAVE_CONFIG_H
+ return PACKAGE_NAME"/"VERSION;
+#else
+ return "TuxMessenger/unknown";
+#endif
+
+}
diff --git a/src/main.h b/src/main.h
new file mode 100644
index 0000000..aabbc33
--- /dev/null
+++ b/src/main.h
@@ -0,0 +1,34 @@
+/*
+ * main.h
+ *
+ * Not Very Much.
+ *
+ * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef MAIN_H
+#define MAIN_H
+
+extern char *tuxmessenger_versionstring();
+
+#endif /* MAIN_H */
diff --git a/src/mainwindow.c b/src/mainwindow.c
new file mode 100644
index 0000000..addb978
--- /dev/null
+++ b/src/mainwindow.c
@@ -0,0 +1,1383 @@
+/*
+ * mainwindow.c
+ *
+ * The main (contact list) window
+ *
+ * (c) 2002-2007 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include "options.h"
+#include "about.h"
+#include "prefswindow.h"
+#include "accountwindow.h"
+#include "mainwindow.h"
+#include "msnprotocol.h"
+#include "msngenerics.h"
+#include "routines.h"
+#include "sbsessions.h"
+#include "statusicons.h"
+#include "main.h"
+#include "debug.h"
+#include "listcache.h"
+#include "contactlist.h"
+#include "addcontact.h"
+#include "messagewindow.h"
+#include "error.h"
+#include "listcleanup.h"
+
+typedef enum {
+ MAINWINDOW_STATE_UNDEFINED,
+ MAINWINDOW_STATE_DISPATCH,
+ MAINWINDOW_STATE_ONLINE
+} MainWindowState;
+
+typedef enum {
+ LISTSORT_ACTIVITY,
+ LISTSORT_ALPHA
+} ContactListSorting;
+
+static struct {
+
+ /* Internal data */
+ MainWindowState state; /* A record of which state the window is in */
+ GtkUIManager *ui; /* UI manager */
+ int quitting; /* Whether or not to quit on disconnection. */
+ int was_menustatuschange; /* Locks to avoid comical looping */
+ int just_updating; /* when changing status. */
+
+ /* Common to both states */
+ GtkWidget *window; /* Overall window */
+ GtkWidget *swindow; /* Scrolled Window inside window */
+ GtkWidget *vbox; /* vbox inside swindow */
+ GtkWidget *top_hbox; /* hbox at top of window */
+ GtkWidget *bigvbox; /* vbox to put scrolled window and menu bar into */
+
+ /* For the dispatch state */
+ GtkWidget *signin_button; /* The Big Red Button */
+ char *signin_text; /* The text inside the big red button */
+
+ /* For the online state */
+ GtkWidget *online_vbox; /* vbox to hold online contacts plus "Online" heading */
+ GtkWidget *offline_vbox; /* vbox to hold offline contacts plus "Offline" heading */
+ GtkWidget *top_vbox; /* vbox to hold friendly name and CSM. */
+ GtkWidget *friendlyname; /* label to contain the user's current friendly name */
+ GtkWidget *fname_eventbox; /* eventbox to contain "friendlyname" */
+ GtkWidget *csm_entry; /* Text entry box when CSM is being changed. */
+ GtkWidget *csm; /* label to contain the user's current CSM */
+ GtkWidget *csm_eventbox; /* eventbox to contain "csm" */
+ GtkWidget *fname_entry; /* Text entry box when friendlyname is being changed. */
+ GtkWidget *setstatus; /* "Change online status" combo box */
+ GtkWidget *online_label; /* "Online" heading */
+ GtkWidget *offline_label; /* "Offline" heading */
+ GtkWidget *status_vbox; /* vbox to sort out size of status */
+ GtkActionGroup *action_group;
+
+ char *menu_subject;
+
+} mainwindow = {
+
+ MAINWINDOW_STATE_UNDEFINED,
+ NULL,
+ 0,
+ 0,
+ 0,
+
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+
+ NULL,
+ NULL,
+
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+
+};
+
+/* Internally-called function to end the program */
+static void mainwindow_destroyed() {
+ gtk_exit(0);
+}
+
+/* Internally-called function to start the sign-in process */
+static void mainwindow_signin() {
+
+ if (mainwindow.state != MAINWINDOW_STATE_DISPATCH) {
+ debug_print("MA: That'd just be silly.\n");
+ return;
+ }
+ mainwindow_setonline();
+ msnprotocol_connect("", 0);
+
+}
+
+/* Internally-called function to undo the effects of mainwindow_setonline */
+static void mainwindow_unsetonline() {
+
+ assert(mainwindow.state == MAINWINDOW_STATE_ONLINE);
+
+ addcontact_closeall();
+
+ gtk_widget_destroy(mainwindow.setstatus);
+ gtk_widget_destroy(mainwindow.online_label);
+ gtk_widget_destroy(mainwindow.offline_label);
+ gtk_widget_destroy(mainwindow.online_vbox);
+ gtk_widget_destroy(mainwindow.offline_vbox);
+ gtk_widget_destroy(mainwindow.status_vbox);
+ if ( mainwindow.friendlyname != NULL ) {
+ gtk_widget_destroy(mainwindow.friendlyname);
+ }
+ if ( mainwindow.fname_entry != NULL ) {
+ gtk_widget_destroy(mainwindow.fname_entry);
+ }
+ gtk_widget_destroy(mainwindow.fname_eventbox);
+ if ( mainwindow.csm != NULL ) {
+ gtk_widget_destroy(mainwindow.csm);
+ }
+ if ( mainwindow.csm_entry != NULL ) {
+ gtk_widget_destroy(mainwindow.csm_entry);
+ }
+ gtk_widget_destroy(mainwindow.csm_eventbox);
+ gtk_widget_destroy(mainwindow.top_vbox);
+
+ mainwindow.state = MAINWINDOW_STATE_UNDEFINED;
+
+ if ( mainwindow.quitting ) {
+ /* Bye Bye... */
+ gtk_widget_destroy(mainwindow.window);
+ }
+
+}
+
+/* Internally-called function to undo the effects of mainwindow_setdispatch */
+static void mainwindow_unsetdispatch() {
+
+ assert(mainwindow.state == MAINWINDOW_STATE_DISPATCH);
+
+ gtk_widget_destroy(mainwindow.signin_button);
+ free(mainwindow.signin_text);
+
+ mainwindow.state = MAINWINDOW_STATE_UNDEFINED;
+
+}
+
+static void mainwindow_fname_labelize() {
+
+ gfloat yalign;
+
+ gtk_widget_destroy(mainwindow.fname_entry);
+ mainwindow.fname_entry = NULL;
+ mainwindow.friendlyname = gtk_label_new("");
+ assert(mainwindow.friendlyname != NULL);
+ gtk_misc_get_alignment(GTK_MISC(mainwindow.friendlyname), NULL, &yalign);
+ gtk_misc_set_alignment(GTK_MISC(mainwindow.friendlyname), 0, yalign);
+ mainwindow_setmfn(listcache_getmfn());
+ gtk_container_add(GTK_CONTAINER(mainwindow.fname_eventbox), mainwindow.friendlyname);
+ gtk_widget_show(mainwindow.friendlyname);
+
+}
+
+static void mainwindow_csm_labelize() {
+
+ gfloat yalign;
+ char *csm;
+
+ gtk_widget_destroy(mainwindow.csm_entry);
+ mainwindow.csm_entry = NULL;
+ mainwindow.csm = gtk_label_new("");
+ assert(mainwindow.csm != NULL);
+ gtk_misc_get_alignment(GTK_MISC(mainwindow.csm), NULL, &yalign);
+ gtk_misc_set_alignment(GTK_MISC(mainwindow.csm), 0, yalign);
+
+ csm = listcache_getcsm();
+ mainwindow_setcsm(csm);
+ free(csm);
+
+ gtk_container_add(GTK_CONTAINER(mainwindow.csm_eventbox), mainwindow.csm);
+ gtk_widget_show(mainwindow.csm);
+
+}
+
+static void mainwindow_fname_activate() {
+
+ const char *new_mfn;
+
+ assert(mainwindow.fname_entry != NULL);
+ assert(mainwindow.friendlyname == NULL);
+
+ new_mfn = gtk_entry_get_text(GTK_ENTRY(mainwindow.fname_entry));
+ debug_print("MA: New friendly name: %s\n", new_mfn);
+
+ /* Switch back to a label... */
+ mainwindow_fname_labelize();
+
+ /* NOW change the name... currently the widget displays the old name, meaning
+ it'll still be accurate if the name change fails. */
+ msnprotocol_setmfn(new_mfn);
+
+}
+
+static void mainwindow_csm_activate() {
+
+ const char *new_csm;
+
+ assert(mainwindow.csm_entry != NULL);
+ assert(mainwindow.csm == NULL);
+
+ new_csm = gtk_entry_get_text(GTK_ENTRY(mainwindow.csm_entry));
+ debug_print("MA: New CSM: %s\n", new_csm);
+
+ msnprotocol_setcsm(new_csm);
+
+ /* Switch back to a label... */
+ mainwindow_csm_labelize();
+
+}
+
+static int mainwindow_fname_keypress(GtkWidget *widget, GdkEventKey *event) {
+
+ if ( event->keyval == GDK_Escape ) {
+ mainwindow_fname_labelize();
+ }
+
+ return 0;
+
+}
+
+static int mainwindow_csm_keypress(GtkWidget *widget, GdkEventKey *event) {
+
+ if ( event->keyval == GDK_Escape ) {
+ mainwindow_csm_labelize();
+ }
+
+ return 0;
+
+}
+
+void mainwindow_fname_startchange() {
+
+ const char *old_mfn;
+ char *mfn_decode;
+
+ if ( mainwindow.fname_entry != NULL ) {
+ /* Already editing! */
+ return;
+ }
+
+ if ( msnprotocol_signedin() != 1 ) {
+ /* Not ready. */
+ return;
+ }
+
+ old_mfn = listcache_getmfn();
+ mfn_decode = routines_urldecode(old_mfn);
+
+ /* Replace friendly name display with editable box. */
+ gtk_widget_destroy(mainwindow.friendlyname);
+ mainwindow.friendlyname = NULL;
+ mainwindow.fname_entry = gtk_entry_new();
+ assert(mainwindow.fname_entry != NULL);
+ gtk_entry_set_width_chars(GTK_ENTRY(mainwindow.fname_entry), strlen(mfn_decode));
+ gtk_entry_set_text(GTK_ENTRY(mainwindow.fname_entry), mfn_decode);
+ g_signal_connect(GTK_OBJECT(mainwindow.fname_entry), "activate", GTK_SIGNAL_FUNC(mainwindow_fname_activate), NULL);
+ g_signal_connect(GTK_OBJECT(mainwindow.fname_entry), "key-press-event", GTK_SIGNAL_FUNC(mainwindow_fname_keypress), NULL);
+ gtk_container_add(GTK_CONTAINER(mainwindow.fname_eventbox), mainwindow.fname_entry);
+ gtk_widget_show(mainwindow.fname_entry);
+
+ gtk_editable_set_position(GTK_EDITABLE(mainwindow.fname_entry), -1);
+ gtk_widget_grab_focus(mainwindow.fname_entry);
+
+ free(mfn_decode);
+
+}
+
+void mainwindow_csm_startchange() {
+
+ char *old_csm;
+
+ if ( mainwindow.csm_entry != NULL ) {
+ /* Already editing! */
+ return;
+ }
+
+ if ( msnprotocol_signedin() != 1 ) {
+ /* Not ready. */
+ return;
+ }
+
+ if ( !GTK_WIDGET_VISIBLE(mainwindow.csm_eventbox) ) {
+
+ /* CSM bar is hidden... better fix that first... */
+ GtkAction *action;
+
+ action = gtk_action_group_get_action(mainwindow.action_group, "ShowCSMAction");
+ if ( action != NULL ) {
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
+ gtk_widget_show(mainwindow.csm_eventbox);
+ } else {
+ debug_print("MA: Whoops! Couldn't find the Action to reveal the CSM widget.\n");
+ }
+
+ }
+
+ old_csm = listcache_getcsm_noentities();
+
+ /* Replace friendly name display with editable box. */
+ gtk_widget_destroy(mainwindow.csm);
+ mainwindow.csm = NULL;
+ mainwindow.csm_entry = gtk_entry_new();
+ assert(mainwindow.csm_entry != NULL);
+ gtk_entry_set_width_chars(GTK_ENTRY(mainwindow.csm_entry), strlen(old_csm));
+ gtk_entry_set_text(GTK_ENTRY(mainwindow.csm_entry), old_csm);
+ free(old_csm);
+ g_signal_connect(GTK_OBJECT(mainwindow.csm_entry), "activate", GTK_SIGNAL_FUNC(mainwindow_csm_activate), NULL);
+ g_signal_connect(GTK_OBJECT(mainwindow.csm_entry), "key-press-event", GTK_SIGNAL_FUNC(mainwindow_csm_keypress), NULL);
+ gtk_container_add(GTK_CONTAINER(mainwindow.csm_eventbox), mainwindow.csm_entry);
+ gtk_widget_show(mainwindow.csm_entry);
+
+ gtk_editable_set_position(GTK_EDITABLE(mainwindow.csm_entry), -1);
+ gtk_widget_grab_focus(mainwindow.csm_entry);
+
+}
+
+/* Entry point when local friendly name is clicked (to change it) */
+static void mainwindow_fname_clicked(GtkWidget *widget, GdkEventButton *button) {
+
+ if ( button->type != GDK_2BUTTON_PRESS ) {
+ return;
+ }
+
+ mainwindow_fname_startchange();
+
+}
+
+static void mainwindow_csm_clicked(GtkWidget *widget, GdkEventButton *button) {
+
+ if ( button->type != GDK_2BUTTON_PRESS ) {
+ return;
+ }
+
+ mainwindow_csm_startchange();
+
+}
+
+/* Called by src/msnprotocol.c's CHG handler to update the window. Normally, this won't change anything
+ because the user will just have set the same setting themself. The exception is at sign-in, when
+ the initial setting does not come from a direct click on the combo box. */
+void mainwindow_forcestatus(OnlineState status) {
+
+ int newstatus;
+
+ debug_print("MA: mainwindow_forcestatus: %i\n", status);
+
+ /* Order of items in this list MUST match those in mainwindow_setonline() */
+ switch ( status ) {
+
+ case ONLINE_NLN : newstatus = 0; break;
+ case ONLINE_AWY : newstatus = 1; break;
+ case ONLINE_BSY : newstatus = 2; break;
+ case ONLINE_BRB : newstatus = 3; break;
+ case ONLINE_PHN : newstatus = 4; break;
+ case ONLINE_LUN : newstatus = 5; break;
+ case ONLINE_HDN : newstatus = 6; break;
+ default : newstatus = -1; break; /* Should never happen */
+
+ }
+
+ gtk_combo_box_set_active(GTK_COMBO_BOX(mainwindow.setstatus), newstatus);
+
+}
+
+/* Combobox "changed" handler. */
+static int mainwindow_setstatus() {
+
+ int newstatus;
+ OnlineState status;
+
+ g_object_get(G_OBJECT(mainwindow.setstatus), "active", &newstatus, NULL);
+ debug_print("MA: mainwindow_setstatus: %i\n", newstatus);
+
+ /* Order of items in this list MUST match those in mainwindow_setonline() */
+ switch ( newstatus ) {
+
+ case 0 : status = ONLINE_NLN; break;
+ case 1 : status = ONLINE_AWY; break;
+ case 2 : status = ONLINE_BSY; break;
+ case 3 : status = ONLINE_BRB; break;
+ case 4 : status = ONLINE_PHN; break;
+ case 5 : status = ONLINE_LUN; break;
+ case 6 : status = ONLINE_HDN; break;
+ default : status = ONLINE_ERR; break;
+
+ }
+
+ msnprotocol_setstatus(status);
+
+ /* If this DIDN'T originate from the menu, update the menu. */
+ if ( mainwindow.was_menustatuschange ) {
+ debug_print("MA: Status change originated from menu.\n");
+ mainwindow.was_menustatuschange = 0;
+ } else {
+ GtkAction *action;
+ debug_print("MA: Updating menu. \n");
+ switch ( status ) {
+ case ONLINE_NLN : action = gtk_action_group_get_action(mainwindow.action_group, "StatusNLNAction"); break;
+ case ONLINE_AWY : action = gtk_action_group_get_action(mainwindow.action_group, "StatusAWYAction"); break;
+ case ONLINE_BSY : action = gtk_action_group_get_action(mainwindow.action_group, "StatusBSYAction"); break;
+ case ONLINE_BRB : action = gtk_action_group_get_action(mainwindow.action_group, "StatusBRBAction"); break;
+ case ONLINE_PHN : action = gtk_action_group_get_action(mainwindow.action_group, "StatusPHNAction"); break;
+ case ONLINE_LUN : action = gtk_action_group_get_action(mainwindow.action_group, "StatusLUNAction"); break;
+ case ONLINE_HDN : action = gtk_action_group_get_action(mainwindow.action_group, "StatusHDNAction"); break;
+ default : action = gtk_action_group_get_action(mainwindow.action_group, "StatusNLNAction"); break;
+ }
+ mainwindow.just_updating = 1;
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
+ }
+
+ return FALSE;
+
+}
+
+/* Put the main window into its main "online" state */
+void mainwindow_setonline() {
+
+ gfloat yalign;
+ GtkWidget *menuitem;
+ char *csm;
+
+ if (mainwindow.state == MAINWINDOW_STATE_DISPATCH) {
+ mainwindow_unsetdispatch();
+ } else if (mainwindow.state == MAINWINDOW_STATE_ONLINE) {
+ return; /* Nothing to do! */
+ }
+ assert(mainwindow.state == MAINWINDOW_STATE_UNDEFINED);
+
+ mainwindow.online_vbox = gtk_vbox_new(FALSE, 0);
+ mainwindow.offline_vbox = gtk_vbox_new(FALSE, 0);
+ mainwindow.top_vbox = gtk_vbox_new(FALSE, 0);
+ mainwindow.status_vbox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(mainwindow.top_hbox), mainwindow.top_vbox, TRUE, TRUE, 5);
+
+ mainwindow.fname_eventbox = gtk_event_box_new();
+ assert(mainwindow.fname_eventbox != NULL);
+ mainwindow.friendlyname = gtk_label_new("Connecting...");
+ gtk_widget_set_usize(GTK_WIDGET(mainwindow.friendlyname), 10, -1);
+ assert(mainwindow.friendlyname != NULL);
+ gtk_container_add(GTK_CONTAINER(mainwindow.fname_eventbox), mainwindow.friendlyname);
+ gtk_box_pack_start(GTK_BOX(mainwindow.top_vbox), mainwindow.fname_eventbox, TRUE, TRUE, 5);
+ gtk_misc_get_alignment(GTK_MISC(mainwindow.friendlyname), NULL, &yalign);
+ gtk_misc_set_alignment(GTK_MISC(mainwindow.friendlyname), 0, yalign);
+ g_signal_connect(GTK_OBJECT(mainwindow.fname_eventbox), "button-press-event", GTK_SIGNAL_FUNC(mainwindow_fname_clicked), NULL);
+
+ mainwindow.csm_eventbox = gtk_event_box_new();
+ assert(mainwindow.csm_eventbox != NULL);
+ mainwindow.csm = gtk_label_new(NULL);
+ gtk_widget_set_usize(GTK_WIDGET(mainwindow.csm), 10, -1);
+ assert(mainwindow.csm != NULL);
+ gtk_container_add(GTK_CONTAINER(mainwindow.csm_eventbox), mainwindow.csm);
+ gtk_box_pack_start(GTK_BOX(mainwindow.top_vbox), mainwindow.csm_eventbox, TRUE, TRUE, 5);
+ gtk_misc_get_alignment(GTK_MISC(mainwindow.csm), NULL, &yalign);
+ gtk_misc_set_alignment(GTK_MISC(mainwindow.csm), 0, yalign);
+ g_signal_connect(GTK_OBJECT(mainwindow.csm_eventbox), "button-press-event", GTK_SIGNAL_FUNC(mainwindow_csm_clicked), NULL);
+
+ csm = listcache_getcsm();
+ mainwindow_setcsm(csm);
+ free(csm);
+
+ mainwindow.setstatus = gtk_combo_box_new_text();
+ gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Online");
+ gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Away");
+ gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Busy");
+ gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Be Right Back");
+ gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "On the Phone");
+ gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Out to Lunch");
+ gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Appear Offline");
+ gtk_signal_connect_object(GTK_OBJECT(mainwindow.setstatus), "changed", GTK_SIGNAL_FUNC(mainwindow_setstatus), NULL);
+ gtk_box_pack_end(GTK_BOX(mainwindow.status_vbox), mainwindow.setstatus, TRUE, FALSE, 0);
+ gtk_box_pack_end(GTK_BOX(mainwindow.top_hbox), mainwindow.status_vbox, FALSE, FALSE, 0);
+
+ /* Add "Online" and "Offline" headings */
+ mainwindow.online_label = gtk_label_new("---- Online ----");
+ gtk_box_pack_start(GTK_BOX(mainwindow.online_vbox), mainwindow.online_label, FALSE, FALSE, 0);
+ mainwindow.offline_label = gtk_label_new("---- Offline ----");
+ gtk_box_pack_start(GTK_BOX(mainwindow.offline_vbox), mainwindow.offline_label, FALSE, FALSE, 0);
+
+ gtk_box_pack_start(GTK_BOX(mainwindow.vbox), mainwindow.online_vbox, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(mainwindow.vbox), mainwindow.offline_vbox, FALSE, FALSE, 0);
+ gtk_widget_set_usize(GTK_WIDGET(mainwindow.offline_vbox), 10, -1);
+ gtk_widget_set_usize(GTK_WIDGET(mainwindow.online_vbox), 10, -1);
+ gtk_widget_set_usize(GTK_WIDGET(mainwindow.offline_label), 10, -1);
+ gtk_widget_set_usize(GTK_WIDGET(mainwindow.online_label), 10, -1);
+
+ gtk_widget_show(mainwindow.online_label);
+ gtk_widget_show(mainwindow.offline_label);
+ gtk_widget_show(mainwindow.online_vbox);
+ if ( !options_hideoffline() ) {
+ gtk_widget_show(mainwindow.offline_vbox);
+ }
+ gtk_widget_show(mainwindow.friendlyname);
+ gtk_widget_show(mainwindow.setstatus);
+ gtk_widget_show(mainwindow.fname_eventbox);
+ if ( !options_hidecsm() ) {
+ gtk_widget_show(mainwindow.csm_eventbox);
+ }
+ gtk_widget_show(mainwindow.csm);
+ gtk_widget_show(mainwindow.top_vbox);
+ gtk_widget_show(mainwindow.status_vbox);
+
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_out");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_in");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/account");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/show_offline");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/show_csm");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_nln");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_awy");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_bsy");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_brb");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_phn");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_lun");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_hdn");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_name");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_csm");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_avatar");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/add_contact");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/list_cleanup");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+
+ mainwindow.state = MAINWINDOW_STATE_ONLINE;
+
+}
+
+void mainwindow_kickdispatch() {
+
+ const char *username;
+ username = options_username();
+
+ if ( mainwindow.state != MAINWINDOW_STATE_DISPATCH ) {
+ return;
+ }
+
+ if ( (strlen(username) != 0) && (strlen(options_password()) != 0) && (strlen(options_hostname()) != 0) ) {
+
+ GtkWidget *menuitem;
+
+ strcpy(mainwindow.signin_text, "Not signed in.\n\n");
+ strncat(mainwindow.signin_text, username, 200);
+ mainwindow.signin_text[255] = '\0';
+ strcat(mainwindow.signin_text, "\n\nClick here to connect");
+ mainwindow.signin_text[255] = '\0';
+ gtk_label_set_text(GTK_LABEL(GTK_BIN(mainwindow.signin_button)->child), mainwindow.signin_text);
+
+ gtk_widget_set_sensitive(mainwindow.signin_button, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_in");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+
+ } else {
+
+ GtkWidget *menuitem;
+
+ gtk_widget_set_sensitive(mainwindow.signin_button, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_in");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+
+ strcpy(mainwindow.signin_text, "Please configure your account details");
+ gtk_label_set_text(GTK_LABEL(GTK_BIN(mainwindow.signin_button)->child), mainwindow.signin_text);
+ /* If this is being called from accountwindow_apply(), this won't re-open the already-open window. */
+ accountwindow_open();
+
+ }
+
+}
+
+/* Set the main window up ready for sign in */
+void mainwindow_setdispatch() {
+
+ GtkWidget *menuitem;
+
+ if (mainwindow.state == MAINWINDOW_STATE_DISPATCH) {
+ return; /* Nothing to do! */
+ } else if (mainwindow.state == MAINWINDOW_STATE_ONLINE) {
+ mainwindow_unsetonline();
+ }
+ assert(mainwindow.state == MAINWINDOW_STATE_UNDEFINED);
+
+ /* The big "Sign In" button. */
+ mainwindow.signin_button = gtk_button_new_with_label("");
+ gtk_label_set_justify(GTK_LABEL(GTK_BIN(mainwindow.signin_button)->child), GTK_JUSTIFY_CENTER);
+ gtk_signal_connect(GTK_OBJECT(mainwindow.signin_button), "clicked", GTK_SIGNAL_FUNC(mainwindow_signin), NULL);
+
+ mainwindow.signin_text = malloc(256);
+ assert(mainwindow.signin_text != NULL);
+
+ gtk_box_pack_start(GTK_BOX(mainwindow.vbox), mainwindow.signin_button, TRUE, TRUE, 5);
+
+ GTK_WIDGET_SET_FLAGS(mainwindow.signin_button, GTK_CAN_DEFAULT);
+ gtk_widget_grab_default(mainwindow.signin_button);
+ gtk_widget_grab_focus(mainwindow.signin_button);
+
+ gtk_widget_show(mainwindow.signin_button);
+
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_out");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_in");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/account");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/show_offline");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/show_groups");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/show_csm");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/sort_alphabet");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/sort_activity");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_nln");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_awy");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_bsy");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_brb");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_phn");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_lun");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_hdn");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_name");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_csm");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_avatar");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/add_contact");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/blockallow_lists");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/reverse_list");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/list_cleanup");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+
+ /* This ensures that the subsequent change to ONLINE_HDN when signing in actually constitutes a change of the menu bar. */
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_action_group_get_action(mainwindow.action_group, "StatusNLNAction")), TRUE);
+
+ mainwindow.state = MAINWINDOW_STATE_DISPATCH;
+ mainwindow_kickdispatch();
+
+}
+
+static void mainwindow_addui_callback(GtkUIManager *ui, GtkWidget *widget, GtkContainer *container) {
+
+ gtk_box_pack_start(GTK_BOX(container), widget, FALSE, FALSE, 0);
+
+ /* Enable overflow menu if this is a toolbar */
+ if ( GTK_IS_TOOLBAR(widget) ) {
+ gtk_toolbar_set_show_arrow(GTK_TOOLBAR(widget), TRUE);
+ }
+
+}
+
+static void mainwindow_connection_signout() {
+
+ if ( !msnprotocol_disconnected() ) {
+ msnprotocol_setstatus(ONLINE_FLN);
+ } else {
+ debug_print("MA: Try signing in first.\n");
+ }
+
+}
+
+static int mainwindow_confirm_quit_response(GtkWidget *widget, int response) {
+
+ if ( response != GTK_RESPONSE_YES ) {
+ gtk_widget_destroy(widget);
+ return FALSE;
+ }
+
+ /* Sign out */
+ if ( msnprotocol_signedin() ) {
+ mainwindow.quitting = 1;
+ msnprotocol_setstatus(ONLINE_FLN);
+ } else {
+ gtk_widget_destroy(mainwindow.window);
+ }
+
+ return FALSE;
+
+}
+
+static int mainwindow_confirm_quit() {
+
+ GtkWidget *window;
+
+ window = gtk_message_dialog_new(GTK_WINDOW(mainwindow.window), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "Are you sure you want to quit TuxMessenger?");
+ g_signal_connect(GTK_DIALOG(window), "response", GTK_SIGNAL_FUNC(mainwindow_confirm_quit_response), NULL);
+ gtk_widget_show_all(window);
+
+ return TRUE;
+
+}
+
+static int mainwindow_connection_quit() {
+
+ mainwindow_confirm_quit();
+ return FALSE;
+
+}
+
+static int mainwindow_togglecsm(GtkWidget *widget) {
+
+ gboolean active;
+ GtkAction *action;
+
+ action = gtk_action_group_get_action(mainwindow.action_group, "ShowCSMAction");
+ assert(action != NULL);
+ active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+
+ if ( active ) {
+
+ /* Show CSM */
+ gtk_widget_show(mainwindow.csm_eventbox);
+ options_sethidecsm(FALSE);
+
+ } else {
+
+ /* Hide CSM - see also mainwindow_sethidecsm() above. */
+ if ( mainwindow.csm_entry != NULL ) {
+ mainwindow_csm_labelize();
+ }
+ gtk_widget_hide(mainwindow.csm_eventbox);
+ options_sethidecsm(TRUE);
+
+ }
+ options_save();
+
+ return FALSE;
+
+}
+
+static int mainwindow_toggleoffline(GtkWidget *widget) {
+
+ gboolean active;
+ GtkAction *action;
+
+ if ( !msnprotocol_signedin() ) {
+ debug_print("MA: Can't change that before signing in...\n");
+ return FALSE;
+ }
+
+ action = gtk_action_group_get_action(mainwindow.action_group, "ShowOfflineAction");
+ assert(action != NULL);
+ active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+
+ if ( active ) {
+ /* Show Offline Contacts */
+ gtk_widget_show(mainwindow.offline_vbox);
+ options_sethideoffline(FALSE);
+ } else {
+ /* Hide Offline Contacts */
+ gtk_widget_hide(mainwindow.offline_vbox);
+ options_sethideoffline(TRUE);
+ }
+ options_save();
+
+ return FALSE;
+
+}
+
+static int mainwindow_menubarstatuschange(GtkRadioAction *action, GtkRadioAction *current, gpointer data) {
+
+ debug_print("MA: mainwindow_menubarstatuschange\n");
+
+ if ( !msnprotocol_signedin() ) {
+ debug_print("MA: Not signed in. Ignoring fixup change.\n");
+ return FALSE;
+ }
+
+ if ( mainwindow.just_updating ) {
+ mainwindow.just_updating = 0;
+ debug_print("MA: Got callback for just updating menu bar.\n");
+ return FALSE;
+ }
+
+ debug_print("MA: Menu bar status change.\n");
+ mainwindow.was_menustatuschange = 1;
+ mainwindow.just_updating = 0;
+ mainwindow_forcestatus(gtk_radio_action_get_current_value(action));
+
+ return FALSE;
+
+}
+
+static gint mainwindow_deletecontact(GtkWidget *widget, gpointer data) {
+
+ printf("MA: Chosen to delete '%s'\n", mainwindow.menu_subject);
+
+ #if 0
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+ GtkWidget *icon;
+ AddContactWindow *item;
+
+ item = malloc(sizeof(AddContactWindow));
+
+ item->window = gtk_dialog_new_with_buttons("Add New Contact", mainwindow_gtkwindow(), GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ NULL);
+
+ g_signal_connect(item->window, "response", G_CALLBACK(addcontact_response), item);
+ g_signal_connect_swapped(item->window, "response", G_CALLBACK(gtk_widget_destroy), item->window);
+ g_signal_connect(item->window, "destroy", G_CALLBACK(addcontact_destroyed), item);
+
+ hbox = gtk_hbox_new(FALSE, 20);
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(item->window)->vbox), hbox);
+ icon = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start(GTK_BOX(hbox), icon, TRUE, TRUE, 0);
+
+ vbox = gtk_vbox_new(FALSE, 5);
+ gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
+
+ gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new("Enter the Passport address of your new contact:"), TRUE, TRUE, 0);
+
+ item->username_entry = gtk_entry_new();
+ gtk_box_pack_start(GTK_BOX(vbox), item->username_entry, TRUE, TRUE, 0);
+ g_signal_connect(item->username_entry, "activate", G_CALLBACK(addcontact_activate), item);
+
+ item->allow_toggle = gtk_check_button_new_with_label("Allow this new contact to message you.");
+ gtk_box_pack_start(GTK_BOX(vbox), item->allow_toggle, TRUE, TRUE, 0);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item->allow_toggle), TRUE);
+
+ gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
+ gtk_widget_show_all(item->window);
+ #endif
+
+ return 0;
+
+}
+
+static void mainwindow_addmenubar() {
+
+ GtkActionEntry entries[] = {
+
+ { "ConnectionAction", NULL, "_Connection", NULL, NULL, NULL },
+
+ #ifdef HAVE_GTK_2_6_0
+ { "SignInAction", GTK_STOCK_CONNECT, "_Sign In", NULL, NULL, G_CALLBACK(mainwindow_signin) },
+ { "SignOutAction", GTK_STOCK_DISCONNECT, "Sign _Out", NULL, NULL, G_CALLBACK(mainwindow_connection_signout) },
+ { "AboutAction", GTK_STOCK_ABOUT, "_About TuxMessenger", NULL, NULL, G_CALLBACK(about_open) },
+ #else /* HAVE_GTK_2_6_0 */
+ { "SignInAction", NULL, "_Sign In", NULL, NULL, G_CALLBACK(mainwindow_signin) },
+ { "SignOutAction", NULL, "Sign _Out", NULL, NULL, G_CALLBACK(mainwindow_connection_signout) },
+ { "AboutAction", NULL, "_About TuxMessenger", NULL, NULL, G_CALLBACK(about_open) },
+ #endif /* HAVE_GTK_2_6_0 */
+
+ { "AccountAction", GTK_STOCK_PROPERTIES, "_Account Details...", NULL, NULL, G_CALLBACK(accountwindow_open) },
+ { "QuitAction", GTK_STOCK_QUIT, NULL, "<control>Q", "Quit the application", G_CALLBACK(mainwindow_connection_quit) },
+
+ { "ViewAction", NULL, "_View", NULL, NULL, NULL },
+
+ { "StatusAction", NULL, "_Status", NULL, NULL, NULL },
+ { "ChangeNameAction", NULL, "Change Screen _Name...", NULL, NULL, G_CALLBACK(mainwindow_fname_startchange) },
+ { "ChangeCSMAction", NULL, "Change Custom _Message...", NULL, NULL, G_CALLBACK(mainwindow_csm_startchange) },
+ { "ChangeAvatarAction", NULL, "Change A_vatar...", NULL, NULL, NULL },
+
+ { "ToolsAction", NULL, "_Tools", NULL, NULL, NULL },
+ { "AddContactAction", GTK_STOCK_ADD, "_Add Contact...", NULL, NULL,G_CALLBACK(addcontact_open) },
+ { "BlockAllowAction", NULL, "_Block and Allow Lists", NULL, NULL, NULL },
+ { "ReverseAction", NULL, "_Reverse List", NULL, NULL, NULL },
+ { "PreferencesAction", GTK_STOCK_PREFERENCES, "_Preferences...", NULL, NULL, G_CALLBACK(prefswindow_open) },
+ { "ListCleanupAction", NULL, "_List Cleanup...", NULL, NULL, G_CALLBACK(listcleanup_open) },
+
+ { "HelpAction", NULL, "_Help", NULL, NULL, NULL },
+
+ { "ContactMessageAction", GTK_STOCK_NEW, "New _Message", NULL, NULL, NULL },
+ { "ContactSendFileAction", GTK_STOCK_OPEN, "Send _File", NULL, NULL, NULL },
+ { "ContactBlockAction", GTK_STOCK_NO, "_Block Contact", NULL, NULL, NULL },
+ { "ContactUnblockAction", GTK_STOCK_YES, "_Unblock Contact", NULL, NULL, NULL },
+ { "ContactDeleteAction", GTK_STOCK_DELETE, "_Delete Contact", NULL, NULL, G_CALLBACK(mainwindow_deletecontact) },
+
+ };
+ GtkToggleActionEntry toggles[] = {
+
+ { "ShowCSMAction", NULL, "Show _Custom Status Message", NULL, NULL, G_CALLBACK(mainwindow_togglecsm), !options_hidecsm() },
+ { "ShowOfflineAction", NULL, "Show _Offline Contacts", NULL, NULL, G_CALLBACK(mainwindow_toggleoffline), !options_hideoffline() },
+ { "ShowGroupsAction", NULL, "Show Contact _Groups", NULL, NULL, NULL, FALSE },
+
+ };
+ GtkRadioActionEntry radios_status[] = {
+
+ { "StatusNLNAction", NULL, "_Online", "<control>O", NULL, ONLINE_NLN },
+ { "StatusAWYAction", NULL, "_Away", "<control>A", NULL, ONLINE_AWY },
+ { "StatusBSYAction", NULL, "_Busy", NULL, NULL, ONLINE_BSY },
+ { "StatusBRBAction", NULL, "Be _Right Back", "<control>B", NULL, ONLINE_BRB },
+ { "StatusPHNAction", NULL, "On The _Phone", NULL, NULL, ONLINE_PHN },
+ { "StatusLUNAction", NULL, "Out To _Lunch", "<control>L", NULL, ONLINE_LUN },
+ { "StatusHDNAction", NULL, "Appear O_ffline", NULL, NULL, ONLINE_HDN },
+
+ };
+ GtkRadioActionEntry radios_sorting[] = {
+
+ { "SortAlphaAction", GTK_STOCK_SORT_ASCENDING, "Sort Contacts _Alphabetically", NULL, NULL, LISTSORT_ALPHA },
+ { "SortActiveAction", NULL, "_Sort Contacts by Activity", NULL, NULL, LISTSORT_ACTIVITY },
+
+ };
+
+ guint n_entries = G_N_ELEMENTS(entries);
+ guint n_toggles = G_N_ELEMENTS(toggles);
+ guint n_radios_status = G_N_ELEMENTS(radios_status);
+ guint n_radios_sorting = G_N_ELEMENTS(radios_sorting);
+ GError *error = NULL;
+
+ mainwindow.action_group = gtk_action_group_new("TuxMessenger");
+ gtk_action_group_add_actions(mainwindow.action_group, entries, n_entries, NULL);
+ gtk_action_group_add_toggle_actions(mainwindow.action_group, toggles, n_toggles, NULL);
+ gtk_action_group_add_radio_actions(mainwindow.action_group, radios_status, n_radios_status, ONLINE_NLN, G_CALLBACK(mainwindow_menubarstatuschange), NULL);
+ gtk_action_group_add_radio_actions(mainwindow.action_group, radios_sorting, n_radios_sorting, LISTSORT_ACTIVITY, NULL, NULL);
+ mainwindow.ui = gtk_ui_manager_new();
+ gtk_ui_manager_insert_action_group(mainwindow.ui, mainwindow.action_group, 0);
+ g_signal_connect(mainwindow.ui, "add_widget", G_CALLBACK(mainwindow_addui_callback), mainwindow.bigvbox);
+ gtk_widget_show(mainwindow.bigvbox);
+ if ( gtk_ui_manager_add_ui_from_file(mainwindow.ui, DATADIR"/tuxmessenger/mainwindow.ui", &error) == 0 ) {
+
+ /* :( */
+ debug_print("MA: Error loading main window menu bar: %s\n", error->message);
+ exit(1);
+
+ }
+
+ gtk_window_add_accel_group(GTK_WINDOW(mainwindow.window), gtk_ui_manager_get_accel_group(mainwindow.ui));
+ gtk_ui_manager_ensure_update(mainwindow.ui);
+
+}
+
+/* Create and open the main window, in dispatch state */
+void mainwindow_open() {
+
+ /* Create the main window. */
+ mainwindow.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_signal_connect(GTK_OBJECT(mainwindow.window), "destroy", GTK_SIGNAL_FUNC(mainwindow_destroyed), NULL);
+ mainwindow.bigvbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(mainwindow.window), mainwindow.bigvbox);
+
+ /* Set the title and size. */
+ gtk_window_set_title(GTK_WINDOW(mainwindow.window), "TuxMessenger");
+ gtk_window_set_default_size(GTK_WINDOW(mainwindow.window), 200, 500);
+
+ /* Add menu bar */
+ mainwindow_addmenubar();
+
+ /* Create a scrolled window to hold the contacts. */
+ mainwindow.swindow = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mainwindow.swindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+ gtk_box_pack_end(GTK_BOX(mainwindow.bigvbox), mainwindow.swindow, TRUE, TRUE, 0);
+
+ /* Structure common to both states */
+ mainwindow.vbox = gtk_vbox_new(FALSE, 0);
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(mainwindow.swindow), GTK_WIDGET(mainwindow.vbox));
+ mainwindow.top_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(mainwindow.vbox), mainwindow.top_hbox, FALSE, FALSE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(mainwindow.vbox), 6);
+
+ g_signal_connect(G_OBJECT(mainwindow.window), "delete-event", GTK_SIGNAL_FUNC(mainwindow_confirm_quit), NULL);
+ mainwindow.state = MAINWINDOW_STATE_UNDEFINED;
+ mainwindow_setdispatch();
+ gtk_widget_show_all(mainwindow.window);
+
+}
+
+/* Trivial function to add an accelerator group to the window, e.g. for the "statusmenu" */
+void mainwindow_addaccelgroup(GtkAccelGroup *accel_group) {
+ gtk_window_add_accel_group(GTK_WINDOW(mainwindow.window), accel_group);
+}
+
+static char *mainwindow_decodestatus(OnlineState status) {
+
+ if ( status == ONLINE_NLN ) {
+ return "";
+ } else if ( status == ONLINE_AWY ) {
+ return "[Away] ";
+ } else if ( status == ONLINE_IDL ) {
+ return "[Idle] ";
+ } else if ( status == ONLINE_BSY ) {
+ return "[Busy] ";
+ } else if ( status == ONLINE_BRB ) {
+ return "[BRB] ";
+ } else if ( status == ONLINE_PHN ) {
+ return "[Phone] ";
+ } else if ( status == ONLINE_LUN ) {
+ return "[Food] ";
+ }
+
+ return "[Whoops] ";
+
+}
+
+/* Called from src/msnprotocol.c to set the UI version of the local user's friendly name */
+void mainwindow_setmfn(const char *new_friendlyname) {
+
+ char *final_fname;
+ char *decoded_fname;
+
+ assert(new_friendlyname != NULL);
+ assert(mainwindow.friendlyname != NULL);
+
+ decoded_fname = routines_urldecode(new_friendlyname);
+
+ final_fname = malloc(strlen(new_friendlyname) + 11);
+ sprintf(final_fname, "––– %s", decoded_fname);
+ gtk_label_set_text(GTK_LABEL(mainwindow.friendlyname), final_fname);
+ free(final_fname);
+ free(decoded_fname);
+
+}
+
+void mainwindow_setcsm(const char *new_csm) {
+
+ char *csm_markup;
+ char *new_csm_edited;
+
+ assert(new_csm != NULL);
+ assert(mainwindow.csm != NULL);
+
+ csm_markup = malloc(strlen(new_csm)+50);
+ new_csm_edited = routines_killtriangles(new_csm);
+ sprintf(csm_markup, "<span foreground=\"#777777\" style=\"italic\">%s</span>", new_csm_edited);
+
+ gtk_label_set_markup(GTK_LABEL(mainwindow.csm), csm_markup);
+ free(new_csm_edited);
+ free(csm_markup);
+
+}
+
+static void mainwindow_adjustmenu_popup(GtkWidget *adjust_button, char *username, guint button, guint time) {
+
+ GtkWidget *menu;
+
+ menu = gtk_ui_manager_get_widget(mainwindow.ui, "/ui/contact");
+
+ /* It is conceivable that neither of these would be true */
+ if ( contactlist_isonlist("BL", username) ) {
+ GtkWidget *disable = gtk_ui_manager_get_widget(mainwindow.ui, "/ui/contact/block");
+ gtk_widget_set_sensitive(GTK_WIDGET(disable), FALSE);
+ }
+ if ( contactlist_isonlist("AL", username) ) {
+ GtkWidget *disable = gtk_ui_manager_get_widget(mainwindow.ui, "/ui/contact/unblock");
+ gtk_widget_set_sensitive(GTK_WIDGET(disable), FALSE);
+ }
+
+
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, button, time);
+ mainwindow.menu_subject = username;
+
+}
+
+/* Contact button "event" */
+static gint mainwindow_contact_event(GtkWidget *button, GdkEvent *event, char *username) {
+
+ if ( event->type == GDK_BUTTON_PRESS ) {
+ if ( ((GdkEventButton *)event)->button == 3 ) {
+ mainwindow_adjustmenu_popup(button, username, ((GdkEventButton *)event)->button, ((GdkEventButton *)event)->time);
+ }
+ }
+
+ return 0;
+
+}
+
+/* Contact button clicked */
+static gint mainwindow_contact_clicked(GtkWidget *button, char *username) {
+
+ if ( msnprotocol_status() == ONLINE_HDN ) {
+ error_report("You can't send messages while you are Invisible!");
+ return 0;
+ }
+
+ messagewindow_create_if_none(username, NULL);
+
+ return 0;
+
+}
+
+static void mainwindow_userdnd_get(GtkWidget *widget, GdkDragContext *drag_context, GtkSelectionData *seldata, guint info, guint time, unsigned char *username) {
+
+ debug_print("MA: DnD: sending '%s'\n", username);
+ gtk_selection_data_set(seldata, gdk_atom_intern("STRING", FALSE), 0, username, strlen((char *)username)+1);
+
+}
+
+/* Neat feature: drag one user onto another to create a three-way conversation with them both. */
+static void mainwindow_userdnd_receive(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *seldata, guint info, guint time, char *username1) {
+
+ char *username2;
+
+ username2 = (char *)seldata->data;
+
+ if ( msnprotocol_status() == ONLINE_HDN ) {
+ error_report("You can't send messages while you are Invisible!");
+ return;
+ }
+
+ if ( strcmp(username1, username2) == 0 ) {
+ debug_print("MA: Try just clicking...\n");
+ return;
+ }
+ debug_print("MA: Pairing '%s' and '%s'...\n", username1, username2);
+ messagewindow_create(username1, sbsessions_create_threeway(username1, username2));
+
+}
+
+
+/* Do the UI business part of adding a contact to the UI */
+static void mainwindow_addcontactui(UIContact *uicontact, char *username, char *friendlyname, OnlineState status) {
+
+ char *friendlynametext;
+ char *friendlynametext2;
+ char *final_fnametext;
+ GtkTargetEntry targets[1];
+ char *csmtext;
+ char *csmtext2;
+ char *tooltips_string;
+ char *csmtext_noentities;
+
+ assert(uicontact->hbox == NULL);
+ assert(uicontact->adjustbutton == NULL);
+ assert(uicontact->button == NULL);
+ assert(uicontact->label == NULL);
+ assert(uicontact->eventbox == NULL);
+ assert(uicontact->tooltips == NULL);
+
+ assert(status != ONLINE_HDN); /* Doesn't happen for contacts */
+
+ /* Create the button */
+ uicontact->button = gtk_button_new();
+ assert(uicontact->button != NULL);
+ GTK_WIDGET_UNSET_FLAGS(uicontact->button, GTK_CAN_FOCUS);
+ gtk_button_set_relief(GTK_BUTTON(uicontact->button), GTK_RELIEF_NONE);
+
+ uicontact->hbox = gtk_hbox_new(FALSE, 0);
+ assert(uicontact->hbox != NULL);
+ gtk_container_add(GTK_CONTAINER(uicontact->button), uicontact->hbox);
+
+ uicontact->pixmap = statusicons_pixmap(status);
+ gtk_box_pack_start(GTK_BOX(uicontact->hbox), uicontact->pixmap, FALSE, FALSE, 0);
+
+ /* Label containing status, friendly name and CSM */
+ friendlynametext = routines_urldecode(friendlyname);
+ friendlynametext2 = routines_killtriangles_and_ampersands(friendlynametext);
+ if ( status != ONLINE_FLN ) {
+
+ csmtext = contactlist_csm(username, 0);
+ csmtext_noentities = contactlist_csm(username, 1);
+ if ( csmtext == NULL ) {
+ csmtext = strdup("");
+ }
+ if ( csmtext_noentities == NULL ) {
+ csmtext_noentities = strdup("");
+ }
+ csmtext2 = routines_killtriangles(csmtext);
+ /* Up to eight characters of status indicator (five letters, two brackets, one space),
+ * 34 characters of status markup, 50 bytes of CSM markup, and one null terminator. */
+ final_fnametext = malloc(strlen(friendlynametext2) + strlen(csmtext2) + 8 + 34 + 50 + 1);
+ sprintf(final_fnametext, "<span foreground=\"#777777\">%s</span>%s<span foreground=\"#777777\" style=\"italic\"> %s</span>",
+ mainwindow_decodestatus(status), friendlynametext2, csmtext2);
+ tooltips_string = malloc(strlen(friendlynametext) + strlen(csmtext_noentities) + strlen(username) + 3);
+ if ( strlen(csmtext_noentities) > 0 ) {
+ sprintf(tooltips_string, "%s\n%s\n%s", friendlynametext, username, csmtext_noentities);
+ } else {
+ sprintf(tooltips_string, "%s\n%s", friendlynametext, username);
+ }
+ free(csmtext_noentities);
+ free(friendlynametext);
+ free(friendlynametext2);
+ free(csmtext);
+ uicontact->label = gtk_label_new(friendlynametext);
+ gtk_label_set_markup(GTK_LABEL(uicontact->label), final_fnametext);
+ gtk_misc_set_alignment(GTK_MISC(uicontact->label), 0, 0.5);
+ assert(uicontact->label != NULL);
+ free(final_fnametext);
+ free(csmtext2);
+
+ g_signal_connect(uicontact->button, "clicked", GTK_SIGNAL_FUNC(mainwindow_contact_clicked), (gpointer)username);
+
+ } else {
+
+ friendlynametext = routines_urldecode(friendlyname);
+ uicontact->label = gtk_label_new(friendlynametext);
+ assert(uicontact->label != NULL);
+ tooltips_string = malloc(strlen(friendlynametext) + strlen(username) + 2);
+ sprintf(tooltips_string, "%s\n%s", friendlynametext, username);
+ free(friendlynametext);
+ gtk_misc_set_alignment(GTK_MISC(uicontact->label), 0, 0.5);
+
+ }
+ gtk_box_pack_start(GTK_BOX(uicontact->hbox), uicontact->label, TRUE, TRUE, 3);
+
+ /* Packing and signal handling */
+ if ( status != ONLINE_FLN ) {
+ gtk_box_pack_end(GTK_BOX(mainwindow.online_vbox), uicontact->button, TRUE, FALSE, 0);
+ } else {
+ gtk_box_pack_end(GTK_BOX(mainwindow.offline_vbox), uicontact->button, TRUE, FALSE, 0);
+ }
+ g_signal_connect(uicontact->button, "event", GTK_SIGNAL_FUNC(mainwindow_contact_event), (gpointer)username);
+
+ /* Tooltips */
+ uicontact->tooltips = gtk_tooltips_new();
+ assert(uicontact->tooltips != NULL);
+ gtk_tooltips_set_tip(uicontact->tooltips, uicontact->button, tooltips_string, NULL);
+ free(tooltips_string);
+
+ /* Drag and Drop */
+ targets[0].target = "tm_username";
+ targets[0].flags = GTK_TARGET_SAME_APP;
+ targets[0].info = 1;
+ gtk_drag_source_set(uicontact->button, GDK_BUTTON1_MASK, targets, 1, GDK_ACTION_COPY);
+// gtk_drag_source_set_icon_pixbuf(uicontact->button, );
+ g_signal_connect(uicontact->button, "drag-data-get", GTK_SIGNAL_FUNC(mainwindow_userdnd_get), (gpointer)username);
+ /* Each button is also a DnD recipient... for pairing users in three-way conversations.. */
+ gtk_drag_dest_set(uicontact->button, GTK_DEST_DEFAULT_ALL, targets, 1, GDK_ACTION_COPY);
+ g_signal_connect(uicontact->button, "drag-data-received", GTK_SIGNAL_FUNC(mainwindow_userdnd_receive), (gpointer)username);
+
+ /* Sort out the size of the new item */
+ gtk_widget_set_usize(uicontact->hbox, 10, -1);
+
+ gtk_widget_show_all(uicontact->button);
+
+}
+
+/* Called from src/contactlist.c to add a contact (on the FL) to the list UI
+ * This function does the extra work of creating the UIContact structure. */
+UIContact *mainwindow_addcontact(char *username, char *friendlyname, OnlineState status) {
+
+ UIContact *uicontact;
+
+ uicontact = malloc(sizeof(UIContact));
+ assert(uicontact != NULL);
+
+ uicontact->hbox = NULL;
+ uicontact->adjustbutton = NULL;
+ uicontact->button = NULL;
+ uicontact->label = NULL;
+ uicontact->eventbox = NULL;
+ uicontact->tooltips = NULL;
+ uicontact->pixmap = NULL;
+
+ mainwindow_addcontactui(uicontact, username, friendlyname, status);
+
+ return uicontact;
+
+}
+
+/* Set a contact's online status to "status" */
+void mainwindow_setcontactstatus(UIContact *uicontact, char *username, char *friendlyname, OnlineState status) {
+
+ debug_print("MA: Setting contact status for %s.\n", username);
+ mainwindow_removecontact(uicontact);
+ mainwindow_addcontactui(uicontact, username, friendlyname, status);
+
+}
+
+void mainwindow_destroycontact(UIContact *uicontact) {
+
+ /* This assumes the contact widgets have already been destroyed */
+ free(uicontact);
+
+}
+
+/* Remove the UI parts of a contact (but leave the data structure intact) */
+void mainwindow_removecontact(UIContact *uicontact) {
+
+ debug_print("MA: Removing a UIContact\n");
+ if ( uicontact->button != NULL ) {
+ gtk_widget_destroy(uicontact->button);
+ }
+ uicontact->eventbox = NULL;
+ uicontact->hbox = NULL;
+ uicontact->adjustbutton = NULL;
+ uicontact->label = NULL;
+ uicontact->tooltips = NULL;
+ uicontact->button = NULL;
+ uicontact->pixmap = NULL;
+
+}
+
+/* Pass bits of information to src/pixmap.c so it can do its stuff */
+GtkStyle *mainwindow_style() {
+ return gtk_widget_get_style(mainwindow.window);
+}
+
+GdkWindow *mainwindow_window() {
+ return mainwindow.window->window;
+}
+
+GtkWindow *mainwindow_gtkwindow() {
+ return GTK_WINDOW(mainwindow.window);
+}
diff --git a/src/mainwindow.h b/src/mainwindow.h
new file mode 100644
index 0000000..971ca5f
--- /dev/null
+++ b/src/mainwindow.h
@@ -0,0 +1,61 @@
+/*
+ * mainwindow.h
+ *
+ * The main (contact list) window
+ *
+ * (c) 2002-2007 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include "msngenerics.h"
+
+#include <gtk/gtk.h>
+/* This is meant to be an opaque data type. Don't try anything clever... */
+typedef struct {
+
+ GtkWidget *hbox; /* Overall hbox */
+ GtkWidget *adjustbutton; /* Button with popup menu and icon */
+ GtkWidget *button; /* The main button itself (only if online) */
+ GtkWidget *label; /* Label containing the friendly name. */
+ GtkWidget *eventbox; /* Eventbox for tooltips (only if offline) */
+ GtkWidget *pixmap; /* Pixmap widget to go inside adjustbutton */
+ GtkTooltips *tooltips; /* Tooltip containing the username and other details */
+
+} UIContact;
+
+extern void mainwindow_open(void);
+extern void mainwindow_setdispatch(void);
+extern void mainwindow_setonline(void);
+extern void mainwindow_setmfn(const char *new_friendlyname);
+extern UIContact *mainwindow_addcontact(char *username, char *friendlyname, OnlineState status);
+extern void mainwindow_setcontactstatus(UIContact *uicontact, char *username, char *friendlyname, OnlineState status);
+extern void mainwindow_addaccelgroup(GtkAccelGroup *accel_group);
+extern void mainwindow_destroycontact(UIContact *uicontact);
+extern void mainwindow_removecontact(UIContact *uicontact);
+extern GtkStyle *mainwindow_style(void);
+extern GdkWindow *mainwindow_window(void);
+extern GtkWindow *mainwindow_gtkwindow(void);
+extern void mainwindow_fname_startchange(void);
+extern void mainwindow_kickdispatch(void);
+extern void mainwindow_forcestatus(OnlineState status);
+extern void mainwindow_setcsm(const char *new_csm);
+
+#endif /* MAINWINDOW_H */
diff --git a/src/messagewindow.c b/src/messagewindow.c
new file mode 100644
index 0000000..ae0326d
--- /dev/null
+++ b/src/messagewindow.c
@@ -0,0 +1,1854 @@
+/*
+ * messagewindow.c
+ *
+ * IM window
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <glob.h>
+#include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "debug.h"
+#include "sbsessions.h"
+#include "contactlist.h"
+#include "routines.h"
+#include "avatars.h"
+#include "sbprotocol.h"
+#include "msnp2p.h"
+#include "about.h"
+#include "fonttrans.h"
+#include "messagewindow.h"
+#include "options.h"
+#include "mime.h"
+#include "filetrans.h"
+
+typedef enum {
+ MESSAGEWINDOW_MODE_TEXT,
+ MESSAGEWINDOW_MODE_INK
+} MessageWindowMode;
+
+static MessageWindow *messagewindows;
+
+static MessageWindow *messagewindow_last() {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ while ( messagewindow ) {
+ assert(messagewindow != NULL);
+ if ( messagewindow->next == NULL ) {
+ return messagewindow;
+ } else {
+ messagewindow = messagewindow->next;
+ }
+ }
+
+ return NULL; /* If there were no messagewindows at all. */
+
+}
+
+void messagewindow_plug(MessageWindow *messagewindow, SbSession *session) {
+ messagewindow->session = session;
+}
+
+/* Unplug any message windows which are plugged into a given SB session */
+void messagewindow_unplug(SbSession *session) {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ while ( messagewindow != NULL ) {
+ if ( messagewindow->session == session ) {
+ messagewindow->session = NULL;
+ }
+ messagewindow = messagewindow->next;
+ }
+
+}
+
+static void messagewindow_destroy(MessageWindow *messagewindow) {
+
+ MessageWindow *prev;
+ MwUser *mwuser;
+
+ prev = messagewindows;
+ if ( prev != messagewindow ) {
+ while ( prev != NULL ) {
+ assert(prev != NULL);
+ if ( prev->next == messagewindow ) {
+ break;
+ } else {
+ prev = prev->next;
+ }
+ }
+ assert(prev->next == messagewindow);
+ /* Link it out of the list. */
+ prev->next = messagewindow->next;
+ } else {
+ /* This session was the first on the list. */
+ assert(messagewindows == messagewindow); /* Can't fail... */
+ messagewindows = messagewindow->next; /* Which may be NULL if the list is now empty. */
+ }
+
+ mwuser = messagewindow->users;
+ while ( mwuser != NULL ) {
+
+ MwUser *next_user = mwuser->next;
+ free(mwuser->username);
+ free(mwuser);
+ mwuser = next_user;
+
+ }
+
+ if ( messagewindow->localcolour_gdk != NULL ) {
+ gdk_color_free(messagewindow->localcolour_gdk);
+ }
+ if ( messagewindow->localcolour_string != NULL ) {
+ free(messagewindow->localcolour_string);
+ }
+ if ( messagewindow->ocolour_gdk != NULL ) {
+ gdk_color_free(messagewindow->ocolour_gdk);
+ }
+ if ( messagewindow->ocolour_string != NULL ) {
+ free(messagewindow->ocolour_string);
+ }
+ if ( messagewindow->localfont != NULL ) {
+ free(messagewindow->localfont);
+ }
+ if ( messagewindow->dislocalfont != NULL ) {
+ free(messagewindow->dislocalfont);
+ }
+ if ( messagewindow->ofont != NULL ) {
+ free(messagewindow->ofont);
+ }
+
+ free(messagewindow);
+
+}
+
+/* Find an IM window with exactly one given user. */
+static MessageWindow *messagewindow_find_single(const char *username) {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ while ( messagewindow != NULL ) {
+ if ( (messagewindow->num_users == 1) && (strcmp(messagewindow->users->username, username) == 0) ) {
+ return messagewindow;
+ }
+ messagewindow = messagewindow->next;
+ }
+
+ return NULL;
+
+}
+
+static MwUser *messagewindow_find_username(MessageWindow *messagewindow, const char *username) {
+
+ MwUser *mwuser;
+
+ assert(messagewindow != NULL);
+
+ mwuser = messagewindow->users;
+ while ( mwuser != NULL ) {
+
+ assert(mwuser != NULL);
+
+ /* Case-insensitive here. Username may have different case depending
+ on its source, since the servers seem to change usernames to
+ lower case but clients might not in (e.g.) TypingUser controls. */
+ if ( strcasecmp(mwuser->username, username) == 0 ) {
+ return mwuser;
+ } else {
+ mwuser = mwuser->next;
+ }
+
+ }
+
+ /* No users. */
+ return NULL;
+
+}
+
+static MessageWindow *messagewindow_find_user(MwUser *mwuser) {
+
+ MwUser *find;
+ MessageWindow *messagewindow;
+
+ assert(mwuser != NULL);
+ messagewindow = messagewindows;
+
+ while ( messagewindow != NULL ) {
+
+ find = messagewindow->users;
+ while ( find != NULL ) {
+
+ assert(find != NULL);
+
+ if ( find == mwuser ) {
+ return messagewindow;
+ } else {
+ find = find->next;
+ }
+
+ }
+
+ messagewindow = messagewindow->next;
+
+ }
+
+ /* Not found. */
+ return NULL;
+
+}
+
+static MwUser *messagewindow_lastuser(MessageWindow *messagewindow) {
+
+ MwUser *mwuser;
+
+ mwuser = messagewindow->users;
+ while ( mwuser != NULL ) {
+
+ assert(mwuser != NULL);
+
+ if ( mwuser->next == NULL ) {
+ return mwuser;
+ } else {
+ mwuser = mwuser->next;
+ }
+
+ }
+
+ /* No users. */
+ return NULL;
+
+}
+
+/* User clicked "Nudge" button. */
+static void messagewindow_sendnudge(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ if ( messagewindow->session == NULL ) {
+
+ debug_print("MW %8p: Not plugged in - sorting out.\n", messagewindow);
+ assert(messagewindow->num_users == 1);
+ assert(messagewindow->users != NULL);
+ messagewindow_plug(messagewindow, sbsessions_create_local(messagewindow->users->username));
+ sbsessions_plug(messagewindow->session, messagewindow);
+
+ }
+
+ messagewindow_addtext_system(messagewindow, "You nudge.");
+ sbprotocol_sendnudge(messagewindow->session);
+
+}
+
+static void messagewindow_scrolltoend(MessageWindow *messagewindow) {
+
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages));
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ gtk_text_iter_set_line_offset(&iter, 0);
+ gtk_text_buffer_move_mark(buffer, messagewindow->mark, &iter);
+ gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(messagewindow->messages), messagewindow->mark, 0, TRUE, 1.0, 0.0);
+
+}
+
+static void messagewindow_size_allocate(GtkWidget *widget, GtkAllocation *allocation, MessageWindow *messagewindow) {
+
+ if ( messagewindow->stuck ) {
+ messagewindow_scrolltoend(messagewindow);
+ }
+
+}
+
+static void messagewindow_scrolled(GtkAdjustment *adjustment, MessageWindow *messagewindow) {
+
+ gdouble pos;
+ gdouble limit;
+ gdouble pagesize;
+
+ /* See if the window is currently scrolled to the bottom. Make a note for later. */
+ pos = gtk_adjustment_get_value(adjustment);
+ g_object_get(G_OBJECT(adjustment), "upper", &limit, "page-size", &pagesize, NULL);
+
+ if ( pos == limit-pagesize ) {
+ messagewindow->stuck = TRUE;
+ } else {
+ messagewindow->stuck = FALSE;
+ }
+
+}
+
+/* Add "System" text to a window (like join/part messages) */
+static void messagewindow_addtext_system_nonewline(MessageWindow *messagewindow, const char *text) {
+
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages));
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, text, -1, "system", NULL);
+
+ if ( messagewindow->stuck ) {
+ messagewindow_scrolltoend(messagewindow);
+ }
+
+}
+
+/* Add "System" text to a window (like join/part messages), adding a newline if this isn't the first text. */
+void messagewindow_addtext_system(MessageWindow *messagewindow, const char *text) {
+
+ /* Add PRECEDING newline if this isn't the first event. */
+ if ( messagewindow->first_event == 0 ) {
+ messagewindow_addtext_system_nonewline(messagewindow, "\n\n");
+ } else {
+ messagewindow->first_event = 0;
+ }
+
+ messagewindow_addtext_system_nonewline(messagewindow, text);
+
+ /* Assume this is the case. If this WAS a NAK, the caller should set it again straight AFTERWARDS. */
+ messagewindow_set_last_was_nak(messagewindow, FALSE);
+
+}
+
+/* Add "User" text to a window (user messages) */
+void messagewindow_addtext_user_nonewline(MessageWindow *messagewindow, const char *text, int length, const char *colour, const char *font) {
+
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ GtkTextTag *tag;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages));
+
+ if ( colour != NULL ) {
+ if ( font != NULL ) {
+ tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, "foreground", colour, "font", font, NULL);
+ } else {
+ tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, "foreground", colour, NULL);
+ }
+ } else {
+ if ( font != NULL ) {
+ tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, "font", font, NULL);
+ } else {
+ tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, NULL);
+ }
+ }
+
+ /* Window should have been opened by now. */
+ assert(messagewindow != NULL);
+
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ gtk_text_buffer_insert_with_tags(buffer, &iter, text, length, tag, NULL);
+
+ if ( messagewindow->stuck ) {
+ messagewindow_scrolltoend(messagewindow);
+ }
+
+/* if ( GTK_WINDOW(record->message_window)->window_has_focus != 1 )
+ messagewindow_flash(record); */
+
+}
+
+/* This isn't needed, since user text is never preceded by a blank line
+static void messagewindow_addtext_user(MessageWindow *messagewindow, char *text) {
+
+ if ( messagewindow->first_event == 0 ) {
+ messagewindow_addtext_user_nonewline(messagewindow, "\n\n");
+ } else {
+ messagewindow->first_event = 0;
+ }
+
+ messagewindow_addtext_user_nonewline(messagewindow, text);
+
+} */
+
+void messagewindow_reportdropped(MessageWindow *messagewindow, char *message, size_t length) {
+
+ const char *mime;
+
+ debug_print("Dropping message: '%s'\n", message);
+
+ messagewindow_addtext_system(messagewindow, "The following message was not sent:\n");
+
+ mime = strstr(message, "MIME-Version:");
+ assert(mime != NULL);
+
+ messagewindow_addtext_user_nonewline(messagewindow, mime_getbody(mime), strlen(mime_getbody(mime)), NULL, NULL);
+
+}
+
+void messagewindow_leavemessage(MessageWindow *messagewindow, const char *username) {
+
+ char *text;
+ char *friendlyname_decoded;
+ const char *friendlyname;
+
+ friendlyname = contactlist_friendlyname(username);
+ friendlyname_decoded = routines_urldecode(friendlyname);
+
+ text = malloc(strlen(username) + strlen(friendlyname_decoded) + 4);
+ assert(text != NULL);
+
+ strcpy(text, friendlyname_decoded);
+ strcat(text, " (");
+ strcat(text, username);
+ strcat(text, ")");
+
+ messagewindow_addtext_system(messagewindow, "<-- Leave: ");
+ messagewindow_addtext_system_nonewline(messagewindow, text);
+
+ free(text);
+ free(friendlyname_decoded);
+
+}
+
+void messagewindow_removeuser(MessageWindow *messagewindow, const char *username) {
+
+ MwUser *find_user;
+ MwUser *mwuser = messagewindow_find_username(messagewindow, username);
+
+ if ( messagewindow->num_users > 1 ) {
+ messagewindow_leavemessage(messagewindow, username);
+ }
+
+ find_user = messagewindow->users;
+ if ( find_user == mwuser ) {
+ /* User was the first in the list. */
+ messagewindow->users = mwuser->next;
+ } else {
+
+ while ( find_user != NULL ) {
+
+ assert(find_user != NULL);
+
+ if ( find_user->next == mwuser ) {
+ find_user->next = mwuser->next;
+ break;
+ } else {
+ find_user = find_user->next;
+ }
+
+ }
+ }
+
+ messagewindow->num_users--;
+ free(mwuser->username);
+ gtk_widget_destroy(mwuser->bar_hbox);
+ gtk_widget_destroy(mwuser->avatar_eventbox);
+
+ free(mwuser);
+
+}
+
+static char *messagewindow_statusbarstring(const char *username) {
+
+ char *text;
+ char *friendlyname_decoded;
+ const char *friendlyname;
+
+ friendlyname = contactlist_friendlyname(username);
+ friendlyname_decoded = routines_urldecode(friendlyname);
+
+ text = malloc(strlen(username) + strlen(friendlyname_decoded) + 4);
+ assert(text != NULL);
+
+ strcpy(text, friendlyname_decoded);
+ strcat(text, " (");
+ strcat(text, username);
+ strcat(text, ")");
+ free(friendlyname_decoded);
+
+ return text;
+
+}
+
+/* Set "User is typing" status. */
+void messagewindow_typing(MessageWindow *messagewindow, const char *username) {
+
+ char *string;
+ char *bigstring;
+ MwUser *mwuser = messagewindow_find_username(messagewindow, username);
+
+ assert(mwuser != NULL);
+
+ string = messagewindow_statusbarstring(username);
+ bigstring = malloc(strlen(string) + 10);
+ strcpy(bigstring, "(Typing) ");
+ strcat(bigstring, string);
+
+ gtk_label_set_text(GTK_LABEL(mwuser->bar), bigstring);
+ free(bigstring);
+ free(string);
+
+}
+
+/* Put friendlyname and username in status bar. */
+void messagewindow_resetstatusbar(MessageWindow *messagewindow, const char *username) {
+
+ char *text;
+ MwUser *mwuser = messagewindow_find_username(messagewindow, username);
+
+ text = messagewindow_statusbarstring(username);
+ gtk_label_set_text(GTK_LABEL(mwuser->bar), text);
+ free(text);
+
+}
+
+static void messagewindow_settextmode(MessageWindow *messagewindow) {
+ gtk_widget_show(messagewindow->textbox_hbox);
+ gtk_widget_hide(messagewindow->gtk_ink);
+}
+
+static void messagewindow_setinkmode(MessageWindow *messagewindow) {
+ gtk_widget_hide(messagewindow->textbox_hbox);
+ gtk_widget_show(messagewindow->gtk_ink);
+}
+
+/* User clicked "Ink" button. */
+static void messagewindow_ink(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ GtkAction *action;
+
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) {
+ /* Ink mode on */
+ messagewindow_setinkmode(messagewindow);
+ action = gtk_action_group_get_action(messagewindow->action_group, "ModeInkAction");
+ } else {
+ /* Ink mode off */
+ messagewindow_settextmode(messagewindow);
+ action = gtk_action_group_get_action(messagewindow->action_group, "ModeTextAction");
+ }
+
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
+
+}
+
+static void messagewindow_setmode(GtkRadioAction *action, GtkRadioAction *current, MessageWindow *messagewindow) {
+
+ MessageWindowMode mode = gtk_radio_action_get_current_value(action);
+
+ if ( mode == MESSAGEWINDOW_MODE_TEXT ) {
+ messagewindow_settextmode(messagewindow);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(messagewindow->ink_button), FALSE);
+ } else {
+ messagewindow_setinkmode(messagewindow);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(messagewindow->ink_button), TRUE);
+ }
+
+}
+
+static void messagewindow_sendfilesel(GtkDialog *dialog, gint response, MessageWindow *messagewindow) {
+
+ if ( response == GTK_RESPONSE_ACCEPT ) {
+
+ char *filename;
+ unsigned int i;
+ MwUser *user;
+ char *usernames[messagewindow->num_users];
+
+ filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+ debug_print("Sending file '%s'\n", filename);
+
+ user = messagewindow->users;
+ for ( i=0; i<messagewindow->num_users; i++ ) {
+ usernames[i] = user->username;
+ user = user->next;
+ }
+ filetrans_offer_multiple(filename, usernames, messagewindow->num_users);
+
+ g_free(filename);
+
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+
+}
+
+/* User clicked "Send File" button. */
+static void messagewindow_sendfile(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ GtkWidget *dialog;
+
+ dialog = gtk_file_chooser_dialog_new("Send File", GTK_WINDOW(messagewindow->window), GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ g_signal_connect(G_OBJECT(dialog), "response", GTK_SIGNAL_FUNC(messagewindow_sendfilesel), messagewindow);
+
+
+ gtk_widget_show(dialog);
+
+}
+
+/* Called when it's time to send whatever's in the text entry widget of a message window. */
+static void messagewindow_send(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gchar *textblock;
+ GtkTextIter start;
+ GtkTextIter end;
+ GtkTextBuffer *buffer;
+ int length;
+ char *colour;
+ char *hash_colour;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->textbox));
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+
+ textblock = gtk_text_iter_get_text(&start, &end);
+ length = strlen(textblock);
+
+ if ( messagewindow->session == NULL ) {
+ messagewindow_plug(messagewindow, sbsessions_create_local(messagewindow->users->username));
+ sbsessions_plug(messagewindow->session, messagewindow);
+ }
+ /* Message might get cached for sending later. */
+ sbprotocol_send(messagewindow->session, textblock, length);
+
+ if ( strlen(textblock) > 0 ) {
+ messagewindow_addtext_system(messagewindow, "You say:\n");
+ if ( messagewindow->localcolour_string != NULL ) {
+ colour = routines_flipcolour(messagewindow->localcolour_string);
+ hash_colour = malloc(8);
+ strcpy(hash_colour, "#");
+ strncat(hash_colour, colour, 6);
+ hash_colour[7] = '\0';
+ messagewindow_addtext_user_nonewline(messagewindow, textblock, length, hash_colour, messagewindow->dislocalfont);
+ free(hash_colour);
+ free(colour);
+ } else {
+ messagewindow_addtext_user_nonewline(messagewindow, textblock, length, NULL, NULL);
+ }
+ }
+
+ /* Empty the text entry widget */
+ gtk_text_buffer_set_text(buffer, "", -1);
+ g_free(textblock);
+
+}
+
+void messagewindow_am_typing(MessageWindow *messagewindow) {
+
+ if ( messagewindow->session == NULL ) {
+
+ debug_print("MW %8p: Not plugged in - not sending TypingUser.\n", messagewindow);
+ if ( messagewindow->num_users == 1 ) {
+ assert(messagewindow->users != NULL);
+ messagewindow_plug(messagewindow, sbsessions_create_local(messagewindow->users->username));
+ sbsessions_plug(messagewindow->session, messagewindow);
+ }
+ return;
+
+ }
+
+ if ( sbsessions_sessionready(messagewindow->session) ) {
+ sbsessions_am_typing(messagewindow->session);
+ } else {
+ debug_print("MW %8p: Session %p isn't ready - not sending TypingUser.\n", messagewindow, messagewindow->session);
+ }
+
+}
+
+/* Called when a key is pressed inside the text entry widget of a message window. */
+static gint messagewindow_key(GtkWidget *ignore1, GdkEventKey *event, MessageWindow *messagewindow) {
+
+ if ( (event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter) ) {
+
+ /* Send the message */
+ messagewindow_send(NULL, messagewindow);
+ return TRUE; /* Don't process further. */
+
+ } else {
+
+ /* YUK */
+ if ( (event->keyval != GDK_Shift_L) && (event->keyval != GDK_Shift_R)
+ && (event->keyval != GDK_Control_L) && (event->keyval != GDK_Control_R)
+ && (event->keyval != GDK_Alt_L) && (event->keyval != GDK_Alt_R)
+ && (event->keyval != GDK_Meta_L) && (event->keyval != GDK_Meta_R)
+ && (event->keyval != GDK_Shift_L) && (event->keyval != GDK_Shift_R) ) {
+
+ messagewindow_am_typing(messagewindow);
+
+ }
+
+ }
+
+ return FALSE;
+
+}
+
+/* Create or update the picture associated with a user. username=NULL means the local user's picture. */
+static void messagewindow_newpicture(MessageWindow *messagewindow, const char *username, const char *filename) {
+
+ GtkWidget *pixmap_widget;
+ GdkPixbuf *pixbuf;
+ MwUser *user;
+ GtkTooltips *tooltip;
+ GtkWidget *event_box;
+
+ int local_image;
+ int changing_image;
+
+ user = NULL;
+ if ( username != NULL ) {
+
+ user = messagewindow_find_username(messagewindow, username);
+ if ( user == NULL ) {
+ debug_print("MW %8p: Couldn't find user data!\n", messagewindow);
+ return;
+ }
+
+ }
+
+ if ( username == NULL ) {
+ local_image = TRUE;
+ } else {
+ local_image = FALSE;
+ }
+
+ /* Get rid of any previous display picture we may have */
+ if ( local_image == FALSE ) {
+
+ if ( user->avatar != NULL ) {
+
+ gtk_widget_destroy(user->avatar);
+ gdk_pixbuf_unref(user->avatar_pixbuf);
+ changing_image = TRUE;
+ user->avatar = NULL;
+ user->avatar_pixbuf = NULL;
+
+ } else {
+ changing_image = FALSE;
+ }
+
+ } else {
+
+ if ( messagewindow->avatar != NULL ) {
+
+ gtk_widget_destroy(messagewindow->avatar);
+ gdk_pixbuf_unref(messagewindow->avatar_pixbuf);
+ messagewindow->avatar = NULL;
+ messagewindow->avatar_pixbuf = NULL;
+ changing_image = TRUE;
+
+ } else {
+ changing_image = FALSE;
+ }
+
+ }
+
+ pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+
+ if ( pixbuf == NULL ) {
+
+ debug_print("MW %8p: Couldn't load user display image - ", messagewindow);
+
+ /* Check if the picture's in the process of being downloaded... */
+ if ( !msnp2p_retrieving(filename) ) {
+
+ /* Delete the file, and re-download it... */
+ if ( remove(filename) == 0 ) {
+ debug_print("deleted it.\n");
+ messagewindow_picturekick(username);
+ return;
+ } else {
+ debug_print("failed to delete it, too. Noooo...\n");
+ return;
+ }
+
+ } else {
+ debug_print("download in progress. Not worrying...\n");
+ }
+
+ return;
+
+ }
+
+ pixmap_widget = gtk_image_new_from_pixbuf(pixbuf);
+
+ if ( changing_image == FALSE ) {
+ event_box = gtk_event_box_new();
+ } else {
+
+ if ( local_image == FALSE ) {
+ event_box = user->avatar_eventbox;
+ } else {
+ event_box = messagewindow->avatar_eventbox;
+ }
+
+ }
+
+ gtk_container_add(GTK_CONTAINER(event_box), pixmap_widget);
+
+ if ( changing_image == FALSE ) {
+
+ if ( username != NULL ) {
+ gtk_box_pack_start(GTK_BOX(messagewindow->picture_list), event_box, FALSE, FALSE, 0);
+ } else {
+ gtk_box_pack_end(GTK_BOX(messagewindow->picture_list), event_box, FALSE, FALSE, 0);
+ }
+
+ tooltip = gtk_tooltips_new();
+ gtk_tooltips_set_tip(tooltip, event_box, username, NULL);
+
+ }
+
+ if ( !local_image ) {
+
+ user->avatar_eventbox = event_box;
+ user->avatar = pixmap_widget;
+ user->avatar_pixbuf = pixbuf;
+
+ } else {
+
+ messagewindow->avatar_eventbox = event_box;
+ messagewindow->avatar = pixmap_widget;
+ messagewindow->avatar_pixbuf = pixbuf;
+
+ }
+
+ gtk_widget_show_all(event_box);
+
+}
+
+/* Decide what to do in order to eventually end up with an avatar for a remote user. */
+void messagewindow_trypicture(MessageWindow *messagewindow, const char *username) {
+
+ const char *dpobject;
+
+ dpobject = contactlist_haspicture(username);
+ if ( (dpobject != NULL) && (strlen(dpobject) > 0) ) {
+
+ char *avatar_filename;
+ avatar_filename = avatars_havepicture(dpobject);
+ if ( avatar_filename != NULL ) {
+
+ debug_print("MW %8p: Already have picture.\n", messagewindow);
+ messagewindow_newpicture(messagewindow, username, avatar_filename);
+
+ } else {
+
+ char *filename;
+
+ debug_print("MW %8p: Setting \"Wait\" picture.\n", messagewindow);
+ filename = avatars_default_fetching();
+ messagewindow_newpicture(messagewindow, username, filename);
+ free(filename);
+
+ if ( (messagewindow->session == NULL) && (!messagewindow->in_creation) ) {
+ messagewindow_plug(messagewindow, sbsessions_create_local(username));
+ sbsessions_plug(messagewindow->session, messagewindow);
+ } else {
+ if ( sbsessions_sessionready(messagewindow->session) ) {
+ msnp2p_getpicture(messagewindow->session, username, dpobject);
+ } else {
+ debug_print("MW %8p: Session isn't ready, but will be...\n", messagewindow);
+ }
+ }
+
+ }
+ free(avatar_filename);
+
+ } else {
+ char *avatars_blank = avatars_default_none();
+ debug_print("MW %8p: Setting \"Blank\" picture.\n", messagewindow);
+ messagewindow_newpicture(messagewindow, username, avatars_blank);
+ free(avatars_blank);
+ }
+
+}
+
+void messagewindow_localpicture(MessageWindow *messagewindow) {
+
+ char *filename;
+
+ filename = avatars_local();
+ if ( filename == NULL ) {
+ filename = DATADIR"/tuxmessenger/no_avatar.png";
+ messagewindow_newpicture(messagewindow, NULL, filename);
+ } else {
+ messagewindow_newpicture(messagewindow, NULL, filename);
+ free(filename);
+ }
+
+}
+
+/* Find all instances of the given user in all SB sessions, and reload the picture for them.
+ NULL means kick the local user's picture. */
+void messagewindow_picturekick(const char *username) {
+
+ MessageWindow *messagewindow;
+
+ messagewindow = messagewindows;
+ while ( messagewindow != NULL ) {
+
+ if ( username != NULL ) {
+
+ /* See if the user in question is in this session. */
+ MwUser *user = messagewindow_find_username(messagewindow, username);
+ if ( user != NULL ) {
+ messagewindow_trypicture(messagewindow, username);
+ }
+
+ } else {
+
+ messagewindow_localpicture(messagewindow);
+
+ }
+
+ messagewindow = messagewindow->next;
+
+ }
+
+}
+
+static int messagewindow_close(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gtk_widget_destroy(messagewindow->window);
+ return FALSE;
+
+}
+
+static int messagewindow_toggleavatars(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gboolean active;
+ GtkAction *action;
+
+ action = gtk_action_group_get_action(messagewindow->action_group, "AvatarsAction");
+ assert(action != NULL);
+ active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+
+ if ( active ) {
+ /* Show avatars */
+ gtk_widget_show(messagewindow->picture_list);
+ } else {
+ /* Hide avatars */
+ gtk_widget_hide(messagewindow->picture_list);
+ }
+
+ return FALSE;
+
+}
+
+static int messagewindow_toggleircstyle(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gboolean active;
+ GtkAction *action;
+
+ action = gtk_action_group_get_action(messagewindow->action_group, "IRCStyleAction");
+ assert(action != NULL);
+ active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+
+ if ( active ) {
+ /* Use IRC style */
+ messagewindow->ircstyle = TRUE;
+ } else {
+ /* Use normal style */
+ messagewindow->ircstyle = FALSE;
+ }
+
+ return FALSE;
+
+}
+
+static int messagewindow_toggletimestamps(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gboolean active;
+ GtkAction *action;
+
+ action = gtk_action_group_get_action(messagewindow->action_group, "TimeStampAction");
+ assert(action != NULL);
+ active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+
+ if ( active ) {
+ /* Show timestamps */
+ messagewindow->timestamps = TRUE;
+ } else {
+ /* Don't show timestamps */
+ messagewindow->timestamps = FALSE;
+ }
+
+ return FALSE;
+
+}
+
+static int messagewindow_toggleemoticons(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gboolean active;
+ GtkAction *action;
+
+ action = gtk_action_group_get_action(messagewindow->action_group, "EmoticonsAction");
+ assert(action != NULL);
+ active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+
+ if ( active ) {
+ /* Show emoticons */
+ messagewindow->showemoticons = TRUE;
+ } else {
+ /* Don't show emoticons */
+ messagewindow->showemoticons = FALSE;
+ }
+
+ return FALSE;
+
+}
+
+static void messagewindow_closed(MessageWindow *messagewindow) {
+
+ /* If this is a multi-user conversation, it'd be polite to at least try to leave it... */
+ if ( messagewindow->session != NULL ) {
+ if ( messagewindow->session->num_users > 1 ) {
+ debug_print("MW %8p: Closing a multi-way conversation. Sending OUT...\n", messagewindow);
+ sbprotocol_leavesession(messagewindow->session);
+ }
+ }
+
+ sbsessions_unplug(messagewindow);
+ messagewindow_destroy(messagewindow);
+
+}
+
+static void messagewindow_lcolsel(GtkWidget *colourbutton, MessageWindow *messagewindow) {
+
+ GdkColor colour;
+ char *string;
+
+ gtk_color_button_get_color(GTK_COLOR_BUTTON(colourbutton), &colour);
+
+ if ( messagewindow->localcolour_gdk != NULL ) {
+ gdk_color_free(messagewindow->localcolour_gdk);
+ }
+ if ( messagewindow->localcolour_string != NULL ) {
+ free(messagewindow->localcolour_string);
+ }
+
+ messagewindow->localcolour_gdk = gdk_color_copy(&colour);
+
+ /* Now work out the string version */
+ string = malloc(7);
+ /* Yukky BGR order instead of RGB */
+ snprintf(string, 7, "%02hhx%02hhx%02hhx", colour.blue >> 8, colour.green >> 8, colour.red >> 8);
+ messagewindow->localcolour_string = string;
+ debug_print("MW %8p: String value '%s'\n", messagewindow, string);
+
+}
+
+static void messagewindow_lfontsel(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ const char *font;
+
+ if ( messagewindow->dislocalfont != NULL ) {
+ free(messagewindow->dislocalfont);
+ }
+ if ( messagewindow->localfont != NULL ) {
+ free(messagewindow->localfont);
+ }
+ font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget));
+ messagewindow->dislocalfont = strdup(font);
+ messagewindow->localfont = fonttrans_font_to_format(font);
+
+}
+
+static void messagewindow_ofontsel(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ const char *font;
+
+ if ( messagewindow->ofont != NULL ) {
+ free(messagewindow->ofont);
+ }
+ font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget));
+ messagewindow->ofont = strdup(font);
+
+}
+
+static void messagewindow_ocolsel(GtkWidget *colourbutton, MessageWindow *messagewindow) {
+
+ GdkColor colour;
+
+ gtk_color_button_get_color(GTK_COLOR_BUTTON(colourbutton), &colour);
+
+ if ( messagewindow->ocolour_gdk != NULL ) {
+ gdk_color_free(messagewindow->ocolour_gdk);
+ }
+
+ if ( messagewindow->ocolour_string != NULL ) {
+ free(messagewindow->ocolour_string);
+ }
+ messagewindow->ocolour_gdk = gdk_color_copy(&colour);
+ messagewindow->ocolour_string = routines_gdk_to_hashrgb(&colour);
+
+}
+
+void messagewindow_fontsdialog_closed(GtkWidget *widget, MessageWindow *messagewindow) {
+ messagewindow->fontsdialog = NULL;
+}
+
+static void messagewindow_ofontoverride_toggle(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) {
+ messagewindow->ofontoverride = TRUE;
+ gtk_widget_set_sensitive(messagewindow->ofont_button, TRUE);
+ gtk_widget_set_sensitive(messagewindow->ocolour_button, TRUE);
+ } else {
+ messagewindow->ofontoverride = FALSE;
+ gtk_widget_set_sensitive(messagewindow->ofont_button, FALSE);
+ gtk_widget_set_sensitive(messagewindow->ocolour_button, FALSE);
+ }
+
+}
+
+void messagewindow_openfontsdialog(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ GtkWidget *dialog_box;
+
+ GtkWidget *fbox;
+ GtkWidget *font_label;
+ GtkWidget *font_label_justify;
+ GtkWidget *font_vbox;
+ GtkWidget *font_hbox;
+ GtkWidget *font_box;
+ GtkWidget *font_button;
+ GtkWidget *colour_button;
+ GtkWidget *nbox;
+ GtkWidget *ofont_label;
+ GtkWidget *ofont_label_justify;
+ GtkWidget *ofont_vbox;
+ GtkWidget *ofont_hbox;
+ GtkWidget *ofont_override;
+ GtkWidget *ofont_box;
+
+ if ( messagewindow->fontsdialog != NULL ) {
+ return;
+ }
+
+ messagewindow->fontsdialog = gtk_dialog_new_with_buttons("Message Fonts", GTK_WINDOW(messagewindow->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL);
+ dialog_box = GTK_DIALOG(messagewindow->fontsdialog)->vbox;
+
+ fbox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(dialog_box), fbox, FALSE, FALSE, 12);
+ font_label = gtk_label_new("");
+ font_label_justify = gtk_hbox_new(FALSE, 0);
+ gtk_label_set_markup(GTK_LABEL(font_label), "<span weight=\"bold\">Font and Colour for Your Messages</span>");
+ gtk_box_pack_start(GTK_BOX(font_label_justify), font_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(fbox), font_label_justify, FALSE, FALSE, 6);
+
+ font_vbox = gtk_vbox_new(FALSE, 0);
+ font_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(fbox), font_hbox, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(font_hbox), font_vbox, FALSE, FALSE, 12);
+
+ font_box = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(font_vbox), font_box, FALSE, FALSE, 0);
+ if ( messagewindow->dislocalfont == NULL ) {
+ font_button = gtk_font_button_new();
+ } else {
+ font_button = gtk_font_button_new_with_font(messagewindow->dislocalfont);
+ }
+ g_signal_connect(G_OBJECT(font_button), "font-set", GTK_SIGNAL_FUNC(messagewindow_lfontsel), messagewindow);
+ gtk_box_pack_start(GTK_BOX(font_box), font_button, FALSE, FALSE, 6);
+ if ( messagewindow->localcolour_gdk == NULL ) {
+ colour_button = gtk_color_button_new();
+ } else {
+ colour_button = gtk_color_button_new_with_color(messagewindow->localcolour_gdk);
+ }
+ g_signal_connect(G_OBJECT(colour_button), "color-set", GTK_SIGNAL_FUNC(messagewindow_lcolsel), messagewindow);
+ gtk_box_pack_start(GTK_BOX(font_box), colour_button, FALSE, FALSE, 6);
+
+ nbox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(dialog_box), nbox, FALSE, FALSE, 12);
+ ofont_label = gtk_label_new("");
+ ofont_label_justify = gtk_hbox_new(FALSE, 0);
+ gtk_label_set_markup(GTK_LABEL(ofont_label), "<span weight=\"bold\">Fonts and Colours for Contacts' Messages</span>");
+ gtk_box_pack_start(GTK_BOX(ofont_label_justify), ofont_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(nbox), ofont_label_justify, FALSE, FALSE, 0);
+
+ ofont_vbox = gtk_vbox_new(FALSE, 0);
+ ofont_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(nbox), ofont_hbox, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(ofont_hbox), ofont_vbox, FALSE, FALSE, 12);
+
+ ofont_override = gtk_check_button_new_with_label("Override contacts' chosen fonts");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ofont_override), messagewindow->ofontoverride);
+ g_signal_connect(G_OBJECT(ofont_override), "toggled", GTK_SIGNAL_FUNC(messagewindow_ofontoverride_toggle), messagewindow);
+ gtk_box_pack_start(GTK_BOX(ofont_vbox), ofont_override, FALSE, FALSE, 6);
+ ofont_box = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(ofont_vbox), ofont_box, FALSE, FALSE, 0);
+ if ( messagewindow->ofont ) {
+ messagewindow->ofont_button = gtk_font_button_new_with_font(messagewindow->ofont);
+ } else {
+ messagewindow->ofont_button = gtk_font_button_new();
+ }
+ g_signal_connect(G_OBJECT(messagewindow->ofont_button), "font-set", GTK_SIGNAL_FUNC(messagewindow_ofontsel), messagewindow);
+ gtk_box_pack_start(GTK_BOX(ofont_box), messagewindow->ofont_button, FALSE, FALSE, 6);
+ if ( messagewindow->ocolour_gdk == NULL ) {
+ messagewindow->ocolour_button = gtk_color_button_new();
+ } else {
+ messagewindow->ocolour_button = gtk_color_button_new_with_color(messagewindow->ocolour_gdk);
+ }
+ g_signal_connect(G_OBJECT(messagewindow->ocolour_button), "color-set", GTK_SIGNAL_FUNC(messagewindow_ocolsel), messagewindow);
+ gtk_box_pack_start(GTK_BOX(ofont_box), messagewindow->ocolour_button, FALSE, FALSE, 6);
+ messagewindow_ofontoverride_toggle(ofont_override, messagewindow);
+
+ g_signal_connect(G_OBJECT(messagewindow->fontsdialog), "destroy", GTK_SIGNAL_FUNC(messagewindow_fontsdialog_closed), messagewindow);
+ g_signal_connect(G_OBJECT(messagewindow->fontsdialog), "response", GTK_SIGNAL_FUNC(gtk_widget_destroy), NULL);
+ gtk_widget_show_all(messagewindow->fontsdialog);
+
+}
+
+static void messagewindow_userdnd_receive(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *seldata, guint info, guint time, MessageWindow *messagewindow) {
+
+ char *username;
+
+ username = seldata->data;
+
+ if ( messagewindow_find_username(messagewindow, username) != NULL ) {
+ debug_print("MW %8p: '%s' is already in this message window.\n", messagewindow, username);
+ return;
+ }
+
+ if ( messagewindow->session != NULL ) {
+ debug_print("MW %8p: Inviting '%s'...\n", messagewindow, username);
+ sbprotocol_invite(messagewindow->session, username);
+ } else {
+ assert(messagewindow->num_users == 1);
+ debug_print("MW %8p: Creating session and inviting '%s' and '%s'...\n", messagewindow, messagewindow->users->username, username);
+ messagewindow->session = sbsessions_create_threeway(messagewindow->users->username, username);
+ }
+
+}
+
+static void messagewindow_addui_callback(GtkUIManager *ui, GtkWidget *widget, GtkContainer *container) {
+
+ gtk_box_pack_start(GTK_BOX(container), widget, FALSE, FALSE, 0);
+
+ /* Enable overflow menu if this is a toolbar */
+ if ( GTK_IS_TOOLBAR(widget) ) {
+ gtk_toolbar_set_show_arrow(GTK_TOOLBAR(widget), TRUE);
+ }
+
+}
+
+static void messagewindow_addmenubar(MessageWindow *messagewindow) {
+
+ GtkActionEntry entries[] = {
+
+ { "ConversationAction", NULL, "_Conversation", NULL, NULL, NULL },
+ { "InviteAction", GTK_STOCK_ADD, "_Invite New User...", NULL, NULL, NULL },
+ { "CloseAction", GTK_STOCK_CLOSE, "_Close", NULL, NULL, G_CALLBACK(messagewindow_close) },
+
+ { "ViewAction", NULL, "_View", NULL, NULL, NULL },
+ { "FontAction", GTK_STOCK_SELECT_FONT, "_Set Fonts and Colours...", NULL, NULL, G_CALLBACK(messagewindow_openfontsdialog) },
+ /* More in toggles[] */
+
+ { "ToolsAction", NULL, "_Tools", NULL, NULL, NULL },
+ { "BlockAction", GTK_STOCK_NO, "_Block User", NULL, NULL, NULL },
+ { "NudgeAction", NULL, "_Nudge", NULL, NULL, G_CALLBACK(messagewindow_sendnudge) },
+ #ifdef HAVE_GTK_2_6_0
+ { "SendFileAction", GTK_STOCK_FILE, "Send a _File...", NULL, NULL, NULL },
+ { "AboutAction", GTK_STOCK_ABOUT, "_About TuxMessenger", NULL, NULL, G_CALLBACK(about_open) },
+ #else /* HAVE_GTK_2_6_0 */
+ { "SendFileAction", NULL, "Send a _File...", NULL, NULL, NULL },
+ { "AboutAction", NULL, "_About TuxMessenger", NULL, NULL, G_CALLBACK(about_open) },
+ #endif /* HAVE_GTK_2_6_0 */
+
+ { "HelpAction", NULL, "_Help", NULL, NULL, NULL },
+
+ };
+ GtkToggleActionEntry toggles[] = {
+
+ { "AvatarsAction", NULL, "Display _Avatars", NULL, NULL, G_CALLBACK(messagewindow_toggleavatars), TRUE },
+ { "IRCStyleAction", NULL, "_IRC Style", NULL, NULL, G_CALLBACK(messagewindow_toggleircstyle), FALSE },
+ { "TimeStampAction", NULL, "_Timestamp Messages", NULL, NULL, G_CALLBACK(messagewindow_toggletimestamps), FALSE },
+ { "EmoticonsAction", NULL, "Enable _Emoticons", NULL, NULL, G_CALLBACK(messagewindow_toggleemoticons), FALSE },
+
+ };
+ GtkRadioActionEntry radios_mode[] = {
+
+ { "ModeTextAction", NULL, "_Text Mode", NULL, NULL, MESSAGEWINDOW_MODE_TEXT },
+ { "ModeInkAction", NULL, "In_k Mode", NULL, NULL, MESSAGEWINDOW_MODE_INK },
+
+ };
+
+ guint n_entries = G_N_ELEMENTS(entries);
+ guint n_toggles = G_N_ELEMENTS(toggles);
+ guint n_radios_mode = G_N_ELEMENTS(radios_mode);
+ GError *error = NULL;
+
+ messagewindow->action_group = gtk_action_group_new("TuxMessengerIMWindow");
+ gtk_action_group_add_actions(messagewindow->action_group, entries, n_entries, messagewindow);
+ gtk_action_group_add_toggle_actions(messagewindow->action_group, toggles, n_toggles, messagewindow);
+ gtk_action_group_add_radio_actions(messagewindow->action_group, radios_mode, n_radios_mode, MESSAGEWINDOW_MODE_TEXT, G_CALLBACK(messagewindow_setmode), messagewindow);
+
+ messagewindow->ui = gtk_ui_manager_new();
+ gtk_ui_manager_insert_action_group(messagewindow->ui, messagewindow->action_group, 0);
+ g_signal_connect(messagewindow->ui, "add_widget", G_CALLBACK(messagewindow_addui_callback), messagewindow->bigvbox);
+ if ( gtk_ui_manager_add_ui_from_file(messagewindow->ui, DATADIR"/tuxmessenger/imwindow.ui", &error) == 0 ) {
+ debug_print("MW %8p: Error loading message window menu bar: %s\n", messagewindow, error->message);
+ return;
+ }
+
+ gtk_window_add_accel_group(GTK_WINDOW(messagewindow->window), gtk_ui_manager_get_accel_group(messagewindow->ui));
+ gtk_ui_manager_ensure_update(messagewindow->ui);
+
+}
+
+static MwUser *messagewindow_adduser(MessageWindow *messagewindow, const char *username) {
+
+ MwUser *mwuser;
+ MwUser *previous_user;
+
+ mwuser = malloc(sizeof(MwUser));
+
+ mwuser->username = strdup(username);
+
+ mwuser->avatar_eventbox = NULL;
+ mwuser->avatar = NULL;
+ mwuser->avatar_pixbuf = NULL;
+ mwuser->bar = gtk_label_new("");
+ mwuser->bar_hbox = gtk_hbox_new(FALSE, 0);
+ mwuser->typing_callback = 0;
+
+ gtk_widget_set_usize(mwuser->bar_hbox, 10, -1);
+ gtk_box_pack_start(GTK_BOX(mwuser->bar_hbox), mwuser->bar, FALSE, FALSE, 0);
+ gtk_box_pack_end(GTK_BOX(messagewindow->windowbox), mwuser->bar_hbox, FALSE, TRUE, 2);
+
+ gtk_widget_show(mwuser->bar);
+ gtk_widget_show(mwuser->bar_hbox);
+
+ previous_user = messagewindow_lastuser(messagewindow);
+ mwuser->next = NULL;
+ if ( previous_user == NULL ) {
+ messagewindow->users = mwuser;
+ } else {
+ previous_user->next = mwuser;
+ }
+
+ messagewindow->num_users++;
+
+ messagewindow_resetstatusbar(messagewindow, username);
+ messagewindow_trypicture(messagewindow, username);
+
+ return mwuser;
+
+}
+
+int messagewindow_get_last_was_nak(MessageWindow *messagewindow) {
+ return messagewindow->last_was_nak;
+}
+
+void messagewindow_set_last_was_nak(MessageWindow *messagewindow, int last_was_nak) {
+ messagewindow->last_was_nak = last_was_nak;
+}
+
+MessageWindow *messagewindow_create(const char *username, SbSession *session) {
+
+ MessageWindow *messagewindow;
+
+ GtkWidget *messagelist_hbox;
+ GtkWidget *messagelist_scroll;
+ GtkWidget *textbox_scroll;
+ GtkWidget *textbox_hbox;
+ GtkWidget *button_box;
+ GtkWidget *picture_hbox;
+ GtkAdjustment *adjustment;
+
+ GtkWidget *send_button;
+ GtkWidget *sendfile_button;
+ GtkWidget *nudge_button;
+ GdkColor bgcolour;
+ GdkColor fgcolour;
+
+ GdkColor colour;
+ GtkTextBuffer *buffer;
+ GtkTextIter start;
+ GtkTextIter end;
+ GtkTextIter iter;
+ char *friendlyname_decoded;
+ GtkTargetEntry targets[1];
+
+ MessageWindow *last_messagewindow;
+
+ messagewindow = malloc(sizeof(MessageWindow));
+ messagewindow->users = NULL;
+ messagewindow->num_users = 0;
+ messagewindow->avatar = NULL;
+ messagewindow->avatar_eventbox = NULL;
+ messagewindow->avatar_pixbuf = NULL;
+ messagewindow->session = NULL;
+ messagewindow->in_creation = TRUE;
+ messagewindow->first_event = TRUE;
+ messagewindow->fontsdialog = NULL;
+ messagewindow->stuck = TRUE;
+
+ if ( options_localcolour_gdk() != NULL ) {
+ messagewindow->localcolour_gdk = gdk_color_copy(options_localcolour_gdk());
+ } else {
+ messagewindow->localcolour_gdk = NULL;
+ }
+ if ( options_localcolour_string() != NULL ) {
+ messagewindow->localcolour_string = strdup(options_localcolour_string());
+ } else {
+ messagewindow->localcolour_string = NULL;
+ }
+ if ( options_ocolour_gdk() != NULL ) {
+ messagewindow->ocolour_gdk = gdk_color_copy(options_ocolour_gdk());
+ messagewindow->ocolour_string = routines_gdk_to_hashrgb(messagewindow->ocolour_gdk);
+ } else {
+ messagewindow->ocolour_gdk = NULL;
+ messagewindow->ocolour_string = NULL;
+ }
+ if ( options_localfont() != NULL ) {
+ messagewindow->localfont = fonttrans_font_to_format(options_localfont());
+ messagewindow->dislocalfont = strdup(options_localfont());
+ } else {
+ messagewindow->localfont = NULL;
+ messagewindow->dislocalfont = NULL;
+ }
+ if ( options_ofont() != NULL ) {
+ messagewindow->ofont = strdup(options_ofont());
+ } else {
+ messagewindow->ofont = NULL;
+ }
+ messagewindow->ofontoverride = options_ofontoverride();
+ messagewindow->ircstyle = options_ircstyle();
+ messagewindow->timestamps = options_timestamps();
+ messagewindow->showemoticons = options_showemoticons();
+ messagewindow->last_was_nak = FALSE;
+
+ debug_print("MW %8p: Created for '%s'\n", messagewindow, username);
+
+ /* Prepare the window. */
+ messagewindow->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_signal_connect_object(GTK_OBJECT(messagewindow->window), "destroy", GTK_SIGNAL_FUNC(messagewindow_closed), (gpointer)messagewindow);
+ gtk_window_set_default_size(GTK_WINDOW(messagewindow->window), 360, 340);
+
+ /* Set window title. */
+ friendlyname_decoded = routines_urldecode(contactlist_friendlyname(username));
+ gtk_window_set_title(GTK_WINDOW(messagewindow->window), friendlyname_decoded);
+ free(friendlyname_decoded);
+
+ /* Top-level structure. */
+ messagewindow->bigvbox = gtk_vbox_new(FALSE, 0);
+ messagewindow->windowbox = gtk_vbox_new(FALSE, 0);
+ gtk_widget_show(messagewindow->windowbox);
+ picture_hbox = gtk_hbox_new(FALSE, 2);
+ gtk_container_set_border_width(GTK_CONTAINER(picture_hbox), 2);
+ gtk_container_add(GTK_CONTAINER(messagewindow->window), messagewindow->bigvbox);
+ gtk_box_pack_end(GTK_BOX(messagewindow->bigvbox), picture_hbox, TRUE, TRUE, 0);
+ gtk_widget_show(picture_hbox);
+ gtk_widget_show(messagewindow->bigvbox);
+
+ messagewindow_addmenubar(messagewindow);
+
+ /* Create the box for past messages. */
+ messagewindow->messages = gtk_text_view_new();
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(messagewindow->messages), GTK_WRAP_WORD);
+ messagelist_hbox = gtk_hbox_new(FALSE, 0);
+ messagelist_scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(messagelist_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+ gtk_container_add(GTK_CONTAINER(messagelist_scroll), GTK_WIDGET(messagewindow->messages));
+ gtk_box_pack_start(GTK_BOX(messagelist_hbox), messagelist_scroll, TRUE, TRUE, 0);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(messagelist_scroll), GTK_SHADOW_IN);
+ gdk_color_parse("white", &colour);
+ gtk_widget_modify_bg(messagewindow->messages, GTK_STATE_NORMAL, &colour);
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages));
+ gtk_text_buffer_create_tag(buffer, "system", "left_margin", 3, "right_margin", 3, "weight", PANGO_WEIGHT_BOLD, "size", 9*PANGO_SCALE, NULL);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(messagewindow->messages), FALSE);
+ gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), messagelist_hbox, TRUE, TRUE, 0);
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ messagewindow->mark = gtk_text_buffer_create_mark(buffer, NULL, &iter, FALSE);
+ gtk_widget_show(messagewindow->messages);
+ gtk_widget_show(messagelist_hbox);
+ gtk_widget_show(messagelist_scroll);
+ GTK_WIDGET_UNSET_FLAGS(messagewindow->messages, GTK_CAN_FOCUS);
+
+ /* Now add a few buttons for the user to control the session with */
+ button_box = gtk_hbox_new(FALSE, 0);
+ sendfile_button = gtk_button_new_with_label("Send File");
+ messagewindow->ink_button = gtk_toggle_button_new_with_label("Ink");
+ nudge_button = gtk_button_new_with_label("Nudge");
+ send_button = gtk_button_new_with_label("Send");
+ gtk_box_pack_start(GTK_BOX(button_box), messagewindow->ink_button, FALSE, FALSE, 5);
+ gtk_box_pack_start(GTK_BOX(button_box), sendfile_button, FALSE, FALSE, 5);
+ gtk_box_pack_end(GTK_BOX(button_box), send_button, FALSE, FALSE, 5);
+ gtk_box_pack_end(GTK_BOX(button_box), nudge_button, FALSE, FALSE, 5);
+ gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), button_box, FALSE, FALSE, 5);
+ gtk_widget_show(button_box);
+ gtk_widget_show(messagewindow->ink_button);
+ gtk_widget_show(sendfile_button);
+ gtk_widget_show(send_button);
+ gtk_widget_show(nudge_button);
+
+ /* Create the box into which the user will type */
+ messagewindow->textbox = gtk_text_view_new();
+ textbox_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(messagewindow->textbox), GTK_WRAP_WORD);
+ textbox_scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(textbox_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_container_add(GTK_CONTAINER(textbox_scroll), GTK_WIDGET(messagewindow->textbox));
+ gtk_box_pack_start(GTK_BOX(textbox_hbox), textbox_scroll, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), textbox_hbox, FALSE, FALSE, 0);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(textbox_scroll), GTK_SHADOW_IN);
+ gdk_color_parse("white", &colour);
+ gtk_widget_modify_bg(messagewindow->textbox, GTK_STATE_NORMAL, &colour);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(messagewindow->textbox), TRUE);
+ g_signal_connect(messagewindow->textbox, "key-press-event", GTK_SIGNAL_FUNC(messagewindow_key), messagewindow);
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->textbox));
+ gtk_text_buffer_create_tag(buffer, "normal", "left_margin", 3, "right_margin", 3, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, NULL);
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ gtk_text_buffer_apply_tag_by_name(buffer, "normal", &start, &end);
+ gtk_widget_show(messagewindow->textbox);
+ gtk_widget_show(textbox_hbox);
+ gtk_widget_show(textbox_scroll);
+
+ messagewindow->textbox_hbox = textbox_hbox;
+ gdk_color_parse("#FFFFFF", &bgcolour);
+ gdk_color_parse("#100080", &fgcolour);
+ messagewindow->gtk_ink = gtk_ink_new(GTK_INK_FLAG_EDITABLE | GTK_INK_FLAG_PAPER, ink_new(), &fgcolour, &bgcolour);
+ gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), messagewindow->gtk_ink, FALSE, FALSE, 0);
+ /* But don't gtk_widget_show() it yet! */
+
+ gtk_widget_set_size_request(messagewindow->gtk_ink, -1, 80);
+ gtk_widget_set_size_request(messagewindow->textbox_hbox, -1, 80);
+
+ gtk_signal_connect(GTK_OBJECT(send_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_send), messagewindow);
+ gtk_signal_connect(GTK_OBJECT(messagewindow->ink_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_ink), messagewindow);
+ gtk_signal_connect(GTK_OBJECT(sendfile_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_sendfile), messagewindow);
+ gtk_signal_connect(GTK_OBJECT(nudge_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_sendnudge), messagewindow);
+
+ messagewindow->picture_list = gtk_vbox_new(FALSE, 5);
+ gtk_widget_set_usize(GTK_WIDGET(messagewindow->picture_list), 96, 0);
+ gtk_box_pack_start(GTK_BOX(picture_hbox), messagewindow->windowbox, TRUE, TRUE, 0);
+ gtk_box_pack_end(GTK_BOX(picture_hbox), messagewindow->picture_list, FALSE, FALSE, 0);
+ gtk_widget_show(messagewindow->picture_list);
+
+ /* Drag and Drop */
+ targets[0].target = "tm_username";
+ targets[0].flags = GTK_TARGET_SAME_APP;
+ targets[0].info = 1;
+ gtk_drag_dest_set(messagewindow->window, GTK_DEST_DEFAULT_ALL, targets, 1, GDK_ACTION_COPY);
+ g_signal_connect(messagewindow->window, "drag-data-received", GTK_SIGNAL_FUNC(messagewindow_userdnd_receive), (gpointer)messagewindow);
+
+ g_signal_connect(messagewindow->window, "size-allocate", GTK_SIGNAL_FUNC(messagewindow_size_allocate), messagewindow);
+ adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(messagelist_scroll));
+ g_signal_connect(adjustment, "value-changed", GTK_SIGNAL_FUNC(messagewindow_scrolled), messagewindow);
+
+ if ( !options_showavatars() ) {
+ GtkAction *action;
+ action = gtk_action_group_get_action(messagewindow->action_group, "AvatarsAction");
+ assert(action != NULL);
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), FALSE);
+ gtk_widget_hide(messagewindow->picture_list);
+ }
+
+ if ( messagewindow->ircstyle ) {
+ GtkAction *action;
+ action = gtk_action_group_get_action(messagewindow->action_group, "IRCStyleAction");
+ assert(action != NULL);
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
+ }
+
+ if ( messagewindow->timestamps ) {
+ GtkAction *action;
+ action = gtk_action_group_get_action(messagewindow->action_group, "TimeStampAction");
+ assert(action != NULL);
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
+ }
+
+ if ( messagewindow->showemoticons ) {
+ GtkAction *action;
+ action = gtk_action_group_get_action(messagewindow->action_group, "EmoticonsAction");
+ assert(action != NULL);
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
+ }
+
+ gtk_widget_show(messagewindow->window); /* Slow */
+ gtk_widget_grab_focus(messagewindow->textbox);
+
+ /* Link into list */
+ last_messagewindow = messagewindow_last();
+ if ( last_messagewindow != NULL ) {
+ assert(last_messagewindow->next == NULL);
+ last_messagewindow->next = messagewindow;
+ } else {
+ messagewindows = messagewindow;
+ }
+ messagewindow->next = NULL;
+
+ if ( session != NULL ) {
+
+ debug_print("MW %8p: Plugging into SB session %p.\n", messagewindow, session);
+ sbsessions_plug(session, messagewindow);
+ messagewindow_plug(messagewindow, session);
+ messagewindow->in_creation = FALSE; /* Fudge! */
+
+ } else {
+
+ if ( (session = sbsessions_find_headless(username)) == NULL ) {
+ debug_print("MW %8p: Creating new SB session.\n", messagewindow);
+ messagewindow_plug(messagewindow, sbsessions_create_local(username));
+ } else {
+ debug_print("MW %8p: Found headless session %p.\n", messagewindow, session);
+ messagewindow_plug(messagewindow, session);
+ messagewindow->in_creation = FALSE; /* Fudge! */
+ }
+ sbsessions_plug(messagewindow->session, messagewindow);
+
+ }
+
+ /* Introduce new and local users. */
+ messagewindow_adduser(messagewindow, username);
+ messagewindow_localpicture(messagewindow);
+
+ messagewindow->in_creation = FALSE;
+ return messagewindow;
+
+}
+
+void messagewindow_create_if_none(const char *username, SbSession *session) {
+
+ MessageWindow *messagewindow = messagewindow_find_single(username);
+
+ if ( messagewindow ) {
+ debug_print("MW %8p: Already have IM window for %s.\n", messagewindow, username);
+ } else {
+ messagewindow_create(username, session);
+ }
+
+}
+
+/* Deal with a user joining a switchboard */
+void messagewindow_joined(MessageWindow *messagewindow, const char *username) {
+
+ MwUser *mwuser = messagewindow_find_username(messagewindow, username);
+
+ if ( mwuser == NULL ) {
+ mwuser = messagewindow_adduser(messagewindow, username);
+ } else {
+ messagewindow_resetstatusbar(messagewindow, username); /* Put Friendlyname (Username) in status bar. */
+ }
+
+ /* Only display message if this isn't the first user. */
+ if ( messagewindow->session->num_users > 1 ) {
+
+ char *text;
+
+ /* Remember, newlines go BEFORE the text in the window. */
+ messagewindow_addtext_system(messagewindow, "--> Join: ");
+ text = messagewindow_statusbarstring(username);
+ messagewindow_addtext_system_nonewline(messagewindow, text);
+
+ free(text);
+
+ }
+
+ messagewindow_trypicture(messagewindow, mwuser->username);
+
+}
+
+/* Called when an attempt is made to write to an IM window which doesn't exist. */
+void messagewindow_mitigate(SbSession *session) {
+
+ MessageWindow *messagewindow = NULL;
+ SbUser *user;
+
+ if ( session->num_users == 1 ) {
+ messagewindow = messagewindow_find_single(session->users->username);
+ if ( messagewindow == NULL ) {
+ messagewindow = messagewindow_create(session->users->username, session);
+ }
+ } else {
+ /* If there's more than one user, there's no chance of window reuse. */
+ messagewindow = messagewindow_create(session->users->username, session);
+ }
+
+ /* IM window favours most recently 'mitigated' session. */
+ messagewindow_plug(messagewindow, session);
+ /* More than one session can report to one IM window. */
+ sbsessions_plug(session, messagewindow);
+
+ user = session->users;
+ while ( user != NULL ) {
+ debug_print("MW %8p: Adding '%s'\n", messagewindow, user->username);
+ messagewindow_joined(messagewindow, user->username);
+ user = user->next;
+ }
+
+}
+
+static void messagewindow_disable(MessageWindow *messagewindow) {
+
+ GtkWidget *menuitem;
+
+ gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->textbox), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->gtk_ink), FALSE);
+
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/conversation/invite_user");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/block_user");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/send_file");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/nudge");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+
+}
+
+static void messagewindow_enable(MessageWindow *messagewindow) {
+
+ GtkWidget *menuitem;
+
+ gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->textbox), TRUE);
+ gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->gtk_ink), TRUE);
+
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/conversation/invite_user");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/block_user");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/send_file");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/nudge");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+
+}
+
+void messagewindow_disable_all() {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ debug_print("MW %8p: disabling all windows..\n", NULL);
+
+ while ( messagewindow != NULL ) {
+ messagewindow_disable(messagewindow);
+ messagewindow = messagewindow->next;
+ }
+
+}
+
+void messagewindow_enable_all() {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ debug_print("MW %8p: enabling all windows..\n", NULL);
+
+ while ( messagewindow != NULL ) {
+ messagewindow_enable(messagewindow);
+ messagewindow = messagewindow->next;
+ }
+
+
+}
+
+int messagewindow_stoptyping(MwUser *user) {
+
+ MessageWindow *messagewindow;
+
+ /* Careful. By the time this callback gets called, the user or window might not exist. */
+ if ( ( messagewindow = messagewindow_find_user(user)) != NULL ) {
+
+ if ( user->typing_callback != 0 ) {
+ gtk_timeout_remove(user->typing_callback);
+ user->typing_callback = 0;
+ }
+
+ messagewindow_resetstatusbar(messagewindow, user->username);
+ user->typing_callback = 0;
+
+ }
+
+ return FALSE;
+
+}
+
+void messagewindow_stoptypingbyusername(MessageWindow *messagewindow, const char *username) {
+ MwUser *user = messagewindow_find_username(messagewindow, username);
+ messagewindow_stoptyping(user);
+}
+
+void messagewindow_starttyping(MessageWindow *messagewindow, const char *username) {
+
+ MwUser *user;
+
+ user = messagewindow_find_username(messagewindow, username);
+ if ( user != NULL ) {
+
+ messagewindow_typing(messagewindow, username);
+ if ( user->typing_callback != 0 ) {
+ gtk_timeout_remove(user->typing_callback);
+ user->typing_callback = 0;
+ }
+ user->typing_callback = gtk_timeout_add(6000, (GtkFunction)messagewindow_stoptyping, user);
+
+ }
+
+}
+
+void messagewindow_notifyoffline(const char *username) {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ while ( messagewindow != NULL ) {
+ MwUser *user = messagewindow->users;
+ while ( user != NULL ) {
+ if ( strcmp(user->username, username) == 0) {
+
+ const char *fname;
+ char *friendlyname;
+ char *message;
+
+ fname = contactlist_friendlyname(username);
+ if ( fname == NULL ) {
+ friendlyname = malloc(strlen(username)+1);
+ strcpy(friendlyname, username);
+ } else {
+ friendlyname = routines_urldecode(fname);
+ }
+
+ message = malloc(strlen(friendlyname)+19);
+
+ debug_print("MW %8p: '%s' went offline.\n", messagewindow, username);
+
+ strcpy(message, "*** ");
+ strcat(message, friendlyname);
+ strcat(message, " went offline.");
+ messagewindow_addtext_system(messagewindow, message);
+ free(message);
+ free(friendlyname);
+
+ }
+ user = user->next;
+
+ }
+ messagewindow = messagewindow->next;
+ }
+
+}
diff --git a/src/messagewindow.h b/src/messagewindow.h
new file mode 100644
index 0000000..7c8397d
--- /dev/null
+++ b/src/messagewindow.h
@@ -0,0 +1,118 @@
+/*
+ * messagewindow.h
+ *
+ * IM windows
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef MESSAGEWINDOW_H
+#define MESSAGEWINDOW_H
+
+#include "sbsessions.h"
+#include "gtk-ink.h"
+
+typedef struct _mwuser {
+
+ struct _mwuser *next;
+
+ char *username;
+
+ GtkWidget *bar; /* GTK label containing the user's name. */
+ GtkWidget *bar_hbox; /* hbox into which "bar" fits */
+ GtkWidget *avatar_eventbox; /* Eventbox into which the "Display picture" fits */
+ GtkWidget *avatar; /* The "Display picture" itself (GtkImage) */
+ GdkPixbuf *avatar_pixbuf; /* Corresponding pixbuf */
+
+ int typing_callback; /* Callback to unset "(Typing) " status. */
+
+} MwUser;
+
+typedef struct _messagewindow {
+
+ struct _messagewindow *next;
+
+ SbSession *session; /* SB session this window talks to */
+
+ MwUser *users;
+ unsigned int num_users; /* Number of people in this window. */
+ unsigned int first_event;
+ unsigned int in_creation; /* Non-zero suppresses opening of an SB session. */
+ unsigned int last_was_nak; /* Last text to this window was NAK warning. */
+
+ GtkWidget *window; /* Overall window. */
+ GtkWidget *windowbox; /* VBox into which to pack Stuff (like status bars) */
+ GtkWidget *bigvbox;
+ GtkWidget *avatar_eventbox; /* Eventbox into which the "Display picture" fits */
+ GtkWidget *avatar; /* The "Display picture" itself (GtkImage) */
+ GdkPixbuf *avatar_pixbuf; /* Corresponding pixbug */
+ GtkWidget *picture_list; /* VBox into which the column of avatars fits */
+ GtkWidget *messages; /* Widget into which to put messages */
+ GtkWidget *textbox; /* Text Entry widget */
+ GtkTextMark *mark; /* Mark at the end of the text. */
+ GtkUIManager *ui; /* UI manager */
+ GtkActionGroup *action_group; /* Action group */
+ GtkWidget *fontsdialog; /* Fonts dialog box */
+ GtkWidget *ofont_button; /* Font button to set override font. */
+ GtkWidget *ocolour_button; /* Colour button to set override font. */
+ GtkWidget *gtk_ink; /* GtkInk widget */
+ GtkWidget *textbox_hbox;
+ GtkWidget *ink_button;
+ unsigned int stuck; /* Flag to see if the window is scrolled to the bottom. */
+
+ GdkColor *localcolour_gdk; /* GdkColor version of local user's colour. */
+ char *localcolour_string; /* String version of local user's colour. */
+ GdkColor *ocolour_gdk; /* GdkColor to override contacts' colours with. */
+ unsigned int ofontoverride; /* Override contacts' colours? */
+ char *ocolour_string; /* String version of ocolour_gdk */
+ char *localfont; /* Local user's font in a format to throw at the SB (FN=xxx EF=xxx etc) */
+ char *dislocalfont; /* Local user's font in a format to use for local display. */
+ char *ofont; /* Font for contacts' messages. */
+
+ unsigned int ircstyle;
+ unsigned int timestamps;
+ unsigned int showemoticons;
+
+} MessageWindow;
+
+/* Operations on IM windows themselves. */
+extern MessageWindow *messagewindow_create(const char *username, SbSession *session);
+extern void messagewindow_create_if_none(const char *username, SbSession *session);
+extern void messagewindow_mitigate(SbSession *session);
+extern void messagewindow_unplug(SbSession *session);
+extern void messagewindow_addtext_system(MessageWindow *messagewindow, const char *text);
+extern void messagewindow_addtext_user_nonewline(MessageWindow *messagewindow, const char *text, int length, const char *colour, const char *font);
+extern void messagewindow_reportdropped(MessageWindow *messagewindow, char *message, size_t length);
+extern int messagewindow_get_last_was_nak(MessageWindow *messagewindow);
+extern void messagewindow_set_last_was_nak(MessageWindow *messagewindow, int last_was_nak);
+
+/* Operations on individual IM window users. */
+extern void messagewindow_joined(MessageWindow *messagewindow, const char *username);
+extern void messagewindow_removeuser(MessageWindow *messagewindow, const char *username);
+extern void messagewindow_starttyping(MessageWindow *messagewindow, const char *username);
+extern int messagewindow_stoptyping(MwUser *user);
+extern void messagewindow_stoptypingbyusername(MessageWindow *messagewindow, const char *username);
+extern void messagewindow_picturekick(const char *username);
+extern void messagewindow_notifyoffline(const char *username);
+
+/* Other stuff */
+extern void messagewindow_disable_all();
+extern void messagewindow_enable_all();
+
+#endif /* MESSAGEWINDOW_H */
diff --git a/src/mime.c b/src/mime.c
new file mode 100644
index 0000000..970f29a
--- /dev/null
+++ b/src/mime.c
@@ -0,0 +1,242 @@
+/*
+ * mime.c
+ *
+ * Rudimentary MIME parser
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "routines.h"
+#include "debug.h"
+
+/* Get the length of the MIME header, including the two newlines (i.e. the offset for the body). */
+size_t mime_headerlength(const char *mime) {
+
+ int status;
+ size_t offs = 0;
+
+ status = 0;
+
+ /* Simply detect two consecutive newlines. */
+ while ( offs < strlen(mime) ) {
+
+ /* State machine :D */
+ if ( (mime[offs] == '\r') && (status == 1) ) {
+ /* \r\r */
+ assert(offs+1 <= strlen(mime));
+ return offs+1;
+ }
+ if ( (mime[offs] == '\n') && (status == 4) ) {
+ /* \n\n */
+ assert(offs+1 <= strlen(mime));
+ return offs+1;
+ }
+ if ( (mime[offs] == '\n') && (status == 0) ) {
+ status = 4;
+ }
+ if ( (mime[offs] == '\r') && (status == 0) ) {
+ status = 1;
+ }
+ if ( (mime[offs] == '\n') && (status == 1) ) {
+ status = 2;
+ }
+ if ( (mime[offs] == '\r') && (status == 2) ) {
+ status = 3;
+ }
+ if ( (mime[offs] == '\n') && (status == 3) ) {
+ /* \r\n\r\n */
+ assert(offs+1 <= strlen(mime));
+ return offs+1;
+ }
+
+ if ( (mime[offs] != '\r') && (mime[offs] != '\n') ) {
+ status = 0;
+ }
+
+ offs++;
+
+ }
+
+ /* Whoops */
+ return 0;
+
+}
+
+/* Return the body of a MIME message. Don't free() the returned string. */
+const char *mime_getbody(const char *mime) {
+
+ return mime+mime_headerlength(mime);
+
+}
+
+/* Return the value of a given MIME field. Returned string is free-able. */
+static char *mime_getfield_internal(const char *mime, const char *given_field, int anywhere) {
+
+ char *line;
+ char *minibuffer;
+ size_t offs = 0;
+ size_t mime_size;
+ char *field;
+ int end_headers = 0;
+
+ mime_size = strlen(mime);
+
+ /* Last character of "field" should be a colon. Add it if it's not already there. */
+ if ( given_field[strlen(given_field)-1] != ':' ) {
+
+ field = malloc(strlen(given_field) + 2);
+ strcpy(field, given_field);
+ field[strlen(given_field)] = ':';
+ field[strlen(given_field)+1] = '\0';
+
+ } else {
+ field = strdup(given_field);
+ }
+
+ line = malloc(strlen(mime)+1); /* Bigger than the maximum possible output size */
+ while ( (offs < mime_size) && !end_headers ) {
+
+ size_t line_offs = 0;
+ int found_eof_line = 0;
+
+ while ( (found_eof_line == 0) && (offs<strlen(mime)) ) {
+
+ if ( (mime[offs] == '\r') || (mime[offs] == '\n') ) {
+
+ found_eof_line = 1;
+ line[line_offs]='\0';
+
+ if ( !anywhere ) {
+ if ( offs<(mime_size-4) ) {
+ if ( strncmp(mime+offs, "\r\n\r\n", 4) == 0 ) {
+ end_headers = 1;
+ }
+ }
+ if ( offs<(mime_size-2) ) {
+ if ( strncmp(mime+offs, "\n\n", 2) == 0 ) {
+ end_headers = 1;
+ }
+ }
+ if ( offs<(mime_size-4) ) {
+ if ( strncmp(mime+offs, "\r\r", 2) == 0 ) {
+ end_headers = 1;
+ }
+ }
+ }
+
+ } else {
+ line[line_offs] = mime[offs];
+ offs++;
+ line_offs++;
+ }
+
+ }
+ if ( offs >= mime_size-1 ) {
+ line[line_offs]='\0';
+ }
+
+ minibuffer = routines_lindex(line, 0);
+ if ( strcmp(minibuffer, field) == 0 ) {
+
+ char *fieldcontents;
+ free(minibuffer);
+ fieldcontents = routines_lindexend(line, 1);
+ free(line);
+ free(field);
+ return fieldcontents;
+
+ }
+ free(minibuffer);
+ offs+=2;
+ }
+
+ /* Field not found, so return an empty string (free()-able) */
+ free(line);
+ free(field);
+ minibuffer = malloc(1);
+ minibuffer[0]='\0';
+ return minibuffer;
+
+}
+
+char *mime_getfield(const char *mime, const char *given_field) {
+ return mime_getfield_internal(mime, given_field, 0);
+}
+
+char *mime_getfield_anywhere(const char *mime, const char *given_field) {
+ return mime_getfield_internal(mime, given_field, 1);
+}
+
+/* Remove a given header from a MIME message. */
+char *mime_removeheader(const char *mime, size_t mimelength, const char *field) {
+
+ size_t i;
+ size_t fieldlen;
+
+ fieldlen = strlen(field);
+
+ for ( i=0; i<mimelength-fieldlen; i++ ) {
+
+ if ( strncmp(mime+i, field, fieldlen) == 0 ) {
+
+ size_t pos = i;
+ size_t endpos;
+ for ( pos=i; pos<mimelength-fieldlen; pos++ ) {
+
+ /* Works with any sensible type of line-ending. */
+ if ( (mime[pos] == '\r') || (mime[pos] == '\n') ) {
+
+ char *new_mime;
+ if ( (mime[pos+1] == '\r') || (mime[pos+1] == '\n') ) {
+ endpos = pos+2;
+ } else {
+ endpos = pos+1;
+ }
+ new_mime = malloc(mimelength-(endpos-i)+1);
+ memcpy(new_mime, mime, i);
+ memcpy(new_mime+i, mime+endpos, mimelength-endpos);
+ new_mime[mimelength-(endpos-i)] = '\0';
+ return new_mime;
+ }
+
+ }
+ /* D'oh :( */
+ debug_print("mime.c: didn't find end of field to be removed!\n");
+ return strdup(mime);
+
+ }
+
+ if ( (strncmp(mime+i, "\r\n\r\n", 4) == 0) || (strncmp(mime+i, "\n\n", 2) == 0) || (strncmp(mime+i, "\r\r", 2) == 0) ) {
+ debug_print("Reached end of MIME headers before finding field to remove.\n");
+ break;
+ }
+
+ }
+
+ return strdup(mime);
+
+}
diff --git a/src/mime.h b/src/mime.h
new file mode 100644
index 0000000..0caad7e
--- /dev/null
+++ b/src/mime.h
@@ -0,0 +1,35 @@
+/*
+ * mime.h
+ *
+ * Rudimentary MIME parser
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ *
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef MIME_H
+#define MIME_H
+
+extern char *mime_getfield(const char *mime, const char *field);
+extern char *mime_getfield_anywhere(const char *mime, const char *field);
+extern const char *mime_getbody(const char *mime);
+extern size_t mime_headerlength(const char *mime);
+extern char *mime_removeheader(const char *mime, size_t mimelength, const char *field);
+
+#endif /* MIME_H */
diff --git a/src/msngenerics.c b/src/msngenerics.c
new file mode 100644
index 0000000..73d6ef4
--- /dev/null
+++ b/src/msngenerics.c
@@ -0,0 +1,71 @@
+/*
+ * msngenerics.c
+ *
+ * General MSN protocol schemas and semantics
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+#include "msngenerics.h"
+
+OnlineState msngenerics_decodestatus(const char *status) {
+
+ OnlineState state = ONLINE_ERR;
+
+ if ( strcmp(status, "NLN") == 0 ) {
+ state = ONLINE_NLN;
+ }
+ if ( strcmp(status, "IDL") == 0 ) {
+ state = ONLINE_IDL;
+ }
+ if ( strcmp(status, "AWY") == 0 ) {
+ state = ONLINE_AWY;
+ }
+ if ( strcmp(status, "BSY") == 0 ) {
+ state = ONLINE_BSY;
+ }
+ if ( strcmp(status, "BRB") == 0 ) {
+ state = ONLINE_BRB;
+ }
+ if ( strcmp(status, "PHN") == 0 ) {
+ state = ONLINE_PHN;
+ }
+ if ( strcmp(status, "LUN") == 0 ) {
+ state = ONLINE_LUN;
+ }
+ if ( strcmp(status, "HDN") == 0 ) {
+ state = ONLINE_HDN;
+ }
+ if ( strcmp(status, "FLN") == 0 ) {
+ state = ONLINE_FLN;
+ }
+
+ assert(state != ONLINE_ERR);
+
+ /* It's up to the caller to check that the status given is actually valid in context. */
+ return state;
+
+}
diff --git a/src/msngenerics.h b/src/msngenerics.h
new file mode 100644
index 0000000..0bbaa27
--- /dev/null
+++ b/src/msngenerics.h
@@ -0,0 +1,47 @@
+/*
+ * msngenerics.h
+ *
+ * General MSN protocol schemas and semantics
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef MSNGENERICS_H
+#define MSNGENERICS_H
+
+#define DEFAULT_NS_PORT 1863
+#define DEFAULT_SB_PORT 1863
+
+/* Different user statuses. */
+typedef enum {
+ ONLINE_ERR, /* Used to catch errors */
+ ONLINE_NLN, /* Online */
+ ONLINE_IDL, /* Idle */
+ ONLINE_AWY, /* Away */
+ ONLINE_BSY, /* Busy */
+ ONLINE_BRB, /* Be Right Back */
+ ONLINE_PHN, /* On The Phone */
+ ONLINE_LUN, /* Lunch */
+ ONLINE_HDN, /* Appear Offline (only allowed for the local user) */
+ ONLINE_FLN /* Offline */
+} OnlineState;
+
+OnlineState msngenerics_decodestatus(const char *status);
+
+#endif /* MSNGENERICS_H */
diff --git a/src/msninvite.c b/src/msninvite.c
new file mode 100644
index 0000000..bcfba27
--- /dev/null
+++ b/src/msninvite.c
@@ -0,0 +1,32 @@
+/*
+ * msninvite.c
+ *
+ * Handle MSMsgsInvite stuff (old-style file transfers etc)
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "sbsessions.h"
+
+void msninvite_parsemsg(SbSession *session, const char *msg) {
+}
diff --git a/src/msninvite.h b/src/msninvite.h
new file mode 100644
index 0000000..1f13ab5
--- /dev/null
+++ b/src/msninvite.h
@@ -0,0 +1,32 @@
+/*
+ * msninvite.h
+ *
+ * Handle MSMsgsInvite stuff
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef MSNINVITE_H
+#define MSNINVITE_H
+
+#include "sbsessions.h"
+
+extern void msninvite_parsemsg(SbSession *session, const char *msg);
+
+#endif /* MSNINVITE_H */
diff --git a/src/msnp11chl.c b/src/msnp11chl.c
new file mode 100644
index 0000000..774de76
--- /dev/null
+++ b/src/msnp11chl.c
@@ -0,0 +1,175 @@
+/*
+ * msnp11.c
+ *
+ * New-style challenge
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ *
+ * Special Notice for the MSNP11 Challenge Module
+ * ----------------------------------------------
+ * The author(s) of this software performed no
+ * reverse-engineering of any Microsoft software
+ * in order to create this module. Information
+ * on the MSNP11-style challenge can be freely
+ * found on the Web.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdint.h>
+#include <openssl/md5.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <glib.h>
+
+#include "msnp11chl.h"
+
+#undef MSNP11CHL_DEBUG
+
+uint32_t msnp11chl_tolittle(uint32_t val) {
+ return GINT32_TO_LE(val);
+}
+
+char *msnp11chl_response(const char *challenge) {
+
+ char *flobbadob;
+ unsigned char *md5;
+ uint64_t words_1[4];
+ uint64_t words_1_orig[4];
+ uint32_t *words_2;
+ int nw2;
+ int i;
+ uint64_t low = 0;
+ uint64_t high = 0;
+ uint64_t key;
+ uint64_t hash1;
+ uint64_t hash2;
+ char *response;
+
+ md5 = malloc(MD5_DIGEST_LENGTH); /* =16 */
+ flobbadob = malloc(strlen(challenge)+strlen(CLIENT_CODE)+1);
+ strcpy(flobbadob, challenge);
+ strcat(flobbadob, CLIENT_CODE);
+ MD5(flobbadob, strlen(flobbadob), md5);
+ free(flobbadob);
+
+ /* Split into four-byte words. */
+ words_1_orig[0] = msnp11chl_tolittle((uint32_t)*(uint32_t *)(md5));
+ words_1_orig[1] = msnp11chl_tolittle((uint32_t)*(uint32_t *)(md5+4));
+ words_1_orig[2] = msnp11chl_tolittle((uint32_t)*(uint32_t *)(md5+8));
+ words_1_orig[3] = msnp11chl_tolittle((uint32_t)*(uint32_t *)(md5+12));
+ free(md5);
+
+ for (i=0; i<4; i++) {
+ words_1[i] = words_1_orig[i] & 0x7FFFFFFF;
+ }
+
+#ifdef MSNP11CHL_DEBUG
+ printf("words_1[0] = 0x%08lx\n", words_1[0]);
+ printf("words_1[1] = 0x%08lx\n", words_1[1]);
+ printf("words_1[2] = 0x%08lx\n", words_1[2]);
+ printf("words_1[3] = 0x%08lx\n", words_1[3]);
+#endif /* MSNP11CHL_DEBUG */
+
+ flobbadob = malloc(strlen(challenge)+strlen(CLIENT_ID)+9);
+ strcpy(flobbadob, challenge);
+ strcat(flobbadob, CLIENT_ID);
+ while ( strlen(flobbadob) % 8 != 0 ) {
+ strcat(flobbadob, "0");
+ }
+
+ /* Split into four-byte words. */
+ words_2 = malloc(strlen(flobbadob));
+ memcpy(words_2, flobbadob, strlen(flobbadob)); /* Not including terminator. */
+ nw2 = strlen(flobbadob)/4; /* = number of members in words_2[] */
+ free(flobbadob);
+ for (i=0; i<nw2; i++) {
+ words_2[i] = GINT32_TO_LE(words_2[i]);
+#ifdef MSNP11CHL_DEBUG
+ printf("words_2[%i] = 0x%08lx\n", i, words_2[i]);
+#endif /* MSNP11CHL_DEBUG */
+ }
+
+ for (i = 0; i<nw2; i+=2) {
+
+ uint64_t temp = words_2[i];
+ temp = (0x0E79A9C1 * temp);
+ temp = temp % 0x7FFFFFFF;
+ temp += high;
+ temp = words_1[0] * temp + words_1[1];
+ temp = temp % 0x7FFFFFFF;
+
+#ifdef MSNP11CHL_DEBUG
+ printf("1: temp = 0x%08lx", temp);
+ printf(" high = 0x%08lx", high);
+ printf(" low = 0x%08lx\n", low);
+#endif /* MSNP11CHL_DEBUG */
+
+ high = words_2[i+1];
+ high = (high + temp) % 0x7FFFFFFF;
+ high = words_1[2] * high + words_1[3];
+ high = high % 0x7FFFFFFF;
+
+ low = low + high + temp;
+
+#ifdef MSNP11CHL_DEBUG
+ printf("2: temp = 0x%08lx", temp);
+ printf(" high = 0x%08lx", high);
+ printf(" low = 0x%08lx\n", low);
+#endif /* MSNP11CHL_DEBUG */
+
+ }
+
+ free(words_2);
+
+ high = (high + words_1[1]) % 0x7FFFFFFF;
+ low = (low + words_1[3]) % 0x7FFFFFFF;
+#ifdef MSNP11CHL_DEBUG
+ printf("high = 0x%08lx", high);
+ printf(" low = 0x%08lx\n", low);
+#endif /* MSNP11CHL_DEBUG */
+
+ /* "high" is a 32-bit integer in a 64-bit type, so swapping it automatically shifts it by 32 bits */
+ key = (low<<32) + high;
+#ifdef MSNP11CHL_DEBUG
+ printf("key = %llx\n", key);
+#endif /* MSNP11CHL_DEBUG */
+
+ hash1 = words_1_orig[0] + (words_1_orig[1]<<32);
+ hash2 = words_1_orig[2] + (words_1_orig[3]<<32);
+#ifdef MSNP11CHL_DEBUG
+ printf("hash1 = 0x%016llx, hash2 = 0x%016llx\n", hash1, hash2);
+#endif /* MSNP11CHL_DEBUG */
+
+ response = malloc(33);
+ sprintf(response, "%.16llx%.16llx", GUINT64_SWAP_LE_BE(key^hash1), GUINT64_SWAP_LE_BE(key^hash2));
+
+ for ( i=0; i<strlen(response); i++ ) {
+ if ( response[i]==' ' ) {
+ response[i]='0';
+ }
+ }
+
+ return response;
+
+}
diff --git a/src/msnp11chl.h b/src/msnp11chl.h
new file mode 100644
index 0000000..c789cf2
--- /dev/null
+++ b/src/msnp11chl.h
@@ -0,0 +1,48 @@
+/*
+ * msnp11.h
+ *
+ * New-style challenge
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ *
+ * Special Notice for the MSNP11 Challenge Module
+ * ----------------------------------------------
+ * The author(s) of this software performed no
+ * reverse-engineering of any Microsoft software
+ * in order to create this module. Information
+ * on the MSNP11-style challenge can be freely
+ * found on the Web.
+ *
+ */
+
+#ifndef MSNP11CHL_H
+#define MSNP11CHL_H
+
+extern char *msnp11chl_response(const char *challenge);
+
+//#define CLIENT_ID "PROD0104U6VVM{UJ"
+//#define CLIENT_CODE "VK67B}379XYM5}$T"
+
+#define CLIENT_ID "PROD0101{0RM?UBW"
+#define CLIENT_CODE "CFHUR$52U_{VIX5T"
+
+//#define CLIENT_ID "PROD0090YUAUV{2B"
+//#define CLIENT_CODE "YMM8C_H7KCQ2S_KL"
+
+#endif /* MSNP11CHL_H */
diff --git a/src/msnp2p.c b/src/msnp2p.c
new file mode 100644
index 0000000..5cd2473
--- /dev/null
+++ b/src/msnp2p.c
@@ -0,0 +1,1325 @@
+/*
+ * msnp2p.c
+ *
+ * Wizb Technologies' Combined MSNP2P/MSNSLP layer
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <glob.h>
+#include <sys/stat.h>
+#include <assert.h>
+
+#include "sbsessions.h"
+#include "mime.h"
+#include "options.h"
+#include "debug.h"
+#include "sbprotocol.h"
+#include "avatars.h"
+#include "routines.h"
+#include "xml.h"
+#include "contactlist.h"
+
+#define MSNP2P_DEBUG
+
+static GList *msnp2p_list = NULL;
+
+struct _mpsession {
+
+ SbSession *parent;
+ char *username;
+ enum {
+ MSNP2P_DIR_RECV,
+ MSNP2P_DIR_SEND
+ } direction;
+
+ unsigned int baseidentifier;
+ unsigned int id_increment;
+ enum {
+ MSNP2P_TYPE_DP,
+ MSNP2P_TYPE_INK,
+ MSNP2P_TYPE_FT
+ } type;
+ unsigned int session_id;
+ FILE *fh;
+ char *save_filename;
+ char *sha1d; /* The SHA1D field from the MSNObject, after xml_killillegalchars. */
+ char *via;
+ char *call_id;
+ size_t offset;
+ void *data;
+ guint delete_timeout;
+
+};
+typedef struct _mpsession MpSession;
+
+struct _mpheader {
+
+ uint32_t session_id;
+ uint32_t identifier;
+ uint64_t offset;
+ uint64_t total_size;
+ uint32_t size;
+ uint32_t flags;
+ uint32_t ack_id;
+ uint32_t ack_sess;
+ uint64_t ack_size;
+
+};
+typedef struct _mpheader MpHeader;
+
+static MpSession *msnp2p_new() {
+
+ MpSession *mpsession = malloc(sizeof(MpSession));
+
+ mpsession->save_filename = NULL;
+ mpsession->sha1d = NULL;
+ mpsession->via = NULL;
+ mpsession->call_id = NULL;
+ mpsession->username = NULL;
+ mpsession->data = NULL;
+ mpsession->delete_timeout = 0;
+
+ return mpsession;
+
+}
+
+static gboolean msnp2p_closesession(MpSession *mpsession) {
+
+ assert(mpsession != NULL);
+
+ if ( mpsession->via != NULL ) {
+ free(mpsession->via);
+ }
+ if ( mpsession->call_id != NULL ) {
+ free(mpsession->call_id);
+ }
+ if ( mpsession->username != NULL ) {
+ debug_print("MP %8p: Deleting an MSNP2P session to '%s'\n", mpsession, mpsession->username);
+ free(mpsession->username);
+ } else {
+ debug_print("MP %8p: Deleting an MSNP2P session.\n", mpsession);
+ }
+ if ( mpsession->save_filename != NULL ) {
+ free(mpsession->save_filename);
+ }
+ if ( mpsession->sha1d != NULL ) {
+ free(mpsession->sha1d);
+ }
+ if ( mpsession->delete_timeout != 0 ) {
+ g_source_remove(mpsession->delete_timeout);
+ }
+ if ( mpsession->data != NULL ) {
+ free(mpsession->data);
+ }
+
+ msnp2p_list = g_list_remove(msnp2p_list, mpsession);
+
+ free(mpsession);
+
+ return FALSE;
+
+}
+
+/* Yes. Not Pretty. */
+static void msnp2p_send(SbSession *session, MpHeader mpheader, const char *username, const char *payload, int payload_length, int app_id) {
+
+ int total_message_length;
+ char *msnp2p_text;
+ char *message_stub;
+ int stub_length;
+ int sf;
+ char *final;
+
+ /* Create the overall header for the message. */
+ msnp2p_text = malloc(74 + strlen(username));
+ if ( msnp2p_text == NULL ) {
+ debug_print("MP: Not enough memory to send MSNP2P message.\n");
+ return;
+ }
+ strcpy(msnp2p_text, "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: ");
+ strcat(msnp2p_text, username);
+ strcat(msnp2p_text, "\r\n\r\n");
+
+ /* Calculate the total message length. */
+ total_message_length = strlen(msnp2p_text) + 48 + payload_length + 4;
+
+ /* Snippet */
+ message_stub = malloc(9);
+ if ( message_stub == NULL ) {
+
+ debug_print("MP: Not enough memory to send MSNP2P message.\n");
+ free(msnp2p_text);
+ return;
+
+ }
+ assert(total_message_length < 10000); /* i.e. fits into four digits. */
+ sprintf(message_stub, "D %i\r\n", total_message_length);
+ stub_length = strlen(message_stub);
+
+ final = malloc(stub_length + strlen(msnp2p_text) + 48 + payload_length + 4);
+ if ( final == NULL ) {
+
+ debug_print("MP: Not enough memory to send MSNP2P message.\n");
+ free(message_stub);
+ free(msnp2p_text);
+ return;
+
+ }
+
+ strcpy(final, message_stub);
+ free(message_stub);
+ strcat(final, msnp2p_text);
+ free(msnp2p_text);
+ sf = strlen(final);
+
+ memcpy (final+sf, &mpheader, 48);
+ memcpy (final+sf+48, payload, payload_length);
+ *((int *)(final+sf+48+payload_length)) = htonl(app_id);
+
+ sbprotocol_sendtr_nonewline(session, "MSG", final, total_message_length+stub_length);
+ free(final);
+
+}
+
+static gint msnp2p_compare_identifier(MpSession *mpsession, unsigned int *identifier) {
+
+ if ( mpsession->baseidentifier == *identifier ) {
+ return 0;
+ }
+
+ return 1;
+
+}
+
+static gint msnp2p_compare_parent(MpSession *mpsession, SbSession **parent) {
+
+ if ( mpsession->parent == *parent ) {
+ return 0;
+ }
+
+ return 1;
+
+}
+
+static gint msnp2p_compare_sessionid(MpSession *mpsession, unsigned int *sessionid) {
+
+ if ( mpsession->session_id == *sessionid ) {
+ return 0;
+ }
+
+ return 1;
+
+}
+
+static gint msnp2p_compare_callid(MpSession *mpsession, char *call_id) {
+
+ if ( mpsession->call_id != NULL ) {
+ if ( strcmp(mpsession->call_id, call_id) == 0 ) {
+ return 0;
+ }
+ }
+
+ return 1;
+
+}
+
+static gint msnp2p_compare_savefilename(MpSession *mpsession, char *save_filename) {
+
+ if ( mpsession->save_filename != NULL ) {
+ if ( strcmp(mpsession->save_filename, save_filename) == 0 ) {
+ return 0;
+ }
+ }
+
+ return 1;
+
+}
+
+/*static gint msnp2p_compare_sha1d(MpSession *mpsession, char *sha1d) {
+
+ if ( mpsession->sha1d != NULL ) {
+ if ( strcmp(mpsession->sha1d, sha1d) == 0 ) {
+ return 0;
+ }
+ }
+
+ return 1;
+
+}*/
+
+static MpSession *msnp2p_find_identifier(unsigned long int identifier) {
+
+ GList *result;
+
+ result = g_list_find_custom(msnp2p_list, &identifier, (GCompareFunc)msnp2p_compare_identifier);
+
+ if ( result != NULL ) {
+ return (MpSession *)result->data;
+ }
+
+ return NULL;
+
+}
+
+static MpSession *msnp2p_find_sessionid(unsigned long int sessionid) {
+
+ GList *result;
+
+ result = g_list_find_custom(msnp2p_list, &sessionid, (GCompareFunc)msnp2p_compare_sessionid);
+
+ if ( result != NULL ) {
+ return (MpSession *)result->data;
+ }
+
+ return NULL;
+
+}
+
+static MpSession *msnp2p_find_callid(char *callid) {
+
+ GList *result;
+
+ result = g_list_find_custom(msnp2p_list, callid, (GCompareFunc)msnp2p_compare_callid);
+
+ if ( result != NULL ) {
+ return (MpSession *)result->data;
+ }
+
+ return NULL;
+
+}
+
+/* Who invented this cr*p? */
+static void msnp2p_inc(MpSession *mpsession) {
+
+ mpsession->id_increment++;
+ if ( mpsession->id_increment == -1 ) {
+ mpsession->id_increment = 1;
+ }
+
+}
+
+static MpSession *msnp2p_check_ok(const char *msnslp) {
+
+ char *session_id_string;
+ MpSession *mpsession;
+
+ session_id_string = mime_getfield_anywhere(msnslp, "SessionID");
+ if ( strlen(session_id_string) == 0 ) {
+ debug_print("MP: Couldn't find SessionID field.\n");
+ free(session_id_string);
+ return NULL;
+ }
+ mpsession = msnp2p_find_sessionid(atoi(session_id_string));
+ free(session_id_string);
+
+ if ( strncmp(msnslp, "MSNSLP/1.0 200 OK", 17) == 0 ) {
+
+ debug_print("MP: Got MSNSLP/1.0 200 OK - ");
+ if ( mpsession == NULL ) {
+ debug_print("but session not found.\n");
+ } else {
+ debug_print("and session found.\n");
+ }
+
+ }
+
+ return mpsession;
+
+}
+
+static int msnp2p_check_bye(SbSession *session, const char *msnslp, MpHeader *mpheader) {
+
+ char *callid_string;
+ MpSession *mpsession;
+
+ callid_string = mime_getfield_anywhere(msnslp, "Call-ID");
+ if ( callid_string == NULL ) {
+ debug_print("MP: Couldn't find Call-ID field.\n");
+ return 0;
+ }
+ mpsession = msnp2p_find_callid(callid_string);
+ free(callid_string);
+
+ if ( strncmp(msnslp, "BYE MSNMSGR", 11) == 0 ) {
+
+ debug_print("MP: Got BYE MSNMSGR - ");
+ if ( mpsession == NULL ) {
+ debug_print("but session not found.\n");
+ } else {
+
+ MpHeader acknowledgement;
+
+ debug_print("and session found.\n");
+
+ /* Send BYE ACK message. */
+ acknowledgement.session_id = 0;
+ acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment);
+ /* msnp2p_inc(mpsession); */
+ acknowledgement.offset = 0;
+ acknowledgement.total_size = 0;
+ acknowledgement.size = 0;
+ acknowledgement.flags = GINT32_TO_LE(0x42);
+ acknowledgement.ack_sess = mpheader->identifier;
+ acknowledgement.ack_id = mpheader->ack_sess;
+ acknowledgement.ack_size = mpheader->size;
+
+ debug_print("MP: Sending BYE ACK\n");
+ msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0);
+
+ /* Delay before deletion allows straggling packets to be collected. */
+ debug_print("MP: Closing MSNP2P session in ten seconds...\n");
+ mpsession->delete_timeout = g_timeout_add(10000, (GSourceFunc)msnp2p_closesession, mpsession);
+
+ return 1;
+
+ }
+
+ }
+
+ return 0;
+
+}
+
+static unsigned int msnp2p_cookie() {
+
+ FILE *fh;
+ unsigned int seed;
+ float num;
+ unsigned int val;
+
+ fh = fopen("/dev/urandom", "r");
+ fread(&seed, sizeof(seed), 1, fh);
+ fclose(fh);
+ srand(seed);
+
+ num = (float)rand() / (float)RAND_MAX;
+ val = (num * 16777216)+8;
+
+ return val;
+
+}
+
+static MpSession *msnp2p_send_dp(MpSession *mpsession, SbSession *session, const char *msnslp) {
+
+ MpHeader msnp2p_binary;
+ char *msnslp_block;
+ size_t msnslp_length;
+ glob_t glob_result;
+ struct stat *statbuf;
+ void *picture_data;
+ unsigned int file_offs;
+ int zero;
+ struct stat stat_buffer;
+ FILE *fh;
+ unsigned int size;
+ int readval;
+ char *request_details;
+ size_t request_details_length;
+ char *local_avatar;
+
+ mpsession->via = mime_getfield_anywhere(msnslp, "Via:");
+ if ( mpsession->via == NULL ) {
+ debug_print("MP: Couldn't find Via field.\n");
+ free(mpsession);
+ return NULL;
+ }
+ mpsession->call_id = mime_getfield_anywhere(msnslp, "Call-ID:");
+ if ( mpsession->call_id == NULL ) {
+ debug_print("MP: Couldn't find Call-ID field.\n");
+ free(mpsession);
+ return NULL;
+ }
+
+ request_details = malloc(26);
+ debug_print("MP: New session ID=%i\n", mpsession->session_id);
+ assert(mpsession->session_id <= 2147483647);
+ strcpy(request_details, "SessionID: ");
+ sprintf(request_details+strlen(request_details), "%i\r\n\r\n", mpsession->session_id);
+ request_details_length = strlen(request_details);
+
+ msnslp_block = malloc( 176 + strlen(options_username()) + strlen(mpsession->username) + strlen(mpsession->via) + strlen(mpsession->call_id) + strlen(request_details) );
+ strcpy(msnslp_block, "MSNSLP/1.0 200 OK\r\nTo: <msnmsgr:");
+ strncat(msnslp_block, mpsession->username, 64);
+ strcat(msnslp_block, ">\r\nFrom: <msnmsgr:");
+ strncat(msnslp_block, options_username(), 64);
+ strcat(msnslp_block, ">\r\nVia: ");
+ strncat(msnslp_block, mpsession->via, 256);
+ strcat(msnslp_block, "\r\nCSeq: 1 \r\nCall-ID: ");
+ strncat(msnslp_block, mpsession->call_id, 256);
+ strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionreqbody\r\nContent-Length: ");
+ assert(request_details_length + 1 < 10000);
+ sprintf(msnslp_block+strlen(msnslp_block), "%i", request_details_length+1);
+ strcat(msnslp_block, "\r\n\r\n");
+ strcat(msnslp_block, request_details);
+ free(request_details);
+
+ msnslp_length = strlen(msnslp_block);
+
+ msnp2p_binary.session_id = 0;
+ msnp2p_binary.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment);
+ msnp2p_inc(mpsession);
+ msnp2p_binary.offset = 0;
+ msnp2p_binary.total_size = GINT64_TO_LE(msnslp_length+1);
+ msnp2p_binary.size = GINT64_TO_LE(msnslp_length+1);
+ msnp2p_binary.flags = 0;
+ msnp2p_binary.ack_sess = GINT32_TO_LE(mpsession->session_id);
+ msnp2p_binary.ack_id = 0;
+ msnp2p_binary.ack_size = 0;
+ debug_print("MP: Sending 200 OK message\n");
+ msnp2p_send(session, msnp2p_binary, mpsession->username, msnslp_block, strlen(msnslp_block)+1, 0);
+ free(msnslp_block);
+
+ local_avatar = avatars_local();
+ glob(local_avatar, GLOB_TILDE, NULL, &glob_result);
+ free(local_avatar);
+
+ statbuf = &stat_buffer;
+ if ( stat(glob_result.gl_pathv[0], statbuf) == -1 ) {
+ debug_print("MP: Couldn't find avatar file :(\n");
+ free(mpsession);
+ return NULL;
+ }
+
+ size = (int)statbuf->st_size;
+ assert(size > 0);
+
+ fh = fopen(glob_result.gl_pathv[0], "r");
+ if ( fh == NULL ) {
+ debug_print("MP: Couldn't open avatar file.\n");
+ free(mpsession);
+ return NULL;
+ }
+ picture_data = malloc(size);
+ readval = fread(picture_data, 1, size, fh);
+ if ( readval < 0 ) {
+ debug_print("MP: Couldn't read avatar file.\n");
+ free(mpsession);
+ free(picture_data);
+ return NULL;
+ }
+ debug_print("MP: Read %i bytes of avatar file\n", readval);
+ fclose(fh);
+ globfree(&glob_result);
+
+ msnp2p_binary.session_id = GINT32_TO_LE(mpsession->session_id);
+ msnp2p_binary.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment);
+ msnp2p_inc(mpsession);
+ msnp2p_binary.offset = 0;
+ msnp2p_binary.total_size = GINT64_TO_LE(4);
+ msnp2p_binary.size = GINT64_TO_LE(4);
+ msnp2p_binary.flags = 0;
+ msnp2p_binary.ack_sess = GINT32_TO_LE(123456);
+ msnp2p_binary.ack_id = 0;
+ msnp2p_binary.ack_size = 0;
+ zero = 0;
+ debug_print("MP: Sending data preparation message\n");
+ msnp2p_send(session, msnp2p_binary, mpsession->username, (char *)&zero, 4, 1);
+
+ msnp2p_binary.session_id = GINT32_TO_LE(mpsession->session_id);
+ msnp2p_binary.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment);
+ msnp2p_inc(mpsession);
+ msnp2p_binary.total_size = GINT64_TO_LE(size);
+ msnp2p_binary.flags = GINT32_TO_LE(0x20);
+ msnp2p_binary.ack_id = 0;
+ msnp2p_binary.ack_size = 0;
+
+ for ( file_offs=0; file_offs<size; file_offs+=1202 ) {
+
+ msnp2p_binary.offset = file_offs;
+ if ( (size-file_offs) > 1201 ) {
+ msnp2p_binary.size = GINT64_TO_LE(1202);
+ } else {
+ msnp2p_binary.size = GINT64_TO_LE((size-file_offs) % 1202);
+ }
+ msnp2p_binary.ack_sess = msnp2p_cookie();
+ debug_print("MP: Sending %i bytes: %i-%i of %i\n", msnp2p_binary.size, (int)msnp2p_binary.offset, (int)(msnp2p_binary.offset+msnp2p_binary.size)-1, (int)msnp2p_binary.total_size);
+ msnp2p_send(session, msnp2p_binary, mpsession->username, picture_data+file_offs, msnp2p_binary.size, 1);
+
+ }
+
+ free(picture_data);
+
+ return mpsession;
+
+}
+
+static MpSession *msnp2p_incomingsession(SbSession *session, const char *msnslp, MpHeader *mpheader) {
+
+ MpSession *mpsession;
+ char *temp_name;
+ char *session_id_string;
+ unsigned int session_id;
+ MpHeader acknowledgement;
+ char *euf_guid;
+ char *content_type;
+
+ if ( strncmp(msnslp, "INVITE MSNMSGR:", 15) != 0 ) {
+ return NULL;
+ }
+
+ content_type = mime_getfield(msnslp, "Content-Type");
+ if ( strcmp(content_type, "application/x-msnmsgr-transreqbody") == 0 ) {
+ }
+ free(content_type);
+
+ session_id_string = mime_getfield_anywhere(msnslp, "SessionID:");
+ if ( strlen(session_id_string) == 0 ) {
+ debug_print("MP: Couldn't find SessionID field.\n");
+ free(session_id_string);
+ return NULL;
+ }
+ session_id = atoi(session_id_string);
+ free(session_id_string);
+ /* Return if this packet isn't to be dealt with here. */
+ if ( session_id == 0 ) {
+ return NULL;
+ }
+
+ euf_guid = mime_getfield_anywhere(msnslp, "EUF-GUID:");
+ if ( strlen(euf_guid) == 0 ) {
+ debug_print("MP: Couldn't find EUF-GUID field.\n");
+ return NULL;
+ }
+
+ mpsession = msnp2p_new();
+ if ( mpsession == NULL ) {
+ debug_print("MP: Not enough memory to start MSNP2P transfer.\n");
+ return NULL;
+ }
+
+ if ( strcmp(euf_guid, "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}") == 0 ) {
+ mpsession->type = MSNP2P_TYPE_DP;
+ } else if ( strcmp(euf_guid, "{5D3E02AB-6190-11D3-BBBB-00C04F795683}") == 0 ) {
+ mpsession->type = MSNP2P_TYPE_FT;
+ } else {
+ debug_print("MP: Unrecognised MSNSLP INVITE.\n");
+ free(euf_guid);
+ return NULL;
+ }
+ free(euf_guid);
+
+ mpsession->parent = session;
+ mpsession->baseidentifier = msnp2p_cookie();
+ mpsession->id_increment = -3;
+ mpsession->session_id = session_id;
+ mpsession->direction = MSNP2P_DIR_SEND;
+ mpsession->save_filename = NULL;
+ mpsession->sha1d = NULL;
+
+ /* Get actual username. */
+ temp_name = mime_getfield(msnslp, "From:");
+ if ( temp_name == NULL ) {
+ debug_print("MP: Couldn't find From field.\n");
+ free(mpsession);
+ return NULL;
+ }
+ if ( strncmp(temp_name, "<msnmsgr:", 9) != 0 ) {
+ debug_print("MP: MSNSLP request not from \"<msnmsgr:*>\" - giving up.\n");
+ free(mpsession);
+ return NULL;
+ }
+ mpsession->username = strdup(temp_name+9);
+ free(temp_name);
+ if ( mpsession->username == NULL ) {
+ debug_print("MP: Couldn't find From field.\n");
+ free(mpsession);
+ return NULL;
+ }
+ mpsession->username[strlen(mpsession->username)-1] = '\0';
+
+ switch ( mpsession->type ) {
+ case MSNP2P_TYPE_DP : debug_print("MP: Got Display Picture Invite from %s\n", mpsession->username); break;
+ case MSNP2P_TYPE_FT : debug_print("MP: Got File Transfer Invite from %s\n", mpsession->username); break;
+ case MSNP2P_TYPE_INK : debug_print("MP: This doesn't happen.\n"); break;
+ }
+
+ debug_print("MSNSLP: '%s'\n", msnslp);
+
+ acknowledgement.session_id = 0;
+ acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier);
+ acknowledgement.offset = 0;
+ acknowledgement.total_size = mpheader->total_size;
+ acknowledgement.size = 0;
+ acknowledgement.flags = GINT32_TO_LE(2);
+ acknowledgement.ack_sess = mpheader->identifier;
+ acknowledgement.ack_id = mpheader->ack_sess;
+ acknowledgement.ack_size = mpheader->total_size;
+ debug_print("MP: Sending BaseIdentifier message\n");
+ msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0);
+
+ if ( mpsession->type == MSNP2P_TYPE_DP ) {
+ mpsession = msnp2p_send_dp(mpsession, session, msnslp);
+ }
+
+ if ( mpsession != NULL ) {
+ msnp2p_list = g_list_append(msnp2p_list, mpsession);
+ }
+
+ return mpsession;
+
+}
+
+static void msnp2p_sendbye(SbSession *session, MpSession *mpsession) {
+
+ char *msnslp_block;
+ MpHeader mpheader;
+ int msnslp_length;
+
+ msnslp_block = malloc( 294 + 2*strlen(mpsession->username) + strlen(options_username()) );
+ strcpy(msnslp_block, "BYE MSNMSGR:");
+ strncat(msnslp_block, mpsession->username, 64);
+ strcat(msnslp_block, " MSNSLP/1.0\r\nTo: <msnmsgr:");
+ strncat(msnslp_block, mpsession->username, 64);
+ strcat(msnslp_block, ">\r\nFrom: <msnmsgr:");
+ strncat(msnslp_block, options_username(), 64);
+ strcat(msnslp_block, ">\r\nVia: MSNSLP/1.0/TLP ;branch={33517CE4-02FC-4428-B6F4-39927229B722}\r\nCSeq: 0 \r\nCall-ID: ");
+ strncat(msnslp_block, mpsession->call_id, 38);
+ strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionclosebody\r\nContent-Length: 3");
+ strcat(msnslp_block, "\r\n\r\n");
+
+ msnslp_length = strlen(msnslp_block);
+
+ mpheader.session_id = 0;
+ mpheader.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment);
+ msnp2p_inc(mpsession);
+ mpheader.offset = 0;
+ mpheader.total_size = GINT64_TO_LE(msnslp_length+1);
+ mpheader.size = GINT64_TO_LE(msnslp_length+1);
+ mpheader.flags = 0;
+ mpheader.ack_sess = GINT32_TO_LE(mpsession->session_id);
+ mpheader.ack_id = 0;
+ mpheader.ack_size = 0;
+
+ debug_print("MP: Sending MSNSLP BYE for display picture.\n");
+ msnp2p_send(session, mpheader, mpsession->username, msnslp_block, msnslp_length+1, 0);
+
+ free(msnslp_block);
+
+}
+
+static void msnp2p_handledatapacket(SbSession *session, MpHeader *mpheader, MpSession *mpsession, const char *msnslp) {
+
+ FILE *fh;
+
+ debug_print("MP: Got data packet: %i-%i of %i\n", (int)mpheader->offset, (int)(mpheader->offset+mpheader->size)-1, (int)mpheader->total_size);
+
+ fh = fopen(mpsession->save_filename, "a");
+ if ( fh == NULL ) {
+ debug_print("MP: Couldn't open avatar file to write data.\n");
+ return;
+ }
+ fwrite(msnslp, 1, mpheader->size, fh);
+ fclose(fh);
+
+ /* Got all of data? */
+ if ( mpheader->offset+mpheader->size >= mpheader->total_size ) {
+
+ /* Send an acknowledgement for the data */
+ MpHeader acknowledgement;
+
+ acknowledgement.session_id = mpheader->session_id;
+ acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment);
+ msnp2p_inc(mpsession);
+ acknowledgement.offset = 0;
+ acknowledgement.total_size = 0;
+ acknowledgement.size = 0;
+ acknowledgement.flags = GINT32_TO_LE(2);
+ acknowledgement.ack_sess = mpheader->session_id;
+ acknowledgement.ack_id = mpheader->identifier;
+ acknowledgement.ack_size = mpheader->total_size;
+
+ msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0);
+
+ msnp2p_sendbye(session, mpsession);
+ contactlist_picturekick_sha1d(mpsession->sha1d);
+
+ /* Delay before deletion allows straggling packets to be collected. */
+ debug_print("MP: Closing MSNP2P session in ten seconds...\n");
+ mpsession->delete_timeout = g_timeout_add(10000, (GSourceFunc)msnp2p_closesession, mpsession);
+
+ }
+
+}
+
+static MpSession *msnp2p_check_ink(SbSession *session, const char *username, MpHeader *header, const void *data, unsigned long int app_id) {
+
+ MpSession *mpsession;
+ size_t datalength = GINT32_TO_LE(header->size);
+
+ if ( !(header->session_id == 64) || !(app_id == 3) ) {
+ return NULL;
+ }
+
+ debug_print("MP: Ink packet from '%s'\n", username);
+
+ if ( header->offset == 0 ) {
+
+
+ debug_print("MP: First in a sequence of Ink packets.\n");
+ mpsession = msnp2p_new();
+
+ mpsession->baseidentifier = GINT32_TO_LE(header->identifier);
+ mpsession->type = MSNP2P_TYPE_INK;
+ mpsession->direction = MSNP2P_DIR_RECV;
+ mpsession->parent = session;
+ mpsession->username = strdup(username);
+ mpsession->session_id = 0;
+ mpsession->via = NULL;
+ mpsession->save_filename = NULL;
+ mpsession->sha1d = NULL;
+ mpsession->call_id = NULL;
+
+ if ( datalength <= 2000 ) { /* Sanity check */
+
+ mpsession->data = malloc(datalength);
+ memcpy(mpsession->data, data, datalength);
+ mpsession->offset = datalength;
+
+ } else {
+
+ debug_print("MP: Ink packet too big - binning.\n");
+ free(mpsession);
+ return NULL;
+
+ }
+
+ msnp2p_list = g_list_append(msnp2p_list, mpsession);
+
+ } else {
+
+ mpsession = msnp2p_find_identifier(GINT32_TO_LE(header->identifier));
+ if ( mpsession == NULL ) {
+ debug_print("MP: Unrecognised Ink packet.\n");
+ return NULL;
+ }
+
+ if ( datalength <= 2000 ) {
+
+ mpsession->data = realloc(mpsession->data, (mpsession->offset) + datalength);
+ if ( mpsession->data == NULL ) {
+ debug_print("MP: Whoops! Couldn't allocate memory.\n");
+ return NULL;
+ }
+ memcpy((mpsession->data)+(mpsession->offset), data, datalength);
+ mpsession->offset += datalength;
+ debug_print("MP: Got some continuing Ink data.\n");
+
+ } else {
+ debug_print("MP: Ink packet too big - binning.\n");
+ return NULL;
+ }
+
+
+ }
+
+ assert(mpsession != NULL);
+
+ /* NB It's possible for a single message to be both the first and last in a sequence. */
+ debug_print("MP: %i of %i bytes.\n", GINT64_TO_LE(header->offset), GINT64_TO_LE(header->total_size));
+ if ( GINT64_TO_LE(header->offset) >= GINT64_TO_LE(header->total_size) ) {
+
+ gchar *body;
+ glong new_length;
+ glong items_in;
+ GError *error;
+
+ debug_print("MP: Last in a sequence of Ink packets.\n");
+
+ debug_print("MP: Decoding UTF-16: %i bytes in.\n", mpsession->offset);
+ body = g_utf16_to_utf8(mpsession->data, mpsession->offset, &items_in, &new_length, &error);
+ debug_print("MP: %i items in, %i items out.\n", items_in, new_length);
+ if ( error == NULL ) {
+ debug_print("MP: No error.\n");
+ } else {
+ debug_print("MP: Error!\n");
+ debug_print("%s'\n", error->message);
+ }
+ if ( body == NULL ) {
+ debug_print("MP: Conversion to UTF-8 failed: '%s'\n", error->message);
+ } else {
+ sbprotocol_parsemsg(session, body, new_length);
+ debug_print("MP: Ink (%i bytes): '%s'\n", new_length, body);
+ g_free(body);
+ }
+
+ debug_print("MP: Closing MSNP2P session in ten seconds...\n");
+ mpsession->delete_timeout = g_timeout_add(10000, (GSourceFunc)msnp2p_closesession, mpsession);
+
+ }
+
+ return mpsession;
+
+}
+
+void msnp2p_parsemsg(SbSession *session, const char *username, const char *whole_msg, size_t len) {
+
+ char *p2p_dest;
+ MpSession *mpsession;
+ MpHeader *mpheader;
+ const char *msnslp;
+ const char *msg;
+ unsigned long int app_id;
+
+ /* First check the destination of the message and ignore if appropriate. */
+ p2p_dest = mime_getfield(whole_msg, "P2P-Dest");
+ if ( p2p_dest == NULL ) {
+ debug_print("MP: Couldn't find P2P-Dest field.\n");
+ return;
+ }
+ if ( strcasecmp(options_username(), p2p_dest) != 0 ) {
+ free(p2p_dest);
+ debug_print("MP: Ignoring misaddressed MSNP2P message.\n");
+ return;
+ }
+ free(p2p_dest);
+
+ /* Now forget about the MIME header. */
+ len -= mime_headerlength(whole_msg);
+ msg = mime_getbody(whole_msg);
+ mpheader = (MpHeader *)msg; /* First bytes of "msg" are the header. Obviously. */
+ msnslp = msg+sizeof(MpHeader); /* Contents */
+
+ /* Check the provided packet size is sane. */
+ if ( len < GINT64_TO_LE(mpheader->size) ) {
+ debug_print("MP: 'size' field in header too big - rejecting packet.\n");
+ return;
+ }
+ app_id = ntohl(*(int *)(msg+(mpheader->size)+48));
+
+#ifdef MSNP2P_DEBUG
+ debug_print("MP: ---- MSNP2P packet ----\n");
+ debug_print("MP: sessionid = 0x%lx\n", mpheader->session_id);
+ debug_print("MP: identifier = 0x%lx\n", mpheader->identifier);
+ debug_print("MP: offset = 0x%llx\n", mpheader->offset);
+ debug_print("MP: total_size = 0x%llx\n", mpheader->total_size);
+ debug_print("MP: size = 0x%lx\n", mpheader->size);
+ debug_print("MP: flags = 0x%lx\n", mpheader->flags);
+ debug_print("MP: ack_id = 0x%lx\n", mpheader->ack_id);
+ debug_print("MP: ack_sess = 0x%lx\n", mpheader->ack_sess);
+ debug_print("MP: ack_size = 0x%llx\n", mpheader->ack_size);
+ debug_print("MP: app_id = 0x%lx\n", app_id);
+#endif /* MSNP2P_DEBUG */
+
+ /* Arrrggggggggggghhhhhhhhhhhhhhhhhhhhhh. */
+ mpsession = msnp2p_find_identifier(GINT32_TO_LE(mpheader->ack_sess));
+ if ( mpsession == NULL ) {
+ mpsession = msnp2p_find_sessionid(GINT32_TO_LE(mpheader->session_id));
+ }
+ if ( mpsession == NULL ) {
+ mpsession = msnp2p_check_ok(msnslp);
+ }
+ if ( mpsession == NULL ) {
+ mpsession = msnp2p_find_sessionid(GINT32_TO_LE(mpheader->ack_id));
+ }
+ if ( (mpsession == NULL) && (mpheader->session_id == 0) ) {
+ mpsession = msnp2p_incomingsession(session, msnslp, mpheader);
+ }
+ if ( msnp2p_check_bye(session, msnslp, mpheader) != 0 ) {
+ debug_print("MP: Got BYE.\n");
+ return;
+ }
+ if ( mpsession == NULL ) {
+ mpsession = msnp2p_check_ink(session, username, mpheader, msnslp, app_id);
+ }
+
+ /* Past this point, if the message hasn't been matched to a session then it's unrecognised. */
+
+ if ( mpsession == NULL ) {
+ debug_print("MP: Unrecognised MSNP2P ");
+ if ( (GINT32_TO_LE(mpheader->flags) & 2) != 0 ) {
+ debug_print("ack.\n");
+ } else {
+ debug_print("packet:\n");
+ debug_print("MP: Flags = %x\n", GINT32_TO_LE(mpheader->flags));
+ debug_print("MP: Data: '%s'\n", msnslp);
+ }
+ return;
+ }
+
+ if ( ( GINT32_TO_LE(mpheader->flags) & 2) != 0 ) {
+ debug_print("MP: Got ACK.\n");
+ }
+
+ if ( ( (GINT32_TO_LE(mpheader->flags) & 2) == 0) && (mpheader->size == mpheader->total_size) ) {
+
+ /* Message wasn't an ACK, so probably needs an ACK sending */
+
+ MpHeader acknowledgement;
+
+ acknowledgement.session_id = GINT32_TO_LE(mpheader->session_id);
+ acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment);
+ /* msnp2p_inc(mpsession); */
+ acknowledgement.offset = 0;
+ acknowledgement.total_size = 0;
+ acknowledgement.size = 0;
+ if ( GINT32_TO_LE(mpheader->flags) == 0x40 ) {
+ acknowledgement.flags = GINT32_TO_LE(0x80);
+ } else {
+ acknowledgement.flags = GINT32_TO_LE(2);
+ }
+ /* Copying directly from received header: don't touch endianness. */
+ acknowledgement.ack_sess = mpheader->session_id;
+ acknowledgement.ack_id = mpheader->identifier;
+ acknowledgement.ack_size = mpheader->size;
+
+ debug_print("MP: Sending ACK.\n");
+ msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0);
+
+ }
+
+ if ( (app_id == 1) && (GINT64_TO_LE(mpheader->total_size) != 4) ) {
+ msnp2p_handledatapacket(session, mpheader, mpsession, msnslp);
+ }
+
+}
+
+/* Check for a receive session for the DP SHA1D given. */
+int msnp2p_retrieving(const char *save_filename) {
+
+ GList *result = NULL;
+
+ result = g_list_find_custom(msnp2p_list, save_filename, (GCompareFunc)msnp2p_compare_savefilename);
+ if ( result != NULL ) {
+ /* At least one session exists. Don't care what it is... */
+ return 1;
+ }
+
+ /* Nothing found. */
+ return 0;
+
+}
+
+/* Initiate the downloading of a picture. */
+void msnp2p_getpicture(SbSession *session, const char *username, const char *dpobject) {
+
+ MpSession *mpsession;
+ MpHeader mpheader;
+
+ char *dpobject_decoded;
+ char *context;
+ char *data_hash;
+ char *filename;
+
+ char *request_details;
+ int request_details_length;
+ char *request_details_length_string;
+ char *msnslp_block;
+ int msnslp_length;
+ glob_t glob_result;
+ struct stat stat_buffer;
+ struct stat *statbuf;
+
+ debug_print("MP: Beginning download for DP from '%s'\n", username);
+
+ assert(username != NULL);
+ assert(session != NULL);
+
+ if ( dpobject == NULL ) {
+ debug_print("MP: Attempt to download avatar for user who doesn't have one.\n");
+ return;
+ }
+
+ if ( sbsessions_sessionready(session) == 0 ) {
+ debug_print("MP: Session isn't ready to download picture - not trying.\n");
+ return;
+ }
+
+ mpsession = msnp2p_new();
+ if ( mpsession == NULL ) {
+ debug_print("MP: Not enough memory to do picture request - giving up.\n");
+ return;
+ }
+
+ mpsession->parent = session;
+ mpsession->baseidentifier = msnp2p_cookie();
+ mpsession->session_id = msnp2p_cookie();
+ mpsession->direction = MSNP2P_DIR_RECV;
+ mpsession->id_increment = -3;
+ mpsession->call_id = routines_guid();
+ mpsession->via = NULL;
+
+ debug_print("MP: Generated: Session ID '%i', BaseIdentifier '%i', Call-ID '%s'.\n", mpsession->session_id, mpsession->baseidentifier, mpsession->call_id);
+
+ /* urldecode() the MSNObject and then base64 it to get the "Context" field,
+ then retrieve the "SHA1D" field from the (urldecoded) MSNObject to generate
+ the filename to save to. */
+ dpobject_decoded = routines_urldecode(dpobject);
+ context = routines_base64(dpobject_decoded);
+ data_hash = xml_getfield(dpobject_decoded, "SHA1D");
+ debug_print("MP: Got data hash: %s\n", data_hash);
+ free(dpobject_decoded);
+ filename = xml_killillegalchars(data_hash);
+ mpsession->sha1d = data_hash; /* Don't free this yet. */
+
+ /* Determine the full filename to save to. */
+ glob("~", GLOB_TILDE, NULL, &glob_result);
+ mpsession->save_filename = malloc(strlen(filename)+strlen("/.tuxmessenger/avatars/")+strlen(glob_result.gl_pathv[0]) + 1);
+ if ( mpsession->save_filename == NULL ) {
+
+ debug_print("MP: Not enough memory to do picture request - giving up.\n");
+ free(mpsession->call_id);
+ free(mpsession);
+ return;
+
+ }
+ strcpy(mpsession->save_filename, glob_result.gl_pathv[0]);
+ strcat(mpsession->save_filename, "/.tuxmessenger/avatars/");
+ strcat(mpsession->save_filename, filename);
+ globfree(&glob_result);
+ free(filename);
+
+ /* Check to see there isn't already a receive session going on for this user. */
+
+ if ( msnp2p_retrieving(mpsession->save_filename) != 0 ) {
+
+ debug_print("MP: A DP receive session is already in progress to '%s' - not starting a new one.\n", mpsession->save_filename);
+ free(mpsession->call_id);
+ free(mpsession);
+ return;
+
+ }
+
+ /* Check if we already have this picture or not */
+ statbuf = &stat_buffer;
+ if ( stat(mpsession->save_filename, statbuf) != -1 ) {
+
+ /* messagewindow_trypicture should have worked this out before. */
+ debug_print("MP: Already have this picture: %s\n", mpsession->save_filename);
+ free(mpsession->save_filename);
+ free(mpsession->call_id);
+ free(mpsession);
+ return;
+
+ }
+
+ /* Silly preliminaries out of the way. Time to do the request... */
+
+ /* Create the payload for the MSNSLP request */
+ request_details = malloc( 96 + strlen(context) + 9 );
+ if ( request_details == NULL ) {
+
+ debug_print("MP: Not enough memory to do picture request - giving up.\n");
+ free(mpsession->save_filename);
+ free(mpsession->call_id);
+ free(mpsession);
+ return;
+
+ }
+ strcpy(request_details, "EUF-GUID: {A4268EEC-FEC5-49E5-95C3-F126696BDBF6}\r\nSessionID: ");
+ /* Check we're not about to screw ourselves over. 8 bytes were allowed
+ above to fit the sessionid into. */
+ if ( mpsession->session_id > 99999999 ) {
+
+ debug_print("MP: SessionID is too big - giving up.\n");
+ free(request_details);
+ free(mpsession->save_filename);
+ free(mpsession->call_id);
+ free(mpsession);
+ return;
+
+ }
+ sprintf(request_details+strlen(request_details), "%i", mpsession->session_id);
+ strcat(request_details, "\r\nAppID: 1\r\nContext: ");
+ strcat(request_details, context);
+ free(context);
+ strcat(request_details, "\r\n\r\n");
+ request_details_length = strlen(request_details);
+
+ request_details_length_string = malloc(5);
+ assert(request_details_length < 9998); /* Sanity check. */
+ sprintf(request_details_length_string, "%i", request_details_length+1);
+
+ /* Now create the MSNSLP request itself */
+ msnslp_block = malloc( 291 + (2*strlen(username)) + strlen(options_username()) + strlen(request_details) + strlen(request_details_length_string) );
+ strcpy(msnslp_block, "INVITE MSNMSGR:");
+ strncat(msnslp_block, username, 64);
+ strcat(msnslp_block, " MSNSLP/1.0\r\nTo: <msnmsgr:");
+ strncat(msnslp_block, username, 64);
+ strcat(msnslp_block, ">\r\nFrom: <msnmsgr:");
+ strncat(msnslp_block, options_username(), 64);
+ strcat(msnslp_block, ">\r\nVia: MSNSLP/1.0/TLP ;branch={33517CE4-02FC-4428-B6F4-39927229B722}\r\nCSeq: 0 \r\nCall-ID: ");
+ strncat(msnslp_block, mpsession->call_id, 38);
+ strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionreqbody\r\nContent-Length: ");
+ strncat(msnslp_block, request_details_length_string, 30);
+ free(request_details_length_string);
+ strcat(msnslp_block, "\r\n\r\n");
+ strcat(msnslp_block, request_details);
+ free(request_details);
+ msnslp_length = strlen(msnslp_block);
+
+ debug_print("MP: Sending MSNSLP INVITE for display picture.\n");
+
+ /* Now wrap it up with the MSNP2P rubbish */
+ mpheader.session_id = 0;
+ mpheader.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment);
+ msnp2p_inc(mpsession);
+ mpheader.offset = 0;
+ mpheader.total_size = GINT64_TO_LE(msnslp_length+1);
+ mpheader.size = GINT64_TO_LE(msnslp_length+1);
+ mpheader.flags = 0;
+ mpheader.ack_sess = GINT32_TO_LE(mpsession->session_id);
+ mpheader.ack_id = 0;
+ mpheader.ack_size = 0;
+
+ msnp2p_send(session, mpheader, username, msnslp_block, strlen(msnslp_block)+1, 0);
+
+ free(msnslp_block);
+
+ mpsession->username = strdup(username);
+ msnp2p_list = g_list_append(msnp2p_list, mpsession);
+
+}
+
+/* Bin all the MSNP2P/MSNSLP sessions in a given switchboard session. */
+void msnp2p_abortall(SbSession *session) {
+
+ GList *result;
+
+ result = g_list_find_custom(msnp2p_list, &session, (GCompareFunc)msnp2p_compare_parent);
+
+ while ( result != NULL ) {
+
+ MpSession *mpsession;
+ mpsession = result->data;
+ msnp2p_closesession(mpsession);
+
+ result = g_list_find_custom(msnp2p_list, &session, (GCompareFunc)msnp2p_compare_parent);
+
+ }
+
+}
+
+void msnp2p_offerfile(SbSession *session, const char *username, const char *filename) {
+
+ MpSession *mpsession;
+ MpHeader mpheader;
+ char *context;
+ char *request_details;
+ size_t request_details_length;
+ char *request_details_length_string;
+ char *msnslp_block;
+ size_t msnslp_length;
+
+ assert(session != NULL);
+ assert(username != NULL);
+ assert(filename != NULL);
+
+ mpsession = msnp2p_new();
+ if ( mpsession == NULL ) {
+ debug_print("MP: Not enough memory to send file - giving up.\n");
+ return;
+ }
+
+ mpsession->parent = session;
+ mpsession->baseidentifier = msnp2p_cookie();
+ mpsession->session_id = msnp2p_cookie();
+ mpsession->direction = MSNP2P_DIR_SEND;
+ mpsession->id_increment = 0;
+ mpsession->call_id = routines_guid();
+ mpsession->via = NULL;
+
+ context = strdup("WIBBLE!!!112"); /* File preview data... */
+
+ debug_print("MP: Generated: Session ID '%i', BaseIdentifier '%i', Call-ID '%s'.\n", mpsession->session_id, mpsession->baseidentifier, mpsession->call_id);
+
+ /* Create the payload for the MSNSLP request */
+ request_details = malloc( 96 + strlen(context) + 9 );
+ if ( request_details == NULL ) {
+
+ debug_print("MP: Not enough memory to do picture request - giving up.\n");
+ free(mpsession->save_filename);
+ free(mpsession->call_id);
+ free(mpsession);
+ return;
+
+ }
+ strcpy(request_details, "EUF-GUID: {5D3E02AB-6190-11D3-BBBB-00C04F795683}\r\nSessionID: ");
+ /* Check we're not about to screw ourselves over. 8 bytes were allowed
+ above to fit the sessionid into. */
+ if ( mpsession->session_id > 99999999 ) {
+
+ debug_print("MP: SessionID is too big - giving up.\n");
+ free(request_details);
+ free(mpsession->call_id);
+ free(mpsession);
+ return;
+
+ }
+ sprintf(request_details+strlen(request_details), "%i", mpsession->session_id);
+ strcat(request_details, "\r\nAppID: 1\r\nContext: ");
+ strcat(request_details, context);
+ free(context);
+ strcat(request_details, "\r\n\r\n");
+ request_details_length = strlen(request_details);
+
+ request_details_length_string = malloc(5);
+ assert(request_details_length < 9998); /* Sanity check. */
+ sprintf(request_details_length_string, "%i", request_details_length+1);
+
+ /* Now create the MSNSLP request itself */
+ msnslp_block = malloc( 291 + (2*strlen(username)) + strlen(options_username()) + strlen(request_details) + strlen(request_details_length_string) );
+ strcpy(msnslp_block, "INVITE MSNMSGR:");
+ strncat(msnslp_block, username, 64);
+ strcat(msnslp_block, " MSNSLP/1.0\r\nTo: <msnmsgr:");
+ strncat(msnslp_block, username, 64);
+ strcat(msnslp_block, ">\r\nFrom: <msnmsgr:");
+ strncat(msnslp_block, options_username(), 64);
+ strcat(msnslp_block, ">\r\nVia: MSNSLP/1.0/TLP ;branch={33517CE4-02FC-4428-B6F4-39927229B722}\r\nCSeq: 0 \r\nCall-ID: ");
+ strncat(msnslp_block, mpsession->call_id, 38);
+ strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionreqbody\r\nContent-Length: ");
+ strncat(msnslp_block, request_details_length_string, 30);
+ free(request_details_length_string);
+ strcat(msnslp_block, "\r\n\r\n");
+ strcat(msnslp_block, request_details);
+ free(request_details);
+ msnslp_length = strlen(msnslp_block);
+
+ debug_print("MP: Sending MSNSLP INVITE for file transfer...\n");
+
+ /* Now wrap it up with the MSNP2P rubbish */
+ mpheader.session_id = 0;
+ mpheader.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment);
+ msnp2p_inc(mpsession);
+ mpheader.offset = 0;
+ mpheader.total_size = GINT64_TO_LE(msnslp_length+1);
+ mpheader.size = GINT64_TO_LE(msnslp_length+1);
+ mpheader.flags = 0;
+ mpheader.ack_sess = GINT32_TO_LE(mpsession->session_id);
+ mpheader.ack_id = 0;
+ mpheader.ack_size = 0;
+
+ msnp2p_send(session, mpheader, username, msnslp_block, strlen(msnslp_block)+1, 0);
+
+ free(msnslp_block);
+
+ mpsession->username = strdup(username);
+ msnp2p_list = g_list_append(msnp2p_list, mpsession);
+
+}
diff --git a/src/msnp2p.h b/src/msnp2p.h
new file mode 100644
index 0000000..25ae61d
--- /dev/null
+++ b/src/msnp2p.h
@@ -0,0 +1,36 @@
+/*
+ * msnp2p.h
+ *
+ * MSNP2P handling
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef MSNP2P_H
+#define MSNP2P_H
+
+#include "sbsessions.h"
+
+extern void msnp2p_parsemsg(SbSession *session, const char *username, const char *msg, size_t len);
+extern void msnp2p_getpicture(SbSession *session, const char *username, const char *dpobject);
+extern void msnp2p_abortall(SbSession *session);
+extern int msnp2p_retrieving(const char *dpsha1d);
+extern void msnp2p_offerfile(SbSession *session, const char *username, const char *filename);
+
+#endif /* MSNP2P_H */
diff --git a/src/msnprotocol.c b/src/msnprotocol.c
new file mode 100644
index 0000000..fe7635e
--- /dev/null
+++ b/src/msnprotocol.c
@@ -0,0 +1,1692 @@
+/*
+ * msnprotocol.c
+ *
+ * Low-level DS/NS protocol handling
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <errno.h>
+#include <gdk/gdk.h>
+#include <assert.h>
+#include <string.h>
+#include <openssl/md5.h>
+
+#include "mainwindow.h"
+#include "error.h"
+#include "options.h"
+#include "debug.h"
+#include "routines.h"
+#include "msnprotocol.h"
+#include "listcache.h"
+#include "twnauth.h"
+#include "contactlist.h"
+#include "msngenerics.h"
+#include "listcache.h"
+#include "sbsessions.h"
+#include "sbprotocol.h"
+#include "avatars.h"
+#include "msnp11chl.h"
+#include "xml.h"
+#include "addcontact.h"
+
+typedef enum {
+ CSTATE_DISCONNECTED, /* Offline, disconnected, mainwindow in dispatch mode */
+ CSTATE_SIGNIN, /* Connected and in the process of signing in */
+ CSTATE_LIST, /* Receiving list data */
+ CSTATE_CONNECTED /* Fully logged in */
+} CState;
+
+/* Part of something messy to avoid duplicating code */
+typedef enum {
+ NLN_NLN,
+ NLN_ILN
+} nln_t;
+
+typedef enum {
+ NSCONMODE_LINE,
+ NSCONMODE_MSG,
+ NSCONMODE_UBX,
+ NSCONMODE_NOT,
+ NSCONMODE_GCF_SHIELDS
+} NSConMode;
+
+static struct {
+
+ int connect_lock; /* Prevent a second connection attempt from starting */
+ int socket; /* The DS or NS connection socket (only one exists at a time */
+ gint wcallback; /* GDK "writeable" callback for the socket */
+ gint rcallback; /* GDK "readable" callback for the socket */
+ unsigned int trid; /* TrID to be used for the next transaction sent */
+ CState connected; /* Indicator of progress into the login procedure */
+ int disconnect_expected; /* Suppress "Read error" message if the disconnection was anticipated */
+ gint disconnect_callback; /* Callback tag for forcing cleanup if disconnection fails. */
+ OnlineState status; /* Current online status */
+ int lst_num; /* Number of LSTs so far received */
+ int lst_num_max; /* Expected number of LSTs (from SYN) */
+ gint ping_callback; /* Callback tag for sending pings */
+ int protocol_version; /* MSNP version in use */
+ NSConMode conmode; /* What's being read at the moment? */
+ size_t expect_length; /* Amount of MSG/UBX etc expected. */
+ char *msg_source; /* Source of MSG/UBX etc */
+
+ /* Read/write buffers */
+ char *wbuffer; /* Buffer for outgoing data */
+ unsigned int wbufsize; /* Current size of the write buffer */
+ unsigned int woffset; /* Current offset into the write buffer */
+ char *rbuffer; /* Buffer for incoming data */
+ unsigned int rbufsize; /* Current size of the read buffer */
+ unsigned int roffset; /* Current offset into the read buffer */
+
+} cstate = {
+
+ 0,
+ -1,
+ 0,
+ 0,
+ 1,
+ CSTATE_DISCONNECTED,
+ 0,
+ 0,
+ ONLINE_FLN,
+ 0,
+ 0,
+ 0,
+ 12,
+ NSCONMODE_LINE,
+ 0,
+ NULL,
+
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+ 0
+
+};
+
+/* Free buffers, remove callbacks etc. */
+static void msnprotocol_cleanup_internal() {
+
+ close(cstate.socket);
+
+ if ( cstate.rbuffer != NULL ) {
+ free(cstate.rbuffer);
+ }
+ cstate.rbufsize = 0;
+ cstate.rbuffer = NULL;
+ cstate.roffset = 0;
+ if ( cstate.wbuffer != NULL ) {
+ free(cstate.wbuffer);
+ }
+ cstate.wbufsize = 0;
+ cstate.wbuffer = NULL;
+ cstate.woffset = 0;
+
+ if ( cstate.rcallback != 0 ) {
+ gdk_input_remove(cstate.rcallback);
+ }
+ cstate.rcallback = 0;
+ if ( cstate.wcallback != 0 ) {
+ gdk_input_remove(cstate.wcallback);
+ }
+ cstate.wcallback = 0;
+
+ if ( cstate.disconnect_callback != 0 ) {
+ gtk_timeout_remove(cstate.disconnect_callback);
+ cstate.disconnect_callback = 0;
+ }
+
+ if ( cstate.ping_callback != 0 ) {
+ gtk_timeout_remove(cstate.ping_callback);
+ cstate.ping_callback = 0;
+ }
+
+ cstate.connect_lock = 0;
+ cstate.trid = 1;
+ cstate.disconnect_expected = 0;
+ cstate.connected = CSTATE_DISCONNECTED;
+
+ sbsessions_destroy_all();
+ messagewindow_disable_all();
+ contactlist_clear();
+
+}
+
+/* Free buffers, remove callbacks, return main window to dispatch mode, etc. */
+static void msnprotocol_cleanup() {
+
+ msnprotocol_cleanup_internal();
+ mainwindow_setdispatch();
+
+}
+
+/* If a disconnection hasn't happened ten seconds after requesting it, pull the plug. */
+static gint msnprotocol_forcedisconnect() {
+
+ int closeval;
+
+ debug_print("NS: Waited too long for disconnection... pulling the plug.\n");
+
+ cstate.disconnect_callback = 0;
+
+ closeval = close(cstate.socket);
+ if ( closeval != 0 ) {
+ /* Grrr.. now it won't close - not much we can do. */
+ debug_print("NS: Couldn't close socket after write error.\n");
+ }
+
+ msnprotocol_cleanup();
+
+ return FALSE;
+
+}
+
+/* Write a chunk of the outgoing data queue to the socket. */
+static void msnprotocol_writeable() {
+
+ ssize_t wlen;
+ int new_wbufsize;
+ char *debug_string;
+ unsigned int i;
+
+ wlen = write(cstate.socket, cstate.wbuffer, cstate.wbufsize);
+
+ debug_string = malloc(cstate.wbufsize+1);
+ memcpy(debug_string, cstate.wbuffer, cstate.wbufsize);
+ debug_string[cstate.wbufsize] = '\0';
+ for ( i=0; i<cstate.wbufsize; i++ ) {
+ if ( debug_string[i] == '\r' ) {
+ debug_string[i] = 'r';
+ }
+ if ( debug_string[i] == '\n' ) {
+ debug_string[i] = 'n';
+ }
+ }
+ debug_print("NS: send: '%s'\n", debug_string);
+ free(debug_string);
+
+ if ( wlen > 0 ) {
+
+ /* wlen holds the number of bytes written. Sort the buffer out accordingly... */
+ memmove(cstate.wbuffer, cstate.wbuffer + wlen, cstate.wbufsize - wlen);
+ new_wbufsize = cstate.wbufsize - wlen;
+ assert(new_wbufsize >= 0);
+
+ cstate.wbuffer = realloc(cstate.wbuffer, new_wbufsize);
+ if ( new_wbufsize == 0 ) {
+
+ gdk_input_remove(cstate.wcallback);
+ cstate.wcallback = 0;
+ cstate.wbuffer = NULL;
+
+ }
+ cstate.wbufsize = new_wbufsize;
+ cstate.woffset -= wlen;
+
+ } else {
+
+ if ( wlen == -1 ) {
+
+ /* Write error! :( */
+ if ( errno != EAGAIN ) { /* EAGAIN should never happen here */
+
+ /* Something bad happened */
+ int closeval;
+ closeval = close(cstate.socket);
+ if ( closeval != 0 ) {
+ /* Grrr.. now it won't close - not much we can do. */
+ debug_print("NS: Couldn't close socket after write error.\n");
+ }
+ error_report("Write error! Signed out.");
+ msnprotocol_cleanup();
+ return;
+
+ }
+
+ }
+
+ }
+
+}
+
+/* Queue a command for sending to the server, adding a TrID - returns the TrID used for this transaction. */
+static int msnprotocol_sendtr_internal(char *instr, char *args, int newline, int send_trid) {
+
+ char *trid_string = NULL;
+ unsigned int len;
+
+ len = strlen(instr);
+ if ( send_trid ) {
+
+ trid_string = malloc(8);
+ assert(cstate.trid < 999999); /* Sanity check */
+ sprintf(trid_string, "%i", cstate.trid);
+ len += 1; /* Space before the TrId */
+ len += strlen(trid_string);
+
+ }
+ if ( strlen(args) > 0 ) {
+ len += strlen(args);
+ len += 1; /* Space after the TrID */
+ }
+ if ( newline ) {
+ len += 2;
+ }
+ if ( cstate.wbufsize == 0 ) {
+
+ /* No buffer space currently exists. Create it. */
+ assert(cstate.wbuffer == NULL);
+ cstate.wbuffer = malloc(len);
+ assert(cstate.wbuffer != NULL);
+ cstate.wbufsize = len;
+ cstate.woffset = 0;
+
+ }
+ if ( (cstate.wbufsize - cstate.woffset) < len ) {
+
+ /* Write buffer isn't big enough. Make it bigger. */
+ cstate.wbuffer = realloc(cstate.wbuffer, len + cstate.woffset);
+ assert(cstate.wbuffer != NULL);
+ cstate.wbufsize = len + cstate.woffset;
+ assert(cstate.wbufsize < 1024*1024); /* Stop the buffer from getting insane */
+
+ }
+
+ /* Do the write (to memory). Deliberately verbose... */
+ memcpy(cstate.wbuffer + cstate.woffset, instr, strlen(instr));
+ cstate.woffset += strlen(instr);
+ if ( send_trid ) {
+
+ *(cstate.wbuffer + cstate.woffset) = ' ';
+ cstate.woffset += 1;
+
+ memcpy(cstate.wbuffer + cstate.woffset, trid_string, strlen(trid_string));
+ cstate.woffset += strlen(trid_string);
+ free(trid_string);
+
+ }
+ if ( strlen(args) > 0 ) {
+
+ *(cstate.wbuffer + cstate.woffset) = ' ';
+ cstate.woffset += 1;
+ memcpy(cstate.wbuffer + cstate.woffset, args, strlen(args));
+ cstate.woffset += strlen(args);
+
+ }
+ if ( newline ) {
+
+ *(cstate.wbuffer + cstate.woffset) = '\r';
+ cstate.woffset += 1;
+ *(cstate.wbuffer + cstate.woffset) = '\n';
+ cstate.woffset += 1;
+
+ }
+ /* Note the lack of a \0 terminator. It's done using records of the buffer data lengths. */
+
+ if ( cstate.wcallback == 0 ) {
+ cstate.wcallback = gdk_input_add(cstate.socket, GDK_INPUT_WRITE, (GdkInputFunction) msnprotocol_writeable, NULL);
+ }
+
+ cstate.trid++;
+ return cstate.trid-1;
+
+}
+
+/* Send a command to the server, adding a TrID and a newline */
+static int msnprotocol_sendtr(char *instr, char *args) {
+
+ return msnprotocol_sendtr_internal(instr, args, 1, 1);
+
+}
+
+static gint msnprotocol_sendping() {
+
+ msnprotocol_sendtr_internal("PNG", "", 1, 0);
+ cstate.ping_callback = 0;
+
+ return FALSE;
+
+}
+
+/* Send a command to the server, adding a TrID but no newline */
+static int msnprotocol_sendtr_nonewline(char *instr, char *args) {
+
+ return msnprotocol_sendtr_internal(instr, args, 0, 1);
+
+}
+
+static void msnprotocol_handle_lst(char *line) {
+
+ char *username = NULL;
+ char *friendlyname = NULL;
+ char *guid = NULL;
+ char *list = NULL;
+ unsigned int list_num;
+ int finished = 0;
+ int field_num = 1;
+ int list_coming = 0;
+
+ /* Step through the LST data and look for friendlyname, username and list number */
+
+ while ( !finished ) {
+
+ char *field;
+ int field_used = 0;
+ field = routines_lindex(line, field_num);
+ field_num++;
+ if ( strlen(field) == 0 ) {
+ finished = 1;
+ } else {
+
+ if ( strlen(field) > 2 ) {
+
+ if ( (field[0] == 'F') && (field[1] == '=') ) {
+ friendlyname = strdup(field+2);
+ list_coming = 1;
+ field_used = 1;
+ } else if ( (field[0] == 'N') && (field[1] == '=') ) {
+ username = strdup(field+2);
+ list_coming = 1;
+ field_used = 1;
+ } else if ( (field[0] == 'C') && (field[1] == '=') ) {
+ guid = strdup(field+2);
+ list_coming = 1;
+ field_used = 1;
+ }
+
+ }
+ /* "list" is the first field without "X=" */
+ if ( list_coming && !field_used ) {
+ list = strdup(field);
+ finished = 1; /* Otherwise the next field will get used instead. */
+ }
+
+ }
+ free(field);
+
+ }
+
+ /* Need at least a username and list to continue */
+ if ( (username == NULL) || (list == NULL) ) {
+
+ debug_print("NS: Didn't get enough information from LST: missing");
+ if ( username == NULL ) {
+ debug_print(" username");
+ } else {
+ free(username);
+ }
+ if ( list == NULL ) {
+ debug_print(" list");
+ } else {
+ free(list);
+ }
+ if ( friendlyname != NULL ) {
+ free(friendlyname); /* Don't leak memory */
+ }
+ debug_print("\n");
+
+ } else {
+
+ list_num = atoi(list);
+ free(list);
+ if ( list_num > 31 ) {
+ debug_print("NS: Warning: contact is on unrecognised list(s).\n");
+ }
+
+ if ( list_num & 1 ) {
+ contactlist_fldetails(CONTACT_SOURCE_LST, username, friendlyname, 0, NULL, ONLINE_FLN, guid);
+ }
+ if ( list_num & 2 ) {
+ contactlist_aldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN);
+ }
+ if ( list_num & 4 ) {
+ contactlist_bldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN);
+ }
+ if ( list_num & 8 ) {
+ contactlist_rldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN);
+ }
+ if ( list_num & 16 ) {
+ contactlist_pldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN);
+ }
+ if ( list_num == 8 ) {
+ /* If contact is ONLY on the RL, add them to the PL as well. This should never
+ happen with MSNP11+, but ensures that caching takes place properly. */
+ contactlist_pldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN);
+ }
+ if ( (list_num == 8) || (list_num & 16) ) {
+
+ /* On RL but not FL, AL or BL indicates new user.
+ On PL also indicates new user. */
+ addcontact_added(username, friendlyname);
+
+ }
+
+ }
+
+}
+
+/* Despite the name, handles ILN and NLN */
+static void msnprotocol_handle_nln(nln_t nln, char *line) {
+
+ OnlineState new_status;
+ char *username;
+ char *friendlyname;
+ char *features_string;
+ int features;
+ char *dpobject;
+ char *status_string;
+ int lindex_sub;
+
+ /* Subtract one from all the list indices if this isn't ILN, since there's no TrID */
+ if ( nln == NLN_ILN ) {
+ lindex_sub = 0;
+ } else {
+ lindex_sub = 1;
+ }
+
+ status_string = routines_lindex(line, 2-lindex_sub);
+ new_status = msngenerics_decodestatus(status_string);
+ assert(new_status != ONLINE_FLN); /* This would be handled elsewhere. */
+ assert(new_status != ONLINE_HDN); /* Nonsense */
+ free(status_string);
+
+ username = routines_lindex(line, 3-lindex_sub);
+ friendlyname = routines_lindex(line, 4-lindex_sub);
+ features_string = routines_lindex(line, 5-lindex_sub);
+ features = atoi(features_string);
+ free(features_string);
+ dpobject = routines_lindex(line, 6-lindex_sub);
+
+ /* Pass NULL if there's no "dpobject" */
+ if ( strlen(dpobject) == 0 ) {
+ free(dpobject);
+ dpobject = NULL;
+ }
+ if ( (dpobject != NULL) && (strcmp(dpobject, "0") == 0) ) {
+ free(dpobject);
+ dpobject = NULL;
+ }
+
+ assert(strlen(username) > 0);
+ assert(strlen(friendlyname) > 0);
+
+ /* contactlist_fldetails won't overwrite the GUID with NULL. */
+ contactlist_fldetails(CONTACT_SOURCE_NLN, username, friendlyname, features, dpobject, new_status, NULL);
+
+ /* contactlist_fldetails copies anything it's interested in */
+ free(username);
+ free(friendlyname);
+ if ( dpobject != NULL ) {
+ free(dpobject);
+ }
+
+}
+
+/* Enter main "Connected" mode. */
+static void msnprotocol_doneconnect() {
+
+ cstate.connected = CSTATE_CONNECTED;
+ mainwindow_forcestatus(ONLINE_HDN);
+
+ /* Start pinging. */
+ if ( cstate.ping_callback == 0 ) {
+ cstate.ping_callback = gtk_timeout_add(50000, (GtkFunction)msnprotocol_sendping, NULL);
+ }
+
+ if ( cstate.protocol_version >= 11 ) {
+ msnprotocol_sendtr("GCF", "Shields.xml");
+ }
+
+}
+
+/* Internally called to parse a line of text from the DS or NS */
+static int msnprotocol_parseline(char *line) {
+
+ char *instr;
+ char *err_str;
+ int err_num;
+ char *trid_string;
+ int trid;
+
+ debug_print("NS: recv: '%s'\n", line);
+
+ /* Prevent blank or short lines from being a problem */
+ if ( strlen(line) < 4 ) {
+ return 0;
+ }
+
+ instr = malloc(4);
+ strncpy(instr, line, 3);
+ instr[3] = '\0';
+
+ if ( line[3] != ' ' ) {
+ line[0] = ' ';
+ line[1] = ' ';
+ line[2] = ' '; /* Prevent bogus interpretations of things that aren't three letters */
+ }
+
+ err_num = atoi(instr);
+ err_str = malloc(4);
+ assert(err_num < 1000);
+ sprintf(err_str, "%i", err_num);
+ if ( strcmp(err_str, instr) == 0 ) {
+ error_report_server(err_num);
+ if ( cstate.connected == CSTATE_SIGNIN ) {
+ cstate.disconnect_expected = 1;
+ }
+ if ( err_num == 540 ) {
+ cstate.disconnect_expected = 1;
+ }
+ }
+ free(err_str);
+
+ trid_string = routines_lindex(line, 1);
+ trid = atoi(trid_string);
+ free(trid_string);
+
+ if ( strcmp("OUT", instr) == 0 ) {
+ char *sit = routines_lindex(line, 1);
+ if ( strcmp(sit, "OTH") == 0 ) {
+ cstate.disconnect_expected = 1;
+ error_report("Signed in somewhere else.");
+ }
+ free(sit);
+ }
+
+ if ( strcmp("VER", instr) == 0 ) {
+
+ char *string;
+ char *protocol;
+ int got_cvr = FALSE;
+ int highest_msnp = 0;
+ int pos = 1;
+
+ protocol = routines_lindex(line, pos++);
+ while ( strlen(protocol) ) {
+ if ( strncmp(protocol, "MSNP", 4) == 0 ) {
+ int vnum = atoi(protocol + 4);
+ if ( vnum > highest_msnp ) {
+ highest_msnp = vnum;
+ }
+ }
+ if ( strcmp(protocol, "CVR0") == 0 ) {
+ got_cvr = TRUE;
+ debug_print("Got CVR0.\n");
+ }
+ free(protocol);
+ protocol = routines_lindex(line, pos++);
+ }
+ debug_print("Negotiated protocol version %i\n", highest_msnp);
+ if ( highest_msnp < 11 ) {
+ debug_print("Too low. Bad luck.\n");
+ error_report("Couldn't negotiate protocol version.\n");
+ msnprotocol_cleanup_internal();
+ return -1;
+ }
+ cstate.protocol_version = highest_msnp;
+
+ string = malloc(128);
+ strcpy(string, "0x0409 winnt 5.1 i386 MSNMSGR 7.5.0311 msmsgs ");
+ strncat(string, options_username(), 64);
+ string[127] = '\0';
+ msnprotocol_sendtr("CVR", string);
+ free(string);
+
+ }
+
+ if ( strcmp("CVR", instr) == 0 ) {
+
+ char *string;
+ string = malloc(128);
+ strcpy(string, "TWN I ");
+ strncat(string, options_username(), 64);
+ string[127] = '\0';
+ msnprotocol_sendtr("USR", string);
+ free(string);
+
+ }
+
+ if ( strcmp("XFR", instr) == 0 ) {
+
+ char *mbuffer_type;
+ mbuffer_type = routines_lindex(line, 2);
+
+ if ( strcmp("NS", mbuffer_type) == 0 ) {
+
+ /* got transferred to a different NS */
+ unsigned int new_port;
+ char *new_hostname;
+ char *target;
+
+ target = routines_lindex(line, 3);
+ new_hostname = routines_hostname(target);
+ new_port = routines_port(target);
+ debug_print("NS: Redirected to '%s' port '%i'\n", new_hostname, new_port);
+ if ( new_port == 0 ) {
+ new_port = DEFAULT_NS_PORT;
+ debug_print("NS: Corrected to %i\n", new_port);
+ }
+
+ msnprotocol_cleanup_internal();
+ msnprotocol_connect(new_hostname, new_port);
+
+ /* ...and now for some "tedious mucking-about in hyperspace"... */
+ free(target);
+ free(new_hostname);
+ free(mbuffer_type);
+ free(instr);
+ return -1;
+
+ } else if ( strcmp("SB", mbuffer_type) == 0 ) {
+
+ /* SB transfer - part of an SbSession negotiation. */
+ unsigned int port;
+ char *hostname;
+ char *target;
+ char *cki_key;
+ char *trid_char;
+ char *authtype;
+
+ target = routines_lindex(line, 3);
+ hostname = routines_hostname(target);
+ port = routines_port(target);
+ debug_print("NS: SB session at '%s' port '%i'\n", hostname, port);
+ if ( port == 0 ) {
+ port = DEFAULT_SB_PORT;
+ debug_print("NS: Corrected to %i\n", port);
+ }
+
+ authtype = routines_lindex(line, 4);
+ if ( strcmp(authtype, "CKI") != 0 ) {
+
+ /* How depressing */
+ debug_print("NS: Incompatible SB authentication method (%s)\n", authtype);
+ free(authtype);
+ free(instr);
+ free(mbuffer_type);
+ return 0;
+
+ }
+ free(authtype);
+
+ cki_key = routines_lindex(line, 5);
+ trid_char = routines_lindex(line, 1);
+ sbprotocol_initiate_local(atoi(trid_char), hostname, port, cki_key);
+
+ free(trid_char);
+ free(cki_key);
+ free(hostname);
+ free(target);
+
+ }
+
+ free(mbuffer_type);
+
+ }
+
+ if ( strcmp("USR", instr) == 0 ) {
+
+ if ( line[10] == 'S' ) {
+
+ char *auth_string;
+ char *auth_data;
+ char *auth_ticket;
+
+ auth_string = malloc(1024);
+ strcpy(auth_string, "TWN S ");
+ auth_data = routines_lindex(line, 4);
+ auth_ticket = twnauth_ticket(auth_data);
+ if ( auth_ticket != NULL ) {
+ strncat(auth_string, auth_ticket, 1000);
+ auth_string[1023] = '\0';
+ free(auth_ticket);
+ msnprotocol_sendtr("USR", auth_string);
+ } else {
+ /* Deal with TWN auth failure */
+ msnprotocol_cleanup();
+ error_report("TWN authentication failed.");
+ cstate.disconnect_expected = 1;
+ free(instr);
+ return -1;
+ }
+ free(auth_data);
+ free(auth_string);
+
+ } else {
+
+ if ( ( line[6] == 'O' ) && ( line[7] == 'K' ) ) {
+
+ /* Success! */
+
+ char *syn_max;
+
+ cstate.connected = CSTATE_LIST;
+
+ syn_max = listcache_loadtag();
+ msnprotocol_sendtr("SYN", syn_max);
+
+ }
+
+ }
+
+ }
+
+ if ( strcmp("LST", instr) == 0 ) {
+
+ msnprotocol_handle_lst(line);
+ cstate.lst_num++;
+ if ( cstate.lst_num == cstate.lst_num_max ) {
+ msnprotocol_doneconnect();
+ }
+
+ }
+
+ if ( strcmp("REM", instr) == 0 ) {
+
+ char *list = routines_lindex(line, 2);
+ char *usernameguid = routines_lindex(line, 3); /* Could be either! */
+
+ if ( strcmp(list, "FL") == 0 ) {
+ contactlist_removecontactguid(list, usernameguid);
+ } else {
+ contactlist_removecontact(list, usernameguid);
+ }
+ free(list);
+ free(usernameguid);
+
+ }
+
+ if ( strcmp("ADC", instr) == 0 ) {
+
+ char *list;
+ unsigned int field_num = 2;
+ int finished = 0;
+ char *friendlyname = NULL;
+ char *username = NULL;
+ char *guid = NULL;
+
+ while ( !finished ) {
+
+ char *field;
+
+ field = routines_lindex(line, field_num);
+ field_num++;
+
+ if ( strlen(field) == 0 ) {
+ finished = 1;
+ } else {
+
+ if ( strlen(field) > 2 ) {
+
+ if ( (field[0] == 'F') && (field[1] == '=') ) {
+ friendlyname = strdup(field+2);
+ } else if ( (field[0] == 'N') && (field[1] == '=') ) {
+ username = strdup(field+2);
+ } else if ( (field[0] == 'C') && (field[1] == '=') ) {
+ guid = strdup(field+2);
+ }
+
+ }
+
+ }
+ free(field);
+
+ }
+
+ list = routines_lindex(line, 2);
+ if ( guid != NULL ) {
+ if ( strlen(guid) == 0 ) {
+ free(guid);
+ guid = NULL;
+ }
+ }
+
+ /* This is a new user if trid=0, otherwise it's a reponse to moving them PL->RL. */
+ if ( (strcmp(list, "RL") == 0) && (username != NULL) ) {
+ /* This actually means "PL", unless the contact is already on the AL or the BL.
+ This is logical. Promise. */
+ if ( (trid == 0) && (contactlist_isonlist("AL", username) || contactlist_isonlist("BL", username)) ) {
+ contactlist_pldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN);
+ addcontact_added(username, friendlyname);
+ } else {
+ contactlist_rldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN);
+ }
+ }
+
+ /* Update contact list as appropriate. */
+ if ( strcmp(list, "FL") == 0 ) {
+ contactlist_fldetails(CONTACT_SOURCE_ADC, username, friendlyname, 0, NULL, ONLINE_FLN, guid);
+ }
+ if ( strcmp(list, "AL") == 0 ) {
+ contactlist_aldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN);
+ }
+ if ( strcmp(list, "BL") == 0 ) {
+ contactlist_bldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN);
+ }
+
+ free(list);
+ if ( username != NULL ) {
+ free(username);
+ }
+ if ( friendlyname != NULL ) {
+ free(friendlyname);
+ }
+ if ( guid != NULL ) {
+ free(guid);
+ }
+
+ }
+
+ if ( strcmp("NLN", instr) == 0 ) {
+ msnprotocol_handle_nln(NLN_NLN, line);
+ }
+
+ if ( strcmp("ILN", instr) == 0 ) {
+ msnprotocol_handle_nln(NLN_ILN, line);
+ }
+
+ if ( strcmp("FLN", instr) == 0 ) {
+
+ char *username = routines_lindex(line, 1);
+ /* NULL values won't overwrite anything since CONTACT_SOURCE_FLN has extremely low priority */
+ contactlist_fldetails(CONTACT_SOURCE_FLN, username, NULL, 0, NULL, ONLINE_FLN, NULL);
+ messagewindow_notifyoffline(username);
+ free(username);
+
+ }
+
+ if ( strcmp("CHL", instr) == 0 ) {
+
+ char *response;
+ char *challenge;
+ challenge = routines_lindex(line, 2);
+
+ if ( cstate.protocol_version < 11 ) {
+
+ unsigned char *md5;
+ unsigned int i;
+
+ response = malloc(1024);
+ strncpy(response, challenge, 64);
+ response[1023] = '\0';
+ free(challenge);
+ /* No reverse engineering of any other software took place to provide the CHL key.
+ * This information is freely available on the web */
+ strcat(response, "Q1P7W2E4J9R8U3S5");
+ md5 = MD5(response, strlen(response), NULL);
+ sprintf(response, "msmsgs@msnmsgr.com 32\r\n%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
+ md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7],
+ md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14],
+ md5[15]);
+ for ( i=23; i<strlen(response); i++ ) {
+ if ( response[i]==' ' ) {
+ response[i]='0';
+ }
+ }
+
+ } else {
+
+ char *hex;
+ hex = msnp11chl_response(challenge);
+ response = malloc(strlen(hex)+strlen(CLIENT_ID)+7);
+ sprintf(response, CLIENT_ID" 32\n%s", hex);
+ free(hex);
+
+ }
+
+ free(challenge);
+ msnprotocol_sendtr_nonewline("QRY", response);
+ free(response);
+
+ }
+
+ if ( strcmp("PRP", instr) == 0 ) {
+
+ char *prp_field;
+ int check;
+
+ /* Format #1: from SYN */
+ prp_field = routines_lindex(line, 1);
+ if ( strcmp(prp_field, "MFN") == 0 ) {
+
+ char *new_friendlyname;
+ new_friendlyname = routines_lindex(line, 2);
+ mainwindow_setmfn(new_friendlyname);
+ listcache_setmfn(new_friendlyname);
+ free(new_friendlyname);
+
+ }
+
+ check = atoi(prp_field);
+ if ( check > 0 ) {
+
+ char *prp_field2;
+
+ /* Format #2: from PRP - only attempt if TrID is present. */
+ prp_field2 = routines_lindex(line, 2);
+ if ( strcmp(prp_field2, "MFN") == 0 ) {
+
+ char *new_friendlyname;
+ new_friendlyname = routines_lindex(line, 3);
+ mainwindow_setmfn(new_friendlyname);
+ listcache_setmfn(new_friendlyname);
+ free(new_friendlyname);
+
+ }
+ free(prp_field2);
+
+ }
+ free(prp_field);
+
+
+
+ }
+
+ if ( strcmp("SYN", instr) == 0 ) {
+
+ char *newest_tag_1;
+ char *newest_tag_2;
+ char *full_tag;
+ char *num_contacts;
+
+ newest_tag_1 = routines_lindex(line, 2);
+ newest_tag_2 = routines_lindex(line, 3);
+ num_contacts = routines_lindex(line, 4);
+ /* num_groups = routines_lindex(line, 5); */
+
+ assert(num_contacts != NULL);
+ cstate.lst_num_max = atoi(num_contacts);
+ free(num_contacts);
+ if ( cstate.lst_num_max == 0 ) {
+ /* This indicates an empty list OR an up-to-date list */
+ listcache_load();
+ msnprotocol_doneconnect();
+ } else {
+ cstate.connected = CSTATE_LIST;
+ }
+ cstate.lst_num = 0;
+
+ assert(newest_tag_1 != NULL);
+ assert(newest_tag_2 != NULL);
+
+ full_tag = malloc(strlen(newest_tag_1) + strlen(newest_tag_2) + 2);
+ assert(full_tag != NULL);
+
+ strcpy(full_tag, newest_tag_1);
+ strcat(full_tag, " ");
+ strcat(full_tag, newest_tag_2);
+
+ listcache_settag(full_tag);
+
+ free(full_tag);
+ free(newest_tag_1);
+ free(newest_tag_2);
+
+ }
+
+ if ( strcmp("QNG", instr) == 0 ) {
+
+ /* The time interval given by the QNG is ignored to keep things under program control.
+ Let me know if this causes any problems for you... */
+ if ( cstate.ping_callback == 0 ) {
+ cstate.ping_callback = gtk_timeout_add(50000, (GtkFunction)msnprotocol_sendping, NULL);
+ }
+
+ }
+
+ if ( strcmp("RNG", instr) == 0 ) {
+
+ /* Yay! */
+ char *sessionid;
+ char *callinguser;
+ char *switchboardaddress;
+ char *authchallenge;
+ char *authtype;
+
+ sessionid = routines_lindex(line, 1);
+ callinguser = routines_lindex(line, 5);
+ switchboardaddress = routines_lindex(line, 2);
+ authchallenge = routines_lindex(line, 4);
+
+ debug_print("NS: Received SB invitation from %s (ID=%s, Key=%s, Addr=%s)\n", callinguser, sessionid, authchallenge, switchboardaddress);
+
+ /* Check if the friendly name is available for this user. If not, TL them.
+ This could actually happen if the user is on another list but has no friendlyname.
+ This situation doesn't matter. */
+ if ( contactlist_friendlyname(callinguser) == NULL ) {
+
+ char *friendlyname = routines_lindex(line, 6);
+ debug_print("NS: Creating TL record: '%s'/'%s'\n", callinguser, friendlyname);
+ contactlist_tldetails(CONTACT_SOURCE_RNG, callinguser, friendlyname, ONLINE_NLN);
+ free(friendlyname);
+
+ }
+
+ authtype = routines_lindex(line, 3);
+ if ( strcmp(authtype, "CKI") != 0 ) {
+
+ /* How depressing */
+ debug_print("NS: Incompatible SB authentication method (%s)\n", authtype);
+ free(authtype);
+ free(instr);
+ return 0;
+
+ }
+ free(authtype);
+ sbsessions_create_remote(callinguser, switchboardaddress, sessionid, authchallenge);
+ free(callinguser);
+ free(switchboardaddress);
+ free(authchallenge);
+ free(sessionid);
+
+ }
+
+ if ( strcmp("MSG", instr) == 0 ) {
+
+ char *length;
+
+ length = routines_lindex(line, 3);
+ cstate.expect_length = atoi(length);
+ free(length);
+ if ( cstate.expect_length > 0 ) {
+ cstate.msg_source = routines_lindex(line, 1);
+ cstate.conmode = NSCONMODE_MSG;
+ } else {
+ debug_print("NS: Zero-sized MSG??\n");
+ }
+
+ }
+
+ if ( strcmp("UBX", instr) == 0 ) {
+
+ char *length;
+
+ length = routines_lindex(line, 2);
+ cstate.expect_length = atoi(length);
+ free(length);
+ if ( cstate.expect_length > 0 ) {
+ cstate.msg_source = routines_lindex(line, 1);
+ cstate.conmode = NSCONMODE_UBX;
+ } else {
+ debug_print("NS: Zero-sized UBX??\n"); /* This seems to happen sometimes. */
+ }
+
+ }
+
+ if ( strcmp("NOT", instr) == 0 ) {
+
+ char *length;
+
+ length = routines_lindex(line, 1);
+ cstate.expect_length = atoi(length);
+ free(length);
+ if ( cstate.expect_length > 0 ) {
+ cstate.conmode = NSCONMODE_NOT;
+ } else {
+ debug_print("NS: Zero-sized NOT??\n");
+ }
+
+ }
+
+ if ( strcmp("GCF", instr) == 0 ) {
+
+ char *length;
+ char *file;
+
+ length = routines_lindex(line, 3);
+ file = routines_lindex(line, 2);
+ cstate.expect_length = atoi(length);
+ free(length);
+ if ( cstate.expect_length > 0 ) {
+ if ( strcmp(file, "Shields.xml") == 0 ) {
+ cstate.conmode = NSCONMODE_GCF_SHIELDS;
+ } else {
+ debug_print("NS: Unrecognised GCF!\n");
+ }
+ } else {
+ debug_print("NS: Zero-sized GCF??\n");
+ }
+ free(file);
+
+ }
+
+ if ( strcmp("CHG", instr) == 0 ) {
+
+ OnlineState old_state = cstate.status;
+
+ char *state = routines_lindex(line, 2);
+ cstate.status = msngenerics_decodestatus(state);
+ free(state);
+
+ if ( (old_state == ONLINE_HDN) && (cstate.status != ONLINE_HDN) ) {
+ messagewindow_enable_all();
+ }
+
+ }
+
+ free(instr);
+
+ return 0;
+
+}
+
+static void msnprotocol_parsemsg(const char *msg, size_t msglen) {
+
+ debug_print("NS: Got NS MSG from '%s' (%i bytes) '%s'\n", cstate.msg_source, msglen, msg);
+ free(cstate.msg_source);
+
+}
+
+static void msnprotocol_parseubx(const char *msg, size_t msglen) {
+
+ debug_print("NS: Got UBX data for '%s' (%i bytes) '%s'\n", cstate.msg_source, msglen, msg);
+ contactlist_setubx(cstate.msg_source, msg, msglen);
+ free(cstate.msg_source);
+
+}
+
+static void msnprotocol_parsenotification(const char *msg, size_t msglen) {
+
+ debug_print("NS: Got notification (%i bytes) '%s'\n", msglen, msg);
+
+}
+
+static void msnprotocol_parseshields(const char *msg, size_t msglen) {
+
+ debug_print("NS: Got Shields.xml file (%i bytes) '%s'\n", msglen, msg);
+
+}
+
+/* Internally called when data arrives from the DS/NS */
+static void msnprotocol_readable() {
+
+ ssize_t rlen;
+ int no_string = 0;
+
+ assert(cstate.rbufsize - cstate.roffset > 0);
+ rlen = read(cstate.socket, cstate.rbuffer + cstate.roffset, cstate.rbufsize - cstate.roffset);
+ if ( rlen >= 0 ) {
+ cstate.roffset += rlen;
+ assert(cstate.roffset <= cstate.rbufsize); /* This would indicate a buffer overrun */
+ }
+
+ /* First, check this isn't a disconnection */
+ if ( (rlen == 0) || (rlen == -1) ) {
+
+ int closeval;
+
+ closeval = close(cstate.socket);
+
+ if ( closeval != 0 ) {
+ debug_print("NS: Couldn't close socket after read error.\n");
+ }
+
+ /* A variety of ways to express this to the user... */
+ if ( cstate.connected == CSTATE_CONNECTED ) {
+ if ( !cstate.disconnect_expected ) {
+ error_report("Read error! Signed out.");
+ listcache_save();
+ } /* Else ignore. */
+ } else if ( (cstate.connected == CSTATE_SIGNIN) || (cstate.connected == CSTATE_LIST) ) {
+ if ( !cstate.disconnect_expected ) {
+ error_report("Read error! Sign-in failed.");
+ }
+ } else {
+ error_report("Read error in unrecognised connection state. This never happens :P");
+ debug_print("NS: Connection state %i\n", cstate.connected);
+ }
+
+ msnprotocol_cleanup();
+ return;
+
+ }
+
+ while ( (!no_string) && (cstate.roffset > 0) ) {
+
+ int block_ready = 0;
+ size_t i = 0;
+
+ /* See if there's a full "block" in the buffer yet */
+ if ( cstate.conmode == NSCONMODE_LINE ) {
+
+ for ( i=0; i<cstate.roffset-1; i++ ) { /* Means the last value looked at is roffset-2 */
+ if ( (cstate.rbuffer[i] == '\r') && (cstate.rbuffer[i+1] == '\n') ) {
+ block_ready = 1;
+ break;
+ }
+ }
+
+ } else if ( cstate.conmode == NSCONMODE_MSG ) {
+
+ if ( cstate.roffset >= cstate.expect_length ) {
+ i = cstate.expect_length - 2;
+ block_ready = 1;
+ }
+
+ } else if ( cstate.conmode == NSCONMODE_UBX ) {
+
+ if ( cstate.roffset >= cstate.expect_length ) {
+ i = cstate.expect_length - 2;
+ block_ready = 1;
+ }
+
+ } else if ( cstate.conmode == NSCONMODE_NOT ) {
+
+ if ( cstate.roffset >= cstate.expect_length ) {
+ i = cstate.expect_length - 2;
+ block_ready = 1;
+ }
+
+ } else if ( cstate.conmode == NSCONMODE_GCF_SHIELDS ) {
+
+ if ( cstate.roffset >= cstate.expect_length ) {
+ i = cstate.expect_length - 2;
+ block_ready = 1;
+ }
+
+ } else {
+ /* "Never happens". */
+ debug_print("NS: Can't determine end of block for NsConMode %i!\n", cstate.conmode);
+ }
+
+ if ( block_ready == 1 ) {
+
+ char *block_buffer = NULL;
+ unsigned int new_rbufsize;
+ unsigned int endbit_length;
+ NSConMode next_mode = cstate.conmode;
+
+ if ( cstate.conmode == NSCONMODE_LINE ) {
+ assert(cstate.rbuffer[i] == '\r');
+ assert(cstate.rbuffer[i+1] == '\n');
+ }
+
+
+ if ( cstate.conmode == NSCONMODE_LINE ) {
+
+ block_buffer = malloc(i+1);
+ memcpy(block_buffer, cstate.rbuffer, i);
+ block_buffer[i] = '\0';
+
+ if ( msnprotocol_parseline(block_buffer) == -1 ) {
+ /* Eeek. Redirected... */
+ free(block_buffer);
+ return;
+ }
+
+ /* Check to see if sbprotocol_parseline changed the mode. If so, make sure
+ it stays changed. */
+ if ( cstate.conmode != NSCONMODE_LINE ) {
+ next_mode = cstate.conmode;
+ }
+
+ } else if ( cstate.conmode == NSCONMODE_MSG ) {
+
+ block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */
+ memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */
+ block_buffer[i+2] = '\0'; /* Terminate */
+
+ msnprotocol_parsemsg(block_buffer, i+2);
+ next_mode = NSCONMODE_LINE;
+
+ } else if ( cstate.conmode == NSCONMODE_UBX ) {
+
+ block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */
+ memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */
+ block_buffer[i+2] = '\0'; /* Terminate */
+
+ msnprotocol_parseubx(block_buffer, i+2);
+ next_mode = NSCONMODE_LINE;
+
+ } else if ( cstate.conmode == NSCONMODE_NOT ) {
+
+ block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */
+ memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */
+ block_buffer[i+2] = '\0'; /* Terminate */
+
+ msnprotocol_parsenotification(block_buffer, i+2);
+ next_mode = NSCONMODE_LINE;
+
+ } else if ( cstate.conmode == NSCONMODE_GCF_SHIELDS ) {
+
+ block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */
+ memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */
+ block_buffer[i+2] = '\0'; /* Terminate */
+
+ msnprotocol_parseshields(block_buffer, i+2);
+ next_mode = NSCONMODE_LINE;
+
+ } else {
+ debug_print("NS: No handler for NsConMode %i !\n", cstate.conmode);
+ }
+
+ free(block_buffer);
+
+ /* Now the block's been parsed, it should be forgotten about */
+ if ( cstate.conmode == NSCONMODE_LINE ) {
+ assert(cstate.rbuffer[i+1] == '\n'); /* Should still be the case. Paranoid. */
+ }
+ endbit_length = i+2;
+ memmove(cstate.rbuffer, cstate.rbuffer + endbit_length, cstate.rbufsize - endbit_length);
+ cstate.roffset = cstate.roffset - endbit_length; /* Subtract the number of bytes removed */
+ new_rbufsize = cstate.rbufsize - endbit_length;
+ if ( new_rbufsize == 0 ) {
+ new_rbufsize = 32;
+ }
+ cstate.rbuffer = realloc(cstate.rbuffer, new_rbufsize);
+ cstate.rbufsize = new_rbufsize;
+ cstate.conmode = next_mode;
+
+ } else {
+
+ if ( cstate.roffset == cstate.rbufsize ) {
+
+ /* More buffer space is needed */
+ cstate.rbuffer = realloc(cstate.rbuffer, cstate.rbufsize + 32);
+ cstate.rbufsize = cstate.rbufsize + 32;
+ /* The new space gets used at the next read, shortly... */
+
+ }
+ no_string = 1;
+
+ }
+
+ }
+
+}
+
+/* Return nonzero if we're signed in and have the lists */
+int msnprotocol_signedin() {
+
+ if ( cstate.connected == CSTATE_CONNECTED ) {
+ return 1;
+ }
+
+ return 0;
+
+}
+
+/* Return nonzero if we're completely disconnected */
+int msnprotocol_disconnected() {
+
+ if ( cstate.connected == CSTATE_DISCONNECTED ) {
+ return 1;
+ }
+
+ return 0;
+
+}
+
+/* Internally called when the socket is connected */
+static void msnprotocol_ready() {
+
+ /* Remove the "writeable" callback, and add a "readable" callback */
+ gdk_input_remove(cstate.wcallback);
+ cstate.wcallback = 0;
+ cstate.rcallback = gdk_input_add(cstate.socket, GDK_INPUT_READ, (GdkInputFunction) msnprotocol_readable, NULL);
+
+ /* Begin the handshake */
+ msnprotocol_sendtr("VER", "MSNP12 MSNP11 CVR0");
+
+}
+
+/* Called from src/mainwindow.c to start the sign-in process */
+void msnprotocol_connect(const char *hostname, unsigned short int port) {
+
+ struct sockaddr_in sa_desc;
+ struct hostent *server;
+ unsigned int sockopts;
+ int conn_error;
+
+ /* Empty values means use the configured value. Other values are provided on an NS redirect */
+ if ( strcmp(hostname, "") == 0 ) {
+ hostname = options_hostname();
+ }
+ if ( port == 0 ) {
+ port = options_port();
+ }
+
+ assert(hostname != NULL);
+ assert(port > 0);
+
+ /* Check if a connection attempt is already in progress. Abort if it is */
+ if ( cstate.connect_lock != 0 ) {
+ debug_print("NS: Aborting connection: locked.\n");
+ return;
+ }
+ cstate.connect_lock = 1;
+
+ /* Create and configure the socket (but don't connect it yet) */
+ cstate.socket = socket(PF_INET, SOCK_STREAM, 0);
+ if ( cstate.socket == -1 ) {
+ error_report("Couldn't create socket");
+ msnprotocol_cleanup();
+ return;
+ }
+ sockopts = fcntl(cstate.socket, F_GETFL);
+ fcntl(cstate.socket, F_SETFL, sockopts | O_NONBLOCK);
+
+ /* Resolve the server name */
+ server = gethostbyname(hostname);
+ if ( server == NULL ) {
+ error_report("No such host");
+ msnprotocol_cleanup();
+ return;
+ }
+
+ memset(&sa_desc, 0, sizeof(sa_desc));
+ sa_desc.sin_family = AF_INET;
+ memcpy(&sa_desc.sin_addr.s_addr, server->h_addr, server->h_length);
+ sa_desc.sin_port = htons(port);
+
+ cstate.rbuffer = malloc(256);
+ assert(cstate.rbuffer != NULL);
+ cstate.rbufsize = 256;
+ /* wbuffer starts at NULL and gets created when data is written */
+
+ conn_error = connect(cstate.socket, (struct sockaddr *)&sa_desc, sizeof(sa_desc));
+ if ( (conn_error < 0) && (errno != EINPROGRESS) ) {
+
+ debug_print("NS: Couldn't connect to server - %i\n", errno);
+ error_report("Couldn't connect to server");
+ msnprotocol_cleanup();
+ return;
+
+ } else {
+ cstate.wcallback = gdk_input_add(cstate.socket, GDK_INPUT_WRITE, (GdkInputFunction) msnprotocol_ready, NULL);
+ }
+
+ cstate.connected = CSTATE_SIGNIN;
+
+}
+
+/* Called by src/statusmenu.c to change online status.
+ * Passes "newstatus" gets cast into a gpointer to do this, which is a bit nasty */
+void msnprotocol_setstatus(OnlineState newstatus) {
+
+ char *mode;
+ char *capabilities;
+ char *dpobj;
+ char *string;
+ unsigned int clientid;
+
+ /* Don't change status too early (MSN server bug) unless signing out */
+ if ( (cstate.connected != CSTATE_CONNECTED) && (newstatus != ONLINE_FLN) ) {
+ debug_print("NS: Not ready for status change - aborting.\n");
+ return;
+ }
+
+ debug_print("NS: Setting new status: %i\n", newstatus);
+
+ mode = "NLN"; /* Fallback */
+ switch ( newstatus ) {
+
+ case ONLINE_NLN : mode = "NLN"; break;
+ case ONLINE_AWY : mode = "AWY"; break;
+ case ONLINE_BSY : mode = "BSY"; break;
+ case ONLINE_BRB : mode = "BRB"; break;
+ case ONLINE_PHN : mode = "PHN"; break;
+ case ONLINE_LUN : mode = "LUN"; break;
+ case ONLINE_HDN : {
+ mode = "HDN";
+ /* sbsessions_destroy_all();
+ messagewindow_disable_all(); */
+ break;
+ }
+ /* The following two shouldn't happen: keep the compiler happy. */
+ case ONLINE_ERR : mode = "NLN"; break;
+ case ONLINE_IDL : mode = "AWY"; break;
+ case ONLINE_FLN : {
+
+ msnprotocol_sendtr("OUT", "");
+ if ( cstate.connected == CSTATE_CONNECTED ) {
+ listcache_save();
+ }
+ cstate.disconnect_expected = 1;
+ if ( cstate.disconnect_callback == 0 ) {
+ cstate.disconnect_callback = gtk_timeout_add(10000, (GtkFunction)msnprotocol_forcedisconnect, NULL);
+ }
+ return;
+
+ }
+
+ }
+
+ clientid = 0x50000000 + (1<<5) + (1<<3) + (1<<2); /* MSNC5, Multipacketing (bit 5), Ink (bits 2 and 3) */
+ capabilities = malloc(12);
+ sprintf(capabilities, "%i", clientid);
+ dpobj = avatars_localobject();
+
+ string = malloc(strlen(mode) + strlen(capabilities) + strlen(dpobj) + 3);
+ strcpy(string, mode);
+ strcat(string, " ");
+ strcat(string, capabilities);
+ strcat(string, " ");
+ strcat(string, dpobj);
+ free(dpobj);
+
+ msnprotocol_sendtr("CHG", string);
+ free(string);
+ free(capabilities);
+
+ messagewindow_picturekick(NULL);
+
+}
+
+/* No information from the session record is needed at this stage. */
+int msnprotocol_initiatesb() {
+
+ return msnprotocol_sendtr("XFR", "SB");
+
+}
+
+void msnprotocol_requestsignout() {
+ msnprotocol_setstatus(ONLINE_FLN);
+}
+
+void msnprotocol_setmfn(const char *new_mfn) {
+
+ char *mfn_string;
+ char *mfn_code;
+
+ mfn_code = routines_urlencode(new_mfn);
+ mfn_string = malloc(strlen(mfn_code)+5);
+ strcpy(mfn_string, "MFN ");
+ strcat(mfn_string, mfn_code);
+
+ msnprotocol_sendtr("PRP", mfn_string);
+
+ free(mfn_string);
+}
+
+void msnprotocol_setcsm(const char *new_csm) {
+
+ const char *template;
+ char *uuxblock;
+ char *sendage;
+
+ if ( cstate.protocol_version >= 11 ) {
+ template = "<Data><PSM></PSM><CurrentMedia></CurrentMedia></Data>";
+ uuxblock = xml_setblock(template, strlen(template), "Data", "PSM", new_csm);
+
+ sendage = malloc(strlen(uuxblock)+8);
+ assert(strlen(uuxblock)<=99999); /* 5 chars max. */
+ sprintf(sendage, "%i\r\n%s", strlen(uuxblock), uuxblock);
+ msnprotocol_sendtr_nonewline("UUX", sendage);
+
+ free(uuxblock);
+ free(sendage);
+
+ }
+
+ listcache_setcsm(new_csm);
+
+}
+
+void msnprotocol_adduser(const char *username, const char *list) {
+
+ char *line = malloc(strlen(list) + 6 + strlen(username));
+ strcpy(line, list);
+ strcat(line, " N=");
+ strcat(line, username);
+ msnprotocol_sendtr("ADC", line);
+ free(line);
+
+}
+
+void msnprotocol_adduserfriendly(const char *username, const char *friendlyname, const char *list) {
+
+ char *line = malloc(strlen(list) + 9 + strlen(username) + strlen(friendlyname));
+ strcpy(line, list);
+ strcat(line, " N=");
+ strcat(line, username);
+ strcat(line, " F=");
+ strcat(line, friendlyname);
+ msnprotocol_sendtr("ADC", line);
+ free(line);
+
+}
+
+void msnprotocol_remuser(const char *username, const char *list) {
+
+ char *line = malloc(strlen(list) + 2 + strlen(username));
+ strcpy(line, list);
+ strcat(line, " ");
+ strcat(line, username);
+ msnprotocol_sendtr("REM", line);
+ free(line);
+
+}
+
+OnlineState msnprotocol_status() {
+ return cstate.status;
+}
diff --git a/src/msnprotocol.h b/src/msnprotocol.h
new file mode 100644
index 0000000..3a8529f
--- /dev/null
+++ b/src/msnprotocol.h
@@ -0,0 +1,44 @@
+/*
+ * msnprotocol.h
+ *
+ * Low-level DS/NS protocol handling
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef MSNPROTOCOL_H
+#define MSNPROTOCOL_H
+
+#include <gtk/gtk.h>
+#include "msngenerics.h"
+#include "sbsessions.h"
+
+extern void msnprotocol_setstatus(OnlineState newstatus);
+extern void msnprotocol_connect(const char *hostname, unsigned short int port);
+extern int msnprotocol_signedin(void);
+extern int msnprotocol_disconnected(void);
+extern int msnprotocol_initiatesb(void);
+extern void msnprotocol_requestsignout(void);
+extern void msnprotocol_setmfn(const char *new_mfn);
+extern void msnprotocol_setcsm(const char *new_csm);
+extern void msnprotocol_adduser(const char *username, const char *list);
+extern void msnprotocol_adduserfriendly(const char *username, const char *friendlyname, const char *list);
+extern void msnprotocol_remuser(const char *username, const char *list);
+extern OnlineState msnprotocol_status(void);
+
+#endif /* MSNPROTOCOL_H */
diff --git a/src/options.c b/src/options.c
new file mode 100644
index 0000000..65dd08d
--- /dev/null
+++ b/src/options.c
@@ -0,0 +1,1051 @@
+/*
+ * options.c
+ *
+ * Low-level option handling
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glob.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include "debug.h"
+#include "accountwindow.h"
+#include "options.h"
+#include "msngenerics.h"
+#include "mainwindow.h"
+#include "error.h"
+#include "routines.h"
+#include "listcache.h"
+
+#define DEFAULT_HOSTNAME "messenger.hotmail.com"
+#define DEFAULT_WGET "/usr/bin/wget"
+
+static struct {
+
+ char *username;
+ char *password;
+ unsigned int rememberlogindetails;
+
+ char *hostname;
+ unsigned int port;
+
+ char *wget;
+
+ char *gtc; /* Special - comes from list cache. */
+ char *blp; /* Special - comes from list cache. */
+
+ unsigned int hideoffline; /* Hide offline contacts? */
+ unsigned int hidecsm; /* Hide local CSM? */
+
+ GdkColor *localcolour_gdk; /* Colour for local user's messages. */
+ char *localcolour_string; /* String version of the above. */
+ unsigned int ofontoverride; /* Override other contacts' chosen fonts/colours? */
+ GdkColor *ocolour_gdk; /* Colour to override contacts' messages with. */
+ char *localfont; /* Font for local user's messages. */
+ char *ofont; /* Font to use for contacts' messages (if overriding). */
+
+ unsigned int ircstyle; /* Use IRC style for new IM windows? */
+ unsigned int timestamps; /* Show timestamps for messages in new IM windows? */
+ unsigned int showavatars; /* Display avatars in new IM windows? */
+ unsigned int showemoticons; /* Show emoticons in new IM windows? */
+
+} options = {
+
+ NULL, /* Username */
+ NULL, /* Password */
+ 0, /* Remember login details */
+
+ NULL, /* Hostname */
+ 0, /* Port */
+
+ NULL, /* Wget */
+
+ NULL, /* GTC */
+ NULL, /* BLP */
+
+ FALSE, /* Hide offline */
+ FALSE, /* Hide CSM */
+
+ NULL, /* localcolour_gdk */
+ NULL, /* localcolour_string */
+ FALSE, /* ofontoverride */
+ NULL, /* ocolour_gdk */
+ NULL, /* localfont */
+ NULL, /* ofont */
+
+ 0, /* ircstyle */
+ 0, /* timestamps */
+ 0, /* showavatars */
+ 0 /* showemoticons */
+
+};
+
+static void options_setdefaults() {
+
+ options.username = strdup("");
+ options.password = strdup("");
+ options.rememberlogindetails = 0;
+
+ options.hostname = strdup(DEFAULT_HOSTNAME);
+ options.port = DEFAULT_NS_PORT;
+
+ options.wget = strdup(DEFAULT_WGET);
+
+ options.gtc = strdup("BL");
+ options.blp = strdup("A");
+
+ options.hideoffline = FALSE;
+ options.hidecsm = FALSE;
+ options.ofontoverride = FALSE;
+
+ options.localcolour_gdk = NULL;
+ options.localcolour_string = NULL;
+ options.ocolour_gdk = NULL;
+ options.localfont = NULL;
+ options.ofont = NULL;
+
+ options.ircstyle = 0;
+ options.timestamps = 0;
+ options.showavatars = 0;
+ options.showemoticons = 0;
+
+}
+
+static void options_createhomedir(char *filename) {
+
+ char *av_name;
+ char *hg_name;
+
+ /* Create directory. */
+ if ( mkdir(filename, S_IRUSR | S_IWUSR | S_IXUSR) != 0 ) {
+ debug_print("OP: Couldn't create ~/.tuxmessenger directory.\n");
+ exit(1);
+ }
+
+ /* Create "avatars" subdirectory. */
+ av_name = malloc(strlen(filename) + 9);
+ strcpy(av_name, filename);
+ strcat(av_name, "/avatars");
+ if ( mkdir(av_name, S_IRUSR | S_IWUSR | S_IXUSR) != 0 ) {
+ debug_print("OP: Couldn't create ~/.tuxmessenger/avatars directory.\n");
+ exit(1);
+ }
+ free(av_name);
+
+ /* Link the "Hourglass", "Default" and "Own" avatars. */
+ hg_name = malloc(strlen(filename) + 20);
+
+ strcpy(hg_name, filename);
+ strcat(hg_name, "/wait_avatar.png");
+ if ( symlink(DATADIR"/tuxmessenger/hourglass.png", hg_name) != 0 ) {
+ debug_print("OP: Couldn't create ~/.tuxmessenger/wait_avatar.png.\n");
+ exit(1);
+ }
+
+ strcpy(hg_name, filename);
+ strcat(hg_name, "/default_avatar.png");
+ if ( symlink(DATADIR"/tuxmessenger/no_avatar.png", hg_name) != 0 ) {
+ debug_print("OP: Couldn't create ~/.tuxmessenger/default_avatar.png.\n");
+ exit(1);
+ }
+
+ strcpy(hg_name, filename);
+ strcat(hg_name, "/avatar.png");
+ if ( symlink(DATADIR"/tuxmessenger/no_avatar.png", hg_name) != 0 ) {
+ debug_print("OP: Couldn't create ~/.tuxmessenger/avatar.png.\n");
+ exit(1);
+ }
+
+ free(hg_name);
+
+}
+
+static void options_bailout() {
+
+ options_setdefaults();
+ accountwindow_open();
+
+}
+
+void options_setusername(const char *username) {
+
+ unsigned int changed = FALSE;
+
+ if ( (username != NULL) && (options.username != NULL) && (strcmp(options.username, "") != 0) && (strcmp(username, options.username) != 0) ) {
+ changed = TRUE;
+ }
+
+ if ( options.username != NULL ) {
+ free(options.username);
+ }
+ options.username = strdup(username);
+
+ if ( changed ) {
+ listcache_setcsm("");
+ listcache_invalidate();
+ }
+
+}
+
+void options_setpassword(const char *password) {
+
+ if ( options.password != NULL ) {
+ free(options.password);
+ }
+ options.password = strdup(password);
+
+}
+
+void options_sethostname(const char *hostname) {
+
+ if ( options.hostname != NULL ) {
+ free(options.hostname);
+ }
+ if ( strlen(hostname) > 0 ) {
+ options.hostname = strdup(hostname);
+ } else {
+ debug_print("OP: Using default server hostname instead of zero-length string.\n");
+ options.hostname = strdup(DEFAULT_HOSTNAME);
+ }
+
+}
+
+void options_setport(const int port) {
+
+ options.port = port;
+ if ( options.port == 0 ) {
+ /* That's just plain silly. */
+ debug_print("OP: Correcting port number: 0->%i.\n", DEFAULT_NS_PORT);
+ options_setport(DEFAULT_NS_PORT);
+ }
+ if ( options.port > 65535 ) {
+ /* D'oh */
+ debug_print("OP: Port number too large: correcting to %i\n", DEFAULT_NS_PORT);
+ options_setport(DEFAULT_NS_PORT);
+ }
+
+}
+
+void options_setwget(const char *wget) {
+
+ if ( options.wget != NULL ) {
+ free(options.wget);
+ }
+ if ( strlen(wget) > 0 ) {
+ options.wget = strdup(wget);
+ } else {
+ debug_print("OP: Using default wget instead of zero-length string.\n");
+ options.wget = strdup(DEFAULT_WGET);
+ }
+
+}
+
+void options_setrememberlogindetails(unsigned int remember) {
+ options.rememberlogindetails = remember;
+}
+
+void options_sethideoffline(unsigned int hide) {
+ options.hideoffline = hide;
+}
+
+void options_sethidecsm(unsigned int hide) {
+ options.hidecsm = hide;
+}
+
+void options_setlocalcolour_gdk(const GdkColor *colour) {
+
+ char *string;
+
+ if ( options.localcolour_gdk != NULL ) {
+ gdk_color_free(options.localcolour_gdk);
+ }
+ if ( options.localcolour_string != NULL ) {
+ free(options.localcolour_string);
+ }
+
+ options.localcolour_gdk = gdk_color_copy(colour);
+
+ /* Now work out the string version */
+ string = malloc(7);
+ /* Yukky BGR order instead of RGB */
+ snprintf(string, 7, "%02hhx%02hhx%02hhx", colour->blue >> 8, colour->green >> 8, colour->red >> 8);
+ options.localcolour_string = string;
+ debug_print("OP: String value '%s'\n", string);
+
+}
+
+void options_setlocalcolour_string(const char *colour) {
+
+ char *flipped;
+ char *string;
+ GdkColor gdkcolour;
+
+ if ( options.localcolour_gdk != NULL ) {
+ gdk_color_free(options.localcolour_gdk);
+ }
+ if ( options.localcolour_string != NULL ) {
+ free(options.localcolour_string);
+ }
+
+ options.localcolour_string = strdup(colour);
+
+ /* Now work out the GDK version - flip then parse */
+ flipped = routines_flipcolour(colour);
+ string = malloc(8);
+ strcpy(string, "#");
+ strncat(string, flipped, 7);
+ string[7] = '\0';
+ free(flipped);
+ if ( gdk_color_parse(string, &gdkcolour) ) {
+ options.localcolour_gdk = gdk_color_copy(&gdkcolour);
+ } else {
+
+ debug_print("OP: Whoops! Colour parsing failed (%s)...\n", string);
+ if ( options.localcolour_gdk != NULL ) {
+ gdk_color_free(options.localcolour_gdk);
+ }
+ if ( options.localcolour_string != NULL ) {
+ free(options.localcolour_string);
+ }
+ options.localcolour_string = NULL;
+ options.localcolour_gdk = NULL;
+
+ }
+ free(string);
+
+}
+
+void options_setocolour_gdk(const GdkColor *colour) {
+
+ if ( options.ocolour_gdk != NULL ) {
+ gdk_color_free(options.ocolour_gdk);
+ }
+
+ options.ocolour_gdk = gdk_color_copy(colour);
+
+}
+
+void options_setofontoverride(unsigned int override) {
+ options.ofontoverride = override;
+}
+
+void options_setocolour_string(const char *colour) {
+
+ char *string;
+ GdkColor gdkcolour;
+
+ if ( options.ocolour_gdk != NULL ) {
+ gdk_color_free(options.localcolour_gdk);
+ }
+
+ string = malloc(8);
+ strcpy(string, "#");
+ strncat(string, colour, 7);
+ string[7] = '\0';
+ if ( gdk_color_parse(string, &gdkcolour) ) {
+ options.ocolour_gdk = gdk_color_copy(&gdkcolour);
+ } else {
+ debug_print("OP: Whoops! Colour parsing failed (%s)...\n", string);
+ options.localcolour_gdk = NULL;
+ }
+ free(string);
+
+}
+
+void options_setlocalfont(const char *font) {
+ options.localfont = strdup(font);
+}
+
+void options_setofont(const char *font) {
+ options.ofont = strdup(font);
+}
+
+void options_setircstyle(unsigned int ircstyle) {
+ options.ircstyle = ircstyle;
+}
+
+void options_settimestamps(unsigned int timestamps) {
+ options.timestamps = timestamps;
+}
+
+void options_setshowavatars(unsigned int showavatars) {
+ options.showavatars = showavatars;
+}
+
+void options_setshowemoticons(unsigned int showemoticons) {
+ options.showemoticons = showemoticons;
+}
+
+void options_load() {
+
+ int glob_retval;
+ glob_t glob_result;
+ struct stat stat_buffer;
+ struct stat *statbuf;
+ char *dir_filename;
+ char *rc_filename;
+
+ glob_retval = glob("~", GLOB_TILDE, NULL, &glob_result);
+ statbuf = &stat_buffer;
+ if ( glob_retval != 0 ) {
+
+ debug_print("OP: glob() for ~/.tuxmessenger failed: ");
+ switch ( glob_retval ) {
+ case GLOB_NOSPACE : debug_print("GLOB_NOSPACE\n"); break;
+ case GLOB_ABORTED : debug_print("GLOB_ABORTED\n"); break;
+ case GLOB_NOMATCH : debug_print("GLOB_NOMATCH\n"); break;
+ default : debug_print("Unknown!\n"); break;
+ }
+ debug_print("OP: Can't continue :(\n");
+ exit(1);
+
+ }
+
+ dir_filename = malloc(strlen(glob_result.gl_pathv[0]) + 15);
+ strcpy(dir_filename, glob_result.gl_pathv[0]);
+ globfree(&glob_result);
+ strcat(dir_filename, "/.tuxmessenger");
+
+ if ( stat(dir_filename, statbuf) != -1 ) {
+
+ if ( S_ISDIR(stat_buffer.st_mode) ) {
+
+ debug_print("OP: Found '%s'.\n", dir_filename);
+ /* All is good. */
+
+ } else {
+
+ debug_print("OP: Found '%s', but it isn't a directory!\n", dir_filename);
+ debug_print("OP: Can't continue :(\n");
+ exit(1);
+
+ }
+
+ } else {
+
+ debug_print("OP: ~/.tuxmessenger directory not found: creating it.\n");
+ options_createhomedir(dir_filename);
+
+ }
+
+ /* Right - after all of that, let's take a look at the central config file. */
+ options_setdefaults(); /* Set defaults to begin with. */
+ rc_filename = malloc(strlen(dir_filename) + 8);
+ strcpy(rc_filename, dir_filename);
+ strcat(rc_filename, "/config");
+ if ( stat(rc_filename, statbuf) != -1 ) {
+
+ if ( (S_ISREG(stat_buffer.st_mode)) || (S_ISLNK(stat_buffer.st_mode)) ) {
+
+ /* Yay :D */
+ FILE *fh;
+ char *line;
+ char *rval;
+ int whoops = 0;
+
+ fh = fopen(rc_filename, "r");
+ if ( fh == NULL ) {
+ debug_print("OP: Error opening options file.\n");
+ options_bailout();
+ free(rc_filename);
+ free(dir_filename);
+ return;
+ }
+
+ line = malloc(1024);
+ assert(line != NULL);
+ rval = "hello"; /* Non-NULL */
+
+ while ( rval ) {
+
+ rval = fgets(line, 1023, fh);
+
+ if ( ferror(fh) && !feof(fh) ) {
+ whoops = 1;
+ break;
+ }
+ if ( strlen(line) > 1 ) {
+ if ( line[strlen(line)-1] == '\n' ) {
+ line[strlen(line)-1] = '\0'; /* Cut off trailing newline. */
+ }
+ } else {
+ line[0] = '\0';
+ }
+
+ if ( strncmp(line, "username ", 9) == 0 ) {
+ debug_print("OP: Got username from config file.\n");
+ options_setusername(line+9);
+ }
+ if ( strncmp(line, "password ", 9) == 0 ) {
+ debug_print("OP: Got password from config file.\n");
+ options_setpassword(line+9);
+ }
+ if ( strncmp(line, "hostname ", 9) == 0 ) {
+ debug_print("OP: Got server hostname from config file.\n");
+ options_sethostname(line+9);
+ }
+ if ( strncmp(line, "port ", 5) == 0 ) {
+ debug_print("OP: Got server port number from config file.\n");
+ options_setport(atoi(line+5));
+ }
+ if ( strncmp(line, "wget ", 5) == 0 ) {
+ debug_print("OP: Got wget command from config file.\n");
+ options_setwget(line+5);
+ }
+ if ( strncmp(line, "hide_offline", 12) == 0 ) {
+ debug_print("OP: Got hide_offline from config file.\n");
+ options_sethideoffline(TRUE);
+ }
+ if ( strncmp(line, "hide_localcsm", 12) == 0 ) {
+ debug_print("OP: Got hide_localcsm from config file.\n");
+ options_sethidecsm(TRUE);
+ }
+ if ( strncmp(line, "localcolour ", 12) == 0 ) {
+ debug_print("OP: Got local colour from config file.\n");
+ options_setlocalcolour_string(line+12);
+ }
+ if ( strncmp(line, "ofontoverride", 12) == 0 ) {
+ debug_print("OP: Got ofontoverride from config file.\n");
+ options_setofontoverride(TRUE);
+ }
+ if ( strncmp(line, "ocolour ", 8) == 0 ) {
+ debug_print("OP: Got override colour from config file.\n");
+ options_setocolour_string(line+8);
+ }
+ if ( strncmp(line, "localfont ", 10) == 0 ) {
+ debug_print("OP: Got localfont from config file.\n");
+ options_setlocalfont(line+10);
+ }
+ if ( strncmp(line, "ofont ", 6) == 0 ) {
+ debug_print("OP: Got ofont from config file.\n");
+ options_setofont(line+6);
+ }
+ if ( strncmp(line, "show_avatars", 12) == 0 ) {
+ debug_print("OP: Got show_avatars from config file.\n");
+ options_setshowavatars(TRUE);
+ }
+ if ( strncmp(line, "show_emoticons", 14) == 0 ) {
+ debug_print("OP: Got show_emoticons from config file.\n");
+ options_setshowemoticons(TRUE);
+ }
+ if ( strncmp(line, "timestamps", 10) == 0 ) {
+ debug_print("OP: Got timestamps from config file.\n");
+ options_settimestamps(TRUE);
+ }
+ if ( strncmp(line, "ircstyle", 8) == 0 ) {
+ debug_print("OP: Got ircstyle from config file.\n");
+ options_setircstyle(TRUE);
+ }
+
+ line[0] = '\0'; /* Prevent this line from being parsed twice. */
+
+ }
+
+ free(line);
+ fclose(fh);
+
+ if ( whoops ) {
+ debug_print("OP: Error reading options file.\n");
+ options_bailout();
+ }
+
+ } else {
+
+ /* FFS. I'm not handling that. */
+ debug_print("OP: Options file exists but isn't a file!\n");
+ exit(1);
+
+ }
+
+ } else {
+
+ /* Options file isn't there. Set "sensible defaults" and open the config window... */
+ debug_print("OP: Options file not found.\n");
+ options_bailout();
+
+ }
+
+ options_setrememberlogindetails(1);
+ /* If the username and password are blank, and the server is set to its default, we assume "Remember login details" was unticked last time. */
+ if ( (strlen(options_username())==0) && (strlen(options_password())==0) && (options_port()==DEFAULT_NS_PORT) && (strcmp(options_hostname(), DEFAULT_HOSTNAME)==0)) {
+ options_setrememberlogindetails(0);
+ }
+
+ free(dir_filename);
+ free(rc_filename);
+
+}
+
+const char *options_username() {
+
+ /* FIXME: Check length */
+ /* Don't forget to change to lower case. */
+
+ return options.username;
+
+}
+
+const char *options_password() {
+ return options.password;
+}
+
+const char *options_hostname() {
+ return options.hostname;
+}
+
+unsigned short int options_port() {
+ return options.port;
+}
+
+const char *options_wget() {
+ return options.wget;
+}
+
+const char *options_blp() {
+ return options.blp;
+}
+
+const char *options_gtc() {
+ return options.gtc;
+}
+
+unsigned int options_rememberlogindetails() {
+ return options.rememberlogindetails;
+}
+
+unsigned int options_hideoffline() {
+ return options.hideoffline;
+}
+
+unsigned int options_hidecsm() {
+ return options.hidecsm;
+}
+
+const GdkColor *options_localcolour_gdk() {
+ return options.localcolour_gdk;
+}
+
+const char *options_localcolour_string() {
+ return options.localcolour_string;
+}
+
+unsigned int options_ofontoverride() {
+ return options.ofontoverride;
+}
+
+const GdkColor *options_ocolour_gdk() {
+ return options.ocolour_gdk;
+}
+
+unsigned int options_ircstyle() {
+ return options.ircstyle;
+}
+
+unsigned int options_timestamps() {
+ return options.timestamps;
+}
+
+unsigned int options_showavatars() {
+ return options.showavatars;
+}
+
+unsigned int options_showemoticons() {
+ return options.showemoticons;
+}
+
+static char *options_ocolour_string() {
+
+ char *string;
+ const GdkColor *colour = options_ocolour_gdk();
+
+ if ( colour == NULL ) {
+ return NULL;
+ }
+
+ string = malloc(7);
+ /* RGB order here */
+ snprintf(string, 7, "%02hhx%02hhx%02hhx", colour->red >> 8, colour->green >> 8, colour->blue >> 8);
+
+ return string;
+
+}
+
+const char *options_localfont() {
+ return options.localfont;
+}
+
+const char *options_ofont() {
+ return options.ofont;
+}
+
+void options_save() {
+
+ FILE *fh;
+ int glob_retval;
+ glob_t glob_result;
+ struct stat stat_buffer;
+ struct stat *statbuf;
+ char *dir_filename;
+ char *rc_filename;
+ int whoops = 0;
+ char *newline;
+
+ const char *ousername;
+ const char *opassword;
+ const char *ohostname;
+ int oport;
+
+ glob_retval = glob("~", GLOB_TILDE, NULL, &glob_result);
+ statbuf = &stat_buffer;
+ if ( glob_retval != 0 ) {
+
+ debug_print("OP: glob() for ~/.tuxmessenger failed: ");
+ switch ( glob_retval ) {
+ case GLOB_NOSPACE : debug_print("GLOB_NOSPACE\n"); break;
+ case GLOB_ABORTED : debug_print("GLOB_ABORTED\n"); break;
+ case GLOB_NOMATCH : debug_print("GLOB_NOMATCH\n"); break;
+ default : debug_print("Unknown!\n"); break;
+ }
+ debug_print("OP: Couldn't save options :(\n");
+ return;
+
+ }
+
+ dir_filename = malloc(strlen(glob_result.gl_pathv[0]) + 15);
+ strcpy(dir_filename, glob_result.gl_pathv[0]);
+ globfree(&glob_result);
+ strcat(dir_filename, "/.tuxmessenger");
+
+ if ( stat(dir_filename, statbuf) != -1 ) {
+
+ if ( S_ISDIR(stat_buffer.st_mode) ) {
+
+ debug_print("OP: Found '%s'.\n", dir_filename);
+ /* All is good. */
+
+ } else {
+
+ debug_print("OP: Found '%s', but it isn't a directory!\n", dir_filename);
+ debug_print("OP: Couldn't save options :(\n");
+ return;
+
+ }
+
+ } else {
+
+ /* This shouldn't happen here unless someone's been a muppet. */
+ debug_print("OP: ~/.tuxmessenger directory not found: creating it.\n");
+ options_createhomedir(dir_filename);
+
+ }
+
+ rc_filename = malloc(strlen(dir_filename) + 8);
+ strcpy(rc_filename, dir_filename);
+ strcat(rc_filename, "/config");
+
+ fh = fopen(rc_filename, "w");
+ if ( chmod(rc_filename, S_IRUSR | S_IWUSR) != 0 ) {
+ error_report("Couldn't set permissions on config file!");
+ }
+ if ( fh == NULL ) {
+
+ debug_print("OP: Error opening options file.\n");
+ debug_print("OP: Couldn't save options :(\n");
+ return;
+
+ }
+
+ newline = malloc(2);
+ sprintf(newline, "\n");
+
+ if ( options_rememberlogindetails() ) {
+ ousername = options_username();
+ opassword = options_password();
+ ohostname = options_hostname();
+ oport = options_port();
+ } else {
+ ousername = "";
+ opassword = "";
+ ohostname = DEFAULT_HOSTNAME;
+ oport = DEFAULT_NS_PORT;
+ }
+
+ /* ----------------------- Username -------------------------- */
+ if ( fputs("username ", fh) == EOF ) {
+ whoops = 1;
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(ousername, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(newline, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+
+ /* ----------------------- Password -------------------------- */
+ if ( whoops == 0 ) {
+ if ( fputs("password ", fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(opassword, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(newline, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+
+ /* ----------------------- wget -------------------------- */
+ if ( whoops == 0 ) {
+ if ( fputs("wget ", fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(options_wget(), fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(newline, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+
+ /* ----------------------- Hostname -------------------------- */
+ if ( whoops == 0 ) {
+ if ( fputs("hostname ", fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(ohostname, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(newline, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+
+ /* ----------------------- Port -------------------------- */
+ if ( whoops == 0 ) {
+
+ char *port;
+
+ if ( fputs("port ", fh) == EOF ) {
+ whoops = 1;
+ }
+ /* options_port() < 65536 because of data size - so always fits. */
+ port = malloc(6);
+ sprintf(port, "%i", oport);
+ if ( whoops == 0 ) {
+ if ( fputs(port, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(newline, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ free(port);
+
+ }
+
+ /* ----------------------- Show Offline Contacts ---------------------- */
+ if ( whoops == 0 ) {
+
+ if ( options_hideoffline() ) {
+ if ( fputs("hide_offline\n", fh) == EOF ) {
+ whoops = 1;
+ }
+ } /* Else write nothing. */
+
+ }
+
+ /* ----------------------- Show CSM ---------------------- */
+ if ( whoops == 0 ) {
+
+ if ( options_hidecsm() ) {
+ if ( fputs("hide_localcsm\n", fh) == EOF ) {
+ whoops = 1;
+ }
+ } /* Else write nothing. */
+
+ }
+
+ /* ----------------------- Local colour -------------------------- */
+ if ( options_localcolour_string() != NULL ) {
+ if ( whoops == 0 ) {
+ if ( fputs("localcolour ", fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(options_localcolour_string(), fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(newline, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ }
+
+ /* ----------------------- Override Contacts' Fonts/Colours ---------------------- */
+ if ( whoops == 0 ) {
+
+ if ( options_ofontoverride() ) {
+ if ( fputs("ofontoverride\n", fh) == EOF ) {
+ whoops = 1;
+ }
+ } /* Else write nothing. */
+
+ }
+
+ /* ----------------------- Override colour -------------------------- */
+ if ( options_ocolour_string() ) {
+ if ( whoops == 0 ) {
+ if ( fputs("ocolour ", fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ char *string = options_ocolour_string();
+ if ( fputs(string, fh) == EOF ) {
+ whoops = 1;
+ }
+ free(string);
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(newline, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ }
+
+ /* ----------------------- Local font -------------------------- */
+ if ( options_localfont() ) {
+ if ( whoops == 0 ) {
+ if ( fputs("localfont ", fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(options_localfont(), fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(newline, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ }
+
+ /* ----------------------- Contacts' font -------------------------- */
+ if ( options_ofont() ) {
+ if ( whoops == 0 ) {
+ if ( fputs("ofont ", fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(options_ofont(), fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ if ( whoops == 0 ) {
+ if ( fputs(newline, fh) == EOF ) {
+ whoops = 1;
+ }
+ }
+ }
+
+ /* ----------------------- IRC style ---------------------- */
+ if ( whoops == 0 ) {
+
+ if ( options_ircstyle() ) {
+ if ( fputs("ircstyle\n", fh) == EOF ) {
+ whoops = 1;
+ }
+ } /* Else write nothing. */
+
+ }
+
+ /* ----------------------- Show emoticons ---------------------- */
+ if ( whoops == 0 ) {
+
+ if ( options_showemoticons() ) {
+ if ( fputs("show_emoticons\n", fh) == EOF ) {
+ whoops = 1;
+ }
+ } /* Else write nothing. */
+
+ }
+
+ /* ----------------------- Show avatars --------------------- */
+ if ( whoops == 0 ) {
+
+ if ( options_showavatars() ) {
+ if ( fputs("show_avatars\n", fh) == EOF ) {
+ whoops = 1;
+ }
+ } /* Else write nothing. */
+
+ }
+
+ /* ----------------------- Timestamps ---------------------- */
+ if ( whoops == 0 ) {
+
+ if ( options_timestamps() ) {
+ if ( fputs("timestamps\n", fh) == EOF ) {
+ whoops = 1;
+ }
+ } /* Else write nothing. */
+
+ }
+
+ if ( whoops != 0 ) {
+ debug_print("OP: Whoops! Config file write failed.\n");
+ }
+
+ fclose(fh);
+ free(newline);
+
+}
diff --git a/src/options.h b/src/options.h
new file mode 100644
index 0000000..c42467e
--- /dev/null
+++ b/src/options.h
@@ -0,0 +1,73 @@
+/*
+ * options.h
+ *
+ * Low-level option handling
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+#include <gdk/gdk.h>
+
+extern void options_load();
+extern void options_save();
+
+/* Single-function interfaces to option values. Returned pointers must not be freed nor strings altered. */
+extern const char *options_username();
+extern const char *options_password();
+extern const char *options_hostname();
+extern unsigned short int options_port();
+extern const char *options_wget();
+extern const char *options_gtc();
+extern const char *options_blp();
+extern unsigned int options_rememberlogindetails();
+extern unsigned int options_hideoffline();
+extern unsigned int options_hidecsm();
+extern const char *options_localcolour_string();
+extern const GdkColor *options_localcolour_gdk();
+extern unsigned int options_ofontoverride(void);
+extern const GdkColor *options_ocolour_gdk();
+extern const char *options_localfont();
+extern const char *options_ofont();
+extern unsigned int options_ircstyle();
+extern unsigned int options_timestamps();
+extern unsigned int options_showavatars();
+extern unsigned int options_showemoticons();
+
+extern void options_setusername(const char *username);
+extern void options_setpassword(const char *password);
+extern void options_sethostname(const char *hostname);
+extern void options_setport(const int port);
+extern void options_setwget(const char *wget);
+extern void options_setrememberlogindetails(unsigned int remember);
+extern void options_sethideoffline(unsigned int hide);
+extern void options_sethidecsm(unsigned int hide);
+extern void options_setlocalcolour_gdk(const GdkColor *colour);
+extern void options_setofontoverride(unsigned int override);
+extern void options_setocolour_gdk(const GdkColor *colour);
+extern void options_setlocalfont(const char *font);
+extern void options_setofont(const char *font);
+extern void options_setircstyle(unsigned int ircstyle);
+extern void options_settimestamps(unsigned int timestamps);
+extern void options_setshowavatars(unsigned int showavatars);
+extern void options_setshowemoticons(unsigned int showemoticons);
+
+#endif /* OPTIONS_H */
diff --git a/src/prefswindow.c b/src/prefswindow.c
new file mode 100644
index 0000000..d056646
--- /dev/null
+++ b/src/prefswindow.c
@@ -0,0 +1,921 @@
+/*
+ * prefswindow.c
+ *
+ * The preferences window
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "options.h"
+#include "msnprotocol.h"
+#include "debug.h"
+#include "contactlist.h"
+#include "routines.h"
+#include "twnauth.h"
+#include "error.h"
+
+/* This is where I go slightly mad. Solid UI code... */
+
+/* Linked list item definition for storing changes to be made to the block/allow lists */
+typedef struct allowblockchange_t {
+ struct allowblockchange *next;
+ char *string;
+} AllowBlockChange;
+
+static struct {
+
+ unsigned int open; /* Non-zero means the prefs window is open. */
+ GtkWidget *window; /* The prefs window itself */
+
+ GtkWidget *blocklist; /* vbox in the "Allow/Block lists" tab */
+ GtkWidget *reverselist; /* vbox in the "Reverse list" tab */
+ GtkWidget *blocklist_sub; /* vbox in the "Allow/Block lists" tab */
+ GtkWidget *reverselist_sub; /* vbox in the "Reverse list" tab */
+
+ GtkWidget *blocklist_allowbutton; /* "<- Allow" button */
+ GtkWidget *blocklist_blockbutton; /* "Block ->" button */
+ GtkWidget *blocklist_allowclist; /* CList for AL */
+ GtkWidget *blocklist_blockclist; /* CList for BL */
+ GtkWidget *reverselist_clist; /* CList for RL */
+ GtkWidget *reverselist_gtc; /* Tick box */
+ GtkWidget *blocklist_blp; /* Tick box */
+ GtkWidget *ofontbutton; /* Font button for other contacts */
+ GtkWidget *ocolourbutton; /* Colour button for other contacts */
+
+ int block_selected;
+ int allow_selected;
+
+ GtkWidget *wget; /* "Wget" box */
+
+} prefswindow = {
+
+ 0,
+ NULL,
+
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+
+ -1,
+ -1,
+
+ NULL
+
+};
+
+static void prefswindow_allowselect(GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data) {
+
+ prefswindow.allow_selected = row;
+ gtk_widget_set_sensitive(prefswindow.blocklist_blockbutton, 1);
+ if ( prefswindow.block_selected != -1 ) {
+ gtk_clist_unselect_row(GTK_CLIST(prefswindow.blocklist_blockclist), prefswindow.block_selected, 0);
+ }
+
+}
+
+static void prefswindow_allowunselect(GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data) {
+
+ prefswindow.allow_selected = -1;
+ gtk_widget_set_sensitive(prefswindow.blocklist_blockbutton, 0);
+
+}
+
+static void prefswindow_blockselect(GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data) {
+
+ prefswindow.block_selected = row;
+ gtk_widget_set_sensitive(prefswindow.blocklist_allowbutton, 1);
+ if ( prefswindow.allow_selected != -1 ) {
+ gtk_clist_unselect_row(GTK_CLIST(prefswindow.blocklist_allowclist), prefswindow.allow_selected, 0);
+ }
+
+}
+
+static void prefswindow_blockunselect(GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data) {
+
+ prefswindow.block_selected = -1;
+ gtk_widget_set_sensitive(prefswindow.blocklist_allowbutton, 0);
+
+}
+
+static void prefswindow_allowclick() {
+}
+
+static void prefswindow_blockclick() {
+}
+
+static void prefswindow_filllist(char *list, GtkWidget *clist) {
+
+/* ContactItem *token;
+ char *contact;
+
+ token = NULL;
+ contact = contactlist_getcontact(list, &token);
+ while ( token ) {
+
+ char *friendlyname;
+ char *friendlyname_decoded;
+ char *username;
+ char *full_string;
+ char *clist_add[1];
+
+ username = routines_lindex(contact, 0);
+ friendlyname = routines_lindex(contact, 1);
+
+ if ( strlen(friendlyname) == 0 ) {
+
+ clist_add[0] = username;
+
+ } else {
+
+ friendlyname_decoded = routines_urldecode(friendlyname);
+ free(friendlyname);
+ full_string = malloc(strlen(username) + strlen(friendlyname_decoded) + 3 + 1);
+ strcpy(full_string, username);
+ strcat(full_string, " - ");
+ strcat(full_string, friendlyname_decoded);
+
+ clist_add[0] = full_string;
+
+ free(friendlyname_decoded);
+ free(username);
+
+ }
+
+ gtk_clist_append(GTK_CLIST(clist), clist_add);
+ free(clist_add[0]);
+
+ contact = contactlist_getcontact(list, &token);
+
+ }*/
+
+}
+
+static void prefswindow_fillreverselist() {
+
+/* ContactItem *token;
+ char *contact;
+
+ token = NULL;
+ contact = contactlist_getcontact("RL", &token);
+ while ( token ) {
+
+ char *friendlyname;
+ char *friendlyname_decoded;
+ char *username;
+ char *clist_add[3];
+
+ username = routines_lindex(contact, 0);
+ friendlyname = routines_lindex(contact, 1);
+
+ clist_add[0] = username;
+ friendlyname_decoded = routines_urldecode(friendlyname);
+ free(friendlyname);
+ clist_add[1] = friendlyname_decoded;
+
+ if ( contactlist_isonlist("AL", username) ) {
+ clist_add[2] = "Allow";
+ } else if ( contactlist_isonlist("BL", username) ) {
+ clist_add[2] = "Block";
+ } else {
+ clist_add[2] = "Neither";
+ }
+
+ gtk_clist_append(GTK_CLIST(prefswindow.reverselist_clist), clist_add);
+
+ free(friendlyname_decoded);
+ free(username);
+
+ contact = contactlist_getcontact("RL", &token);
+
+ }*/
+
+}
+
+/* Parallel of mainwindow_setonline. */
+void prefswindow_setonline() {
+
+ GtkWidget *prefs_blocklist_vbox;
+ GtkWidget *prefs_blocklist_buttonbox;
+ GtkWidget *prefs_blocklist_scroll;
+ GtkWidget *prefs_blocklist_viewport;
+ GtkWidget *prefs_blocklist_hbox;
+ GtkWidget *prefs_blocklist_allow_clist_label;
+ GtkWidget *prefs_blocklist_block_clist_label;
+
+ GtkWidget *prefs_reverselist_vbox;
+ GtkWidget *prefs_reverselist_scroll;
+ GtkWidget *prefs_reverselist_clist_username;
+ GtkWidget *prefs_reverselist_clist_displayname;
+ GtkWidget *prefs_reverselist_clist_status;
+
+ if ( !prefswindow.open ) {
+ return;
+ }
+
+ if ( prefswindow.blocklist_sub != NULL ) {
+ gtk_widget_destroy(prefswindow.blocklist_sub);
+ }
+
+ if ( prefswindow.reverselist_sub != NULL ) {
+ gtk_widget_destroy(prefswindow.reverselist_sub);
+ }
+
+ prefswindow.blocklist_sub = gtk_vbox_new(TRUE, 0);
+ prefswindow.reverselist_sub = gtk_vbox_new(TRUE, 0);
+ gtk_widget_show(prefswindow.reverselist_sub);
+
+ prefs_blocklist_vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_ref (prefs_blocklist_vbox);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_vbox", prefs_blocklist_vbox,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_blocklist_vbox);
+ gtk_container_add (GTK_CONTAINER (prefswindow.blocklist_sub), prefs_blocklist_vbox);
+ gtk_container_add (GTK_CONTAINER (prefswindow.blocklist), prefswindow.blocklist_sub);
+ gtk_widget_show(prefswindow.blocklist_sub);
+
+ prefs_blocklist_scroll = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_ref (prefs_blocklist_scroll);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_scroll", prefs_blocklist_scroll,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_blocklist_scroll);
+ gtk_box_pack_start (GTK_BOX (prefs_blocklist_vbox), prefs_blocklist_scroll, TRUE, TRUE, 0);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (prefs_blocklist_scroll), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+
+ prefs_blocklist_viewport = gtk_viewport_new (NULL, NULL);
+ gtk_widget_ref (prefs_blocklist_viewport);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_viewport", prefs_blocklist_viewport,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_blocklist_viewport);
+ gtk_container_add (GTK_CONTAINER (prefs_blocklist_scroll), prefs_blocklist_viewport);
+
+ prefs_blocklist_hbox = gtk_hbox_new (FALSE, 0);
+ gtk_widget_ref (prefs_blocklist_hbox);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_hbox", prefs_blocklist_hbox,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_blocklist_hbox);
+ gtk_container_add (GTK_CONTAINER (prefs_blocklist_viewport), prefs_blocklist_hbox);
+
+ prefswindow.blocklist_allowclist = gtk_clist_new (1);
+ gtk_widget_ref (prefswindow.blocklist_allowclist);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_allow_clist", prefswindow.blocklist_allowclist,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefswindow.blocklist_allowclist);
+ gtk_box_pack_start (GTK_BOX (prefs_blocklist_hbox), prefswindow.blocklist_allowclist, TRUE, TRUE, 0);
+ gtk_clist_set_column_width (GTK_CLIST (prefswindow.blocklist_allowclist), 0, 80);
+ gtk_clist_column_titles_show (GTK_CLIST (prefswindow.blocklist_allowclist));
+
+ prefs_blocklist_allow_clist_label = gtk_label_new ("Allow");
+ gtk_widget_ref (prefs_blocklist_allow_clist_label);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_allow_clist_label", prefs_blocklist_allow_clist_label,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_blocklist_allow_clist_label);
+ gtk_clist_set_column_widget (GTK_CLIST (prefswindow.blocklist_allowclist), 0, prefs_blocklist_allow_clist_label);
+
+ prefswindow.blocklist_blockclist = gtk_clist_new (1);
+ gtk_widget_ref (prefswindow.blocklist_blockclist);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_block_clist", prefswindow.blocklist_blockclist,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefswindow.blocklist_blockclist);
+ gtk_box_pack_start (GTK_BOX (prefs_blocklist_hbox), prefswindow.blocklist_blockclist, TRUE, TRUE, 0);
+ gtk_clist_set_column_width (GTK_CLIST (prefswindow.blocklist_blockclist), 0, 80);
+ gtk_clist_column_titles_show (GTK_CLIST (prefswindow.blocklist_blockclist));
+
+ prefs_blocklist_block_clist_label = gtk_label_new ("Block");
+ gtk_widget_ref (prefs_blocklist_block_clist_label);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_block_clist_label", prefs_blocklist_block_clist_label,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_blocklist_block_clist_label);
+ gtk_clist_set_column_widget (GTK_CLIST (prefswindow.blocklist_blockclist), 0, prefs_blocklist_block_clist_label);
+
+ prefs_blocklist_buttonbox = gtk_hbutton_box_new ();
+ gtk_widget_ref (prefs_blocklist_buttonbox);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_buttonbox", prefs_blocklist_buttonbox,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_blocklist_buttonbox);
+ gtk_box_pack_start (GTK_BOX (prefs_blocklist_vbox), prefs_blocklist_buttonbox, FALSE, FALSE, 0);
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (prefs_blocklist_buttonbox), GTK_BUTTONBOX_SPREAD);
+
+ prefswindow.blocklist_allowbutton = gtk_button_new_with_label ("<- Allow");
+ gtk_widget_ref (prefswindow.blocklist_allowbutton);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_allow_button", prefswindow.blocklist_allowbutton,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefswindow.blocklist_allowbutton);
+ gtk_container_add (GTK_CONTAINER (prefs_blocklist_buttonbox), prefswindow.blocklist_allowbutton);
+ GTK_WIDGET_SET_FLAGS (prefswindow.blocklist_allowbutton, GTK_CAN_DEFAULT);
+
+ prefswindow.blocklist_blockbutton = gtk_button_new_with_label ("Block ->");
+ gtk_widget_ref (prefswindow.blocklist_blockbutton);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_block_button", prefswindow.blocklist_blockbutton,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefswindow.blocklist_blockbutton);
+ gtk_container_add (GTK_CONTAINER (prefs_blocklist_buttonbox), prefswindow.blocklist_blockbutton);
+ GTK_WIDGET_SET_FLAGS (prefswindow.blocklist_blockbutton, GTK_CAN_DEFAULT);
+
+ gtk_container_set_border_width(GTK_CONTAINER(prefswindow.blocklist), 10);
+ gtk_clist_column_titles_passive(GTK_CLIST(prefswindow.blocklist_allowclist));
+ gtk_clist_column_titles_passive(GTK_CLIST(prefswindow.blocklist_blockclist));
+
+ prefswindow.blocklist_blp = gtk_check_button_new_with_label ("Block all other users");
+ gtk_widget_ref (prefswindow.blocklist_blp);
+ gtk_widget_show (prefswindow.blocklist_blp);
+ gtk_box_pack_start (GTK_BOX (prefs_blocklist_vbox), prefswindow.blocklist_blp, FALSE, FALSE, 0);
+
+ if ( strcmp(options_blp(), "BL") == 0) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefswindow.blocklist_blp), TRUE);
+ } else {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefswindow.blocklist_blp), FALSE);
+ }
+
+ gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_allowclist), "select_row", GTK_SIGNAL_FUNC(prefswindow_allowselect), NULL);
+ gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_allowclist), "unselect_row", GTK_SIGNAL_FUNC(prefswindow_allowunselect), NULL);
+ gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_blockbutton), "clicked", GTK_SIGNAL_FUNC(prefswindow_allowclick), NULL);
+
+ gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_blockclist), "select_row", GTK_SIGNAL_FUNC(prefswindow_blockselect), NULL);
+ gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_blockclist), "unselect_row", GTK_SIGNAL_FUNC(prefswindow_blockunselect), NULL);
+ gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_allowbutton), "clicked", GTK_SIGNAL_FUNC(prefswindow_blockclick), NULL);
+
+ prefswindow_filllist("BL", prefswindow.blocklist_blockclist);
+ prefswindow_filllist("AL", prefswindow.blocklist_allowclist);
+
+ gtk_widget_set_sensitive(prefswindow.blocklist_allowbutton , 0);
+ gtk_widget_set_sensitive(prefswindow.blocklist_blockbutton, 0);
+
+ /* ************* now the Reverse List section ************** */
+
+ prefs_reverselist_vbox = gtk_vbox_new (FALSE, 3);
+ gtk_widget_ref (prefs_reverselist_vbox);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_vbox", prefs_reverselist_vbox,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_reverselist_vbox);
+ gtk_container_add (GTK_CONTAINER (prefswindow.reverselist_sub), prefs_reverselist_vbox);
+ gtk_container_add(GTK_CONTAINER(prefswindow.reverselist), prefswindow.reverselist_sub);
+
+ prefs_reverselist_scroll = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_ref (prefs_reverselist_scroll);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_scroll", prefs_reverselist_scroll,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_reverselist_scroll);
+ gtk_box_pack_start (GTK_BOX (prefs_reverselist_vbox), prefs_reverselist_scroll, TRUE, TRUE, 0);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (prefs_reverselist_scroll), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+
+ prefswindow.reverselist_clist = gtk_clist_new (3);
+ gtk_widget_ref (prefswindow.reverselist_clist);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_clist", prefswindow.reverselist_clist,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefswindow.reverselist_clist);
+ gtk_container_add (GTK_CONTAINER (prefs_reverselist_scroll), prefswindow.reverselist_clist);
+ gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 0, 80);
+ gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 1, 80);
+ gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 2, 80);
+ gtk_clist_column_titles_show (GTK_CLIST (prefswindow.reverselist_clist));
+
+ prefs_reverselist_clist_username = gtk_label_new ("Username");
+ gtk_widget_ref (prefs_reverselist_clist_username);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_clist_username", prefs_reverselist_clist_username,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_reverselist_clist_username);
+ gtk_clist_set_column_widget (GTK_CLIST (prefswindow.reverselist_clist), 0, prefs_reverselist_clist_username);
+
+ prefs_reverselist_clist_displayname = gtk_label_new ("Display Name");
+ gtk_widget_ref (prefs_reverselist_clist_displayname);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_clist_displayname", prefs_reverselist_clist_displayname,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_reverselist_clist_displayname);
+ gtk_clist_set_column_widget (GTK_CLIST (prefswindow.reverselist_clist), 1, prefs_reverselist_clist_displayname);
+ gtk_widget_set_usize (prefs_reverselist_clist_displayname, 128, -2);
+
+ prefs_reverselist_clist_status = gtk_label_new ("Status");
+ gtk_widget_ref (prefs_reverselist_clist_status);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_clist_status", prefs_reverselist_clist_status,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefs_reverselist_clist_status);
+ gtk_clist_set_column_widget (GTK_CLIST (prefswindow.reverselist_clist), 2, prefs_reverselist_clist_status);
+ gtk_widget_set_usize (prefs_reverselist_clist_status, 31, -2);
+
+ prefswindow.reverselist_gtc = gtk_check_button_new_with_label ("Add new users to the Allow List automatically");
+ gtk_widget_ref (prefswindow.reverselist_gtc);
+ gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_gtc", prefswindow.reverselist_gtc,
+ (GtkDestroyNotify) gtk_widget_unref);
+ gtk_widget_show (prefswindow.reverselist_gtc);
+ gtk_box_pack_start (GTK_BOX (prefs_reverselist_vbox), prefswindow.reverselist_gtc, FALSE, FALSE, 0);
+
+ if ( strcmp(options_gtc(), "N") == 0) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefswindow.reverselist_gtc), TRUE);
+ } else {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefswindow.reverselist_gtc), FALSE);
+ }
+
+ gtk_container_set_border_width(GTK_CONTAINER(prefswindow.reverselist), 10);
+ gtk_clist_column_titles_passive(GTK_CLIST(prefswindow.reverselist_clist));
+
+ gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 0, 149);
+ gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 1, 210);
+ gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 2, 69);
+
+ prefswindow_fillreverselist();
+
+}
+
+/* Parallel of mainwindow_setdispatch. Tell the user they can't modify lists while disconnected. */
+void prefswindow_setdispatch() {
+
+ GtkWidget *message_label;
+
+ if ( !prefswindow.open ) {
+ return;
+ }
+
+ if ( prefswindow.blocklist_sub != NULL ) {
+ gtk_widget_destroy(prefswindow.blocklist_sub);
+ }
+
+ if ( prefswindow.reverselist_sub != NULL ) {
+ gtk_widget_destroy(prefswindow.reverselist_sub);
+ }
+
+ prefswindow.blocklist_sub = gtk_vbox_new(TRUE, 0);
+ assert(prefswindow.blocklist_sub != NULL);
+ gtk_container_add(GTK_CONTAINER(prefswindow.blocklist), prefswindow.blocklist_sub);
+ message_label = gtk_label_new("You must be signed in to view or modify your block list");
+ assert(message_label != NULL);
+ gtk_box_pack_start(GTK_BOX(prefswindow.blocklist_sub), message_label, TRUE, TRUE, 0);
+ gtk_label_set_line_wrap(GTK_LABEL(message_label), TRUE);
+ gtk_label_set_justify(GTK_LABEL(message_label), GTK_JUSTIFY_CENTER);
+ gtk_widget_show_all(prefswindow.blocklist_sub);
+
+ prefswindow.reverselist_sub = gtk_vbox_new(TRUE, 0);
+ assert(prefswindow.reverselist_sub != NULL);
+ gtk_container_add(GTK_CONTAINER(prefswindow.reverselist), prefswindow.reverselist_sub);
+ message_label = gtk_label_new("You must be signed in to view your reverse list");
+ assert(message_label != NULL);
+ gtk_box_pack_start(GTK_BOX(prefswindow.reverselist_sub), message_label, TRUE, TRUE, 0);
+ gtk_label_set_line_wrap(GTK_LABEL(message_label), TRUE);
+ gtk_label_set_justify(GTK_LABEL(message_label), GTK_JUSTIFY_CENTER);
+ gtk_widget_show_all(prefswindow.reverselist_sub);
+
+}
+
+static void prefswindow_closed() {
+ prefswindow.open = 0;
+}
+
+static void prefswindow_lcolsel(GtkWidget *widget, gpointer data) {
+
+ GdkColor colour;
+
+ gtk_color_button_get_color(GTK_COLOR_BUTTON(widget), &colour);
+ options_setlocalcolour_gdk(&colour);
+ options_save();
+
+}
+
+static void prefswindow_lfontsel(GtkWidget *widget, gpointer data) {
+
+ const char *font;
+
+ font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget));
+ debug_print("PW: Font name '%s'\n", font);
+ options_setlocalfont(font);
+ options_save();
+
+}
+
+static void prefswindow_ofontsel(GtkWidget *widget, gpointer data) {
+
+ const char *font;
+
+ font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget));
+ debug_print("PW: Font name '%s'\n", font);
+ options_setofont(font);
+ options_save();
+
+}
+
+static void prefswindow_ocolsel(GtkWidget *widget, gpointer data) {
+
+ GdkColor colour;
+
+ gtk_color_button_get_color(GTK_COLOR_BUTTON(widget), &colour);
+ options_setocolour_gdk(&colour);
+ options_save();
+
+}
+
+static void prefswindow_ofontoverride_toggle(GtkWidget *widget, gpointer data) {
+
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) {
+ options_setofontoverride(TRUE);
+ gtk_widget_set_sensitive(prefswindow.ofontbutton, TRUE);
+ gtk_widget_set_sensitive(prefswindow.ocolourbutton, TRUE);
+ } else {
+ options_setofontoverride(FALSE);
+ gtk_widget_set_sensitive(prefswindow.ofontbutton, FALSE);
+ gtk_widget_set_sensitive(prefswindow.ocolourbutton, FALSE);
+ }
+ options_save();
+
+}
+
+static void prefswindow_ircstyle_toggle(GtkWidget *widget, gpointer data) {
+
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) {
+ options_setircstyle(TRUE);
+ } else {
+ options_setircstyle(FALSE);
+ }
+ options_save();
+
+}
+
+static void prefswindow_timestamps_toggle(GtkWidget *widget, gpointer data) {
+
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) {
+ options_settimestamps(TRUE);
+ } else {
+ options_settimestamps(FALSE);
+ }
+ options_save();
+
+}
+
+static void prefswindow_showavatars_toggle(GtkWidget *widget, gpointer data) {
+
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) {
+ options_setshowavatars(TRUE);
+ } else {
+ options_setshowavatars(FALSE);
+ }
+ options_save();
+
+}
+
+static gint prefswindow_validate_wget(GtkWidget *widget, gpointer data) {
+
+ const char *text = gtk_entry_get_text(GTK_ENTRY(widget));
+ options_setwget(text);
+ options_save();
+
+ return FALSE;
+
+}
+
+static void prefswindow_do_wget() {
+ gtk_entry_set_text(GTK_ENTRY(prefswindow.wget), options_wget());
+}
+
+static gint prefswindow_guess_wget(GtkWidget *widget, gpointer data) {
+
+ char *nexus;
+
+ options_setwget("/usr/bin/wget");
+ nexus = twnauth_loginurl();
+ if ( strlen(nexus) == 0 ) {
+ free(nexus);
+ options_setwget("/usr/bin/wget --no-check-certificate");
+ nexus = twnauth_loginurl();
+ if ( strlen(nexus) == 0 ) {
+ free(nexus);
+ options_setwget("/usr/local/bin/wget");
+ nexus = twnauth_loginurl();
+ if ( strlen(nexus) == 0 ) {
+ free(nexus);
+ options_setwget("/usr/local/bin/wget --no-check-certificate");
+ nexus = twnauth_loginurl();
+ if ( strlen(nexus) == 0 ) {
+ free(nexus);
+ options_setwget("~/bin/wget");
+ nexus = twnauth_loginurl();
+ if ( strlen(nexus) == 0 ) {
+ free(nexus);
+ options_setwget("~/bin/wget --no-check-certificate");
+ nexus = twnauth_loginurl();
+ if ( strlen(nexus) == 0 ) {
+ free(nexus);
+ options_setwget("wget");
+ nexus = twnauth_loginurl();
+ if ( strlen(nexus) == 0 ) {
+ free(nexus);
+ options_setwget("wget --no-check-certificate");
+ nexus = twnauth_loginurl();
+ if ( strlen(nexus) == 0 ) {
+ error_report("Couldn't determine correct wget configuration - is wget installed on your system?");
+ free(nexus);
+ return FALSE;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ free(nexus);
+ prefswindow_do_wget();
+ error_message("Successfully determined wget configuration.");
+
+ return FALSE;
+
+}
+
+static gint prefswindow_check_wget(GtkWidget *widget, gpointer data) {
+
+ char *nexus;
+
+ nexus = twnauth_loginurl();
+
+ if ( strlen(nexus) != 0 ) {
+ GtkWidget *window;
+ window = gtk_message_dialog_new(GTK_WINDOW(prefswindow.window), GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "'wget' configuration appears to be correct.");
+ g_signal_connect_swapped(window, "response", G_CALLBACK(gtk_widget_destroy), window);
+ gtk_widget_show(window);
+ } else {
+ GtkWidget *window;
+ window = gtk_message_dialog_new(GTK_WINDOW(prefswindow.window), GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, "'wget' appears not to work...");
+ g_signal_connect_swapped(window, "response", G_CALLBACK(gtk_widget_destroy), window);
+ gtk_widget_show(window);
+ }
+
+ free(nexus);
+
+ return FALSE;
+
+}
+
+static gint prefswindow_showemoticons_toggle(GtkWidget *widget, gpointer data) {
+
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) {
+ options_setshowemoticons(TRUE);
+ } else {
+ options_setshowemoticons(FALSE);
+ }
+ options_save();
+
+ return FALSE;
+
+}
+
+/* Open the preferences window */
+void prefswindow_open(int page) {
+
+ GtkWidget *prefs_notebook;
+
+ GtkWidget *prefs_auth_label;
+ GtkWidget *prefs_auth_box;
+ GtkWidget *prefs_auth_wget_label;
+ GtkWidget *prefs_auth_wget_guess;
+ GtkWidget *prefs_auth_wget_check;
+ GtkWidget *prefs_auth_wget_hbox;
+ GtkWidget *prefs_auth_wget_vbox;
+ GtkWidget *prefs_auth_wget_nbox;
+ GtkWidget *prefs_auth_wget_heading;
+ GtkWidget *prefs_auth_wget_heading_justify;
+
+ /* Arrrggghhhhh */
+ GtkWidget *prefs_messagewindows_label;
+ GtkWidget *prefs_messagewindows_box;
+ GtkWidget *prefs_messagewindows_text;
+ GtkWidget *prefs_messagewindows_font_box;
+ GtkWidget *prefs_messagewindows_font_button;
+ GtkWidget *prefs_messagewindows_font_label;
+ GtkWidget *prefs_messagewindows_font_label_justify;
+ GtkWidget *prefs_messagewindows_colour_button;
+ GtkWidget *prefs_messagewindows_ofont_box;
+ GtkWidget *prefs_messagewindows_ofont_label;
+ GtkWidget *prefs_messagewindows_ofont_label_justify;
+ GtkWidget *prefs_messagewindows_ofont_override;
+ GtkWidget *prefs_messagewindows_font_hbox;
+ GtkWidget *prefs_messagewindows_font_vbox;
+ GtkWidget *prefs_messagewindows_ofont_hbox;
+ GtkWidget *prefs_messagewindows_ofont_vbox;
+ GtkWidget *prefs_messagewindows_fbox;
+ GtkWidget *prefs_messagewindows_nbox;
+ GtkWidget *prefs_messagewindows_qbox;
+ GtkWidget *prefs_messagewindows_toggles_vbox;
+ GtkWidget *prefs_messagewindows_toggles_hbox;
+ GtkWidget *prefs_messagewindows_toggles_label;
+ GtkWidget *prefs_messagewindows_toggles_label_justify;
+ GtkWidget *prefs_messagewindows_toggles_irc;
+ GtkWidget *prefs_messagewindows_toggles_timestamp;
+ GtkWidget *prefs_messagewindows_toggles_avatars;
+ GtkWidget *prefs_messagewindows_toggles_emoticons;
+
+ /* Prevent opening of the preferences window more than once */
+ if ( prefswindow.open == 1 ) {
+ return;
+ }
+
+ prefswindow.window = gtk_dialog_new_with_buttons("TuxMessenger Preferences", mainwindow_gtkwindow(), 0, GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL);
+ gtk_dialog_set_has_separator(GTK_DIALOG(prefswindow.window), FALSE);
+
+ prefs_notebook = gtk_notebook_new();
+ gtk_notebook_set_tab_pos(GTK_NOTEBOOK(prefs_notebook), GTK_POS_TOP);
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(prefswindow.window)->vbox), prefs_notebook, TRUE, TRUE, 0);
+
+ /* ****************** Message Windows *************************************************************/
+
+ prefs_messagewindows_label = gtk_label_new("Message Windows");
+ prefs_messagewindows_box = gtk_vbox_new(FALSE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(prefs_messagewindows_box), 12);
+ gtk_notebook_append_page(GTK_NOTEBOOK(prefs_notebook), prefs_messagewindows_box, prefs_messagewindows_label);
+
+ prefs_messagewindows_text = gtk_label_new("");
+ gtk_label_set_markup(GTK_LABEL(prefs_messagewindows_text), "<span style=\"italic\" weight=\"light\">These settings define the behaviour of newly-created instant message windows. You can also set all of these options individually using the menus for each window.</span>");
+ gtk_widget_set_size_request(GTK_WIDGET(prefs_messagewindows_text), 500, -1);
+ gtk_label_set_line_wrap(GTK_LABEL(prefs_messagewindows_text), TRUE);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_box), prefs_messagewindows_text, FALSE, FALSE, 0);
+
+ prefs_messagewindows_fbox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_box), prefs_messagewindows_fbox, FALSE, FALSE, 12);
+ prefs_messagewindows_font_label = gtk_label_new("");
+ prefs_messagewindows_font_label_justify = gtk_hbox_new(FALSE, 0);
+ gtk_label_set_markup(GTK_LABEL(prefs_messagewindows_font_label), "<span weight=\"bold\">Font and Colour for Your Messages</span>");
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_font_label_justify), prefs_messagewindows_font_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_fbox), prefs_messagewindows_font_label_justify, FALSE, FALSE, 6);
+
+ prefs_messagewindows_font_vbox = gtk_vbox_new(FALSE, 0);
+ prefs_messagewindows_font_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_fbox), prefs_messagewindows_font_hbox, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_font_hbox), prefs_messagewindows_font_vbox, FALSE, FALSE, 12);
+
+ prefs_messagewindows_font_box = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_font_vbox), prefs_messagewindows_font_box, FALSE, FALSE, 0);
+ if ( options_localfont() ) {
+ prefs_messagewindows_font_button = gtk_font_button_new_with_font(options_localfont());
+ } else {
+ prefs_messagewindows_font_button = gtk_font_button_new();
+ }
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_font_box), prefs_messagewindows_font_button, FALSE, FALSE, 6);
+ g_signal_connect(G_OBJECT(prefs_messagewindows_font_button), "font-set", GTK_SIGNAL_FUNC(prefswindow_lfontsel), NULL);
+
+ if ( options_localcolour_gdk() == NULL ) {
+ prefs_messagewindows_colour_button = gtk_color_button_new();
+ } else {
+ prefs_messagewindows_colour_button = gtk_color_button_new_with_color(options_localcolour_gdk());
+ }
+ g_signal_connect(G_OBJECT(prefs_messagewindows_colour_button), "color-set", GTK_SIGNAL_FUNC(prefswindow_lcolsel), NULL);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_font_box), prefs_messagewindows_colour_button, FALSE, FALSE, 6);
+
+ prefs_messagewindows_nbox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_box), prefs_messagewindows_nbox, FALSE, FALSE, 12);
+ prefs_messagewindows_ofont_label = gtk_label_new("");
+ prefs_messagewindows_ofont_label_justify = gtk_hbox_new(FALSE, 0);
+ gtk_label_set_markup(GTK_LABEL(prefs_messagewindows_ofont_label), "<span weight=\"bold\">Fonts and Colours for Contacts' Messages</span>");
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_label_justify), prefs_messagewindows_ofont_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_nbox), prefs_messagewindows_ofont_label_justify, FALSE, FALSE, 0);
+
+ prefs_messagewindows_ofont_vbox = gtk_vbox_new(FALSE, 0);
+ prefs_messagewindows_ofont_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_nbox), prefs_messagewindows_ofont_hbox, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_hbox), prefs_messagewindows_ofont_vbox, FALSE, FALSE, 12);
+
+ prefs_messagewindows_ofont_override = gtk_check_button_new_with_label("Override contacts' chosen fonts");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs_messagewindows_ofont_override), options_ofontoverride());
+
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_vbox), prefs_messagewindows_ofont_override, FALSE, FALSE, 5);
+ prefs_messagewindows_ofont_box = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_vbox), prefs_messagewindows_ofont_box, FALSE, FALSE, 0);
+ if ( options_ofont() ) {
+ prefswindow.ofontbutton = gtk_font_button_new_with_font(options_ofont());
+ } else {
+ prefswindow.ofontbutton = gtk_font_button_new();
+ }
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_box), prefswindow.ofontbutton, FALSE, FALSE, 6);
+ g_signal_connect(G_OBJECT(prefswindow.ofontbutton), "font-set", GTK_SIGNAL_FUNC(prefswindow_ofontsel), NULL);
+ if ( options_ocolour_gdk() == NULL ) {
+ prefswindow.ocolourbutton = gtk_color_button_new();
+ } else {
+ prefswindow.ocolourbutton = gtk_color_button_new_with_color(options_ocolour_gdk());
+ }
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_box), prefswindow.ocolourbutton, FALSE, FALSE, 6);
+ g_signal_connect(G_OBJECT(prefswindow.ocolourbutton), "color-set", GTK_SIGNAL_FUNC(prefswindow_ocolsel), NULL);
+
+ /* Call the check button's callback to sort out greying-out of stuff as appropriate.
+ Results in a spurious option setting, but saves code duplication. */
+ prefswindow_ofontoverride_toggle(prefs_messagewindows_ofont_override, NULL);
+ /* NOW connect the signal handler. Doing it any earlier makes Bad Stuff happen. */
+ g_signal_connect(G_OBJECT(prefs_messagewindows_ofont_override), "toggled", GTK_SIGNAL_FUNC(prefswindow_ofontoverride_toggle), prefs_messagewindows_ofont_box);
+
+ prefs_messagewindows_qbox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_box), prefs_messagewindows_qbox, FALSE, FALSE, 12);
+ prefs_messagewindows_toggles_label = gtk_label_new("");
+ prefs_messagewindows_toggles_label_justify = gtk_hbox_new(FALSE, 0);
+ gtk_label_set_markup(GTK_LABEL(prefs_messagewindows_toggles_label), "<span weight=\"bold\">Text and Window Layout</span>");
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_label_justify), prefs_messagewindows_toggles_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_qbox), prefs_messagewindows_toggles_label_justify, FALSE, FALSE, 0);
+
+ prefs_messagewindows_toggles_vbox = gtk_vbox_new(FALSE, 0);
+ prefs_messagewindows_toggles_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_qbox), prefs_messagewindows_toggles_hbox, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_hbox), prefs_messagewindows_toggles_vbox, FALSE, FALSE, 12);
+
+ prefs_messagewindows_toggles_irc = gtk_check_button_new_with_label("Use IRC style for messages");
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_vbox), prefs_messagewindows_toggles_irc, FALSE, FALSE, 3);
+ prefs_messagewindows_toggles_timestamp = gtk_check_button_new_with_label("Timestamp messages");
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_vbox), prefs_messagewindows_toggles_timestamp, FALSE, FALSE, 3);
+ prefs_messagewindows_toggles_avatars = gtk_check_button_new_with_label("Display avatars");
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_vbox), prefs_messagewindows_toggles_avatars, FALSE, FALSE, 3);
+ prefs_messagewindows_toggles_emoticons = gtk_check_button_new_with_label("Display emoticons");
+ gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_vbox), prefs_messagewindows_toggles_emoticons, FALSE, FALSE, 3);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs_messagewindows_toggles_irc), options_ircstyle());
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs_messagewindows_toggles_timestamp), options_timestamps());
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs_messagewindows_toggles_avatars), options_showavatars());
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs_messagewindows_toggles_emoticons), options_showemoticons());
+ g_signal_connect(G_OBJECT(prefs_messagewindows_toggles_irc), "toggled", GTK_SIGNAL_FUNC(prefswindow_ircstyle_toggle), NULL);
+ g_signal_connect(G_OBJECT(prefs_messagewindows_toggles_timestamp), "toggled", GTK_SIGNAL_FUNC(prefswindow_timestamps_toggle), NULL);
+ g_signal_connect(G_OBJECT(prefs_messagewindows_toggles_avatars), "toggled", GTK_SIGNAL_FUNC(prefswindow_showavatars_toggle), NULL);
+ g_signal_connect(G_OBJECT(prefs_messagewindows_toggles_emoticons), "toggled", GTK_SIGNAL_FUNC(prefswindow_showemoticons_toggle), NULL);
+
+ /* ****************** Authentication *************************************************************/
+
+ prefs_auth_label = gtk_label_new("Authentication");
+ prefs_auth_box = gtk_vbox_new(FALSE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(prefs_auth_box), 12);
+ gtk_notebook_append_page(GTK_NOTEBOOK(prefs_notebook), prefs_auth_box, prefs_auth_label);
+
+ prefs_auth_wget_heading = gtk_label_new("");
+ gtk_label_set_markup(GTK_LABEL(prefs_auth_wget_heading), "<span weight=\"bold\">Command for 'wget'</span>");
+ prefs_auth_wget_heading_justify = gtk_hbox_new(FALSE, 0);
+ prefs_auth_wget_nbox = gtk_vbox_new(FALSE, 2);
+ gtk_box_pack_start(GTK_BOX(prefs_auth_wget_nbox), prefs_auth_wget_heading_justify, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_auth_wget_heading_justify), prefs_auth_wget_heading, FALSE, FALSE, 0);
+
+ gtk_box_pack_start(GTK_BOX(prefs_auth_box), prefs_auth_wget_nbox, FALSE, FALSE, 12);
+ prefs_auth_wget_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_auth_wget_nbox), prefs_auth_wget_hbox, FALSE, FALSE, 0);
+ prefs_auth_wget_vbox = gtk_vbox_new(FALSE, 6);
+ gtk_box_pack_start(GTK_BOX(prefs_auth_wget_hbox), prefs_auth_wget_vbox, FALSE, FALSE, 12);
+
+ prefs_auth_wget_label = gtk_label_new("");
+ gtk_label_set_markup(GTK_LABEL(prefs_auth_wget_label), "<span style=\"italic\" weight=\"light\">TuxMessenger calls the 'wget' program to help log in to the server. You need to specify the command to run to invoke a copy of 'wget' which is capable of using HTTPS. In most cases you can simply enter 'wget' here.</span>");
+ gtk_widget_set_size_request(GTK_WIDGET(prefs_auth_wget_label), 500, -1);
+ gtk_label_set_line_wrap(GTK_LABEL(prefs_auth_wget_label), TRUE);
+ gtk_box_pack_start(GTK_BOX(prefs_auth_wget_vbox), prefs_auth_wget_label, FALSE, FALSE, 0);
+
+ prefswindow.wget = gtk_entry_new_with_max_length(128);
+ prefswindow_do_wget();
+ gtk_box_pack_start(GTK_BOX(prefs_auth_wget_vbox), prefswindow.wget, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(prefswindow.wget), "focus-out-event", GTK_SIGNAL_FUNC(prefswindow_validate_wget), NULL);
+ g_signal_connect(G_OBJECT(prefswindow.wget), "activate", GTK_SIGNAL_FUNC(prefswindow_validate_wget), NULL);
+
+ prefs_auth_wget_guess = gtk_button_new_with_label("Attempt to automatically determine correct wget configuration");
+ gtk_box_pack_start(GTK_BOX(prefs_auth_wget_vbox), prefs_auth_wget_guess, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(prefs_auth_wget_guess), "clicked", GTK_SIGNAL_FUNC(prefswindow_guess_wget), NULL);
+
+ prefs_auth_wget_check = gtk_button_new_with_label("Check wget configuration");
+ gtk_box_pack_start(GTK_BOX(prefs_auth_wget_vbox), prefs_auth_wget_check, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(prefs_auth_wget_check), "clicked", GTK_SIGNAL_FUNC(prefswindow_check_wget), NULL);
+
+ /* ************************************************************************************************/
+
+ gtk_window_set_title(GTK_WINDOW(prefswindow.window), "TuxMessenger Preferences");
+ gtk_window_position(GTK_WINDOW(prefswindow.window), GTK_WIN_POS_MOUSE);
+ gtk_window_set_policy(GTK_WINDOW(prefswindow.window), FALSE, TRUE, FALSE);
+
+ gtk_widget_show_all(prefswindow.window);
+ gtk_notebook_set_page(GTK_NOTEBOOK(prefs_notebook), page);
+
+ g_signal_connect(G_OBJECT(prefswindow.window), "destroy", GTK_SIGNAL_FUNC(prefswindow_closed), NULL);
+ g_signal_connect(G_OBJECT(prefswindow.window), "response", GTK_SIGNAL_FUNC(gtk_widget_destroy), NULL);
+
+ prefswindow.open = 1;
+
+}
diff --git a/src/prefswindow.h b/src/prefswindow.h
new file mode 100644
index 0000000..aa05f56
--- /dev/null
+++ b/src/prefswindow.h
@@ -0,0 +1,30 @@
+/*
+ * prefswindow.h
+ *
+ * The preferences window
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef PREFSWINDOW_H
+#define PREFSWINDOW_H
+
+extern void prefswindow_open(void);
+
+#endif /* PREFSWINDOW_H */
diff --git a/src/reversewindow.c b/src/reversewindow.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/reversewindow.c
diff --git a/src/reversewindow.h b/src/reversewindow.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/reversewindow.h
diff --git a/src/routines.c b/src/routines.c
new file mode 100644
index 0000000..8ce937b
--- /dev/null
+++ b/src/routines.c
@@ -0,0 +1,800 @@
+/*
+ * routines.c
+ *
+ * Random Useful Routines and Functions
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <glob.h>
+#include <gdk/gdk.h>
+
+#include "debug.h"
+
+char *routines_glob(const char *filename) {
+
+ char *full_filename;
+ glob_t glob_result;
+ int glob_retval;
+
+ glob_retval = glob(filename, GLOB_TILDE, NULL, &glob_result);
+ if ( glob_retval != 0 ) {
+
+ debug_print("glob() failed: ");
+ switch ( glob_retval ) {
+ case GLOB_NOSPACE : debug_print("GLOB_NOSPACE\n"); break;
+ case GLOB_ABORTED : debug_print("GLOB_ABORTED\n"); break;
+ case GLOB_NOMATCH : debug_print("GLOB_NOMATCH\n"); break;
+ default : debug_print("Unknown!\n"); break;
+ }
+ return NULL;
+
+ }
+ full_filename = malloc(strlen(glob_result.gl_pathv[0])+1);
+ strcpy(full_filename, glob_result.gl_pathv[0]);
+ globfree(&glob_result);
+
+ return full_filename;
+
+}
+
+/* TCL-style lindex using ' ' as a separator */
+char *routines_lindex(char *string, unsigned int pos) {
+
+ unsigned int i;
+ unsigned int s=0;
+ char *minibuffer;
+ unsigned int max_output_length = strlen(string);
+ for ( i=0; i<strlen(string); i++ ) {
+
+ if ( string[i] == ' ' )
+ s++;
+ if ( s == pos ) {
+
+ char ch='z';
+ unsigned int p=0;
+ char *result;
+ result = malloc(2+max_output_length);
+ if ( s == 0 )
+ i--;
+ while ((ch != '\0') && (p<max_output_length)) {
+
+ ch = string[i+p+1];
+ if ( ch == ' ' )
+ ch = '\0';
+ if ( ch == '\r' )
+ ch = '\0';
+ if ( ch == '\n' )
+ ch = '\0';
+ result[p]=ch;
+ p++;
+
+ }
+ result[p]='\0';
+
+ /* now copy the string into a buffer that's the right size */
+ minibuffer=malloc(strlen(result)+sizeof(char));
+ strcpy(minibuffer, result);
+ free(result);
+ return minibuffer;
+
+ }
+
+ }
+ /* whoopsie didn't find it */
+ minibuffer = malloc(1);
+ minibuffer[0]='\0';
+ return minibuffer; /* return something that can safely be freed */
+
+}
+
+/* same as above, but returns all of the rest of the string, without stopping at the next space*/
+char *routines_lindexend(char *string, unsigned int pos) {
+
+ unsigned int i;
+ unsigned int s=0;
+ char *minibuffer;
+ unsigned int max_output_length = strlen(string);
+
+ for ( i=0; i<strlen(string); i++ ) {
+ if ( string[i] == ' ' )
+ s++;
+ if ( s == pos ) {
+ char ch='z';
+ unsigned int p=0;
+ char *result;
+ result = malloc(2+max_output_length);
+ if ( s == 0 )
+ i--;
+ while ((ch != '\0') && (p<max_output_length)) {
+ ch = string[i+p+1];
+ if ( ch == '\n' )
+ ch = '\0';
+ if ( ch == '\r' )
+ ch = '\0';
+ result[p]=ch;
+ p++;
+ }
+ result[p]='\0';
+
+ /* now copy the string into a buffer that's the right size */
+ minibuffer=malloc(strlen(result)+sizeof(char));
+ strcpy(minibuffer, result);
+ free(result);
+ return minibuffer;
+ }
+ }
+ /* whoopsie didn't find it */
+ minibuffer = malloc(1);
+ minibuffer[0]='\0';
+ return minibuffer; /* return something that can still be freed */
+
+}
+
+/* Convert a single hex digit char to an int */
+static unsigned int routines_unhex(char ch) {
+
+ switch (ch) {
+ case '0': return 0;
+ case '1': return 1;
+ case '2': return 2;
+ case '3': return 3;
+ case '4': return 4;
+ case '5': return 5;
+ case '6': return 6;
+ case '7': return 7;
+ case '8': return 8;
+ case '9': return 9;
+ case 'a': return 10;
+ case 'b': return 11;
+ case 'c': return 12;
+ case 'd': return 13;
+ case 'e': return 14;
+ case 'f': return 15;
+ case 'A': return 10;
+ case 'B': return 11;
+ case 'C': return 12;
+ case 'D': return 13;
+ case 'E': return 14;
+ case 'F': return 15; /* yes I know i could have done it differently... */
+ }
+
+ return 0; /* clearly this would be the caller's fault... */
+
+}
+
+char *routines_urldecode(const char *words) {
+
+ char *result;
+ char first_char=' ', second_char=' ';
+ unsigned int i, s=0, j=0;
+
+ result = malloc(strlen(words)+1);
+ for ( i=0; i<=strlen(words); i++ ) {
+
+ char ch;
+ ch=words[i];
+
+ if ( isxdigit(ch) && (s==2) ) {
+
+ unsigned int new_ch = 0;
+ unsigned int first_num, second_num;
+ j-=2;
+ second_char=ch;
+
+ /* now we take our two hex digits and decimalize... */
+ first_num = routines_unhex(first_char);
+ second_num = routines_unhex(second_char);
+ new_ch = (char) second_num + (16 * first_num);
+ if ( new_ch == '\0' )
+ new_ch = ' '; /* no way... */
+ if ( new_ch == '\10' )
+ new_ch = ' '; /* f--k off... */
+ if ( new_ch == '\13' )
+ new_ch = ' '; /* grrr */
+
+ ch=new_ch;
+ s=0;
+
+ }
+ if ( isxdigit(ch) && (s==1) ) {
+ s=2;
+ first_char=ch;
+ }
+ if (ch=='%') {
+ s=1;
+ }
+ result[j]=ch;
+ j++;
+
+ }
+ return result;
+
+}
+
+char *routines_urlencode(const char *words) {
+
+ char *result;
+ unsigned int i, p;
+
+ result = malloc((3*strlen(words))+1); /* this is the maximum possible size it could be */
+ p = 0;
+ for ( i=0; i<strlen(words); i++ ) {
+ char ch;
+ ch = words[i];
+ if ( ch == ' ' ) {
+ result[p]='%';
+ p++;
+ result[p]='2';
+ p++;
+ result[p]='0';
+ } else if ( ch == '<' ) {
+ result[p]='%';
+ p++;
+ result[p]='3';
+ p++;
+ result[p]='C';
+ } else if( ch == '>' ) {
+ result[p]='%';
+ p++;
+ result[p]='3';
+ p++;
+ result[p]='E';
+ } else if ( ch == '=' ) {
+ result[p]='%';
+ p++;
+ result[p]='3';
+ p++;
+ result[p]='D';
+ } else if ( ch == '@' ) {
+ result[p]='%';
+ p++;
+ result[p]='4';
+ p++;
+ result[p]='0';
+ } else if ( ch == '\"' ) {
+ result[p]='%';
+ p++;
+ result[p]='2';
+ p++;
+ result[p]='2';
+ } else if ( ch == '/' ) {
+ result[p]='%';
+ p++;
+ result[p]='2';
+ p++;
+ result[p]='F';
+ } else if ( ch == '+' ) {
+ result[p]='%';
+ p++;
+ result[p]='2';
+ p++;
+ result[p]='B';
+ } else {
+ result[p]=ch;
+ }
+ p++;
+ }
+ result[p]='\0';
+ return result;
+}
+
+/* extract hostname from hostname:port style string */
+char *routines_hostname(const char *input) {
+
+ unsigned int i, n=0;
+ char ch='z';
+ char *output;
+
+ /* First we must check that there is actually a colon present */
+ for ( i=0; i<strlen(input); i++ ) {
+ if ( input[i] == ':' ) {
+ n++;
+ }
+ }
+ /* if there's no colon, host is just the same as the input */
+ if ( n == 0 )
+ return strdup(input);
+
+ i = 0;
+ /* A few bytes get wasted here */
+ output = malloc(strlen(input));
+ while ( ch != '\0' ) {
+
+ ch = input[i];
+ if ( ch == ':' ) {
+ ch = '\0';
+ }
+ output[i] = ch;
+ i++;
+
+ }
+ return output;
+ /* You can free the output string when you've finished with it */
+
+}
+
+/* extract the port number from hostname:port */
+int routines_port(const char *input) {
+
+ char ch='z';
+ unsigned int i=0, n=0;
+
+ /* first we must check that there is actually a colon present */
+ for ( i=0; i<strlen(input); i++ ) {
+ if ( input[i] == ':' )
+ n++;
+ }
+ /* Bail out if no port specified */
+ if ( n == 0 ) {
+ return 0;
+ }
+
+ i=0;
+ while ( (ch != ':') && (i<=strlen(input)) ) {
+ ch = input[i];
+ i++;
+ }
+
+ return atoi(input+i);
+}
+
+static char routines_base64char(unsigned int sextet) {
+
+ switch (sextet) {
+
+ case 0 : return 'A';
+ case 1 : return 'B';
+ case 2 : return 'C';
+ case 3 : return 'D';
+ case 4 : return 'E';
+ case 5 : return 'F';
+ case 6 : return 'G';
+ case 7 : return 'H';
+ case 8 : return 'I';
+ case 9 : return 'J';
+ case 10 : return 'K';
+ case 11 : return 'L';
+ case 12 : return 'M';
+ case 13 : return 'N';
+ case 14 : return 'O';
+ case 15 : return 'P';
+ case 16 : return 'Q';
+ case 17 : return 'R';
+ case 18 : return 'S';
+ case 19 : return 'T';
+ case 20 : return 'U';
+ case 21 : return 'V';
+ case 22 : return 'W';
+ case 23 : return 'X';
+ case 24 : return 'Y';
+ case 25 : return 'Z';
+ case 26 : return 'a';
+ case 27 : return 'b';
+ case 28 : return 'c';
+ case 29 : return 'd';
+ case 30 : return 'e';
+ case 31 : return 'f';
+ case 32 : return 'g';
+ case 33 : return 'h';
+ case 34 : return 'i';
+ case 35 : return 'j';
+ case 36 : return 'k';
+ case 37 : return 'l';
+ case 38 : return 'm';
+ case 39 : return 'n';
+ case 40 : return 'o';
+ case 41 : return 'p';
+ case 42 : return 'q';
+ case 43 : return 'r';
+ case 44 : return 's';
+ case 45 : return 't';
+ case 46 : return 'u';
+ case 47 : return 'v';
+ case 48 : return 'w';
+ case 49 : return 'x';
+ case 50 : return 'y';
+ case 51 : return 'z';
+ case 52 : return '0';
+ case 53 : return '1';
+ case 54 : return '2';
+ case 55 : return '3';
+ case 56 : return '4';
+ case 57 : return '5';
+ case 58 : return '6';
+ case 59 : return '7';
+ case 60 : return '8';
+ case 61 : return '9';
+ case 62 : return '+';
+ case 63 : return '/';
+
+ }
+
+ return 'A';
+
+}
+
+static int routines_unbase64char(unsigned char sextet) {
+
+ switch (sextet) {
+
+ case 'A' : return 0;
+ case 'B' : return 1;
+ case 'C' : return 2;
+ case 'D' : return 3;
+ case 'E' : return 4;
+ case 'F' : return 5;
+ case 'G' : return 6;
+ case 'H' : return 7;
+ case 'I' : return 8;
+ case 'J' : return 9;
+ case 'K' : return 10;
+ case 'L' : return 11;
+ case 'M' : return 12;
+ case 'N' : return 13;
+ case 'O' : return 14;
+ case 'P' : return 15;
+ case 'Q' : return 16;
+ case 'R' : return 17;
+ case 'S' : return 18;
+ case 'T' : return 19;
+ case 'U' : return 20;
+ case 'V' : return 21;
+ case 'W' : return 22;
+ case 'X' : return 23;
+ case 'Y' : return 24;
+ case 'Z' : return 25;
+ case 'a' : return 26;
+ case 'b' : return 27;
+ case 'c' : return 28;
+ case 'd' : return 29;
+ case 'e' : return 30;
+ case 'f' : return 31;
+ case 'g' : return 32;
+ case 'h' : return 33;
+ case 'i' : return 34;
+ case 'j' : return 35;
+ case 'k' : return 36;
+ case 'l' : return 37;
+ case 'm' : return 38;
+ case 'n' : return 39;
+ case 'o' : return 40;
+ case 'p' : return 41;
+ case 'q' : return 42;
+ case 'r' : return 43;
+ case 's' : return 44;
+ case 't' : return 45;
+ case 'u' : return 46;
+ case 'v' : return 47;
+ case 'w' : return 48;
+ case 'x' : return 49;
+ case 'y' : return 50;
+ case 'z' : return 51;
+ case '0' : return 52;
+ case '1' : return 53;
+ case '2' : return 54;
+ case '3' : return 55;
+ case '4' : return 56;
+ case '5' : return 57;
+ case '6' : return 58;
+ case '7' : return 59;
+ case '8' : return 60;
+ case '9' : return 61;
+ case '+' : return 62;
+ case '/' : return 63;
+
+ }
+
+ return 0;
+
+}
+
+char *routines_base64givenlength(char *base64_input, ssize_t length) {
+
+ ssize_t i;
+ char *base64_output;
+ unsigned int sextet;
+ size_t j = 0;
+
+ /* One extra byte for \0 terminator, another for rounding error. */
+ base64_output = malloc((4*length)/3 + 4);
+
+ for ( i=0; i<=length-3; i+=3 ) {
+
+ /* First sextet */
+ sextet = (base64_input[i] & 0xfc) >> 2;
+ base64_output[j++] = routines_base64char(sextet);
+
+ /* Second sextet */
+ sextet = ((base64_input[i] & 0x3) << 4) + ((base64_input[i+1] & 0xf0) >> 4);
+ base64_output[j++] = routines_base64char(sextet);
+
+ /* Third sextet */
+ sextet = ((base64_input[i+1] & 0x0f) << 2) + ((base64_input[i+2] & 0xc0) >> 6);
+ base64_output[j++] = routines_base64char(sextet);
+
+ /* Fourth sextet */
+ sextet = (base64_input[i+2] & 0x3f);
+ base64_output[j++] = routines_base64char(sextet);
+
+ }
+
+ switch ((length) % 3) {
+
+ case 1 : {
+
+ /* One byte left over */
+ i = length-1;
+
+ sextet = (base64_input[i] & 0xfc) >> 2;
+ base64_output[j++] = routines_base64char(sextet);
+
+ sextet = (base64_input[i] & 0x03) << 4;
+ base64_output[j++] = routines_base64char(sextet);
+
+ base64_output[j++] = '=';
+ base64_output[j++] = '=';
+
+ break;
+
+ }
+
+ case 2 : {
+
+ /* Two bytes left over */
+ i = length-2;
+
+ sextet = (base64_input[i] & 0xfc) >> 2;
+ base64_output[j++] = routines_base64char(sextet);
+
+ sextet = ((base64_input[i] & 0x3) << 4) + ((base64_input[i+1] & 0xf0) >> 4);
+ base64_output[j++] = routines_base64char(sextet);
+
+ sextet = (base64_input[i+1] & 0x0f) << 2;
+ base64_output[j++] = routines_base64char(sextet);
+
+ base64_output[j++] = '=';
+
+ break;
+
+ }
+
+ }
+
+ base64_output[j++] = '\0';
+
+ return base64_output;
+
+}
+
+/* Base64-encode something. Needs to have a NULL terminator. */
+char *routines_base64(char *base64_input) {
+ return routines_base64givenlength(base64_input, strlen(base64_input)+1);
+}
+
+/* Decode Base64 string (with terminator) and put pointer to result at "output". Return length of result. */
+size_t routines_base64decode(const char *base64_input, char **output) {
+
+ size_t len = 0;
+ size_t ptr = 0;
+ char *op = malloc(strlen(base64_input));
+
+ assert(strlen(base64_input) % 4 == 0 );
+
+ while ( ptr < strlen(base64_input) ) {
+
+ op[len] = (routines_unbase64char(base64_input[ptr]) << 2) + ((routines_unbase64char(base64_input[ptr+1]) & 48) >> 4);
+ op[len+1] = ((routines_unbase64char(base64_input[ptr+1]) & 15) << 4) + ((routines_unbase64char(base64_input[ptr+2]) & 60) >> 2);
+ op[len+2] = ((routines_unbase64char(base64_input[ptr+2]) & 3) << 6) + routines_unbase64char(base64_input[ptr+3]);
+
+ len+=3;
+ ptr+=4;
+
+ }
+
+ if ( base64_input[ptr-1] == '=' ) {
+ len--;
+ }
+ if ( base64_input[ptr-2] == '=' ) {
+ len--;
+ }
+
+ *output = op;
+ return len;
+
+}
+
+static char *routines_randomhexbyte() {
+
+ int j;
+ char *response = malloc(3);
+
+ /* Taken from the "rand()" man page. */
+ j = 1 +(int)(256.0*rand()/(RAND_MAX+1.0));
+ sprintf(response, "%2hhx", j);
+
+ return response;
+
+}
+
+/* Generate a GUID */
+char *routines_guid() {
+
+ int i, k;
+ char *guid;
+
+ guid = malloc(39);
+ assert(guid != NULL);
+ strcpy(guid, "{");
+
+ for ( i=0; i<4; i++ ) {
+
+ char *byte;
+ byte = routines_randomhexbyte();
+ strncat(guid, byte, 2);
+ free(byte);
+
+ }
+
+ for ( k=0; k<3; k++ ) {
+
+ strcat(guid, "-");
+ for ( i=0; i<2; i++ ) {
+
+ char *byte;
+ byte = routines_randomhexbyte();
+ strncat(guid, byte, 2);
+ free(byte);
+
+ }
+
+ }
+
+ /* To be a valid GUID, the version and variant fields need to be set appropriately. */
+ guid[15] = '4';
+ guid[20] = 'a';
+
+ strcat(guid, "-");
+ for ( i=0; i<6; i++ ) {
+
+ char *byte;
+ byte = routines_randomhexbyte();
+ strncat(guid, byte, 2);
+ free(byte);
+
+ }
+ strcat(guid, "}");
+
+ /* Fix up spaces. */
+ for ( i=0; i<(strlen(guid)-1); i++ ) {
+ if ( guid[i] == ' ' ) {
+ guid[i] = '0';
+ }
+ }
+
+ return guid;
+
+}
+
+/* Replace triangular brackets with something Pango can handle. */
+char *routines_killtriangles(const char *input) {
+
+ char *output;
+ size_t i;
+ size_t j = 0;
+
+ output = malloc(2*strlen(input)+1);
+ for ( i=0; i<=strlen(input); i++ ) {
+ if ( input[i] == '<' ) {
+ output[j] = '&'; j++;
+ output[j] = 'l'; j++;
+ output[j] = 't'; j++;
+ output[j] = ';'; j++;
+ } else if ( input[i] == '>' ) {
+ output[j] = '&'; j++;
+ output[j] = 'g'; j++;
+ output[j] = 't'; j++;
+ output[j] = ';'; j++;
+ } else {
+ output[j] = input[i]; j++;
+ }
+ }
+
+ return output;
+
+}
+
+/* Replace triangular brackets and ampersands with something Pango can handle. */
+char *routines_killtriangles_and_ampersands(const char *input) {
+
+ char *output;
+ size_t i;
+ size_t j = 0;
+
+ output = malloc(2*strlen(input)+1);
+ for ( i=0; i<=strlen(input); i++ ) {
+ if ( input[i] == '<' ) {
+ output[j] = '&'; j++;
+ output[j] = 'l'; j++;
+ output[j] = 't'; j++;
+ output[j] = ';'; j++;
+ } else if ( input[i] == '>' ) {
+ output[j] = '&'; j++;
+ output[j] = 'g'; j++;
+ output[j] = 't'; j++;
+ output[j] = ';'; j++;
+ } else if ( input[i] == '&' ) {
+ output[j] = '&'; j++;
+ output[j] = 'a'; j++;
+ output[j] = 'm'; j++;
+ output[j] = 'p'; j++;
+ output[j] = ';'; j++;
+ } else {
+ output[j] = input[i]; j++;
+ }
+ }
+
+ return output;
+
+}
+
+char *routines_flipcolour(const char *colour) {
+
+ char *flipped = malloc(7);
+ char scratch;
+ strncpy(flipped, colour, 6);
+ flipped[6] = '\0';
+
+ scratch = flipped[0];
+ flipped[0] = flipped[4];
+ flipped[4] = scratch;
+
+ scratch = flipped[1];
+ flipped[1] = flipped[5];
+ flipped[5] = scratch;
+
+ return flipped;
+
+}
+
+char *routines_gdk_to_hashrgb(const GdkColor *colour) {
+
+ char *string;
+
+ if ( colour == NULL ) {
+ return NULL;
+ }
+
+ string = malloc(8);
+ /* RGB order */
+ snprintf(string, 8, "#%02hhx%02hhx%02hhx", colour->red >> 8, colour->green >> 8, colour->blue >> 8);
+ string[7] = '\0';
+
+ return string;
+
+}
diff --git a/src/routines.h b/src/routines.h
new file mode 100644
index 0000000..0d3326d
--- /dev/null
+++ b/src/routines.h
@@ -0,0 +1,53 @@
+/*
+ * routines.h
+ *
+ * Random Useful Routines and Function
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef ROUTINES_H
+#define ROUTINES_H
+
+#include <gdk/gdk.h>
+
+extern char *routines_lindex(char *string, unsigned int pos);
+extern char *routines_lindexend(char *string, unsigned int pos);
+
+extern char *routines_urldecode(const char *words);
+extern char *routines_urlencode(const char *words);
+
+extern char *routines_hostname(const char *input);
+extern int routines_port(const char *input);
+
+extern char *routines_base64(char *base64_input);
+extern char *routines_base64givenlength(char *base64_input, ssize_t length);
+extern size_t routines_base64decode(const char *base64_input, char **output);
+
+extern char *routines_guid(void);
+
+extern char *routines_glob(const char *filename);
+
+extern char *routines_killtriangles(const char *input);
+extern char *routines_killtriangles_and_ampersands(const char *input);
+
+extern char *routines_flipcolour(const char *colour);
+extern char *routines_gdk_to_hashrgb(const GdkColor *colour);
+
+#endif /* ROUTINES_H */
diff --git a/src/sbprotocol.c b/src/sbprotocol.c
new file mode 100644
index 0000000..1d58ac7
--- /dev/null
+++ b/src/sbprotocol.c
@@ -0,0 +1,1271 @@
+/*
+ * sbprotocol.c
+ *
+ * SB protocol handling
+ *
+ * (c) 2002-2006 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+
+#include "sbsessions.h"
+#include "msnprotocol.h"
+#include "error.h"
+#include "debug.h"
+#include "options.h"
+#include "routines.h"
+#include "messagewindow.h"
+#include "mime.h"
+#include "contactlist.h"
+#include "main.h"
+#include "msnp2p.h"
+#include "msninvite.h"
+#include "ink.h"
+#include "sbprotocol.h"
+#include "msngenerics.h"
+
+/* Maximum allowed message size, and chunk size to split large messages into.
+ * MAX_MSG_SIZE needs to be sufficiently bigger than MULTIPACKET_SIZE to
+ * fit the headers in, otherwise you get an infinite loop. */
+#define MULTIPACKET_SIZE 1024
+#define MAX_MSG_SIZE 1280
+
+/* Define this if you want to see what gets written to the socket. */
+#define SBSEND_DEBUG 1
+/* Define if you want to see SB write buffer operations. */
+#undef SBWRITE_DEBUG
+
+/* Some prototypes */
+static void sbprotocol_sendmsg(SbSession *session, char *contenttype, char *extra_headers, char *body, ssize_t length);
+
+/* Called when a socket gets disconnected. Session record vanishes. */
+static void sbprotocol_disconnected(SbSession *session) {
+ debug_print("SB %8p: Disconnected - deleting.\n", session);
+ sbsessions_destroy(session);
+}
+
+/* Write a chunk of the outgoing data queue to an SB socket. */
+static void sbprotocol_writeable(SbSession *session) {
+
+ ssize_t wlen;
+ unsigned int new_wbufsize;
+#ifdef SBSEND_DEBUG
+ char *debug_string;
+ unsigned int i;
+#endif /* SBSEND_DEBUG */
+
+ wlen = write(session->socket, session->wbuffer, session->wbufsize);
+
+#ifdef SBSEND_DEBUG
+ debug_string = malloc(session->wbufsize+1);
+ memcpy(debug_string, session->wbuffer, session->wbufsize);
+ debug_string[wlen] = '\0';
+ for ( i=0; i<session->wbufsize; i++ ) {
+ if ( debug_string[i] == '\r' ) {
+ debug_string[i] = 'r';
+ }
+ if ( debug_string[i] == '\n' ) {
+ debug_string[i] = 'n';
+ }
+ }
+ debug_print("SB %8p: Send:'%s'\n", session, debug_string);
+ free(debug_string);
+#endif /* SBSEND_DEBUG */
+
+ if ( wlen > 0 ) {
+
+#ifdef SBWRITE_DEBUG
+ debug_print("SB %8p: Wrote %i bytes to socket.\n", session, wlen);
+#endif /* SBWRITE_DEBUG */
+
+ /* wlen holds the number of bytes written. Sort the buffer out accordingly... */
+ memmove(session->wbuffer, session->wbuffer + wlen, session->wbufsize - wlen);
+ new_wbufsize = session->wbufsize - wlen;
+
+ session->wbuffer = realloc(session->wbuffer, new_wbufsize);
+ if ( new_wbufsize == 0 ) {
+
+#ifdef SBWRITE_DEBUG
+ debug_print("SB %8p: SB write buffer empty: destroying.\n", session);
+#endif /* SBWRITE_DEBUG */
+ gdk_input_remove(session->wcallback);
+ session->wcallback = 0;
+ session->wbuffer = NULL;
+
+ }
+ session->wbufsize = new_wbufsize;
+ session->woffset -= wlen;
+
+ } else {
+
+ if ( wlen == -1 ) {
+
+ /* Write error! :( */
+ if ( errno != EAGAIN ) { /* EAGAIN should never happen here */
+
+ /* Something bad happened */
+ int closeval;
+ closeval = close(session->socket);
+ if ( closeval != 0 ) {
+ /* Grrr.. now it won't close - not much we can do. */
+ debug_print("SB %8p: Couldn't close socket after write error.\n", session);
+ }
+ sbprotocol_disconnected(session);
+ return;
+
+ }
+
+ }
+
+ }
+
+}
+
+/* Queue data for sending to the server, adding a TrID - returns the TrID used for this transaction. */
+static int sbprotocol_sendtr_internal(SbSession *session, const char *instr, const char *args, size_t arglen, int newline) {
+
+ char *trid_string;
+ size_t len;
+
+ len = strlen(instr);
+ trid_string = malloc(8);
+ assert(session->trid < 999999); /* Sanity check */
+ sprintf(trid_string, "%i", session->trid);
+ len += 1; /* Space before the TrId */
+ len += strlen(trid_string);
+ if ( arglen > 0 ) {
+ len += arglen;
+ len += 1; /* Space after the TrID */
+ }
+ if ( newline ) {
+ len += 2;
+ }
+
+ if ( session->wbufsize == 0 ) {
+
+ /* No buffer space currently exists. Create it. */
+#ifdef SBWRITE_DEBUG
+ debug_print("SB %8p: Creating SB write buffer: %i bytes.\n", session, len);
+#endif /* SBWRITE_DEBUG */
+ assert(session->wbuffer == NULL);
+ session->wbuffer = malloc(len);
+ assert(session->wbuffer != NULL);
+ session->wbufsize = len;
+ session->woffset = 0;
+
+ }
+ if ( (session->wbufsize - session->woffset) < len ) {
+
+ /* Write buffer isn't big enough. Make it bigger. */
+#ifdef SBWRITE_DEBUG
+ debug_print("SB %8p: Extending SB write buffer. Old size=%i (offset %i), Need %i, New size=%i\n", session, session->wbufsize, session->woffset, len, len+session->woffset);
+#endif /* SBWRITE_DEBUG */
+ session->wbuffer = realloc(session->wbuffer, len + session->woffset);
+ assert(session->wbuffer != NULL);
+ session->wbufsize = len + session->woffset;
+ assert(session->wbufsize < 1024*1024); /* Stop the buffer from getting insane */
+
+ }
+
+ /* Do the write (to memory). Deliberately verbose... */
+ memcpy(session->wbuffer + session->woffset, instr, strlen(instr));
+ session->woffset += strlen(instr);
+
+ *(char *)(session->wbuffer + session->woffset) = ' ';
+ session->woffset += 1;
+
+ memcpy(session->wbuffer + session->woffset, trid_string, strlen(trid_string));
+ session->woffset += strlen(trid_string);
+ free(trid_string);
+
+ if ( arglen > 0 ) {
+
+ *(session->wbuffer + session->woffset) = ' ';
+ session->woffset += 1;
+ memcpy(session->wbuffer + session->woffset, args, arglen);
+ session->woffset += arglen;
+
+ }
+
+ if ( newline ) {
+
+ *(session->wbuffer + session->woffset) = '\r';
+ session->woffset += 1;
+ *(session->wbuffer + session->woffset) = '\n';
+ session->woffset += 1;
+
+ }
+ /* Note the lack of a \0 terminator. It's done using records of the buffer data lengths. */
+
+ if ( session->wcallback == 0 ) {
+ session->wcallback = gdk_input_add(session->socket, GDK_INPUT_WRITE, (GdkInputFunction)sbprotocol_writeable, session);
+ }
+
+ session->trid++;
+ return session->trid-1;
+
+}
+
+/* Send a command to the switchboard, adding a TrID and a newline */
+static int sbprotocol_sendtr(SbSession *session, const char *instr, const char *args) {
+ return sbprotocol_sendtr_internal(session, instr, args, strlen(args), 1);
+}
+
+/* Send a command to the switchboard, adding a TrID but no newline. */
+int sbprotocol_sendtr_nonewline(SbSession *session, const char *instr, const char *args, ssize_t length) {
+ return sbprotocol_sendtr_internal(session, instr, args, length, 0);
+}
+
+void sbprotocol_leavesession(SbSession *session) {
+ sbprotocol_sendtr(session, "OUT", "");
+}
+
+/* Split a large message up and throw it back at sbprotocol_sendmsg */
+static int sbprotocol_sendmultipacket(SbSession *session, char *contenttype, char *extra_headers, char *body, ssize_t length) {
+
+ char *messageid = routines_guid();
+ int parts = (length / MULTIPACKET_SIZE) + 1;
+ int i;
+ char *new_extra_headers;
+ char *packet = malloc(MULTIPACKET_SIZE);
+
+ debug_print("SB %8p: Sending multipacket message (%i parts)\n", session, parts);
+ if ( parts > 9999 ) {
+ debug_print("SB %8p: Silly number of packets - aborting.\n", session);
+ return 0;
+ }
+
+ /* Message-ID: Chunks: Chunk: */
+ new_extra_headers = malloc(strlen(extra_headers) +12+40 +7+4 +6+4 +1);
+
+ for ( i=0; i<parts; i++ ) {
+
+ ssize_t size;
+
+ strcpy(new_extra_headers, extra_headers);
+ strcat(new_extra_headers, "\r\nMessage-ID: ");
+ strcat(new_extra_headers, messageid);
+
+ if ( i == 0 ) {
+
+ /* First packet. Modify header to include number of chunks... */
+
+ char *chunks;
+
+ strcat(new_extra_headers, "\r\nChunks: ");
+ chunks = malloc(5);
+ sprintf(chunks, "%i", parts);
+ strcat(new_extra_headers, chunks);
+ free(chunks);
+
+ } else {
+
+ /* Not first packet: include chunk ID */
+
+ char *chunk;
+
+ strcat(new_extra_headers, "\r\nChunk: ");
+ chunk = malloc(5);
+ sprintf(chunk, "%i", i);
+ strcat(new_extra_headers, chunk);
+ free(chunk);
+
+ }
+
+ size = MULTIPACKET_SIZE;
+ if ( i == parts-1 ) {
+ /* Last message */
+ size = length % MULTIPACKET_SIZE;
+ }
+ memcpy(packet, body+(MULTIPACKET_SIZE*i), size);
+
+ debug_print("SB %8p: Sending packet %i of %i (bytes %i-%i of %i)\n", session, i+1, parts, MULTIPACKET_SIZE*i, size+(MULTIPACKET_SIZE*i), length);
+ sbprotocol_sendmsg(session, contenttype, new_extra_headers, packet, size);
+
+ }
+
+ return 1;
+
+}
+
+static void sbprotocol_flushcache(SbSession *session) {
+
+ CachedMsg *cached = session->cached;
+ CachedMsg *next;
+
+ while ( cached != NULL ) {
+
+ debug_print("Sending cached message %8p.\n", cached);
+ sbprotocol_sendtr_nonewline(session, "MSG", cached->message, cached->length);
+ free(cached->message);
+ next = cached->next;
+ free(cached);
+ session->cached = next;
+ cached = next;
+
+ }
+
+}
+
+static void sbprotocol_sendmsg(SbSession *session, char *contenttype, char *extra_headers, char *body, ssize_t mlength) {
+
+ char *serversend;
+ char *length_text;
+ size_t length;
+
+ length = mlength;
+ if ( extra_headers != NULL ) {
+ length += (strlen(extra_headers) + 2);
+ }
+ length += (35 + strlen(contenttype) + 2);
+
+ /* Check if this needs to be multipacketed or not... */
+ if ( length > MAX_MSG_SIZE ) {
+ sbprotocol_sendmultipacket(session, contenttype, extra_headers, body, mlength);
+ return;
+ }
+
+ serversend = malloc(length+15);
+
+ strcpy(serversend, "N ");
+ length_text = malloc(16);
+ sprintf(length_text, "%i", length);
+ strncat(serversend, length_text, 15);
+
+ strcat(serversend, "\r\nMIME-Version: 1.0\r\nContent-Type: ");
+ strcat(serversend, contenttype);
+ strcat(serversend, "\r\n");
+ if ( extra_headers != NULL ) {
+ strcat(serversend, extra_headers);
+ strcat(serversend, "\r\n");
+ }
+ strcat(serversend, "\r\n");
+ memcpy(serversend+strlen(serversend), body, mlength);
+
+ if ( session->ready ) {
+ sbprotocol_sendtr_nonewline(session, "MSG", serversend, length+strlen(length_text)+4);
+ } else {
+
+ CachedMsg *cached = malloc(sizeof(CachedMsg));
+
+ cached->message = malloc(length+strlen(length_text)+5);
+ memcpy(cached->message, serversend, length+strlen(length_text)+5);
+
+ cached->message[length+strlen(length_text)+4] = '\0'; /* NULL-terminate! */
+
+ cached->length = length+strlen(length_text)+4;
+
+ if ( session->cached == NULL ) {
+ session->cached = cached;
+ } else {
+ CachedMsg *find = session->cached;
+ while ( find->next != NULL ) {
+ find = find->next;
+ }
+ find->next = cached;
+ }
+
+ cached->next = NULL;
+
+ debug_print("SB %8p: Cached message for sending later (%8p).\n", session, cached);
+
+ }
+
+ free(length_text);
+ free(serversend);
+
+}
+
+void sbprotocol_sendcaps(SbSession *session) {
+
+ char *caps_body;
+
+ caps_body = malloc(64 + strlen(tuxmessenger_versionstring()));
+ strcpy(caps_body, "Client-Name: ");
+ strcat(caps_body, tuxmessenger_versionstring());
+ strcat(caps_body, "\r\nChat-Logging: N"); /* Logging not implemented yet. */
+
+ sbprotocol_sendmsg(session, "text/x-clientcaps", NULL, caps_body, strlen(caps_body));
+ free(caps_body);
+
+}
+
+void sbprotocol_invite(SbSession *session, const char *username) {
+
+ sbprotocol_sendtr(session, "CAL", username);
+
+}
+
+static void sbprotocol_parseline(SbSession *session, char *line) {
+
+ char *token;
+
+ debug_print("SB %8p: Recv '%s'\n", session, line);
+ assert(session != NULL);
+
+ token = routines_lindex(line, 0);
+
+ if ( strcmp(token, "USR") == 0 ) {
+
+ char *status;
+
+ status = routines_lindex(line, 2);
+
+ if ( strcmp(status, "OK") == 0 ) {
+
+ if ( session->num_users != 1 ) {
+ /* Not quite inconceivable, but would only happen in a *really* screwed-up case. */
+ debug_print("SB %8p: %i users on session record during negotiation!\n", session, session->num_users);
+ }
+
+ assert(session->users != NULL);
+ assert(session->users->username != NULL);
+ debug_print("SB %8p: Inviting %s\n", session, session->users->username);
+ sbprotocol_invite(session, session->users->username);
+ if ( session->threeway_intent != NULL ) {
+ debug_print("SB %8p: Also inviting %s\n", session, session->threeway_intent);
+ sbprotocol_invite(session, session->threeway_intent);
+ }
+
+ } else {
+
+ debug_print("SB %8p: Negotiation failed\n", session);
+
+ }
+
+ free(status);
+
+ }
+
+ if ( strcmp(token, "JOI") == 0 ) {
+
+ char *username;
+ char *friendlyname;
+
+ session->ready = TRUE;
+ sbprotocol_sendcaps(session);
+ sbprotocol_flushcache(session);
+
+ username = routines_lindex(line, 1);
+ friendlyname = routines_lindex(line, 2);
+ sbsessions_joined(session, username, friendlyname);
+ free(username);
+ free(friendlyname);
+
+ }
+
+ if ( strcmp(token, "IRO") == 0 ) {
+
+ char *username;
+ char *friendlyname;
+
+ username = routines_lindex(line, 4);
+ friendlyname = routines_lindex(line, 5);
+ session->ready = TRUE;
+ sbsessions_joined(session, username, friendlyname);
+ free(username);
+ free(friendlyname);
+
+ }
+
+ if ( strcmp(token, "MSG") == 0 ) {
+
+ char *length;
+
+ length = routines_lindex(line, 3);
+ session->expect_length = atoi(length);
+ free(length);
+
+ session->msg_source = routines_lindex(line, 1);
+
+ assert(session->expect_length > 0);
+ session->conmode = SBCONMODE_MSG;
+
+ }
+
+ if ( strcmp(token, "BYE") == 0 ) {
+
+ char *username;
+ username = routines_lindex(line, 1);
+ sbsessions_left(session, username);
+ free(username);
+
+ }
+
+ if ( strcmp(token, "ANS") == 0 ) {
+
+ char *reply;
+
+ reply = routines_lindex(line, 2);
+ if ( strcmp(reply, "OK") == 0 ) {
+ session->ready = TRUE; /* This is probably already the case. */
+ sbprotocol_sendcaps(session);
+ } else {
+ debug_print("SB %8p: ANS reply not OK!\n", session);
+ }
+ free(reply);
+
+ }
+
+ if ( strcmp(token, "NAK") == 0 ) {
+
+ /* Difficult to analyse this in detail... */
+ if ( session->messagewindow != NULL ) {
+ if ( !messagewindow_get_last_was_nak(session->messagewindow) ) {
+ messagewindow_addtext_system(session->messagewindow, "Connection problems at the other end?");
+ messagewindow_set_last_was_nak(session->messagewindow, TRUE);
+ }
+ }
+
+ }
+
+ if ( strcmp(token, "217") == 0 ) {
+
+ /* Hmmm. */
+ if ( !session->ready ) {
+ debug_print("SB %8p: 217 before READY. Leaving...\n", session);
+ sbprotocol_leavesession(session);
+ }
+
+ }
+
+ free(token);
+
+}
+
+/* Deal with an incoming user IM */
+static void sbprotocol_parseim(SbSession *session, const char *msg, size_t length) {
+
+ char *says_text;
+ const char *friendlyname_coded;
+ char *friendlyname;
+ SbUser *user;
+ char *format;
+ char *colour = NULL;
+ char *flipcolour;
+ char *hashcolour = NULL;
+ char *padcolour;
+ size_t i;
+ char *font = NULL;
+
+ if ( session->messagewindow == NULL ) {
+ messagewindow_mitigate(session);
+ }
+
+ friendlyname_coded = contactlist_friendlyname(session->msg_source);
+ friendlyname = routines_urldecode(friendlyname_coded);
+ says_text = malloc(strlen(friendlyname) + 7);
+ strcpy(says_text, friendlyname);
+ free(friendlyname);
+ strcat(says_text, " says:");
+
+ if ( session->messagewindow->ofontoverride ) {
+ hashcolour = strdup(session->messagewindow->ocolour_string);
+ font = session->messagewindow->ofont;
+ } else {
+ format = mime_getfield(msg, "X-MMS-IM-Format");
+ debug_print("SB %8p: Got format: '%s'\n", session, format);
+ if ( strlen(format) > 0 ) {
+
+ colour = strdup("000000");
+ for ( i=0; i<strlen(format); i++ ) {
+
+ if ( (format[i]=='C') && (format[i+1]=='O') && (format[i+2]=='=') ) {
+
+ char c;
+ size_t j = 0;
+
+ free(colour);
+ colour = malloc(7);
+ c = format[i+3+j];
+ while ( (j<7) && (c != ';') && (c != ' ') && (i+3+j < strlen(format)-1) ) {
+ colour[j] = c;
+ j++;
+ c = format[i+3+j];
+ }
+ colour[j] = '\0';
+ break;
+
+ }
+
+ }
+ free(format);
+ assert(colour != NULL);
+ debug_print("SB %8p: Got colour '%s'\n", session, colour);
+
+ if ( strlen(colour) < 6 ) {
+
+ size_t len = strlen(colour);
+ padcolour = strdup("000000");
+ padcolour[6-len] = '\0';
+ strcat(padcolour, colour);
+ free(colour);
+ colour = padcolour;
+ debug_print("SB %8p: Padded: '%s'\n", session, colour);
+
+ }
+
+ flipcolour = routines_flipcolour(colour);
+ hashcolour = malloc(8);
+ strcpy(hashcolour, "#");
+ strncat(hashcolour, flipcolour, 6);
+ hashcolour[7] = '\0';
+ free(flipcolour);
+ free(colour);
+ debug_print("SB %8p: Flipped and hashed: '%s'\n", session, hashcolour);
+ }
+
+ }
+
+ messagewindow_addtext_system(session->messagewindow, says_text);
+ free(says_text);
+ messagewindow_addtext_user_nonewline(session->messagewindow, "\n", 1, NULL, NULL);
+ messagewindow_addtext_user_nonewline(session->messagewindow, mime_getbody(msg), length-mime_headerlength(msg), hashcolour, font);
+ free(hashcolour);
+
+ user = sbsessions_find_username(session, session->msg_source);
+ if ( user == NULL ) {
+ debug_print("SB %8p: IM source user not found on switchboard!\n", session);
+ return;
+ }
+ messagewindow_stoptypingbyusername(session->messagewindow, session->msg_source);
+
+}
+
+/* Handle an "MSMsgsControl" */
+static void sbprotocol_parsemsgcontrol(SbSession *session, const char *msg) {
+
+ char *typinguser;
+
+ /* Check for a typing user.*/
+ typinguser = mime_getfield(msg, "TypingUser");
+ if ( strlen(typinguser) > 0 ) {
+
+ if ( session->messagewindow == NULL ) {
+ messagewindow_mitigate(session);
+ }
+ messagewindow_starttyping(session->messagewindow, typinguser);
+
+ } else {
+ debug_print("SB %8p: Unrecognised TypingUser control!\n", session);
+ }
+
+ free(typinguser);
+
+}
+
+static void sbprotocol_handleclientcaps(SbSession *session, const char *msg) {
+
+ debug_print("SB %8p: clientcaps: '%s'\n", session, msg);
+
+}
+
+static void sbprotocol_handledatacast(SbSession *session, const char *username, const char *data) {
+
+ int id;
+ char *id_string = mime_getfield(data, "ID");
+
+ if ( id_string == NULL ) {
+ debug_print("SB %8p: Got datacast, couldn't find ID.\n", session);
+ return;
+ }
+
+ id = atoi(id_string);
+ free(id_string);
+
+ debug_print("SB %8p: Got datacast: ID %i.\n", session, id);
+
+ if ( id == 1 ) {
+
+ const char *friendlyname;
+ char *friendlyname_decoded;
+ char *text;
+
+ friendlyname = contactlist_friendlyname(username);
+ friendlyname_decoded = routines_urldecode(friendlyname);
+
+ text = malloc(strlen(friendlyname_decoded)+9);
+ strcpy(text, friendlyname_decoded);
+ strcat(text, " nudges.");
+ free(friendlyname_decoded);
+
+ if ( session->messagewindow == NULL ) {
+ messagewindow_mitigate(session);
+ }
+ messagewindow_addtext_system(session->messagewindow, text);
+ free(text);
+
+ }
+
+}
+
+
+static void sbprotocol_handleink_isf(SbSession *session, const char *username, const char *ink) {
+
+ debug_print("SB %8p: Got ISF: '%s'\n", session, ink);
+
+}
+
+static MultiPacketMsg *sbprotocol_findmultipacket(SbSession *session, const char *message_id) {
+
+ MultiPacketMsg *msg = session->multipackets;
+
+ while ( msg != NULL ) {
+ if ( strcmp(msg->message_id, message_id) == 0 ) {
+ return msg;
+ }
+ msg = msg->next;
+ }
+
+ return NULL;
+
+}
+
+/* Currently, this assumes the packets will arrive in the correct order. */
+static int sbprotocol_checkmultipacket(SbSession *session, const char *msgdata, ssize_t msgdatalength) {
+
+ char *chunks;
+ char *chunk;
+
+ chunks = mime_getfield(msgdata, "Chunks");
+ chunk = mime_getfield(msgdata, "Chunk");
+ if ( (strlen(chunks)>0) || (strlen(chunk)>0) ) {
+
+ /* This is part of a multipacketed message. */
+ MultiPacketMsg *msg;
+ char *messageid;
+
+ debug_print("SB %8p: Got a part of a multipacketed message.\n", session);
+
+ messageid = mime_getfield(msgdata, "Message-ID");
+ if ( strlen(messageid) == 0 ) {
+ debug_print("SB %8p: No Message-ID!\n", session);
+ free(messageid);
+ free(chunks);
+ free(chunk);
+ return 0;
+ }
+
+ if ( (atoi(chunk) == 0) && (atoi(chunks) == 0) ) {
+ debug_print("SB %8p: Couldn't find either a sensible 'Chunk' or 'Chunks' value!\n", session);
+ free(messageid);
+ free(chunks);
+ free(chunk);
+ return 0;
+ }
+
+ msg = sbprotocol_findmultipacket(session, messageid);
+ if ( msg == NULL ) {
+
+ /* No previous record of this series, so it had *better* contain the number of chunks to expect... */
+ if ( atoi(chunks) == 0 ) {
+ debug_print("SB %8p: Couldn't find the length of the multipacket series...\n", session);
+ free(messageid);
+ free(chunks);
+ free(chunk);
+ return 0;
+ }
+
+ msg = malloc(sizeof(MultiPacketMsg));
+ msg->chunks_expected = atoi(chunks);
+ msg->chunks_received = 1;
+ msg->data = mime_removeheader(msgdata, msgdatalength, "Chunks");
+ msg->datalength = msgdatalength - strlen(chunks) - strlen("Chunks: rn");
+ msg->message_id = messageid;
+
+ /* Link it into the list for this session. */
+ msg->next = session->multipackets;
+ msg->previous = NULL;
+ if ( session->multipackets != NULL ) {
+ session->multipackets->previous = msg;
+ }
+ session->multipackets = msg;
+
+ } else {
+
+ size_t body_size;
+ const char *body;
+
+ /* Known series, so this had *better* contain a chunk ID... */
+ if ( atoi(chunk) == 0 ) {
+ debug_print("SB %8p: Couldn't find the multipacket chunk ID...\n", session);
+ return 0;
+ }
+
+ body = mime_getbody(msgdata);
+ body_size = msgdatalength - mime_headerlength(msgdata);
+
+ msg->data = realloc(msg->data, body_size+(msg->datalength));
+ memcpy((msg->data)+msg->datalength, body, body_size);
+ msg->datalength += body_size;
+
+ msg->chunks_received++;
+ if ( msg->chunks_received == msg->chunks_expected ) {
+
+ debug_print("SB %8p: Got all parts of a multipacket message...\n", session);
+
+ /* Extend buffer by one byte and fix \0 terminator. */
+ msg->data = realloc(msg->data, (msg->datalength)+1);
+ msg->data[msg->datalength] = '\0';
+
+ sbprotocol_parsemsg(session, msg->data, msg->datalength);
+
+ /* Remove from list */
+ if ( msg->previous != NULL ) {
+ msg->previous->next = msg->next;
+ } else {
+ session->multipackets = msg->next;
+ }
+ if ( msg->next != NULL ) {
+ msg->next->previous = msg->previous;
+ }
+
+ free(msg->data);
+ free(msg);
+
+ return 1; /* It's already been handled. */
+
+ }
+
+ }
+
+ } else {
+
+ free(chunks);
+ free(chunk);
+ return 0;
+
+ }
+
+ free(chunks);
+ free(chunk);
+
+ return 1; /* Cause sbprotocol_parsemsg to ignore this one for now. */
+
+}
+
+/* Decide what to do with a received message.
+ Also called by msnp2p.c to throw us a MIME message sent via MSNP2P (Yuk!). */
+void sbprotocol_parsemsg(SbSession *session, const char *msg, ssize_t msglength) {
+
+ char *content_type;
+
+ debug_print("SB %8p: MSG (%i bytes): '%s'\n", session, msglength, msg);
+
+ if ( sbprotocol_checkmultipacket(session, msg, msglength) ) {
+ /* Soon, my pretties... */
+ return;
+ }
+
+ content_type = mime_getfield(msg, "Content-Type");
+
+ if ( strstr(content_type, "text/plain") != NULL ) {
+ sbprotocol_parseim(session, msg, msglength);
+ } else if ( strstr(content_type, "text/x-msmsgscontrol") != NULL ) {
+ sbprotocol_parsemsgcontrol(session, msg);
+ } else if ( strstr(content_type, "application/x-msnmsgrp2p") != NULL ) {
+ msnp2p_parsemsg(session, session->msg_source, msg, session->expect_length);
+ } else if ( strstr(content_type, "text/x-msmsgsinvite") != NULL ) {
+ msninvite_parsemsg(session, msg);
+ } else if ( strstr(content_type, "text/x-clientcaps") != NULL ) {
+ sbprotocol_handleclientcaps(session, msg);
+ } else if ( strstr(content_type, "application/x-ms-ink") != NULL ) {
+ sbprotocol_handleink_isf(session, session->msg_source, mime_getbody(msg));
+ } else if ( strstr(content_type, "text/x-msnmsgr-datacast") != NULL ) {
+ sbprotocol_handledatacast(session, session->msg_source, mime_getbody(msg));
+ } else {
+ debug_print("SB %8p: No handler for this MSG (%s).\n", session, content_type);
+ }
+
+ free(content_type);
+ free(session->msg_source); /* Can safely be freed even if routines_lindex didn't find anything earlier. */
+
+}
+
+/* Internally called when data arrives from the SB */
+static void sbprotocol_readable(SbSession *session) {
+
+ unsigned int i = 0; /* =0 keeps compiler happy */
+ ssize_t rlen;
+ int no_string = 0;
+
+ assert(session->rbufsize - session->roffset > 0);
+ rlen = read(session->socket, session->rbuffer + session->roffset, session->rbufsize - session->roffset);
+ if ( rlen > 0 ) {
+ session->roffset += rlen;
+ }
+ assert(session->roffset <= session->rbufsize); /* This would indicate a buffer overrun */
+
+ /* First, check this isn't a disconnection. rlen=0 is EOF, rlen=-1 is an error. */
+ if ( (rlen == 0) || (rlen == -1) ) {
+
+ int closeval;
+
+ closeval = close(session->socket);
+
+ if ( closeval != 0 ) {
+ debug_print("SB %8p: Couldn't close socket after read error.\n", session);
+ }
+
+ sbprotocol_disconnected(session);
+ return;
+
+ }
+
+ while ( (!no_string) && (session->roffset > 0) ) {
+
+ int block_ready = 0;
+
+ /* See if there's a full "block" in the buffer yet */
+ if ( session->conmode == SBCONMODE_LINE ) {
+
+ for ( i=0; i<session->roffset-1; i++ ) { /* Means the last value looked at is roffset-2 */
+ if ( (session->rbuffer[i] == '\r') && (session->rbuffer[i+1] == '\n') ) {
+ block_ready = 1;
+ break;
+ }
+ }
+
+ } else if ( session->conmode == SBCONMODE_MSG ) {
+
+ if ( session->roffset >= session->expect_length ) {
+ i = session->expect_length - 2;
+ block_ready = 1;
+ }
+
+ } else {
+ /* "Never happens". */
+ debug_print("SB %8p: Can't determine end of block for SbConMode %i!\n", session, session->conmode);
+ }
+
+ if ( block_ready == 1 ) {
+
+ char *block_buffer = NULL;
+ unsigned int new_rbufsize;
+ unsigned int endbit_length;
+ SbConMode next_mode = session->conmode;
+
+ if ( session->conmode == SBCONMODE_LINE ) {
+ assert(session->rbuffer[i] == '\r');
+ assert(session->rbuffer[i+1] == '\n');
+ }
+
+
+ if ( session->conmode == SBCONMODE_LINE ) {
+
+ block_buffer = malloc(i+1);
+ memcpy(block_buffer, session->rbuffer, i);
+ block_buffer[i] = '\0';
+
+ sbprotocol_parseline(session, block_buffer);
+
+ /* Check to see if sbprotocol_parseline changed the mode. If so, make sure
+ it stays changed. */
+ if ( session->conmode != SBCONMODE_LINE ) {
+ next_mode = session->conmode;
+ }
+
+ } else if ( session->conmode == SBCONMODE_MSG ) {
+
+ block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */
+ memcpy(block_buffer, session->rbuffer, i+2); /* Copy it in */
+ block_buffer[i+2] = '\0'; /* Terminate */
+
+ sbprotocol_parsemsg(session, block_buffer, i+2);
+ next_mode = SBCONMODE_LINE;
+
+ } else {
+ debug_print("SB %8p: No handler for SbConMode %i !\n", session, session->conmode);
+ }
+
+ free(block_buffer);
+
+ /* Now the block's been parsed, it should be forgotten about */
+ if ( session->conmode == SBCONMODE_LINE ) {
+ assert(session->rbuffer[i+1] == '\n'); /* Should still be the case. Paranoid. */
+ }
+ endbit_length = i+2;
+ memmove(session->rbuffer, session->rbuffer + endbit_length, session->rbufsize - endbit_length);
+ session->roffset = session->roffset - endbit_length; /* Subtract the number of bytes removed */
+ new_rbufsize = session->rbufsize - endbit_length;
+ if ( new_rbufsize == 0 ) {
+ new_rbufsize = 32;
+ }
+ session->rbuffer = realloc(session->rbuffer, new_rbufsize);
+ session->rbufsize = new_rbufsize;
+ session->conmode = next_mode;
+
+ } else {
+
+ if ( session->roffset == session->rbufsize ) {
+
+ /* More buffer space is needed */
+ session->rbuffer = realloc(session->rbuffer, session->rbufsize + 32);
+ session->rbufsize = session->rbufsize + 32;
+ /* The new space gets used at the next read, shortly... */
+
+ }
+ no_string = 1;
+
+ }
+
+ }
+
+}
+
+/* Internally called when the socket is connected */
+static void sbprotocol_ready(SbSession *session) {
+
+ /* Remove the "writeable" callback, and add a "readable" callback */
+ gdk_input_remove(session->wcallback);
+ session->wcallback = 0;
+ session->rcallback = gdk_input_add(session->socket, GDK_INPUT_READ, (GdkInputFunction)sbprotocol_readable, session);
+
+ /* Begin the handshake */
+ if ( session->source == SESSION_SOURCE_REMOTE ) {
+
+ char *args;
+ const char *username;
+
+ username = options_username();
+ args = malloc(strlen(username) + strlen(session->cki_key) + strlen(session->sessionid) +3);
+
+ assert(username != NULL);
+ assert(args != NULL);
+ assert(session->cki_key != NULL);
+ assert(session->sessionid != NULL);
+
+ strcpy(args, username);
+ strcat(args, " ");
+ strcat(args, session->cki_key);
+ strcat(args, " ");
+ strcat(args, session->sessionid);
+ free(session->cki_key);
+ free(session->sessionid);
+
+ sbprotocol_sendtr(session, "ANS", args);
+ free(args);
+
+ } else {
+
+ char *args;
+ const char *username;
+
+ username = options_username();
+ args = malloc(strlen(username) + strlen(session->cki_key) + 2);
+
+ assert(username != NULL);
+ assert(args != NULL);
+ assert(session->cki_key != NULL);
+
+ strcpy(args, username);
+ strcat(args, " ");
+ strcat(args, session->cki_key);
+ free(session->cki_key);
+
+ sbprotocol_sendtr(session, "USR", args);
+ free(args);
+
+ }
+
+}
+
+static void sbprotocol_connect(SbSession *session, const char *hostname, unsigned short int port) {
+
+ struct sockaddr_in sa_desc;
+ struct hostent *server;
+ unsigned int sockopts;
+ int conn_error;
+
+ assert(hostname != NULL);
+ if ( port == 0 ) {
+ port = DEFAULT_SB_PORT;
+ debug_print("SB %8p: Fixed obviously wrong port number to %i\n", session, port);
+ }
+ debug_print("SB %8p: Connecting to '%s' port %i\n", session, hostname, port);
+
+ /* Create and configure the socket (but don't connect it yet) */
+ session->socket = socket(PF_INET, SOCK_STREAM, 0);
+ if ( session->socket == -1 ) {
+ error_report("Couldn't create switchboard socket");
+ return;
+ }
+ sockopts = fcntl(session->socket, F_GETFL);
+ fcntl(session->socket, F_SETFL, sockopts | O_NONBLOCK);
+
+ /* Resolve the server name */
+ server = gethostbyname(hostname);
+ if ( server == NULL ) {
+ error_report("Couldn't resolve switchboard hostname.");
+ return;
+ }
+
+ memset(&sa_desc, 0, sizeof(sa_desc));
+ sa_desc.sin_family = AF_INET;
+ memcpy(&sa_desc.sin_addr.s_addr, server->h_addr, server->h_length);
+ sa_desc.sin_port = htons(port);
+
+ session->rbuffer = malloc(256);
+ assert(session->rbuffer != NULL);
+ session->rbufsize = 256;
+ session->roffset = 0;
+ session->wbufsize = 0;
+ session->wbuffer = NULL;
+ session->woffset = 0;
+
+ conn_error = connect(session->socket, (struct sockaddr *)&sa_desc, sizeof(sa_desc))<0;
+ if ( (conn_error < 0) && (conn_error != EINPROGRESS) ) {
+
+ debug_print("SB %8p: Couldn't connect to server", session);
+ error_report("Couldn't connect to switchboard.");
+ return;
+
+ }
+
+ /* Call back when the socket is connected */
+ session->wcallback = gdk_input_add(session->socket, GDK_INPUT_WRITE, (GdkInputFunction)sbprotocol_ready, session);
+
+ session->trid = 1;
+ session->conmode = SBCONMODE_LINE;
+ session->expect_length = 0;
+
+}
+
+/* Called from src/msnprotocol.c when the XFR SB information is ready. */
+void sbprotocol_initiate_local(unsigned int trid, const char *hostname, unsigned int port, const char *cki_key) {
+
+ SbSession *session;
+
+ session = sbsessions_find_trid(trid);
+ if ( session == NULL ) {
+ debug_print("SB %8p: Couldn't find session! Aborting.\n", NULL);
+ return;
+ }
+
+ assert(session->users != NULL);
+ assert(session->users->username != NULL);
+ debug_print("SB %8p: Matched to user %s\n", session, session->users->username);
+ session->neg_trid = 0; /* Stop this session from being re-matched. */
+
+ session->cki_key = strdup(cki_key);
+ sbprotocol_connect(session, hostname, port);
+
+}
+
+void sbprotocol_initiate_remote(SbSession *session, const char *switchboardaddress, const char *sessionid, const char *authchallenge) {
+
+ char *hostname;
+ unsigned int port;
+
+ session->cki_key = strdup(authchallenge);
+ session->sessionid = strdup(sessionid);
+
+ hostname = routines_hostname(switchboardaddress);
+ port = routines_port(switchboardaddress);
+
+ sbprotocol_connect(session, hostname, port);
+
+ free(hostname);
+
+}
+
+void sbprotocol_send(SbSession *session, char *textblock, int length) {
+
+ if ( strlen(textblock) != 0 ) {
+
+ char *user_header;
+
+ session->am_typing = 0;
+ if ( session->am_typing_callback != 0 ) {
+ gtk_timeout_remove(session->am_typing_callback);
+ }
+
+ /* Send "User-Agent" and text style string with normal messages. */
+ user_header = malloc(256 + strlen(tuxmessenger_versionstring()));
+ strcpy(user_header, "User-Agent: ");
+ strcat(user_header, tuxmessenger_versionstring());
+
+ if ( session->messagewindow->localfont != NULL ) {
+ strcat(user_header, "\r\nX-MMS-IM-Format: ");
+ strcat(user_header, session->messagewindow->localfont);
+ if ( session->messagewindow->localcolour_string != NULL ) {
+ strcat(user_header, "; CO=");
+ strcat(user_header, session->messagewindow->localcolour_string);
+ }
+ } else {
+ if ( session->messagewindow->localcolour_string != NULL ) {
+ strcat(user_header, "\r\nX-MMS-IM-Format: CO=");
+ strcat(user_header, session->messagewindow->localcolour_string);
+ }
+ }
+
+ sbprotocol_sendmsg(session, "text/plain; charset=UTF-8", user_header, textblock, strlen(textblock));
+
+ free(user_header);
+ return;
+
+ } else {
+
+ debug_print("SB %8p: Not sending empty message.\n", session);
+ return;
+
+ }
+
+}
+
+void sbprotocol_sendnudge(SbSession *session) {
+ sbprotocol_sendmsg(session, "text/x-msnmsgr-datacast", NULL, "ID: 1\r\n\r\n", 9);
+}
+
+void sbprotocol_sendtypingcontrol(SbSession *session) {
+
+ char *control_header;
+
+ if ( session->ready != FALSE ) {
+
+ control_header = malloc(strlen(options_username())+16);
+ strcpy(control_header, "TypingUser: ");
+ strcat(control_header, options_username());
+ sbprotocol_sendmsg(session, "text/x-msmsgscontrol", control_header, "", 0);
+ free(control_header);
+
+ } else {
+
+ debug_print("SB %8p: Not sending TypingUser control before session is ready.\n", session);
+
+ }
+
+}
+
+void sbprotocol_close(SbSession *session) {
+ close(session->socket);
+}
diff --git a/src/sbprotocol.h b/src/sbprotocol.h
new file mode 100644
index 0000000..5fb8d69
--- /dev/null
+++ b/src/sbprotocol.h
@@ -0,0 +1,46 @@
+/*
+ * sbprotocol.h
+ *
+ * SB protocol handling
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef SBPROTOCOL_H
+#define SBPROTOCOL_H
+
+#include "sbsessions.h"
+
+/* Session initiation hooks. */
+extern void sbprotocol_initiate_local(unsigned int trid, const char *hostname, unsigned int port, const char *cki_key);
+extern void sbprotocol_initiate_remote(SbSession *session, const char *switchboardaddress, const char *sessionid, const char *authchallenge);
+
+/* High-level protocol operations. */
+extern void sbprotocol_invite(SbSession *session, const char *username);
+extern void sbprotocol_leavesession(SbSession *session);
+extern void sbprotocol_sendtypingcontrol(SbSession *session);
+extern void sbprotocol_sendnudge(SbSession *session);
+
+/* Low-level protocol operations. */
+extern void sbprotocol_send(SbSession *session, char *textblock, int length);
+extern int sbprotocol_sendtr_nonewline(SbSession *session, const char *instr, const char *args, ssize_t length);
+extern void sbprotocol_parsemsg(SbSession *session, const char *msg, ssize_t msglength);
+extern void sbprotocol_close(SbSession *session);
+
+#endif /* SBPROTOCOL_H */
diff --git a/src/sbsessions.c b/src/sbsessions.c
new file mode 100644
index 0000000..0ce353c
--- /dev/null
+++ b/src/sbsessions.c
@@ -0,0 +1,597 @@
+/*
+ * sbsessions.c
+ *
+ * SB session (=>IM window) management (but not the protocol nor UI)
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "debug.h"
+#include "messagewindow.h"
+#include "sbsessions.h"
+#include "sbprotocol.h"
+#include "msnprotocol.h"
+#include "contactlist.h"
+#include "msngenerics.h"
+#include "options.h"
+#include "msnp2p.h"
+#include "error.h"
+#include "routines.h"
+#include "fonttrans.h"
+
+SbSession *sessions = NULL;
+
+static SbSession *sbsessions_lastsession() {
+
+ SbSession *session = sessions;
+
+ while ( session ) {
+
+ assert(session != NULL);
+
+ if ( session->next == NULL ) {
+ return session;
+ } else {
+ session = session->next;
+ }
+
+ }
+
+ return NULL; /* If there were no sessions at all. */
+
+}
+
+static void sbsessions_inituser(SbUser *new_user) {
+
+}
+
+static int sbsessions_ready_callback(SbSession *session) {
+
+ if ( !session->ready ) {
+ debug_print("SS %8p: Not ready after 60 seconds - deleting.\n", session);
+ sbsessions_destroy(session);
+ return FALSE;
+ }
+ session->ready_timeout = 0;
+
+ return FALSE; /* Don't repeat */
+
+}
+
+
+static SbSession *sbsessions_new(const char *username, SbSessionSource source) {
+
+ SbSession *new_session;
+ SbSession *last_session;
+
+ new_session = malloc(sizeof(SbSession));
+ new_session->users = malloc(sizeof(SbUser));
+ assert(new_session->users != NULL);
+
+ new_session->messagewindow = NULL;
+
+ new_session->users->next = NULL;
+ new_session->users->username = strdup(username);
+ sbsessions_inituser(new_session->users);
+
+ new_session->ready = FALSE;
+ new_session->num_users = 1;
+ new_session->rbufsize = 0;
+ new_session->wbufsize = 0;
+ new_session->rbuffer = NULL;
+ new_session->wbuffer = NULL;
+ new_session->roffset = 0;
+ new_session->roffset = 0;
+ new_session->am_typing = 0;
+ new_session->am_typing_callback = 0;
+ new_session->threeway_intent = NULL;
+ new_session->multipackets = NULL;
+ new_session->trid = 1; /* Reset in sbprotocol_connect */
+ new_session->cached = NULL;
+
+ new_session->source = source;
+ new_session->rcallback = 0;
+ new_session->wcallback = 0;
+
+ /* 60 seconds to get ready. */
+ new_session->ready_timeout = gtk_timeout_add(60000, (GtkFunction)sbsessions_ready_callback, new_session);
+
+ /* Link it into the list. */
+ last_session = sbsessions_lastsession();
+ if ( last_session != NULL ) {
+ assert(last_session->next == NULL);
+ last_session->next = new_session;
+ } else {
+ sessions = new_session;
+ }
+ new_session->next = NULL;
+
+ return new_session;
+
+}
+
+SbSession *sbsessions_create_local(const char *username) {
+
+ SbSession *session = sbsessions_new(username, SESSION_SOURCE_LOCAL);
+
+ session->neg_trid = msnprotocol_initiatesb();
+ debug_print("SS %8p: New local session for user %s.\n", session, username);
+
+ return session;
+
+}
+
+/* Called when the user clicks on a name in the contact list. */
+SbSession *sbsessions_create_remote(char *username, char *switchboardaddress, char *sessionid, char *authchallenge) {
+
+ SbSession *session = sbsessions_new(username, SESSION_SOURCE_REMOTE);
+
+ session->neg_trid = 0;
+ sbprotocol_initiate_remote(session, switchboardaddress, sessionid, authchallenge);
+ debug_print("SS %8p: New remote session for user %s.\n", session, username);
+
+ return session;
+
+}
+
+/* Create a three-way session in "one go". */
+SbSession *sbsessions_create_threeway(char *username1, char *username2) {
+
+ SbSession *session = sbsessions_new(username1, SESSION_SOURCE_LOCAL);
+
+ session->threeway_intent = strdup(username2);
+ session->neg_trid = msnprotocol_initiatesb();
+ debug_print("SS %8p: New local session for user %s. Intending to invite %s too.\n", session, username1, username2);
+
+ return session;
+
+}
+
+void sbsessions_plug(SbSession *session, MessageWindow *messagewindow) {
+ session->messagewindow = messagewindow;
+}
+
+/* Unplug any SB sessions which are plugged into a given IM window */
+void sbsessions_unplug(MessageWindow *messagewindow) {
+
+ SbSession *session = sessions;
+
+ while ( session != NULL ) {
+ if ( session->messagewindow == messagewindow ) {
+ session->messagewindow = NULL;
+ }
+ session = session->next;
+ }
+
+}
+
+/* Find a headless session with this username. */
+SbSession *sbsessions_find_headless(const char *username) {
+
+ SbSession *session = sessions;
+
+ while ( session != NULL ) {
+ if ( (session->num_users == 1) && (strcmp(username, session->users->username) == 0) && (session->messagewindow == NULL) ) {
+ return session;
+ }
+ session = session->next;
+ }
+
+ return NULL;
+
+}
+
+/* Find a session with just this user in, and noone about to randomly join (hopefully...) */
+SbSession *sbsessions_find_single_safe(const char *username) {
+
+ SbSession *session = sessions;
+
+ while ( session != NULL ) {
+ if ( (session->num_users == 1) && (strcmp(username, session->users->username) == 0) ) {
+ if ( session->threeway_intent == NULL ) {
+ return session;
+ }
+ }
+ session = session->next;
+ }
+
+ return NULL;
+
+}
+
+
+/* SbUsers are unique to a particular SbSession. Find the parent SbSession
+ given the SbUser. Could just keep a note of the parent record in the
+ SbUser record, but sbsessions_stoptyping needs to know if the SbUser
+ is still attached to (any) SbSession. */
+SbSession *sbsessions_find_user(SbUser *target_user) {
+
+ SbSession *session;
+
+ session = sessions;
+ while ( session != NULL ) {
+ SbUser *user = session->users;
+ while ( user != NULL ) {
+ if ( user == target_user ) {
+ return session;
+ }
+ user = user->next;
+ }
+ session = session->next;
+ }
+
+ debug_print("SS %8p: Couldn't find user in all sessions.\n", NULL);
+ return NULL;
+
+}
+
+/* Return a session's record given "TrID". */
+SbSession *sbsessions_find_trid(unsigned int trid) {
+
+ SbSession *session;
+
+ session = sessions;
+ while ( session != NULL ) {
+
+ assert(session != NULL);
+
+ if ( session->neg_trid == trid ) {
+ return session;
+ } else {
+ session = session->next;
+ }
+
+ }
+
+ debug_print("SS %8p: Failed to find session negotiated with TrId %i\n", NULL, trid);
+ return NULL;
+
+}
+
+SbUser *sbsessions_find_username(SbSession *session, const char *username) {
+
+ SbUser *user;
+
+ assert(session != NULL);
+ assert(session->users != NULL);
+
+ user = session->users;
+ while ( user != NULL ) {
+
+ assert(user != NULL);
+
+ /* Case-insensitive here. Username may have different case depending
+ on its source, since the servers seem to change usernames to
+ lower case but clients might not in (e.g.) TypingUser controls. */
+ if ( strcasecmp(user->username, username) == 0 ) {
+ return user;
+ } else {
+ user = user->next;
+ }
+
+ }
+
+ return NULL;
+
+}
+
+/* Locate a user on a given SB session from their DP SHA1D field. */
+SbUser *sbsessions_find_dpsha1d(SbSession *session, char *sha1d) {
+
+ SbUser *user;
+
+ assert(session != NULL);
+ assert(session->users != NULL);
+
+ user = session->users;
+ while ( user != NULL ) {
+
+ assert(user != NULL);
+
+ if ( strcmp(user->dpsha1d, sha1d) == 0 ) {
+ return user;
+ } else {
+ user = user->next;
+ }
+
+ }
+
+ return NULL;
+
+}
+
+void sbsessions_destroy(SbSession *session) {
+
+ SbSession *prev_session;
+ SbUser *user;
+ CachedMsg *cached;
+
+ /* Unplug the session and close the socket. */
+ messagewindow_unplug(session);
+ sbprotocol_close(session);
+
+ /* Remove the session from the list. */
+ prev_session = sessions;
+ if ( prev_session != session ) {
+ while ( prev_session != NULL ) {
+ assert(prev_session != NULL);
+ if ( prev_session->next == session ) {
+ break;
+ } else {
+ prev_session = prev_session->next;
+ }
+ }
+ assert(prev_session->next == session);
+ /* Link it out of the list. */
+ prev_session->next = session->next;
+ } else {
+ /* This session was the first on the list. */
+ assert(sessions == session); /* Can't fail... */
+ sessions = session->next; /* Which may be NULL if the list is now empty. */
+ }
+
+ /* Drop all MSNP2P sessions in this SB session. */
+ msnp2p_abortall(session);
+
+ /* Drop any cached messages, and inform the user. */
+ cached = session->cached;
+ while ( cached != NULL ) {
+
+ CachedMsg *next = cached->next;
+ if ( !session->messagewindow ) {
+ messagewindow_mitigate(session);
+ }
+ messagewindow_reportdropped(session->messagewindow, cached->message, cached->length);
+ free(cached->message);
+ free(cached);
+ cached = next;
+ messagewindow_unplug(session); /* This session doesn't exist, remember? */
+
+ }
+
+ /* Remove all the users. */
+ user = session->users;
+ while ( user != NULL ) {
+
+ SbUser *next_user = user->next;
+ free(user->username);
+ free(user);
+ user = next_user;
+
+ }
+
+ /* Wind up the low-level business. */
+ if ( session->rcallback != 0 ) {
+ gdk_input_remove(session->rcallback);
+ }
+ session->rcallback = 0;
+ if ( session->wcallback != 0 ) {
+ gdk_input_remove(session->wcallback);
+ }
+ session->wcallback = 0;
+
+ if ( session->ready_timeout != 0 ) {
+ gtk_timeout_remove(session->ready_timeout);
+ }
+ session->ready_timeout = 0;
+
+ if ( session->am_typing_callback != 0 ) {
+ gtk_timeout_remove(session->am_typing_callback);
+ session->am_typing = 0;
+ session->am_typing_callback = 0;
+ }
+
+ if ( session->rbuffer != NULL ) {
+ free(session->rbuffer);
+ }
+ if ( session->wbuffer != NULL ) {
+ free(session->wbuffer);
+ }
+
+ free(session);
+
+}
+
+SbUser *sbsessions_lastuser(SbSession *session) {
+
+ SbUser *user;
+
+ user = session->users;
+ while ( user != NULL ) {
+
+ assert(user != NULL);
+
+ if ( user->next == NULL ) {
+ return user;
+ } else {
+ user = user->next;
+ }
+
+ }
+
+ debug_print("SS %8p: Reached end of sbsessions_lastuser! This doesn't happen!\n", session);
+ return NULL;
+
+}
+
+static SbUser *sbsessions_addnewuser(SbSession *session, char *username) {
+
+ SbUser *new_user;
+ SbUser *previous_user;
+
+ new_user = malloc(sizeof(SbUser));
+ assert(new_user != NULL);
+ new_user->next = NULL;
+ new_user->username = strdup(username);
+ sbsessions_inituser(new_user);
+
+ previous_user = sbsessions_lastuser(session);
+ assert(previous_user->next == NULL);
+ previous_user->next = new_user;
+
+ session->num_users++;
+
+ return new_user;
+
+}
+
+/* Deal with any user joining a session (whether by JOI or IRO). */
+void sbsessions_joined(SbSession *session, char *username, char *friendlyname) {
+
+ SbUser *user;
+
+ /* "friendlyname" is from the JOI message. This user might not be in any of the
+ contact lists. Now would be a good time to check, and if not, create a
+ temporary record for them. */
+ if ( contactlist_friendlyname(username) == NULL ) {
+ debug_print("SS %8p: Creating temporary user record for '%s'/'%s'\n", session, username, friendlyname);
+ contactlist_tldetails(CONTACT_SOURCE_RNG, username, friendlyname, ONLINE_NLN);
+ }
+
+ user = sbsessions_find_username(session, username);
+ if ( user == NULL ) {
+
+ /* User is new - probably got invited by someone else */
+ debug_print("SS %8p: Couldn't find user %s in SB record - adding them.\n", session, username);
+
+ user = sbsessions_addnewuser(session, username);
+
+ }
+
+ if ( session->messagewindow != NULL ) {
+ messagewindow_joined(session->messagewindow, username);
+ }
+
+}
+
+/* Deal with any user leaving a session. */
+void sbsessions_left(SbSession *session, char *username) {
+
+ SbUser *user;
+
+ user = sbsessions_find_username(session, username);
+ if ( user == NULL ) {
+ debug_print("SS %8p: Couldn't find leaving user %s in SB record!\n", session, username);
+ return;
+ }
+
+ if ( session->num_users == 1 ) {
+ sbprotocol_leavesession(session);
+ } else {
+
+ /* "Normal" situation. Remove user record, their DP and status bar. */
+
+ SbUser *find_user;
+
+ session->num_users--;
+ if ( session->messagewindow != NULL ) {
+ messagewindow_removeuser(session->messagewindow, username);
+ }
+
+ /* Find the preceding user in the session and link this user out. */
+ find_user = session->users;
+ if ( find_user == user ) {
+ /* User was the first in the list. */
+ session->users = user->next;
+ } else {
+
+ while ( find_user != NULL ) {
+
+ assert(find_user != NULL);
+
+ if ( find_user->next == user ) {
+ find_user->next = user->next;
+ break;
+ } else {
+ find_user = find_user->next;
+ }
+
+ }
+
+ }
+ free(user->username);
+ free(user);
+
+ }
+
+}
+
+static int sbsessions_amtyping_callback(SbSession *session) {
+
+ session->am_typing = 0;
+ session->am_typing_callback = 0;
+
+ return FALSE; /* Don't repeat */
+
+}
+
+void sbsessions_am_typing(SbSession *session) {
+
+ if ( !session->ready ) {
+ debug_print("SS %8p: Not ready - not sending TypingUser.\n", session);
+ return;
+ }
+
+ /* Calculate timeout then send TypingUser control. */
+ if ( session->am_typing == 0 ) {
+
+ sbprotocol_sendtypingcontrol(session);
+ session->am_typing = 1;
+ session->am_typing_callback = gtk_timeout_add(5000, (GtkFunction)sbsessions_amtyping_callback, session);
+
+ }
+
+}
+
+/* Check a session is ready for I/O. */
+int sbsessions_sessionready(SbSession *session) {
+
+ if ( session == NULL ) {
+ return FALSE;
+ }
+
+ return session->ready;
+
+}
+
+/* Delete all sessions. */
+void sbsessions_destroy_all() {
+
+ SbSession *session;
+ SbSession *next;
+
+ debug_print("SB %8p: destroying all sessions.\n", NULL);
+
+ session = sessions;
+ while ( session != NULL ) {
+ next = session->next;
+ sbsessions_destroy(session);
+ session = next;
+ }
+
+}
diff --git a/src/sbsessions.h b/src/sbsessions.h
new file mode 100644
index 0000000..4df1507
--- /dev/null
+++ b/src/sbsessions.h
@@ -0,0 +1,140 @@
+/*
+ * sbsessions.h
+ *
+ * SB session management (but not the protocol nor UI)
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef SBSESSIONS_H
+#define SBSESSIONS_H
+
+#include <gtk/gtk.h>
+
+typedef enum {
+ SESSION_SOURCE_LOCAL, /* Session was initiated by the user clicking on a contact. */
+ SESSION_SOURCE_REMOTE /* Session was initiated by an RNG message from Outside. */
+} SbSessionSource;
+
+typedef enum {
+ SBCONMODE_LINE, /* Normal, line-based, communication. */
+ SBCONMODE_MSG /* A message is coming through. */
+} SbConMode;
+
+typedef struct _sbuser { /* No data is here that's already held by src/contactlist.c, except the username. */
+
+ struct _sbuser *next;
+
+ char *username;
+
+ char *dpsha1d; /* SHA1D field from their picture as it appears in the IM window. */
+
+} SbUser;
+
+/* Data structure to hold information while reconstructing a multipacketed message. */
+typedef struct _multipacketmsg {
+
+ struct _multipacketmsg *next;
+ struct _multipacketmsg *previous;
+
+ char *message_id;
+ int chunks_expected;
+ int chunks_received;
+ char *data;
+ size_t datalength;
+
+} MultiPacketMsg;
+
+typedef struct _cachedmsg {
+
+ struct _cachedmsg *next;
+ char *message;
+ size_t length;
+
+} CachedMsg;
+
+typedef struct _sbsession {
+
+ struct _sbsession *next;
+
+ /* List of Users */
+ SbUser *users; /* List of people in the session. */
+ unsigned int num_users; /* Number of remote users in this session. Session destroyed if this = zero. */
+
+ /* User interface stuff */
+ struct _messagewindow *messagewindow;
+
+ /* High-level protocol stuff */
+ int ready; /* Non-zero if session is up and running. */
+ gint ready_timeout; /* Callback tag to timeout session if it doesn't connect. */
+ unsigned int neg_trid; /* TrID used in negotiating this session. */
+ unsigned int trid; /* TrID used in communicating on the SB (don't confuse with neg_trid) */
+ int socket; /* Socket to the switchboard. */
+ SbSessionSource source; /* Where the session was started from. */
+ char *cki_key; /* Authentication data for negotiating the session. */
+ char *sessionid; /* SessionID (part of authentication to the SB) */
+ int first_event; /* Non-zero if message window currently contains no text */
+ char *msg_source; /* Who the MSG currently being dealt with came from */
+ int am_typing; /* Non-zero if the local user is currently "thought to be typing". */
+ int am_typing_callback; /* Callback tag for the end of each typing period for the local user. */
+ char *threeway_intent; /* Extra user to invite when ready (if any). */
+ MultiPacketMsg *multipackets; /* Linked list of multipacketing records. */
+ CachedMsg *cached; /* Cached messages to send when session is ready. */
+
+ /* Low-level protocol stuff */
+ int wcallback; /* Writeable callback */
+ char *wbuffer; /* Write buffer */
+ unsigned int wbufsize; /* Write buffer size */
+ unsigned int woffset; /* Write buffer position */
+ int rcallback; /* Readable callback */
+ char *rbuffer; /* Read buffer */
+ unsigned int rbufsize; /* Read buffer size */
+ unsigned int roffset; /* Read buffer position */
+ SbConMode conmode; /* What's coming through the socket at the moment */
+ unsigned int expect_length; /* Expected length of (eg) a MSG */
+
+} SbSession;
+
+#include "messagewindow.h"
+
+/* Operations on SB sessions. */
+extern SbSession *sbsessions_create_local(const char *username);
+extern SbSession *sbsessions_create_remote(char *username, char *switchboardaddress, char *sessionid, char *authchallenge);
+extern SbSession *sbsessions_create_threeway(char *username1, char *username2);
+extern void sbsessions_destroy(SbSession *session);
+extern void sbsessions_plug(SbSession *session, struct _messagewindow *messagewindow);
+extern void sbsessions_unplug(struct _messagewindow *messagewindow);
+extern SbSession *sbsessions_find_trid(unsigned int trid);
+extern SbSession *sbsessions_find_headless(const char *username);
+extern SbSession *sbsessions_find_single_safe(const char *username);
+extern int sbsessions_sessionready(SbSession *session);
+
+/* Operations on the local user in SB sessions. */
+extern void sbsessions_am_typing(SbSession *session);
+
+/* Operations on other users in SB sessions. */
+extern void sbsessions_joined(SbSession *session, char *username, char *friendlyname);
+extern void sbsessions_left(SbSession *session, char *username);
+extern SbUser *sbsessions_find_username(SbSession *session, const char *username);
+extern int sbsessions_stoptyping(SbUser *user);
+
+/* Other stuff. */
+extern void sbsessions_destroy_all();
+
+#endif /* SBSESSIONS_H */
diff --git a/src/statusicons.c b/src/statusicons.c
new file mode 100644
index 0000000..0cc8ed2
--- /dev/null
+++ b/src/statusicons.c
@@ -0,0 +1,116 @@
+/*
+ * statusicons.c
+ *
+ * Little pixmaps to go next to the contacts in the list
+ *
+ * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <assert.h>
+
+#include "debug.h"
+#include "mainwindow.h"
+
+static struct {
+
+ GdkPixmap *online_pixmap;
+ GdkPixmap *offline_pixmap;
+ GdkPixmap *away_pixmap;
+ GdkPixmap *busy_pixmap;
+
+ GdkPixmap *online_blocked_pixmap;
+ GdkPixmap *offline_blocked_pixmap;
+ GdkPixmap *away_blocked_pixmap;
+ GdkPixmap *busy_blocked_pixmap;
+
+} statusicons = {
+
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+
+ NULL,
+ NULL,
+ NULL,
+ NULL
+
+};
+
+/* Return the correct pixmap to pack into the contact's widget */
+GtkWidget *statusicons_pixmap(OnlineState status) {
+
+ GdkPixmap **pixmap;
+ char *file;
+
+ pixmap = NULL;
+ file = NULL;
+
+ if ( status == ONLINE_NLN ) {
+ pixmap = &statusicons.online_pixmap;
+ file = DATADIR"/tuxmessenger/online.xpm";
+ } else if ( status == ONLINE_IDL ) {
+ pixmap = &statusicons.away_pixmap;
+ file = DATADIR"/tuxmessenger/away.xpm";
+ } else if ( status == ONLINE_AWY ) {
+ pixmap = &statusicons.away_pixmap;
+ file = DATADIR"/tuxmessenger/away.xpm";
+ } else if ( status == ONLINE_BSY ) {
+ pixmap = &statusicons.busy_pixmap;
+ file = DATADIR"/tuxmessenger/occ.xpm";
+ } else if ( status == ONLINE_LUN ) {
+ pixmap = &statusicons.away_pixmap;
+ file = DATADIR"/tuxmessenger/away.xpm";
+ } else if ( status == ONLINE_BRB ) {
+ pixmap = &statusicons.away_pixmap;
+ file = DATADIR"/tuxmessenger/away.xpm";
+ } else if ( status == ONLINE_PHN ) {
+ pixmap = &statusicons.busy_pixmap;
+ file = DATADIR"/tuxmessenger/occ.xpm";
+ } else if ( status == ONLINE_FLN ) {
+ pixmap = &statusicons.offline_pixmap;
+ file = DATADIR"/tuxmessenger/offline.xpm";
+ }
+
+ assert(pixmap != NULL);
+ assert(file != NULL);
+
+ if ( *pixmap == NULL ) {
+
+ /* XPM hasn't been loaded yet. Load it. */
+ GtkStyle *style;
+ style = mainwindow_style();
+ *pixmap = gdk_pixmap_create_from_xpm(mainwindow_window(), NULL, &style->bg[GTK_STATE_NORMAL], file);
+ if ( *pixmap == NULL ) {
+ /* Contingency if the image can't be loaded. */
+ debug_print("SI: Couldn't load status icon!\n");
+ return gtk_label_new("");
+ }
+
+ }
+
+ return gtk_pixmap_new(*pixmap, NULL);
+
+}
diff --git a/src/statusicons.h b/src/statusicons.h
new file mode 100644
index 0000000..d80f042
--- /dev/null
+++ b/src/statusicons.h
@@ -0,0 +1,32 @@
+/*
+ * statusicons.h
+ *
+ * Little pixmaps to go next to the contacts in the list
+ *
+ * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef STATUSICONS_H
+#define STATUSICONS_H
+
+#include "msngenerics.h"
+
+extern GtkWidget *statusicons_pixmap(OnlineState status);
+
+#endif /* STATUSICONS_H */
diff --git a/src/twnauth.c b/src/twnauth.c
new file mode 100644
index 0000000..19b9aef
--- /dev/null
+++ b/src/twnauth.c
@@ -0,0 +1,218 @@
+/*
+ * twnauth.c
+ *
+ * TWN authentication
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "debug.h"
+#include "options.h"
+#include "routines.h"
+
+/* Nothing insecure happens if the server data won't fit in this - anything
+ beyond this limit simply gets ignored. */
+#define TWNAUTH_WGETSIZE 4096
+//#define TWNAUTH_DEBUG 1
+
+/* Internally called to get the login URL from Nexus */
+char *twnauth_loginurl() {
+
+ char *shellcommand;
+ int read_more;
+ FILE *wget_stream;
+ char *nexus = NULL;
+ char *wget_buffer;
+ int wget_offset = 0;
+ size_t i;
+
+ shellcommand = malloc(1024);
+ strncpy(shellcommand, options_wget(), 256);
+ shellcommand[255] = '\0';
+ strcat(shellcommand, " -S -O - https://nexus.passport.com/rdr/pprdr.asp 2>&1");
+
+ debug_print("twnauth: nexus: %s\n", shellcommand);
+ wget_stream = popen(shellcommand, "r");
+
+ if ( wget_stream == NULL ) {
+ debug_print("twnauth: Nexus failed.\n");
+ return strdup("");
+ }
+
+ wget_buffer = malloc(TWNAUTH_WGETSIZE);
+ read_more = 1;
+ while ( read_more ) {
+
+ ssize_t readval;
+ readval = fread(wget_buffer + wget_offset, 1, TWNAUTH_WGETSIZE-wget_offset, wget_stream);
+ if ( (readval != 0) && (readval != -1) && (wget_offset < TWNAUTH_WGETSIZE) ) {
+ wget_offset += readval;
+ } else {
+ read_more = 0;
+ wget_buffer[wget_offset] = '\0';
+ }
+
+ }
+
+ pclose(wget_stream);
+
+#ifdef TWNAUTH_DEBUG
+ debug_print("'%s'\n", wget_buffer);
+#endif
+
+ /* Now try and find the new nexus in the wget output */
+ for ( i=0; i<strlen(wget_buffer); i++ ) {
+
+ if ( strncmp((wget_buffer+i), "DALogin=", 8) == 0 ) {
+
+ size_t j;
+
+ /* Find the end of the ticket and tie it off */
+ for ( j=i+7; j<strlen(wget_buffer); j++ ) {
+ if ( wget_buffer[j] == ',' ) {
+ wget_buffer[j] = '\0';
+ }
+ }
+
+ nexus = strdup(wget_buffer+i+8);
+ debug_print("twnauth: nexus: %s\n", nexus);
+
+ }
+
+ }
+
+ free(wget_buffer);
+ free(shellcommand);
+
+ if ( nexus == NULL ) {
+ debug_print("Warning! Couldn't retrieve URL from Nexus!\n");
+ nexus = strdup("");
+ }
+ return nexus;
+
+}
+
+char *twnauth_ticket(char *authdata) {
+
+ char *shellcommand;
+ int read_more;
+ FILE *wget_stream;
+ char *options_username_2;
+ char *auth_ticket = NULL;
+ char *wget_buffer;
+ int wget_offset = 0;
+ size_t i;
+ char *loginurl;
+
+ assert(strlen(authdata) < 255);
+
+ loginurl = twnauth_loginurl();
+
+ shellcommand = malloc(1024);
+ strncpy(shellcommand, options_wget(), 256);
+ shellcommand[255] = '\0';
+ strcat(shellcommand, " -S -O - --header='Authorization: Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in=");
+ options_username_2 = routines_urlencode(options_username());
+ strncat(shellcommand, options_username_2, 64);
+ shellcommand[1023] = '\0';
+ free(options_username_2);
+ strncat(shellcommand, ",pwd=", 64);
+ shellcommand[1023] = '\0';
+ strncat(shellcommand, options_password(), 64);
+ shellcommand[1023] = '\0';
+ strcat(shellcommand, ",");
+ shellcommand[1023] = '\0';
+ strncat(shellcommand, authdata, 256);
+ shellcommand[1023] = '\0';
+ strcat(shellcommand, "' https://");
+ shellcommand[1023] = '\0';
+ strncat(shellcommand, loginurl, 64);
+ shellcommand[1023] = '\0';
+ free(loginurl);
+ strcat(shellcommand, " 2>&1");
+ shellcommand[1023] = '\0';
+
+#ifdef TWNAUTH_DEBUG
+ debug_print("twnauth: %s\n", shellcommand);
+#else
+ debug_print("twnauth: (sending authentication data)\n");
+#endif
+ wget_stream = popen(shellcommand, "r");
+
+ if ( wget_stream == NULL ) {
+ return NULL;
+ }
+
+ wget_buffer = malloc(TWNAUTH_WGETSIZE);
+ read_more = 1;
+ while ( read_more ) {
+
+ ssize_t readval;
+ readval = fread(wget_buffer + wget_offset, 1, TWNAUTH_WGETSIZE-wget_offset, wget_stream);
+ if ( (readval != 0) && (readval != -1) && (wget_offset < TWNAUTH_WGETSIZE) ) {
+ wget_offset += readval;
+ } else {
+ read_more = 0;
+ wget_buffer[wget_offset] = '\0';
+ }
+
+ }
+
+ pclose(wget_stream);
+
+#ifdef TWNAUTH_DEBUG
+ debug_print("'%s'\n", wget_buffer);
+#endif
+
+ /* Now try and find the ticket in the wget output */
+ for ( i=0; i<strlen(wget_buffer); i++ ) {
+
+ if ( strncmp((wget_buffer+i), "from-PP='t=:", 11) == 0 ) {
+
+ size_t j;
+
+ /* Find the end of the ticket and tie it off */
+ for ( j=i+10; j<strlen(wget_buffer); j++ ) {
+ if ( wget_buffer[j] == '\'' ) {
+ wget_buffer[j] = '\0';
+ }
+ }
+
+ auth_ticket = strdup(wget_buffer+i+9);
+ debug_print("twnauth: %s\n", auth_ticket);
+
+ }
+
+ }
+
+ free(wget_buffer);
+ free(shellcommand);
+
+ return auth_ticket;
+
+}
diff --git a/src/twnauth.h b/src/twnauth.h
new file mode 100644
index 0000000..32db8ee
--- /dev/null
+++ b/src/twnauth.h
@@ -0,0 +1,31 @@
+/*
+ * twnauth.h
+ *
+ * TWN authentication
+ *
+ * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef TWNAUTH_H
+#define TWNAUTH_H
+
+extern char *twnauth_loginurl();
+extern char *twnauth_ticket(char *authdata);
+
+#endif /* TWNAUTH_H */
diff --git a/src/xml.c b/src/xml.c
new file mode 100644
index 0000000..4ec82b2
--- /dev/null
+++ b/src/xml.c
@@ -0,0 +1,186 @@
+/*
+ * xml.c
+ *
+ * Rudimentary XML parser
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+
+#include "routines.h"
+#include "debug.h"
+
+char *xml_getfield(const char *xml, const char *field) {
+
+ char *identifier_start;
+ char *value;
+
+ identifier_start = strstr(xml, field);
+
+ if ( identifier_start == NULL ) {
+ return NULL;
+ }
+
+ value = routines_lindex(identifier_start + strlen(field) + 2, 0);
+ value[strlen(value)-1] = '\0';
+
+ return value;
+
+}
+
+char *xml_killillegalchars(const char *temp) {
+
+ char *result;
+ unsigned int i;
+
+ if ( temp == NULL ) {
+ return strdup("");
+ }
+
+ result = malloc(strlen(temp)+1);
+ strcpy(result, temp);
+ for ( i=0; i<strlen(result); i++ ) {
+
+ if ( result[i] == '/' ) {
+ result[i] = '-';
+ }
+
+ /* Don't even think about directory traversal... */
+ if ( result[i] == '.' ) {
+ result[i] = '-';
+ }
+
+ }
+
+ return result;
+
+}
+
+/* Get the block of text between two given tags. */
+char *xml_getblock(const char *xml, size_t xmllength, const char *rootnode, const char *keyword, int entities) {
+
+ xmlDocPtr doc;
+ xmlNodePtr cur;
+
+ doc = xmlParseMemory(xml, xmllength);
+
+ if (doc == NULL ) {
+ return NULL;
+ }
+
+ cur = xmlDocGetRootElement(doc);
+
+ if (cur == NULL) {
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ if (xmlStrcmp(cur->name, rootnode)) {
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ cur = cur->xmlChildrenNode;
+ while (cur != NULL) {
+
+ if ((!xmlStrcmp(cur->name, keyword))) {
+
+ char *resultchar;
+
+ xmlChar *result = xmlNodeListGetString(doc, cur->xmlChildrenNode, entities);
+ if ( result == NULL ) {
+ return NULL;
+ }
+ resultchar = strdup(result);
+ xmlFree(result);
+ return resultchar;
+
+ }
+
+ cur = cur->next;
+
+ }
+
+ return NULL;
+
+}
+
+/* Set the block of text between two given tags. */
+char *xml_setblock(const char *xml, size_t xmllength, const char *rootnode, const char *keyword, const char *value) {
+
+ xmlDocPtr doc;
+ xmlNodePtr cur;
+
+ doc = xmlParseMemory(xml, xmllength);
+
+ if (doc == NULL ) {
+ return NULL;
+ }
+
+ cur = xmlDocGetRootElement(doc);
+
+ if (cur == NULL) {
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ if (xmlStrcmp(cur->name, rootnode)) {
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ cur = cur->xmlChildrenNode;
+ while (cur != NULL) {
+
+ if ((!xmlStrcmp(cur->name, keyword))) {
+
+ xmlChar *xmlbuf;
+ size_t xmlbuflength;
+ char *text;
+
+ xmlNewTextChild(cur->parent, NULL, keyword, value);
+ xmlUnlinkNode(cur);
+ xmlFreeNode(cur);
+
+ xmlDocDumpFormatMemory(doc, &xmlbuf, &xmlbuflength, 0);
+ text = malloc(xmlbuflength+1);
+ memcpy(text, xmlbuf, xmlbuflength+1);
+ xmlFree(xmlbuf);
+
+ return text;
+
+
+
+ }
+
+ cur = cur->next;
+
+ }
+
+ return NULL;
+
+}
diff --git a/src/xml.h b/src/xml.h
new file mode 100644
index 0000000..8fec651
--- /dev/null
+++ b/src/xml.h
@@ -0,0 +1,35 @@
+/*
+ * xml.h
+ *
+ * Rudimentary XML parser
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef XML_H
+#define XML_H
+
+#include <libxml/xmlmemory.h>
+
+extern char *xml_getfield(const char *xml, const char *field);
+extern char *xml_killillegalchars(const char *temp);
+extern char *xml_getblock(const char *xml, size_t xmllength, const char *rootnode, const char *keyword, int entities);
+extern char *xml_setblock(const char *xml, size_t xmllength, const char *rootnode, const char *keyword, const char *value);
+
+#endif /* XML_H */
diff --git a/tuxmessenger.desktop b/tuxmessenger.desktop
new file mode 100644
index 0000000..6e34cb2
--- /dev/null
+++ b/tuxmessenger.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Name=TuxMessenger
+Comment=MSN Messenger client
+Exec=tuxmessenger
+Icon=tuxmessenger
+Terminal=false
+Type=Application
+Categories=GTK;Network;Email;News;
+