From 9ae0abe3414ea26f83fe3e01a37c3cd4819a82b9 Mon Sep 17 00:00:00 2001 From: Thomas White Date: Fri, 17 Jul 2009 12:46:27 +0100 Subject: Initial import --- .gitignore | 28 + AUTHORS | 21 + COPYING | 279 +++++++ ChangeLog | 434 +++++++++++ INSTALL | 182 +++++ Makefile.am | 12 + NEWS | 41 + README | 76 ++ TODO | 67 ++ autogen.sh | 6 + configure.ac | 60 ++ data/Makefile.am | 4 + data/away-blocked.xpm | 48 ++ data/away.xpm | 51 ++ data/hourglass.png | Bin 0 -> 16978 bytes data/icon.png | Bin 0 -> 11835 bytes data/imwindow.ui | 32 + data/mainwindow.ui | 57 ++ data/no_avatar.png | Bin 0 -> 10844 bytes data/occ-blocked.xpm | 48 ++ data/occ.xpm | 51 ++ data/offline-blocked.xpm | 48 ++ data/offline.xpm | 51 ++ data/online-blocked.xpm | 48 ++ data/online.xpm | 51 ++ data/paper.png | Bin 0 -> 331527 bytes src/Makefile.am | 9 + src/about.c | 81 ++ src/about.h | 30 + src/accountwindow.c | 206 ++++++ src/accountwindow.h | 30 + src/addcontact.c | 299 ++++++++ src/addcontact.h | 32 + src/avatars.c | 207 ++++++ src/avatars.h | 36 + src/blockwindow.c | 0 src/blockwindow.h | 0 src/contactlist.c | 901 ++++++++++++++++++++++ src/contactlist.h | 70 ++ src/debug.h | 33 + src/error.c | 121 +++ src/error.h | 32 + src/filetrans.c | 57 ++ src/filetrans.h | 30 + src/fonttrans.c | 132 ++++ src/fonttrans.h | 30 + src/gtk-ink.c | 297 ++++++++ src/gtk-ink.h | 68 ++ src/ink.c | 159 ++++ src/ink.h | 68 ++ src/listcache.c | 615 +++++++++++++++ src/listcache.h | 41 + src/listcleanup.c | 88 +++ src/listcleanup.h | 30 + src/main.c | 64 ++ src/main.h | 34 + src/mainwindow.c | 1383 ++++++++++++++++++++++++++++++++++ src/mainwindow.h | 61 ++ src/messagewindow.c | 1854 ++++++++++++++++++++++++++++++++++++++++++++++ src/messagewindow.h | 118 +++ src/mime.c | 242 ++++++ src/mime.h | 35 + src/msngenerics.c | 71 ++ src/msngenerics.h | 47 ++ src/msninvite.c | 32 + src/msninvite.h | 32 + src/msnp11chl.c | 175 +++++ src/msnp11chl.h | 48 ++ src/msnp2p.c | 1325 +++++++++++++++++++++++++++++++++ src/msnp2p.h | 36 + src/msnprotocol.c | 1692 ++++++++++++++++++++++++++++++++++++++++++ src/msnprotocol.h | 44 ++ src/options.c | 1051 ++++++++++++++++++++++++++ src/options.h | 73 ++ src/prefswindow.c | 921 +++++++++++++++++++++++ src/prefswindow.h | 30 + src/reversewindow.c | 0 src/reversewindow.h | 0 src/routines.c | 800 ++++++++++++++++++++ src/routines.h | 53 ++ src/sbprotocol.c | 1271 +++++++++++++++++++++++++++++++ src/sbprotocol.h | 46 ++ src/sbsessions.c | 597 +++++++++++++++ src/sbsessions.h | 140 ++++ src/statusicons.c | 116 +++ src/statusicons.h | 32 + src/twnauth.c | 218 ++++++ src/twnauth.h | 31 + src/xml.c | 186 +++++ src/xml.h | 35 + tuxmessenger.desktop | 9 + 91 files changed, 18299 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 TODO create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 data/Makefile.am create mode 100644 data/away-blocked.xpm create mode 100644 data/away.xpm create mode 100644 data/hourglass.png create mode 100644 data/icon.png create mode 100644 data/imwindow.ui create mode 100644 data/mainwindow.ui create mode 100644 data/no_avatar.png create mode 100644 data/occ-blocked.xpm create mode 100644 data/occ.xpm create mode 100644 data/offline-blocked.xpm create mode 100644 data/offline.xpm create mode 100644 data/online-blocked.xpm create mode 100644 data/online.xpm create mode 100644 data/paper.png create mode 100644 src/Makefile.am create mode 100644 src/about.c create mode 100644 src/about.h create mode 100644 src/accountwindow.c create mode 100644 src/accountwindow.h create mode 100644 src/addcontact.c create mode 100644 src/addcontact.h create mode 100644 src/avatars.c create mode 100644 src/avatars.h create mode 100644 src/blockwindow.c create mode 100644 src/blockwindow.h create mode 100644 src/contactlist.c create mode 100644 src/contactlist.h create mode 100644 src/debug.h create mode 100644 src/error.c create mode 100644 src/error.h create mode 100644 src/filetrans.c create mode 100644 src/filetrans.h create mode 100644 src/fonttrans.c create mode 100644 src/fonttrans.h create mode 100644 src/gtk-ink.c create mode 100644 src/gtk-ink.h create mode 100644 src/ink.c create mode 100644 src/ink.h create mode 100644 src/listcache.c create mode 100644 src/listcache.h create mode 100644 src/listcleanup.c create mode 100644 src/listcleanup.h create mode 100644 src/main.c create mode 100644 src/main.h create mode 100644 src/mainwindow.c create mode 100644 src/mainwindow.h create mode 100644 src/messagewindow.c create mode 100644 src/messagewindow.h create mode 100644 src/mime.c create mode 100644 src/mime.h create mode 100644 src/msngenerics.c create mode 100644 src/msngenerics.h create mode 100644 src/msninvite.c create mode 100644 src/msninvite.h create mode 100644 src/msnp11chl.c create mode 100644 src/msnp11chl.h create mode 100644 src/msnp2p.c create mode 100644 src/msnp2p.h create mode 100644 src/msnprotocol.c create mode 100644 src/msnprotocol.h create mode 100644 src/options.c create mode 100644 src/options.h create mode 100644 src/prefswindow.c create mode 100644 src/prefswindow.h create mode 100644 src/reversewindow.c create mode 100644 src/reversewindow.h create mode 100644 src/routines.c create mode 100644 src/routines.h create mode 100644 src/sbprotocol.c create mode 100644 src/sbprotocol.h create mode 100644 src/sbsessions.c create mode 100644 src/sbsessions.h create mode 100644 src/statusicons.c create mode 100644 src/statusicons.h create mode 100644 src/twnauth.c create mode 100644 src/twnauth.h create mode 100644 src/xml.c create mode 100644 src/xml.h create mode 100644 tuxmessenger.desktop 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 + +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 + +--------------------------------------------------------------------------- + +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 ], [ 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 ], , + [ 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 Binary files /dev/null and b/data/hourglass.png differ diff --git a/data/icon.png b/data/icon.png new file mode 100644 index 0000000..98984da Binary files /dev/null and b/data/icon.png 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/no_avatar.png b/data/no_avatar.png new file mode 100644 index 0000000..038edc8 Binary files /dev/null and b/data/no_avatar.png 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 Binary files /dev/null and b/data/paper.png 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 + * 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 +#endif + +#include + +#include "debug.h" + +void about_open() { + +#ifdef HAVE_GTK_2_6_0 + GtkWidget *window; + const gchar *authors[] = { + "Thomas White ", + "", + "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 "); + 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 + * 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 + * 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 +#endif + +#include +#include +#include + +#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 + * 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 + * 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 +#endif + +#include +#include +#include +#include + +#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 + * 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 + * 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 +#endif + +#include +#include +#include +#include +#include + +#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, "", 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 + * 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 diff --git a/src/blockwindow.h b/src/blockwindow.h new file mode 100644 index 0000000..e69de29 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 + * 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 +#endif + +#include +#include +#include +#include + +#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 + * 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 + +#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 + * 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 +#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 + * 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 +#endif + +#include +#include + +#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 + * 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 + * 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 +#endif + +#include + +#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 + * 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 + * 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 +#endif + +#include +#include +#include + +#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 + * 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 + * 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 +#endif + +#include +#include +#include + +#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; xwidth/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(), >k_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 + * 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 +#include + +#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 + * 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 +#endif + +#include +#include +#include + +#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 + * 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 + * 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 +#endif + +#include +#include +#include +#include +#include +#include + +#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 = ""; + 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 + * 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 + * 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 +#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 + * 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 + * 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 +#endif + +#include +#include +#include +#include +#include +#include + +#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 + * 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 +#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 + * 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 +#endif + +#include +#include +#include +#include +#include +#include + +#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, "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", "O", NULL, ONLINE_NLN }, + { "StatusAWYAction", NULL, "_Away", "A", NULL, ONLINE_AWY }, + { "StatusBSYAction", NULL, "_Busy", NULL, NULL, ONLINE_BSY }, + { "StatusBRBAction", NULL, "Be _Right Back", "B", NULL, ONLINE_BRB }, + { "StatusPHNAction", NULL, "On The _Phone", NULL, NULL, ONLINE_PHN }, + { "StatusLUNAction", NULL, "Out To _Lunch", "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, "%s", 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, "%s%s %s", + 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 + * 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 +/* 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 + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; inum_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), "Font and Colour for Your Messages"); + 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), "Fonts and Colours for Contacts' Messages"); + 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 + * 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 + * 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 +#endif + +#include +#include +#include + +#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= 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 + * + * 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 + * 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 +#endif + +#include +#include + +#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 + * 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 + * 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 +#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 + * 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 + * 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 +#endif + +#include +#include +#include +#include +#include +#include + +#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 + * 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 + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#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: username, 64); + strcat(msnslp_block, ">\r\nFrom: \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 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, "\" - 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: username, 64); + strcat(msnslp_block, ">\r\nFrom: \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: \r\nFrom: \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: \r\nFrom: \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 + * 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 + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 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.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 = ""; + 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 + * + * 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 +#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 + * 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 +#endif + +#include +#include +#include +#include +#include +#include + +#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 + * 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 + +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 + * 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 +#endif + +#include +#include +#include +#include + +#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), "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."); + 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), "Font and Colour for Your Messages"); + 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), "Fonts and Colours for Contacts' Messages"); + 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), "Text and Window Layout"); + 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), "Command for 'wget'"); + 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), "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."); + 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 + * 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 diff --git a/src/reversewindow.h b/src/reversewindow.h new file mode 100644 index 0000000..e69de29 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 + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#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' ) { + 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> 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 + * 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 + +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 + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; iwbufsize; 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; icached; + 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; imessagewindow, 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; iroffset-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 + * 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 + * 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 +#endif + +#include +#include +#include +#include + +#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 + * 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 + +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 + * 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 +#endif + +#include +#include +#include + +#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 + * 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 + * 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 +#endif + +#include +#include +#include +#include + +#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&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 + * 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 + * 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 +#endif + +#include +#include +#include +#include + +#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; iname, 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 + * 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 + +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; + -- cgit v1.2.3