diff options
author | Thomas White <taw@bitwiz.org.uk> | 2009-07-17 12:46:27 +0100 |
---|---|---|
committer | Thomas White <taw@bitwiz.org.uk> | 2009-07-17 12:46:27 +0100 |
commit | 9ae0abe3414ea26f83fe3e01a37c3cd4819a82b9 (patch) | |
tree | d9e87a4b4bc035132a7e93b71c97ba90f257faec |
Initial import
91 files changed, 18299 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bcea043 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +config.h +config.h.in +config.h.in~ +config.log +config.status +config +configure +libesv/.deps +libesv/.libs +libesv/Makefile +libesv/Makefile.in +libesv/esvseries.lo +libesv/esvseries.o +libesv/libesv.la +libtool +src/.deps +src/.libs +src/Makefile +src/Makefile.in +src/*.o +src/tuxmessenger +stamp-h +stamp-h1 +stamp-h.in @@ -0,0 +1,21 @@ +Copyright (C) 2002-2005 Thomas White <taw27@srcf.ucam.org> + +Thanks are due to: + + Andrew Millar + Thomas Arbesser-Rastburg + (for testing the client itself, especially on various non-x86/Linux platforms). + + Matthew Mayer + James Bell + (for assistance in testing various features). + + Barnaby Gray + (for permission to use certain graphics, though not included since version 2.0.0d). + + ZoRoNaX + bugy + (for massively helping me with Display Pictures and MSNSLP file transfer). + + Hiroyuki Yamamoto + (for some UI routines borrowed from the (wonderful) email client "Sylpheed"). @@ -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. @@ -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 @@ -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. @@ -0,0 +1,76 @@ +--------------------------------------------------------------------------- + +TuxMessenger + - a GTK+-based client for Microsoft MSN Messenger service + + Version 2.0.10 + +By Thomas White <taw27@srcf.ucam.org> + +--------------------------------------------------------------------------- + +Copyright (c) Thomas White, 2002-2005, except where otherwise stated. +Please see the "AUTHORS" file for a full list of credits and thanks. + + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 dated June, 1991. + + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this package; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. + +-------------------------------------------------------------------------- + +Installation follows the standard procedure for installing programs from source. +See the "INSTALL" file for the ugly details, otherwise follow these simple steps +at a command prompt. + +1) Unpack the source code tarball: tar -xzvf tuxmessenger-2.0.0d.tar.gz + +2) Change to the top level of the source tree: cd tuxmessenger-2.0.0d + +3) Run "./configure" to check the build environment. You will need various + libraries installed, including their C header files ("Development" + packages). You will be told if something is missing. + +4) Type "make" to compile the program. + +5) Become root, then type "make install". + +6) Type "tuxmessenger" to run. + +Should you wish to remove the program, simply "make uninstall". You may also +wish to erase TuxMessenger's configuration files: "rm -rf ~/.tuxmessenger". + +Unlike with other MSN clients, you do not need to mess around with SSL or +other cryptography libraries. TuxMessenger invokes "wget" to do the same +job. Accordingly, you'll need a copy of "wget" which can retrieve "https:" +URLs. To test if a suitable wget is installed or not, type the following +at a shell prompt: + + wget https://www.google.com/ + +If all is well, the last line of the output produced should contain +"`index.html' saved". If you get an "Unsupported scheme." message, +you have wget installed but it doesn't understand https. In this case, +you'll have to get hold of a suitable "wget". This *might* mean compiling +one yourself. + +Whatever "wget" you use, you'll need to tell TuxMessenger about it's +location in the filesystem. To find out the relevant location, type this +in a shell: + + which wget + +If you've had to find a special wget for this, make a note of the location +you installed it to. Enter the location information under the "General" tab +in the Preferences window. + +Problems / questions / suggestions / bug reports to taw27@srcf.ucam.org @@ -0,0 +1,67 @@ +Blatent Bugs +------------ + +* Big fat crash on receiving a P2P Ink message. +* Big fat crash for CSCA :/ +* Failed assertion (messagewindow.c:1241) on DnDing a user to a "dead" multi-way conversation + +Annoyances +---------- + +* There's a repeating MSNP2P packet with Flags=4 from the Official Client + after it receives the display picture from TM2. + - "Waiting for response". +* Signedness issues. + +To Do List +---------- + +* "List Cleanup". + - UI parts. +* UI for setting DP. +* Contact list menu (Message, Send File?, Block, Unblock, Delete). +* Allow/Block list support. + - "Block" message window button. + - Tools->Block User. + - Different contact list icons when blocked. +* File transfer + - MSNSLP. + - "Synergise" MSNP2P multipacket reception. + - MSNFTP. +* Change \n to \r\n in outgoing messages. +* Invite users to conversations via Ugly Method (Conversation->Add). +* Update message window titles when friendlynames change. + - Display number of participants in message window title bar. +* Auto-reconnect. +* IRC-style message window output. +* Timestamp messages. +* Sort contact list. + +# 2.1.0 "release". + +* Contact list groups (Redesign contact list UI?). +* Emoticons. +* Ink messages. +* Set P4-Context friendly name + - Per-IM-window? +* Useful log information window. + +Stuff that isn't my fault +------------------------- + +* Resize main window during sign-in ---> nastiness. + - GTK bug? + - Happens with GTK 2.0.2 + - Reported not to happen with GTK 2.2.4 + - Doesn't happen with GTK 2.6.4 + - Clicking on contact "Buttons" redraws them. +* Gaim and other libgaim-based clients close the SB session + after completing an avatar transfer. +* There isn't a way to recover the CSM from the server. + +Features That Aren't Going To Happen +------------------------------------ + +* Animated emoticons or avatars. +* Networks other than MSN. +* Custom emoticons. (?) diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..4960d71 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +aclocal +autoconf +autoheader +automake -a diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..25dbbed --- /dev/null +++ b/configure.ac @@ -0,0 +1,60 @@ +dnl Process this file with autoconf to produce a configure script. +AC_INIT(TuxMessenger, 2.0.13, taw27@srcf.ucam.org) + +dnl Version number +VERSION=AC_PACKAGE_VERSION + +AM_CONFIG_HEADER(config.h) +AM_INIT_AUTOMAKE(tuxmessenger, "$VERSION") + +OLD_CFLAGS="$CFLAGS" +AC_PROG_CC +CFLAGS="$OLD_CFLAGS" + +AC_PROG_AWK +AC_PROG_INSTALL +AC_PROG_LN_S + +AC_HEADER_STDC +AC_CHECK_HEADERS([fcntl.h netdb.h netinet/in.h stdlib.h string.h sys/socket.h unistd.h]) +AC_C_CONST +AC_FUNC_MALLOC +AC_TYPE_SIGNAL +AC_TYPE_SIZE_T +AC_FUNC_STAT +AC_FUNC_REALLOC +AC_CHECK_FUNCS([gethostbyname memmove memset socket strdup strstr strcasecmp mkdir]) + +dnl Check for GTK+ 2.6.0 (prefered version, but not mandatory) +AM_PATH_GTK_2_0(2.6.0,AC_DEFINE(HAVE_GTK_2_6_0, TRUE, [Have GTK 2.6.0?]),AC_MSG_WARN([ +*** Didn't find GTK 2.6.0 or above - some (insignificant) features will be disabled...])) + +dnl Now check for GTK+ 2.4.0 (minimum acceptable version) +dnl This has to be done after checking for 2.6.x in order to have the flags set up. +AM_PATH_GTK_2_0(2.4.0,,AC_MSG_ERROR([ +*** GTK+ 2.4.0 or above is required by TuxMessenger. Please make sure you have the GTK+ +*** development files installed. The latest version of GTK+ is +*** always available at http://www.gtk.org/.])) + +AM_PATH_XML2(2.5.0,,AC_MSG_ERROR([ +*** Libxml is required to build TuxMessenger; Available at +http://www.libxml.org/.])) + +dnl Check for OpenSSL +AC_MSG_CHECKING([if openssl is available]) +LIBS="$LIBS -lssl -lcrypto" +AC_TRY_LINK([#include <openssl/opensslv.h>], [ return OPENSSL_VERSION_NUMBER; ], + [ AC_MSG_RESULT(yes) ], + [ AC_MSG_RESULT(no) + AC_MSG_ERROR([*** Couldn't find OpenSSL ***]) ] ) + +dnl Check for GNOME-Canvas +AC_MSG_CHECKING([for GNOME-Canvas]) +CFLAGS="$CFLAGS `pkg-config --cflags libgnomecanvas-2.0`" +LIBS="$LIBS `pkg-config --libs libgnomecanvas-2.0`" +AC_TRY_LINK([#include <libgnomecanvas/libgnomecanvas.h>], , + [ AC_MSG_RESULT(found) ], + [ AC_MSG_RESULT(not found) + AC_MSG_ERROR([*** Couldn't find GNOME-Canvas ***]) ] ) + +AC_OUTPUT(Makefile src/Makefile data/Makefile) diff --git a/data/Makefile.am b/data/Makefile.am new file mode 100644 index 0000000..e236b17 --- /dev/null +++ b/data/Makefile.am @@ -0,0 +1,4 @@ +tuxmessengerdir = $(datadir)/tuxmessenger +tuxmessenger_DATA = online.xpm away.xpm occ.xpm offline.xpm \ + online-blocked.xpm away-blocked.xpm occ-blocked.xpm offline-blocked.xpm \ + no_avatar.png hourglass.png icon.png mainwindow.ui imwindow.ui paper.png diff --git a/data/away-blocked.xpm b/data/away-blocked.xpm new file mode 100644 index 0000000..eaf183f --- /dev/null +++ b/data/away-blocked.xpm @@ -0,0 +1,48 @@ +/* XPM */ +static char * away_blocked_xpm[] = { +"16 16 29 1", +" c None", +". c #FF0000", +"+ c #E8EF00", +"@ c #E1E700", +"# c #DADF00", +"$ c #D3D800", +"% c #CCD001", +"& c #C5C801", +"* c #BEC001", +"= c #B7B801", +"- c #B0B101", +"; c #AAA902", +"> c #A3A102", +", c #9C9902", +"' c #959202", +") c #8E8A02", +"! c #878202", +"~ c #807A03", +"{ c #797203", +"] c #726B03", +"^ c #6B6303", +"/ c #645B03", +"( c #5E5304", +"_ c #574C04", +": c #504404", +"< c #493C04", +"[ c #423404", +"} c #3B2C05", +"| c #342505", +"..+@#$%&*=-;>,')", +"...#$%&*=-;>,')!", +"+...%&*=-;>,')!~", +"@#...*=-;>,')!~{", +"#$%...-;>,')!~{]", +"$%&*...>,')!~{]^", +"%&*=-...')!~{]^/", +"&*=-;>...!~{]^/(", +"*=-;>,'...{]^/(_", +"=-;>,')!...^/(_:", +"-;>,')!~{...(_:<", +";>,')!~{]^...:<[", +">,')!~{]^/(...[}", +",')!~{]^/(_:...|", +"')!~{]^/(_:<[...", +")!~{]^/(_:<[}|.."}; diff --git a/data/away.xpm b/data/away.xpm new file mode 100644 index 0000000..ff09dc6 --- /dev/null +++ b/data/away.xpm @@ -0,0 +1,51 @@ +/* XPM */ +static char * away_xpm[] = { +"16 16 32 1", +" c None", +". c #F6FF00", +"+ c #EFF700", +"@ c #E8EF00", +"# c #E1E700", +"$ c #DADF00", +"% c #D3D800", +"& c #CCD001", +"* c #C5C801", +"= c #BEC001", +"- c #B7B801", +"; c #B0B101", +"> c #AAA902", +", c #A3A102", +"' c #9C9902", +") c #959202", +"! c #8E8A02", +"~ c #878202", +"{ c #807A03", +"] c #797203", +"^ c #726B03", +"/ c #6B6303", +"( c #645B03", +"_ c #5E5304", +": c #574C04", +"< c #504404", +"[ c #493C04", +"} c #423404", +"| c #3B2C05", +"1 c #342505", +"2 c #2D1D05", +"3 c #261505", +".+@#$%&*=-;>,')!", +"+@#$%&*=-;>,')!~", +"@#$%&*=-;>,')!~{", +"#$%&*=-;>,')!~{]", +"$%&*=-;>,')!~{]^", +"%&*=-;>,')!~{]^/", +"&*=-;>,')!~{]^/(", +"*=-;>,')!~{]^/(_", +"=-;>,')!~{]^/(_:", +"-;>,')!~{]^/(_:<", +";>,')!~{]^/(_:<[", +">,')!~{]^/(_:<[}", +",')!~{]^/(_:<[}|", +"')!~{]^/(_:<[}|1", +")!~{]^/(_:<[}|12", +"!~{]^/(_:<[}|123"}; diff --git a/data/hourglass.png b/data/hourglass.png Binary files differnew file mode 100644 index 0000000..73c77f7 --- /dev/null +++ b/data/hourglass.png diff --git a/data/icon.png b/data/icon.png Binary files differnew file mode 100644 index 0000000..98984da --- /dev/null +++ b/data/icon.png diff --git a/data/imwindow.ui b/data/imwindow.ui new file mode 100644 index 0000000..af3a8ec --- /dev/null +++ b/data/imwindow.ui @@ -0,0 +1,32 @@ +<ui> + <menubar name="imwindow"> + + <menu name="conversation" action="ConversationAction"> + <menuitem name="invite_user" action="InviteAction" /> + <separator /> + <menuitem name="mode_text" action="ModeTextAction" /> + <menuitem name="mode_ink" action="ModeInkAction" /> + <separator /> + <menuitem name="close" action="CloseAction" /> + </menu> + + <menu name="view" action="ViewAction"> + <menuitem name="font" action="FontAction" /> + <menuitem name="irc_style" action="IRCStyleAction" /> + <menuitem name="timestamp" action="TimeStampAction" /> + <menuitem name="display_avatars" action="AvatarsAction" /> + <menuitem name="emoticons" action="EmoticonsAction" /> + </menu> + + <menu name="tools" action="ToolsAction"> + <menuitem name="nudge" action="NudgeAction" /> + <menuitem name="block_user" action="BlockAction" /> + <menuitem name="send_file" action="SendFileAction" /> + </menu> + + <menu name="help" action="HelpAction"> + <menuitem name="about" action="AboutAction" /> + </menu> + + </menubar> +</ui> diff --git a/data/mainwindow.ui b/data/mainwindow.ui new file mode 100644 index 0000000..b7c7ae1 --- /dev/null +++ b/data/mainwindow.ui @@ -0,0 +1,57 @@ +<ui> + <menubar name="mainwindow"> + + <menu name="connection" action="ConnectionAction"> + <menuitem name="sign_in" action="SignInAction" /> + <menuitem name="account" action="AccountAction" /> + <separator /> + <menuitem name="sign_out" action="SignOutAction" /> + <menuitem name="quit" action="QuitAction" /> + </menu> + + <menu name="view" action="ViewAction"> + <menuitem name="show_offline" action="ShowOfflineAction" /> + <menuitem name="show_groups" action="ShowGroupsAction" /> + <menuitem name="show_csm" action="ShowCSMAction" /> + <separator /> + <menuitem name="sort_alphabet" action="SortAlphaAction" /> + <menuitem name="sort_activity" action="SortActiveAction" /> + </menu> + + <menu name="status" action="StatusAction"> + <menuitem name="status_nln" action="StatusNLNAction" /> + <menuitem name="status_awy" action="StatusAWYAction" /> + <menuitem name="status_bsy" action="StatusBSYAction" /> + <menuitem name="status_brb" action="StatusBRBAction" /> + <menuitem name="status_phn" action="StatusPHNAction" /> + <menuitem name="status_lun" action="StatusLUNAction" /> + <menuitem name="status_hdn" action="StatusHDNAction" /> + <separator /> + <menuitem name="change_name" action="ChangeNameAction" /> + <menuitem name="change_csm" action="ChangeCSMAction" /> + <menuitem name="change_avatar" action="ChangeAvatarAction" /> + </menu> + + <menu name="tools" action="ToolsAction"> + <menuitem name="add_contact" action="AddContactAction" /> + <menuitem name="blockallow_lists" action="BlockAllowAction" /> + <menuitem name="reverse_list" action="ReverseAction" /> + <menuitem name="list_cleanup" action="ListCleanupAction" /> + <separator /> + <menuitem name="preferences" action="PreferencesAction" /> + </menu> + + <menu name="help" action="HelpAction"> + <menuitem name="about" action="AboutAction" /> + </menu> + </menubar> + + <popup name="contact"> + <menuitem name="message" action="ContactMessageAction" /> + <menuitem name="sendfile" action="ContactSendFileAction" /> + <menuitem name="block" action="ContactBlockAction" /> + <menuitem name="unblock" action="ContactUnblockAction" /> + <menuitem name="delete" action="ContactDeleteAction" /> + </popup> + +</ui> diff --git a/data/no_avatar.png b/data/no_avatar.png Binary files differnew file mode 100644 index 0000000..038edc8 --- /dev/null +++ b/data/no_avatar.png 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 Binary files differnew file mode 100644 index 0000000..7626e8e --- /dev/null +++ b/data/paper.png diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..5578b7b --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,9 @@ +bin_PROGRAMS = tuxmessenger +tuxmessenger_SOURCES = about.c listcache.c mainwindow.c msnprotocol.c sbprotocol.c \ + addcontact.c contactlist.c listcleanup.c messagewindow.c options.c sbsessions.c twnauth.c \ + error.c main.c msngenerics.c routines.c statusicons.c avatars.c mime.c xml.c \ + msnp2p.c msninvite.c accountwindow.c prefswindow.c blockwindow.c reversewindow.c ink.c \ + msnp11chl.c gtk-ink.c fonttrans.c filetrans.c +tuxmessenger_LDADD = @LIBS@ @GTK_LIBS@ @XML_LIBS@ +AM_CFLAGS = -Wall -g @CFLAGS@ @GTK_CFLAGS@ @XML_CPPFLAGS@ +AM_CPPFLAGS = -DDATADIR=\""$(datadir)"\" diff --git a/src/about.c b/src/about.c new file mode 100644 index 0000000..4210f16 --- /dev/null +++ b/src/about.c @@ -0,0 +1,81 @@ +/* + * about.c + * + * The "About TuxMessenger" box + * + * (c) 2002-2007 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> + +#include "debug.h" + +void about_open() { + +#ifdef HAVE_GTK_2_6_0 + GtkWidget *window; + const gchar *authors[] = { + "Thomas White <taw27@srcf.ucam.org>", + "", + "Thanks to:", + "Andrew Millar", + "Thomas Arbesser-Rastburg", + "Ben Whitehead", + "Marielle Vonk", + "Siebe Tolsma", + "Kevin Lu", + "Matthew Mayer", + "Neill Horie", + "Barnaby Gray", + "Hiroyuki Yamamoto", + NULL + }; + + window = gtk_about_dialog_new(); + + gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(window), PACKAGE_NAME); + gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(window), PACKAGE_VERSION); + gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(window), "(c) 2002-2007 Thomas White <taw27@srcf.ucam.org>"); + gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(window), "MSN Messenger Client"); + gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(window), "This package is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation; version 2 dated June, 1991.\n\n" + "This package is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n\n" + "You should have received a copy of the GNU General Public License\n" + "along with this package; if not, write to the Free Software\n" + "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA\n" + "02111-1307, USA.\n"); + gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(window), "http://www.srcf.ucam.org/~taw27/"); + gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(window), authors); + + g_signal_connect(window, "response", G_CALLBACK(gtk_widget_destroy), NULL); + + gtk_widget_show_all(window); +#else /* HAVE_GTK_2_6_0 */ + debug_print("AB: About window doesn't work without GTK 2.6.0 :(\n"); +#endif /* HAVE_GTK_2_6_0 */ + +} diff --git a/src/about.h b/src/about.h new file mode 100644 index 0000000..ee7448d --- /dev/null +++ b/src/about.h @@ -0,0 +1,30 @@ +/* + * about.h + * + * The "About TuxMessenger" box + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef ABOUT_H +#define ABOUT_H + +extern void about_open(); + +#endif /* ABOUT_H */ diff --git a/src/accountwindow.c b/src/accountwindow.c new file mode 100644 index 0000000..dae37af --- /dev/null +++ b/src/accountwindow.c @@ -0,0 +1,206 @@ +/* + * accountwindow.c + * + * The "set account details" window + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <stdlib.h> +#include <string.h> + +#include "debug.h" +#include "options.h" +#include "mainwindow.h" +#include "routines.h" +#include "error.h" + +static int accountwindow_isopen = 0; + +static int accountwindow_validate_username(GtkWidget *widget) { + + options_setusername(gtk_entry_get_text(GTK_ENTRY(widget))); + mainwindow_kickdispatch(); + return FALSE; + +} + +static int accountwindow_validate_password(GtkWidget *widget) { + + options_setpassword(gtk_entry_get_text(GTK_ENTRY(widget))); + mainwindow_kickdispatch(); + return FALSE; + +} + +static int accountwindow_validate_server(GtkWidget *widget) { + + char *newserver; + const char *server = gtk_entry_get_text(GTK_ENTRY(widget)); + char *hostname = routines_hostname(server); + + options_sethostname(hostname); + free(hostname); + options_setport(routines_port(server)); + + /* options_sethostname and options_setport will change the values if they're obviously wrong. */ + newserver = malloc(strlen(options_hostname())+6); + /* options_port() is "unsigned short int" so max 16384 = 5 chars */ + sprintf(newserver, "%s:%i", options_hostname(), options_port()); + gtk_entry_set_text(GTK_ENTRY(widget), newserver); + free(newserver); + + mainwindow_kickdispatch(); + + return FALSE; + +} + +static int accountwindow_validate_remember(GtkWidget *widget) { + + int remember; + + g_object_get(G_OBJECT(widget), "active", &remember, NULL); + debug_print("AW: remember: %i\n", remember); + options_setrememberlogindetails(remember); + + return FALSE; + +} + +static int accountwindow_closed() { + + accountwindow_isopen = 0; + options_save(); + + if ( strlen(options_username()) == 0 ) { + error_report("You must enter a username!"); + } + + return FALSE; + +} + +void accountwindow_open() { + + char *hostnameport; + GtkWidget *window; + GtkWidget *border_hbox; + GtkWidget *border_vbox; + + GtkWidget *username; + GtkWidget *username_justify; + GtkWidget *username_hbox; + GtkWidget *username_spacer; + + GtkWidget *password; + GtkWidget *password_justify; + GtkWidget *password_hbox; + GtkWidget *password_spacer; + + GtkWidget *server; + GtkWidget *server_justify; + GtkWidget *server_hbox; + GtkWidget *server_spacer; + + GtkWidget *remember; + + if ( accountwindow_isopen != 0 ) { + debug_print("AW: Account details window already open.\n"); + return; + } + + window = gtk_dialog_new_with_buttons("Configure Account Details", mainwindow_gtkwindow(), 0, GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL); + border_hbox = gtk_hbox_new(FALSE, 0); + border_vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(border_hbox), border_vbox, FALSE, FALSE, 6); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), border_hbox, FALSE, FALSE, 6); + + /* "Username" entry box */ + username_hbox = gtk_hbox_new(FALSE, 0); + username_justify = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(username_justify), gtk_label_new("Username:"), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(username_hbox), username_justify, FALSE, FALSE, 0); + username = gtk_entry_new_with_max_length(255); + gtk_box_pack_end(GTK_BOX(username_hbox), username, FALSE, FALSE, 0); + username_spacer = gtk_label_new(""); + gtk_widget_set_size_request(username_spacer, 12, -1); + gtk_box_pack_end(GTK_BOX(username_hbox), username_spacer, FALSE, FALSE, 0); + g_object_set(G_OBJECT(username), "width-chars", 32, NULL); + g_signal_connect(G_OBJECT(username), "focus-out-event", GTK_SIGNAL_FUNC(accountwindow_validate_username), NULL); + g_signal_connect(G_OBJECT(username), "activate", GTK_SIGNAL_FUNC(accountwindow_validate_username), NULL); + gtk_box_pack_start(GTK_BOX(border_vbox), username_hbox, FALSE, FALSE, 6); + + /* "Password" entry box */ + password_hbox = gtk_hbox_new(FALSE, 0); + password_justify = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(password_justify), gtk_label_new("Password:"), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(password_hbox), password_justify, FALSE, FALSE, 0); + password = gtk_entry_new_with_max_length(255); + gtk_box_pack_end(GTK_BOX(password_hbox), password, FALSE, FALSE, 0); + password_spacer = gtk_label_new(""); + gtk_widget_set_size_request(password_spacer, 12, -1); + gtk_box_pack_end(GTK_BOX(password_hbox), password_spacer, FALSE, FALSE, 0); + g_object_set(G_OBJECT(password), "width-chars", 32, NULL); + gtk_entry_set_visibility(GTK_ENTRY(password), FALSE); + g_signal_connect(G_OBJECT(password), "focus-out-event", GTK_SIGNAL_FUNC(accountwindow_validate_password), NULL); + g_signal_connect(G_OBJECT(password), "activate", GTK_SIGNAL_FUNC(accountwindow_validate_password), NULL); + gtk_box_pack_start(GTK_BOX(border_vbox), password_hbox, FALSE, FALSE, 6); + + /* "Server" entry box */ + server_hbox = gtk_hbox_new(FALSE, 0); + server_justify = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(server_justify), gtk_label_new("Server:"), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(server_hbox), server_justify, FALSE, FALSE, 0); + server = gtk_entry_new_with_max_length(255); + gtk_box_pack_end(GTK_BOX(server_hbox), server, FALSE, FALSE, 0); + server_spacer = gtk_label_new(""); + gtk_widget_set_size_request(server_spacer, 12, -1); + gtk_box_pack_end(GTK_BOX(server_hbox), server_spacer, FALSE, FALSE, 0); + g_object_set(G_OBJECT(server), "width-chars", 32, NULL); + g_signal_connect(G_OBJECT(server), "focus-out-event", GTK_SIGNAL_FUNC(accountwindow_validate_server), NULL); + g_signal_connect(G_OBJECT(server), "activate", GTK_SIGNAL_FUNC(accountwindow_validate_server), NULL); + gtk_box_pack_start(GTK_BOX(border_vbox), server_hbox, FALSE, FALSE, 6); + + /* Rememer login details check-box */ + remember = gtk_check_button_new_with_label("Remember login details"); + gtk_box_pack_start(GTK_BOX(border_vbox), remember, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(remember), "toggled", GTK_SIGNAL_FUNC(accountwindow_validate_remember), NULL); + + /* Fill the fields in... */ + gtk_entry_set_text(GTK_ENTRY(username), options_username()); + gtk_entry_set_text(GTK_ENTRY(password), options_password()); + hostnameport = malloc(strlen(options_hostname())+6); + /* options_port() < 65536 because of data size - so always fits. */ + sprintf(hostnameport, "%s:%i", options_hostname(), options_port()); + gtk_entry_set_text(GTK_ENTRY(server), hostnameport); + free(hostnameport); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remember), options_rememberlogindetails()); + + gtk_widget_show_all(window); + g_signal_connect(G_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(accountwindow_closed), NULL); + g_signal_connect(G_OBJECT(window), "response", GTK_SIGNAL_FUNC(gtk_widget_destroy), NULL); + accountwindow_isopen = 1; + +} diff --git a/src/accountwindow.h b/src/accountwindow.h new file mode 100644 index 0000000..fe7cdad --- /dev/null +++ b/src/accountwindow.h @@ -0,0 +1,30 @@ +/* + * accountwindow.h + * + * The "set account details" window + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef ACCOUNTWINDOW_H +#define ACCOUNTWINDOW_H + +extern void accountwindow_open(void); + +#endif /* ACCOUNTWINDOW_H */ diff --git a/src/addcontact.c b/src/addcontact.c new file mode 100644 index 0000000..91cfd8f --- /dev/null +++ b/src/addcontact.c @@ -0,0 +1,299 @@ +/* + * addcontact.c + * + * UI parts of adding new contacts, and being added yourself. + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "mainwindow.h" +#include "debug.h" +#include "error.h" +#include "msnprotocol.h" +#include "routines.h" +#include "contactlist.h" + +typedef struct stru_addedwindow { + + struct stru_addedwindow *next; + GtkWidget *window; + char *username; + GtkWidget *allow_radio; + GtkWidget *block_radio; + GtkWidget *forward_toggle; + +} AddedWindow; + +typedef struct { + + GtkWidget *window; + GtkWidget *allow_toggle; + GtkWidget *username_entry; + +} AddContactWindow; + +static AddedWindow *addedwindows_list = NULL; + +static int addcontact_destroyed(GtkWidget *widget, AddContactWindow *item) { + free(item); + return 0; +} + +static int addcontact_response(GtkWidget *widget, gint response, AddContactWindow *item) { + + const char *username; + + if ( response == GTK_RESPONSE_REJECT ) { + return 0; + } + + if ( msnprotocol_signedin() ) { + + username = gtk_entry_get_text(GTK_ENTRY(item->username_entry)); + debug_print("AC: Adding new contact: '%s'\n", username); + + if ( strstr(username, "@") == NULL ) { + /* Err... nope. */ + error_report("Invalid Passport address."); + return 0; + } + + msnprotocol_adduserfriendly(username, username, "FL"); + if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(item->allow_toggle)) ) { + msnprotocol_adduser(username, "AL"); + } + + } else { + + error_report("Try again when you're signed in."); + + } + + return 0; + +} + +static int addcontact_activate(GtkWidget *widget, AddContactWindow *item) { + + addcontact_response(item->window, GTK_RESPONSE_ACCEPT, item); + gtk_widget_destroy(item->window); + + return 0; + +} + +void addcontact_open() { + + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *icon; + AddContactWindow *item; + + item = malloc(sizeof(AddContactWindow)); + + item->window = gtk_dialog_new_with_buttons("Add New Contact", mainwindow_gtkwindow(), GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + + g_signal_connect(item->window, "response", G_CALLBACK(addcontact_response), item); + g_signal_connect_swapped(item->window, "response", G_CALLBACK(gtk_widget_destroy), item->window); + g_signal_connect(item->window, "destroy", G_CALLBACK(addcontact_destroyed), item); + + hbox = gtk_hbox_new(FALSE, 20); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(item->window)->vbox), hbox); + icon = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_DIALOG); + gtk_box_pack_start(GTK_BOX(hbox), icon, TRUE, TRUE, 0); + + vbox = gtk_vbox_new(FALSE, 5); + gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0); + + gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new("Enter the Passport address of your new contact:"), TRUE, TRUE, 0); + + item->username_entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(vbox), item->username_entry, TRUE, TRUE, 0); + g_signal_connect(item->username_entry, "activate", G_CALLBACK(addcontact_activate), item); + + item->allow_toggle = gtk_check_button_new_with_label("Allow this new contact to message you."); + gtk_box_pack_start(GTK_BOX(vbox), item->allow_toggle, TRUE, TRUE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item->allow_toggle), TRUE); + + gtk_container_set_border_width(GTK_CONTAINER(hbox), 10); + gtk_widget_show_all(item->window); + +} + +static int addcontact_addedclose(GtkWidget *widget, gpointer data) { + + AddedWindow *addedwindow = (AddedWindow *)data; + AddedWindow *previous = NULL; + AddedWindow *check = addedwindows_list; + + debug_print("AC: Removing New Contact window for %s\n", addedwindow->username); + + /* Remove from list. */ + while ( check != NULL ) { + if ( check == addedwindow ) { + free(check->username); + if ( previous != NULL ) { + previous->next = check->next; + } else { + /* Was first on the list */ + addedwindows_list = check->next; + } + free(check); + return 0; + } + previous = check; + check = check->next; + } + + return 0; + +} + +static int addcontact_addedresponse(GtkWidget *widget, gint response, gpointer data) { + + AddedWindow *addedwindow = (AddedWindow *)data; + + if ( response == GTK_RESPONSE_REJECT ) { + addcontact_addedclose(widget, data); + return 0; + } + + if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(addedwindow->forward_toggle)) ) { + /* Add to FL. Friendlyname isn't given: it'll arrive soon on NLN. */ + msnprotocol_adduserfriendly(addedwindow->username, addedwindow->username, "FL"); + } + + if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(addedwindow->allow_radio)) ) { + /* Add to AL */ + msnprotocol_adduser(addedwindow->username, "AL"); + } else { + /* Add to BL */ + msnprotocol_adduser(addedwindow->username, "BL"); + } + + /* Remove from PL and add to RL */ + msnprotocol_adduser(addedwindow->username, "RL"); + msnprotocol_remuser(addedwindow->username, "PL"); + + addcontact_addedclose(widget, data); + + return 0; + +} + +void addcontact_added(const char *username, const char *friendlyname_coded) { + + GtkWidget *label; + char *text; + AddedWindow *addedwindow; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *icon; + char *friendlyname; + + debug_print("AC: New contact! %s / %s\n", username, friendlyname_coded); + + addedwindow = malloc(sizeof(AddedWindow)); + addedwindow->next = addedwindows_list; + debug_print("AC: Next = %p\n", addedwindow->next); + addedwindow->username = strdup(username); + addedwindows_list = addedwindow; + + if ( friendlyname_coded != NULL ) { + friendlyname = routines_urldecode(friendlyname_coded); + } else { + friendlyname = NULL; + } + + if ( friendlyname != NULL ) { + text = malloc(45 + strlen(username) + strlen(friendlyname)); + sprintf(text, "'%s' (%s) added you to his/her contact list.", friendlyname, username); + free(friendlyname); + } else { + text = malloc(40 + strlen(username)); + sprintf(text, "'%s' added you to his/her contact list.", username); + } + + addedwindow->window = gtk_dialog_new_with_buttons("New Contact", mainwindow_gtkwindow(), GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + g_signal_connect(addedwindow->window, "response", G_CALLBACK(addcontact_addedresponse), addedwindow); + g_signal_connect_swapped(addedwindow->window, "response", G_CALLBACK(gtk_widget_destroy), addedwindow->window); + g_signal_connect(addedwindow->window, "delete_event", G_CALLBACK(addcontact_addedclose), addedwindow); + + hbox = gtk_hbox_new(FALSE, 20); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(addedwindow->window)->vbox), hbox); + icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG); + gtk_box_pack_start(GTK_BOX(hbox), icon, TRUE, TRUE, 0); + + vbox = gtk_vbox_new(FALSE, 5); + gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0); + + label = gtk_label_new(text); + gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0); + addedwindow->allow_radio = gtk_radio_button_new_with_label(NULL, "Add him/her to your Allow List."); + addedwindow->block_radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(addedwindow->allow_radio), "Block him/her."); + addedwindow->forward_toggle = gtk_check_button_new_with_label("Add him/her to your contact list as well."); + gtk_box_pack_start(GTK_BOX(vbox), addedwindow->allow_radio, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox), addedwindow->block_radio, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox), addedwindow->forward_toggle, TRUE, TRUE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(addedwindow->forward_toggle), TRUE); + + /* Disable "Add to your list as well" option if they're already on the FL. */ + gtk_widget_set_sensitive(GTK_WIDGET(addedwindow->forward_toggle), !contactlist_isonlist("FL", username)); + + gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new("If you press Cancel, you will be asked again the next time you sign in."), TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 10); + + gtk_widget_show_all(addedwindow->window); + free(text); + +} + +void addcontact_closeall() { + + AddedWindow *item = addedwindows_list; + + while ( item != NULL ) { + + AddedWindow *next_item; + + next_item = item->next; + gtk_widget_destroy(item->window); + debug_print("AC: Next = %p\n", next_item); + addcontact_addedclose(NULL, item); + item = next_item; + + } + +} diff --git a/src/addcontact.h b/src/addcontact.h new file mode 100644 index 0000000..0e6cf41 --- /dev/null +++ b/src/addcontact.h @@ -0,0 +1,32 @@ +/* + * addcontact.h + * + * UI parts of adding new contacts, and being added yourself. + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef ADDCONTACT_H +#define ADDCONTACT_H + +extern void addcontact_open(void); +extern void addcontact_added(const char *username, const char *friendlyname); +extern void addcontact_closeall(void); + +#endif /* ADDCONTACT_H */ diff --git a/src/avatars.c b/src/avatars.c new file mode 100644 index 0000000..9bb68ac --- /dev/null +++ b/src/avatars.c @@ -0,0 +1,207 @@ +/* + * avatars.h + * + * Handling of User Display Pictures (but not displaying them) + * N.B. msnp2p.c does most of the work of actually sending + * and receiving them on the network. + * + * (c) 2002-20045Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <assert.h> +#include <openssl/sha.h> + +#include "sbsessions.h" +#include "debug.h" +#include "routines.h" +#include "xml.h" +#include "options.h" + +char *avatars_getlocalfilename(const char *name) { + + char *filename; + char *done; + + filename = malloc(strlen(name)+17); + strcpy(filename, "~/.tuxmessenger/"); + strcat(filename, name); + + done = routines_glob(filename); + free(filename); + + return done; + +} + + +static char *avatars_getfilename(const char *sha1d) { + + char *filename; + char *filename2; + char *done; + + filename = xml_killillegalchars(sha1d); + + filename2 = malloc(strlen(filename)+25); + strcpy(filename2, "~/.tuxmessenger/avatars/"); + strcat(filename2, filename); + free(filename); + + done = routines_glob(filename2); + free(filename2); + + return done; + +} + +/* Check if the picture for a user is already downloaded. Returns the filename if it's here. */ +char *avatars_havepicture(const char *dpobject) { + + char *picture_data_hash; + struct stat stat_buffer; + struct stat *statbuf; + char *dpobject_decoded; + char *full_filename; + + assert(dpobject != NULL); + + dpobject_decoded = routines_urldecode(dpobject); + picture_data_hash = xml_getfield(dpobject_decoded, "SHA1D"); + free(dpobject_decoded); + full_filename = avatars_getfilename(picture_data_hash); + free(picture_data_hash); + if ( full_filename == NULL ) { + return NULL; + } + + /* Check if we already have this picture or not */ + statbuf = &stat_buffer; + debug_print("AV: Looking for '%s': ", full_filename); + if ( stat(full_filename, statbuf) != -1 ) { + debug_print("found!\n"); + return full_filename; + } else { + debug_print("not found.\n"); + } + free(full_filename); + + return NULL; + +} + +/* Return filename of the image to use while downloading a picture. */ +char *avatars_default_fetching() { + + return avatars_getlocalfilename("wait_avatar.png"); + +} + +/* Return filename of default image to use when a user has no picture. */ +char *avatars_default_none() { + + return avatars_getlocalfilename("default_avatar.png"); + +} + +/* Return filename of image to use for local user. */ +char *avatars_local() { + + return avatars_getlocalfilename("avatar.png"); + +} + +char *avatars_localobject() { + + char *msnobject; + char *msnobject_coded; + char *sha1c_b64; + char *sha1d_b64; + char *sha1c; + char *sha1d; + struct stat stat_buffer; + struct stat *statbuf; + void *picture_data; + size_t size; + FILE *fh; + char *filename; + + filename = avatars_local(); + if ( filename == NULL ) { + debug_print("AV: No avatar found.\n"); + return strdup("0"); + } + + statbuf = &stat_buffer; + if ( stat(filename, statbuf) == -1 ) { + debug_print("AV: No avatar found.\n"); + return strdup("0"); + } + + size = (int)statbuf->st_size; + + fh = fopen(filename, "r"); + picture_data = malloc(size); + if ( fread(picture_data, size, 1, fh) < 0 ) { + debug_print("AV: Couldn't open avatar file.\n"); + return strdup("0"); + } + fclose(fh); + sha1d = SHA1(picture_data, size, NULL); + sha1d_b64 = routines_base64givenlength(sha1d, 20); + free(picture_data); + + msnobject = malloc(256); + sprintf(msnobject, "Creator%sSize%iType3LocationTFR6B.tmpFriendlyAAA=SHA1D%s", options_username(), size, sha1d_b64); + sha1c = SHA1(msnobject, strlen(msnobject), NULL); + sha1c_b64 = routines_base64givenlength(sha1c, 20); + + debug_print("AV: Avatar: %s - %i bytes\n", filename, size); + debug_print("AV: SHA1D: %s\n", sha1d_b64); + + sprintf(msnobject, "<msnobj Creator=\"%s\" Size=\"%i\" Type=\"3\" Location=\"TFR6B.tmp\" Friendly=\"AAA=\" SHA1D=\"%s\" SHA1C=\"%s\"/>", options_username(), size, sha1d_b64, sha1c_b64); + free(sha1d_b64); + free(sha1c_b64); + + msnobject_coded = routines_urlencode(msnobject); + free(msnobject); + free(filename); + + return msnobject_coded; + +} + +char *avatars_unwrapsha1d(const char *dpobject) { + + char *dpobject_decoded; + char *data_hash; + + dpobject_decoded = routines_urldecode(dpobject); + data_hash = xml_getfield(dpobject_decoded, "SHA1D"); + free(dpobject_decoded); + + return data_hash; + +} diff --git a/src/avatars.h b/src/avatars.h new file mode 100644 index 0000000..d9193e4 --- /dev/null +++ b/src/avatars.h @@ -0,0 +1,36 @@ +/* + * avatars.h + * + * Handling of User Display Pictures (but not displaying them) + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef AVATARS_H +#define AVATARS_H + +extern char *avatars_havepicture(const char *username); +extern char *avatars_default_none(); +extern char *avatars_default_fetching(); +extern char *avatars_local(); +extern char *avatars_localobject(); +extern char *avatars_unwrapsha1d(const char *dpobject); +extern char *avatars_getlocalfilename(const char *name); + +#endif /* AVATARS_H */ diff --git a/src/blockwindow.c b/src/blockwindow.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/blockwindow.c diff --git a/src/blockwindow.h b/src/blockwindow.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/blockwindow.h diff --git a/src/contactlist.c b/src/contactlist.c new file mode 100644 index 0000000..03e2a53 --- /dev/null +++ b/src/contactlist.c @@ -0,0 +1,901 @@ +/* + * contactlist.c + * + * Contact list (FL, BL, AL and RL) data structures + * + * (c) 2002-2006 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> + +#include "debug.h" +#include "contactlist.h" +#include "mainwindow.h" +#include "msngenerics.h" +#include "sbsessions.h" +#include "avatars.h" +#include "xml.h" + +/* The record format for a contact. The same record can be referenced from more than one list. Opaque. */ +typedef struct { + + char *username; /* Username */ + char *friendlyname; /* Friendlyname */ + OnlineState status; /* Online status */ + unsigned int features; /* Client features (e.g. webcam) */ + char *dpobject; /* Display Picture MSNObject */ + ContactSource source; /* The last source the details were updated from */ + int references; /* Number of times this contact is referenced by ContactItems */ + UIContact *uicontact; /* src/mainwindow.c's representation of the contact */ + char *dpsha1d; /* SHA1D field (if any) from user's DP MSNObject (if any). */ + char *ubxdata; /* UBX data for this contact. */ + size_t ubxdatalen; /* Length of UBX data. */ + char *guid; /* Contact's GUID */ + +} Contact; +/* ... but ContactItems actually appear on the lists ... */ + +typedef struct contactitem { + + Contact *contact; /* The contact's record */ + struct contactitem *next; /* Link to the next contact on this list */ + +} ContactItem; + +static ContactItem *contactlist_forward = NULL; +static ContactItem *contactlist_block = NULL; +static ContactItem *contactlist_allow = NULL; +static ContactItem *contactlist_reverse = NULL; +static ContactItem *contactlist_pending = NULL; +static ContactItem *contactlist_temporary = NULL; + +static ContactItem *contactlist_lastcontactitem(ContactItem *contact_list) { + + ContactItem *contactitem = contact_list; + + if ( contactitem == NULL ) { + return NULL; + } + + while ( contactitem ) { + + assert(contactitem != NULL); + assert(contactitem->contact != NULL); + assert(contactitem->contact->username != NULL); + + if ( contactitem->next == NULL ) { + return contactitem; + } else { + contactitem = contactitem->next; + } + + } + + /* Should never get here */ + debug_print("CL: Reached end of contactlist_lastcontactitem(). D'oh.\n"); + return contactitem; + +} + + +static ContactItem **contactlist_translatelist(const char *list) { + + if ( strcmp(list, "AL") == 0 ) { + return &contactlist_allow; + } else if ( strcmp(list, "FL") == 0 ) { + return &contactlist_forward; + } else if ( strcmp(list, "BL") == 0 ) { + return &contactlist_block; + } else if ( strcmp(list, "RL") == 0 ) { + return &contactlist_reverse; + } else if ( strcmp(list, "PL") == 0 ) { + return &contactlist_pending; + } else { + return NULL; + } + +} + +/* Link a contact into a given contact list, creating the ContactItem structure */ +static void contactlist_link(ContactItem **contact_list, Contact *contact) { + + ContactItem *last_contactitem; + ContactItem *new_contactitem; + + new_contactitem = malloc(sizeof(ContactItem)); + new_contactitem->contact = contact; + new_contactitem->next = NULL; + + last_contactitem = contactlist_lastcontactitem(*contact_list); + if ( last_contactitem != NULL ) { + assert(last_contactitem->next == NULL); + last_contactitem->next = new_contactitem; + } else { + *contact_list = new_contactitem;; + } + + contact->references++; + +} + +static ContactItem *contactlist_previous(ContactItem **contact_list, ContactItem *s_contactitem) { + + ContactItem *contactitem; + + contactitem = *contact_list; + + while ( contactitem ) { + + assert(contactitem != NULL); + assert(contactitem->contact != NULL); + assert(contactitem->contact->username != NULL); + + if ( contactitem->next == s_contactitem ) { + return contactitem; + } else { + contactitem = contactitem->next; + } + + } + + /* Didn't find it - means it was the first item on the list. */ + return NULL; + +} + +/* Unlink a contact from a given contact list, destroying it if there's no more references */ +static void contactlist_unlink(ContactItem **contact_list, ContactItem *contactitem) { + + Contact *contact; + ContactItem *previous_contact; + ContactItem *next_contact; + + contact = contactitem->contact; + +/* debug_print("CL: Unlinking %s\n", contact->username);*/ + + /* First link it out of the list. */ + previous_contact = contactlist_previous(contact_list, contactitem); + next_contact = contactitem->next; + if ( previous_contact != NULL ) { + previous_contact->next = next_contact; + } else { + *contact_list = next_contact; + } + contact->references--; +/* debug_print("CL: Reference count now %i\n", contact->references);*/ + + if ( (contact_list == &contactlist_forward) && (contact->uicontact != NULL) ) { + /* This has probably already happened. */ + debug_print("CL: Destroying UIContact\n"); + mainwindow_removecontact(contact->uicontact); + mainwindow_destroycontact(contact->uicontact); + } + + if ( contact->references == 0 ) { + + debug_print("CL: Destroying contact record for %s\n", contact->username); + + /* Destroy the contact record */ + free(contact->username); + if ( contact->friendlyname != NULL ) { + free(contact->friendlyname); + } + if ( contact->dpobject != NULL ) { + free(contact->dpobject); + } + if ( contact->dpsha1d != NULL ) { + free(contact->dpsha1d); + } + if ( contact->ubxdata != NULL ) { + free(contact->ubxdata); + } + if ( contact->guid != NULL ) { + free(contact->guid); + } + free(contact); + + } + + free(contactitem); + +} + +/* Locate a contact's record in a given list by username */ +static ContactItem *contactlist_findcontact(ContactItem *contact_list, const char *username) { + + ContactItem *contactitem = contact_list; + + if ( contactitem == NULL ) { + return NULL; + } + + while ( contactitem ) { + + assert(contactitem != NULL); + assert(contactitem->contact != NULL); + assert(contactitem->contact->username != NULL); + + /* Case-insensitive here, like in sbsessions_find_username(). Username + may have different case depending on its source, since the servers + seem to change usernames to lower case but clients might not in + (e.g.) TypingUser controls. */ + if ( strcasecmp(contactitem->contact->username, username) == 0 ) { + return contactitem; + } else { + contactitem = contactitem->next; + } + + } + + return NULL; + +} + +/* Locate a contact's record in a given list by GUID */ +static ContactItem *contactlist_findcontactguid(ContactItem *contact_list, const char *guid) { + + ContactItem *contactitem = contact_list; + + if ( contactitem == NULL ) { + return NULL; + } + + while ( contactitem ) { + + assert(contactitem != NULL); + assert(contactitem->contact != NULL); + assert(contactitem->contact->guid != NULL); + + /* Case-insensitive */ + if ( strcasecmp(contactitem->contact->guid, guid) == 0 ) { + return contactitem; + } else { + contactitem = contactitem->next; + } + + } + + return NULL; + +} + +void contactlist_removecontact(const char *list, const char *username) { + + ContactItem **contact_list = contactlist_translatelist(list); + ContactItem *contactitem; + + debug_print("CL: Removing %s from %s\n", username, list); + + contactitem = contactlist_findcontact(*contact_list, username); + assert(contactitem != NULL); + contactlist_unlink(contact_list, contactitem); + +} + +void contactlist_removecontactguid(const char *list, const char *guid) { + + ContactItem **contact_list = contactlist_translatelist(list); + ContactItem *contactitem; + + debug_print("CL: Removing %s from %s\n", guid, list); + + contactitem = contactlist_findcontactguid(*contact_list, guid); + assert(contactitem != NULL); + contactlist_unlink(contact_list, contactitem); + +} + +static Contact *contactlist_findcontactrecord(ContactItem *contact_list, const char *username) { + + ContactItem *contactitem; + + contactitem = contactlist_findcontact(contact_list, username); + if ( contactitem == NULL ) { + return NULL; + } + + return contactitem->contact; + +} + +/* Create a new contact record */ +static Contact *contactlist_createcontact(ContactSource source, const char *username, const char *friendlyname, OnlineState status, int features, const char *dpobject, const char *guid) { + + Contact *newcontact; + + newcontact = malloc(sizeof(Contact)); + assert(newcontact != NULL); + + newcontact->username = strdup(username); + if ( friendlyname != NULL ) { + newcontact->friendlyname = strdup(friendlyname); + } else { + newcontact->friendlyname = NULL; + } + if ( dpobject != NULL ) { + newcontact->dpobject = strdup(dpobject); + } else { + newcontact->dpobject = NULL; /* Don't try to strdup(NULL) */ + } + if ( guid != NULL ) { + newcontact->guid = strdup(guid); + } else { + newcontact->guid = NULL; + } + newcontact->source = source; + newcontact->uicontact = NULL; /* This is filled in if/when the contact is linked into the FL */ + newcontact->features = features; + newcontact->status = status; + newcontact->references = 0; + newcontact->dpsha1d = NULL; + newcontact->ubxdata = NULL; + + /* Caller probably means to call contactlist_link pretty soon... */ + return newcontact; + +} + +/* Add a user to the a given list or update details if already there */ +static void contactlist_details(ContactItem **contact_list, ContactSource source, const char *username, const char *friendlyname, int features, const char *dpobject, OnlineState status, const char *guid) { + + ContactItem *contact_item; + Contact *contact; + char *list_string; + + assert(username != NULL); + assert(contact_list != NULL); /* But *contact_list is allowed to be NULL (empty list) */ + + if ( contact_list == &contactlist_allow ) { + list_string = "AL"; + } else if ( contact_list == &contactlist_forward ) { + list_string = "FL"; + } else if ( contact_list == &contactlist_block ) { + list_string = "BL"; + } else if ( contact_list == &contactlist_reverse ) { + list_string = "RL"; + } else if ( contact_list == &contactlist_temporary ) { + list_string = "TL"; + } else if ( contact_list == &contactlist_pending ) { + list_string = "PL"; + } else { + list_string = "??"; /* Whoops */ + } + + /* Look in all the lists to try and find the record for this user */ + contact_item = contactlist_findcontact(contactlist_forward, username); + if ( contact_item == NULL ) { + contact_item = contactlist_findcontact(contactlist_reverse, username); + } + if ( contact_item == NULL ) { + contact_item = contactlist_findcontact(contactlist_allow, username); + } + if ( contact_item == NULL ) { + contact_item = contactlist_findcontact(contactlist_block, username); + } + if ( contact_item == NULL ) { + contact_item = contactlist_findcontact(contactlist_temporary, username); + } + if ( contact_item == NULL ) { + contact_item = contactlist_findcontact(contactlist_pending, username); + } + + /* If they're not found, create them. */ + if ( contact_item == NULL ) { + + debug_print("CL: Creating contact record: %s / %s\n", username, friendlyname); + contact = contactlist_createcontact(source, username, friendlyname, status, features, dpobject, guid); + assert(contact != NULL); /* Shouldn't ever happen */ + + } else { + + /* Found the contact, so check if they need to be updated */ + contact = contact_item->contact; + assert(contact != NULL); + + if ( contact->source <= source ) { + + int dpchanged = 0; + + debug_print("CL: Updating record: %s / %s\n", username, friendlyname); + + /* Don't change friendlyname non-NULL to NULL */ + if ( !((contact->friendlyname != NULL) && (friendlyname == NULL)) ) { + if ( contact->friendlyname != NULL ) { + free(contact->friendlyname); + } + if ( friendlyname != NULL ) { + contact->friendlyname = strdup(friendlyname); + } else { + contact->friendlyname = NULL; + } + } + + /* Don't change GUID non-NULL to NULL either. In fact, this shouldn't be changing + anyway, but I'm not willing to assume that. */ + if ( !((contact->guid != NULL) && (guid == NULL)) ) { + if ( contact->guid != NULL ) { + free(contact->guid); + } + if ( guid != NULL ) { + contact->guid = strdup(guid); + } else { + contact->guid = NULL; + } + } + + /* This detects DP->no DP and No DP->DP transitions. */ + if ( contact->dpobject != dpobject ) { + dpchanged = 1; + } + /* This detects DP->DP transitions and invalidates same-DP changes. */ + if ( contact->dpobject != NULL ) { + + if ( dpobject != NULL ) { + + if ( strcmp(dpobject, contact->dpobject) != 0 ) { + dpchanged = 1; + } else { + dpchanged = 0; + } + + } + free(contact->dpobject); + if ( contact->dpsha1d != NULL ) { + free(contact->dpsha1d); + } + + } + + contact->features = features; + if ( dpobject != NULL ) { + contact->dpobject = strdup(dpobject); + contact->dpsha1d = avatars_unwrapsha1d(dpobject); + } else { + contact->dpobject = NULL; + contact->dpsha1d = NULL; + } + + if ( dpchanged ) { + + /* Change of DP */ + debug_print("CL: Display picture for %s changed.\n", username); + messagewindow_picturekick(username); + + } + + contact->source = source; + contact->status = status; + + } + + } + + /* Check if they're in the right list. If not, link them into it. */ + if ( contactlist_findcontact(*contact_list, username) == NULL ) { + + assert(contact != NULL); + assert(contact->username != NULL); + debug_print("CL: Linking into %s: %s\n", list_string, contact->username); + + contactlist_link(contact_list, contact); + + if ( contact_list == &contactlist_forward ) { + /* Linking into the FL so create an UIContact for them. */ + if ( contact->uicontact == NULL ) { + contact->uicontact = mainwindow_addcontact(contact->username, contact->friendlyname, status); + } + } + + } + + /* If an NLN, ILN or FLN (for the FL) is BEING HANDLED, and the details have been updated, sort the UI out. + N.B. What's being handled doesn't necessarily have anything to do with the contact's status. */ + if ( ((source == CONTACT_SOURCE_NLN) || (source == CONTACT_SOURCE_FLN)) && (contact_list == &contactlist_forward) ) { + assert(contact->uicontact != NULL); + mainwindow_setcontactstatus(contact->uicontact, contact->username, contact->friendlyname, status); + } + +} + +/* Add a user to the FL or update details if already there */ +void contactlist_fldetails(ContactSource source, const char *username, const char *friendlyname, int features, const char *dpobject, OnlineState status, const char *guid) { + contactlist_details(&contactlist_forward, source, username, friendlyname, features, dpobject, status, guid); +} + +/* Add a user to the AL or update details if already there */ +void contactlist_aldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) { + contactlist_details(&contactlist_allow, source, username, friendlyname, 0, NULL, status, NULL); +} + +/* Add a user to the BL or update details if already there */ +void contactlist_bldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) { + contactlist_details(&contactlist_block, source, username, friendlyname, 0, NULL, status, NULL); +} + +/* Add a user to the RL or update details if already there */ +void contactlist_rldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) { + contactlist_details(&contactlist_reverse, source, username, friendlyname, 0, NULL, status, NULL); +} + +/* Add a user to the TL (temporary list) */ +void contactlist_tldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) { + contactlist_details(&contactlist_temporary, source, username, friendlyname, 0, NULL, status, NULL); +} + +/* Add a user to the PL (pending list) */ +void contactlist_pldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) { + contactlist_details(&contactlist_pending, source, username, friendlyname, 0, NULL, status, NULL); +} + +/* Do the same as messagewindow_picturekick, but identify "kickees" by DP SHA1D field. */ +void contactlist_picturekick_sha1d(const char *sha1d) { + + /* Find all users who have this DP SHA1D */ + ContactItem *contactitem = contactlist_forward; + if ( contactitem == NULL ) { + return; + } + + while ( contactitem ) { + + assert(contactitem->contact != NULL); + if ( contactitem->contact->dpsha1d != NULL ) { + if ( strcmp(contactitem->contact->dpsha1d , sha1d) == 0 ) { + messagewindow_picturekick(contactitem->contact->username); + } + } + + contactitem = contactitem->next; + + } + +} + +static void contactlist_clear_list(ContactItem **contact_list) { + + ContactItem *contactitem = *contact_list; + + /* Check the list isn't empty first. */ + if ( contactitem == NULL ) { + debug_print("CL: List is empty\n"); + return; + } + + while ( contactitem ) { + + ContactItem *next_contactitem = NULL; + + assert(contactitem != NULL); + assert(contactitem->contact != NULL); + assert(contactitem->contact->username != NULL); + + next_contactitem = contactitem->next; + + contactlist_unlink(contact_list, contactitem); + contactitem = next_contactitem; + + } + +} + +int contactlist_isonlist(const char *list, const char *username) { + + ContactItem **contact_list = contactlist_translatelist(list); + + if ( contactlist_findcontact(*contact_list, username) == NULL ) { + return 0; + } + + return 1; + +} + +/* Called by src/msnprotocol.c to wipe the contact list at sign-out, and by src/listcache.c to invalidate the lists. */ +void contactlist_clear() { + + debug_print("CL: Clearing FL\n"); + contactlist_clear_list(&contactlist_forward); + debug_print("CL: Clearing RL\n"); + contactlist_clear_list(&contactlist_reverse); + debug_print("CL: Clearing BL\n"); + contactlist_clear_list(&contactlist_block); + debug_print("CL: Clearing AL\n"); + contactlist_clear_list(&contactlist_allow); + debug_print("CL: Clearing TL\n"); + contactlist_clear_list(&contactlist_temporary); + debug_print("CL: Clearing PL\n"); + contactlist_clear_list(&contactlist_pending); + + /* Check */ + assert(contactlist_forward == NULL); + assert(contactlist_block == NULL); + assert(contactlist_allow == NULL); + assert(contactlist_reverse == NULL); + assert(contactlist_temporary == NULL); + +} + +/* Return contact data as a string. */ +static char *contactlist_getcontact(const char *list, ContactItem **item) { + + char *r_contact; + size_t length; + + if ( (*item == NULL) && (strcmp(list, "FL") == 0) ) { + *item = contactlist_forward; + } else if ( (*item == NULL) && (strcmp(list, "AL") == 0) ) { + *item = contactlist_allow; + } else if ( (*item == NULL) && (strcmp(list, "BL") == 0) ) { + *item = contactlist_block; + } else if ( (*item == NULL) && (strcmp(list, "RL") == 0) ) { + *item = contactlist_reverse; + } else if ( (*item == NULL) && (strcmp(list, "PL") == 0) ) { + *item = contactlist_pending; + } else { + assert(item != NULL); + assert(*item != NULL); + *item = (*item)->next; + } + + if ( *item == NULL ) { + return NULL; + } + + length = strlen((*item)->contact->username) + 1; + if ( (*item)->contact->friendlyname != NULL ) { + length += strlen((*item)->contact->friendlyname) + 1; + } + if ( (*item)->contact->guid != NULL ) { + length += strlen((*item)->contact->guid) + 1; + if ( (*item)->contact->friendlyname == NULL ) { + /* Whoops! GUID but no friendly name. */ + length += 2; + } + } + r_contact = malloc(length); + + strcpy(r_contact, (*item)->contact->username); + if ( (*item)->contact->friendlyname != NULL ) { + strcat(r_contact, " "); + strcat(r_contact, (*item)->contact->friendlyname); + } + if ( (*item)->contact->guid != NULL ) { + if ( (*item)->contact->friendlyname == NULL ) { + /* Shouldn't ever happen. Nasty situation. */ + strcat(r_contact, " -"); + } + strcat(r_contact, " "); + strcat(r_contact, (*item)->contact->guid); + } + + return r_contact; + +} + +/* Tweak line ending before sending a line to a file. */ +static int contactlist_writeout(FILE *fh, const char *list, const char *contact) { + + char *line; + int rval; + + line = malloc(strlen(list) + strlen(contact) + 3); + strcpy(line, list); + strcat(line, " "); + strcat(line, contact); + line[strlen(line)+1] = '\0'; + line[strlen(line)] = '\n'; + + rval = fputs(line, fh); + free(line); + + if ( rval == EOF ) { + return -1; + } + + return 0; + +} + +/* Dump a given list into the file handle given. */ +int contactlist_dumplist(FILE *fh, const char *list) { + + ContactItem *token; + char *contact; + + token = NULL; + contact = contactlist_getcontact(list, &token); + while ( token ) { + + if ( contactlist_writeout(fh, list, contact) != 0 ) { + free(contact); + return -1; + } + free(contact); + contact = contactlist_getcontact(list, &token); + + } + + return 0; + +} + +const char *contactlist_friendlyname(const char *username) { + + ContactItem *contact; + + contact = contactlist_findcontact(contactlist_forward, username); + if ( contact == NULL ) { + contact = contactlist_findcontact(contactlist_reverse, username); + } + if ( contact == NULL ) { + contact = contactlist_findcontact(contactlist_allow, username); + } + if ( contact == NULL ) { + contact = contactlist_findcontact(contactlist_block, username); + } + if ( contact == NULL ) { + contact = contactlist_findcontact(contactlist_temporary, username); + } + if ( contact == NULL ) { + contact = contactlist_findcontact(contactlist_pending, username); + } + + /* Tricky one to handle. User isn't in any of the contact lists. Caller will probably create a + * record in the temporary list (which isn't stored on the server or in the cache) and use + * that as the SPOT. */ + if ( contact == NULL ) { + debug_print("CL: contactlist_friendlyname: couldn't find user data.\n"); + return NULL; + } + + assert(contact != NULL); + assert(contact->contact != NULL); + + /* Don't try to free this! You may also want to urldecode it. */ + return contact->contact->friendlyname; + +} + +/* Check if a user has a display picture or not. Return NULL or an MSNObject as appropriate. */ +const char *contactlist_haspicture(const char *username) { + + Contact *contact; + + contact = contactlist_findcontactrecord(contactlist_forward, username); + if ( contact == NULL ) { + return NULL; /* e.g. if user was only on TL */ + } + if ( (contact->dpobject != NULL) && (strlen(contact->dpobject) > 0) ) { + return contact->dpobject; + } + + return NULL; + +} + +/* Return a contact's DP SHA1D, if they have one.. */ +const char *contactlist_dpsha1d(const char *username) { + + Contact *contact; + + contact = contactlist_findcontactrecord(contactlist_forward, username); + if ( contact != NULL ) { + return NULL; + } + + if ( contact->dpsha1d != NULL ) { + return contact->dpsha1d; + } + + return NULL; + +} + +/* Set UBX data for a contact. */ +void contactlist_setubx(const char *username, const char *ubxdata, size_t ubxdatalen) { + + Contact *contact; + char *newubx; + + contact = contactlist_findcontactrecord(contactlist_forward, username); + g_return_if_fail(contact != NULL); + + if ( contact->ubxdata != NULL ) { + free(contact->ubxdata); + } + + newubx = malloc(ubxdatalen); + memcpy(newubx, ubxdata, ubxdatalen); + contact->ubxdata = newubx; + contact->ubxdatalen = ubxdatalen; + + /* Kick the UI */ + debug_print("CL: Kicking UIContact because of UBX change.\n"); + mainwindow_setcontactstatus(contact->uicontact, contact->username, contact->friendlyname, contact->status); + +} + +/* Return the Customised Status Message for a contact. */ +char *contactlist_csm(const char *username, int replace_entities) { + + Contact *contact; + + contact = contactlist_findcontactrecord(contactlist_forward, username); + if ( contact == NULL ) { + return NULL; + } + if ( contact->ubxdata == NULL ) { + return NULL; + } + + /* Don't translate entities - Pango will do that later. */ + return xml_getblock(contact->ubxdata, contact->ubxdatalen, "Data", "PSM", replace_entities); + +} + +ContactListIter *contactlist_iter_new() { + + ContactListIter *iter = malloc(sizeof(ContactListIter)); + *iter = 0; + + return iter; + +} + +void contactlist_iter_destroy(ContactListIter *iter) { + free(iter); +} + +const char *contactlist_getusername(ContactListIter *iter, const char *listl) { + + unsigned int i; + ContactItem **list = contactlist_translatelist(listl); + ContactItem *item = *list; + + if ( list == NULL ) { + debug_print("CL: contactlist_getusername: invalid list.\n"); + return NULL; + } + + for ( i=0; i<*iter; i++ ) { + if ( item == NULL ) { + break; + } + item = item->next; + } + + if ( item == NULL ) { + return NULL; + } + + (*iter)++; + return item->contact->username; + +} + +int contactlist_msnc(const char *username) { +} diff --git a/src/contactlist.h b/src/contactlist.h new file mode 100644 index 0000000..f36ad9b --- /dev/null +++ b/src/contactlist.h @@ -0,0 +1,70 @@ +/* + * contactlist.h + * + * Contact list (FL, BL, AL and RL) data structures + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef CONTACTLIST_H +#define CONTACTLIST_H + +#include <stdio.h> + +#include "mainwindow.h" +#include "msngenerics.h" + +/* Sources of contact details, in ascending order of accuracy */ +typedef enum { + CONTACT_SOURCE_FLN, /* Details came from FLN (which provides no information) */ + CONTACT_SOURCE_LST, /* Contact's details came from LST */ + CONTACT_SOURCE_CACHE, /* Details came from the cache */ + CONTACT_SOURCE_ADC, /* Details came from an ADC (asynchronous add) */ + CONTACT_SOURCE_RNG, /* Details came from a RNG or JOI */ + CONTACT_SOURCE_NLN /* Details came from NLN or ILN */ +} ContactSource; + +typedef unsigned int ContactListIter; + +extern void contactlist_fldetails(ContactSource source, const char *username, const char *friendlyname, int features, const char *dpobject, OnlineState status, const char *guid); +extern void contactlist_aldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status); +extern void contactlist_bldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status); +extern void contactlist_rldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status); +extern void contactlist_tldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status); +extern void contactlist_pldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status); + +extern void contactlist_removecontact(const char *list, const char *username); +extern void contactlist_removecontactguid(const char *list, const char *guid); + +extern void contactlist_setubx(const char *username, const char *ubxdata, size_t ubxdatalen); +extern char *contactlist_csm(const char *username, int replace_entities); + +extern void contactlist_clear(); +extern int contactlist_isonlist(const char *list, const char *username); +extern const char *contactlist_friendlyname(const char *username); +extern const char *contactlist_haspicture(const char *username); +extern void contactlist_picturekick_sha1d(const char *sha1d); +extern const char *contactlist_dpsha1d(const char *username); + +extern int contactlist_dumplist(FILE *fh, const char *list); +extern ContactListIter *contactlist_iter_new(); +extern const char *contactlist_getusername(ContactListIter *iter, const char *list); +extern void contactlist_iter_destroy(ContactListIter *iter); + +#endif /* CONTACTLIST_H */ diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..508e739 --- /dev/null +++ b/src/debug.h @@ -0,0 +1,33 @@ +/* + * debug.h + * + * Debugging stuff + * + * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef DEBUG_H +#define DEBUG_H + +/* Simple #define for now. This could be changed to a more sophisticated + * system later (e.g. "tuxmessenger --debug") */ +#include <stdio.h> +#define debug_print printf + +#endif /* DEBUG_H */ diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..0388584 --- /dev/null +++ b/src/error.c @@ -0,0 +1,121 @@ +/* + * error.c + * + * Report error messages + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <string.h> + +#include "mainwindow.h" + +void error_report(const char *message) { + + GtkWidget *window; + + window = gtk_message_dialog_new(mainwindow_gtkwindow(), GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, message); + + g_signal_connect_swapped(window, "response", G_CALLBACK(gtk_widget_destroy), window); + gtk_widget_show(window); + +} + +static const char *decode_error(int error_number) { + + /* decode server error numbers into understandable messages + for the User */ + + switch (error_number) { + /* I don't pretend to know what all of these mean... */ + + case 200: return "Server reported Syntax Error"; + case 201: return "Server reported Invalid Parameter"; + case 205: return "Server reported Invalid User"; + case 206: return "Server reported FQDN Missing"; + case 207: return "Already logged in"; + case 208: return "Server reported Invalid Username"; + case 209: return "Invalid friendly name"; + case 210: return "You have too many people on your contact list"; + case 215: return "IGNORE"; /* Already there */ + case 216: return "That user is not on your list"; + case 218: return "Already in that mode"; + case 219: return "Already in opposite list"; + case 280: return "Switchboard request failed"; + case 281: return "NS XFR failed"; + case 300: return "Server reported Required Fields Missing"; + case 302: return "Not logged in"; + case 403: return "List unavailable"; + case 500: return "Internal server error"; + case 501: return "DB server error"; + case 510: return "File operation error"; + case 520: return "Memory allocation error"; + case 540: return "Failed challenge"; + case 600: return "Server busy"; + case 601: return "Server unavailable"; + case 602: return "Peer NS down"; + case 603: return "DB connection error"; + case 604: return "Service is going down"; + case 707: return "Error creating connection"; + case 711: return "Error with blocking write"; + case 712: return "Session overloaded"; + case 713: return "User too active"; + case 714: return "Too many sessions"; + case 715: return "Not expected"; + case 717: return "Bad friend file"; + case 800: return "Too many requests: slow down..."; + case 911: return "Authentication failed"; + case 913: return "Not allowed while Invisible"; + case 920: return "Not accepting new users"; + case 928: return "TWN authentication ticket rejected - sign-in failed. This is probably a bug."; + + } + + return "Unknown error message reported by server"; + +} + +void error_report_server(int number) { + + const char *error_message; + + error_message = decode_error(number); + if ( strcmp(error_message, "IGNORE") != 0 ) { + error_report(error_message); + } + +} + +void error_message(const char *message) { + + GtkWidget *window; + + window = gtk_message_dialog_new(mainwindow_gtkwindow(), GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, message); + + g_signal_connect_swapped(window, "response", G_CALLBACK(gtk_widget_destroy), window); + gtk_widget_show(window); + +} diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..f2c7fc9 --- /dev/null +++ b/src/error.h @@ -0,0 +1,32 @@ +/* + * error.h + * + * Report error messages + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef ERROR_H +#define ERROR_H + +extern void error_report(const char *message); +extern void error_report_server(int number); +extern void error_message(const char *message); + +#endif /* ERROR_H */ diff --git a/src/filetrans.c b/src/filetrans.c new file mode 100644 index 0000000..24fa47c --- /dev/null +++ b/src/filetrans.c @@ -0,0 +1,57 @@ +/* + * filetrans.c + * + * File transfer + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include "debug.h" +#include "sbsessions.h" +#include "msnp2p.h" + +/* Offer a file to a single recipient */ +static void filetrans_offer(const char *filename, const char *username) { + + SbSession *session; + + debug_print("FT: Offering '%s' to '%s'\n", filename, username); + + /* Find a suitable session. */ + session = sbsessions_find_single_safe(username); + + msnp2p_offerfile(session, username, filename); + +} + +/* Offer a file to multiple recipients */ +void filetrans_offer_multiple(const char *filename, char *usernames[], unsigned int num_users) { + + unsigned int i; + for ( i=0; i<num_users; i++ ) { + filetrans_offer(filename, usernames[i]); + } + +} diff --git a/src/filetrans.h b/src/filetrans.h new file mode 100644 index 0000000..dcfc806 --- /dev/null +++ b/src/filetrans.h @@ -0,0 +1,30 @@ +/* + * filetrans.h + * + * File transfer + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef FILETRANS_H +#define FILETRANS_H + +extern void filetrans_offer_multiple(const char *filename, char *usernames[], unsigned int num_users); + +#endif /* FILETRANS_H */ diff --git a/src/fonttrans.c b/src/fonttrans.c new file mode 100644 index 0000000..6dbb6a3 --- /dev/null +++ b/src/fonttrans.c @@ -0,0 +1,132 @@ +/* + * fonttrans.c + * + * Translate to and from MSN-style format strings + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "routines.h" +#include "debug.h" + +char *fonttrans_font_to_format(const char *font) { + + char *format; + char *temp; + int family = 0; + int pitch = 0; + char *fontname; + int i; + + debug_print("FO: Font '%s'\n", font); + + format = malloc(128); + + /* Turn the font name into something useful at the other end. */ + strcpy(format, "FN="); + fontname = strdup(font); + + /* Cut off the font size */ + for ( i=strlen(fontname); i>0; i-- ) { + if ( fontname[i] == ' ' ) { + fontname[i] = '\0'; + break; + } + } + + /* Cut out "Bold", "Italic" etc. */ + if ( (temp = strstr(fontname, " Bold")) ) { + strcpy(temp, temp+5); + } + if ( (temp = strstr(fontname, " Oblique")) ) { + strcpy(temp, temp+8); + } + if ( (temp = strstr(fontname, " Italic")) ) { + strcpy(temp, temp+7); + } + if ( (temp = strstr(fontname, " Underline")) ) { + strcpy(temp, temp+10); + } + if ( (temp = strstr(fontname, " Strikethrough")) ) { + strcpy(temp, temp+14); + } + + temp = routines_urlencode(fontname); + free(fontname); + strncat(format, temp, 100); + free(temp); + + /* Work out bold, underline, italic etc. */ + strcat(format, "; EF="); + if ( strstr(font, " Bold ") ) { + strcat(format, "B"); + } + if ( strstr(font, " Italic ") || strstr(font, " Oblique ") ) { + strcat(format, "I"); + } + if ( strstr(font, " Strikethrough ") ) { + strcat(format, "S"); + } + if ( strstr(font, " Underline ") ) { + strcat(format, "U"); + } + + /* Work out font family and pitch. */ + strcat(format, "; PF="); + if ( strstr(font, "Serif") || strstr(font, "Palladio") || strstr(font, "Bookman") || strstr(font, "Roman") ) { + family = 1; + } + if ( strstr(font, "Sans") || strstr(font, "Gothic") ) { + family = 2; + } + if ( strstr(font, "Monospace") || strstr(font, "Mono ") || strstr(font, "System") || strstr(font, "Fixed") ) { + family = 3; + } + if ( strstr(font, "Script") || strstr(font, "Chancery") ) { + family = 4; + } + if ( strstr(font, "Comic") ) { + family = 5; + } + + /* Pitch */ + if ( strstr(font, "Mono ") || strstr(font, "Monospace") || strstr(font, "Fixed") || strstr(font, "System") ) { + pitch = 1; + } else { + pitch = 2; + } + + temp = malloc(3); + snprintf(temp, 3, "%i%i", family, pitch); + strcat(format, temp); + free(temp); + + debug_print("FO: Format '%s'\n", format); + + return format; + +} diff --git a/src/fonttrans.h b/src/fonttrans.h new file mode 100644 index 0000000..2795e05 --- /dev/null +++ b/src/fonttrans.h @@ -0,0 +1,30 @@ +/* + * fonttrans.h + * + * Translate to and from MSN-style format strings + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef FONTTRANS_H +#define FONTTRANS_H + +extern char *fonttrans_font_to_format(const char *font); + +#endif /* FONTTRANS_H */ diff --git a/src/gtk-ink.c b/src/gtk-ink.c new file mode 100644 index 0000000..793911d --- /dev/null +++ b/src/gtk-ink.c @@ -0,0 +1,297 @@ +/* + * gtk-ink.c + * + * GTK widget to display and collect Ink + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <math.h> +#include <libgnomecanvas/libgnomecanvas.h> + +#include "gtk-ink.h" +#include "ink.h" + +#define PAPER_WIDTH 640 +#define PAPER_HEIGHT 480 + +static GtkObjectClass *parent_class = NULL; + +static void gtk_ink_draw_point(GtkInk *gtk_ink, GnomeCanvasGroup *root, InkPoint *prev_point, InkPoint *new_point) { + + if ( prev_point == NULL ) { + gnome_canvas_item_new(root, gnome_canvas_ellipse_get_type(), "fill-color-gdk", gtk_ink->fgcolour, "x1", new_point->x-1, "y1", new_point->y-1, "x2", new_point->x+1, "y2", new_point->y+1, NULL); + } else { + + GnomeCanvasPoints *points; + gdouble width; + gdouble delta; + + points = gnome_canvas_points_new(2); + points->coords[0] = prev_point->x; + points->coords[1] = prev_point->y; + points->coords[2] = new_point->x; + points->coords[3] = new_point->y; + + delta = sqrt((prev_point->x - new_point->x)*(prev_point->x - new_point->x) + (prev_point->y - new_point->y)*(prev_point->y - new_point->y)); + + /* Calibrate 'feel' here. */ + width = (15-delta)/6.5; + if ( width < 0 ) { + width = 0; + } + + gnome_canvas_item_new(root, gnome_canvas_line_get_type(), "fill-color-gdk", gtk_ink->fgcolour, "points", points, "width-units", width, NULL); + } + +} + +gboolean gtk_ink_realize(GtkWidget *widget, GtkInk *gtk_ink) { + + InkStroke *stroke; + GnomeCanvasGroup *root; + + GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED); + root = gnome_canvas_root(GNOME_CANVAS(gtk_ink)); + + /* Draw the (initial) pattern. */ + stroke = gtk_ink->ink->strokes; + while ( stroke != NULL ) { + + InkPoint *point; + point = stroke->points; + + while ( point != NULL ) { + + gtk_ink_draw_point(gtk_ink, root, point->prev, point); + point = point->next; + + } + stroke = stroke->next; + } + + return FALSE; + +} + +gboolean gtk_ink_expose_event(GtkWidget *widget, GdkEventExpose *event, GtkInk *gtk_ink) { + + /* Draw the border. */ + gtk_paint_shadow(widget->style, ((GtkLayout *)gtk_ink)->bin_window, widget->state, GTK_SHADOW_IN, &event->area, widget, NULL, 0, 0, gtk_ink->width, gtk_ink->height); + + return FALSE; + +} + +static void gtk_ink_size_allocate(GtkWidget *widget, GtkAllocation *allocation, GtkInk *gtk_ink) { + + gtk_ink->width = allocation->width; + gtk_ink->height = allocation->height; + gnome_canvas_set_scroll_region(GNOME_CANVAS(gtk_ink), 0, 0, allocation->width, allocation->height); + + /* Sort the background out. */ + if ( (gtk_ink->width > gtk_ink->x_bg_drawn * PAPER_WIDTH) || (gtk_ink->height > gtk_ink->y_bg_drawn * PAPER_HEIGHT) ) { + + int x, y; + GnomeCanvasGroup *root; + + root = gnome_canvas_root(GNOME_CANVAS(gtk_ink)); + + for ( x=gtk_ink->x_bg_drawn; x<=gtk_ink->width/PAPER_WIDTH; x++ ) { + for ( y=0; y<=gtk_ink->height/PAPER_HEIGHT; y++ ) { + if ( gtk_ink->bgpixbuf == NULL ) { + gnome_canvas_item_lower_to_bottom(gnome_canvas_item_new(root, gnome_canvas_rect_get_type(), "fill-color-gdk", gtk_ink->bgcolour, "x1", (gdouble)x*PAPER_WIDTH, "y1", (gdouble)y*PAPER_HEIGHT, "x2", (gdouble)(x+1)*PAPER_WIDTH, "y2", (gdouble)(y+1)*PAPER_HEIGHT, NULL)); + } else { + gnome_canvas_item_lower_to_bottom(gnome_canvas_item_new(root, gnome_canvas_pixbuf_get_type(), "pixbuf", gtk_ink->bgpixbuf, "x", (gdouble)x*PAPER_WIDTH, "y", (gdouble)y*PAPER_HEIGHT, "x-in-pixels", TRUE, "y-in-pixels", TRUE, "width-set", FALSE, "height-set", FALSE, NULL)); + } + } + } + gtk_ink->x_bg_drawn = (gtk_ink->width/PAPER_WIDTH)+1; + + for ( x=0; x<gtk_ink->width/PAPER_WIDTH; x++ ) { + for ( y=gtk_ink->y_bg_drawn; y<=gtk_ink->height/PAPER_HEIGHT; y++ ) { + if ( gtk_ink->bgpixbuf == NULL ) { + gnome_canvas_item_lower_to_bottom(gnome_canvas_item_new(root, gnome_canvas_rect_get_type(), "fill-color-gdk", gtk_ink->bgcolour, "x1", (gdouble)x*PAPER_WIDTH, "y1", (gdouble)y*PAPER_HEIGHT, "x2", (gdouble)(x+1)*PAPER_WIDTH, "y2", (gdouble)(y+1)*PAPER_HEIGHT, NULL)); + } else { + gnome_canvas_item_lower_to_bottom(gnome_canvas_item_new(root, gnome_canvas_pixbuf_get_type(), "pixbuf", gtk_ink->bgpixbuf, "x", (gdouble)x*PAPER_WIDTH, "y", (gdouble)y*PAPER_HEIGHT, "x-in-pixels", TRUE, "y-in-pixels", TRUE, "width-set", FALSE, "height-set", FALSE, NULL)); + } + } + } + gtk_ink->y_bg_drawn = (gtk_ink->height/PAPER_HEIGHT)+1; + + } + +} + +static void gtk_ink_add_point_and_draw(GtkInk *gtk_ink, double x, double y) { + + InkStroke *stroke; + InkPoint *prev_point; + InkPoint *new_point; + + stroke = ink_stroke_get_last(gtk_ink->ink); + prev_point = ink_point_get_last(stroke); + new_point = ink_point_add(stroke, x, y); + + gtk_ink_draw_point(gtk_ink, gnome_canvas_root(GNOME_CANVAS(gtk_ink)), prev_point, new_point); + +} + +static gboolean gtk_ink_button_release(GtkWidget *widget, GdkEventButton *event, GtkInk *gtk_ink) { + gtk_ink->drawing = FALSE; + return TRUE; +} + +static gboolean gtk_ink_button_press(GtkWidget *widget, GdkEventButton *event, GtkInk *gtk_ink) { + + ink_stroke_new(gtk_ink->ink); + gtk_ink_add_point_and_draw(gtk_ink, event->x, event->y); + gtk_ink->drawing = TRUE; + + /* Grab focus */ + if ( !GTK_WIDGET_HAS_DEFAULT(gtk_ink) ) { + gtk_widget_grab_default(GTK_WIDGET(gtk_ink)); + } + if ( !GTK_WIDGET_HAS_FOCUS(gtk_ink) ) { + gtk_widget_grab_focus(GTK_WIDGET(gtk_ink)); + } + + return TRUE; /* Claim it. */ + +} + +static gboolean gtk_ink_motion(GtkWidget *widget, GdkEventMotion *event, GtkInk *gtk_ink) { + + if ( gtk_ink->drawing ) { + gtk_ink_add_point_and_draw(gtk_ink, event->x, event->y); + return TRUE; + } + + return FALSE; + +} + +static void gtk_ink_destroy(GtkObject *gtk_ink) { + + /* GdkColors automagically freed. */ + parent_class->destroy(gtk_ink); + +} + +GtkWidget *gtk_ink_new(GtkInkFlags flags, Ink *ink, GdkColor *fgcolour, GdkColor *bgcolour) { + + GtkInk *gtk_ink; + + gtk_ink = GTK_INK(gtk_type_new(gtk_ink_get_type())); + + gnome_canvas_set_pixels_per_unit(GNOME_CANVAS(gtk_ink), 1); + + gtk_signal_connect_after(GTK_OBJECT(gtk_ink), "expose_event", GTK_SIGNAL_FUNC(gtk_ink_expose_event), gtk_ink); + gtk_signal_connect(GTK_OBJECT(gtk_ink), "size_allocate", GTK_SIGNAL_FUNC(gtk_ink_size_allocate), gtk_ink); + gtk_signal_connect(GTK_OBJECT(gtk_ink), "realize", GTK_SIGNAL_FUNC(gtk_ink_realize), gtk_ink); + + if ( flags & GTK_INK_FLAG_EDITABLE ) { + gtk_signal_connect(GTK_OBJECT(gtk_ink), "button-press-event", GTK_SIGNAL_FUNC(gtk_ink_button_press), gtk_ink); + gtk_signal_connect(GTK_OBJECT(gtk_ink), "button-release-event", GTK_SIGNAL_FUNC(gtk_ink_button_release), gtk_ink); + gtk_signal_connect(GTK_OBJECT(gtk_ink), "motion-notify-event", GTK_SIGNAL_FUNC(gtk_ink_motion), gtk_ink); + gtk_widget_add_events(GTK_WIDGET(gtk_ink), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK); + } + g_object_set(G_OBJECT(gtk_ink), "can-focus", TRUE, "can-default", TRUE, NULL); + + /* Set up properties. */ + gtk_ink->bgcolour = gdk_color_copy(bgcolour); + gtk_ink->fgcolour = gdk_color_copy(fgcolour); + gtk_ink->flags = flags; + gtk_ink->ink = ink; + gtk_ink->x_bg_drawn = 0; + gtk_ink->y_bg_drawn = 0; + gtk_ink->drawing = FALSE; + + if ( flags & GTK_INK_FLAG_PAPER ) { + gtk_ink->bgpixbuf = gdk_pixbuf_new_from_file("/usr/local/share/tuxmessenger/paper.png", NULL); + } else { + gtk_ink->bgpixbuf = NULL; + } + + return GTK_WIDGET(gtk_ink); + +} + +static GObject *gtk_ink_constructor(GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { + + GtkInkClass *class; + GObjectClass *p_class; + GObject *obj; + + class = GTK_INK_CLASS(g_type_class_peek(gtk_ink_get_type())); + p_class = G_OBJECT_CLASS(g_type_class_peek_parent(class)); + + obj = p_class->constructor(type, n_construct_properties, construct_properties); + g_object_set(obj, "aa", TRUE, NULL); + + return obj; + +} + +static void gtk_ink_class_init(GtkInkClass *class) { + + GtkObjectClass *object_class; + GObjectClass *g_object_class; + + object_class = (GtkObjectClass *) class; + g_object_class = G_OBJECT_CLASS(class); + + object_class->destroy = gtk_ink_destroy; + g_object_class->constructor = gtk_ink_constructor; + + parent_class = gtk_type_class(gnome_canvas_get_type()); + +} + +static void gtk_ink_init(GtkInk *gtk_ink) { +} + +guint gtk_ink_get_type(void) { + + static guint gtk_ink_type = 0; + + if ( !gtk_ink_type ) { + + GtkTypeInfo gtk_ink_info = { + "GtkInk", + sizeof(GtkInk), + sizeof(GtkInkClass), + (GtkClassInitFunc) gtk_ink_class_init, + (GtkObjectInitFunc) gtk_ink_init, + NULL, + NULL, + (GtkClassInitFunc) NULL, + }; + gtk_ink_type = gtk_type_unique(gnome_canvas_get_type(), >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 <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef GTKINK_H +#define GTKINK_H + +#include <gtk/gtk.h> +#include <libgnomecanvas/libgnomecanvas.h> + +#include "ink.h" + +typedef enum { + GTK_INK_FLAG_EDITABLE = 1<<0, /* Editable by the user. */ + GTK_INK_FLAG_PAPER = 1<<1, /* Draw nice paper background. */ +} GtkInkFlags; + +typedef struct { + GnomeCanvasClass parent_class; +} GtkInkClass; + +typedef struct { + + GnomeCanvas parent; /* Parent widget */ + + Ink *ink; /* Ink object this widget is associated with. */ + GtkInkFlags flags; /* Flags for this GtkInk widget. */ + + int width; /* Width of drawing area. */ + int height; /* Height of drawing area. */ + int drawing; /* Currently drawing? */ + GdkColor *bgcolour; /* Background colour for drawing area. */ + GdkColor *fgcolour; /* Foreground colour for drawing area. */ + GdkPixbuf *bgpixbuf; /* Pixbuf to use for the background. */ + + int x_bg_drawn; + int y_bg_drawn; /* Number of background tiles drawn in the x and y directions. */ + +} GtkInk; + +extern guint gtk_ink_get_type(void); +extern GtkWidget *gtk_ink_new(GtkInkFlags flags, Ink *ink, GdkColor *fgcolour, GdkColor *bgcolour); + +#define GTK_INK(obj) GTK_CHECK_CAST(obj, gtk_ink_get_type(), GtkInk) +#define GTK_INK_CLASS(class) GTK_CHECK_CLASS_CAST(class, gtk_ink_get_type(), GtkInkClass) +#define GTK_IS_INK(obj) GTK_CHECK_TYPE(obj, gtk_ink_get_type()) + +#endif /* GTKINK_H */ diff --git a/src/ink.c b/src/ink.c new file mode 100644 index 0000000..9655d8e --- /dev/null +++ b/src/ink.c @@ -0,0 +1,159 @@ +/* + * ink.c + * + * Routines for handling Ink + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "ink.h" +#include "assert.h" +#include "routines.h" + +/* Create a new Ink object. */ +Ink *ink_new() { + + Ink *ink = malloc(sizeof(Ink)); + + ink->strokes = NULL; + ink->last_stroke = NULL; + + return ink; + +} + +static Ink *ink_new_from_isf_base64(const char *base64_isf) { + + Ink *ink; + size_t isf_len; + char *isf; + FILE *fh = fopen("/home/weiss/isf.dat", "w"); + + ink = ink_new(); + isf_len = routines_base64decode(base64_isf, &isf); + + printf("'%s' (%i bytes)\n", isf, isf_len); + fwrite(isf, isf_len, 1, fh); + fclose(fh); + + return ink; + +} + +/* Create a new Ink object and load it with data from an ISF string. */ +Ink *ink_new_from_isf(const char *isf) { + + if ( strncmp(isf, "base64:", 7) == 0 ) { + return ink_new_from_isf_base64(isf+7); + } + + /* Can't load anything else at the moment. Return a blank. */ + return ink_new(); + +} + +/* Create a new stroke in a given Ink object. */ +InkStroke *ink_stroke_new(Ink *ink) { + + InkStroke *stroke = malloc(sizeof(InkStroke)); + + if ( ink->last_stroke != NULL ) { + assert(ink->last_stroke->next == NULL); + ink->last_stroke->next = stroke; + } else { + /* No strokes */ + ink->strokes = stroke; + } + + ink->last_stroke = stroke; + + stroke->next = NULL; + stroke->points = NULL; + stroke->last_point = NULL; + + return stroke; + +} + +/* Get last stroke - automatically creating the first one if there aren't any. */ +InkStroke *ink_stroke_get_last(Ink *ink) { + + if ( ink->last_stroke != NULL ) { + return ink->last_stroke; + } + + return ink_stroke_new(ink); /* Automatically becomes the last stroke. */ + +} + +InkPoint *ink_point_get_last(InkStroke *stroke) { + + assert(stroke != NULL); + return stroke->last_point; /* Might be NULL. */ + +} + +InkPoint *ink_point_get_previous(InkPoint *point) { + + if ( point == NULL ) { + return NULL; + } + + return point->prev; /* Might be NULL. */ + +} + +/* Create a new point in a given stroke. */ +InkPoint *ink_point_add(InkStroke *stroke, double x, double y) { + + InkPoint *last_point = ink_point_get_last(stroke); + InkPoint *point = malloc(sizeof(InkPoint)); + + if ( last_point != NULL ) { + assert(last_point->next == NULL); + last_point->next = point; + point->prev = last_point; + } else { + /* No points! */ + stroke->points = point; + point->prev = NULL; + } + + stroke->last_point = point; + + point->next = NULL; + point->x = x; + point->y = y; + + return point; + +} + +/* Destroy an Ink object. */ +void ink_destroy(Ink *ink) { + free(ink); +} diff --git a/src/ink.h b/src/ink.h new file mode 100644 index 0000000..4842b9a --- /dev/null +++ b/src/ink.h @@ -0,0 +1,68 @@ +/* + * ink.h + * + * Routines for handling Ink + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef INK_H +#define INK_H + +typedef struct stru_inkpoint { + + struct stru_inkpoint *next; /* Next point in linked list */ + + double x; + double y; + + struct stru_inkpoint *prev; /* Previous point in linked list */ + +} InkPoint; + +typedef struct stru_inkstroke { + + struct stru_inkstroke *next; /* Next stroke in linked list */ + InkPoint *points; /* Linked list of points. */ + + InkPoint *last_point; /* Last point in list (saves rescanning) */ + +} InkStroke; + +/* Opaque! */ +typedef struct { + + InkStroke *strokes; /* Linked list of strokes */ + + InkStroke *last_stroke; /* Last stroke in list (saves rescanning) */ + +} Ink; + +extern Ink *ink_new(); +extern Ink *ink_new_from_isf(const char *isf); +extern void ink_destroy(Ink *ink); + +extern InkStroke *ink_stroke_new(Ink *ink); +extern InkStroke *ink_stroke_get_last(Ink *ink); + +extern InkPoint *ink_point_add(InkStroke *stroke, double x, double y); +extern InkPoint *ink_point_get_last(InkStroke *stroke); +extern InkPoint *ink_point_get_previous(InkPoint *point); + +#endif /* INK_H */ diff --git a/src/listcache.c b/src/listcache.c new file mode 100644 index 0000000..a895ff8 --- /dev/null +++ b/src/listcache.c @@ -0,0 +1,615 @@ +/* + * listcache.c + * + * Contact list caching + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "contactlist.h" +#include "debug.h" +#include "options.h" +#include "routines.h" +#include "mainwindow.h" +#include "xml.h" +#include "msnprotocol.h" +#include "addcontact.h" +#include "error.h" + +static char *listcache_tag = NULL; +static char *listcache_mfn = NULL; +static char *listcache_ubxdata = NULL; +static unsigned int listcache_invalid = FALSE; + +static char *listcache_file() { + + char *filename; + char *done; + + filename = routines_glob("~/.tuxmessenger/"); + done = malloc(strlen(filename)+11); + strcpy(done, filename); + free(filename); + strcat(done, "list.cache"); + + return done; + +} + +/* Called to record the friendly name for later caching */ +void listcache_setmfn(const char *mfn) { + + if ( listcache_mfn != NULL ) { + free(listcache_mfn); + } + + listcache_mfn = strdup(mfn); + +} + +const char *listcache_getmfn() { + + if ( listcache_mfn == NULL ) { + return ""; + } + + return listcache_mfn; + +} + +void listcache_setcsm(const char *csm) { + + const char *template; + + if ( listcache_ubxdata != NULL ) { + free(listcache_ubxdata); + } + + template = "<Data><PSM></PSM><CurrentMedia></CurrentMedia></Data>"; + listcache_ubxdata = xml_setblock(template, strlen(template), "Data", "PSM", csm); + +} + +static char *listcache_getcsm_internal(int entities) { + + char *result; + + if ( listcache_ubxdata == NULL ) { + return strdup(""); + } + + result = xml_getblock(listcache_ubxdata, strlen(listcache_ubxdata), "Data", "PSM", entities); + if ( result == NULL ) { + return strdup(""); + } + + return result; + +} + +char *listcache_getcsm() { + return listcache_getcsm_internal(0); +} + +char *listcache_getcsm_noentities() { + return listcache_getcsm_internal(1); +} + +static int listcache_processline(char *line) { + + char *token = routines_lindex(line, 0); + + if ( strcmp(token, "FL") == 0 ) { + + char *username = routines_lindex(line, 1); + char *friendlyname = routines_lindex(line, 2); + char *guid = routines_lindex(line, 3); + if ( username == NULL ) { + return -1; + } + if ( strlen(guid) == 0 ) { + free(guid); + guid = NULL; + } + if ( strlen(friendlyname) == 0 ) { + /* This would be a bad thing. */ + free(friendlyname); + friendlyname = NULL; + } + contactlist_fldetails(CONTACT_SOURCE_CACHE, username, friendlyname, 0, NULL, ONLINE_FLN, guid); + free(username); + free(friendlyname); + + } else if ( strcmp(token, "AL") == 0 ) { + + char *username = routines_lindex(line, 1); + char *friendlyname = routines_lindex(line, 2); + if ( username == NULL ) { + return -1; + } + if ( strlen(friendlyname) == 0 ) { + free(friendlyname); + friendlyname = NULL; + } + contactlist_aldetails(CONTACT_SOURCE_CACHE, username, friendlyname, ONLINE_FLN); + free(username); + if ( friendlyname != NULL ) { + free(friendlyname); + } + + } else if ( strcmp(token, "BL") == 0 ) { + + char *username = routines_lindex(line, 1); + char *friendlyname = routines_lindex(line, 2); + if ( username == NULL ) { + return -1; + } + if ( strlen(friendlyname) == 0 ) { + free(friendlyname); + friendlyname = NULL; + } + contactlist_bldetails(CONTACT_SOURCE_CACHE, username, friendlyname, ONLINE_FLN); + free(username); + if ( friendlyname != NULL ) { + free(friendlyname); + } + + } else if ( strcmp(token, "RL") == 0 ) { + + char *username = routines_lindex(line, 1); + char *friendlyname = routines_lindex(line, 2); + if ( username == NULL ) { + return -1; + } + if ( strlen(friendlyname) == 0 ) { + free(friendlyname); + friendlyname = NULL; + } + contactlist_rldetails(CONTACT_SOURCE_CACHE, username, friendlyname, ONLINE_FLN); + free(username); + if ( friendlyname != NULL ) { + free(friendlyname); + } + + } else if ( strcmp(token, "PL") == 0 ) { + + char *username = routines_lindex(line, 1); + char *friendlyname = routines_lindex(line, 2); + if ( username == NULL ) { + return -1; + } + if ( strlen(friendlyname) == 0 ) { + free(friendlyname); + friendlyname = NULL; + } + contactlist_pldetails(CONTACT_SOURCE_CACHE, username, friendlyname, ONLINE_FLN); + addcontact_added(username, friendlyname); + + free(username); + if ( friendlyname != NULL ) { + free(friendlyname); + } + + } else if ( strcmp(token, "BLP") == 0 ) { + debug_print("LC: Got BLP from cache.\n"); + } else if ( strcmp(token, "GTC") == 0 ) { + debug_print("LC: Got GTC from cache.\n"); + } else if ( strcmp(token, "MFN") == 0 ) { + + char *mfn = routines_lindex(line, 1); + debug_print("LC: Got MFN from cache.\n"); + mainwindow_setmfn(mfn); + listcache_setmfn(mfn); + free(mfn); + + } else if ( strcmp(token, "CSM") == 0 ) { + + /* See also listcache_loadtag_internal() */ + char *csm_text; + char *csm = routines_lindexend(line, 1); + msnprotocol_setcsm(csm); + + csm_text = listcache_getcsm(); + mainwindow_setcsm(csm_text); + free(csm_text); + + free(csm); + debug_print("LC: Got CSM from cache.\n"); + + } else { + free(token); + return -1; + } + + free(token); + return 0; + +} + +static FILE *listcache_loadtag_internal() { + + FILE *fh; + char *line; + char *rval; + char *cachefile; + int done = 0; + + cachefile = listcache_file(); + + fh = fopen(cachefile, "r"); + free(cachefile); + if ( fh == NULL ) { + debug_print("LC: Error opening list cache file.\n"); + return NULL; + } + + line = malloc(1024); + assert(line != NULL); + + /* Check this cache file is right for the username. Bin it if not... */ + rval = fgets(line, 1023, fh); + if ( ferror(fh) || (rval == NULL) ) { + debug_print("LC: Error retrieving list cache username.\n"); + return NULL; + } + if ( line[strlen(line)-1] == '\n' ) { + line[strlen(line)-1] = '\0'; /* Cut off trailing newline. */ + } + debug_print("LC: list.cache file is for user '%s' ", line); + if ( strcmp(line, options_username()) == 0 ) { + debug_print("- good.\n"); + } else { + + debug_print("- wrong. Deleting...\n"); + free(line); + fclose(fh); + cachefile = listcache_file(); + fh = fopen(listcache_file(), "w"); + free(cachefile); + return NULL; + + } + + /* Get the SYN tag. */ + rval = fgets(line, 1023, fh); + if ( ferror(fh) || (rval == NULL) ) { + debug_print("LC: Error retrieving SYN tag.\n"); + free(line); + fclose(fh); + return NULL; + } + if ( line[strlen(line)-1] == '\n' ) { + line[strlen(line)-1] = '\0'; /* Cut off trailing newline. */ + } + if ( listcache_tag != NULL ) { + free(listcache_tag); + } + listcache_tag = strdup(line); + + /* Because there doesn't seem to be a way to retrieve the CSM, the CSM also + needs to be read from the cache file at this point. Yuk. */ + while ( rval && !done ) { + + rval = fgets(line, 1023, fh); + if ( rval != NULL ) { + + char *token; + + if ( line[strlen(line)-1] == '\n' ) { + line[strlen(line)-1] = '\0'; /* Cut off trailing newline. */ + } + + token = routines_lindex(line, 0); + + if ( strcmp(token, "CSM") == 0 ) { + + /* See also listcache_processline() */ + char *csm_text; + char *csm = routines_lindexend(line, 1); + msnprotocol_setcsm(csm); + + csm_text = listcache_getcsm(); + mainwindow_setcsm(csm_text); + free(csm_text); + + free(csm); + debug_print("LC: Got CSM from cache.\n"); + done = 1; + + } + + free(token); + + } + + } + + free(line); + + return fh; + +} + +char *listcache_loadtag() { + + FILE *fh; + + if ( listcache_invalid ) { + debug_print("LC: Cache invalid - not loading.\n"); + return "0 0"; + } + + fh = listcache_loadtag_internal(); + if ( fh != NULL ) { + fclose(fh); + return listcache_tag; + } + + return "0 0"; + +} + +/* Throws the cached contact list at contactlist.c. Returns the tag to send with SYN. + (Actually, this return value is never actually used...) */ +char *listcache_load() { + + FILE *fh; + char *line; + char *rval = "boing"; + int whoops = 0; + + line = malloc(1024); + assert(line != NULL); + + if ( listcache_invalid ) { + debug_print("LC: Cache invalid - not loading.\n"); + return "0 0"; + } + + fh = listcache_loadtag_internal(); + if ( fh == NULL ) { + return "0 0"; + } + + while ( rval && !whoops ) { + rval = fgets(line, 1023, fh); + if ( rval != NULL ) { + if ( line[strlen(line)-1] == '\n' ) { + line[strlen(line)-1] = '\0'; /* Cut off trailing newline. */ + } + whoops = listcache_processline(line); + } + } + if ( ferror(fh) || whoops ) { + debug_print("LC: Error reading list cache file - re-requesting list.\n"); + contactlist_clear(); + return "0 0"; + } + + fclose(fh); + free(line); + + return listcache_tag; + +} + +/* Called to record the most recent tag returned by SYN, to be saved with the list at the next listcache_save */ +void listcache_settag(const char *tag) { + + if ( listcache_tag != NULL ) { + + if ( strcmp(tag, listcache_tag) != 0 ) { + /* Tag has changed, so invalidate the lists ready for the new version.*/ + contactlist_clear(); + } + + free(listcache_tag); + + } + + listcache_tag = strdup(tag); + +} + +/* Save the contact data (from contactlist.c) out to disk. */ +void listcache_save() { + + FILE *fh; + int rval = 0; + char *tag_newline; + char *mfn; + char *cachefile; + + cachefile = listcache_file(); + + fh = fopen(cachefile, "w"); + if ( chmod(cachefile, S_IRUSR | S_IWUSR) != 0 ) { + error_report("Couldn't set permissions on contact list cache file!"); + } + free(cachefile); + + if ( fh == NULL ) { + debug_print("LC: Error opening list cache file.\n"); + return; + } + + /* Record the username */ + tag_newline = malloc(strlen(options_username())+2); + assert(tag_newline != NULL); + strcpy(tag_newline, options_username()); + tag_newline[strlen(tag_newline)+1] = '\0'; + tag_newline[strlen(tag_newline)] = '\n'; + if ( fputs(tag_newline, fh) == EOF ) { + rval = -1; + } + free(tag_newline); + + /* Record the SYN tag */ + tag_newline = malloc(strlen(listcache_tag)+2); + assert(tag_newline != NULL); + strcpy(tag_newline, listcache_tag); + tag_newline[strlen(tag_newline)+1] = '\0'; + tag_newline[strlen(tag_newline)] = '\n'; + if ( fputs(tag_newline, fh) == EOF ) { + rval = -1; + } + free(tag_newline); + + /* CSM has to be saved before anything else, since listcache_loadtag_internal loads + the file as far as the CSM, ignoring everything else, then passes the file + handle to the main parser without resetting the pointer. */ + if ( rval == 0 ) { + + char *csm_text = listcache_getcsm_noentities(); + if ( csm_text != NULL ) { + + /* Record CSM */ + char *csm = malloc(strlen(csm_text)+7); + strcpy(csm, "CSM "); + strcat(csm, csm_text); + csm[strlen(csm)+1] = '\0'; + csm[strlen(csm)] = '\n'; + if ( fputs(csm, fh) == EOF ) { + rval = -1; + } + free(csm); + free(csm_text); + + } + + } + + if ( rval == 0 ) { + + char *blp_newline; + + /* Record the value of BLP */ + blp_newline = malloc(6+strlen(options_blp())); + assert(blp_newline != NULL); + + strcpy(blp_newline, "BLP "); + strcat(blp_newline, options_blp()); + blp_newline[strlen(blp_newline)+1] = '\0'; + blp_newline[strlen(blp_newline)] = '\n'; + if ( fputs(blp_newline, fh) == EOF ) { + rval = -1; + } + free(blp_newline); + + } + + if ( rval == 0 ) { + + char *gtc_newline; + + /* Record the value of GTC */ + gtc_newline = malloc(6+strlen(options_gtc())); + assert(gtc_newline != NULL); + + strcpy(gtc_newline, "GTC "); + strcat(gtc_newline, options_gtc()); + gtc_newline[strlen(gtc_newline)+1] = '\0'; + gtc_newline[strlen(gtc_newline)] = '\n'; + if ( fputs(gtc_newline, fh) == EOF ) { + rval = -1; + } + free(gtc_newline); + + } + + if ( rval == 0 ) { + + if ( listcache_mfn != NULL ) { + + /* Record friendly name */ + mfn = malloc(strlen(listcache_mfn)+7); + strcpy(mfn, "MFN "); + strcat(mfn, listcache_mfn); + mfn[strlen(mfn)+1] = '\0'; + mfn[strlen(mfn)] = '\n'; + if ( fputs(mfn, fh) == EOF ) { + rval = -1; + } + free(mfn); + + } + + } + + if ( rval == 0 ) { + rval = contactlist_dumplist(fh, "FL"); + } + if ( rval == 0 ) { + rval = contactlist_dumplist(fh, "RL"); + } + if ( rval == 0 ) { + rval = contactlist_dumplist(fh, "AL"); + } + if ( rval == 0 ) { + rval = contactlist_dumplist(fh, "BL"); + } + if ( rval == 0 ) { + rval = contactlist_dumplist(fh, "PL"); + } + + if ( rval == 0 ) { + debug_print("LC: Saved contacts to cache.\n"); + } else { + + char *cachefile; + + debug_print("LC: Error saving contacts to cache.\n"); + fclose(fh); + /* Attempt to truncate the cache file for safety next time. */ + cachefile = listcache_file(); + fh = fopen(cachefile, "w"); + free(cachefile); + + } + + fclose(fh); + listcache_invalid = FALSE; + +} + +int listcache_checktag(const char *tag) { + + if ( strcmp(tag, listcache_tag) == 0 ) { + return 1; + } + + return 0; + +} + +/* Mark the contents of the cache as invalid, for example, after changing the local username. */ +void listcache_invalidate() { + listcache_invalid = TRUE; +} diff --git a/src/listcache.h b/src/listcache.h new file mode 100644 index 0000000..732dd85 --- /dev/null +++ b/src/listcache.h @@ -0,0 +1,41 @@ +/* + * listcache.h + * + * Contact list caching + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef LISTCACHE_H +#define LISTCACHE_H + +extern char *listcache_load(void); +extern char *listcache_loadtag(void); +extern void listcache_save(void); +extern void listcache_invalidate(); + +extern void listcache_settag(const char *tag); +extern void listcache_setmfn(const char *mfn); +extern const char *listcache_getmfn(void); + +extern void listcache_setcsm(const char *csm); +extern char *listcache_getcsm(void); +extern char *listcache_getcsm_noentities(void); + +#endif /* LISTCACHE_H */ diff --git a/src/listcleanup.c b/src/listcleanup.c new file mode 100644 index 0000000..322ba0e --- /dev/null +++ b/src/listcleanup.c @@ -0,0 +1,88 @@ +/* + * listcleanup.c + * + * Automatic list cleanup feature - e.g. remove unnecessary blocks + * + * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "contactlist.h" +#include "debug.h" + +void listcleanup_open() { + + ContactListIter *iter; + const char *username = ""; + + /* On FL but not RL (i.e. person doesn't like you) */ + iter = contactlist_iter_new(); + while ( username != NULL ) { + username = contactlist_getusername(iter, "FL"); + if ( username != NULL ) { + if ( !contactlist_isonlist("RL", username) ) { + debug_print("LU: Hasn't added you: %s\n", username); + } + } + } + contactlist_iter_destroy(iter); + + /* On RL but not FL (i.e. you're being rude) */ + iter = contactlist_iter_new(); + username = ""; + while ( username != NULL ) { + username = contactlist_getusername(iter, "RL"); + if ( username != NULL ) { + if ( !contactlist_isonlist("FL", username) ) { + debug_print("LU: You haven't added: %s\n", username); + } + } + } + contactlist_iter_destroy(iter); + + /* On AL but not RL (i.e. pointless Allow) */ + iter = contactlist_iter_new(); + username = ""; + while ( username != NULL ) { + username = contactlist_getusername(iter, "AL"); + if ( username != NULL ) { + if ( !contactlist_isonlist("RL", username) ) { + debug_print("LU: Possible 'pointless allow': %s\n", username); + } + } + } + contactlist_iter_destroy(iter); + + /* On BL but not RL (i.e. pointless block) */ + iter = contactlist_iter_new(); + username = ""; + while ( username != NULL ) { + username = contactlist_getusername(iter, "BL"); + if ( username != NULL ) { + if ( !contactlist_isonlist("RL", username) ) { + debug_print("LU: Possible 'pointless block': %s\n", username); + } + } + } + contactlist_iter_destroy(iter); + +} diff --git a/src/listcleanup.h b/src/listcleanup.h new file mode 100644 index 0000000..c982302 --- /dev/null +++ b/src/listcleanup.h @@ -0,0 +1,30 @@ +/* + * listcleanup.h + * + * Automatic list cleanup feature - e.g. remove unnecessary blocks + * + * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef LISTCLEANUP_H +#define LISTCLEANUP_H + +extern void listcleanup_open(); + +#endif /* LISTCLEANUP_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..a49ba58 --- /dev/null +++ b/src/main.c @@ -0,0 +1,64 @@ +/* + * main.c + * + * The Top Level Source File + * + * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <gtk/gtk.h> +#include <stdio.h> +#include <stdarg.h> +#include <signal.h> + +#include "mainwindow.h" +#include "options.h" +#include "debug.h" + +int main (int argc, char *argv[]) { + + /* This makes it look so easy... */ + gtk_init(&argc, &argv); + gtk_window_set_default_icon_from_file(DATADIR"/tuxmessenger/icon.png", NULL); + + options_load(); + mainwindow_open(); + /* Prevent horrid sudden death */ + signal(SIGPIPE, SIG_IGN); + gtk_main(); + + return 0; + +} + +char *tuxmessenger_versionstring() { + +#ifdef HAVE_CONFIG_H + return PACKAGE_NAME"/"VERSION; +#else + return "TuxMessenger/unknown"; +#endif + +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..aabbc33 --- /dev/null +++ b/src/main.h @@ -0,0 +1,34 @@ +/* + * main.h + * + * Not Very Much. + * + * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifndef MAIN_H +#define MAIN_H + +extern char *tuxmessenger_versionstring(); + +#endif /* MAIN_H */ diff --git a/src/mainwindow.c b/src/mainwindow.c new file mode 100644 index 0000000..addb978 --- /dev/null +++ b/src/mainwindow.c @@ -0,0 +1,1383 @@ +/* + * mainwindow.c + * + * The main (contact list) window + * + * (c) 2002-2007 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdio.h> + +#include "options.h" +#include "about.h" +#include "prefswindow.h" +#include "accountwindow.h" +#include "mainwindow.h" +#include "msnprotocol.h" +#include "msngenerics.h" +#include "routines.h" +#include "sbsessions.h" +#include "statusicons.h" +#include "main.h" +#include "debug.h" +#include "listcache.h" +#include "contactlist.h" +#include "addcontact.h" +#include "messagewindow.h" +#include "error.h" +#include "listcleanup.h" + +typedef enum { + MAINWINDOW_STATE_UNDEFINED, + MAINWINDOW_STATE_DISPATCH, + MAINWINDOW_STATE_ONLINE +} MainWindowState; + +typedef enum { + LISTSORT_ACTIVITY, + LISTSORT_ALPHA +} ContactListSorting; + +static struct { + + /* Internal data */ + MainWindowState state; /* A record of which state the window is in */ + GtkUIManager *ui; /* UI manager */ + int quitting; /* Whether or not to quit on disconnection. */ + int was_menustatuschange; /* Locks to avoid comical looping */ + int just_updating; /* when changing status. */ + + /* Common to both states */ + GtkWidget *window; /* Overall window */ + GtkWidget *swindow; /* Scrolled Window inside window */ + GtkWidget *vbox; /* vbox inside swindow */ + GtkWidget *top_hbox; /* hbox at top of window */ + GtkWidget *bigvbox; /* vbox to put scrolled window and menu bar into */ + + /* For the dispatch state */ + GtkWidget *signin_button; /* The Big Red Button */ + char *signin_text; /* The text inside the big red button */ + + /* For the online state */ + GtkWidget *online_vbox; /* vbox to hold online contacts plus "Online" heading */ + GtkWidget *offline_vbox; /* vbox to hold offline contacts plus "Offline" heading */ + GtkWidget *top_vbox; /* vbox to hold friendly name and CSM. */ + GtkWidget *friendlyname; /* label to contain the user's current friendly name */ + GtkWidget *fname_eventbox; /* eventbox to contain "friendlyname" */ + GtkWidget *csm_entry; /* Text entry box when CSM is being changed. */ + GtkWidget *csm; /* label to contain the user's current CSM */ + GtkWidget *csm_eventbox; /* eventbox to contain "csm" */ + GtkWidget *fname_entry; /* Text entry box when friendlyname is being changed. */ + GtkWidget *setstatus; /* "Change online status" combo box */ + GtkWidget *online_label; /* "Online" heading */ + GtkWidget *offline_label; /* "Offline" heading */ + GtkWidget *status_vbox; /* vbox to sort out size of status */ + GtkActionGroup *action_group; + + char *menu_subject; + +} mainwindow = { + + MAINWINDOW_STATE_UNDEFINED, + NULL, + 0, + 0, + 0, + + NULL, + NULL, + NULL, + NULL, + NULL, + + NULL, + NULL, + + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL + +}; + +/* Internally-called function to end the program */ +static void mainwindow_destroyed() { + gtk_exit(0); +} + +/* Internally-called function to start the sign-in process */ +static void mainwindow_signin() { + + if (mainwindow.state != MAINWINDOW_STATE_DISPATCH) { + debug_print("MA: That'd just be silly.\n"); + return; + } + mainwindow_setonline(); + msnprotocol_connect("", 0); + +} + +/* Internally-called function to undo the effects of mainwindow_setonline */ +static void mainwindow_unsetonline() { + + assert(mainwindow.state == MAINWINDOW_STATE_ONLINE); + + addcontact_closeall(); + + gtk_widget_destroy(mainwindow.setstatus); + gtk_widget_destroy(mainwindow.online_label); + gtk_widget_destroy(mainwindow.offline_label); + gtk_widget_destroy(mainwindow.online_vbox); + gtk_widget_destroy(mainwindow.offline_vbox); + gtk_widget_destroy(mainwindow.status_vbox); + if ( mainwindow.friendlyname != NULL ) { + gtk_widget_destroy(mainwindow.friendlyname); + } + if ( mainwindow.fname_entry != NULL ) { + gtk_widget_destroy(mainwindow.fname_entry); + } + gtk_widget_destroy(mainwindow.fname_eventbox); + if ( mainwindow.csm != NULL ) { + gtk_widget_destroy(mainwindow.csm); + } + if ( mainwindow.csm_entry != NULL ) { + gtk_widget_destroy(mainwindow.csm_entry); + } + gtk_widget_destroy(mainwindow.csm_eventbox); + gtk_widget_destroy(mainwindow.top_vbox); + + mainwindow.state = MAINWINDOW_STATE_UNDEFINED; + + if ( mainwindow.quitting ) { + /* Bye Bye... */ + gtk_widget_destroy(mainwindow.window); + } + +} + +/* Internally-called function to undo the effects of mainwindow_setdispatch */ +static void mainwindow_unsetdispatch() { + + assert(mainwindow.state == MAINWINDOW_STATE_DISPATCH); + + gtk_widget_destroy(mainwindow.signin_button); + free(mainwindow.signin_text); + + mainwindow.state = MAINWINDOW_STATE_UNDEFINED; + +} + +static void mainwindow_fname_labelize() { + + gfloat yalign; + + gtk_widget_destroy(mainwindow.fname_entry); + mainwindow.fname_entry = NULL; + mainwindow.friendlyname = gtk_label_new(""); + assert(mainwindow.friendlyname != NULL); + gtk_misc_get_alignment(GTK_MISC(mainwindow.friendlyname), NULL, &yalign); + gtk_misc_set_alignment(GTK_MISC(mainwindow.friendlyname), 0, yalign); + mainwindow_setmfn(listcache_getmfn()); + gtk_container_add(GTK_CONTAINER(mainwindow.fname_eventbox), mainwindow.friendlyname); + gtk_widget_show(mainwindow.friendlyname); + +} + +static void mainwindow_csm_labelize() { + + gfloat yalign; + char *csm; + + gtk_widget_destroy(mainwindow.csm_entry); + mainwindow.csm_entry = NULL; + mainwindow.csm = gtk_label_new(""); + assert(mainwindow.csm != NULL); + gtk_misc_get_alignment(GTK_MISC(mainwindow.csm), NULL, &yalign); + gtk_misc_set_alignment(GTK_MISC(mainwindow.csm), 0, yalign); + + csm = listcache_getcsm(); + mainwindow_setcsm(csm); + free(csm); + + gtk_container_add(GTK_CONTAINER(mainwindow.csm_eventbox), mainwindow.csm); + gtk_widget_show(mainwindow.csm); + +} + +static void mainwindow_fname_activate() { + + const char *new_mfn; + + assert(mainwindow.fname_entry != NULL); + assert(mainwindow.friendlyname == NULL); + + new_mfn = gtk_entry_get_text(GTK_ENTRY(mainwindow.fname_entry)); + debug_print("MA: New friendly name: %s\n", new_mfn); + + /* Switch back to a label... */ + mainwindow_fname_labelize(); + + /* NOW change the name... currently the widget displays the old name, meaning + it'll still be accurate if the name change fails. */ + msnprotocol_setmfn(new_mfn); + +} + +static void mainwindow_csm_activate() { + + const char *new_csm; + + assert(mainwindow.csm_entry != NULL); + assert(mainwindow.csm == NULL); + + new_csm = gtk_entry_get_text(GTK_ENTRY(mainwindow.csm_entry)); + debug_print("MA: New CSM: %s\n", new_csm); + + msnprotocol_setcsm(new_csm); + + /* Switch back to a label... */ + mainwindow_csm_labelize(); + +} + +static int mainwindow_fname_keypress(GtkWidget *widget, GdkEventKey *event) { + + if ( event->keyval == GDK_Escape ) { + mainwindow_fname_labelize(); + } + + return 0; + +} + +static int mainwindow_csm_keypress(GtkWidget *widget, GdkEventKey *event) { + + if ( event->keyval == GDK_Escape ) { + mainwindow_csm_labelize(); + } + + return 0; + +} + +void mainwindow_fname_startchange() { + + const char *old_mfn; + char *mfn_decode; + + if ( mainwindow.fname_entry != NULL ) { + /* Already editing! */ + return; + } + + if ( msnprotocol_signedin() != 1 ) { + /* Not ready. */ + return; + } + + old_mfn = listcache_getmfn(); + mfn_decode = routines_urldecode(old_mfn); + + /* Replace friendly name display with editable box. */ + gtk_widget_destroy(mainwindow.friendlyname); + mainwindow.friendlyname = NULL; + mainwindow.fname_entry = gtk_entry_new(); + assert(mainwindow.fname_entry != NULL); + gtk_entry_set_width_chars(GTK_ENTRY(mainwindow.fname_entry), strlen(mfn_decode)); + gtk_entry_set_text(GTK_ENTRY(mainwindow.fname_entry), mfn_decode); + g_signal_connect(GTK_OBJECT(mainwindow.fname_entry), "activate", GTK_SIGNAL_FUNC(mainwindow_fname_activate), NULL); + g_signal_connect(GTK_OBJECT(mainwindow.fname_entry), "key-press-event", GTK_SIGNAL_FUNC(mainwindow_fname_keypress), NULL); + gtk_container_add(GTK_CONTAINER(mainwindow.fname_eventbox), mainwindow.fname_entry); + gtk_widget_show(mainwindow.fname_entry); + + gtk_editable_set_position(GTK_EDITABLE(mainwindow.fname_entry), -1); + gtk_widget_grab_focus(mainwindow.fname_entry); + + free(mfn_decode); + +} + +void mainwindow_csm_startchange() { + + char *old_csm; + + if ( mainwindow.csm_entry != NULL ) { + /* Already editing! */ + return; + } + + if ( msnprotocol_signedin() != 1 ) { + /* Not ready. */ + return; + } + + if ( !GTK_WIDGET_VISIBLE(mainwindow.csm_eventbox) ) { + + /* CSM bar is hidden... better fix that first... */ + GtkAction *action; + + action = gtk_action_group_get_action(mainwindow.action_group, "ShowCSMAction"); + if ( action != NULL ) { + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE); + gtk_widget_show(mainwindow.csm_eventbox); + } else { + debug_print("MA: Whoops! Couldn't find the Action to reveal the CSM widget.\n"); + } + + } + + old_csm = listcache_getcsm_noentities(); + + /* Replace friendly name display with editable box. */ + gtk_widget_destroy(mainwindow.csm); + mainwindow.csm = NULL; + mainwindow.csm_entry = gtk_entry_new(); + assert(mainwindow.csm_entry != NULL); + gtk_entry_set_width_chars(GTK_ENTRY(mainwindow.csm_entry), strlen(old_csm)); + gtk_entry_set_text(GTK_ENTRY(mainwindow.csm_entry), old_csm); + free(old_csm); + g_signal_connect(GTK_OBJECT(mainwindow.csm_entry), "activate", GTK_SIGNAL_FUNC(mainwindow_csm_activate), NULL); + g_signal_connect(GTK_OBJECT(mainwindow.csm_entry), "key-press-event", GTK_SIGNAL_FUNC(mainwindow_csm_keypress), NULL); + gtk_container_add(GTK_CONTAINER(mainwindow.csm_eventbox), mainwindow.csm_entry); + gtk_widget_show(mainwindow.csm_entry); + + gtk_editable_set_position(GTK_EDITABLE(mainwindow.csm_entry), -1); + gtk_widget_grab_focus(mainwindow.csm_entry); + +} + +/* Entry point when local friendly name is clicked (to change it) */ +static void mainwindow_fname_clicked(GtkWidget *widget, GdkEventButton *button) { + + if ( button->type != GDK_2BUTTON_PRESS ) { + return; + } + + mainwindow_fname_startchange(); + +} + +static void mainwindow_csm_clicked(GtkWidget *widget, GdkEventButton *button) { + + if ( button->type != GDK_2BUTTON_PRESS ) { + return; + } + + mainwindow_csm_startchange(); + +} + +/* Called by src/msnprotocol.c's CHG handler to update the window. Normally, this won't change anything + because the user will just have set the same setting themself. The exception is at sign-in, when + the initial setting does not come from a direct click on the combo box. */ +void mainwindow_forcestatus(OnlineState status) { + + int newstatus; + + debug_print("MA: mainwindow_forcestatus: %i\n", status); + + /* Order of items in this list MUST match those in mainwindow_setonline() */ + switch ( status ) { + + case ONLINE_NLN : newstatus = 0; break; + case ONLINE_AWY : newstatus = 1; break; + case ONLINE_BSY : newstatus = 2; break; + case ONLINE_BRB : newstatus = 3; break; + case ONLINE_PHN : newstatus = 4; break; + case ONLINE_LUN : newstatus = 5; break; + case ONLINE_HDN : newstatus = 6; break; + default : newstatus = -1; break; /* Should never happen */ + + } + + gtk_combo_box_set_active(GTK_COMBO_BOX(mainwindow.setstatus), newstatus); + +} + +/* Combobox "changed" handler. */ +static int mainwindow_setstatus() { + + int newstatus; + OnlineState status; + + g_object_get(G_OBJECT(mainwindow.setstatus), "active", &newstatus, NULL); + debug_print("MA: mainwindow_setstatus: %i\n", newstatus); + + /* Order of items in this list MUST match those in mainwindow_setonline() */ + switch ( newstatus ) { + + case 0 : status = ONLINE_NLN; break; + case 1 : status = ONLINE_AWY; break; + case 2 : status = ONLINE_BSY; break; + case 3 : status = ONLINE_BRB; break; + case 4 : status = ONLINE_PHN; break; + case 5 : status = ONLINE_LUN; break; + case 6 : status = ONLINE_HDN; break; + default : status = ONLINE_ERR; break; + + } + + msnprotocol_setstatus(status); + + /* If this DIDN'T originate from the menu, update the menu. */ + if ( mainwindow.was_menustatuschange ) { + debug_print("MA: Status change originated from menu.\n"); + mainwindow.was_menustatuschange = 0; + } else { + GtkAction *action; + debug_print("MA: Updating menu. \n"); + switch ( status ) { + case ONLINE_NLN : action = gtk_action_group_get_action(mainwindow.action_group, "StatusNLNAction"); break; + case ONLINE_AWY : action = gtk_action_group_get_action(mainwindow.action_group, "StatusAWYAction"); break; + case ONLINE_BSY : action = gtk_action_group_get_action(mainwindow.action_group, "StatusBSYAction"); break; + case ONLINE_BRB : action = gtk_action_group_get_action(mainwindow.action_group, "StatusBRBAction"); break; + case ONLINE_PHN : action = gtk_action_group_get_action(mainwindow.action_group, "StatusPHNAction"); break; + case ONLINE_LUN : action = gtk_action_group_get_action(mainwindow.action_group, "StatusLUNAction"); break; + case ONLINE_HDN : action = gtk_action_group_get_action(mainwindow.action_group, "StatusHDNAction"); break; + default : action = gtk_action_group_get_action(mainwindow.action_group, "StatusNLNAction"); break; + } + mainwindow.just_updating = 1; + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE); + } + + return FALSE; + +} + +/* Put the main window into its main "online" state */ +void mainwindow_setonline() { + + gfloat yalign; + GtkWidget *menuitem; + char *csm; + + if (mainwindow.state == MAINWINDOW_STATE_DISPATCH) { + mainwindow_unsetdispatch(); + } else if (mainwindow.state == MAINWINDOW_STATE_ONLINE) { + return; /* Nothing to do! */ + } + assert(mainwindow.state == MAINWINDOW_STATE_UNDEFINED); + + mainwindow.online_vbox = gtk_vbox_new(FALSE, 0); + mainwindow.offline_vbox = gtk_vbox_new(FALSE, 0); + mainwindow.top_vbox = gtk_vbox_new(FALSE, 0); + mainwindow.status_vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(mainwindow.top_hbox), mainwindow.top_vbox, TRUE, TRUE, 5); + + mainwindow.fname_eventbox = gtk_event_box_new(); + assert(mainwindow.fname_eventbox != NULL); + mainwindow.friendlyname = gtk_label_new("Connecting..."); + gtk_widget_set_usize(GTK_WIDGET(mainwindow.friendlyname), 10, -1); + assert(mainwindow.friendlyname != NULL); + gtk_container_add(GTK_CONTAINER(mainwindow.fname_eventbox), mainwindow.friendlyname); + gtk_box_pack_start(GTK_BOX(mainwindow.top_vbox), mainwindow.fname_eventbox, TRUE, TRUE, 5); + gtk_misc_get_alignment(GTK_MISC(mainwindow.friendlyname), NULL, &yalign); + gtk_misc_set_alignment(GTK_MISC(mainwindow.friendlyname), 0, yalign); + g_signal_connect(GTK_OBJECT(mainwindow.fname_eventbox), "button-press-event", GTK_SIGNAL_FUNC(mainwindow_fname_clicked), NULL); + + mainwindow.csm_eventbox = gtk_event_box_new(); + assert(mainwindow.csm_eventbox != NULL); + mainwindow.csm = gtk_label_new(NULL); + gtk_widget_set_usize(GTK_WIDGET(mainwindow.csm), 10, -1); + assert(mainwindow.csm != NULL); + gtk_container_add(GTK_CONTAINER(mainwindow.csm_eventbox), mainwindow.csm); + gtk_box_pack_start(GTK_BOX(mainwindow.top_vbox), mainwindow.csm_eventbox, TRUE, TRUE, 5); + gtk_misc_get_alignment(GTK_MISC(mainwindow.csm), NULL, &yalign); + gtk_misc_set_alignment(GTK_MISC(mainwindow.csm), 0, yalign); + g_signal_connect(GTK_OBJECT(mainwindow.csm_eventbox), "button-press-event", GTK_SIGNAL_FUNC(mainwindow_csm_clicked), NULL); + + csm = listcache_getcsm(); + mainwindow_setcsm(csm); + free(csm); + + mainwindow.setstatus = gtk_combo_box_new_text(); + gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Online"); + gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Away"); + gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Busy"); + gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Be Right Back"); + gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "On the Phone"); + gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Out to Lunch"); + gtk_combo_box_append_text(GTK_COMBO_BOX(mainwindow.setstatus), "Appear Offline"); + gtk_signal_connect_object(GTK_OBJECT(mainwindow.setstatus), "changed", GTK_SIGNAL_FUNC(mainwindow_setstatus), NULL); + gtk_box_pack_end(GTK_BOX(mainwindow.status_vbox), mainwindow.setstatus, TRUE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(mainwindow.top_hbox), mainwindow.status_vbox, FALSE, FALSE, 0); + + /* Add "Online" and "Offline" headings */ + mainwindow.online_label = gtk_label_new("---- Online ----"); + gtk_box_pack_start(GTK_BOX(mainwindow.online_vbox), mainwindow.online_label, FALSE, FALSE, 0); + mainwindow.offline_label = gtk_label_new("---- Offline ----"); + gtk_box_pack_start(GTK_BOX(mainwindow.offline_vbox), mainwindow.offline_label, FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(mainwindow.vbox), mainwindow.online_vbox, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(mainwindow.vbox), mainwindow.offline_vbox, FALSE, FALSE, 0); + gtk_widget_set_usize(GTK_WIDGET(mainwindow.offline_vbox), 10, -1); + gtk_widget_set_usize(GTK_WIDGET(mainwindow.online_vbox), 10, -1); + gtk_widget_set_usize(GTK_WIDGET(mainwindow.offline_label), 10, -1); + gtk_widget_set_usize(GTK_WIDGET(mainwindow.online_label), 10, -1); + + gtk_widget_show(mainwindow.online_label); + gtk_widget_show(mainwindow.offline_label); + gtk_widget_show(mainwindow.online_vbox); + if ( !options_hideoffline() ) { + gtk_widget_show(mainwindow.offline_vbox); + } + gtk_widget_show(mainwindow.friendlyname); + gtk_widget_show(mainwindow.setstatus); + gtk_widget_show(mainwindow.fname_eventbox); + if ( !options_hidecsm() ) { + gtk_widget_show(mainwindow.csm_eventbox); + } + gtk_widget_show(mainwindow.csm); + gtk_widget_show(mainwindow.top_vbox); + gtk_widget_show(mainwindow.status_vbox); + + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_out"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_in"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/account"); + gtk_widget_set_sensitive(menuitem, FALSE); + + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/show_offline"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/show_csm"); + gtk_widget_set_sensitive(menuitem, TRUE); + + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_nln"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_awy"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_bsy"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_brb"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_phn"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_lun"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_hdn"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_name"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_csm"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_avatar"); + gtk_widget_set_sensitive(menuitem, TRUE); + + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/add_contact"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/list_cleanup"); + gtk_widget_set_sensitive(menuitem, TRUE); + + mainwindow.state = MAINWINDOW_STATE_ONLINE; + +} + +void mainwindow_kickdispatch() { + + const char *username; + username = options_username(); + + if ( mainwindow.state != MAINWINDOW_STATE_DISPATCH ) { + return; + } + + if ( (strlen(username) != 0) && (strlen(options_password()) != 0) && (strlen(options_hostname()) != 0) ) { + + GtkWidget *menuitem; + + strcpy(mainwindow.signin_text, "Not signed in.\n\n"); + strncat(mainwindow.signin_text, username, 200); + mainwindow.signin_text[255] = '\0'; + strcat(mainwindow.signin_text, "\n\nClick here to connect"); + mainwindow.signin_text[255] = '\0'; + gtk_label_set_text(GTK_LABEL(GTK_BIN(mainwindow.signin_button)->child), mainwindow.signin_text); + + gtk_widget_set_sensitive(mainwindow.signin_button, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_in"); + gtk_widget_set_sensitive(menuitem, TRUE); + + } else { + + GtkWidget *menuitem; + + gtk_widget_set_sensitive(mainwindow.signin_button, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_in"); + gtk_widget_set_sensitive(menuitem, FALSE); + + strcpy(mainwindow.signin_text, "Please configure your account details"); + gtk_label_set_text(GTK_LABEL(GTK_BIN(mainwindow.signin_button)->child), mainwindow.signin_text); + /* If this is being called from accountwindow_apply(), this won't re-open the already-open window. */ + accountwindow_open(); + + } + +} + +/* Set the main window up ready for sign in */ +void mainwindow_setdispatch() { + + GtkWidget *menuitem; + + if (mainwindow.state == MAINWINDOW_STATE_DISPATCH) { + return; /* Nothing to do! */ + } else if (mainwindow.state == MAINWINDOW_STATE_ONLINE) { + mainwindow_unsetonline(); + } + assert(mainwindow.state == MAINWINDOW_STATE_UNDEFINED); + + /* The big "Sign In" button. */ + mainwindow.signin_button = gtk_button_new_with_label(""); + gtk_label_set_justify(GTK_LABEL(GTK_BIN(mainwindow.signin_button)->child), GTK_JUSTIFY_CENTER); + gtk_signal_connect(GTK_OBJECT(mainwindow.signin_button), "clicked", GTK_SIGNAL_FUNC(mainwindow_signin), NULL); + + mainwindow.signin_text = malloc(256); + assert(mainwindow.signin_text != NULL); + + gtk_box_pack_start(GTK_BOX(mainwindow.vbox), mainwindow.signin_button, TRUE, TRUE, 5); + + GTK_WIDGET_SET_FLAGS(mainwindow.signin_button, GTK_CAN_DEFAULT); + gtk_widget_grab_default(mainwindow.signin_button); + gtk_widget_grab_focus(mainwindow.signin_button); + + gtk_widget_show(mainwindow.signin_button); + + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_out"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/sign_in"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/connection/account"); + gtk_widget_set_sensitive(menuitem, TRUE); + + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/show_offline"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/show_groups"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/show_csm"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/sort_alphabet"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/view/sort_activity"); + gtk_widget_set_sensitive(menuitem, FALSE); + + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_nln"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_awy"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_bsy"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_brb"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_phn"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_lun"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/status_hdn"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_name"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_csm"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/status/change_avatar"); + gtk_widget_set_sensitive(menuitem, FALSE); + + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/add_contact"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/blockallow_lists"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/reverse_list"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(mainwindow.ui, "/mainwindow/tools/list_cleanup"); + gtk_widget_set_sensitive(menuitem, FALSE); + + /* This ensures that the subsequent change to ONLINE_HDN when signing in actually constitutes a change of the menu bar. */ + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_action_group_get_action(mainwindow.action_group, "StatusNLNAction")), TRUE); + + mainwindow.state = MAINWINDOW_STATE_DISPATCH; + mainwindow_kickdispatch(); + +} + +static void mainwindow_addui_callback(GtkUIManager *ui, GtkWidget *widget, GtkContainer *container) { + + gtk_box_pack_start(GTK_BOX(container), widget, FALSE, FALSE, 0); + + /* Enable overflow menu if this is a toolbar */ + if ( GTK_IS_TOOLBAR(widget) ) { + gtk_toolbar_set_show_arrow(GTK_TOOLBAR(widget), TRUE); + } + +} + +static void mainwindow_connection_signout() { + + if ( !msnprotocol_disconnected() ) { + msnprotocol_setstatus(ONLINE_FLN); + } else { + debug_print("MA: Try signing in first.\n"); + } + +} + +static int mainwindow_confirm_quit_response(GtkWidget *widget, int response) { + + if ( response != GTK_RESPONSE_YES ) { + gtk_widget_destroy(widget); + return FALSE; + } + + /* Sign out */ + if ( msnprotocol_signedin() ) { + mainwindow.quitting = 1; + msnprotocol_setstatus(ONLINE_FLN); + } else { + gtk_widget_destroy(mainwindow.window); + } + + return FALSE; + +} + +static int mainwindow_confirm_quit() { + + GtkWidget *window; + + window = gtk_message_dialog_new(GTK_WINDOW(mainwindow.window), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "Are you sure you want to quit TuxMessenger?"); + g_signal_connect(GTK_DIALOG(window), "response", GTK_SIGNAL_FUNC(mainwindow_confirm_quit_response), NULL); + gtk_widget_show_all(window); + + return TRUE; + +} + +static int mainwindow_connection_quit() { + + mainwindow_confirm_quit(); + return FALSE; + +} + +static int mainwindow_togglecsm(GtkWidget *widget) { + + gboolean active; + GtkAction *action; + + action = gtk_action_group_get_action(mainwindow.action_group, "ShowCSMAction"); + assert(action != NULL); + active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)); + + if ( active ) { + + /* Show CSM */ + gtk_widget_show(mainwindow.csm_eventbox); + options_sethidecsm(FALSE); + + } else { + + /* Hide CSM - see also mainwindow_sethidecsm() above. */ + if ( mainwindow.csm_entry != NULL ) { + mainwindow_csm_labelize(); + } + gtk_widget_hide(mainwindow.csm_eventbox); + options_sethidecsm(TRUE); + + } + options_save(); + + return FALSE; + +} + +static int mainwindow_toggleoffline(GtkWidget *widget) { + + gboolean active; + GtkAction *action; + + if ( !msnprotocol_signedin() ) { + debug_print("MA: Can't change that before signing in...\n"); + return FALSE; + } + + action = gtk_action_group_get_action(mainwindow.action_group, "ShowOfflineAction"); + assert(action != NULL); + active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)); + + if ( active ) { + /* Show Offline Contacts */ + gtk_widget_show(mainwindow.offline_vbox); + options_sethideoffline(FALSE); + } else { + /* Hide Offline Contacts */ + gtk_widget_hide(mainwindow.offline_vbox); + options_sethideoffline(TRUE); + } + options_save(); + + return FALSE; + +} + +static int mainwindow_menubarstatuschange(GtkRadioAction *action, GtkRadioAction *current, gpointer data) { + + debug_print("MA: mainwindow_menubarstatuschange\n"); + + if ( !msnprotocol_signedin() ) { + debug_print("MA: Not signed in. Ignoring fixup change.\n"); + return FALSE; + } + + if ( mainwindow.just_updating ) { + mainwindow.just_updating = 0; + debug_print("MA: Got callback for just updating menu bar.\n"); + return FALSE; + } + + debug_print("MA: Menu bar status change.\n"); + mainwindow.was_menustatuschange = 1; + mainwindow.just_updating = 0; + mainwindow_forcestatus(gtk_radio_action_get_current_value(action)); + + return FALSE; + +} + +static gint mainwindow_deletecontact(GtkWidget *widget, gpointer data) { + + printf("MA: Chosen to delete '%s'\n", mainwindow.menu_subject); + + #if 0 + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *icon; + AddContactWindow *item; + + item = malloc(sizeof(AddContactWindow)); + + item->window = gtk_dialog_new_with_buttons("Add New Contact", mainwindow_gtkwindow(), GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + g_signal_connect(item->window, "response", G_CALLBACK(addcontact_response), item); + g_signal_connect_swapped(item->window, "response", G_CALLBACK(gtk_widget_destroy), item->window); + g_signal_connect(item->window, "destroy", G_CALLBACK(addcontact_destroyed), item); + + hbox = gtk_hbox_new(FALSE, 20); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(item->window)->vbox), hbox); + icon = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_DIALOG); + gtk_box_pack_start(GTK_BOX(hbox), icon, TRUE, TRUE, 0); + + vbox = gtk_vbox_new(FALSE, 5); + gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0); + + gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new("Enter the Passport address of your new contact:"), TRUE, TRUE, 0); + + item->username_entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(vbox), item->username_entry, TRUE, TRUE, 0); + g_signal_connect(item->username_entry, "activate", G_CALLBACK(addcontact_activate), item); + + item->allow_toggle = gtk_check_button_new_with_label("Allow this new contact to message you."); + gtk_box_pack_start(GTK_BOX(vbox), item->allow_toggle, TRUE, TRUE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item->allow_toggle), TRUE); + + gtk_container_set_border_width(GTK_CONTAINER(hbox), 10); + gtk_widget_show_all(item->window); + #endif + + return 0; + +} + +static void mainwindow_addmenubar() { + + GtkActionEntry entries[] = { + + { "ConnectionAction", NULL, "_Connection", NULL, NULL, NULL }, + + #ifdef HAVE_GTK_2_6_0 + { "SignInAction", GTK_STOCK_CONNECT, "_Sign In", NULL, NULL, G_CALLBACK(mainwindow_signin) }, + { "SignOutAction", GTK_STOCK_DISCONNECT, "Sign _Out", NULL, NULL, G_CALLBACK(mainwindow_connection_signout) }, + { "AboutAction", GTK_STOCK_ABOUT, "_About TuxMessenger", NULL, NULL, G_CALLBACK(about_open) }, + #else /* HAVE_GTK_2_6_0 */ + { "SignInAction", NULL, "_Sign In", NULL, NULL, G_CALLBACK(mainwindow_signin) }, + { "SignOutAction", NULL, "Sign _Out", NULL, NULL, G_CALLBACK(mainwindow_connection_signout) }, + { "AboutAction", NULL, "_About TuxMessenger", NULL, NULL, G_CALLBACK(about_open) }, + #endif /* HAVE_GTK_2_6_0 */ + + { "AccountAction", GTK_STOCK_PROPERTIES, "_Account Details...", NULL, NULL, G_CALLBACK(accountwindow_open) }, + { "QuitAction", GTK_STOCK_QUIT, NULL, "<control>Q", "Quit the application", G_CALLBACK(mainwindow_connection_quit) }, + + { "ViewAction", NULL, "_View", NULL, NULL, NULL }, + + { "StatusAction", NULL, "_Status", NULL, NULL, NULL }, + { "ChangeNameAction", NULL, "Change Screen _Name...", NULL, NULL, G_CALLBACK(mainwindow_fname_startchange) }, + { "ChangeCSMAction", NULL, "Change Custom _Message...", NULL, NULL, G_CALLBACK(mainwindow_csm_startchange) }, + { "ChangeAvatarAction", NULL, "Change A_vatar...", NULL, NULL, NULL }, + + { "ToolsAction", NULL, "_Tools", NULL, NULL, NULL }, + { "AddContactAction", GTK_STOCK_ADD, "_Add Contact...", NULL, NULL,G_CALLBACK(addcontact_open) }, + { "BlockAllowAction", NULL, "_Block and Allow Lists", NULL, NULL, NULL }, + { "ReverseAction", NULL, "_Reverse List", NULL, NULL, NULL }, + { "PreferencesAction", GTK_STOCK_PREFERENCES, "_Preferences...", NULL, NULL, G_CALLBACK(prefswindow_open) }, + { "ListCleanupAction", NULL, "_List Cleanup...", NULL, NULL, G_CALLBACK(listcleanup_open) }, + + { "HelpAction", NULL, "_Help", NULL, NULL, NULL }, + + { "ContactMessageAction", GTK_STOCK_NEW, "New _Message", NULL, NULL, NULL }, + { "ContactSendFileAction", GTK_STOCK_OPEN, "Send _File", NULL, NULL, NULL }, + { "ContactBlockAction", GTK_STOCK_NO, "_Block Contact", NULL, NULL, NULL }, + { "ContactUnblockAction", GTK_STOCK_YES, "_Unblock Contact", NULL, NULL, NULL }, + { "ContactDeleteAction", GTK_STOCK_DELETE, "_Delete Contact", NULL, NULL, G_CALLBACK(mainwindow_deletecontact) }, + + }; + GtkToggleActionEntry toggles[] = { + + { "ShowCSMAction", NULL, "Show _Custom Status Message", NULL, NULL, G_CALLBACK(mainwindow_togglecsm), !options_hidecsm() }, + { "ShowOfflineAction", NULL, "Show _Offline Contacts", NULL, NULL, G_CALLBACK(mainwindow_toggleoffline), !options_hideoffline() }, + { "ShowGroupsAction", NULL, "Show Contact _Groups", NULL, NULL, NULL, FALSE }, + + }; + GtkRadioActionEntry radios_status[] = { + + { "StatusNLNAction", NULL, "_Online", "<control>O", NULL, ONLINE_NLN }, + { "StatusAWYAction", NULL, "_Away", "<control>A", NULL, ONLINE_AWY }, + { "StatusBSYAction", NULL, "_Busy", NULL, NULL, ONLINE_BSY }, + { "StatusBRBAction", NULL, "Be _Right Back", "<control>B", NULL, ONLINE_BRB }, + { "StatusPHNAction", NULL, "On The _Phone", NULL, NULL, ONLINE_PHN }, + { "StatusLUNAction", NULL, "Out To _Lunch", "<control>L", NULL, ONLINE_LUN }, + { "StatusHDNAction", NULL, "Appear O_ffline", NULL, NULL, ONLINE_HDN }, + + }; + GtkRadioActionEntry radios_sorting[] = { + + { "SortAlphaAction", GTK_STOCK_SORT_ASCENDING, "Sort Contacts _Alphabetically", NULL, NULL, LISTSORT_ALPHA }, + { "SortActiveAction", NULL, "_Sort Contacts by Activity", NULL, NULL, LISTSORT_ACTIVITY }, + + }; + + guint n_entries = G_N_ELEMENTS(entries); + guint n_toggles = G_N_ELEMENTS(toggles); + guint n_radios_status = G_N_ELEMENTS(radios_status); + guint n_radios_sorting = G_N_ELEMENTS(radios_sorting); + GError *error = NULL; + + mainwindow.action_group = gtk_action_group_new("TuxMessenger"); + gtk_action_group_add_actions(mainwindow.action_group, entries, n_entries, NULL); + gtk_action_group_add_toggle_actions(mainwindow.action_group, toggles, n_toggles, NULL); + gtk_action_group_add_radio_actions(mainwindow.action_group, radios_status, n_radios_status, ONLINE_NLN, G_CALLBACK(mainwindow_menubarstatuschange), NULL); + gtk_action_group_add_radio_actions(mainwindow.action_group, radios_sorting, n_radios_sorting, LISTSORT_ACTIVITY, NULL, NULL); + mainwindow.ui = gtk_ui_manager_new(); + gtk_ui_manager_insert_action_group(mainwindow.ui, mainwindow.action_group, 0); + g_signal_connect(mainwindow.ui, "add_widget", G_CALLBACK(mainwindow_addui_callback), mainwindow.bigvbox); + gtk_widget_show(mainwindow.bigvbox); + if ( gtk_ui_manager_add_ui_from_file(mainwindow.ui, DATADIR"/tuxmessenger/mainwindow.ui", &error) == 0 ) { + + /* :( */ + debug_print("MA: Error loading main window menu bar: %s\n", error->message); + exit(1); + + } + + gtk_window_add_accel_group(GTK_WINDOW(mainwindow.window), gtk_ui_manager_get_accel_group(mainwindow.ui)); + gtk_ui_manager_ensure_update(mainwindow.ui); + +} + +/* Create and open the main window, in dispatch state */ +void mainwindow_open() { + + /* Create the main window. */ + mainwindow.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_signal_connect(GTK_OBJECT(mainwindow.window), "destroy", GTK_SIGNAL_FUNC(mainwindow_destroyed), NULL); + mainwindow.bigvbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(mainwindow.window), mainwindow.bigvbox); + + /* Set the title and size. */ + gtk_window_set_title(GTK_WINDOW(mainwindow.window), "TuxMessenger"); + gtk_window_set_default_size(GTK_WINDOW(mainwindow.window), 200, 500); + + /* Add menu bar */ + mainwindow_addmenubar(); + + /* Create a scrolled window to hold the contacts. */ + mainwindow.swindow = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mainwindow.swindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_box_pack_end(GTK_BOX(mainwindow.bigvbox), mainwindow.swindow, TRUE, TRUE, 0); + + /* Structure common to both states */ + mainwindow.vbox = gtk_vbox_new(FALSE, 0); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(mainwindow.swindow), GTK_WIDGET(mainwindow.vbox)); + mainwindow.top_hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(mainwindow.vbox), mainwindow.top_hbox, FALSE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(mainwindow.vbox), 6); + + g_signal_connect(G_OBJECT(mainwindow.window), "delete-event", GTK_SIGNAL_FUNC(mainwindow_confirm_quit), NULL); + mainwindow.state = MAINWINDOW_STATE_UNDEFINED; + mainwindow_setdispatch(); + gtk_widget_show_all(mainwindow.window); + +} + +/* Trivial function to add an accelerator group to the window, e.g. for the "statusmenu" */ +void mainwindow_addaccelgroup(GtkAccelGroup *accel_group) { + gtk_window_add_accel_group(GTK_WINDOW(mainwindow.window), accel_group); +} + +static char *mainwindow_decodestatus(OnlineState status) { + + if ( status == ONLINE_NLN ) { + return ""; + } else if ( status == ONLINE_AWY ) { + return "[Away] "; + } else if ( status == ONLINE_IDL ) { + return "[Idle] "; + } else if ( status == ONLINE_BSY ) { + return "[Busy] "; + } else if ( status == ONLINE_BRB ) { + return "[BRB] "; + } else if ( status == ONLINE_PHN ) { + return "[Phone] "; + } else if ( status == ONLINE_LUN ) { + return "[Food] "; + } + + return "[Whoops] "; + +} + +/* Called from src/msnprotocol.c to set the UI version of the local user's friendly name */ +void mainwindow_setmfn(const char *new_friendlyname) { + + char *final_fname; + char *decoded_fname; + + assert(new_friendlyname != NULL); + assert(mainwindow.friendlyname != NULL); + + decoded_fname = routines_urldecode(new_friendlyname); + + final_fname = malloc(strlen(new_friendlyname) + 11); + sprintf(final_fname, "––– %s", decoded_fname); + gtk_label_set_text(GTK_LABEL(mainwindow.friendlyname), final_fname); + free(final_fname); + free(decoded_fname); + +} + +void mainwindow_setcsm(const char *new_csm) { + + char *csm_markup; + char *new_csm_edited; + + assert(new_csm != NULL); + assert(mainwindow.csm != NULL); + + csm_markup = malloc(strlen(new_csm)+50); + new_csm_edited = routines_killtriangles(new_csm); + sprintf(csm_markup, "<span foreground=\"#777777\" style=\"italic\">%s</span>", new_csm_edited); + + gtk_label_set_markup(GTK_LABEL(mainwindow.csm), csm_markup); + free(new_csm_edited); + free(csm_markup); + +} + +static void mainwindow_adjustmenu_popup(GtkWidget *adjust_button, char *username, guint button, guint time) { + + GtkWidget *menu; + + menu = gtk_ui_manager_get_widget(mainwindow.ui, "/ui/contact"); + + /* It is conceivable that neither of these would be true */ + if ( contactlist_isonlist("BL", username) ) { + GtkWidget *disable = gtk_ui_manager_get_widget(mainwindow.ui, "/ui/contact/block"); + gtk_widget_set_sensitive(GTK_WIDGET(disable), FALSE); + } + if ( contactlist_isonlist("AL", username) ) { + GtkWidget *disable = gtk_ui_manager_get_widget(mainwindow.ui, "/ui/contact/unblock"); + gtk_widget_set_sensitive(GTK_WIDGET(disable), FALSE); + } + + + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, button, time); + mainwindow.menu_subject = username; + +} + +/* Contact button "event" */ +static gint mainwindow_contact_event(GtkWidget *button, GdkEvent *event, char *username) { + + if ( event->type == GDK_BUTTON_PRESS ) { + if ( ((GdkEventButton *)event)->button == 3 ) { + mainwindow_adjustmenu_popup(button, username, ((GdkEventButton *)event)->button, ((GdkEventButton *)event)->time); + } + } + + return 0; + +} + +/* Contact button clicked */ +static gint mainwindow_contact_clicked(GtkWidget *button, char *username) { + + if ( msnprotocol_status() == ONLINE_HDN ) { + error_report("You can't send messages while you are Invisible!"); + return 0; + } + + messagewindow_create_if_none(username, NULL); + + return 0; + +} + +static void mainwindow_userdnd_get(GtkWidget *widget, GdkDragContext *drag_context, GtkSelectionData *seldata, guint info, guint time, unsigned char *username) { + + debug_print("MA: DnD: sending '%s'\n", username); + gtk_selection_data_set(seldata, gdk_atom_intern("STRING", FALSE), 0, username, strlen((char *)username)+1); + +} + +/* Neat feature: drag one user onto another to create a three-way conversation with them both. */ +static void mainwindow_userdnd_receive(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *seldata, guint info, guint time, char *username1) { + + char *username2; + + username2 = (char *)seldata->data; + + if ( msnprotocol_status() == ONLINE_HDN ) { + error_report("You can't send messages while you are Invisible!"); + return; + } + + if ( strcmp(username1, username2) == 0 ) { + debug_print("MA: Try just clicking...\n"); + return; + } + debug_print("MA: Pairing '%s' and '%s'...\n", username1, username2); + messagewindow_create(username1, sbsessions_create_threeway(username1, username2)); + +} + + +/* Do the UI business part of adding a contact to the UI */ +static void mainwindow_addcontactui(UIContact *uicontact, char *username, char *friendlyname, OnlineState status) { + + char *friendlynametext; + char *friendlynametext2; + char *final_fnametext; + GtkTargetEntry targets[1]; + char *csmtext; + char *csmtext2; + char *tooltips_string; + char *csmtext_noentities; + + assert(uicontact->hbox == NULL); + assert(uicontact->adjustbutton == NULL); + assert(uicontact->button == NULL); + assert(uicontact->label == NULL); + assert(uicontact->eventbox == NULL); + assert(uicontact->tooltips == NULL); + + assert(status != ONLINE_HDN); /* Doesn't happen for contacts */ + + /* Create the button */ + uicontact->button = gtk_button_new(); + assert(uicontact->button != NULL); + GTK_WIDGET_UNSET_FLAGS(uicontact->button, GTK_CAN_FOCUS); + gtk_button_set_relief(GTK_BUTTON(uicontact->button), GTK_RELIEF_NONE); + + uicontact->hbox = gtk_hbox_new(FALSE, 0); + assert(uicontact->hbox != NULL); + gtk_container_add(GTK_CONTAINER(uicontact->button), uicontact->hbox); + + uicontact->pixmap = statusicons_pixmap(status); + gtk_box_pack_start(GTK_BOX(uicontact->hbox), uicontact->pixmap, FALSE, FALSE, 0); + + /* Label containing status, friendly name and CSM */ + friendlynametext = routines_urldecode(friendlyname); + friendlynametext2 = routines_killtriangles_and_ampersands(friendlynametext); + if ( status != ONLINE_FLN ) { + + csmtext = contactlist_csm(username, 0); + csmtext_noentities = contactlist_csm(username, 1); + if ( csmtext == NULL ) { + csmtext = strdup(""); + } + if ( csmtext_noentities == NULL ) { + csmtext_noentities = strdup(""); + } + csmtext2 = routines_killtriangles(csmtext); + /* Up to eight characters of status indicator (five letters, two brackets, one space), + * 34 characters of status markup, 50 bytes of CSM markup, and one null terminator. */ + final_fnametext = malloc(strlen(friendlynametext2) + strlen(csmtext2) + 8 + 34 + 50 + 1); + sprintf(final_fnametext, "<span foreground=\"#777777\">%s</span>%s<span foreground=\"#777777\" style=\"italic\"> %s</span>", + mainwindow_decodestatus(status), friendlynametext2, csmtext2); + tooltips_string = malloc(strlen(friendlynametext) + strlen(csmtext_noentities) + strlen(username) + 3); + if ( strlen(csmtext_noentities) > 0 ) { + sprintf(tooltips_string, "%s\n%s\n%s", friendlynametext, username, csmtext_noentities); + } else { + sprintf(tooltips_string, "%s\n%s", friendlynametext, username); + } + free(csmtext_noentities); + free(friendlynametext); + free(friendlynametext2); + free(csmtext); + uicontact->label = gtk_label_new(friendlynametext); + gtk_label_set_markup(GTK_LABEL(uicontact->label), final_fnametext); + gtk_misc_set_alignment(GTK_MISC(uicontact->label), 0, 0.5); + assert(uicontact->label != NULL); + free(final_fnametext); + free(csmtext2); + + g_signal_connect(uicontact->button, "clicked", GTK_SIGNAL_FUNC(mainwindow_contact_clicked), (gpointer)username); + + } else { + + friendlynametext = routines_urldecode(friendlyname); + uicontact->label = gtk_label_new(friendlynametext); + assert(uicontact->label != NULL); + tooltips_string = malloc(strlen(friendlynametext) + strlen(username) + 2); + sprintf(tooltips_string, "%s\n%s", friendlynametext, username); + free(friendlynametext); + gtk_misc_set_alignment(GTK_MISC(uicontact->label), 0, 0.5); + + } + gtk_box_pack_start(GTK_BOX(uicontact->hbox), uicontact->label, TRUE, TRUE, 3); + + /* Packing and signal handling */ + if ( status != ONLINE_FLN ) { + gtk_box_pack_end(GTK_BOX(mainwindow.online_vbox), uicontact->button, TRUE, FALSE, 0); + } else { + gtk_box_pack_end(GTK_BOX(mainwindow.offline_vbox), uicontact->button, TRUE, FALSE, 0); + } + g_signal_connect(uicontact->button, "event", GTK_SIGNAL_FUNC(mainwindow_contact_event), (gpointer)username); + + /* Tooltips */ + uicontact->tooltips = gtk_tooltips_new(); + assert(uicontact->tooltips != NULL); + gtk_tooltips_set_tip(uicontact->tooltips, uicontact->button, tooltips_string, NULL); + free(tooltips_string); + + /* Drag and Drop */ + targets[0].target = "tm_username"; + targets[0].flags = GTK_TARGET_SAME_APP; + targets[0].info = 1; + gtk_drag_source_set(uicontact->button, GDK_BUTTON1_MASK, targets, 1, GDK_ACTION_COPY); +// gtk_drag_source_set_icon_pixbuf(uicontact->button, ); + g_signal_connect(uicontact->button, "drag-data-get", GTK_SIGNAL_FUNC(mainwindow_userdnd_get), (gpointer)username); + /* Each button is also a DnD recipient... for pairing users in three-way conversations.. */ + gtk_drag_dest_set(uicontact->button, GTK_DEST_DEFAULT_ALL, targets, 1, GDK_ACTION_COPY); + g_signal_connect(uicontact->button, "drag-data-received", GTK_SIGNAL_FUNC(mainwindow_userdnd_receive), (gpointer)username); + + /* Sort out the size of the new item */ + gtk_widget_set_usize(uicontact->hbox, 10, -1); + + gtk_widget_show_all(uicontact->button); + +} + +/* Called from src/contactlist.c to add a contact (on the FL) to the list UI + * This function does the extra work of creating the UIContact structure. */ +UIContact *mainwindow_addcontact(char *username, char *friendlyname, OnlineState status) { + + UIContact *uicontact; + + uicontact = malloc(sizeof(UIContact)); + assert(uicontact != NULL); + + uicontact->hbox = NULL; + uicontact->adjustbutton = NULL; + uicontact->button = NULL; + uicontact->label = NULL; + uicontact->eventbox = NULL; + uicontact->tooltips = NULL; + uicontact->pixmap = NULL; + + mainwindow_addcontactui(uicontact, username, friendlyname, status); + + return uicontact; + +} + +/* Set a contact's online status to "status" */ +void mainwindow_setcontactstatus(UIContact *uicontact, char *username, char *friendlyname, OnlineState status) { + + debug_print("MA: Setting contact status for %s.\n", username); + mainwindow_removecontact(uicontact); + mainwindow_addcontactui(uicontact, username, friendlyname, status); + +} + +void mainwindow_destroycontact(UIContact *uicontact) { + + /* This assumes the contact widgets have already been destroyed */ + free(uicontact); + +} + +/* Remove the UI parts of a contact (but leave the data structure intact) */ +void mainwindow_removecontact(UIContact *uicontact) { + + debug_print("MA: Removing a UIContact\n"); + if ( uicontact->button != NULL ) { + gtk_widget_destroy(uicontact->button); + } + uicontact->eventbox = NULL; + uicontact->hbox = NULL; + uicontact->adjustbutton = NULL; + uicontact->label = NULL; + uicontact->tooltips = NULL; + uicontact->button = NULL; + uicontact->pixmap = NULL; + +} + +/* Pass bits of information to src/pixmap.c so it can do its stuff */ +GtkStyle *mainwindow_style() { + return gtk_widget_get_style(mainwindow.window); +} + +GdkWindow *mainwindow_window() { + return mainwindow.window->window; +} + +GtkWindow *mainwindow_gtkwindow() { + return GTK_WINDOW(mainwindow.window); +} diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..971ca5f --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,61 @@ +/* + * mainwindow.h + * + * The main (contact list) window + * + * (c) 2002-2007 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "msngenerics.h" + +#include <gtk/gtk.h> +/* This is meant to be an opaque data type. Don't try anything clever... */ +typedef struct { + + GtkWidget *hbox; /* Overall hbox */ + GtkWidget *adjustbutton; /* Button with popup menu and icon */ + GtkWidget *button; /* The main button itself (only if online) */ + GtkWidget *label; /* Label containing the friendly name. */ + GtkWidget *eventbox; /* Eventbox for tooltips (only if offline) */ + GtkWidget *pixmap; /* Pixmap widget to go inside adjustbutton */ + GtkTooltips *tooltips; /* Tooltip containing the username and other details */ + +} UIContact; + +extern void mainwindow_open(void); +extern void mainwindow_setdispatch(void); +extern void mainwindow_setonline(void); +extern void mainwindow_setmfn(const char *new_friendlyname); +extern UIContact *mainwindow_addcontact(char *username, char *friendlyname, OnlineState status); +extern void mainwindow_setcontactstatus(UIContact *uicontact, char *username, char *friendlyname, OnlineState status); +extern void mainwindow_addaccelgroup(GtkAccelGroup *accel_group); +extern void mainwindow_destroycontact(UIContact *uicontact); +extern void mainwindow_removecontact(UIContact *uicontact); +extern GtkStyle *mainwindow_style(void); +extern GdkWindow *mainwindow_window(void); +extern GtkWindow *mainwindow_gtkwindow(void); +extern void mainwindow_fname_startchange(void); +extern void mainwindow_kickdispatch(void); +extern void mainwindow_forcestatus(OnlineState status); +extern void mainwindow_setcsm(const char *new_csm); + +#endif /* MAINWINDOW_H */ diff --git a/src/messagewindow.c b/src/messagewindow.c new file mode 100644 index 0000000..ae0326d --- /dev/null +++ b/src/messagewindow.c @@ -0,0 +1,1854 @@ +/* + * messagewindow.c + * + * IM window + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <glob.h> +#include <gdk/gdk.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <gdk/gdkkeysyms.h> + +#include "debug.h" +#include "sbsessions.h" +#include "contactlist.h" +#include "routines.h" +#include "avatars.h" +#include "sbprotocol.h" +#include "msnp2p.h" +#include "about.h" +#include "fonttrans.h" +#include "messagewindow.h" +#include "options.h" +#include "mime.h" +#include "filetrans.h" + +typedef enum { + MESSAGEWINDOW_MODE_TEXT, + MESSAGEWINDOW_MODE_INK +} MessageWindowMode; + +static MessageWindow *messagewindows; + +static MessageWindow *messagewindow_last() { + + MessageWindow *messagewindow = messagewindows; + + while ( messagewindow ) { + assert(messagewindow != NULL); + if ( messagewindow->next == NULL ) { + return messagewindow; + } else { + messagewindow = messagewindow->next; + } + } + + return NULL; /* If there were no messagewindows at all. */ + +} + +void messagewindow_plug(MessageWindow *messagewindow, SbSession *session) { + messagewindow->session = session; +} + +/* Unplug any message windows which are plugged into a given SB session */ +void messagewindow_unplug(SbSession *session) { + + MessageWindow *messagewindow = messagewindows; + + while ( messagewindow != NULL ) { + if ( messagewindow->session == session ) { + messagewindow->session = NULL; + } + messagewindow = messagewindow->next; + } + +} + +static void messagewindow_destroy(MessageWindow *messagewindow) { + + MessageWindow *prev; + MwUser *mwuser; + + prev = messagewindows; + if ( prev != messagewindow ) { + while ( prev != NULL ) { + assert(prev != NULL); + if ( prev->next == messagewindow ) { + break; + } else { + prev = prev->next; + } + } + assert(prev->next == messagewindow); + /* Link it out of the list. */ + prev->next = messagewindow->next; + } else { + /* This session was the first on the list. */ + assert(messagewindows == messagewindow); /* Can't fail... */ + messagewindows = messagewindow->next; /* Which may be NULL if the list is now empty. */ + } + + mwuser = messagewindow->users; + while ( mwuser != NULL ) { + + MwUser *next_user = mwuser->next; + free(mwuser->username); + free(mwuser); + mwuser = next_user; + + } + + if ( messagewindow->localcolour_gdk != NULL ) { + gdk_color_free(messagewindow->localcolour_gdk); + } + if ( messagewindow->localcolour_string != NULL ) { + free(messagewindow->localcolour_string); + } + if ( messagewindow->ocolour_gdk != NULL ) { + gdk_color_free(messagewindow->ocolour_gdk); + } + if ( messagewindow->ocolour_string != NULL ) { + free(messagewindow->ocolour_string); + } + if ( messagewindow->localfont != NULL ) { + free(messagewindow->localfont); + } + if ( messagewindow->dislocalfont != NULL ) { + free(messagewindow->dislocalfont); + } + if ( messagewindow->ofont != NULL ) { + free(messagewindow->ofont); + } + + free(messagewindow); + +} + +/* Find an IM window with exactly one given user. */ +static MessageWindow *messagewindow_find_single(const char *username) { + + MessageWindow *messagewindow = messagewindows; + + while ( messagewindow != NULL ) { + if ( (messagewindow->num_users == 1) && (strcmp(messagewindow->users->username, username) == 0) ) { + return messagewindow; + } + messagewindow = messagewindow->next; + } + + return NULL; + +} + +static MwUser *messagewindow_find_username(MessageWindow *messagewindow, const char *username) { + + MwUser *mwuser; + + assert(messagewindow != NULL); + + mwuser = messagewindow->users; + while ( mwuser != NULL ) { + + assert(mwuser != NULL); + + /* Case-insensitive here. Username may have different case depending + on its source, since the servers seem to change usernames to + lower case but clients might not in (e.g.) TypingUser controls. */ + if ( strcasecmp(mwuser->username, username) == 0 ) { + return mwuser; + } else { + mwuser = mwuser->next; + } + + } + + /* No users. */ + return NULL; + +} + +static MessageWindow *messagewindow_find_user(MwUser *mwuser) { + + MwUser *find; + MessageWindow *messagewindow; + + assert(mwuser != NULL); + messagewindow = messagewindows; + + while ( messagewindow != NULL ) { + + find = messagewindow->users; + while ( find != NULL ) { + + assert(find != NULL); + + if ( find == mwuser ) { + return messagewindow; + } else { + find = find->next; + } + + } + + messagewindow = messagewindow->next; + + } + + /* Not found. */ + return NULL; + +} + +static MwUser *messagewindow_lastuser(MessageWindow *messagewindow) { + + MwUser *mwuser; + + mwuser = messagewindow->users; + while ( mwuser != NULL ) { + + assert(mwuser != NULL); + + if ( mwuser->next == NULL ) { + return mwuser; + } else { + mwuser = mwuser->next; + } + + } + + /* No users. */ + return NULL; + +} + +/* User clicked "Nudge" button. */ +static void messagewindow_sendnudge(GtkWidget *widget, MessageWindow *messagewindow) { + + if ( messagewindow->session == NULL ) { + + debug_print("MW %8p: Not plugged in - sorting out.\n", messagewindow); + assert(messagewindow->num_users == 1); + assert(messagewindow->users != NULL); + messagewindow_plug(messagewindow, sbsessions_create_local(messagewindow->users->username)); + sbsessions_plug(messagewindow->session, messagewindow); + + } + + messagewindow_addtext_system(messagewindow, "You nudge."); + sbprotocol_sendnudge(messagewindow->session); + +} + +static void messagewindow_scrolltoend(MessageWindow *messagewindow) { + + GtkTextBuffer *buffer; + GtkTextIter iter; + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages)); + gtk_text_buffer_get_end_iter(buffer, &iter); + gtk_text_iter_set_line_offset(&iter, 0); + gtk_text_buffer_move_mark(buffer, messagewindow->mark, &iter); + gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(messagewindow->messages), messagewindow->mark, 0, TRUE, 1.0, 0.0); + +} + +static void messagewindow_size_allocate(GtkWidget *widget, GtkAllocation *allocation, MessageWindow *messagewindow) { + + if ( messagewindow->stuck ) { + messagewindow_scrolltoend(messagewindow); + } + +} + +static void messagewindow_scrolled(GtkAdjustment *adjustment, MessageWindow *messagewindow) { + + gdouble pos; + gdouble limit; + gdouble pagesize; + + /* See if the window is currently scrolled to the bottom. Make a note for later. */ + pos = gtk_adjustment_get_value(adjustment); + g_object_get(G_OBJECT(adjustment), "upper", &limit, "page-size", &pagesize, NULL); + + if ( pos == limit-pagesize ) { + messagewindow->stuck = TRUE; + } else { + messagewindow->stuck = FALSE; + } + +} + +/* Add "System" text to a window (like join/part messages) */ +static void messagewindow_addtext_system_nonewline(MessageWindow *messagewindow, const char *text) { + + GtkTextBuffer *buffer; + GtkTextIter iter; + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages)); + gtk_text_buffer_get_end_iter(buffer, &iter); + gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, text, -1, "system", NULL); + + if ( messagewindow->stuck ) { + messagewindow_scrolltoend(messagewindow); + } + +} + +/* Add "System" text to a window (like join/part messages), adding a newline if this isn't the first text. */ +void messagewindow_addtext_system(MessageWindow *messagewindow, const char *text) { + + /* Add PRECEDING newline if this isn't the first event. */ + if ( messagewindow->first_event == 0 ) { + messagewindow_addtext_system_nonewline(messagewindow, "\n\n"); + } else { + messagewindow->first_event = 0; + } + + messagewindow_addtext_system_nonewline(messagewindow, text); + + /* Assume this is the case. If this WAS a NAK, the caller should set it again straight AFTERWARDS. */ + messagewindow_set_last_was_nak(messagewindow, FALSE); + +} + +/* Add "User" text to a window (user messages) */ +void messagewindow_addtext_user_nonewline(MessageWindow *messagewindow, const char *text, int length, const char *colour, const char *font) { + + GtkTextBuffer *buffer; + GtkTextIter iter; + GtkTextTag *tag; + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages)); + + if ( colour != NULL ) { + if ( font != NULL ) { + tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, "foreground", colour, "font", font, NULL); + } else { + tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, "foreground", colour, NULL); + } + } else { + if ( font != NULL ) { + tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, "font", font, NULL); + } else { + tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, NULL); + } + } + + /* Window should have been opened by now. */ + assert(messagewindow != NULL); + + gtk_text_buffer_get_end_iter(buffer, &iter); + gtk_text_buffer_insert_with_tags(buffer, &iter, text, length, tag, NULL); + + if ( messagewindow->stuck ) { + messagewindow_scrolltoend(messagewindow); + } + +/* if ( GTK_WINDOW(record->message_window)->window_has_focus != 1 ) + messagewindow_flash(record); */ + +} + +/* This isn't needed, since user text is never preceded by a blank line +static void messagewindow_addtext_user(MessageWindow *messagewindow, char *text) { + + if ( messagewindow->first_event == 0 ) { + messagewindow_addtext_user_nonewline(messagewindow, "\n\n"); + } else { + messagewindow->first_event = 0; + } + + messagewindow_addtext_user_nonewline(messagewindow, text); + +} */ + +void messagewindow_reportdropped(MessageWindow *messagewindow, char *message, size_t length) { + + const char *mime; + + debug_print("Dropping message: '%s'\n", message); + + messagewindow_addtext_system(messagewindow, "The following message was not sent:\n"); + + mime = strstr(message, "MIME-Version:"); + assert(mime != NULL); + + messagewindow_addtext_user_nonewline(messagewindow, mime_getbody(mime), strlen(mime_getbody(mime)), NULL, NULL); + +} + +void messagewindow_leavemessage(MessageWindow *messagewindow, const char *username) { + + char *text; + char *friendlyname_decoded; + const char *friendlyname; + + friendlyname = contactlist_friendlyname(username); + friendlyname_decoded = routines_urldecode(friendlyname); + + text = malloc(strlen(username) + strlen(friendlyname_decoded) + 4); + assert(text != NULL); + + strcpy(text, friendlyname_decoded); + strcat(text, " ("); + strcat(text, username); + strcat(text, ")"); + + messagewindow_addtext_system(messagewindow, "<-- Leave: "); + messagewindow_addtext_system_nonewline(messagewindow, text); + + free(text); + free(friendlyname_decoded); + +} + +void messagewindow_removeuser(MessageWindow *messagewindow, const char *username) { + + MwUser *find_user; + MwUser *mwuser = messagewindow_find_username(messagewindow, username); + + if ( messagewindow->num_users > 1 ) { + messagewindow_leavemessage(messagewindow, username); + } + + find_user = messagewindow->users; + if ( find_user == mwuser ) { + /* User was the first in the list. */ + messagewindow->users = mwuser->next; + } else { + + while ( find_user != NULL ) { + + assert(find_user != NULL); + + if ( find_user->next == mwuser ) { + find_user->next = mwuser->next; + break; + } else { + find_user = find_user->next; + } + + } + } + + messagewindow->num_users--; + free(mwuser->username); + gtk_widget_destroy(mwuser->bar_hbox); + gtk_widget_destroy(mwuser->avatar_eventbox); + + free(mwuser); + +} + +static char *messagewindow_statusbarstring(const char *username) { + + char *text; + char *friendlyname_decoded; + const char *friendlyname; + + friendlyname = contactlist_friendlyname(username); + friendlyname_decoded = routines_urldecode(friendlyname); + + text = malloc(strlen(username) + strlen(friendlyname_decoded) + 4); + assert(text != NULL); + + strcpy(text, friendlyname_decoded); + strcat(text, " ("); + strcat(text, username); + strcat(text, ")"); + free(friendlyname_decoded); + + return text; + +} + +/* Set "User is typing" status. */ +void messagewindow_typing(MessageWindow *messagewindow, const char *username) { + + char *string; + char *bigstring; + MwUser *mwuser = messagewindow_find_username(messagewindow, username); + + assert(mwuser != NULL); + + string = messagewindow_statusbarstring(username); + bigstring = malloc(strlen(string) + 10); + strcpy(bigstring, "(Typing) "); + strcat(bigstring, string); + + gtk_label_set_text(GTK_LABEL(mwuser->bar), bigstring); + free(bigstring); + free(string); + +} + +/* Put friendlyname and username in status bar. */ +void messagewindow_resetstatusbar(MessageWindow *messagewindow, const char *username) { + + char *text; + MwUser *mwuser = messagewindow_find_username(messagewindow, username); + + text = messagewindow_statusbarstring(username); + gtk_label_set_text(GTK_LABEL(mwuser->bar), text); + free(text); + +} + +static void messagewindow_settextmode(MessageWindow *messagewindow) { + gtk_widget_show(messagewindow->textbox_hbox); + gtk_widget_hide(messagewindow->gtk_ink); +} + +static void messagewindow_setinkmode(MessageWindow *messagewindow) { + gtk_widget_hide(messagewindow->textbox_hbox); + gtk_widget_show(messagewindow->gtk_ink); +} + +/* User clicked "Ink" button. */ +static void messagewindow_ink(GtkWidget *widget, MessageWindow *messagewindow) { + + GtkAction *action; + + if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) { + /* Ink mode on */ + messagewindow_setinkmode(messagewindow); + action = gtk_action_group_get_action(messagewindow->action_group, "ModeInkAction"); + } else { + /* Ink mode off */ + messagewindow_settextmode(messagewindow); + action = gtk_action_group_get_action(messagewindow->action_group, "ModeTextAction"); + } + + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE); + +} + +static void messagewindow_setmode(GtkRadioAction *action, GtkRadioAction *current, MessageWindow *messagewindow) { + + MessageWindowMode mode = gtk_radio_action_get_current_value(action); + + if ( mode == MESSAGEWINDOW_MODE_TEXT ) { + messagewindow_settextmode(messagewindow); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(messagewindow->ink_button), FALSE); + } else { + messagewindow_setinkmode(messagewindow); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(messagewindow->ink_button), TRUE); + } + +} + +static void messagewindow_sendfilesel(GtkDialog *dialog, gint response, MessageWindow *messagewindow) { + + if ( response == GTK_RESPONSE_ACCEPT ) { + + char *filename; + unsigned int i; + MwUser *user; + char *usernames[messagewindow->num_users]; + + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + debug_print("Sending file '%s'\n", filename); + + user = messagewindow->users; + for ( i=0; i<messagewindow->num_users; i++ ) { + usernames[i] = user->username; + user = user->next; + } + filetrans_offer_multiple(filename, usernames, messagewindow->num_users); + + g_free(filename); + + } + + gtk_widget_destroy(GTK_WIDGET(dialog)); + +} + +/* User clicked "Send File" button. */ +static void messagewindow_sendfile(GtkWidget *widget, MessageWindow *messagewindow) { + + GtkWidget *dialog; + + dialog = gtk_file_chooser_dialog_new("Send File", GTK_WINDOW(messagewindow->window), GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + + g_signal_connect(G_OBJECT(dialog), "response", GTK_SIGNAL_FUNC(messagewindow_sendfilesel), messagewindow); + + + gtk_widget_show(dialog); + +} + +/* Called when it's time to send whatever's in the text entry widget of a message window. */ +static void messagewindow_send(GtkWidget *widget, MessageWindow *messagewindow) { + + gchar *textblock; + GtkTextIter start; + GtkTextIter end; + GtkTextBuffer *buffer; + int length; + char *colour; + char *hash_colour; + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->textbox)); + gtk_text_buffer_get_start_iter(buffer, &start); + gtk_text_buffer_get_end_iter(buffer, &end); + + textblock = gtk_text_iter_get_text(&start, &end); + length = strlen(textblock); + + if ( messagewindow->session == NULL ) { + messagewindow_plug(messagewindow, sbsessions_create_local(messagewindow->users->username)); + sbsessions_plug(messagewindow->session, messagewindow); + } + /* Message might get cached for sending later. */ + sbprotocol_send(messagewindow->session, textblock, length); + + if ( strlen(textblock) > 0 ) { + messagewindow_addtext_system(messagewindow, "You say:\n"); + if ( messagewindow->localcolour_string != NULL ) { + colour = routines_flipcolour(messagewindow->localcolour_string); + hash_colour = malloc(8); + strcpy(hash_colour, "#"); + strncat(hash_colour, colour, 6); + hash_colour[7] = '\0'; + messagewindow_addtext_user_nonewline(messagewindow, textblock, length, hash_colour, messagewindow->dislocalfont); + free(hash_colour); + free(colour); + } else { + messagewindow_addtext_user_nonewline(messagewindow, textblock, length, NULL, NULL); + } + } + + /* Empty the text entry widget */ + gtk_text_buffer_set_text(buffer, "", -1); + g_free(textblock); + +} + +void messagewindow_am_typing(MessageWindow *messagewindow) { + + if ( messagewindow->session == NULL ) { + + debug_print("MW %8p: Not plugged in - not sending TypingUser.\n", messagewindow); + if ( messagewindow->num_users == 1 ) { + assert(messagewindow->users != NULL); + messagewindow_plug(messagewindow, sbsessions_create_local(messagewindow->users->username)); + sbsessions_plug(messagewindow->session, messagewindow); + } + return; + + } + + if ( sbsessions_sessionready(messagewindow->session) ) { + sbsessions_am_typing(messagewindow->session); + } else { + debug_print("MW %8p: Session %p isn't ready - not sending TypingUser.\n", messagewindow, messagewindow->session); + } + +} + +/* Called when a key is pressed inside the text entry widget of a message window. */ +static gint messagewindow_key(GtkWidget *ignore1, GdkEventKey *event, MessageWindow *messagewindow) { + + if ( (event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter) ) { + + /* Send the message */ + messagewindow_send(NULL, messagewindow); + return TRUE; /* Don't process further. */ + + } else { + + /* YUK */ + if ( (event->keyval != GDK_Shift_L) && (event->keyval != GDK_Shift_R) + && (event->keyval != GDK_Control_L) && (event->keyval != GDK_Control_R) + && (event->keyval != GDK_Alt_L) && (event->keyval != GDK_Alt_R) + && (event->keyval != GDK_Meta_L) && (event->keyval != GDK_Meta_R) + && (event->keyval != GDK_Shift_L) && (event->keyval != GDK_Shift_R) ) { + + messagewindow_am_typing(messagewindow); + + } + + } + + return FALSE; + +} + +/* Create or update the picture associated with a user. username=NULL means the local user's picture. */ +static void messagewindow_newpicture(MessageWindow *messagewindow, const char *username, const char *filename) { + + GtkWidget *pixmap_widget; + GdkPixbuf *pixbuf; + MwUser *user; + GtkTooltips *tooltip; + GtkWidget *event_box; + + int local_image; + int changing_image; + + user = NULL; + if ( username != NULL ) { + + user = messagewindow_find_username(messagewindow, username); + if ( user == NULL ) { + debug_print("MW %8p: Couldn't find user data!\n", messagewindow); + return; + } + + } + + if ( username == NULL ) { + local_image = TRUE; + } else { + local_image = FALSE; + } + + /* Get rid of any previous display picture we may have */ + if ( local_image == FALSE ) { + + if ( user->avatar != NULL ) { + + gtk_widget_destroy(user->avatar); + gdk_pixbuf_unref(user->avatar_pixbuf); + changing_image = TRUE; + user->avatar = NULL; + user->avatar_pixbuf = NULL; + + } else { + changing_image = FALSE; + } + + } else { + + if ( messagewindow->avatar != NULL ) { + + gtk_widget_destroy(messagewindow->avatar); + gdk_pixbuf_unref(messagewindow->avatar_pixbuf); + messagewindow->avatar = NULL; + messagewindow->avatar_pixbuf = NULL; + changing_image = TRUE; + + } else { + changing_image = FALSE; + } + + } + + pixbuf = gdk_pixbuf_new_from_file(filename, NULL); + + if ( pixbuf == NULL ) { + + debug_print("MW %8p: Couldn't load user display image - ", messagewindow); + + /* Check if the picture's in the process of being downloaded... */ + if ( !msnp2p_retrieving(filename) ) { + + /* Delete the file, and re-download it... */ + if ( remove(filename) == 0 ) { + debug_print("deleted it.\n"); + messagewindow_picturekick(username); + return; + } else { + debug_print("failed to delete it, too. Noooo...\n"); + return; + } + + } else { + debug_print("download in progress. Not worrying...\n"); + } + + return; + + } + + pixmap_widget = gtk_image_new_from_pixbuf(pixbuf); + + if ( changing_image == FALSE ) { + event_box = gtk_event_box_new(); + } else { + + if ( local_image == FALSE ) { + event_box = user->avatar_eventbox; + } else { + event_box = messagewindow->avatar_eventbox; + } + + } + + gtk_container_add(GTK_CONTAINER(event_box), pixmap_widget); + + if ( changing_image == FALSE ) { + + if ( username != NULL ) { + gtk_box_pack_start(GTK_BOX(messagewindow->picture_list), event_box, FALSE, FALSE, 0); + } else { + gtk_box_pack_end(GTK_BOX(messagewindow->picture_list), event_box, FALSE, FALSE, 0); + } + + tooltip = gtk_tooltips_new(); + gtk_tooltips_set_tip(tooltip, event_box, username, NULL); + + } + + if ( !local_image ) { + + user->avatar_eventbox = event_box; + user->avatar = pixmap_widget; + user->avatar_pixbuf = pixbuf; + + } else { + + messagewindow->avatar_eventbox = event_box; + messagewindow->avatar = pixmap_widget; + messagewindow->avatar_pixbuf = pixbuf; + + } + + gtk_widget_show_all(event_box); + +} + +/* Decide what to do in order to eventually end up with an avatar for a remote user. */ +void messagewindow_trypicture(MessageWindow *messagewindow, const char *username) { + + const char *dpobject; + + dpobject = contactlist_haspicture(username); + if ( (dpobject != NULL) && (strlen(dpobject) > 0) ) { + + char *avatar_filename; + avatar_filename = avatars_havepicture(dpobject); + if ( avatar_filename != NULL ) { + + debug_print("MW %8p: Already have picture.\n", messagewindow); + messagewindow_newpicture(messagewindow, username, avatar_filename); + + } else { + + char *filename; + + debug_print("MW %8p: Setting \"Wait\" picture.\n", messagewindow); + filename = avatars_default_fetching(); + messagewindow_newpicture(messagewindow, username, filename); + free(filename); + + if ( (messagewindow->session == NULL) && (!messagewindow->in_creation) ) { + messagewindow_plug(messagewindow, sbsessions_create_local(username)); + sbsessions_plug(messagewindow->session, messagewindow); + } else { + if ( sbsessions_sessionready(messagewindow->session) ) { + msnp2p_getpicture(messagewindow->session, username, dpobject); + } else { + debug_print("MW %8p: Session isn't ready, but will be...\n", messagewindow); + } + } + + } + free(avatar_filename); + + } else { + char *avatars_blank = avatars_default_none(); + debug_print("MW %8p: Setting \"Blank\" picture.\n", messagewindow); + messagewindow_newpicture(messagewindow, username, avatars_blank); + free(avatars_blank); + } + +} + +void messagewindow_localpicture(MessageWindow *messagewindow) { + + char *filename; + + filename = avatars_local(); + if ( filename == NULL ) { + filename = DATADIR"/tuxmessenger/no_avatar.png"; + messagewindow_newpicture(messagewindow, NULL, filename); + } else { + messagewindow_newpicture(messagewindow, NULL, filename); + free(filename); + } + +} + +/* Find all instances of the given user in all SB sessions, and reload the picture for them. + NULL means kick the local user's picture. */ +void messagewindow_picturekick(const char *username) { + + MessageWindow *messagewindow; + + messagewindow = messagewindows; + while ( messagewindow != NULL ) { + + if ( username != NULL ) { + + /* See if the user in question is in this session. */ + MwUser *user = messagewindow_find_username(messagewindow, username); + if ( user != NULL ) { + messagewindow_trypicture(messagewindow, username); + } + + } else { + + messagewindow_localpicture(messagewindow); + + } + + messagewindow = messagewindow->next; + + } + +} + +static int messagewindow_close(GtkWidget *widget, MessageWindow *messagewindow) { + + gtk_widget_destroy(messagewindow->window); + return FALSE; + +} + +static int messagewindow_toggleavatars(GtkWidget *widget, MessageWindow *messagewindow) { + + gboolean active; + GtkAction *action; + + action = gtk_action_group_get_action(messagewindow->action_group, "AvatarsAction"); + assert(action != NULL); + active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)); + + if ( active ) { + /* Show avatars */ + gtk_widget_show(messagewindow->picture_list); + } else { + /* Hide avatars */ + gtk_widget_hide(messagewindow->picture_list); + } + + return FALSE; + +} + +static int messagewindow_toggleircstyle(GtkWidget *widget, MessageWindow *messagewindow) { + + gboolean active; + GtkAction *action; + + action = gtk_action_group_get_action(messagewindow->action_group, "IRCStyleAction"); + assert(action != NULL); + active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)); + + if ( active ) { + /* Use IRC style */ + messagewindow->ircstyle = TRUE; + } else { + /* Use normal style */ + messagewindow->ircstyle = FALSE; + } + + return FALSE; + +} + +static int messagewindow_toggletimestamps(GtkWidget *widget, MessageWindow *messagewindow) { + + gboolean active; + GtkAction *action; + + action = gtk_action_group_get_action(messagewindow->action_group, "TimeStampAction"); + assert(action != NULL); + active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)); + + if ( active ) { + /* Show timestamps */ + messagewindow->timestamps = TRUE; + } else { + /* Don't show timestamps */ + messagewindow->timestamps = FALSE; + } + + return FALSE; + +} + +static int messagewindow_toggleemoticons(GtkWidget *widget, MessageWindow *messagewindow) { + + gboolean active; + GtkAction *action; + + action = gtk_action_group_get_action(messagewindow->action_group, "EmoticonsAction"); + assert(action != NULL); + active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)); + + if ( active ) { + /* Show emoticons */ + messagewindow->showemoticons = TRUE; + } else { + /* Don't show emoticons */ + messagewindow->showemoticons = FALSE; + } + + return FALSE; + +} + +static void messagewindow_closed(MessageWindow *messagewindow) { + + /* If this is a multi-user conversation, it'd be polite to at least try to leave it... */ + if ( messagewindow->session != NULL ) { + if ( messagewindow->session->num_users > 1 ) { + debug_print("MW %8p: Closing a multi-way conversation. Sending OUT...\n", messagewindow); + sbprotocol_leavesession(messagewindow->session); + } + } + + sbsessions_unplug(messagewindow); + messagewindow_destroy(messagewindow); + +} + +static void messagewindow_lcolsel(GtkWidget *colourbutton, MessageWindow *messagewindow) { + + GdkColor colour; + char *string; + + gtk_color_button_get_color(GTK_COLOR_BUTTON(colourbutton), &colour); + + if ( messagewindow->localcolour_gdk != NULL ) { + gdk_color_free(messagewindow->localcolour_gdk); + } + if ( messagewindow->localcolour_string != NULL ) { + free(messagewindow->localcolour_string); + } + + messagewindow->localcolour_gdk = gdk_color_copy(&colour); + + /* Now work out the string version */ + string = malloc(7); + /* Yukky BGR order instead of RGB */ + snprintf(string, 7, "%02hhx%02hhx%02hhx", colour.blue >> 8, colour.green >> 8, colour.red >> 8); + messagewindow->localcolour_string = string; + debug_print("MW %8p: String value '%s'\n", messagewindow, string); + +} + +static void messagewindow_lfontsel(GtkWidget *widget, MessageWindow *messagewindow) { + + const char *font; + + if ( messagewindow->dislocalfont != NULL ) { + free(messagewindow->dislocalfont); + } + if ( messagewindow->localfont != NULL ) { + free(messagewindow->localfont); + } + font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget)); + messagewindow->dislocalfont = strdup(font); + messagewindow->localfont = fonttrans_font_to_format(font); + +} + +static void messagewindow_ofontsel(GtkWidget *widget, MessageWindow *messagewindow) { + + const char *font; + + if ( messagewindow->ofont != NULL ) { + free(messagewindow->ofont); + } + font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget)); + messagewindow->ofont = strdup(font); + +} + +static void messagewindow_ocolsel(GtkWidget *colourbutton, MessageWindow *messagewindow) { + + GdkColor colour; + + gtk_color_button_get_color(GTK_COLOR_BUTTON(colourbutton), &colour); + + if ( messagewindow->ocolour_gdk != NULL ) { + gdk_color_free(messagewindow->ocolour_gdk); + } + + if ( messagewindow->ocolour_string != NULL ) { + free(messagewindow->ocolour_string); + } + messagewindow->ocolour_gdk = gdk_color_copy(&colour); + messagewindow->ocolour_string = routines_gdk_to_hashrgb(&colour); + +} + +void messagewindow_fontsdialog_closed(GtkWidget *widget, MessageWindow *messagewindow) { + messagewindow->fontsdialog = NULL; +} + +static void messagewindow_ofontoverride_toggle(GtkWidget *widget, MessageWindow *messagewindow) { + + if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) { + messagewindow->ofontoverride = TRUE; + gtk_widget_set_sensitive(messagewindow->ofont_button, TRUE); + gtk_widget_set_sensitive(messagewindow->ocolour_button, TRUE); + } else { + messagewindow->ofontoverride = FALSE; + gtk_widget_set_sensitive(messagewindow->ofont_button, FALSE); + gtk_widget_set_sensitive(messagewindow->ocolour_button, FALSE); + } + +} + +void messagewindow_openfontsdialog(GtkWidget *widget, MessageWindow *messagewindow) { + + GtkWidget *dialog_box; + + GtkWidget *fbox; + GtkWidget *font_label; + GtkWidget *font_label_justify; + GtkWidget *font_vbox; + GtkWidget *font_hbox; + GtkWidget *font_box; + GtkWidget *font_button; + GtkWidget *colour_button; + GtkWidget *nbox; + GtkWidget *ofont_label; + GtkWidget *ofont_label_justify; + GtkWidget *ofont_vbox; + GtkWidget *ofont_hbox; + GtkWidget *ofont_override; + GtkWidget *ofont_box; + + if ( messagewindow->fontsdialog != NULL ) { + return; + } + + messagewindow->fontsdialog = gtk_dialog_new_with_buttons("Message Fonts", GTK_WINDOW(messagewindow->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL); + dialog_box = GTK_DIALOG(messagewindow->fontsdialog)->vbox; + + fbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(dialog_box), fbox, FALSE, FALSE, 12); + font_label = gtk_label_new(""); + font_label_justify = gtk_hbox_new(FALSE, 0); + gtk_label_set_markup(GTK_LABEL(font_label), "<span weight=\"bold\">Font and Colour for Your Messages</span>"); + gtk_box_pack_start(GTK_BOX(font_label_justify), font_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(fbox), font_label_justify, FALSE, FALSE, 6); + + font_vbox = gtk_vbox_new(FALSE, 0); + font_hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(fbox), font_hbox, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(font_hbox), font_vbox, FALSE, FALSE, 12); + + font_box = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(font_vbox), font_box, FALSE, FALSE, 0); + if ( messagewindow->dislocalfont == NULL ) { + font_button = gtk_font_button_new(); + } else { + font_button = gtk_font_button_new_with_font(messagewindow->dislocalfont); + } + g_signal_connect(G_OBJECT(font_button), "font-set", GTK_SIGNAL_FUNC(messagewindow_lfontsel), messagewindow); + gtk_box_pack_start(GTK_BOX(font_box), font_button, FALSE, FALSE, 6); + if ( messagewindow->localcolour_gdk == NULL ) { + colour_button = gtk_color_button_new(); + } else { + colour_button = gtk_color_button_new_with_color(messagewindow->localcolour_gdk); + } + g_signal_connect(G_OBJECT(colour_button), "color-set", GTK_SIGNAL_FUNC(messagewindow_lcolsel), messagewindow); + gtk_box_pack_start(GTK_BOX(font_box), colour_button, FALSE, FALSE, 6); + + nbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(dialog_box), nbox, FALSE, FALSE, 12); + ofont_label = gtk_label_new(""); + ofont_label_justify = gtk_hbox_new(FALSE, 0); + gtk_label_set_markup(GTK_LABEL(ofont_label), "<span weight=\"bold\">Fonts and Colours for Contacts' Messages</span>"); + gtk_box_pack_start(GTK_BOX(ofont_label_justify), ofont_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(nbox), ofont_label_justify, FALSE, FALSE, 0); + + ofont_vbox = gtk_vbox_new(FALSE, 0); + ofont_hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(nbox), ofont_hbox, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(ofont_hbox), ofont_vbox, FALSE, FALSE, 12); + + ofont_override = gtk_check_button_new_with_label("Override contacts' chosen fonts"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ofont_override), messagewindow->ofontoverride); + g_signal_connect(G_OBJECT(ofont_override), "toggled", GTK_SIGNAL_FUNC(messagewindow_ofontoverride_toggle), messagewindow); + gtk_box_pack_start(GTK_BOX(ofont_vbox), ofont_override, FALSE, FALSE, 6); + ofont_box = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(ofont_vbox), ofont_box, FALSE, FALSE, 0); + if ( messagewindow->ofont ) { + messagewindow->ofont_button = gtk_font_button_new_with_font(messagewindow->ofont); + } else { + messagewindow->ofont_button = gtk_font_button_new(); + } + g_signal_connect(G_OBJECT(messagewindow->ofont_button), "font-set", GTK_SIGNAL_FUNC(messagewindow_ofontsel), messagewindow); + gtk_box_pack_start(GTK_BOX(ofont_box), messagewindow->ofont_button, FALSE, FALSE, 6); + if ( messagewindow->ocolour_gdk == NULL ) { + messagewindow->ocolour_button = gtk_color_button_new(); + } else { + messagewindow->ocolour_button = gtk_color_button_new_with_color(messagewindow->ocolour_gdk); + } + g_signal_connect(G_OBJECT(messagewindow->ocolour_button), "color-set", GTK_SIGNAL_FUNC(messagewindow_ocolsel), messagewindow); + gtk_box_pack_start(GTK_BOX(ofont_box), messagewindow->ocolour_button, FALSE, FALSE, 6); + messagewindow_ofontoverride_toggle(ofont_override, messagewindow); + + g_signal_connect(G_OBJECT(messagewindow->fontsdialog), "destroy", GTK_SIGNAL_FUNC(messagewindow_fontsdialog_closed), messagewindow); + g_signal_connect(G_OBJECT(messagewindow->fontsdialog), "response", GTK_SIGNAL_FUNC(gtk_widget_destroy), NULL); + gtk_widget_show_all(messagewindow->fontsdialog); + +} + +static void messagewindow_userdnd_receive(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *seldata, guint info, guint time, MessageWindow *messagewindow) { + + char *username; + + username = seldata->data; + + if ( messagewindow_find_username(messagewindow, username) != NULL ) { + debug_print("MW %8p: '%s' is already in this message window.\n", messagewindow, username); + return; + } + + if ( messagewindow->session != NULL ) { + debug_print("MW %8p: Inviting '%s'...\n", messagewindow, username); + sbprotocol_invite(messagewindow->session, username); + } else { + assert(messagewindow->num_users == 1); + debug_print("MW %8p: Creating session and inviting '%s' and '%s'...\n", messagewindow, messagewindow->users->username, username); + messagewindow->session = sbsessions_create_threeway(messagewindow->users->username, username); + } + +} + +static void messagewindow_addui_callback(GtkUIManager *ui, GtkWidget *widget, GtkContainer *container) { + + gtk_box_pack_start(GTK_BOX(container), widget, FALSE, FALSE, 0); + + /* Enable overflow menu if this is a toolbar */ + if ( GTK_IS_TOOLBAR(widget) ) { + gtk_toolbar_set_show_arrow(GTK_TOOLBAR(widget), TRUE); + } + +} + +static void messagewindow_addmenubar(MessageWindow *messagewindow) { + + GtkActionEntry entries[] = { + + { "ConversationAction", NULL, "_Conversation", NULL, NULL, NULL }, + { "InviteAction", GTK_STOCK_ADD, "_Invite New User...", NULL, NULL, NULL }, + { "CloseAction", GTK_STOCK_CLOSE, "_Close", NULL, NULL, G_CALLBACK(messagewindow_close) }, + + { "ViewAction", NULL, "_View", NULL, NULL, NULL }, + { "FontAction", GTK_STOCK_SELECT_FONT, "_Set Fonts and Colours...", NULL, NULL, G_CALLBACK(messagewindow_openfontsdialog) }, + /* More in toggles[] */ + + { "ToolsAction", NULL, "_Tools", NULL, NULL, NULL }, + { "BlockAction", GTK_STOCK_NO, "_Block User", NULL, NULL, NULL }, + { "NudgeAction", NULL, "_Nudge", NULL, NULL, G_CALLBACK(messagewindow_sendnudge) }, + #ifdef HAVE_GTK_2_6_0 + { "SendFileAction", GTK_STOCK_FILE, "Send a _File...", NULL, NULL, NULL }, + { "AboutAction", GTK_STOCK_ABOUT, "_About TuxMessenger", NULL, NULL, G_CALLBACK(about_open) }, + #else /* HAVE_GTK_2_6_0 */ + { "SendFileAction", NULL, "Send a _File...", NULL, NULL, NULL }, + { "AboutAction", NULL, "_About TuxMessenger", NULL, NULL, G_CALLBACK(about_open) }, + #endif /* HAVE_GTK_2_6_0 */ + + { "HelpAction", NULL, "_Help", NULL, NULL, NULL }, + + }; + GtkToggleActionEntry toggles[] = { + + { "AvatarsAction", NULL, "Display _Avatars", NULL, NULL, G_CALLBACK(messagewindow_toggleavatars), TRUE }, + { "IRCStyleAction", NULL, "_IRC Style", NULL, NULL, G_CALLBACK(messagewindow_toggleircstyle), FALSE }, + { "TimeStampAction", NULL, "_Timestamp Messages", NULL, NULL, G_CALLBACK(messagewindow_toggletimestamps), FALSE }, + { "EmoticonsAction", NULL, "Enable _Emoticons", NULL, NULL, G_CALLBACK(messagewindow_toggleemoticons), FALSE }, + + }; + GtkRadioActionEntry radios_mode[] = { + + { "ModeTextAction", NULL, "_Text Mode", NULL, NULL, MESSAGEWINDOW_MODE_TEXT }, + { "ModeInkAction", NULL, "In_k Mode", NULL, NULL, MESSAGEWINDOW_MODE_INK }, + + }; + + guint n_entries = G_N_ELEMENTS(entries); + guint n_toggles = G_N_ELEMENTS(toggles); + guint n_radios_mode = G_N_ELEMENTS(radios_mode); + GError *error = NULL; + + messagewindow->action_group = gtk_action_group_new("TuxMessengerIMWindow"); + gtk_action_group_add_actions(messagewindow->action_group, entries, n_entries, messagewindow); + gtk_action_group_add_toggle_actions(messagewindow->action_group, toggles, n_toggles, messagewindow); + gtk_action_group_add_radio_actions(messagewindow->action_group, radios_mode, n_radios_mode, MESSAGEWINDOW_MODE_TEXT, G_CALLBACK(messagewindow_setmode), messagewindow); + + messagewindow->ui = gtk_ui_manager_new(); + gtk_ui_manager_insert_action_group(messagewindow->ui, messagewindow->action_group, 0); + g_signal_connect(messagewindow->ui, "add_widget", G_CALLBACK(messagewindow_addui_callback), messagewindow->bigvbox); + if ( gtk_ui_manager_add_ui_from_file(messagewindow->ui, DATADIR"/tuxmessenger/imwindow.ui", &error) == 0 ) { + debug_print("MW %8p: Error loading message window menu bar: %s\n", messagewindow, error->message); + return; + } + + gtk_window_add_accel_group(GTK_WINDOW(messagewindow->window), gtk_ui_manager_get_accel_group(messagewindow->ui)); + gtk_ui_manager_ensure_update(messagewindow->ui); + +} + +static MwUser *messagewindow_adduser(MessageWindow *messagewindow, const char *username) { + + MwUser *mwuser; + MwUser *previous_user; + + mwuser = malloc(sizeof(MwUser)); + + mwuser->username = strdup(username); + + mwuser->avatar_eventbox = NULL; + mwuser->avatar = NULL; + mwuser->avatar_pixbuf = NULL; + mwuser->bar = gtk_label_new(""); + mwuser->bar_hbox = gtk_hbox_new(FALSE, 0); + mwuser->typing_callback = 0; + + gtk_widget_set_usize(mwuser->bar_hbox, 10, -1); + gtk_box_pack_start(GTK_BOX(mwuser->bar_hbox), mwuser->bar, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(messagewindow->windowbox), mwuser->bar_hbox, FALSE, TRUE, 2); + + gtk_widget_show(mwuser->bar); + gtk_widget_show(mwuser->bar_hbox); + + previous_user = messagewindow_lastuser(messagewindow); + mwuser->next = NULL; + if ( previous_user == NULL ) { + messagewindow->users = mwuser; + } else { + previous_user->next = mwuser; + } + + messagewindow->num_users++; + + messagewindow_resetstatusbar(messagewindow, username); + messagewindow_trypicture(messagewindow, username); + + return mwuser; + +} + +int messagewindow_get_last_was_nak(MessageWindow *messagewindow) { + return messagewindow->last_was_nak; +} + +void messagewindow_set_last_was_nak(MessageWindow *messagewindow, int last_was_nak) { + messagewindow->last_was_nak = last_was_nak; +} + +MessageWindow *messagewindow_create(const char *username, SbSession *session) { + + MessageWindow *messagewindow; + + GtkWidget *messagelist_hbox; + GtkWidget *messagelist_scroll; + GtkWidget *textbox_scroll; + GtkWidget *textbox_hbox; + GtkWidget *button_box; + GtkWidget *picture_hbox; + GtkAdjustment *adjustment; + + GtkWidget *send_button; + GtkWidget *sendfile_button; + GtkWidget *nudge_button; + GdkColor bgcolour; + GdkColor fgcolour; + + GdkColor colour; + GtkTextBuffer *buffer; + GtkTextIter start; + GtkTextIter end; + GtkTextIter iter; + char *friendlyname_decoded; + GtkTargetEntry targets[1]; + + MessageWindow *last_messagewindow; + + messagewindow = malloc(sizeof(MessageWindow)); + messagewindow->users = NULL; + messagewindow->num_users = 0; + messagewindow->avatar = NULL; + messagewindow->avatar_eventbox = NULL; + messagewindow->avatar_pixbuf = NULL; + messagewindow->session = NULL; + messagewindow->in_creation = TRUE; + messagewindow->first_event = TRUE; + messagewindow->fontsdialog = NULL; + messagewindow->stuck = TRUE; + + if ( options_localcolour_gdk() != NULL ) { + messagewindow->localcolour_gdk = gdk_color_copy(options_localcolour_gdk()); + } else { + messagewindow->localcolour_gdk = NULL; + } + if ( options_localcolour_string() != NULL ) { + messagewindow->localcolour_string = strdup(options_localcolour_string()); + } else { + messagewindow->localcolour_string = NULL; + } + if ( options_ocolour_gdk() != NULL ) { + messagewindow->ocolour_gdk = gdk_color_copy(options_ocolour_gdk()); + messagewindow->ocolour_string = routines_gdk_to_hashrgb(messagewindow->ocolour_gdk); + } else { + messagewindow->ocolour_gdk = NULL; + messagewindow->ocolour_string = NULL; + } + if ( options_localfont() != NULL ) { + messagewindow->localfont = fonttrans_font_to_format(options_localfont()); + messagewindow->dislocalfont = strdup(options_localfont()); + } else { + messagewindow->localfont = NULL; + messagewindow->dislocalfont = NULL; + } + if ( options_ofont() != NULL ) { + messagewindow->ofont = strdup(options_ofont()); + } else { + messagewindow->ofont = NULL; + } + messagewindow->ofontoverride = options_ofontoverride(); + messagewindow->ircstyle = options_ircstyle(); + messagewindow->timestamps = options_timestamps(); + messagewindow->showemoticons = options_showemoticons(); + messagewindow->last_was_nak = FALSE; + + debug_print("MW %8p: Created for '%s'\n", messagewindow, username); + + /* Prepare the window. */ + messagewindow->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_signal_connect_object(GTK_OBJECT(messagewindow->window), "destroy", GTK_SIGNAL_FUNC(messagewindow_closed), (gpointer)messagewindow); + gtk_window_set_default_size(GTK_WINDOW(messagewindow->window), 360, 340); + + /* Set window title. */ + friendlyname_decoded = routines_urldecode(contactlist_friendlyname(username)); + gtk_window_set_title(GTK_WINDOW(messagewindow->window), friendlyname_decoded); + free(friendlyname_decoded); + + /* Top-level structure. */ + messagewindow->bigvbox = gtk_vbox_new(FALSE, 0); + messagewindow->windowbox = gtk_vbox_new(FALSE, 0); + gtk_widget_show(messagewindow->windowbox); + picture_hbox = gtk_hbox_new(FALSE, 2); + gtk_container_set_border_width(GTK_CONTAINER(picture_hbox), 2); + gtk_container_add(GTK_CONTAINER(messagewindow->window), messagewindow->bigvbox); + gtk_box_pack_end(GTK_BOX(messagewindow->bigvbox), picture_hbox, TRUE, TRUE, 0); + gtk_widget_show(picture_hbox); + gtk_widget_show(messagewindow->bigvbox); + + messagewindow_addmenubar(messagewindow); + + /* Create the box for past messages. */ + messagewindow->messages = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(messagewindow->messages), GTK_WRAP_WORD); + messagelist_hbox = gtk_hbox_new(FALSE, 0); + messagelist_scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(messagelist_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_container_add(GTK_CONTAINER(messagelist_scroll), GTK_WIDGET(messagewindow->messages)); + gtk_box_pack_start(GTK_BOX(messagelist_hbox), messagelist_scroll, TRUE, TRUE, 0); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(messagelist_scroll), GTK_SHADOW_IN); + gdk_color_parse("white", &colour); + gtk_widget_modify_bg(messagewindow->messages, GTK_STATE_NORMAL, &colour); + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages)); + gtk_text_buffer_create_tag(buffer, "system", "left_margin", 3, "right_margin", 3, "weight", PANGO_WEIGHT_BOLD, "size", 9*PANGO_SCALE, NULL); + gtk_text_view_set_editable(GTK_TEXT_VIEW(messagewindow->messages), FALSE); + gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), messagelist_hbox, TRUE, TRUE, 0); + gtk_text_buffer_get_end_iter(buffer, &iter); + messagewindow->mark = gtk_text_buffer_create_mark(buffer, NULL, &iter, FALSE); + gtk_widget_show(messagewindow->messages); + gtk_widget_show(messagelist_hbox); + gtk_widget_show(messagelist_scroll); + GTK_WIDGET_UNSET_FLAGS(messagewindow->messages, GTK_CAN_FOCUS); + + /* Now add a few buttons for the user to control the session with */ + button_box = gtk_hbox_new(FALSE, 0); + sendfile_button = gtk_button_new_with_label("Send File"); + messagewindow->ink_button = gtk_toggle_button_new_with_label("Ink"); + nudge_button = gtk_button_new_with_label("Nudge"); + send_button = gtk_button_new_with_label("Send"); + gtk_box_pack_start(GTK_BOX(button_box), messagewindow->ink_button, FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(button_box), sendfile_button, FALSE, FALSE, 5); + gtk_box_pack_end(GTK_BOX(button_box), send_button, FALSE, FALSE, 5); + gtk_box_pack_end(GTK_BOX(button_box), nudge_button, FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), button_box, FALSE, FALSE, 5); + gtk_widget_show(button_box); + gtk_widget_show(messagewindow->ink_button); + gtk_widget_show(sendfile_button); + gtk_widget_show(send_button); + gtk_widget_show(nudge_button); + + /* Create the box into which the user will type */ + messagewindow->textbox = gtk_text_view_new(); + textbox_hbox = gtk_hbox_new(FALSE, 0); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(messagewindow->textbox), GTK_WRAP_WORD); + textbox_scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(textbox_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(textbox_scroll), GTK_WIDGET(messagewindow->textbox)); + gtk_box_pack_start(GTK_BOX(textbox_hbox), textbox_scroll, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), textbox_hbox, FALSE, FALSE, 0); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(textbox_scroll), GTK_SHADOW_IN); + gdk_color_parse("white", &colour); + gtk_widget_modify_bg(messagewindow->textbox, GTK_STATE_NORMAL, &colour); + gtk_text_view_set_editable(GTK_TEXT_VIEW(messagewindow->textbox), TRUE); + g_signal_connect(messagewindow->textbox, "key-press-event", GTK_SIGNAL_FUNC(messagewindow_key), messagewindow); + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->textbox)); + gtk_text_buffer_create_tag(buffer, "normal", "left_margin", 3, "right_margin", 3, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, NULL); + gtk_text_buffer_get_start_iter(buffer, &start); + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_apply_tag_by_name(buffer, "normal", &start, &end); + gtk_widget_show(messagewindow->textbox); + gtk_widget_show(textbox_hbox); + gtk_widget_show(textbox_scroll); + + messagewindow->textbox_hbox = textbox_hbox; + gdk_color_parse("#FFFFFF", &bgcolour); + gdk_color_parse("#100080", &fgcolour); + messagewindow->gtk_ink = gtk_ink_new(GTK_INK_FLAG_EDITABLE | GTK_INK_FLAG_PAPER, ink_new(), &fgcolour, &bgcolour); + gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), messagewindow->gtk_ink, FALSE, FALSE, 0); + /* But don't gtk_widget_show() it yet! */ + + gtk_widget_set_size_request(messagewindow->gtk_ink, -1, 80); + gtk_widget_set_size_request(messagewindow->textbox_hbox, -1, 80); + + gtk_signal_connect(GTK_OBJECT(send_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_send), messagewindow); + gtk_signal_connect(GTK_OBJECT(messagewindow->ink_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_ink), messagewindow); + gtk_signal_connect(GTK_OBJECT(sendfile_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_sendfile), messagewindow); + gtk_signal_connect(GTK_OBJECT(nudge_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_sendnudge), messagewindow); + + messagewindow->picture_list = gtk_vbox_new(FALSE, 5); + gtk_widget_set_usize(GTK_WIDGET(messagewindow->picture_list), 96, 0); + gtk_box_pack_start(GTK_BOX(picture_hbox), messagewindow->windowbox, TRUE, TRUE, 0); + gtk_box_pack_end(GTK_BOX(picture_hbox), messagewindow->picture_list, FALSE, FALSE, 0); + gtk_widget_show(messagewindow->picture_list); + + /* Drag and Drop */ + targets[0].target = "tm_username"; + targets[0].flags = GTK_TARGET_SAME_APP; + targets[0].info = 1; + gtk_drag_dest_set(messagewindow->window, GTK_DEST_DEFAULT_ALL, targets, 1, GDK_ACTION_COPY); + g_signal_connect(messagewindow->window, "drag-data-received", GTK_SIGNAL_FUNC(messagewindow_userdnd_receive), (gpointer)messagewindow); + + g_signal_connect(messagewindow->window, "size-allocate", GTK_SIGNAL_FUNC(messagewindow_size_allocate), messagewindow); + adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(messagelist_scroll)); + g_signal_connect(adjustment, "value-changed", GTK_SIGNAL_FUNC(messagewindow_scrolled), messagewindow); + + if ( !options_showavatars() ) { + GtkAction *action; + action = gtk_action_group_get_action(messagewindow->action_group, "AvatarsAction"); + assert(action != NULL); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), FALSE); + gtk_widget_hide(messagewindow->picture_list); + } + + if ( messagewindow->ircstyle ) { + GtkAction *action; + action = gtk_action_group_get_action(messagewindow->action_group, "IRCStyleAction"); + assert(action != NULL); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE); + } + + if ( messagewindow->timestamps ) { + GtkAction *action; + action = gtk_action_group_get_action(messagewindow->action_group, "TimeStampAction"); + assert(action != NULL); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE); + } + + if ( messagewindow->showemoticons ) { + GtkAction *action; + action = gtk_action_group_get_action(messagewindow->action_group, "EmoticonsAction"); + assert(action != NULL); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE); + } + + gtk_widget_show(messagewindow->window); /* Slow */ + gtk_widget_grab_focus(messagewindow->textbox); + + /* Link into list */ + last_messagewindow = messagewindow_last(); + if ( last_messagewindow != NULL ) { + assert(last_messagewindow->next == NULL); + last_messagewindow->next = messagewindow; + } else { + messagewindows = messagewindow; + } + messagewindow->next = NULL; + + if ( session != NULL ) { + + debug_print("MW %8p: Plugging into SB session %p.\n", messagewindow, session); + sbsessions_plug(session, messagewindow); + messagewindow_plug(messagewindow, session); + messagewindow->in_creation = FALSE; /* Fudge! */ + + } else { + + if ( (session = sbsessions_find_headless(username)) == NULL ) { + debug_print("MW %8p: Creating new SB session.\n", messagewindow); + messagewindow_plug(messagewindow, sbsessions_create_local(username)); + } else { + debug_print("MW %8p: Found headless session %p.\n", messagewindow, session); + messagewindow_plug(messagewindow, session); + messagewindow->in_creation = FALSE; /* Fudge! */ + } + sbsessions_plug(messagewindow->session, messagewindow); + + } + + /* Introduce new and local users. */ + messagewindow_adduser(messagewindow, username); + messagewindow_localpicture(messagewindow); + + messagewindow->in_creation = FALSE; + return messagewindow; + +} + +void messagewindow_create_if_none(const char *username, SbSession *session) { + + MessageWindow *messagewindow = messagewindow_find_single(username); + + if ( messagewindow ) { + debug_print("MW %8p: Already have IM window for %s.\n", messagewindow, username); + } else { + messagewindow_create(username, session); + } + +} + +/* Deal with a user joining a switchboard */ +void messagewindow_joined(MessageWindow *messagewindow, const char *username) { + + MwUser *mwuser = messagewindow_find_username(messagewindow, username); + + if ( mwuser == NULL ) { + mwuser = messagewindow_adduser(messagewindow, username); + } else { + messagewindow_resetstatusbar(messagewindow, username); /* Put Friendlyname (Username) in status bar. */ + } + + /* Only display message if this isn't the first user. */ + if ( messagewindow->session->num_users > 1 ) { + + char *text; + + /* Remember, newlines go BEFORE the text in the window. */ + messagewindow_addtext_system(messagewindow, "--> Join: "); + text = messagewindow_statusbarstring(username); + messagewindow_addtext_system_nonewline(messagewindow, text); + + free(text); + + } + + messagewindow_trypicture(messagewindow, mwuser->username); + +} + +/* Called when an attempt is made to write to an IM window which doesn't exist. */ +void messagewindow_mitigate(SbSession *session) { + + MessageWindow *messagewindow = NULL; + SbUser *user; + + if ( session->num_users == 1 ) { + messagewindow = messagewindow_find_single(session->users->username); + if ( messagewindow == NULL ) { + messagewindow = messagewindow_create(session->users->username, session); + } + } else { + /* If there's more than one user, there's no chance of window reuse. */ + messagewindow = messagewindow_create(session->users->username, session); + } + + /* IM window favours most recently 'mitigated' session. */ + messagewindow_plug(messagewindow, session); + /* More than one session can report to one IM window. */ + sbsessions_plug(session, messagewindow); + + user = session->users; + while ( user != NULL ) { + debug_print("MW %8p: Adding '%s'\n", messagewindow, user->username); + messagewindow_joined(messagewindow, user->username); + user = user->next; + } + +} + +static void messagewindow_disable(MessageWindow *messagewindow) { + + GtkWidget *menuitem; + + gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->textbox), FALSE); + gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->gtk_ink), FALSE); + + menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/conversation/invite_user"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/block_user"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/send_file"); + gtk_widget_set_sensitive(menuitem, FALSE); + menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/nudge"); + gtk_widget_set_sensitive(menuitem, FALSE); + +} + +static void messagewindow_enable(MessageWindow *messagewindow) { + + GtkWidget *menuitem; + + gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->textbox), TRUE); + gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->gtk_ink), TRUE); + + menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/conversation/invite_user"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/block_user"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/send_file"); + gtk_widget_set_sensitive(menuitem, TRUE); + menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/nudge"); + gtk_widget_set_sensitive(menuitem, TRUE); + +} + +void messagewindow_disable_all() { + + MessageWindow *messagewindow = messagewindows; + + debug_print("MW %8p: disabling all windows..\n", NULL); + + while ( messagewindow != NULL ) { + messagewindow_disable(messagewindow); + messagewindow = messagewindow->next; + } + +} + +void messagewindow_enable_all() { + + MessageWindow *messagewindow = messagewindows; + + debug_print("MW %8p: enabling all windows..\n", NULL); + + while ( messagewindow != NULL ) { + messagewindow_enable(messagewindow); + messagewindow = messagewindow->next; + } + + +} + +int messagewindow_stoptyping(MwUser *user) { + + MessageWindow *messagewindow; + + /* Careful. By the time this callback gets called, the user or window might not exist. */ + if ( ( messagewindow = messagewindow_find_user(user)) != NULL ) { + + if ( user->typing_callback != 0 ) { + gtk_timeout_remove(user->typing_callback); + user->typing_callback = 0; + } + + messagewindow_resetstatusbar(messagewindow, user->username); + user->typing_callback = 0; + + } + + return FALSE; + +} + +void messagewindow_stoptypingbyusername(MessageWindow *messagewindow, const char *username) { + MwUser *user = messagewindow_find_username(messagewindow, username); + messagewindow_stoptyping(user); +} + +void messagewindow_starttyping(MessageWindow *messagewindow, const char *username) { + + MwUser *user; + + user = messagewindow_find_username(messagewindow, username); + if ( user != NULL ) { + + messagewindow_typing(messagewindow, username); + if ( user->typing_callback != 0 ) { + gtk_timeout_remove(user->typing_callback); + user->typing_callback = 0; + } + user->typing_callback = gtk_timeout_add(6000, (GtkFunction)messagewindow_stoptyping, user); + + } + +} + +void messagewindow_notifyoffline(const char *username) { + + MessageWindow *messagewindow = messagewindows; + + while ( messagewindow != NULL ) { + MwUser *user = messagewindow->users; + while ( user != NULL ) { + if ( strcmp(user->username, username) == 0) { + + const char *fname; + char *friendlyname; + char *message; + + fname = contactlist_friendlyname(username); + if ( fname == NULL ) { + friendlyname = malloc(strlen(username)+1); + strcpy(friendlyname, username); + } else { + friendlyname = routines_urldecode(fname); + } + + message = malloc(strlen(friendlyname)+19); + + debug_print("MW %8p: '%s' went offline.\n", messagewindow, username); + + strcpy(message, "*** "); + strcat(message, friendlyname); + strcat(message, " went offline."); + messagewindow_addtext_system(messagewindow, message); + free(message); + free(friendlyname); + + } + user = user->next; + + } + messagewindow = messagewindow->next; + } + +} diff --git a/src/messagewindow.h b/src/messagewindow.h new file mode 100644 index 0000000..7c8397d --- /dev/null +++ b/src/messagewindow.h @@ -0,0 +1,118 @@ +/* + * messagewindow.h + * + * IM windows + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef MESSAGEWINDOW_H +#define MESSAGEWINDOW_H + +#include "sbsessions.h" +#include "gtk-ink.h" + +typedef struct _mwuser { + + struct _mwuser *next; + + char *username; + + GtkWidget *bar; /* GTK label containing the user's name. */ + GtkWidget *bar_hbox; /* hbox into which "bar" fits */ + GtkWidget *avatar_eventbox; /* Eventbox into which the "Display picture" fits */ + GtkWidget *avatar; /* The "Display picture" itself (GtkImage) */ + GdkPixbuf *avatar_pixbuf; /* Corresponding pixbuf */ + + int typing_callback; /* Callback to unset "(Typing) " status. */ + +} MwUser; + +typedef struct _messagewindow { + + struct _messagewindow *next; + + SbSession *session; /* SB session this window talks to */ + + MwUser *users; + unsigned int num_users; /* Number of people in this window. */ + unsigned int first_event; + unsigned int in_creation; /* Non-zero suppresses opening of an SB session. */ + unsigned int last_was_nak; /* Last text to this window was NAK warning. */ + + GtkWidget *window; /* Overall window. */ + GtkWidget *windowbox; /* VBox into which to pack Stuff (like status bars) */ + GtkWidget *bigvbox; + GtkWidget *avatar_eventbox; /* Eventbox into which the "Display picture" fits */ + GtkWidget *avatar; /* The "Display picture" itself (GtkImage) */ + GdkPixbuf *avatar_pixbuf; /* Corresponding pixbug */ + GtkWidget *picture_list; /* VBox into which the column of avatars fits */ + GtkWidget *messages; /* Widget into which to put messages */ + GtkWidget *textbox; /* Text Entry widget */ + GtkTextMark *mark; /* Mark at the end of the text. */ + GtkUIManager *ui; /* UI manager */ + GtkActionGroup *action_group; /* Action group */ + GtkWidget *fontsdialog; /* Fonts dialog box */ + GtkWidget *ofont_button; /* Font button to set override font. */ + GtkWidget *ocolour_button; /* Colour button to set override font. */ + GtkWidget *gtk_ink; /* GtkInk widget */ + GtkWidget *textbox_hbox; + GtkWidget *ink_button; + unsigned int stuck; /* Flag to see if the window is scrolled to the bottom. */ + + GdkColor *localcolour_gdk; /* GdkColor version of local user's colour. */ + char *localcolour_string; /* String version of local user's colour. */ + GdkColor *ocolour_gdk; /* GdkColor to override contacts' colours with. */ + unsigned int ofontoverride; /* Override contacts' colours? */ + char *ocolour_string; /* String version of ocolour_gdk */ + char *localfont; /* Local user's font in a format to throw at the SB (FN=xxx EF=xxx etc) */ + char *dislocalfont; /* Local user's font in a format to use for local display. */ + char *ofont; /* Font for contacts' messages. */ + + unsigned int ircstyle; + unsigned int timestamps; + unsigned int showemoticons; + +} MessageWindow; + +/* Operations on IM windows themselves. */ +extern MessageWindow *messagewindow_create(const char *username, SbSession *session); +extern void messagewindow_create_if_none(const char *username, SbSession *session); +extern void messagewindow_mitigate(SbSession *session); +extern void messagewindow_unplug(SbSession *session); +extern void messagewindow_addtext_system(MessageWindow *messagewindow, const char *text); +extern void messagewindow_addtext_user_nonewline(MessageWindow *messagewindow, const char *text, int length, const char *colour, const char *font); +extern void messagewindow_reportdropped(MessageWindow *messagewindow, char *message, size_t length); +extern int messagewindow_get_last_was_nak(MessageWindow *messagewindow); +extern void messagewindow_set_last_was_nak(MessageWindow *messagewindow, int last_was_nak); + +/* Operations on individual IM window users. */ +extern void messagewindow_joined(MessageWindow *messagewindow, const char *username); +extern void messagewindow_removeuser(MessageWindow *messagewindow, const char *username); +extern void messagewindow_starttyping(MessageWindow *messagewindow, const char *username); +extern int messagewindow_stoptyping(MwUser *user); +extern void messagewindow_stoptypingbyusername(MessageWindow *messagewindow, const char *username); +extern void messagewindow_picturekick(const char *username); +extern void messagewindow_notifyoffline(const char *username); + +/* Other stuff */ +extern void messagewindow_disable_all(); +extern void messagewindow_enable_all(); + +#endif /* MESSAGEWINDOW_H */ diff --git a/src/mime.c b/src/mime.c new file mode 100644 index 0000000..970f29a --- /dev/null +++ b/src/mime.c @@ -0,0 +1,242 @@ +/* + * mime.c + * + * Rudimentary MIME parser + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "routines.h" +#include "debug.h" + +/* Get the length of the MIME header, including the two newlines (i.e. the offset for the body). */ +size_t mime_headerlength(const char *mime) { + + int status; + size_t offs = 0; + + status = 0; + + /* Simply detect two consecutive newlines. */ + while ( offs < strlen(mime) ) { + + /* State machine :D */ + if ( (mime[offs] == '\r') && (status == 1) ) { + /* \r\r */ + assert(offs+1 <= strlen(mime)); + return offs+1; + } + if ( (mime[offs] == '\n') && (status == 4) ) { + /* \n\n */ + assert(offs+1 <= strlen(mime)); + return offs+1; + } + if ( (mime[offs] == '\n') && (status == 0) ) { + status = 4; + } + if ( (mime[offs] == '\r') && (status == 0) ) { + status = 1; + } + if ( (mime[offs] == '\n') && (status == 1) ) { + status = 2; + } + if ( (mime[offs] == '\r') && (status == 2) ) { + status = 3; + } + if ( (mime[offs] == '\n') && (status == 3) ) { + /* \r\n\r\n */ + assert(offs+1 <= strlen(mime)); + return offs+1; + } + + if ( (mime[offs] != '\r') && (mime[offs] != '\n') ) { + status = 0; + } + + offs++; + + } + + /* Whoops */ + return 0; + +} + +/* Return the body of a MIME message. Don't free() the returned string. */ +const char *mime_getbody(const char *mime) { + + return mime+mime_headerlength(mime); + +} + +/* Return the value of a given MIME field. Returned string is free-able. */ +static char *mime_getfield_internal(const char *mime, const char *given_field, int anywhere) { + + char *line; + char *minibuffer; + size_t offs = 0; + size_t mime_size; + char *field; + int end_headers = 0; + + mime_size = strlen(mime); + + /* Last character of "field" should be a colon. Add it if it's not already there. */ + if ( given_field[strlen(given_field)-1] != ':' ) { + + field = malloc(strlen(given_field) + 2); + strcpy(field, given_field); + field[strlen(given_field)] = ':'; + field[strlen(given_field)+1] = '\0'; + + } else { + field = strdup(given_field); + } + + line = malloc(strlen(mime)+1); /* Bigger than the maximum possible output size */ + while ( (offs < mime_size) && !end_headers ) { + + size_t line_offs = 0; + int found_eof_line = 0; + + while ( (found_eof_line == 0) && (offs<strlen(mime)) ) { + + if ( (mime[offs] == '\r') || (mime[offs] == '\n') ) { + + found_eof_line = 1; + line[line_offs]='\0'; + + if ( !anywhere ) { + if ( offs<(mime_size-4) ) { + if ( strncmp(mime+offs, "\r\n\r\n", 4) == 0 ) { + end_headers = 1; + } + } + if ( offs<(mime_size-2) ) { + if ( strncmp(mime+offs, "\n\n", 2) == 0 ) { + end_headers = 1; + } + } + if ( offs<(mime_size-4) ) { + if ( strncmp(mime+offs, "\r\r", 2) == 0 ) { + end_headers = 1; + } + } + } + + } else { + line[line_offs] = mime[offs]; + offs++; + line_offs++; + } + + } + if ( offs >= mime_size-1 ) { + line[line_offs]='\0'; + } + + minibuffer = routines_lindex(line, 0); + if ( strcmp(minibuffer, field) == 0 ) { + + char *fieldcontents; + free(minibuffer); + fieldcontents = routines_lindexend(line, 1); + free(line); + free(field); + return fieldcontents; + + } + free(minibuffer); + offs+=2; + } + + /* Field not found, so return an empty string (free()-able) */ + free(line); + free(field); + minibuffer = malloc(1); + minibuffer[0]='\0'; + return minibuffer; + +} + +char *mime_getfield(const char *mime, const char *given_field) { + return mime_getfield_internal(mime, given_field, 0); +} + +char *mime_getfield_anywhere(const char *mime, const char *given_field) { + return mime_getfield_internal(mime, given_field, 1); +} + +/* Remove a given header from a MIME message. */ +char *mime_removeheader(const char *mime, size_t mimelength, const char *field) { + + size_t i; + size_t fieldlen; + + fieldlen = strlen(field); + + for ( i=0; i<mimelength-fieldlen; i++ ) { + + if ( strncmp(mime+i, field, fieldlen) == 0 ) { + + size_t pos = i; + size_t endpos; + for ( pos=i; pos<mimelength-fieldlen; pos++ ) { + + /* Works with any sensible type of line-ending. */ + if ( (mime[pos] == '\r') || (mime[pos] == '\n') ) { + + char *new_mime; + if ( (mime[pos+1] == '\r') || (mime[pos+1] == '\n') ) { + endpos = pos+2; + } else { + endpos = pos+1; + } + new_mime = malloc(mimelength-(endpos-i)+1); + memcpy(new_mime, mime, i); + memcpy(new_mime+i, mime+endpos, mimelength-endpos); + new_mime[mimelength-(endpos-i)] = '\0'; + return new_mime; + } + + } + /* D'oh :( */ + debug_print("mime.c: didn't find end of field to be removed!\n"); + return strdup(mime); + + } + + if ( (strncmp(mime+i, "\r\n\r\n", 4) == 0) || (strncmp(mime+i, "\n\n", 2) == 0) || (strncmp(mime+i, "\r\r", 2) == 0) ) { + debug_print("Reached end of MIME headers before finding field to remove.\n"); + break; + } + + } + + return strdup(mime); + +} diff --git a/src/mime.h b/src/mime.h new file mode 100644 index 0000000..0caad7e --- /dev/null +++ b/src/mime.h @@ -0,0 +1,35 @@ +/* + * mime.h + * + * Rudimentary MIME parser + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef MIME_H +#define MIME_H + +extern char *mime_getfield(const char *mime, const char *field); +extern char *mime_getfield_anywhere(const char *mime, const char *field); +extern const char *mime_getbody(const char *mime); +extern size_t mime_headerlength(const char *mime); +extern char *mime_removeheader(const char *mime, size_t mimelength, const char *field); + +#endif /* MIME_H */ diff --git a/src/msngenerics.c b/src/msngenerics.c new file mode 100644 index 0000000..73d6ef4 --- /dev/null +++ b/src/msngenerics.c @@ -0,0 +1,71 @@ +/* + * msngenerics.c + * + * General MSN protocol schemas and semantics + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <string.h> + +#include "msngenerics.h" + +OnlineState msngenerics_decodestatus(const char *status) { + + OnlineState state = ONLINE_ERR; + + if ( strcmp(status, "NLN") == 0 ) { + state = ONLINE_NLN; + } + if ( strcmp(status, "IDL") == 0 ) { + state = ONLINE_IDL; + } + if ( strcmp(status, "AWY") == 0 ) { + state = ONLINE_AWY; + } + if ( strcmp(status, "BSY") == 0 ) { + state = ONLINE_BSY; + } + if ( strcmp(status, "BRB") == 0 ) { + state = ONLINE_BRB; + } + if ( strcmp(status, "PHN") == 0 ) { + state = ONLINE_PHN; + } + if ( strcmp(status, "LUN") == 0 ) { + state = ONLINE_LUN; + } + if ( strcmp(status, "HDN") == 0 ) { + state = ONLINE_HDN; + } + if ( strcmp(status, "FLN") == 0 ) { + state = ONLINE_FLN; + } + + assert(state != ONLINE_ERR); + + /* It's up to the caller to check that the status given is actually valid in context. */ + return state; + +} diff --git a/src/msngenerics.h b/src/msngenerics.h new file mode 100644 index 0000000..0bbaa27 --- /dev/null +++ b/src/msngenerics.h @@ -0,0 +1,47 @@ +/* + * msngenerics.h + * + * General MSN protocol schemas and semantics + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef MSNGENERICS_H +#define MSNGENERICS_H + +#define DEFAULT_NS_PORT 1863 +#define DEFAULT_SB_PORT 1863 + +/* Different user statuses. */ +typedef enum { + ONLINE_ERR, /* Used to catch errors */ + ONLINE_NLN, /* Online */ + ONLINE_IDL, /* Idle */ + ONLINE_AWY, /* Away */ + ONLINE_BSY, /* Busy */ + ONLINE_BRB, /* Be Right Back */ + ONLINE_PHN, /* On The Phone */ + ONLINE_LUN, /* Lunch */ + ONLINE_HDN, /* Appear Offline (only allowed for the local user) */ + ONLINE_FLN /* Offline */ +} OnlineState; + +OnlineState msngenerics_decodestatus(const char *status); + +#endif /* MSNGENERICS_H */ diff --git a/src/msninvite.c b/src/msninvite.c new file mode 100644 index 0000000..bcfba27 --- /dev/null +++ b/src/msninvite.c @@ -0,0 +1,32 @@ +/* + * msninvite.c + * + * Handle MSMsgsInvite stuff (old-style file transfers etc) + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "sbsessions.h" + +void msninvite_parsemsg(SbSession *session, const char *msg) { +} diff --git a/src/msninvite.h b/src/msninvite.h new file mode 100644 index 0000000..1f13ab5 --- /dev/null +++ b/src/msninvite.h @@ -0,0 +1,32 @@ +/* + * msninvite.h + * + * Handle MSMsgsInvite stuff + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef MSNINVITE_H +#define MSNINVITE_H + +#include "sbsessions.h" + +extern void msninvite_parsemsg(SbSession *session, const char *msg); + +#endif /* MSNINVITE_H */ diff --git a/src/msnp11chl.c b/src/msnp11chl.c new file mode 100644 index 0000000..774de76 --- /dev/null +++ b/src/msnp11chl.c @@ -0,0 +1,175 @@ +/* + * msnp11.c + * + * New-style challenge + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * + * Special Notice for the MSNP11 Challenge Module + * ---------------------------------------------- + * The author(s) of this software performed no + * reverse-engineering of any Microsoft software + * in order to create this module. Information + * on the MSNP11-style challenge can be freely + * found on the Web. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdint.h> +#include <openssl/md5.h> +#include <stdlib.h> +#include <stdio.h> +#include <glib.h> + +#include "msnp11chl.h" + +#undef MSNP11CHL_DEBUG + +uint32_t msnp11chl_tolittle(uint32_t val) { + return GINT32_TO_LE(val); +} + +char *msnp11chl_response(const char *challenge) { + + char *flobbadob; + unsigned char *md5; + uint64_t words_1[4]; + uint64_t words_1_orig[4]; + uint32_t *words_2; + int nw2; + int i; + uint64_t low = 0; + uint64_t high = 0; + uint64_t key; + uint64_t hash1; + uint64_t hash2; + char *response; + + md5 = malloc(MD5_DIGEST_LENGTH); /* =16 */ + flobbadob = malloc(strlen(challenge)+strlen(CLIENT_CODE)+1); + strcpy(flobbadob, challenge); + strcat(flobbadob, CLIENT_CODE); + MD5(flobbadob, strlen(flobbadob), md5); + free(flobbadob); + + /* Split into four-byte words. */ + words_1_orig[0] = msnp11chl_tolittle((uint32_t)*(uint32_t *)(md5)); + words_1_orig[1] = msnp11chl_tolittle((uint32_t)*(uint32_t *)(md5+4)); + words_1_orig[2] = msnp11chl_tolittle((uint32_t)*(uint32_t *)(md5+8)); + words_1_orig[3] = msnp11chl_tolittle((uint32_t)*(uint32_t *)(md5+12)); + free(md5); + + for (i=0; i<4; i++) { + words_1[i] = words_1_orig[i] & 0x7FFFFFFF; + } + +#ifdef MSNP11CHL_DEBUG + printf("words_1[0] = 0x%08lx\n", words_1[0]); + printf("words_1[1] = 0x%08lx\n", words_1[1]); + printf("words_1[2] = 0x%08lx\n", words_1[2]); + printf("words_1[3] = 0x%08lx\n", words_1[3]); +#endif /* MSNP11CHL_DEBUG */ + + flobbadob = malloc(strlen(challenge)+strlen(CLIENT_ID)+9); + strcpy(flobbadob, challenge); + strcat(flobbadob, CLIENT_ID); + while ( strlen(flobbadob) % 8 != 0 ) { + strcat(flobbadob, "0"); + } + + /* Split into four-byte words. */ + words_2 = malloc(strlen(flobbadob)); + memcpy(words_2, flobbadob, strlen(flobbadob)); /* Not including terminator. */ + nw2 = strlen(flobbadob)/4; /* = number of members in words_2[] */ + free(flobbadob); + for (i=0; i<nw2; i++) { + words_2[i] = GINT32_TO_LE(words_2[i]); +#ifdef MSNP11CHL_DEBUG + printf("words_2[%i] = 0x%08lx\n", i, words_2[i]); +#endif /* MSNP11CHL_DEBUG */ + } + + for (i = 0; i<nw2; i+=2) { + + uint64_t temp = words_2[i]; + temp = (0x0E79A9C1 * temp); + temp = temp % 0x7FFFFFFF; + temp += high; + temp = words_1[0] * temp + words_1[1]; + temp = temp % 0x7FFFFFFF; + +#ifdef MSNP11CHL_DEBUG + printf("1: temp = 0x%08lx", temp); + printf(" high = 0x%08lx", high); + printf(" low = 0x%08lx\n", low); +#endif /* MSNP11CHL_DEBUG */ + + high = words_2[i+1]; + high = (high + temp) % 0x7FFFFFFF; + high = words_1[2] * high + words_1[3]; + high = high % 0x7FFFFFFF; + + low = low + high + temp; + +#ifdef MSNP11CHL_DEBUG + printf("2: temp = 0x%08lx", temp); + printf(" high = 0x%08lx", high); + printf(" low = 0x%08lx\n", low); +#endif /* MSNP11CHL_DEBUG */ + + } + + free(words_2); + + high = (high + words_1[1]) % 0x7FFFFFFF; + low = (low + words_1[3]) % 0x7FFFFFFF; +#ifdef MSNP11CHL_DEBUG + printf("high = 0x%08lx", high); + printf(" low = 0x%08lx\n", low); +#endif /* MSNP11CHL_DEBUG */ + + /* "high" is a 32-bit integer in a 64-bit type, so swapping it automatically shifts it by 32 bits */ + key = (low<<32) + high; +#ifdef MSNP11CHL_DEBUG + printf("key = %llx\n", key); +#endif /* MSNP11CHL_DEBUG */ + + hash1 = words_1_orig[0] + (words_1_orig[1]<<32); + hash2 = words_1_orig[2] + (words_1_orig[3]<<32); +#ifdef MSNP11CHL_DEBUG + printf("hash1 = 0x%016llx, hash2 = 0x%016llx\n", hash1, hash2); +#endif /* MSNP11CHL_DEBUG */ + + response = malloc(33); + sprintf(response, "%.16llx%.16llx", GUINT64_SWAP_LE_BE(key^hash1), GUINT64_SWAP_LE_BE(key^hash2)); + + for ( i=0; i<strlen(response); i++ ) { + if ( response[i]==' ' ) { + response[i]='0'; + } + } + + return response; + +} diff --git a/src/msnp11chl.h b/src/msnp11chl.h new file mode 100644 index 0000000..c789cf2 --- /dev/null +++ b/src/msnp11chl.h @@ -0,0 +1,48 @@ +/* + * msnp11.h + * + * New-style challenge + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * + * Special Notice for the MSNP11 Challenge Module + * ---------------------------------------------- + * The author(s) of this software performed no + * reverse-engineering of any Microsoft software + * in order to create this module. Information + * on the MSNP11-style challenge can be freely + * found on the Web. + * + */ + +#ifndef MSNP11CHL_H +#define MSNP11CHL_H + +extern char *msnp11chl_response(const char *challenge); + +//#define CLIENT_ID "PROD0104U6VVM{UJ" +//#define CLIENT_CODE "VK67B}379XYM5}$T" + +#define CLIENT_ID "PROD0101{0RM?UBW" +#define CLIENT_CODE "CFHUR$52U_{VIX5T" + +//#define CLIENT_ID "PROD0090YUAUV{2B" +//#define CLIENT_CODE "YMM8C_H7KCQ2S_KL" + +#endif /* MSNP11CHL_H */ diff --git a/src/msnp2p.c b/src/msnp2p.c new file mode 100644 index 0000000..5cd2473 --- /dev/null +++ b/src/msnp2p.c @@ -0,0 +1,1325 @@ +/* + * msnp2p.c + * + * Wizb Technologies' Combined MSNP2P/MSNSLP layer + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <glob.h> +#include <sys/stat.h> +#include <assert.h> + +#include "sbsessions.h" +#include "mime.h" +#include "options.h" +#include "debug.h" +#include "sbprotocol.h" +#include "avatars.h" +#include "routines.h" +#include "xml.h" +#include "contactlist.h" + +#define MSNP2P_DEBUG + +static GList *msnp2p_list = NULL; + +struct _mpsession { + + SbSession *parent; + char *username; + enum { + MSNP2P_DIR_RECV, + MSNP2P_DIR_SEND + } direction; + + unsigned int baseidentifier; + unsigned int id_increment; + enum { + MSNP2P_TYPE_DP, + MSNP2P_TYPE_INK, + MSNP2P_TYPE_FT + } type; + unsigned int session_id; + FILE *fh; + char *save_filename; + char *sha1d; /* The SHA1D field from the MSNObject, after xml_killillegalchars. */ + char *via; + char *call_id; + size_t offset; + void *data; + guint delete_timeout; + +}; +typedef struct _mpsession MpSession; + +struct _mpheader { + + uint32_t session_id; + uint32_t identifier; + uint64_t offset; + uint64_t total_size; + uint32_t size; + uint32_t flags; + uint32_t ack_id; + uint32_t ack_sess; + uint64_t ack_size; + +}; +typedef struct _mpheader MpHeader; + +static MpSession *msnp2p_new() { + + MpSession *mpsession = malloc(sizeof(MpSession)); + + mpsession->save_filename = NULL; + mpsession->sha1d = NULL; + mpsession->via = NULL; + mpsession->call_id = NULL; + mpsession->username = NULL; + mpsession->data = NULL; + mpsession->delete_timeout = 0; + + return mpsession; + +} + +static gboolean msnp2p_closesession(MpSession *mpsession) { + + assert(mpsession != NULL); + + if ( mpsession->via != NULL ) { + free(mpsession->via); + } + if ( mpsession->call_id != NULL ) { + free(mpsession->call_id); + } + if ( mpsession->username != NULL ) { + debug_print("MP %8p: Deleting an MSNP2P session to '%s'\n", mpsession, mpsession->username); + free(mpsession->username); + } else { + debug_print("MP %8p: Deleting an MSNP2P session.\n", mpsession); + } + if ( mpsession->save_filename != NULL ) { + free(mpsession->save_filename); + } + if ( mpsession->sha1d != NULL ) { + free(mpsession->sha1d); + } + if ( mpsession->delete_timeout != 0 ) { + g_source_remove(mpsession->delete_timeout); + } + if ( mpsession->data != NULL ) { + free(mpsession->data); + } + + msnp2p_list = g_list_remove(msnp2p_list, mpsession); + + free(mpsession); + + return FALSE; + +} + +/* Yes. Not Pretty. */ +static void msnp2p_send(SbSession *session, MpHeader mpheader, const char *username, const char *payload, int payload_length, int app_id) { + + int total_message_length; + char *msnp2p_text; + char *message_stub; + int stub_length; + int sf; + char *final; + + /* Create the overall header for the message. */ + msnp2p_text = malloc(74 + strlen(username)); + if ( msnp2p_text == NULL ) { + debug_print("MP: Not enough memory to send MSNP2P message.\n"); + return; + } + strcpy(msnp2p_text, "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: "); + strcat(msnp2p_text, username); + strcat(msnp2p_text, "\r\n\r\n"); + + /* Calculate the total message length. */ + total_message_length = strlen(msnp2p_text) + 48 + payload_length + 4; + + /* Snippet */ + message_stub = malloc(9); + if ( message_stub == NULL ) { + + debug_print("MP: Not enough memory to send MSNP2P message.\n"); + free(msnp2p_text); + return; + + } + assert(total_message_length < 10000); /* i.e. fits into four digits. */ + sprintf(message_stub, "D %i\r\n", total_message_length); + stub_length = strlen(message_stub); + + final = malloc(stub_length + strlen(msnp2p_text) + 48 + payload_length + 4); + if ( final == NULL ) { + + debug_print("MP: Not enough memory to send MSNP2P message.\n"); + free(message_stub); + free(msnp2p_text); + return; + + } + + strcpy(final, message_stub); + free(message_stub); + strcat(final, msnp2p_text); + free(msnp2p_text); + sf = strlen(final); + + memcpy (final+sf, &mpheader, 48); + memcpy (final+sf+48, payload, payload_length); + *((int *)(final+sf+48+payload_length)) = htonl(app_id); + + sbprotocol_sendtr_nonewline(session, "MSG", final, total_message_length+stub_length); + free(final); + +} + +static gint msnp2p_compare_identifier(MpSession *mpsession, unsigned int *identifier) { + + if ( mpsession->baseidentifier == *identifier ) { + return 0; + } + + return 1; + +} + +static gint msnp2p_compare_parent(MpSession *mpsession, SbSession **parent) { + + if ( mpsession->parent == *parent ) { + return 0; + } + + return 1; + +} + +static gint msnp2p_compare_sessionid(MpSession *mpsession, unsigned int *sessionid) { + + if ( mpsession->session_id == *sessionid ) { + return 0; + } + + return 1; + +} + +static gint msnp2p_compare_callid(MpSession *mpsession, char *call_id) { + + if ( mpsession->call_id != NULL ) { + if ( strcmp(mpsession->call_id, call_id) == 0 ) { + return 0; + } + } + + return 1; + +} + +static gint msnp2p_compare_savefilename(MpSession *mpsession, char *save_filename) { + + if ( mpsession->save_filename != NULL ) { + if ( strcmp(mpsession->save_filename, save_filename) == 0 ) { + return 0; + } + } + + return 1; + +} + +/*static gint msnp2p_compare_sha1d(MpSession *mpsession, char *sha1d) { + + if ( mpsession->sha1d != NULL ) { + if ( strcmp(mpsession->sha1d, sha1d) == 0 ) { + return 0; + } + } + + return 1; + +}*/ + +static MpSession *msnp2p_find_identifier(unsigned long int identifier) { + + GList *result; + + result = g_list_find_custom(msnp2p_list, &identifier, (GCompareFunc)msnp2p_compare_identifier); + + if ( result != NULL ) { + return (MpSession *)result->data; + } + + return NULL; + +} + +static MpSession *msnp2p_find_sessionid(unsigned long int sessionid) { + + GList *result; + + result = g_list_find_custom(msnp2p_list, &sessionid, (GCompareFunc)msnp2p_compare_sessionid); + + if ( result != NULL ) { + return (MpSession *)result->data; + } + + return NULL; + +} + +static MpSession *msnp2p_find_callid(char *callid) { + + GList *result; + + result = g_list_find_custom(msnp2p_list, callid, (GCompareFunc)msnp2p_compare_callid); + + if ( result != NULL ) { + return (MpSession *)result->data; + } + + return NULL; + +} + +/* Who invented this cr*p? */ +static void msnp2p_inc(MpSession *mpsession) { + + mpsession->id_increment++; + if ( mpsession->id_increment == -1 ) { + mpsession->id_increment = 1; + } + +} + +static MpSession *msnp2p_check_ok(const char *msnslp) { + + char *session_id_string; + MpSession *mpsession; + + session_id_string = mime_getfield_anywhere(msnslp, "SessionID"); + if ( strlen(session_id_string) == 0 ) { + debug_print("MP: Couldn't find SessionID field.\n"); + free(session_id_string); + return NULL; + } + mpsession = msnp2p_find_sessionid(atoi(session_id_string)); + free(session_id_string); + + if ( strncmp(msnslp, "MSNSLP/1.0 200 OK", 17) == 0 ) { + + debug_print("MP: Got MSNSLP/1.0 200 OK - "); + if ( mpsession == NULL ) { + debug_print("but session not found.\n"); + } else { + debug_print("and session found.\n"); + } + + } + + return mpsession; + +} + +static int msnp2p_check_bye(SbSession *session, const char *msnslp, MpHeader *mpheader) { + + char *callid_string; + MpSession *mpsession; + + callid_string = mime_getfield_anywhere(msnslp, "Call-ID"); + if ( callid_string == NULL ) { + debug_print("MP: Couldn't find Call-ID field.\n"); + return 0; + } + mpsession = msnp2p_find_callid(callid_string); + free(callid_string); + + if ( strncmp(msnslp, "BYE MSNMSGR", 11) == 0 ) { + + debug_print("MP: Got BYE MSNMSGR - "); + if ( mpsession == NULL ) { + debug_print("but session not found.\n"); + } else { + + MpHeader acknowledgement; + + debug_print("and session found.\n"); + + /* Send BYE ACK message. */ + acknowledgement.session_id = 0; + acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); + /* msnp2p_inc(mpsession); */ + acknowledgement.offset = 0; + acknowledgement.total_size = 0; + acknowledgement.size = 0; + acknowledgement.flags = GINT32_TO_LE(0x42); + acknowledgement.ack_sess = mpheader->identifier; + acknowledgement.ack_id = mpheader->ack_sess; + acknowledgement.ack_size = mpheader->size; + + debug_print("MP: Sending BYE ACK\n"); + msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0); + + /* Delay before deletion allows straggling packets to be collected. */ + debug_print("MP: Closing MSNP2P session in ten seconds...\n"); + mpsession->delete_timeout = g_timeout_add(10000, (GSourceFunc)msnp2p_closesession, mpsession); + + return 1; + + } + + } + + return 0; + +} + +static unsigned int msnp2p_cookie() { + + FILE *fh; + unsigned int seed; + float num; + unsigned int val; + + fh = fopen("/dev/urandom", "r"); + fread(&seed, sizeof(seed), 1, fh); + fclose(fh); + srand(seed); + + num = (float)rand() / (float)RAND_MAX; + val = (num * 16777216)+8; + + return val; + +} + +static MpSession *msnp2p_send_dp(MpSession *mpsession, SbSession *session, const char *msnslp) { + + MpHeader msnp2p_binary; + char *msnslp_block; + size_t msnslp_length; + glob_t glob_result; + struct stat *statbuf; + void *picture_data; + unsigned int file_offs; + int zero; + struct stat stat_buffer; + FILE *fh; + unsigned int size; + int readval; + char *request_details; + size_t request_details_length; + char *local_avatar; + + mpsession->via = mime_getfield_anywhere(msnslp, "Via:"); + if ( mpsession->via == NULL ) { + debug_print("MP: Couldn't find Via field.\n"); + free(mpsession); + return NULL; + } + mpsession->call_id = mime_getfield_anywhere(msnslp, "Call-ID:"); + if ( mpsession->call_id == NULL ) { + debug_print("MP: Couldn't find Call-ID field.\n"); + free(mpsession); + return NULL; + } + + request_details = malloc(26); + debug_print("MP: New session ID=%i\n", mpsession->session_id); + assert(mpsession->session_id <= 2147483647); + strcpy(request_details, "SessionID: "); + sprintf(request_details+strlen(request_details), "%i\r\n\r\n", mpsession->session_id); + request_details_length = strlen(request_details); + + msnslp_block = malloc( 176 + strlen(options_username()) + strlen(mpsession->username) + strlen(mpsession->via) + strlen(mpsession->call_id) + strlen(request_details) ); + strcpy(msnslp_block, "MSNSLP/1.0 200 OK\r\nTo: <msnmsgr:"); + strncat(msnslp_block, mpsession->username, 64); + strcat(msnslp_block, ">\r\nFrom: <msnmsgr:"); + strncat(msnslp_block, options_username(), 64); + strcat(msnslp_block, ">\r\nVia: "); + strncat(msnslp_block, mpsession->via, 256); + strcat(msnslp_block, "\r\nCSeq: 1 \r\nCall-ID: "); + strncat(msnslp_block, mpsession->call_id, 256); + strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionreqbody\r\nContent-Length: "); + assert(request_details_length + 1 < 10000); + sprintf(msnslp_block+strlen(msnslp_block), "%i", request_details_length+1); + strcat(msnslp_block, "\r\n\r\n"); + strcat(msnslp_block, request_details); + free(request_details); + + msnslp_length = strlen(msnslp_block); + + msnp2p_binary.session_id = 0; + msnp2p_binary.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); + msnp2p_inc(mpsession); + msnp2p_binary.offset = 0; + msnp2p_binary.total_size = GINT64_TO_LE(msnslp_length+1); + msnp2p_binary.size = GINT64_TO_LE(msnslp_length+1); + msnp2p_binary.flags = 0; + msnp2p_binary.ack_sess = GINT32_TO_LE(mpsession->session_id); + msnp2p_binary.ack_id = 0; + msnp2p_binary.ack_size = 0; + debug_print("MP: Sending 200 OK message\n"); + msnp2p_send(session, msnp2p_binary, mpsession->username, msnslp_block, strlen(msnslp_block)+1, 0); + free(msnslp_block); + + local_avatar = avatars_local(); + glob(local_avatar, GLOB_TILDE, NULL, &glob_result); + free(local_avatar); + + statbuf = &stat_buffer; + if ( stat(glob_result.gl_pathv[0], statbuf) == -1 ) { + debug_print("MP: Couldn't find avatar file :(\n"); + free(mpsession); + return NULL; + } + + size = (int)statbuf->st_size; + assert(size > 0); + + fh = fopen(glob_result.gl_pathv[0], "r"); + if ( fh == NULL ) { + debug_print("MP: Couldn't open avatar file.\n"); + free(mpsession); + return NULL; + } + picture_data = malloc(size); + readval = fread(picture_data, 1, size, fh); + if ( readval < 0 ) { + debug_print("MP: Couldn't read avatar file.\n"); + free(mpsession); + free(picture_data); + return NULL; + } + debug_print("MP: Read %i bytes of avatar file\n", readval); + fclose(fh); + globfree(&glob_result); + + msnp2p_binary.session_id = GINT32_TO_LE(mpsession->session_id); + msnp2p_binary.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); + msnp2p_inc(mpsession); + msnp2p_binary.offset = 0; + msnp2p_binary.total_size = GINT64_TO_LE(4); + msnp2p_binary.size = GINT64_TO_LE(4); + msnp2p_binary.flags = 0; + msnp2p_binary.ack_sess = GINT32_TO_LE(123456); + msnp2p_binary.ack_id = 0; + msnp2p_binary.ack_size = 0; + zero = 0; + debug_print("MP: Sending data preparation message\n"); + msnp2p_send(session, msnp2p_binary, mpsession->username, (char *)&zero, 4, 1); + + msnp2p_binary.session_id = GINT32_TO_LE(mpsession->session_id); + msnp2p_binary.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); + msnp2p_inc(mpsession); + msnp2p_binary.total_size = GINT64_TO_LE(size); + msnp2p_binary.flags = GINT32_TO_LE(0x20); + msnp2p_binary.ack_id = 0; + msnp2p_binary.ack_size = 0; + + for ( file_offs=0; file_offs<size; file_offs+=1202 ) { + + msnp2p_binary.offset = file_offs; + if ( (size-file_offs) > 1201 ) { + msnp2p_binary.size = GINT64_TO_LE(1202); + } else { + msnp2p_binary.size = GINT64_TO_LE((size-file_offs) % 1202); + } + msnp2p_binary.ack_sess = msnp2p_cookie(); + debug_print("MP: Sending %i bytes: %i-%i of %i\n", msnp2p_binary.size, (int)msnp2p_binary.offset, (int)(msnp2p_binary.offset+msnp2p_binary.size)-1, (int)msnp2p_binary.total_size); + msnp2p_send(session, msnp2p_binary, mpsession->username, picture_data+file_offs, msnp2p_binary.size, 1); + + } + + free(picture_data); + + return mpsession; + +} + +static MpSession *msnp2p_incomingsession(SbSession *session, const char *msnslp, MpHeader *mpheader) { + + MpSession *mpsession; + char *temp_name; + char *session_id_string; + unsigned int session_id; + MpHeader acknowledgement; + char *euf_guid; + char *content_type; + + if ( strncmp(msnslp, "INVITE MSNMSGR:", 15) != 0 ) { + return NULL; + } + + content_type = mime_getfield(msnslp, "Content-Type"); + if ( strcmp(content_type, "application/x-msnmsgr-transreqbody") == 0 ) { + } + free(content_type); + + session_id_string = mime_getfield_anywhere(msnslp, "SessionID:"); + if ( strlen(session_id_string) == 0 ) { + debug_print("MP: Couldn't find SessionID field.\n"); + free(session_id_string); + return NULL; + } + session_id = atoi(session_id_string); + free(session_id_string); + /* Return if this packet isn't to be dealt with here. */ + if ( session_id == 0 ) { + return NULL; + } + + euf_guid = mime_getfield_anywhere(msnslp, "EUF-GUID:"); + if ( strlen(euf_guid) == 0 ) { + debug_print("MP: Couldn't find EUF-GUID field.\n"); + return NULL; + } + + mpsession = msnp2p_new(); + if ( mpsession == NULL ) { + debug_print("MP: Not enough memory to start MSNP2P transfer.\n"); + return NULL; + } + + if ( strcmp(euf_guid, "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}") == 0 ) { + mpsession->type = MSNP2P_TYPE_DP; + } else if ( strcmp(euf_guid, "{5D3E02AB-6190-11D3-BBBB-00C04F795683}") == 0 ) { + mpsession->type = MSNP2P_TYPE_FT; + } else { + debug_print("MP: Unrecognised MSNSLP INVITE.\n"); + free(euf_guid); + return NULL; + } + free(euf_guid); + + mpsession->parent = session; + mpsession->baseidentifier = msnp2p_cookie(); + mpsession->id_increment = -3; + mpsession->session_id = session_id; + mpsession->direction = MSNP2P_DIR_SEND; + mpsession->save_filename = NULL; + mpsession->sha1d = NULL; + + /* Get actual username. */ + temp_name = mime_getfield(msnslp, "From:"); + if ( temp_name == NULL ) { + debug_print("MP: Couldn't find From field.\n"); + free(mpsession); + return NULL; + } + if ( strncmp(temp_name, "<msnmsgr:", 9) != 0 ) { + debug_print("MP: MSNSLP request not from \"<msnmsgr:*>\" - giving up.\n"); + free(mpsession); + return NULL; + } + mpsession->username = strdup(temp_name+9); + free(temp_name); + if ( mpsession->username == NULL ) { + debug_print("MP: Couldn't find From field.\n"); + free(mpsession); + return NULL; + } + mpsession->username[strlen(mpsession->username)-1] = '\0'; + + switch ( mpsession->type ) { + case MSNP2P_TYPE_DP : debug_print("MP: Got Display Picture Invite from %s\n", mpsession->username); break; + case MSNP2P_TYPE_FT : debug_print("MP: Got File Transfer Invite from %s\n", mpsession->username); break; + case MSNP2P_TYPE_INK : debug_print("MP: This doesn't happen.\n"); break; + } + + debug_print("MSNSLP: '%s'\n", msnslp); + + acknowledgement.session_id = 0; + acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier); + acknowledgement.offset = 0; + acknowledgement.total_size = mpheader->total_size; + acknowledgement.size = 0; + acknowledgement.flags = GINT32_TO_LE(2); + acknowledgement.ack_sess = mpheader->identifier; + acknowledgement.ack_id = mpheader->ack_sess; + acknowledgement.ack_size = mpheader->total_size; + debug_print("MP: Sending BaseIdentifier message\n"); + msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0); + + if ( mpsession->type == MSNP2P_TYPE_DP ) { + mpsession = msnp2p_send_dp(mpsession, session, msnslp); + } + + if ( mpsession != NULL ) { + msnp2p_list = g_list_append(msnp2p_list, mpsession); + } + + return mpsession; + +} + +static void msnp2p_sendbye(SbSession *session, MpSession *mpsession) { + + char *msnslp_block; + MpHeader mpheader; + int msnslp_length; + + msnslp_block = malloc( 294 + 2*strlen(mpsession->username) + strlen(options_username()) ); + strcpy(msnslp_block, "BYE MSNMSGR:"); + strncat(msnslp_block, mpsession->username, 64); + strcat(msnslp_block, " MSNSLP/1.0\r\nTo: <msnmsgr:"); + strncat(msnslp_block, mpsession->username, 64); + strcat(msnslp_block, ">\r\nFrom: <msnmsgr:"); + strncat(msnslp_block, options_username(), 64); + strcat(msnslp_block, ">\r\nVia: MSNSLP/1.0/TLP ;branch={33517CE4-02FC-4428-B6F4-39927229B722}\r\nCSeq: 0 \r\nCall-ID: "); + strncat(msnslp_block, mpsession->call_id, 38); + strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionclosebody\r\nContent-Length: 3"); + strcat(msnslp_block, "\r\n\r\n"); + + msnslp_length = strlen(msnslp_block); + + mpheader.session_id = 0; + mpheader.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); + msnp2p_inc(mpsession); + mpheader.offset = 0; + mpheader.total_size = GINT64_TO_LE(msnslp_length+1); + mpheader.size = GINT64_TO_LE(msnslp_length+1); + mpheader.flags = 0; + mpheader.ack_sess = GINT32_TO_LE(mpsession->session_id); + mpheader.ack_id = 0; + mpheader.ack_size = 0; + + debug_print("MP: Sending MSNSLP BYE for display picture.\n"); + msnp2p_send(session, mpheader, mpsession->username, msnslp_block, msnslp_length+1, 0); + + free(msnslp_block); + +} + +static void msnp2p_handledatapacket(SbSession *session, MpHeader *mpheader, MpSession *mpsession, const char *msnslp) { + + FILE *fh; + + debug_print("MP: Got data packet: %i-%i of %i\n", (int)mpheader->offset, (int)(mpheader->offset+mpheader->size)-1, (int)mpheader->total_size); + + fh = fopen(mpsession->save_filename, "a"); + if ( fh == NULL ) { + debug_print("MP: Couldn't open avatar file to write data.\n"); + return; + } + fwrite(msnslp, 1, mpheader->size, fh); + fclose(fh); + + /* Got all of data? */ + if ( mpheader->offset+mpheader->size >= mpheader->total_size ) { + + /* Send an acknowledgement for the data */ + MpHeader acknowledgement; + + acknowledgement.session_id = mpheader->session_id; + acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); + msnp2p_inc(mpsession); + acknowledgement.offset = 0; + acknowledgement.total_size = 0; + acknowledgement.size = 0; + acknowledgement.flags = GINT32_TO_LE(2); + acknowledgement.ack_sess = mpheader->session_id; + acknowledgement.ack_id = mpheader->identifier; + acknowledgement.ack_size = mpheader->total_size; + + msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0); + + msnp2p_sendbye(session, mpsession); + contactlist_picturekick_sha1d(mpsession->sha1d); + + /* Delay before deletion allows straggling packets to be collected. */ + debug_print("MP: Closing MSNP2P session in ten seconds...\n"); + mpsession->delete_timeout = g_timeout_add(10000, (GSourceFunc)msnp2p_closesession, mpsession); + + } + +} + +static MpSession *msnp2p_check_ink(SbSession *session, const char *username, MpHeader *header, const void *data, unsigned long int app_id) { + + MpSession *mpsession; + size_t datalength = GINT32_TO_LE(header->size); + + if ( !(header->session_id == 64) || !(app_id == 3) ) { + return NULL; + } + + debug_print("MP: Ink packet from '%s'\n", username); + + if ( header->offset == 0 ) { + + + debug_print("MP: First in a sequence of Ink packets.\n"); + mpsession = msnp2p_new(); + + mpsession->baseidentifier = GINT32_TO_LE(header->identifier); + mpsession->type = MSNP2P_TYPE_INK; + mpsession->direction = MSNP2P_DIR_RECV; + mpsession->parent = session; + mpsession->username = strdup(username); + mpsession->session_id = 0; + mpsession->via = NULL; + mpsession->save_filename = NULL; + mpsession->sha1d = NULL; + mpsession->call_id = NULL; + + if ( datalength <= 2000 ) { /* Sanity check */ + + mpsession->data = malloc(datalength); + memcpy(mpsession->data, data, datalength); + mpsession->offset = datalength; + + } else { + + debug_print("MP: Ink packet too big - binning.\n"); + free(mpsession); + return NULL; + + } + + msnp2p_list = g_list_append(msnp2p_list, mpsession); + + } else { + + mpsession = msnp2p_find_identifier(GINT32_TO_LE(header->identifier)); + if ( mpsession == NULL ) { + debug_print("MP: Unrecognised Ink packet.\n"); + return NULL; + } + + if ( datalength <= 2000 ) { + + mpsession->data = realloc(mpsession->data, (mpsession->offset) + datalength); + if ( mpsession->data == NULL ) { + debug_print("MP: Whoops! Couldn't allocate memory.\n"); + return NULL; + } + memcpy((mpsession->data)+(mpsession->offset), data, datalength); + mpsession->offset += datalength; + debug_print("MP: Got some continuing Ink data.\n"); + + } else { + debug_print("MP: Ink packet too big - binning.\n"); + return NULL; + } + + + } + + assert(mpsession != NULL); + + /* NB It's possible for a single message to be both the first and last in a sequence. */ + debug_print("MP: %i of %i bytes.\n", GINT64_TO_LE(header->offset), GINT64_TO_LE(header->total_size)); + if ( GINT64_TO_LE(header->offset) >= GINT64_TO_LE(header->total_size) ) { + + gchar *body; + glong new_length; + glong items_in; + GError *error; + + debug_print("MP: Last in a sequence of Ink packets.\n"); + + debug_print("MP: Decoding UTF-16: %i bytes in.\n", mpsession->offset); + body = g_utf16_to_utf8(mpsession->data, mpsession->offset, &items_in, &new_length, &error); + debug_print("MP: %i items in, %i items out.\n", items_in, new_length); + if ( error == NULL ) { + debug_print("MP: No error.\n"); + } else { + debug_print("MP: Error!\n"); + debug_print("%s'\n", error->message); + } + if ( body == NULL ) { + debug_print("MP: Conversion to UTF-8 failed: '%s'\n", error->message); + } else { + sbprotocol_parsemsg(session, body, new_length); + debug_print("MP: Ink (%i bytes): '%s'\n", new_length, body); + g_free(body); + } + + debug_print("MP: Closing MSNP2P session in ten seconds...\n"); + mpsession->delete_timeout = g_timeout_add(10000, (GSourceFunc)msnp2p_closesession, mpsession); + + } + + return mpsession; + +} + +void msnp2p_parsemsg(SbSession *session, const char *username, const char *whole_msg, size_t len) { + + char *p2p_dest; + MpSession *mpsession; + MpHeader *mpheader; + const char *msnslp; + const char *msg; + unsigned long int app_id; + + /* First check the destination of the message and ignore if appropriate. */ + p2p_dest = mime_getfield(whole_msg, "P2P-Dest"); + if ( p2p_dest == NULL ) { + debug_print("MP: Couldn't find P2P-Dest field.\n"); + return; + } + if ( strcasecmp(options_username(), p2p_dest) != 0 ) { + free(p2p_dest); + debug_print("MP: Ignoring misaddressed MSNP2P message.\n"); + return; + } + free(p2p_dest); + + /* Now forget about the MIME header. */ + len -= mime_headerlength(whole_msg); + msg = mime_getbody(whole_msg); + mpheader = (MpHeader *)msg; /* First bytes of "msg" are the header. Obviously. */ + msnslp = msg+sizeof(MpHeader); /* Contents */ + + /* Check the provided packet size is sane. */ + if ( len < GINT64_TO_LE(mpheader->size) ) { + debug_print("MP: 'size' field in header too big - rejecting packet.\n"); + return; + } + app_id = ntohl(*(int *)(msg+(mpheader->size)+48)); + +#ifdef MSNP2P_DEBUG + debug_print("MP: ---- MSNP2P packet ----\n"); + debug_print("MP: sessionid = 0x%lx\n", mpheader->session_id); + debug_print("MP: identifier = 0x%lx\n", mpheader->identifier); + debug_print("MP: offset = 0x%llx\n", mpheader->offset); + debug_print("MP: total_size = 0x%llx\n", mpheader->total_size); + debug_print("MP: size = 0x%lx\n", mpheader->size); + debug_print("MP: flags = 0x%lx\n", mpheader->flags); + debug_print("MP: ack_id = 0x%lx\n", mpheader->ack_id); + debug_print("MP: ack_sess = 0x%lx\n", mpheader->ack_sess); + debug_print("MP: ack_size = 0x%llx\n", mpheader->ack_size); + debug_print("MP: app_id = 0x%lx\n", app_id); +#endif /* MSNP2P_DEBUG */ + + /* Arrrggggggggggghhhhhhhhhhhhhhhhhhhhhh. */ + mpsession = msnp2p_find_identifier(GINT32_TO_LE(mpheader->ack_sess)); + if ( mpsession == NULL ) { + mpsession = msnp2p_find_sessionid(GINT32_TO_LE(mpheader->session_id)); + } + if ( mpsession == NULL ) { + mpsession = msnp2p_check_ok(msnslp); + } + if ( mpsession == NULL ) { + mpsession = msnp2p_find_sessionid(GINT32_TO_LE(mpheader->ack_id)); + } + if ( (mpsession == NULL) && (mpheader->session_id == 0) ) { + mpsession = msnp2p_incomingsession(session, msnslp, mpheader); + } + if ( msnp2p_check_bye(session, msnslp, mpheader) != 0 ) { + debug_print("MP: Got BYE.\n"); + return; + } + if ( mpsession == NULL ) { + mpsession = msnp2p_check_ink(session, username, mpheader, msnslp, app_id); + } + + /* Past this point, if the message hasn't been matched to a session then it's unrecognised. */ + + if ( mpsession == NULL ) { + debug_print("MP: Unrecognised MSNP2P "); + if ( (GINT32_TO_LE(mpheader->flags) & 2) != 0 ) { + debug_print("ack.\n"); + } else { + debug_print("packet:\n"); + debug_print("MP: Flags = %x\n", GINT32_TO_LE(mpheader->flags)); + debug_print("MP: Data: '%s'\n", msnslp); + } + return; + } + + if ( ( GINT32_TO_LE(mpheader->flags) & 2) != 0 ) { + debug_print("MP: Got ACK.\n"); + } + + if ( ( (GINT32_TO_LE(mpheader->flags) & 2) == 0) && (mpheader->size == mpheader->total_size) ) { + + /* Message wasn't an ACK, so probably needs an ACK sending */ + + MpHeader acknowledgement; + + acknowledgement.session_id = GINT32_TO_LE(mpheader->session_id); + acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); + /* msnp2p_inc(mpsession); */ + acknowledgement.offset = 0; + acknowledgement.total_size = 0; + acknowledgement.size = 0; + if ( GINT32_TO_LE(mpheader->flags) == 0x40 ) { + acknowledgement.flags = GINT32_TO_LE(0x80); + } else { + acknowledgement.flags = GINT32_TO_LE(2); + } + /* Copying directly from received header: don't touch endianness. */ + acknowledgement.ack_sess = mpheader->session_id; + acknowledgement.ack_id = mpheader->identifier; + acknowledgement.ack_size = mpheader->size; + + debug_print("MP: Sending ACK.\n"); + msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0); + + } + + if ( (app_id == 1) && (GINT64_TO_LE(mpheader->total_size) != 4) ) { + msnp2p_handledatapacket(session, mpheader, mpsession, msnslp); + } + +} + +/* Check for a receive session for the DP SHA1D given. */ +int msnp2p_retrieving(const char *save_filename) { + + GList *result = NULL; + + result = g_list_find_custom(msnp2p_list, save_filename, (GCompareFunc)msnp2p_compare_savefilename); + if ( result != NULL ) { + /* At least one session exists. Don't care what it is... */ + return 1; + } + + /* Nothing found. */ + return 0; + +} + +/* Initiate the downloading of a picture. */ +void msnp2p_getpicture(SbSession *session, const char *username, const char *dpobject) { + + MpSession *mpsession; + MpHeader mpheader; + + char *dpobject_decoded; + char *context; + char *data_hash; + char *filename; + + char *request_details; + int request_details_length; + char *request_details_length_string; + char *msnslp_block; + int msnslp_length; + glob_t glob_result; + struct stat stat_buffer; + struct stat *statbuf; + + debug_print("MP: Beginning download for DP from '%s'\n", username); + + assert(username != NULL); + assert(session != NULL); + + if ( dpobject == NULL ) { + debug_print("MP: Attempt to download avatar for user who doesn't have one.\n"); + return; + } + + if ( sbsessions_sessionready(session) == 0 ) { + debug_print("MP: Session isn't ready to download picture - not trying.\n"); + return; + } + + mpsession = msnp2p_new(); + if ( mpsession == NULL ) { + debug_print("MP: Not enough memory to do picture request - giving up.\n"); + return; + } + + mpsession->parent = session; + mpsession->baseidentifier = msnp2p_cookie(); + mpsession->session_id = msnp2p_cookie(); + mpsession->direction = MSNP2P_DIR_RECV; + mpsession->id_increment = -3; + mpsession->call_id = routines_guid(); + mpsession->via = NULL; + + debug_print("MP: Generated: Session ID '%i', BaseIdentifier '%i', Call-ID '%s'.\n", mpsession->session_id, mpsession->baseidentifier, mpsession->call_id); + + /* urldecode() the MSNObject and then base64 it to get the "Context" field, + then retrieve the "SHA1D" field from the (urldecoded) MSNObject to generate + the filename to save to. */ + dpobject_decoded = routines_urldecode(dpobject); + context = routines_base64(dpobject_decoded); + data_hash = xml_getfield(dpobject_decoded, "SHA1D"); + debug_print("MP: Got data hash: %s\n", data_hash); + free(dpobject_decoded); + filename = xml_killillegalchars(data_hash); + mpsession->sha1d = data_hash; /* Don't free this yet. */ + + /* Determine the full filename to save to. */ + glob("~", GLOB_TILDE, NULL, &glob_result); + mpsession->save_filename = malloc(strlen(filename)+strlen("/.tuxmessenger/avatars/")+strlen(glob_result.gl_pathv[0]) + 1); + if ( mpsession->save_filename == NULL ) { + + debug_print("MP: Not enough memory to do picture request - giving up.\n"); + free(mpsession->call_id); + free(mpsession); + return; + + } + strcpy(mpsession->save_filename, glob_result.gl_pathv[0]); + strcat(mpsession->save_filename, "/.tuxmessenger/avatars/"); + strcat(mpsession->save_filename, filename); + globfree(&glob_result); + free(filename); + + /* Check to see there isn't already a receive session going on for this user. */ + + if ( msnp2p_retrieving(mpsession->save_filename) != 0 ) { + + debug_print("MP: A DP receive session is already in progress to '%s' - not starting a new one.\n", mpsession->save_filename); + free(mpsession->call_id); + free(mpsession); + return; + + } + + /* Check if we already have this picture or not */ + statbuf = &stat_buffer; + if ( stat(mpsession->save_filename, statbuf) != -1 ) { + + /* messagewindow_trypicture should have worked this out before. */ + debug_print("MP: Already have this picture: %s\n", mpsession->save_filename); + free(mpsession->save_filename); + free(mpsession->call_id); + free(mpsession); + return; + + } + + /* Silly preliminaries out of the way. Time to do the request... */ + + /* Create the payload for the MSNSLP request */ + request_details = malloc( 96 + strlen(context) + 9 ); + if ( request_details == NULL ) { + + debug_print("MP: Not enough memory to do picture request - giving up.\n"); + free(mpsession->save_filename); + free(mpsession->call_id); + free(mpsession); + return; + + } + strcpy(request_details, "EUF-GUID: {A4268EEC-FEC5-49E5-95C3-F126696BDBF6}\r\nSessionID: "); + /* Check we're not about to screw ourselves over. 8 bytes were allowed + above to fit the sessionid into. */ + if ( mpsession->session_id > 99999999 ) { + + debug_print("MP: SessionID is too big - giving up.\n"); + free(request_details); + free(mpsession->save_filename); + free(mpsession->call_id); + free(mpsession); + return; + + } + sprintf(request_details+strlen(request_details), "%i", mpsession->session_id); + strcat(request_details, "\r\nAppID: 1\r\nContext: "); + strcat(request_details, context); + free(context); + strcat(request_details, "\r\n\r\n"); + request_details_length = strlen(request_details); + + request_details_length_string = malloc(5); + assert(request_details_length < 9998); /* Sanity check. */ + sprintf(request_details_length_string, "%i", request_details_length+1); + + /* Now create the MSNSLP request itself */ + msnslp_block = malloc( 291 + (2*strlen(username)) + strlen(options_username()) + strlen(request_details) + strlen(request_details_length_string) ); + strcpy(msnslp_block, "INVITE MSNMSGR:"); + strncat(msnslp_block, username, 64); + strcat(msnslp_block, " MSNSLP/1.0\r\nTo: <msnmsgr:"); + strncat(msnslp_block, username, 64); + strcat(msnslp_block, ">\r\nFrom: <msnmsgr:"); + strncat(msnslp_block, options_username(), 64); + strcat(msnslp_block, ">\r\nVia: MSNSLP/1.0/TLP ;branch={33517CE4-02FC-4428-B6F4-39927229B722}\r\nCSeq: 0 \r\nCall-ID: "); + strncat(msnslp_block, mpsession->call_id, 38); + strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionreqbody\r\nContent-Length: "); + strncat(msnslp_block, request_details_length_string, 30); + free(request_details_length_string); + strcat(msnslp_block, "\r\n\r\n"); + strcat(msnslp_block, request_details); + free(request_details); + msnslp_length = strlen(msnslp_block); + + debug_print("MP: Sending MSNSLP INVITE for display picture.\n"); + + /* Now wrap it up with the MSNP2P rubbish */ + mpheader.session_id = 0; + mpheader.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); + msnp2p_inc(mpsession); + mpheader.offset = 0; + mpheader.total_size = GINT64_TO_LE(msnslp_length+1); + mpheader.size = GINT64_TO_LE(msnslp_length+1); + mpheader.flags = 0; + mpheader.ack_sess = GINT32_TO_LE(mpsession->session_id); + mpheader.ack_id = 0; + mpheader.ack_size = 0; + + msnp2p_send(session, mpheader, username, msnslp_block, strlen(msnslp_block)+1, 0); + + free(msnslp_block); + + mpsession->username = strdup(username); + msnp2p_list = g_list_append(msnp2p_list, mpsession); + +} + +/* Bin all the MSNP2P/MSNSLP sessions in a given switchboard session. */ +void msnp2p_abortall(SbSession *session) { + + GList *result; + + result = g_list_find_custom(msnp2p_list, &session, (GCompareFunc)msnp2p_compare_parent); + + while ( result != NULL ) { + + MpSession *mpsession; + mpsession = result->data; + msnp2p_closesession(mpsession); + + result = g_list_find_custom(msnp2p_list, &session, (GCompareFunc)msnp2p_compare_parent); + + } + +} + +void msnp2p_offerfile(SbSession *session, const char *username, const char *filename) { + + MpSession *mpsession; + MpHeader mpheader; + char *context; + char *request_details; + size_t request_details_length; + char *request_details_length_string; + char *msnslp_block; + size_t msnslp_length; + + assert(session != NULL); + assert(username != NULL); + assert(filename != NULL); + + mpsession = msnp2p_new(); + if ( mpsession == NULL ) { + debug_print("MP: Not enough memory to send file - giving up.\n"); + return; + } + + mpsession->parent = session; + mpsession->baseidentifier = msnp2p_cookie(); + mpsession->session_id = msnp2p_cookie(); + mpsession->direction = MSNP2P_DIR_SEND; + mpsession->id_increment = 0; + mpsession->call_id = routines_guid(); + mpsession->via = NULL; + + context = strdup("WIBBLE!!!112"); /* File preview data... */ + + debug_print("MP: Generated: Session ID '%i', BaseIdentifier '%i', Call-ID '%s'.\n", mpsession->session_id, mpsession->baseidentifier, mpsession->call_id); + + /* Create the payload for the MSNSLP request */ + request_details = malloc( 96 + strlen(context) + 9 ); + if ( request_details == NULL ) { + + debug_print("MP: Not enough memory to do picture request - giving up.\n"); + free(mpsession->save_filename); + free(mpsession->call_id); + free(mpsession); + return; + + } + strcpy(request_details, "EUF-GUID: {5D3E02AB-6190-11D3-BBBB-00C04F795683}\r\nSessionID: "); + /* Check we're not about to screw ourselves over. 8 bytes were allowed + above to fit the sessionid into. */ + if ( mpsession->session_id > 99999999 ) { + + debug_print("MP: SessionID is too big - giving up.\n"); + free(request_details); + free(mpsession->call_id); + free(mpsession); + return; + + } + sprintf(request_details+strlen(request_details), "%i", mpsession->session_id); + strcat(request_details, "\r\nAppID: 1\r\nContext: "); + strcat(request_details, context); + free(context); + strcat(request_details, "\r\n\r\n"); + request_details_length = strlen(request_details); + + request_details_length_string = malloc(5); + assert(request_details_length < 9998); /* Sanity check. */ + sprintf(request_details_length_string, "%i", request_details_length+1); + + /* Now create the MSNSLP request itself */ + msnslp_block = malloc( 291 + (2*strlen(username)) + strlen(options_username()) + strlen(request_details) + strlen(request_details_length_string) ); + strcpy(msnslp_block, "INVITE MSNMSGR:"); + strncat(msnslp_block, username, 64); + strcat(msnslp_block, " MSNSLP/1.0\r\nTo: <msnmsgr:"); + strncat(msnslp_block, username, 64); + strcat(msnslp_block, ">\r\nFrom: <msnmsgr:"); + strncat(msnslp_block, options_username(), 64); + strcat(msnslp_block, ">\r\nVia: MSNSLP/1.0/TLP ;branch={33517CE4-02FC-4428-B6F4-39927229B722}\r\nCSeq: 0 \r\nCall-ID: "); + strncat(msnslp_block, mpsession->call_id, 38); + strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionreqbody\r\nContent-Length: "); + strncat(msnslp_block, request_details_length_string, 30); + free(request_details_length_string); + strcat(msnslp_block, "\r\n\r\n"); + strcat(msnslp_block, request_details); + free(request_details); + msnslp_length = strlen(msnslp_block); + + debug_print("MP: Sending MSNSLP INVITE for file transfer...\n"); + + /* Now wrap it up with the MSNP2P rubbish */ + mpheader.session_id = 0; + mpheader.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); + msnp2p_inc(mpsession); + mpheader.offset = 0; + mpheader.total_size = GINT64_TO_LE(msnslp_length+1); + mpheader.size = GINT64_TO_LE(msnslp_length+1); + mpheader.flags = 0; + mpheader.ack_sess = GINT32_TO_LE(mpsession->session_id); + mpheader.ack_id = 0; + mpheader.ack_size = 0; + + msnp2p_send(session, mpheader, username, msnslp_block, strlen(msnslp_block)+1, 0); + + free(msnslp_block); + + mpsession->username = strdup(username); + msnp2p_list = g_list_append(msnp2p_list, mpsession); + +} diff --git a/src/msnp2p.h b/src/msnp2p.h new file mode 100644 index 0000000..25ae61d --- /dev/null +++ b/src/msnp2p.h @@ -0,0 +1,36 @@ +/* + * msnp2p.h + * + * MSNP2P handling + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef MSNP2P_H +#define MSNP2P_H + +#include "sbsessions.h" + +extern void msnp2p_parsemsg(SbSession *session, const char *username, const char *msg, size_t len); +extern void msnp2p_getpicture(SbSession *session, const char *username, const char *dpobject); +extern void msnp2p_abortall(SbSession *session); +extern int msnp2p_retrieving(const char *dpsha1d); +extern void msnp2p_offerfile(SbSession *session, const char *username, const char *filename); + +#endif /* MSNP2P_H */ diff --git a/src/msnprotocol.c b/src/msnprotocol.c new file mode 100644 index 0000000..fe7635e --- /dev/null +++ b/src/msnprotocol.c @@ -0,0 +1,1692 @@ +/* + * msnprotocol.c + * + * Low-level DS/NS protocol handling + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <unistd.h> +#include <fcntl.h> +#include <netdb.h> +#include <errno.h> +#include <gdk/gdk.h> +#include <assert.h> +#include <string.h> +#include <openssl/md5.h> + +#include "mainwindow.h" +#include "error.h" +#include "options.h" +#include "debug.h" +#include "routines.h" +#include "msnprotocol.h" +#include "listcache.h" +#include "twnauth.h" +#include "contactlist.h" +#include "msngenerics.h" +#include "listcache.h" +#include "sbsessions.h" +#include "sbprotocol.h" +#include "avatars.h" +#include "msnp11chl.h" +#include "xml.h" +#include "addcontact.h" + +typedef enum { + CSTATE_DISCONNECTED, /* Offline, disconnected, mainwindow in dispatch mode */ + CSTATE_SIGNIN, /* Connected and in the process of signing in */ + CSTATE_LIST, /* Receiving list data */ + CSTATE_CONNECTED /* Fully logged in */ +} CState; + +/* Part of something messy to avoid duplicating code */ +typedef enum { + NLN_NLN, + NLN_ILN +} nln_t; + +typedef enum { + NSCONMODE_LINE, + NSCONMODE_MSG, + NSCONMODE_UBX, + NSCONMODE_NOT, + NSCONMODE_GCF_SHIELDS +} NSConMode; + +static struct { + + int connect_lock; /* Prevent a second connection attempt from starting */ + int socket; /* The DS or NS connection socket (only one exists at a time */ + gint wcallback; /* GDK "writeable" callback for the socket */ + gint rcallback; /* GDK "readable" callback for the socket */ + unsigned int trid; /* TrID to be used for the next transaction sent */ + CState connected; /* Indicator of progress into the login procedure */ + int disconnect_expected; /* Suppress "Read error" message if the disconnection was anticipated */ + gint disconnect_callback; /* Callback tag for forcing cleanup if disconnection fails. */ + OnlineState status; /* Current online status */ + int lst_num; /* Number of LSTs so far received */ + int lst_num_max; /* Expected number of LSTs (from SYN) */ + gint ping_callback; /* Callback tag for sending pings */ + int protocol_version; /* MSNP version in use */ + NSConMode conmode; /* What's being read at the moment? */ + size_t expect_length; /* Amount of MSG/UBX etc expected. */ + char *msg_source; /* Source of MSG/UBX etc */ + + /* Read/write buffers */ + char *wbuffer; /* Buffer for outgoing data */ + unsigned int wbufsize; /* Current size of the write buffer */ + unsigned int woffset; /* Current offset into the write buffer */ + char *rbuffer; /* Buffer for incoming data */ + unsigned int rbufsize; /* Current size of the read buffer */ + unsigned int roffset; /* Current offset into the read buffer */ + +} cstate = { + + 0, + -1, + 0, + 0, + 1, + CSTATE_DISCONNECTED, + 0, + 0, + ONLINE_FLN, + 0, + 0, + 0, + 12, + NSCONMODE_LINE, + 0, + NULL, + + NULL, + 0, + 0, + NULL, + 0, + 0 + +}; + +/* Free buffers, remove callbacks etc. */ +static void msnprotocol_cleanup_internal() { + + close(cstate.socket); + + if ( cstate.rbuffer != NULL ) { + free(cstate.rbuffer); + } + cstate.rbufsize = 0; + cstate.rbuffer = NULL; + cstate.roffset = 0; + if ( cstate.wbuffer != NULL ) { + free(cstate.wbuffer); + } + cstate.wbufsize = 0; + cstate.wbuffer = NULL; + cstate.woffset = 0; + + if ( cstate.rcallback != 0 ) { + gdk_input_remove(cstate.rcallback); + } + cstate.rcallback = 0; + if ( cstate.wcallback != 0 ) { + gdk_input_remove(cstate.wcallback); + } + cstate.wcallback = 0; + + if ( cstate.disconnect_callback != 0 ) { + gtk_timeout_remove(cstate.disconnect_callback); + cstate.disconnect_callback = 0; + } + + if ( cstate.ping_callback != 0 ) { + gtk_timeout_remove(cstate.ping_callback); + cstate.ping_callback = 0; + } + + cstate.connect_lock = 0; + cstate.trid = 1; + cstate.disconnect_expected = 0; + cstate.connected = CSTATE_DISCONNECTED; + + sbsessions_destroy_all(); + messagewindow_disable_all(); + contactlist_clear(); + +} + +/* Free buffers, remove callbacks, return main window to dispatch mode, etc. */ +static void msnprotocol_cleanup() { + + msnprotocol_cleanup_internal(); + mainwindow_setdispatch(); + +} + +/* If a disconnection hasn't happened ten seconds after requesting it, pull the plug. */ +static gint msnprotocol_forcedisconnect() { + + int closeval; + + debug_print("NS: Waited too long for disconnection... pulling the plug.\n"); + + cstate.disconnect_callback = 0; + + closeval = close(cstate.socket); + if ( closeval != 0 ) { + /* Grrr.. now it won't close - not much we can do. */ + debug_print("NS: Couldn't close socket after write error.\n"); + } + + msnprotocol_cleanup(); + + return FALSE; + +} + +/* Write a chunk of the outgoing data queue to the socket. */ +static void msnprotocol_writeable() { + + ssize_t wlen; + int new_wbufsize; + char *debug_string; + unsigned int i; + + wlen = write(cstate.socket, cstate.wbuffer, cstate.wbufsize); + + debug_string = malloc(cstate.wbufsize+1); + memcpy(debug_string, cstate.wbuffer, cstate.wbufsize); + debug_string[cstate.wbufsize] = '\0'; + for ( i=0; i<cstate.wbufsize; i++ ) { + if ( debug_string[i] == '\r' ) { + debug_string[i] = 'r'; + } + if ( debug_string[i] == '\n' ) { + debug_string[i] = 'n'; + } + } + debug_print("NS: send: '%s'\n", debug_string); + free(debug_string); + + if ( wlen > 0 ) { + + /* wlen holds the number of bytes written. Sort the buffer out accordingly... */ + memmove(cstate.wbuffer, cstate.wbuffer + wlen, cstate.wbufsize - wlen); + new_wbufsize = cstate.wbufsize - wlen; + assert(new_wbufsize >= 0); + + cstate.wbuffer = realloc(cstate.wbuffer, new_wbufsize); + if ( new_wbufsize == 0 ) { + + gdk_input_remove(cstate.wcallback); + cstate.wcallback = 0; + cstate.wbuffer = NULL; + + } + cstate.wbufsize = new_wbufsize; + cstate.woffset -= wlen; + + } else { + + if ( wlen == -1 ) { + + /* Write error! :( */ + if ( errno != EAGAIN ) { /* EAGAIN should never happen here */ + + /* Something bad happened */ + int closeval; + closeval = close(cstate.socket); + if ( closeval != 0 ) { + /* Grrr.. now it won't close - not much we can do. */ + debug_print("NS: Couldn't close socket after write error.\n"); + } + error_report("Write error! Signed out."); + msnprotocol_cleanup(); + return; + + } + + } + + } + +} + +/* Queue a command for sending to the server, adding a TrID - returns the TrID used for this transaction. */ +static int msnprotocol_sendtr_internal(char *instr, char *args, int newline, int send_trid) { + + char *trid_string = NULL; + unsigned int len; + + len = strlen(instr); + if ( send_trid ) { + + trid_string = malloc(8); + assert(cstate.trid < 999999); /* Sanity check */ + sprintf(trid_string, "%i", cstate.trid); + len += 1; /* Space before the TrId */ + len += strlen(trid_string); + + } + if ( strlen(args) > 0 ) { + len += strlen(args); + len += 1; /* Space after the TrID */ + } + if ( newline ) { + len += 2; + } + if ( cstate.wbufsize == 0 ) { + + /* No buffer space currently exists. Create it. */ + assert(cstate.wbuffer == NULL); + cstate.wbuffer = malloc(len); + assert(cstate.wbuffer != NULL); + cstate.wbufsize = len; + cstate.woffset = 0; + + } + if ( (cstate.wbufsize - cstate.woffset) < len ) { + + /* Write buffer isn't big enough. Make it bigger. */ + cstate.wbuffer = realloc(cstate.wbuffer, len + cstate.woffset); + assert(cstate.wbuffer != NULL); + cstate.wbufsize = len + cstate.woffset; + assert(cstate.wbufsize < 1024*1024); /* Stop the buffer from getting insane */ + + } + + /* Do the write (to memory). Deliberately verbose... */ + memcpy(cstate.wbuffer + cstate.woffset, instr, strlen(instr)); + cstate.woffset += strlen(instr); + if ( send_trid ) { + + *(cstate.wbuffer + cstate.woffset) = ' '; + cstate.woffset += 1; + + memcpy(cstate.wbuffer + cstate.woffset, trid_string, strlen(trid_string)); + cstate.woffset += strlen(trid_string); + free(trid_string); + + } + if ( strlen(args) > 0 ) { + + *(cstate.wbuffer + cstate.woffset) = ' '; + cstate.woffset += 1; + memcpy(cstate.wbuffer + cstate.woffset, args, strlen(args)); + cstate.woffset += strlen(args); + + } + if ( newline ) { + + *(cstate.wbuffer + cstate.woffset) = '\r'; + cstate.woffset += 1; + *(cstate.wbuffer + cstate.woffset) = '\n'; + cstate.woffset += 1; + + } + /* Note the lack of a \0 terminator. It's done using records of the buffer data lengths. */ + + if ( cstate.wcallback == 0 ) { + cstate.wcallback = gdk_input_add(cstate.socket, GDK_INPUT_WRITE, (GdkInputFunction) msnprotocol_writeable, NULL); + } + + cstate.trid++; + return cstate.trid-1; + +} + +/* Send a command to the server, adding a TrID and a newline */ +static int msnprotocol_sendtr(char *instr, char *args) { + + return msnprotocol_sendtr_internal(instr, args, 1, 1); + +} + +static gint msnprotocol_sendping() { + + msnprotocol_sendtr_internal("PNG", "", 1, 0); + cstate.ping_callback = 0; + + return FALSE; + +} + +/* Send a command to the server, adding a TrID but no newline */ +static int msnprotocol_sendtr_nonewline(char *instr, char *args) { + + return msnprotocol_sendtr_internal(instr, args, 0, 1); + +} + +static void msnprotocol_handle_lst(char *line) { + + char *username = NULL; + char *friendlyname = NULL; + char *guid = NULL; + char *list = NULL; + unsigned int list_num; + int finished = 0; + int field_num = 1; + int list_coming = 0; + + /* Step through the LST data and look for friendlyname, username and list number */ + + while ( !finished ) { + + char *field; + int field_used = 0; + field = routines_lindex(line, field_num); + field_num++; + if ( strlen(field) == 0 ) { + finished = 1; + } else { + + if ( strlen(field) > 2 ) { + + if ( (field[0] == 'F') && (field[1] == '=') ) { + friendlyname = strdup(field+2); + list_coming = 1; + field_used = 1; + } else if ( (field[0] == 'N') && (field[1] == '=') ) { + username = strdup(field+2); + list_coming = 1; + field_used = 1; + } else if ( (field[0] == 'C') && (field[1] == '=') ) { + guid = strdup(field+2); + list_coming = 1; + field_used = 1; + } + + } + /* "list" is the first field without "X=" */ + if ( list_coming && !field_used ) { + list = strdup(field); + finished = 1; /* Otherwise the next field will get used instead. */ + } + + } + free(field); + + } + + /* Need at least a username and list to continue */ + if ( (username == NULL) || (list == NULL) ) { + + debug_print("NS: Didn't get enough information from LST: missing"); + if ( username == NULL ) { + debug_print(" username"); + } else { + free(username); + } + if ( list == NULL ) { + debug_print(" list"); + } else { + free(list); + } + if ( friendlyname != NULL ) { + free(friendlyname); /* Don't leak memory */ + } + debug_print("\n"); + + } else { + + list_num = atoi(list); + free(list); + if ( list_num > 31 ) { + debug_print("NS: Warning: contact is on unrecognised list(s).\n"); + } + + if ( list_num & 1 ) { + contactlist_fldetails(CONTACT_SOURCE_LST, username, friendlyname, 0, NULL, ONLINE_FLN, guid); + } + if ( list_num & 2 ) { + contactlist_aldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN); + } + if ( list_num & 4 ) { + contactlist_bldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN); + } + if ( list_num & 8 ) { + contactlist_rldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN); + } + if ( list_num & 16 ) { + contactlist_pldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN); + } + if ( list_num == 8 ) { + /* If contact is ONLY on the RL, add them to the PL as well. This should never + happen with MSNP11+, but ensures that caching takes place properly. */ + contactlist_pldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN); + } + if ( (list_num == 8) || (list_num & 16) ) { + + /* On RL but not FL, AL or BL indicates new user. + On PL also indicates new user. */ + addcontact_added(username, friendlyname); + + } + + } + +} + +/* Despite the name, handles ILN and NLN */ +static void msnprotocol_handle_nln(nln_t nln, char *line) { + + OnlineState new_status; + char *username; + char *friendlyname; + char *features_string; + int features; + char *dpobject; + char *status_string; + int lindex_sub; + + /* Subtract one from all the list indices if this isn't ILN, since there's no TrID */ + if ( nln == NLN_ILN ) { + lindex_sub = 0; + } else { + lindex_sub = 1; + } + + status_string = routines_lindex(line, 2-lindex_sub); + new_status = msngenerics_decodestatus(status_string); + assert(new_status != ONLINE_FLN); /* This would be handled elsewhere. */ + assert(new_status != ONLINE_HDN); /* Nonsense */ + free(status_string); + + username = routines_lindex(line, 3-lindex_sub); + friendlyname = routines_lindex(line, 4-lindex_sub); + features_string = routines_lindex(line, 5-lindex_sub); + features = atoi(features_string); + free(features_string); + dpobject = routines_lindex(line, 6-lindex_sub); + + /* Pass NULL if there's no "dpobject" */ + if ( strlen(dpobject) == 0 ) { + free(dpobject); + dpobject = NULL; + } + if ( (dpobject != NULL) && (strcmp(dpobject, "0") == 0) ) { + free(dpobject); + dpobject = NULL; + } + + assert(strlen(username) > 0); + assert(strlen(friendlyname) > 0); + + /* contactlist_fldetails won't overwrite the GUID with NULL. */ + contactlist_fldetails(CONTACT_SOURCE_NLN, username, friendlyname, features, dpobject, new_status, NULL); + + /* contactlist_fldetails copies anything it's interested in */ + free(username); + free(friendlyname); + if ( dpobject != NULL ) { + free(dpobject); + } + +} + +/* Enter main "Connected" mode. */ +static void msnprotocol_doneconnect() { + + cstate.connected = CSTATE_CONNECTED; + mainwindow_forcestatus(ONLINE_HDN); + + /* Start pinging. */ + if ( cstate.ping_callback == 0 ) { + cstate.ping_callback = gtk_timeout_add(50000, (GtkFunction)msnprotocol_sendping, NULL); + } + + if ( cstate.protocol_version >= 11 ) { + msnprotocol_sendtr("GCF", "Shields.xml"); + } + +} + +/* Internally called to parse a line of text from the DS or NS */ +static int msnprotocol_parseline(char *line) { + + char *instr; + char *err_str; + int err_num; + char *trid_string; + int trid; + + debug_print("NS: recv: '%s'\n", line); + + /* Prevent blank or short lines from being a problem */ + if ( strlen(line) < 4 ) { + return 0; + } + + instr = malloc(4); + strncpy(instr, line, 3); + instr[3] = '\0'; + + if ( line[3] != ' ' ) { + line[0] = ' '; + line[1] = ' '; + line[2] = ' '; /* Prevent bogus interpretations of things that aren't three letters */ + } + + err_num = atoi(instr); + err_str = malloc(4); + assert(err_num < 1000); + sprintf(err_str, "%i", err_num); + if ( strcmp(err_str, instr) == 0 ) { + error_report_server(err_num); + if ( cstate.connected == CSTATE_SIGNIN ) { + cstate.disconnect_expected = 1; + } + if ( err_num == 540 ) { + cstate.disconnect_expected = 1; + } + } + free(err_str); + + trid_string = routines_lindex(line, 1); + trid = atoi(trid_string); + free(trid_string); + + if ( strcmp("OUT", instr) == 0 ) { + char *sit = routines_lindex(line, 1); + if ( strcmp(sit, "OTH") == 0 ) { + cstate.disconnect_expected = 1; + error_report("Signed in somewhere else."); + } + free(sit); + } + + if ( strcmp("VER", instr) == 0 ) { + + char *string; + char *protocol; + int got_cvr = FALSE; + int highest_msnp = 0; + int pos = 1; + + protocol = routines_lindex(line, pos++); + while ( strlen(protocol) ) { + if ( strncmp(protocol, "MSNP", 4) == 0 ) { + int vnum = atoi(protocol + 4); + if ( vnum > highest_msnp ) { + highest_msnp = vnum; + } + } + if ( strcmp(protocol, "CVR0") == 0 ) { + got_cvr = TRUE; + debug_print("Got CVR0.\n"); + } + free(protocol); + protocol = routines_lindex(line, pos++); + } + debug_print("Negotiated protocol version %i\n", highest_msnp); + if ( highest_msnp < 11 ) { + debug_print("Too low. Bad luck.\n"); + error_report("Couldn't negotiate protocol version.\n"); + msnprotocol_cleanup_internal(); + return -1; + } + cstate.protocol_version = highest_msnp; + + string = malloc(128); + strcpy(string, "0x0409 winnt 5.1 i386 MSNMSGR 7.5.0311 msmsgs "); + strncat(string, options_username(), 64); + string[127] = '\0'; + msnprotocol_sendtr("CVR", string); + free(string); + + } + + if ( strcmp("CVR", instr) == 0 ) { + + char *string; + string = malloc(128); + strcpy(string, "TWN I "); + strncat(string, options_username(), 64); + string[127] = '\0'; + msnprotocol_sendtr("USR", string); + free(string); + + } + + if ( strcmp("XFR", instr) == 0 ) { + + char *mbuffer_type; + mbuffer_type = routines_lindex(line, 2); + + if ( strcmp("NS", mbuffer_type) == 0 ) { + + /* got transferred to a different NS */ + unsigned int new_port; + char *new_hostname; + char *target; + + target = routines_lindex(line, 3); + new_hostname = routines_hostname(target); + new_port = routines_port(target); + debug_print("NS: Redirected to '%s' port '%i'\n", new_hostname, new_port); + if ( new_port == 0 ) { + new_port = DEFAULT_NS_PORT; + debug_print("NS: Corrected to %i\n", new_port); + } + + msnprotocol_cleanup_internal(); + msnprotocol_connect(new_hostname, new_port); + + /* ...and now for some "tedious mucking-about in hyperspace"... */ + free(target); + free(new_hostname); + free(mbuffer_type); + free(instr); + return -1; + + } else if ( strcmp("SB", mbuffer_type) == 0 ) { + + /* SB transfer - part of an SbSession negotiation. */ + unsigned int port; + char *hostname; + char *target; + char *cki_key; + char *trid_char; + char *authtype; + + target = routines_lindex(line, 3); + hostname = routines_hostname(target); + port = routines_port(target); + debug_print("NS: SB session at '%s' port '%i'\n", hostname, port); + if ( port == 0 ) { + port = DEFAULT_SB_PORT; + debug_print("NS: Corrected to %i\n", port); + } + + authtype = routines_lindex(line, 4); + if ( strcmp(authtype, "CKI") != 0 ) { + + /* How depressing */ + debug_print("NS: Incompatible SB authentication method (%s)\n", authtype); + free(authtype); + free(instr); + free(mbuffer_type); + return 0; + + } + free(authtype); + + cki_key = routines_lindex(line, 5); + trid_char = routines_lindex(line, 1); + sbprotocol_initiate_local(atoi(trid_char), hostname, port, cki_key); + + free(trid_char); + free(cki_key); + free(hostname); + free(target); + + } + + free(mbuffer_type); + + } + + if ( strcmp("USR", instr) == 0 ) { + + if ( line[10] == 'S' ) { + + char *auth_string; + char *auth_data; + char *auth_ticket; + + auth_string = malloc(1024); + strcpy(auth_string, "TWN S "); + auth_data = routines_lindex(line, 4); + auth_ticket = twnauth_ticket(auth_data); + if ( auth_ticket != NULL ) { + strncat(auth_string, auth_ticket, 1000); + auth_string[1023] = '\0'; + free(auth_ticket); + msnprotocol_sendtr("USR", auth_string); + } else { + /* Deal with TWN auth failure */ + msnprotocol_cleanup(); + error_report("TWN authentication failed."); + cstate.disconnect_expected = 1; + free(instr); + return -1; + } + free(auth_data); + free(auth_string); + + } else { + + if ( ( line[6] == 'O' ) && ( line[7] == 'K' ) ) { + + /* Success! */ + + char *syn_max; + + cstate.connected = CSTATE_LIST; + + syn_max = listcache_loadtag(); + msnprotocol_sendtr("SYN", syn_max); + + } + + } + + } + + if ( strcmp("LST", instr) == 0 ) { + + msnprotocol_handle_lst(line); + cstate.lst_num++; + if ( cstate.lst_num == cstate.lst_num_max ) { + msnprotocol_doneconnect(); + } + + } + + if ( strcmp("REM", instr) == 0 ) { + + char *list = routines_lindex(line, 2); + char *usernameguid = routines_lindex(line, 3); /* Could be either! */ + + if ( strcmp(list, "FL") == 0 ) { + contactlist_removecontactguid(list, usernameguid); + } else { + contactlist_removecontact(list, usernameguid); + } + free(list); + free(usernameguid); + + } + + if ( strcmp("ADC", instr) == 0 ) { + + char *list; + unsigned int field_num = 2; + int finished = 0; + char *friendlyname = NULL; + char *username = NULL; + char *guid = NULL; + + while ( !finished ) { + + char *field; + + field = routines_lindex(line, field_num); + field_num++; + + if ( strlen(field) == 0 ) { + finished = 1; + } else { + + if ( strlen(field) > 2 ) { + + if ( (field[0] == 'F') && (field[1] == '=') ) { + friendlyname = strdup(field+2); + } else if ( (field[0] == 'N') && (field[1] == '=') ) { + username = strdup(field+2); + } else if ( (field[0] == 'C') && (field[1] == '=') ) { + guid = strdup(field+2); + } + + } + + } + free(field); + + } + + list = routines_lindex(line, 2); + if ( guid != NULL ) { + if ( strlen(guid) == 0 ) { + free(guid); + guid = NULL; + } + } + + /* This is a new user if trid=0, otherwise it's a reponse to moving them PL->RL. */ + if ( (strcmp(list, "RL") == 0) && (username != NULL) ) { + /* This actually means "PL", unless the contact is already on the AL or the BL. + This is logical. Promise. */ + if ( (trid == 0) && (contactlist_isonlist("AL", username) || contactlist_isonlist("BL", username)) ) { + contactlist_pldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN); + addcontact_added(username, friendlyname); + } else { + contactlist_rldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN); + } + } + + /* Update contact list as appropriate. */ + if ( strcmp(list, "FL") == 0 ) { + contactlist_fldetails(CONTACT_SOURCE_ADC, username, friendlyname, 0, NULL, ONLINE_FLN, guid); + } + if ( strcmp(list, "AL") == 0 ) { + contactlist_aldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN); + } + if ( strcmp(list, "BL") == 0 ) { + contactlist_bldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN); + } + + free(list); + if ( username != NULL ) { + free(username); + } + if ( friendlyname != NULL ) { + free(friendlyname); + } + if ( guid != NULL ) { + free(guid); + } + + } + + if ( strcmp("NLN", instr) == 0 ) { + msnprotocol_handle_nln(NLN_NLN, line); + } + + if ( strcmp("ILN", instr) == 0 ) { + msnprotocol_handle_nln(NLN_ILN, line); + } + + if ( strcmp("FLN", instr) == 0 ) { + + char *username = routines_lindex(line, 1); + /* NULL values won't overwrite anything since CONTACT_SOURCE_FLN has extremely low priority */ + contactlist_fldetails(CONTACT_SOURCE_FLN, username, NULL, 0, NULL, ONLINE_FLN, NULL); + messagewindow_notifyoffline(username); + free(username); + + } + + if ( strcmp("CHL", instr) == 0 ) { + + char *response; + char *challenge; + challenge = routines_lindex(line, 2); + + if ( cstate.protocol_version < 11 ) { + + unsigned char *md5; + unsigned int i; + + response = malloc(1024); + strncpy(response, challenge, 64); + response[1023] = '\0'; + free(challenge); + /* No reverse engineering of any other software took place to provide the CHL key. + * This information is freely available on the web */ + strcat(response, "Q1P7W2E4J9R8U3S5"); + md5 = MD5(response, strlen(response), NULL); + sprintf(response, "msmsgs@msnmsgr.com 32\r\n%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", + md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7], + md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14], + md5[15]); + for ( i=23; i<strlen(response); i++ ) { + if ( response[i]==' ' ) { + response[i]='0'; + } + } + + } else { + + char *hex; + hex = msnp11chl_response(challenge); + response = malloc(strlen(hex)+strlen(CLIENT_ID)+7); + sprintf(response, CLIENT_ID" 32\n%s", hex); + free(hex); + + } + + free(challenge); + msnprotocol_sendtr_nonewline("QRY", response); + free(response); + + } + + if ( strcmp("PRP", instr) == 0 ) { + + char *prp_field; + int check; + + /* Format #1: from SYN */ + prp_field = routines_lindex(line, 1); + if ( strcmp(prp_field, "MFN") == 0 ) { + + char *new_friendlyname; + new_friendlyname = routines_lindex(line, 2); + mainwindow_setmfn(new_friendlyname); + listcache_setmfn(new_friendlyname); + free(new_friendlyname); + + } + + check = atoi(prp_field); + if ( check > 0 ) { + + char *prp_field2; + + /* Format #2: from PRP - only attempt if TrID is present. */ + prp_field2 = routines_lindex(line, 2); + if ( strcmp(prp_field2, "MFN") == 0 ) { + + char *new_friendlyname; + new_friendlyname = routines_lindex(line, 3); + mainwindow_setmfn(new_friendlyname); + listcache_setmfn(new_friendlyname); + free(new_friendlyname); + + } + free(prp_field2); + + } + free(prp_field); + + + + } + + if ( strcmp("SYN", instr) == 0 ) { + + char *newest_tag_1; + char *newest_tag_2; + char *full_tag; + char *num_contacts; + + newest_tag_1 = routines_lindex(line, 2); + newest_tag_2 = routines_lindex(line, 3); + num_contacts = routines_lindex(line, 4); + /* num_groups = routines_lindex(line, 5); */ + + assert(num_contacts != NULL); + cstate.lst_num_max = atoi(num_contacts); + free(num_contacts); + if ( cstate.lst_num_max == 0 ) { + /* This indicates an empty list OR an up-to-date list */ + listcache_load(); + msnprotocol_doneconnect(); + } else { + cstate.connected = CSTATE_LIST; + } + cstate.lst_num = 0; + + assert(newest_tag_1 != NULL); + assert(newest_tag_2 != NULL); + + full_tag = malloc(strlen(newest_tag_1) + strlen(newest_tag_2) + 2); + assert(full_tag != NULL); + + strcpy(full_tag, newest_tag_1); + strcat(full_tag, " "); + strcat(full_tag, newest_tag_2); + + listcache_settag(full_tag); + + free(full_tag); + free(newest_tag_1); + free(newest_tag_2); + + } + + if ( strcmp("QNG", instr) == 0 ) { + + /* The time interval given by the QNG is ignored to keep things under program control. + Let me know if this causes any problems for you... */ + if ( cstate.ping_callback == 0 ) { + cstate.ping_callback = gtk_timeout_add(50000, (GtkFunction)msnprotocol_sendping, NULL); + } + + } + + if ( strcmp("RNG", instr) == 0 ) { + + /* Yay! */ + char *sessionid; + char *callinguser; + char *switchboardaddress; + char *authchallenge; + char *authtype; + + sessionid = routines_lindex(line, 1); + callinguser = routines_lindex(line, 5); + switchboardaddress = routines_lindex(line, 2); + authchallenge = routines_lindex(line, 4); + + debug_print("NS: Received SB invitation from %s (ID=%s, Key=%s, Addr=%s)\n", callinguser, sessionid, authchallenge, switchboardaddress); + + /* Check if the friendly name is available for this user. If not, TL them. + This could actually happen if the user is on another list but has no friendlyname. + This situation doesn't matter. */ + if ( contactlist_friendlyname(callinguser) == NULL ) { + + char *friendlyname = routines_lindex(line, 6); + debug_print("NS: Creating TL record: '%s'/'%s'\n", callinguser, friendlyname); + contactlist_tldetails(CONTACT_SOURCE_RNG, callinguser, friendlyname, ONLINE_NLN); + free(friendlyname); + + } + + authtype = routines_lindex(line, 3); + if ( strcmp(authtype, "CKI") != 0 ) { + + /* How depressing */ + debug_print("NS: Incompatible SB authentication method (%s)\n", authtype); + free(authtype); + free(instr); + return 0; + + } + free(authtype); + sbsessions_create_remote(callinguser, switchboardaddress, sessionid, authchallenge); + free(callinguser); + free(switchboardaddress); + free(authchallenge); + free(sessionid); + + } + + if ( strcmp("MSG", instr) == 0 ) { + + char *length; + + length = routines_lindex(line, 3); + cstate.expect_length = atoi(length); + free(length); + if ( cstate.expect_length > 0 ) { + cstate.msg_source = routines_lindex(line, 1); + cstate.conmode = NSCONMODE_MSG; + } else { + debug_print("NS: Zero-sized MSG??\n"); + } + + } + + if ( strcmp("UBX", instr) == 0 ) { + + char *length; + + length = routines_lindex(line, 2); + cstate.expect_length = atoi(length); + free(length); + if ( cstate.expect_length > 0 ) { + cstate.msg_source = routines_lindex(line, 1); + cstate.conmode = NSCONMODE_UBX; + } else { + debug_print("NS: Zero-sized UBX??\n"); /* This seems to happen sometimes. */ + } + + } + + if ( strcmp("NOT", instr) == 0 ) { + + char *length; + + length = routines_lindex(line, 1); + cstate.expect_length = atoi(length); + free(length); + if ( cstate.expect_length > 0 ) { + cstate.conmode = NSCONMODE_NOT; + } else { + debug_print("NS: Zero-sized NOT??\n"); + } + + } + + if ( strcmp("GCF", instr) == 0 ) { + + char *length; + char *file; + + length = routines_lindex(line, 3); + file = routines_lindex(line, 2); + cstate.expect_length = atoi(length); + free(length); + if ( cstate.expect_length > 0 ) { + if ( strcmp(file, "Shields.xml") == 0 ) { + cstate.conmode = NSCONMODE_GCF_SHIELDS; + } else { + debug_print("NS: Unrecognised GCF!\n"); + } + } else { + debug_print("NS: Zero-sized GCF??\n"); + } + free(file); + + } + + if ( strcmp("CHG", instr) == 0 ) { + + OnlineState old_state = cstate.status; + + char *state = routines_lindex(line, 2); + cstate.status = msngenerics_decodestatus(state); + free(state); + + if ( (old_state == ONLINE_HDN) && (cstate.status != ONLINE_HDN) ) { + messagewindow_enable_all(); + } + + } + + free(instr); + + return 0; + +} + +static void msnprotocol_parsemsg(const char *msg, size_t msglen) { + + debug_print("NS: Got NS MSG from '%s' (%i bytes) '%s'\n", cstate.msg_source, msglen, msg); + free(cstate.msg_source); + +} + +static void msnprotocol_parseubx(const char *msg, size_t msglen) { + + debug_print("NS: Got UBX data for '%s' (%i bytes) '%s'\n", cstate.msg_source, msglen, msg); + contactlist_setubx(cstate.msg_source, msg, msglen); + free(cstate.msg_source); + +} + +static void msnprotocol_parsenotification(const char *msg, size_t msglen) { + + debug_print("NS: Got notification (%i bytes) '%s'\n", msglen, msg); + +} + +static void msnprotocol_parseshields(const char *msg, size_t msglen) { + + debug_print("NS: Got Shields.xml file (%i bytes) '%s'\n", msglen, msg); + +} + +/* Internally called when data arrives from the DS/NS */ +static void msnprotocol_readable() { + + ssize_t rlen; + int no_string = 0; + + assert(cstate.rbufsize - cstate.roffset > 0); + rlen = read(cstate.socket, cstate.rbuffer + cstate.roffset, cstate.rbufsize - cstate.roffset); + if ( rlen >= 0 ) { + cstate.roffset += rlen; + assert(cstate.roffset <= cstate.rbufsize); /* This would indicate a buffer overrun */ + } + + /* First, check this isn't a disconnection */ + if ( (rlen == 0) || (rlen == -1) ) { + + int closeval; + + closeval = close(cstate.socket); + + if ( closeval != 0 ) { + debug_print("NS: Couldn't close socket after read error.\n"); + } + + /* A variety of ways to express this to the user... */ + if ( cstate.connected == CSTATE_CONNECTED ) { + if ( !cstate.disconnect_expected ) { + error_report("Read error! Signed out."); + listcache_save(); + } /* Else ignore. */ + } else if ( (cstate.connected == CSTATE_SIGNIN) || (cstate.connected == CSTATE_LIST) ) { + if ( !cstate.disconnect_expected ) { + error_report("Read error! Sign-in failed."); + } + } else { + error_report("Read error in unrecognised connection state. This never happens :P"); + debug_print("NS: Connection state %i\n", cstate.connected); + } + + msnprotocol_cleanup(); + return; + + } + + while ( (!no_string) && (cstate.roffset > 0) ) { + + int block_ready = 0; + size_t i = 0; + + /* See if there's a full "block" in the buffer yet */ + if ( cstate.conmode == NSCONMODE_LINE ) { + + for ( i=0; i<cstate.roffset-1; i++ ) { /* Means the last value looked at is roffset-2 */ + if ( (cstate.rbuffer[i] == '\r') && (cstate.rbuffer[i+1] == '\n') ) { + block_ready = 1; + break; + } + } + + } else if ( cstate.conmode == NSCONMODE_MSG ) { + + if ( cstate.roffset >= cstate.expect_length ) { + i = cstate.expect_length - 2; + block_ready = 1; + } + + } else if ( cstate.conmode == NSCONMODE_UBX ) { + + if ( cstate.roffset >= cstate.expect_length ) { + i = cstate.expect_length - 2; + block_ready = 1; + } + + } else if ( cstate.conmode == NSCONMODE_NOT ) { + + if ( cstate.roffset >= cstate.expect_length ) { + i = cstate.expect_length - 2; + block_ready = 1; + } + + } else if ( cstate.conmode == NSCONMODE_GCF_SHIELDS ) { + + if ( cstate.roffset >= cstate.expect_length ) { + i = cstate.expect_length - 2; + block_ready = 1; + } + + } else { + /* "Never happens". */ + debug_print("NS: Can't determine end of block for NsConMode %i!\n", cstate.conmode); + } + + if ( block_ready == 1 ) { + + char *block_buffer = NULL; + unsigned int new_rbufsize; + unsigned int endbit_length; + NSConMode next_mode = cstate.conmode; + + if ( cstate.conmode == NSCONMODE_LINE ) { + assert(cstate.rbuffer[i] == '\r'); + assert(cstate.rbuffer[i+1] == '\n'); + } + + + if ( cstate.conmode == NSCONMODE_LINE ) { + + block_buffer = malloc(i+1); + memcpy(block_buffer, cstate.rbuffer, i); + block_buffer[i] = '\0'; + + if ( msnprotocol_parseline(block_buffer) == -1 ) { + /* Eeek. Redirected... */ + free(block_buffer); + return; + } + + /* Check to see if sbprotocol_parseline changed the mode. If so, make sure + it stays changed. */ + if ( cstate.conmode != NSCONMODE_LINE ) { + next_mode = cstate.conmode; + } + + } else if ( cstate.conmode == NSCONMODE_MSG ) { + + block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */ + memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */ + block_buffer[i+2] = '\0'; /* Terminate */ + + msnprotocol_parsemsg(block_buffer, i+2); + next_mode = NSCONMODE_LINE; + + } else if ( cstate.conmode == NSCONMODE_UBX ) { + + block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */ + memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */ + block_buffer[i+2] = '\0'; /* Terminate */ + + msnprotocol_parseubx(block_buffer, i+2); + next_mode = NSCONMODE_LINE; + + } else if ( cstate.conmode == NSCONMODE_NOT ) { + + block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */ + memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */ + block_buffer[i+2] = '\0'; /* Terminate */ + + msnprotocol_parsenotification(block_buffer, i+2); + next_mode = NSCONMODE_LINE; + + } else if ( cstate.conmode == NSCONMODE_GCF_SHIELDS ) { + + block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */ + memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */ + block_buffer[i+2] = '\0'; /* Terminate */ + + msnprotocol_parseshields(block_buffer, i+2); + next_mode = NSCONMODE_LINE; + + } else { + debug_print("NS: No handler for NsConMode %i !\n", cstate.conmode); + } + + free(block_buffer); + + /* Now the block's been parsed, it should be forgotten about */ + if ( cstate.conmode == NSCONMODE_LINE ) { + assert(cstate.rbuffer[i+1] == '\n'); /* Should still be the case. Paranoid. */ + } + endbit_length = i+2; + memmove(cstate.rbuffer, cstate.rbuffer + endbit_length, cstate.rbufsize - endbit_length); + cstate.roffset = cstate.roffset - endbit_length; /* Subtract the number of bytes removed */ + new_rbufsize = cstate.rbufsize - endbit_length; + if ( new_rbufsize == 0 ) { + new_rbufsize = 32; + } + cstate.rbuffer = realloc(cstate.rbuffer, new_rbufsize); + cstate.rbufsize = new_rbufsize; + cstate.conmode = next_mode; + + } else { + + if ( cstate.roffset == cstate.rbufsize ) { + + /* More buffer space is needed */ + cstate.rbuffer = realloc(cstate.rbuffer, cstate.rbufsize + 32); + cstate.rbufsize = cstate.rbufsize + 32; + /* The new space gets used at the next read, shortly... */ + + } + no_string = 1; + + } + + } + +} + +/* Return nonzero if we're signed in and have the lists */ +int msnprotocol_signedin() { + + if ( cstate.connected == CSTATE_CONNECTED ) { + return 1; + } + + return 0; + +} + +/* Return nonzero if we're completely disconnected */ +int msnprotocol_disconnected() { + + if ( cstate.connected == CSTATE_DISCONNECTED ) { + return 1; + } + + return 0; + +} + +/* Internally called when the socket is connected */ +static void msnprotocol_ready() { + + /* Remove the "writeable" callback, and add a "readable" callback */ + gdk_input_remove(cstate.wcallback); + cstate.wcallback = 0; + cstate.rcallback = gdk_input_add(cstate.socket, GDK_INPUT_READ, (GdkInputFunction) msnprotocol_readable, NULL); + + /* Begin the handshake */ + msnprotocol_sendtr("VER", "MSNP12 MSNP11 CVR0"); + +} + +/* Called from src/mainwindow.c to start the sign-in process */ +void msnprotocol_connect(const char *hostname, unsigned short int port) { + + struct sockaddr_in sa_desc; + struct hostent *server; + unsigned int sockopts; + int conn_error; + + /* Empty values means use the configured value. Other values are provided on an NS redirect */ + if ( strcmp(hostname, "") == 0 ) { + hostname = options_hostname(); + } + if ( port == 0 ) { + port = options_port(); + } + + assert(hostname != NULL); + assert(port > 0); + + /* Check if a connection attempt is already in progress. Abort if it is */ + if ( cstate.connect_lock != 0 ) { + debug_print("NS: Aborting connection: locked.\n"); + return; + } + cstate.connect_lock = 1; + + /* Create and configure the socket (but don't connect it yet) */ + cstate.socket = socket(PF_INET, SOCK_STREAM, 0); + if ( cstate.socket == -1 ) { + error_report("Couldn't create socket"); + msnprotocol_cleanup(); + return; + } + sockopts = fcntl(cstate.socket, F_GETFL); + fcntl(cstate.socket, F_SETFL, sockopts | O_NONBLOCK); + + /* Resolve the server name */ + server = gethostbyname(hostname); + if ( server == NULL ) { + error_report("No such host"); + msnprotocol_cleanup(); + return; + } + + memset(&sa_desc, 0, sizeof(sa_desc)); + sa_desc.sin_family = AF_INET; + memcpy(&sa_desc.sin_addr.s_addr, server->h_addr, server->h_length); + sa_desc.sin_port = htons(port); + + cstate.rbuffer = malloc(256); + assert(cstate.rbuffer != NULL); + cstate.rbufsize = 256; + /* wbuffer starts at NULL and gets created when data is written */ + + conn_error = connect(cstate.socket, (struct sockaddr *)&sa_desc, sizeof(sa_desc)); + if ( (conn_error < 0) && (errno != EINPROGRESS) ) { + + debug_print("NS: Couldn't connect to server - %i\n", errno); + error_report("Couldn't connect to server"); + msnprotocol_cleanup(); + return; + + } else { + cstate.wcallback = gdk_input_add(cstate.socket, GDK_INPUT_WRITE, (GdkInputFunction) msnprotocol_ready, NULL); + } + + cstate.connected = CSTATE_SIGNIN; + +} + +/* Called by src/statusmenu.c to change online status. + * Passes "newstatus" gets cast into a gpointer to do this, which is a bit nasty */ +void msnprotocol_setstatus(OnlineState newstatus) { + + char *mode; + char *capabilities; + char *dpobj; + char *string; + unsigned int clientid; + + /* Don't change status too early (MSN server bug) unless signing out */ + if ( (cstate.connected != CSTATE_CONNECTED) && (newstatus != ONLINE_FLN) ) { + debug_print("NS: Not ready for status change - aborting.\n"); + return; + } + + debug_print("NS: Setting new status: %i\n", newstatus); + + mode = "NLN"; /* Fallback */ + switch ( newstatus ) { + + case ONLINE_NLN : mode = "NLN"; break; + case ONLINE_AWY : mode = "AWY"; break; + case ONLINE_BSY : mode = "BSY"; break; + case ONLINE_BRB : mode = "BRB"; break; + case ONLINE_PHN : mode = "PHN"; break; + case ONLINE_LUN : mode = "LUN"; break; + case ONLINE_HDN : { + mode = "HDN"; + /* sbsessions_destroy_all(); + messagewindow_disable_all(); */ + break; + } + /* The following two shouldn't happen: keep the compiler happy. */ + case ONLINE_ERR : mode = "NLN"; break; + case ONLINE_IDL : mode = "AWY"; break; + case ONLINE_FLN : { + + msnprotocol_sendtr("OUT", ""); + if ( cstate.connected == CSTATE_CONNECTED ) { + listcache_save(); + } + cstate.disconnect_expected = 1; + if ( cstate.disconnect_callback == 0 ) { + cstate.disconnect_callback = gtk_timeout_add(10000, (GtkFunction)msnprotocol_forcedisconnect, NULL); + } + return; + + } + + } + + clientid = 0x50000000 + (1<<5) + (1<<3) + (1<<2); /* MSNC5, Multipacketing (bit 5), Ink (bits 2 and 3) */ + capabilities = malloc(12); + sprintf(capabilities, "%i", clientid); + dpobj = avatars_localobject(); + + string = malloc(strlen(mode) + strlen(capabilities) + strlen(dpobj) + 3); + strcpy(string, mode); + strcat(string, " "); + strcat(string, capabilities); + strcat(string, " "); + strcat(string, dpobj); + free(dpobj); + + msnprotocol_sendtr("CHG", string); + free(string); + free(capabilities); + + messagewindow_picturekick(NULL); + +} + +/* No information from the session record is needed at this stage. */ +int msnprotocol_initiatesb() { + + return msnprotocol_sendtr("XFR", "SB"); + +} + +void msnprotocol_requestsignout() { + msnprotocol_setstatus(ONLINE_FLN); +} + +void msnprotocol_setmfn(const char *new_mfn) { + + char *mfn_string; + char *mfn_code; + + mfn_code = routines_urlencode(new_mfn); + mfn_string = malloc(strlen(mfn_code)+5); + strcpy(mfn_string, "MFN "); + strcat(mfn_string, mfn_code); + + msnprotocol_sendtr("PRP", mfn_string); + + free(mfn_string); +} + +void msnprotocol_setcsm(const char *new_csm) { + + const char *template; + char *uuxblock; + char *sendage; + + if ( cstate.protocol_version >= 11 ) { + template = "<Data><PSM></PSM><CurrentMedia></CurrentMedia></Data>"; + uuxblock = xml_setblock(template, strlen(template), "Data", "PSM", new_csm); + + sendage = malloc(strlen(uuxblock)+8); + assert(strlen(uuxblock)<=99999); /* 5 chars max. */ + sprintf(sendage, "%i\r\n%s", strlen(uuxblock), uuxblock); + msnprotocol_sendtr_nonewline("UUX", sendage); + + free(uuxblock); + free(sendage); + + } + + listcache_setcsm(new_csm); + +} + +void msnprotocol_adduser(const char *username, const char *list) { + + char *line = malloc(strlen(list) + 6 + strlen(username)); + strcpy(line, list); + strcat(line, " N="); + strcat(line, username); + msnprotocol_sendtr("ADC", line); + free(line); + +} + +void msnprotocol_adduserfriendly(const char *username, const char *friendlyname, const char *list) { + + char *line = malloc(strlen(list) + 9 + strlen(username) + strlen(friendlyname)); + strcpy(line, list); + strcat(line, " N="); + strcat(line, username); + strcat(line, " F="); + strcat(line, friendlyname); + msnprotocol_sendtr("ADC", line); + free(line); + +} + +void msnprotocol_remuser(const char *username, const char *list) { + + char *line = malloc(strlen(list) + 2 + strlen(username)); + strcpy(line, list); + strcat(line, " "); + strcat(line, username); + msnprotocol_sendtr("REM", line); + free(line); + +} + +OnlineState msnprotocol_status() { + return cstate.status; +} diff --git a/src/msnprotocol.h b/src/msnprotocol.h new file mode 100644 index 0000000..3a8529f --- /dev/null +++ b/src/msnprotocol.h @@ -0,0 +1,44 @@ +/* + * msnprotocol.h + * + * Low-level DS/NS protocol handling + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef MSNPROTOCOL_H +#define MSNPROTOCOL_H + +#include <gtk/gtk.h> +#include "msngenerics.h" +#include "sbsessions.h" + +extern void msnprotocol_setstatus(OnlineState newstatus); +extern void msnprotocol_connect(const char *hostname, unsigned short int port); +extern int msnprotocol_signedin(void); +extern int msnprotocol_disconnected(void); +extern int msnprotocol_initiatesb(void); +extern void msnprotocol_requestsignout(void); +extern void msnprotocol_setmfn(const char *new_mfn); +extern void msnprotocol_setcsm(const char *new_csm); +extern void msnprotocol_adduser(const char *username, const char *list); +extern void msnprotocol_adduserfriendly(const char *username, const char *friendlyname, const char *list); +extern void msnprotocol_remuser(const char *username, const char *list); +extern OnlineState msnprotocol_status(void); + +#endif /* MSNPROTOCOL_H */ diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..65dd08d --- /dev/null +++ b/src/options.c @@ -0,0 +1,1051 @@ +/* + * options.c + * + * Low-level option handling + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <glob.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> + +#include "debug.h" +#include "accountwindow.h" +#include "options.h" +#include "msngenerics.h" +#include "mainwindow.h" +#include "error.h" +#include "routines.h" +#include "listcache.h" + +#define DEFAULT_HOSTNAME "messenger.hotmail.com" +#define DEFAULT_WGET "/usr/bin/wget" + +static struct { + + char *username; + char *password; + unsigned int rememberlogindetails; + + char *hostname; + unsigned int port; + + char *wget; + + char *gtc; /* Special - comes from list cache. */ + char *blp; /* Special - comes from list cache. */ + + unsigned int hideoffline; /* Hide offline contacts? */ + unsigned int hidecsm; /* Hide local CSM? */ + + GdkColor *localcolour_gdk; /* Colour for local user's messages. */ + char *localcolour_string; /* String version of the above. */ + unsigned int ofontoverride; /* Override other contacts' chosen fonts/colours? */ + GdkColor *ocolour_gdk; /* Colour to override contacts' messages with. */ + char *localfont; /* Font for local user's messages. */ + char *ofont; /* Font to use for contacts' messages (if overriding). */ + + unsigned int ircstyle; /* Use IRC style for new IM windows? */ + unsigned int timestamps; /* Show timestamps for messages in new IM windows? */ + unsigned int showavatars; /* Display avatars in new IM windows? */ + unsigned int showemoticons; /* Show emoticons in new IM windows? */ + +} options = { + + NULL, /* Username */ + NULL, /* Password */ + 0, /* Remember login details */ + + NULL, /* Hostname */ + 0, /* Port */ + + NULL, /* Wget */ + + NULL, /* GTC */ + NULL, /* BLP */ + + FALSE, /* Hide offline */ + FALSE, /* Hide CSM */ + + NULL, /* localcolour_gdk */ + NULL, /* localcolour_string */ + FALSE, /* ofontoverride */ + NULL, /* ocolour_gdk */ + NULL, /* localfont */ + NULL, /* ofont */ + + 0, /* ircstyle */ + 0, /* timestamps */ + 0, /* showavatars */ + 0 /* showemoticons */ + +}; + +static void options_setdefaults() { + + options.username = strdup(""); + options.password = strdup(""); + options.rememberlogindetails = 0; + + options.hostname = strdup(DEFAULT_HOSTNAME); + options.port = DEFAULT_NS_PORT; + + options.wget = strdup(DEFAULT_WGET); + + options.gtc = strdup("BL"); + options.blp = strdup("A"); + + options.hideoffline = FALSE; + options.hidecsm = FALSE; + options.ofontoverride = FALSE; + + options.localcolour_gdk = NULL; + options.localcolour_string = NULL; + options.ocolour_gdk = NULL; + options.localfont = NULL; + options.ofont = NULL; + + options.ircstyle = 0; + options.timestamps = 0; + options.showavatars = 0; + options.showemoticons = 0; + +} + +static void options_createhomedir(char *filename) { + + char *av_name; + char *hg_name; + + /* Create directory. */ + if ( mkdir(filename, S_IRUSR | S_IWUSR | S_IXUSR) != 0 ) { + debug_print("OP: Couldn't create ~/.tuxmessenger directory.\n"); + exit(1); + } + + /* Create "avatars" subdirectory. */ + av_name = malloc(strlen(filename) + 9); + strcpy(av_name, filename); + strcat(av_name, "/avatars"); + if ( mkdir(av_name, S_IRUSR | S_IWUSR | S_IXUSR) != 0 ) { + debug_print("OP: Couldn't create ~/.tuxmessenger/avatars directory.\n"); + exit(1); + } + free(av_name); + + /* Link the "Hourglass", "Default" and "Own" avatars. */ + hg_name = malloc(strlen(filename) + 20); + + strcpy(hg_name, filename); + strcat(hg_name, "/wait_avatar.png"); + if ( symlink(DATADIR"/tuxmessenger/hourglass.png", hg_name) != 0 ) { + debug_print("OP: Couldn't create ~/.tuxmessenger/wait_avatar.png.\n"); + exit(1); + } + + strcpy(hg_name, filename); + strcat(hg_name, "/default_avatar.png"); + if ( symlink(DATADIR"/tuxmessenger/no_avatar.png", hg_name) != 0 ) { + debug_print("OP: Couldn't create ~/.tuxmessenger/default_avatar.png.\n"); + exit(1); + } + + strcpy(hg_name, filename); + strcat(hg_name, "/avatar.png"); + if ( symlink(DATADIR"/tuxmessenger/no_avatar.png", hg_name) != 0 ) { + debug_print("OP: Couldn't create ~/.tuxmessenger/avatar.png.\n"); + exit(1); + } + + free(hg_name); + +} + +static void options_bailout() { + + options_setdefaults(); + accountwindow_open(); + +} + +void options_setusername(const char *username) { + + unsigned int changed = FALSE; + + if ( (username != NULL) && (options.username != NULL) && (strcmp(options.username, "") != 0) && (strcmp(username, options.username) != 0) ) { + changed = TRUE; + } + + if ( options.username != NULL ) { + free(options.username); + } + options.username = strdup(username); + + if ( changed ) { + listcache_setcsm(""); + listcache_invalidate(); + } + +} + +void options_setpassword(const char *password) { + + if ( options.password != NULL ) { + free(options.password); + } + options.password = strdup(password); + +} + +void options_sethostname(const char *hostname) { + + if ( options.hostname != NULL ) { + free(options.hostname); + } + if ( strlen(hostname) > 0 ) { + options.hostname = strdup(hostname); + } else { + debug_print("OP: Using default server hostname instead of zero-length string.\n"); + options.hostname = strdup(DEFAULT_HOSTNAME); + } + +} + +void options_setport(const int port) { + + options.port = port; + if ( options.port == 0 ) { + /* That's just plain silly. */ + debug_print("OP: Correcting port number: 0->%i.\n", DEFAULT_NS_PORT); + options_setport(DEFAULT_NS_PORT); + } + if ( options.port > 65535 ) { + /* D'oh */ + debug_print("OP: Port number too large: correcting to %i\n", DEFAULT_NS_PORT); + options_setport(DEFAULT_NS_PORT); + } + +} + +void options_setwget(const char *wget) { + + if ( options.wget != NULL ) { + free(options.wget); + } + if ( strlen(wget) > 0 ) { + options.wget = strdup(wget); + } else { + debug_print("OP: Using default wget instead of zero-length string.\n"); + options.wget = strdup(DEFAULT_WGET); + } + +} + +void options_setrememberlogindetails(unsigned int remember) { + options.rememberlogindetails = remember; +} + +void options_sethideoffline(unsigned int hide) { + options.hideoffline = hide; +} + +void options_sethidecsm(unsigned int hide) { + options.hidecsm = hide; +} + +void options_setlocalcolour_gdk(const GdkColor *colour) { + + char *string; + + if ( options.localcolour_gdk != NULL ) { + gdk_color_free(options.localcolour_gdk); + } + if ( options.localcolour_string != NULL ) { + free(options.localcolour_string); + } + + options.localcolour_gdk = gdk_color_copy(colour); + + /* Now work out the string version */ + string = malloc(7); + /* Yukky BGR order instead of RGB */ + snprintf(string, 7, "%02hhx%02hhx%02hhx", colour->blue >> 8, colour->green >> 8, colour->red >> 8); + options.localcolour_string = string; + debug_print("OP: String value '%s'\n", string); + +} + +void options_setlocalcolour_string(const char *colour) { + + char *flipped; + char *string; + GdkColor gdkcolour; + + if ( options.localcolour_gdk != NULL ) { + gdk_color_free(options.localcolour_gdk); + } + if ( options.localcolour_string != NULL ) { + free(options.localcolour_string); + } + + options.localcolour_string = strdup(colour); + + /* Now work out the GDK version - flip then parse */ + flipped = routines_flipcolour(colour); + string = malloc(8); + strcpy(string, "#"); + strncat(string, flipped, 7); + string[7] = '\0'; + free(flipped); + if ( gdk_color_parse(string, &gdkcolour) ) { + options.localcolour_gdk = gdk_color_copy(&gdkcolour); + } else { + + debug_print("OP: Whoops! Colour parsing failed (%s)...\n", string); + if ( options.localcolour_gdk != NULL ) { + gdk_color_free(options.localcolour_gdk); + } + if ( options.localcolour_string != NULL ) { + free(options.localcolour_string); + } + options.localcolour_string = NULL; + options.localcolour_gdk = NULL; + + } + free(string); + +} + +void options_setocolour_gdk(const GdkColor *colour) { + + if ( options.ocolour_gdk != NULL ) { + gdk_color_free(options.ocolour_gdk); + } + + options.ocolour_gdk = gdk_color_copy(colour); + +} + +void options_setofontoverride(unsigned int override) { + options.ofontoverride = override; +} + +void options_setocolour_string(const char *colour) { + + char *string; + GdkColor gdkcolour; + + if ( options.ocolour_gdk != NULL ) { + gdk_color_free(options.localcolour_gdk); + } + + string = malloc(8); + strcpy(string, "#"); + strncat(string, colour, 7); + string[7] = '\0'; + if ( gdk_color_parse(string, &gdkcolour) ) { + options.ocolour_gdk = gdk_color_copy(&gdkcolour); + } else { + debug_print("OP: Whoops! Colour parsing failed (%s)...\n", string); + options.localcolour_gdk = NULL; + } + free(string); + +} + +void options_setlocalfont(const char *font) { + options.localfont = strdup(font); +} + +void options_setofont(const char *font) { + options.ofont = strdup(font); +} + +void options_setircstyle(unsigned int ircstyle) { + options.ircstyle = ircstyle; +} + +void options_settimestamps(unsigned int timestamps) { + options.timestamps = timestamps; +} + +void options_setshowavatars(unsigned int showavatars) { + options.showavatars = showavatars; +} + +void options_setshowemoticons(unsigned int showemoticons) { + options.showemoticons = showemoticons; +} + +void options_load() { + + int glob_retval; + glob_t glob_result; + struct stat stat_buffer; + struct stat *statbuf; + char *dir_filename; + char *rc_filename; + + glob_retval = glob("~", GLOB_TILDE, NULL, &glob_result); + statbuf = &stat_buffer; + if ( glob_retval != 0 ) { + + debug_print("OP: glob() for ~/.tuxmessenger failed: "); + switch ( glob_retval ) { + case GLOB_NOSPACE : debug_print("GLOB_NOSPACE\n"); break; + case GLOB_ABORTED : debug_print("GLOB_ABORTED\n"); break; + case GLOB_NOMATCH : debug_print("GLOB_NOMATCH\n"); break; + default : debug_print("Unknown!\n"); break; + } + debug_print("OP: Can't continue :(\n"); + exit(1); + + } + + dir_filename = malloc(strlen(glob_result.gl_pathv[0]) + 15); + strcpy(dir_filename, glob_result.gl_pathv[0]); + globfree(&glob_result); + strcat(dir_filename, "/.tuxmessenger"); + + if ( stat(dir_filename, statbuf) != -1 ) { + + if ( S_ISDIR(stat_buffer.st_mode) ) { + + debug_print("OP: Found '%s'.\n", dir_filename); + /* All is good. */ + + } else { + + debug_print("OP: Found '%s', but it isn't a directory!\n", dir_filename); + debug_print("OP: Can't continue :(\n"); + exit(1); + + } + + } else { + + debug_print("OP: ~/.tuxmessenger directory not found: creating it.\n"); + options_createhomedir(dir_filename); + + } + + /* Right - after all of that, let's take a look at the central config file. */ + options_setdefaults(); /* Set defaults to begin with. */ + rc_filename = malloc(strlen(dir_filename) + 8); + strcpy(rc_filename, dir_filename); + strcat(rc_filename, "/config"); + if ( stat(rc_filename, statbuf) != -1 ) { + + if ( (S_ISREG(stat_buffer.st_mode)) || (S_ISLNK(stat_buffer.st_mode)) ) { + + /* Yay :D */ + FILE *fh; + char *line; + char *rval; + int whoops = 0; + + fh = fopen(rc_filename, "r"); + if ( fh == NULL ) { + debug_print("OP: Error opening options file.\n"); + options_bailout(); + free(rc_filename); + free(dir_filename); + return; + } + + line = malloc(1024); + assert(line != NULL); + rval = "hello"; /* Non-NULL */ + + while ( rval ) { + + rval = fgets(line, 1023, fh); + + if ( ferror(fh) && !feof(fh) ) { + whoops = 1; + break; + } + if ( strlen(line) > 1 ) { + if ( line[strlen(line)-1] == '\n' ) { + line[strlen(line)-1] = '\0'; /* Cut off trailing newline. */ + } + } else { + line[0] = '\0'; + } + + if ( strncmp(line, "username ", 9) == 0 ) { + debug_print("OP: Got username from config file.\n"); + options_setusername(line+9); + } + if ( strncmp(line, "password ", 9) == 0 ) { + debug_print("OP: Got password from config file.\n"); + options_setpassword(line+9); + } + if ( strncmp(line, "hostname ", 9) == 0 ) { + debug_print("OP: Got server hostname from config file.\n"); + options_sethostname(line+9); + } + if ( strncmp(line, "port ", 5) == 0 ) { + debug_print("OP: Got server port number from config file.\n"); + options_setport(atoi(line+5)); + } + if ( strncmp(line, "wget ", 5) == 0 ) { + debug_print("OP: Got wget command from config file.\n"); + options_setwget(line+5); + } + if ( strncmp(line, "hide_offline", 12) == 0 ) { + debug_print("OP: Got hide_offline from config file.\n"); + options_sethideoffline(TRUE); + } + if ( strncmp(line, "hide_localcsm", 12) == 0 ) { + debug_print("OP: Got hide_localcsm from config file.\n"); + options_sethidecsm(TRUE); + } + if ( strncmp(line, "localcolour ", 12) == 0 ) { + debug_print("OP: Got local colour from config file.\n"); + options_setlocalcolour_string(line+12); + } + if ( strncmp(line, "ofontoverride", 12) == 0 ) { + debug_print("OP: Got ofontoverride from config file.\n"); + options_setofontoverride(TRUE); + } + if ( strncmp(line, "ocolour ", 8) == 0 ) { + debug_print("OP: Got override colour from config file.\n"); + options_setocolour_string(line+8); + } + if ( strncmp(line, "localfont ", 10) == 0 ) { + debug_print("OP: Got localfont from config file.\n"); + options_setlocalfont(line+10); + } + if ( strncmp(line, "ofont ", 6) == 0 ) { + debug_print("OP: Got ofont from config file.\n"); + options_setofont(line+6); + } + if ( strncmp(line, "show_avatars", 12) == 0 ) { + debug_print("OP: Got show_avatars from config file.\n"); + options_setshowavatars(TRUE); + } + if ( strncmp(line, "show_emoticons", 14) == 0 ) { + debug_print("OP: Got show_emoticons from config file.\n"); + options_setshowemoticons(TRUE); + } + if ( strncmp(line, "timestamps", 10) == 0 ) { + debug_print("OP: Got timestamps from config file.\n"); + options_settimestamps(TRUE); + } + if ( strncmp(line, "ircstyle", 8) == 0 ) { + debug_print("OP: Got ircstyle from config file.\n"); + options_setircstyle(TRUE); + } + + line[0] = '\0'; /* Prevent this line from being parsed twice. */ + + } + + free(line); + fclose(fh); + + if ( whoops ) { + debug_print("OP: Error reading options file.\n"); + options_bailout(); + } + + } else { + + /* FFS. I'm not handling that. */ + debug_print("OP: Options file exists but isn't a file!\n"); + exit(1); + + } + + } else { + + /* Options file isn't there. Set "sensible defaults" and open the config window... */ + debug_print("OP: Options file not found.\n"); + options_bailout(); + + } + + options_setrememberlogindetails(1); + /* If the username and password are blank, and the server is set to its default, we assume "Remember login details" was unticked last time. */ + if ( (strlen(options_username())==0) && (strlen(options_password())==0) && (options_port()==DEFAULT_NS_PORT) && (strcmp(options_hostname(), DEFAULT_HOSTNAME)==0)) { + options_setrememberlogindetails(0); + } + + free(dir_filename); + free(rc_filename); + +} + +const char *options_username() { + + /* FIXME: Check length */ + /* Don't forget to change to lower case. */ + + return options.username; + +} + +const char *options_password() { + return options.password; +} + +const char *options_hostname() { + return options.hostname; +} + +unsigned short int options_port() { + return options.port; +} + +const char *options_wget() { + return options.wget; +} + +const char *options_blp() { + return options.blp; +} + +const char *options_gtc() { + return options.gtc; +} + +unsigned int options_rememberlogindetails() { + return options.rememberlogindetails; +} + +unsigned int options_hideoffline() { + return options.hideoffline; +} + +unsigned int options_hidecsm() { + return options.hidecsm; +} + +const GdkColor *options_localcolour_gdk() { + return options.localcolour_gdk; +} + +const char *options_localcolour_string() { + return options.localcolour_string; +} + +unsigned int options_ofontoverride() { + return options.ofontoverride; +} + +const GdkColor *options_ocolour_gdk() { + return options.ocolour_gdk; +} + +unsigned int options_ircstyle() { + return options.ircstyle; +} + +unsigned int options_timestamps() { + return options.timestamps; +} + +unsigned int options_showavatars() { + return options.showavatars; +} + +unsigned int options_showemoticons() { + return options.showemoticons; +} + +static char *options_ocolour_string() { + + char *string; + const GdkColor *colour = options_ocolour_gdk(); + + if ( colour == NULL ) { + return NULL; + } + + string = malloc(7); + /* RGB order here */ + snprintf(string, 7, "%02hhx%02hhx%02hhx", colour->red >> 8, colour->green >> 8, colour->blue >> 8); + + return string; + +} + +const char *options_localfont() { + return options.localfont; +} + +const char *options_ofont() { + return options.ofont; +} + +void options_save() { + + FILE *fh; + int glob_retval; + glob_t glob_result; + struct stat stat_buffer; + struct stat *statbuf; + char *dir_filename; + char *rc_filename; + int whoops = 0; + char *newline; + + const char *ousername; + const char *opassword; + const char *ohostname; + int oport; + + glob_retval = glob("~", GLOB_TILDE, NULL, &glob_result); + statbuf = &stat_buffer; + if ( glob_retval != 0 ) { + + debug_print("OP: glob() for ~/.tuxmessenger failed: "); + switch ( glob_retval ) { + case GLOB_NOSPACE : debug_print("GLOB_NOSPACE\n"); break; + case GLOB_ABORTED : debug_print("GLOB_ABORTED\n"); break; + case GLOB_NOMATCH : debug_print("GLOB_NOMATCH\n"); break; + default : debug_print("Unknown!\n"); break; + } + debug_print("OP: Couldn't save options :(\n"); + return; + + } + + dir_filename = malloc(strlen(glob_result.gl_pathv[0]) + 15); + strcpy(dir_filename, glob_result.gl_pathv[0]); + globfree(&glob_result); + strcat(dir_filename, "/.tuxmessenger"); + + if ( stat(dir_filename, statbuf) != -1 ) { + + if ( S_ISDIR(stat_buffer.st_mode) ) { + + debug_print("OP: Found '%s'.\n", dir_filename); + /* All is good. */ + + } else { + + debug_print("OP: Found '%s', but it isn't a directory!\n", dir_filename); + debug_print("OP: Couldn't save options :(\n"); + return; + + } + + } else { + + /* This shouldn't happen here unless someone's been a muppet. */ + debug_print("OP: ~/.tuxmessenger directory not found: creating it.\n"); + options_createhomedir(dir_filename); + + } + + rc_filename = malloc(strlen(dir_filename) + 8); + strcpy(rc_filename, dir_filename); + strcat(rc_filename, "/config"); + + fh = fopen(rc_filename, "w"); + if ( chmod(rc_filename, S_IRUSR | S_IWUSR) != 0 ) { + error_report("Couldn't set permissions on config file!"); + } + if ( fh == NULL ) { + + debug_print("OP: Error opening options file.\n"); + debug_print("OP: Couldn't save options :(\n"); + return; + + } + + newline = malloc(2); + sprintf(newline, "\n"); + + if ( options_rememberlogindetails() ) { + ousername = options_username(); + opassword = options_password(); + ohostname = options_hostname(); + oport = options_port(); + } else { + ousername = ""; + opassword = ""; + ohostname = DEFAULT_HOSTNAME; + oport = DEFAULT_NS_PORT; + } + + /* ----------------------- Username -------------------------- */ + if ( fputs("username ", fh) == EOF ) { + whoops = 1; + } + if ( whoops == 0 ) { + if ( fputs(ousername, fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(newline, fh) == EOF ) { + whoops = 1; + } + } + + /* ----------------------- Password -------------------------- */ + if ( whoops == 0 ) { + if ( fputs("password ", fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(opassword, fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(newline, fh) == EOF ) { + whoops = 1; + } + } + + /* ----------------------- wget -------------------------- */ + if ( whoops == 0 ) { + if ( fputs("wget ", fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(options_wget(), fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(newline, fh) == EOF ) { + whoops = 1; + } + } + + /* ----------------------- Hostname -------------------------- */ + if ( whoops == 0 ) { + if ( fputs("hostname ", fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(ohostname, fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(newline, fh) == EOF ) { + whoops = 1; + } + } + + /* ----------------------- Port -------------------------- */ + if ( whoops == 0 ) { + + char *port; + + if ( fputs("port ", fh) == EOF ) { + whoops = 1; + } + /* options_port() < 65536 because of data size - so always fits. */ + port = malloc(6); + sprintf(port, "%i", oport); + if ( whoops == 0 ) { + if ( fputs(port, fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(newline, fh) == EOF ) { + whoops = 1; + } + } + free(port); + + } + + /* ----------------------- Show Offline Contacts ---------------------- */ + if ( whoops == 0 ) { + + if ( options_hideoffline() ) { + if ( fputs("hide_offline\n", fh) == EOF ) { + whoops = 1; + } + } /* Else write nothing. */ + + } + + /* ----------------------- Show CSM ---------------------- */ + if ( whoops == 0 ) { + + if ( options_hidecsm() ) { + if ( fputs("hide_localcsm\n", fh) == EOF ) { + whoops = 1; + } + } /* Else write nothing. */ + + } + + /* ----------------------- Local colour -------------------------- */ + if ( options_localcolour_string() != NULL ) { + if ( whoops == 0 ) { + if ( fputs("localcolour ", fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(options_localcolour_string(), fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(newline, fh) == EOF ) { + whoops = 1; + } + } + } + + /* ----------------------- Override Contacts' Fonts/Colours ---------------------- */ + if ( whoops == 0 ) { + + if ( options_ofontoverride() ) { + if ( fputs("ofontoverride\n", fh) == EOF ) { + whoops = 1; + } + } /* Else write nothing. */ + + } + + /* ----------------------- Override colour -------------------------- */ + if ( options_ocolour_string() ) { + if ( whoops == 0 ) { + if ( fputs("ocolour ", fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + char *string = options_ocolour_string(); + if ( fputs(string, fh) == EOF ) { + whoops = 1; + } + free(string); + } + if ( whoops == 0 ) { + if ( fputs(newline, fh) == EOF ) { + whoops = 1; + } + } + } + + /* ----------------------- Local font -------------------------- */ + if ( options_localfont() ) { + if ( whoops == 0 ) { + if ( fputs("localfont ", fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(options_localfont(), fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(newline, fh) == EOF ) { + whoops = 1; + } + } + } + + /* ----------------------- Contacts' font -------------------------- */ + if ( options_ofont() ) { + if ( whoops == 0 ) { + if ( fputs("ofont ", fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(options_ofont(), fh) == EOF ) { + whoops = 1; + } + } + if ( whoops == 0 ) { + if ( fputs(newline, fh) == EOF ) { + whoops = 1; + } + } + } + + /* ----------------------- IRC style ---------------------- */ + if ( whoops == 0 ) { + + if ( options_ircstyle() ) { + if ( fputs("ircstyle\n", fh) == EOF ) { + whoops = 1; + } + } /* Else write nothing. */ + + } + + /* ----------------------- Show emoticons ---------------------- */ + if ( whoops == 0 ) { + + if ( options_showemoticons() ) { + if ( fputs("show_emoticons\n", fh) == EOF ) { + whoops = 1; + } + } /* Else write nothing. */ + + } + + /* ----------------------- Show avatars --------------------- */ + if ( whoops == 0 ) { + + if ( options_showavatars() ) { + if ( fputs("show_avatars\n", fh) == EOF ) { + whoops = 1; + } + } /* Else write nothing. */ + + } + + /* ----------------------- Timestamps ---------------------- */ + if ( whoops == 0 ) { + + if ( options_timestamps() ) { + if ( fputs("timestamps\n", fh) == EOF ) { + whoops = 1; + } + } /* Else write nothing. */ + + } + + if ( whoops != 0 ) { + debug_print("OP: Whoops! Config file write failed.\n"); + } + + fclose(fh); + free(newline); + +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..c42467e --- /dev/null +++ b/src/options.h @@ -0,0 +1,73 @@ +/* + * options.h + * + * Low-level option handling + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef OPTIONS_H +#define OPTIONS_H + +#include <gdk/gdk.h> + +extern void options_load(); +extern void options_save(); + +/* Single-function interfaces to option values. Returned pointers must not be freed nor strings altered. */ +extern const char *options_username(); +extern const char *options_password(); +extern const char *options_hostname(); +extern unsigned short int options_port(); +extern const char *options_wget(); +extern const char *options_gtc(); +extern const char *options_blp(); +extern unsigned int options_rememberlogindetails(); +extern unsigned int options_hideoffline(); +extern unsigned int options_hidecsm(); +extern const char *options_localcolour_string(); +extern const GdkColor *options_localcolour_gdk(); +extern unsigned int options_ofontoverride(void); +extern const GdkColor *options_ocolour_gdk(); +extern const char *options_localfont(); +extern const char *options_ofont(); +extern unsigned int options_ircstyle(); +extern unsigned int options_timestamps(); +extern unsigned int options_showavatars(); +extern unsigned int options_showemoticons(); + +extern void options_setusername(const char *username); +extern void options_setpassword(const char *password); +extern void options_sethostname(const char *hostname); +extern void options_setport(const int port); +extern void options_setwget(const char *wget); +extern void options_setrememberlogindetails(unsigned int remember); +extern void options_sethideoffline(unsigned int hide); +extern void options_sethidecsm(unsigned int hide); +extern void options_setlocalcolour_gdk(const GdkColor *colour); +extern void options_setofontoverride(unsigned int override); +extern void options_setocolour_gdk(const GdkColor *colour); +extern void options_setlocalfont(const char *font); +extern void options_setofont(const char *font); +extern void options_setircstyle(unsigned int ircstyle); +extern void options_settimestamps(unsigned int timestamps); +extern void options_setshowavatars(unsigned int showavatars); +extern void options_setshowemoticons(unsigned int showemoticons); + +#endif /* OPTIONS_H */ diff --git a/src/prefswindow.c b/src/prefswindow.c new file mode 100644 index 0000000..d056646 --- /dev/null +++ b/src/prefswindow.c @@ -0,0 +1,921 @@ +/* + * prefswindow.c + * + * The preferences window + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "options.h" +#include "msnprotocol.h" +#include "debug.h" +#include "contactlist.h" +#include "routines.h" +#include "twnauth.h" +#include "error.h" + +/* This is where I go slightly mad. Solid UI code... */ + +/* Linked list item definition for storing changes to be made to the block/allow lists */ +typedef struct allowblockchange_t { + struct allowblockchange *next; + char *string; +} AllowBlockChange; + +static struct { + + unsigned int open; /* Non-zero means the prefs window is open. */ + GtkWidget *window; /* The prefs window itself */ + + GtkWidget *blocklist; /* vbox in the "Allow/Block lists" tab */ + GtkWidget *reverselist; /* vbox in the "Reverse list" tab */ + GtkWidget *blocklist_sub; /* vbox in the "Allow/Block lists" tab */ + GtkWidget *reverselist_sub; /* vbox in the "Reverse list" tab */ + + GtkWidget *blocklist_allowbutton; /* "<- Allow" button */ + GtkWidget *blocklist_blockbutton; /* "Block ->" button */ + GtkWidget *blocklist_allowclist; /* CList for AL */ + GtkWidget *blocklist_blockclist; /* CList for BL */ + GtkWidget *reverselist_clist; /* CList for RL */ + GtkWidget *reverselist_gtc; /* Tick box */ + GtkWidget *blocklist_blp; /* Tick box */ + GtkWidget *ofontbutton; /* Font button for other contacts */ + GtkWidget *ocolourbutton; /* Colour button for other contacts */ + + int block_selected; + int allow_selected; + + GtkWidget *wget; /* "Wget" box */ + +} prefswindow = { + + 0, + NULL, + + NULL, + NULL, + NULL, + NULL, + + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + + -1, + -1, + + NULL + +}; + +static void prefswindow_allowselect(GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data) { + + prefswindow.allow_selected = row; + gtk_widget_set_sensitive(prefswindow.blocklist_blockbutton, 1); + if ( prefswindow.block_selected != -1 ) { + gtk_clist_unselect_row(GTK_CLIST(prefswindow.blocklist_blockclist), prefswindow.block_selected, 0); + } + +} + +static void prefswindow_allowunselect(GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data) { + + prefswindow.allow_selected = -1; + gtk_widget_set_sensitive(prefswindow.blocklist_blockbutton, 0); + +} + +static void prefswindow_blockselect(GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data) { + + prefswindow.block_selected = row; + gtk_widget_set_sensitive(prefswindow.blocklist_allowbutton, 1); + if ( prefswindow.allow_selected != -1 ) { + gtk_clist_unselect_row(GTK_CLIST(prefswindow.blocklist_allowclist), prefswindow.allow_selected, 0); + } + +} + +static void prefswindow_blockunselect(GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data) { + + prefswindow.block_selected = -1; + gtk_widget_set_sensitive(prefswindow.blocklist_allowbutton, 0); + +} + +static void prefswindow_allowclick() { +} + +static void prefswindow_blockclick() { +} + +static void prefswindow_filllist(char *list, GtkWidget *clist) { + +/* ContactItem *token; + char *contact; + + token = NULL; + contact = contactlist_getcontact(list, &token); + while ( token ) { + + char *friendlyname; + char *friendlyname_decoded; + char *username; + char *full_string; + char *clist_add[1]; + + username = routines_lindex(contact, 0); + friendlyname = routines_lindex(contact, 1); + + if ( strlen(friendlyname) == 0 ) { + + clist_add[0] = username; + + } else { + + friendlyname_decoded = routines_urldecode(friendlyname); + free(friendlyname); + full_string = malloc(strlen(username) + strlen(friendlyname_decoded) + 3 + 1); + strcpy(full_string, username); + strcat(full_string, " - "); + strcat(full_string, friendlyname_decoded); + + clist_add[0] = full_string; + + free(friendlyname_decoded); + free(username); + + } + + gtk_clist_append(GTK_CLIST(clist), clist_add); + free(clist_add[0]); + + contact = contactlist_getcontact(list, &token); + + }*/ + +} + +static void prefswindow_fillreverselist() { + +/* ContactItem *token; + char *contact; + + token = NULL; + contact = contactlist_getcontact("RL", &token); + while ( token ) { + + char *friendlyname; + char *friendlyname_decoded; + char *username; + char *clist_add[3]; + + username = routines_lindex(contact, 0); + friendlyname = routines_lindex(contact, 1); + + clist_add[0] = username; + friendlyname_decoded = routines_urldecode(friendlyname); + free(friendlyname); + clist_add[1] = friendlyname_decoded; + + if ( contactlist_isonlist("AL", username) ) { + clist_add[2] = "Allow"; + } else if ( contactlist_isonlist("BL", username) ) { + clist_add[2] = "Block"; + } else { + clist_add[2] = "Neither"; + } + + gtk_clist_append(GTK_CLIST(prefswindow.reverselist_clist), clist_add); + + free(friendlyname_decoded); + free(username); + + contact = contactlist_getcontact("RL", &token); + + }*/ + +} + +/* Parallel of mainwindow_setonline. */ +void prefswindow_setonline() { + + GtkWidget *prefs_blocklist_vbox; + GtkWidget *prefs_blocklist_buttonbox; + GtkWidget *prefs_blocklist_scroll; + GtkWidget *prefs_blocklist_viewport; + GtkWidget *prefs_blocklist_hbox; + GtkWidget *prefs_blocklist_allow_clist_label; + GtkWidget *prefs_blocklist_block_clist_label; + + GtkWidget *prefs_reverselist_vbox; + GtkWidget *prefs_reverselist_scroll; + GtkWidget *prefs_reverselist_clist_username; + GtkWidget *prefs_reverselist_clist_displayname; + GtkWidget *prefs_reverselist_clist_status; + + if ( !prefswindow.open ) { + return; + } + + if ( prefswindow.blocklist_sub != NULL ) { + gtk_widget_destroy(prefswindow.blocklist_sub); + } + + if ( prefswindow.reverselist_sub != NULL ) { + gtk_widget_destroy(prefswindow.reverselist_sub); + } + + prefswindow.blocklist_sub = gtk_vbox_new(TRUE, 0); + prefswindow.reverselist_sub = gtk_vbox_new(TRUE, 0); + gtk_widget_show(prefswindow.reverselist_sub); + + prefs_blocklist_vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_ref (prefs_blocklist_vbox); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_vbox", prefs_blocklist_vbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_blocklist_vbox); + gtk_container_add (GTK_CONTAINER (prefswindow.blocklist_sub), prefs_blocklist_vbox); + gtk_container_add (GTK_CONTAINER (prefswindow.blocklist), prefswindow.blocklist_sub); + gtk_widget_show(prefswindow.blocklist_sub); + + prefs_blocklist_scroll = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_ref (prefs_blocklist_scroll); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_scroll", prefs_blocklist_scroll, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_blocklist_scroll); + gtk_box_pack_start (GTK_BOX (prefs_blocklist_vbox), prefs_blocklist_scroll, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (prefs_blocklist_scroll), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + + prefs_blocklist_viewport = gtk_viewport_new (NULL, NULL); + gtk_widget_ref (prefs_blocklist_viewport); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_viewport", prefs_blocklist_viewport, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_blocklist_viewport); + gtk_container_add (GTK_CONTAINER (prefs_blocklist_scroll), prefs_blocklist_viewport); + + prefs_blocklist_hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_ref (prefs_blocklist_hbox); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_hbox", prefs_blocklist_hbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_blocklist_hbox); + gtk_container_add (GTK_CONTAINER (prefs_blocklist_viewport), prefs_blocklist_hbox); + + prefswindow.blocklist_allowclist = gtk_clist_new (1); + gtk_widget_ref (prefswindow.blocklist_allowclist); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_allow_clist", prefswindow.blocklist_allowclist, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefswindow.blocklist_allowclist); + gtk_box_pack_start (GTK_BOX (prefs_blocklist_hbox), prefswindow.blocklist_allowclist, TRUE, TRUE, 0); + gtk_clist_set_column_width (GTK_CLIST (prefswindow.blocklist_allowclist), 0, 80); + gtk_clist_column_titles_show (GTK_CLIST (prefswindow.blocklist_allowclist)); + + prefs_blocklist_allow_clist_label = gtk_label_new ("Allow"); + gtk_widget_ref (prefs_blocklist_allow_clist_label); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_allow_clist_label", prefs_blocklist_allow_clist_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_blocklist_allow_clist_label); + gtk_clist_set_column_widget (GTK_CLIST (prefswindow.blocklist_allowclist), 0, prefs_blocklist_allow_clist_label); + + prefswindow.blocklist_blockclist = gtk_clist_new (1); + gtk_widget_ref (prefswindow.blocklist_blockclist); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_block_clist", prefswindow.blocklist_blockclist, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefswindow.blocklist_blockclist); + gtk_box_pack_start (GTK_BOX (prefs_blocklist_hbox), prefswindow.blocklist_blockclist, TRUE, TRUE, 0); + gtk_clist_set_column_width (GTK_CLIST (prefswindow.blocklist_blockclist), 0, 80); + gtk_clist_column_titles_show (GTK_CLIST (prefswindow.blocklist_blockclist)); + + prefs_blocklist_block_clist_label = gtk_label_new ("Block"); + gtk_widget_ref (prefs_blocklist_block_clist_label); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_block_clist_label", prefs_blocklist_block_clist_label, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_blocklist_block_clist_label); + gtk_clist_set_column_widget (GTK_CLIST (prefswindow.blocklist_blockclist), 0, prefs_blocklist_block_clist_label); + + prefs_blocklist_buttonbox = gtk_hbutton_box_new (); + gtk_widget_ref (prefs_blocklist_buttonbox); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_buttonbox", prefs_blocklist_buttonbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_blocklist_buttonbox); + gtk_box_pack_start (GTK_BOX (prefs_blocklist_vbox), prefs_blocklist_buttonbox, FALSE, FALSE, 0); + gtk_button_box_set_layout (GTK_BUTTON_BOX (prefs_blocklist_buttonbox), GTK_BUTTONBOX_SPREAD); + + prefswindow.blocklist_allowbutton = gtk_button_new_with_label ("<- Allow"); + gtk_widget_ref (prefswindow.blocklist_allowbutton); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_allow_button", prefswindow.blocklist_allowbutton, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefswindow.blocklist_allowbutton); + gtk_container_add (GTK_CONTAINER (prefs_blocklist_buttonbox), prefswindow.blocklist_allowbutton); + GTK_WIDGET_SET_FLAGS (prefswindow.blocklist_allowbutton, GTK_CAN_DEFAULT); + + prefswindow.blocklist_blockbutton = gtk_button_new_with_label ("Block ->"); + gtk_widget_ref (prefswindow.blocklist_blockbutton); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.blocklist), "prefs_blocklist_block_button", prefswindow.blocklist_blockbutton, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefswindow.blocklist_blockbutton); + gtk_container_add (GTK_CONTAINER (prefs_blocklist_buttonbox), prefswindow.blocklist_blockbutton); + GTK_WIDGET_SET_FLAGS (prefswindow.blocklist_blockbutton, GTK_CAN_DEFAULT); + + gtk_container_set_border_width(GTK_CONTAINER(prefswindow.blocklist), 10); + gtk_clist_column_titles_passive(GTK_CLIST(prefswindow.blocklist_allowclist)); + gtk_clist_column_titles_passive(GTK_CLIST(prefswindow.blocklist_blockclist)); + + prefswindow.blocklist_blp = gtk_check_button_new_with_label ("Block all other users"); + gtk_widget_ref (prefswindow.blocklist_blp); + gtk_widget_show (prefswindow.blocklist_blp); + gtk_box_pack_start (GTK_BOX (prefs_blocklist_vbox), prefswindow.blocklist_blp, FALSE, FALSE, 0); + + if ( strcmp(options_blp(), "BL") == 0) { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefswindow.blocklist_blp), TRUE); + } else { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefswindow.blocklist_blp), FALSE); + } + + gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_allowclist), "select_row", GTK_SIGNAL_FUNC(prefswindow_allowselect), NULL); + gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_allowclist), "unselect_row", GTK_SIGNAL_FUNC(prefswindow_allowunselect), NULL); + gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_blockbutton), "clicked", GTK_SIGNAL_FUNC(prefswindow_allowclick), NULL); + + gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_blockclist), "select_row", GTK_SIGNAL_FUNC(prefswindow_blockselect), NULL); + gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_blockclist), "unselect_row", GTK_SIGNAL_FUNC(prefswindow_blockunselect), NULL); + gtk_signal_connect(GTK_OBJECT(prefswindow.blocklist_allowbutton), "clicked", GTK_SIGNAL_FUNC(prefswindow_blockclick), NULL); + + prefswindow_filllist("BL", prefswindow.blocklist_blockclist); + prefswindow_filllist("AL", prefswindow.blocklist_allowclist); + + gtk_widget_set_sensitive(prefswindow.blocklist_allowbutton , 0); + gtk_widget_set_sensitive(prefswindow.blocklist_blockbutton, 0); + + /* ************* now the Reverse List section ************** */ + + prefs_reverselist_vbox = gtk_vbox_new (FALSE, 3); + gtk_widget_ref (prefs_reverselist_vbox); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_vbox", prefs_reverselist_vbox, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_reverselist_vbox); + gtk_container_add (GTK_CONTAINER (prefswindow.reverselist_sub), prefs_reverselist_vbox); + gtk_container_add(GTK_CONTAINER(prefswindow.reverselist), prefswindow.reverselist_sub); + + prefs_reverselist_scroll = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_ref (prefs_reverselist_scroll); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_scroll", prefs_reverselist_scroll, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_reverselist_scroll); + gtk_box_pack_start (GTK_BOX (prefs_reverselist_vbox), prefs_reverselist_scroll, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (prefs_reverselist_scroll), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + + prefswindow.reverselist_clist = gtk_clist_new (3); + gtk_widget_ref (prefswindow.reverselist_clist); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_clist", prefswindow.reverselist_clist, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefswindow.reverselist_clist); + gtk_container_add (GTK_CONTAINER (prefs_reverselist_scroll), prefswindow.reverselist_clist); + gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 0, 80); + gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 1, 80); + gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 2, 80); + gtk_clist_column_titles_show (GTK_CLIST (prefswindow.reverselist_clist)); + + prefs_reverselist_clist_username = gtk_label_new ("Username"); + gtk_widget_ref (prefs_reverselist_clist_username); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_clist_username", prefs_reverselist_clist_username, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_reverselist_clist_username); + gtk_clist_set_column_widget (GTK_CLIST (prefswindow.reverselist_clist), 0, prefs_reverselist_clist_username); + + prefs_reverselist_clist_displayname = gtk_label_new ("Display Name"); + gtk_widget_ref (prefs_reverselist_clist_displayname); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_clist_displayname", prefs_reverselist_clist_displayname, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_reverselist_clist_displayname); + gtk_clist_set_column_widget (GTK_CLIST (prefswindow.reverselist_clist), 1, prefs_reverselist_clist_displayname); + gtk_widget_set_usize (prefs_reverselist_clist_displayname, 128, -2); + + prefs_reverselist_clist_status = gtk_label_new ("Status"); + gtk_widget_ref (prefs_reverselist_clist_status); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_clist_status", prefs_reverselist_clist_status, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefs_reverselist_clist_status); + gtk_clist_set_column_widget (GTK_CLIST (prefswindow.reverselist_clist), 2, prefs_reverselist_clist_status); + gtk_widget_set_usize (prefs_reverselist_clist_status, 31, -2); + + prefswindow.reverselist_gtc = gtk_check_button_new_with_label ("Add new users to the Allow List automatically"); + gtk_widget_ref (prefswindow.reverselist_gtc); + gtk_object_set_data_full (GTK_OBJECT (prefswindow.reverselist), "prefs_reverselist_gtc", prefswindow.reverselist_gtc, + (GtkDestroyNotify) gtk_widget_unref); + gtk_widget_show (prefswindow.reverselist_gtc); + gtk_box_pack_start (GTK_BOX (prefs_reverselist_vbox), prefswindow.reverselist_gtc, FALSE, FALSE, 0); + + if ( strcmp(options_gtc(), "N") == 0) { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefswindow.reverselist_gtc), TRUE); + } else { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefswindow.reverselist_gtc), FALSE); + } + + gtk_container_set_border_width(GTK_CONTAINER(prefswindow.reverselist), 10); + gtk_clist_column_titles_passive(GTK_CLIST(prefswindow.reverselist_clist)); + + gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 0, 149); + gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 1, 210); + gtk_clist_set_column_width (GTK_CLIST (prefswindow.reverselist_clist), 2, 69); + + prefswindow_fillreverselist(); + +} + +/* Parallel of mainwindow_setdispatch. Tell the user they can't modify lists while disconnected. */ +void prefswindow_setdispatch() { + + GtkWidget *message_label; + + if ( !prefswindow.open ) { + return; + } + + if ( prefswindow.blocklist_sub != NULL ) { + gtk_widget_destroy(prefswindow.blocklist_sub); + } + + if ( prefswindow.reverselist_sub != NULL ) { + gtk_widget_destroy(prefswindow.reverselist_sub); + } + + prefswindow.blocklist_sub = gtk_vbox_new(TRUE, 0); + assert(prefswindow.blocklist_sub != NULL); + gtk_container_add(GTK_CONTAINER(prefswindow.blocklist), prefswindow.blocklist_sub); + message_label = gtk_label_new("You must be signed in to view or modify your block list"); + assert(message_label != NULL); + gtk_box_pack_start(GTK_BOX(prefswindow.blocklist_sub), message_label, TRUE, TRUE, 0); + gtk_label_set_line_wrap(GTK_LABEL(message_label), TRUE); + gtk_label_set_justify(GTK_LABEL(message_label), GTK_JUSTIFY_CENTER); + gtk_widget_show_all(prefswindow.blocklist_sub); + + prefswindow.reverselist_sub = gtk_vbox_new(TRUE, 0); + assert(prefswindow.reverselist_sub != NULL); + gtk_container_add(GTK_CONTAINER(prefswindow.reverselist), prefswindow.reverselist_sub); + message_label = gtk_label_new("You must be signed in to view your reverse list"); + assert(message_label != NULL); + gtk_box_pack_start(GTK_BOX(prefswindow.reverselist_sub), message_label, TRUE, TRUE, 0); + gtk_label_set_line_wrap(GTK_LABEL(message_label), TRUE); + gtk_label_set_justify(GTK_LABEL(message_label), GTK_JUSTIFY_CENTER); + gtk_widget_show_all(prefswindow.reverselist_sub); + +} + +static void prefswindow_closed() { + prefswindow.open = 0; +} + +static void prefswindow_lcolsel(GtkWidget *widget, gpointer data) { + + GdkColor colour; + + gtk_color_button_get_color(GTK_COLOR_BUTTON(widget), &colour); + options_setlocalcolour_gdk(&colour); + options_save(); + +} + +static void prefswindow_lfontsel(GtkWidget *widget, gpointer data) { + + const char *font; + + font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget)); + debug_print("PW: Font name '%s'\n", font); + options_setlocalfont(font); + options_save(); + +} + +static void prefswindow_ofontsel(GtkWidget *widget, gpointer data) { + + const char *font; + + font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget)); + debug_print("PW: Font name '%s'\n", font); + options_setofont(font); + options_save(); + +} + +static void prefswindow_ocolsel(GtkWidget *widget, gpointer data) { + + GdkColor colour; + + gtk_color_button_get_color(GTK_COLOR_BUTTON(widget), &colour); + options_setocolour_gdk(&colour); + options_save(); + +} + +static void prefswindow_ofontoverride_toggle(GtkWidget *widget, gpointer data) { + + if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) { + options_setofontoverride(TRUE); + gtk_widget_set_sensitive(prefswindow.ofontbutton, TRUE); + gtk_widget_set_sensitive(prefswindow.ocolourbutton, TRUE); + } else { + options_setofontoverride(FALSE); + gtk_widget_set_sensitive(prefswindow.ofontbutton, FALSE); + gtk_widget_set_sensitive(prefswindow.ocolourbutton, FALSE); + } + options_save(); + +} + +static void prefswindow_ircstyle_toggle(GtkWidget *widget, gpointer data) { + + if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) { + options_setircstyle(TRUE); + } else { + options_setircstyle(FALSE); + } + options_save(); + +} + +static void prefswindow_timestamps_toggle(GtkWidget *widget, gpointer data) { + + if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) { + options_settimestamps(TRUE); + } else { + options_settimestamps(FALSE); + } + options_save(); + +} + +static void prefswindow_showavatars_toggle(GtkWidget *widget, gpointer data) { + + if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) { + options_setshowavatars(TRUE); + } else { + options_setshowavatars(FALSE); + } + options_save(); + +} + +static gint prefswindow_validate_wget(GtkWidget *widget, gpointer data) { + + const char *text = gtk_entry_get_text(GTK_ENTRY(widget)); + options_setwget(text); + options_save(); + + return FALSE; + +} + +static void prefswindow_do_wget() { + gtk_entry_set_text(GTK_ENTRY(prefswindow.wget), options_wget()); +} + +static gint prefswindow_guess_wget(GtkWidget *widget, gpointer data) { + + char *nexus; + + options_setwget("/usr/bin/wget"); + nexus = twnauth_loginurl(); + if ( strlen(nexus) == 0 ) { + free(nexus); + options_setwget("/usr/bin/wget --no-check-certificate"); + nexus = twnauth_loginurl(); + if ( strlen(nexus) == 0 ) { + free(nexus); + options_setwget("/usr/local/bin/wget"); + nexus = twnauth_loginurl(); + if ( strlen(nexus) == 0 ) { + free(nexus); + options_setwget("/usr/local/bin/wget --no-check-certificate"); + nexus = twnauth_loginurl(); + if ( strlen(nexus) == 0 ) { + free(nexus); + options_setwget("~/bin/wget"); + nexus = twnauth_loginurl(); + if ( strlen(nexus) == 0 ) { + free(nexus); + options_setwget("~/bin/wget --no-check-certificate"); + nexus = twnauth_loginurl(); + if ( strlen(nexus) == 0 ) { + free(nexus); + options_setwget("wget"); + nexus = twnauth_loginurl(); + if ( strlen(nexus) == 0 ) { + free(nexus); + options_setwget("wget --no-check-certificate"); + nexus = twnauth_loginurl(); + if ( strlen(nexus) == 0 ) { + error_report("Couldn't determine correct wget configuration - is wget installed on your system?"); + free(nexus); + return FALSE; + } + } + } + } + } + } + } + } + + free(nexus); + prefswindow_do_wget(); + error_message("Successfully determined wget configuration."); + + return FALSE; + +} + +static gint prefswindow_check_wget(GtkWidget *widget, gpointer data) { + + char *nexus; + + nexus = twnauth_loginurl(); + + if ( strlen(nexus) != 0 ) { + GtkWidget *window; + window = gtk_message_dialog_new(GTK_WINDOW(prefswindow.window), GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "'wget' configuration appears to be correct."); + g_signal_connect_swapped(window, "response", G_CALLBACK(gtk_widget_destroy), window); + gtk_widget_show(window); + } else { + GtkWidget *window; + window = gtk_message_dialog_new(GTK_WINDOW(prefswindow.window), GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, "'wget' appears not to work..."); + g_signal_connect_swapped(window, "response", G_CALLBACK(gtk_widget_destroy), window); + gtk_widget_show(window); + } + + free(nexus); + + return FALSE; + +} + +static gint prefswindow_showemoticons_toggle(GtkWidget *widget, gpointer data) { + + if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) { + options_setshowemoticons(TRUE); + } else { + options_setshowemoticons(FALSE); + } + options_save(); + + return FALSE; + +} + +/* Open the preferences window */ +void prefswindow_open(int page) { + + GtkWidget *prefs_notebook; + + GtkWidget *prefs_auth_label; + GtkWidget *prefs_auth_box; + GtkWidget *prefs_auth_wget_label; + GtkWidget *prefs_auth_wget_guess; + GtkWidget *prefs_auth_wget_check; + GtkWidget *prefs_auth_wget_hbox; + GtkWidget *prefs_auth_wget_vbox; + GtkWidget *prefs_auth_wget_nbox; + GtkWidget *prefs_auth_wget_heading; + GtkWidget *prefs_auth_wget_heading_justify; + + /* Arrrggghhhhh */ + GtkWidget *prefs_messagewindows_label; + GtkWidget *prefs_messagewindows_box; + GtkWidget *prefs_messagewindows_text; + GtkWidget *prefs_messagewindows_font_box; + GtkWidget *prefs_messagewindows_font_button; + GtkWidget *prefs_messagewindows_font_label; + GtkWidget *prefs_messagewindows_font_label_justify; + GtkWidget *prefs_messagewindows_colour_button; + GtkWidget *prefs_messagewindows_ofont_box; + GtkWidget *prefs_messagewindows_ofont_label; + GtkWidget *prefs_messagewindows_ofont_label_justify; + GtkWidget *prefs_messagewindows_ofont_override; + GtkWidget *prefs_messagewindows_font_hbox; + GtkWidget *prefs_messagewindows_font_vbox; + GtkWidget *prefs_messagewindows_ofont_hbox; + GtkWidget *prefs_messagewindows_ofont_vbox; + GtkWidget *prefs_messagewindows_fbox; + GtkWidget *prefs_messagewindows_nbox; + GtkWidget *prefs_messagewindows_qbox; + GtkWidget *prefs_messagewindows_toggles_vbox; + GtkWidget *prefs_messagewindows_toggles_hbox; + GtkWidget *prefs_messagewindows_toggles_label; + GtkWidget *prefs_messagewindows_toggles_label_justify; + GtkWidget *prefs_messagewindows_toggles_irc; + GtkWidget *prefs_messagewindows_toggles_timestamp; + GtkWidget *prefs_messagewindows_toggles_avatars; + GtkWidget *prefs_messagewindows_toggles_emoticons; + + /* Prevent opening of the preferences window more than once */ + if ( prefswindow.open == 1 ) { + return; + } + + prefswindow.window = gtk_dialog_new_with_buttons("TuxMessenger Preferences", mainwindow_gtkwindow(), 0, GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL); + gtk_dialog_set_has_separator(GTK_DIALOG(prefswindow.window), FALSE); + + prefs_notebook = gtk_notebook_new(); + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(prefs_notebook), GTK_POS_TOP); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(prefswindow.window)->vbox), prefs_notebook, TRUE, TRUE, 0); + + /* ****************** Message Windows *************************************************************/ + + prefs_messagewindows_label = gtk_label_new("Message Windows"); + prefs_messagewindows_box = gtk_vbox_new(FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(prefs_messagewindows_box), 12); + gtk_notebook_append_page(GTK_NOTEBOOK(prefs_notebook), prefs_messagewindows_box, prefs_messagewindows_label); + + prefs_messagewindows_text = gtk_label_new(""); + gtk_label_set_markup(GTK_LABEL(prefs_messagewindows_text), "<span style=\"italic\" weight=\"light\">These settings define the behaviour of newly-created instant message windows. You can also set all of these options individually using the menus for each window.</span>"); + gtk_widget_set_size_request(GTK_WIDGET(prefs_messagewindows_text), 500, -1); + gtk_label_set_line_wrap(GTK_LABEL(prefs_messagewindows_text), TRUE); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_box), prefs_messagewindows_text, FALSE, FALSE, 0); + + prefs_messagewindows_fbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_box), prefs_messagewindows_fbox, FALSE, FALSE, 12); + prefs_messagewindows_font_label = gtk_label_new(""); + prefs_messagewindows_font_label_justify = gtk_hbox_new(FALSE, 0); + gtk_label_set_markup(GTK_LABEL(prefs_messagewindows_font_label), "<span weight=\"bold\">Font and Colour for Your Messages</span>"); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_font_label_justify), prefs_messagewindows_font_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_fbox), prefs_messagewindows_font_label_justify, FALSE, FALSE, 6); + + prefs_messagewindows_font_vbox = gtk_vbox_new(FALSE, 0); + prefs_messagewindows_font_hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_fbox), prefs_messagewindows_font_hbox, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_font_hbox), prefs_messagewindows_font_vbox, FALSE, FALSE, 12); + + prefs_messagewindows_font_box = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_font_vbox), prefs_messagewindows_font_box, FALSE, FALSE, 0); + if ( options_localfont() ) { + prefs_messagewindows_font_button = gtk_font_button_new_with_font(options_localfont()); + } else { + prefs_messagewindows_font_button = gtk_font_button_new(); + } + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_font_box), prefs_messagewindows_font_button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(prefs_messagewindows_font_button), "font-set", GTK_SIGNAL_FUNC(prefswindow_lfontsel), NULL); + + if ( options_localcolour_gdk() == NULL ) { + prefs_messagewindows_colour_button = gtk_color_button_new(); + } else { + prefs_messagewindows_colour_button = gtk_color_button_new_with_color(options_localcolour_gdk()); + } + g_signal_connect(G_OBJECT(prefs_messagewindows_colour_button), "color-set", GTK_SIGNAL_FUNC(prefswindow_lcolsel), NULL); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_font_box), prefs_messagewindows_colour_button, FALSE, FALSE, 6); + + prefs_messagewindows_nbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_box), prefs_messagewindows_nbox, FALSE, FALSE, 12); + prefs_messagewindows_ofont_label = gtk_label_new(""); + prefs_messagewindows_ofont_label_justify = gtk_hbox_new(FALSE, 0); + gtk_label_set_markup(GTK_LABEL(prefs_messagewindows_ofont_label), "<span weight=\"bold\">Fonts and Colours for Contacts' Messages</span>"); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_label_justify), prefs_messagewindows_ofont_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_nbox), prefs_messagewindows_ofont_label_justify, FALSE, FALSE, 0); + + prefs_messagewindows_ofont_vbox = gtk_vbox_new(FALSE, 0); + prefs_messagewindows_ofont_hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_nbox), prefs_messagewindows_ofont_hbox, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_hbox), prefs_messagewindows_ofont_vbox, FALSE, FALSE, 12); + + prefs_messagewindows_ofont_override = gtk_check_button_new_with_label("Override contacts' chosen fonts"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs_messagewindows_ofont_override), options_ofontoverride()); + + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_vbox), prefs_messagewindows_ofont_override, FALSE, FALSE, 5); + prefs_messagewindows_ofont_box = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_vbox), prefs_messagewindows_ofont_box, FALSE, FALSE, 0); + if ( options_ofont() ) { + prefswindow.ofontbutton = gtk_font_button_new_with_font(options_ofont()); + } else { + prefswindow.ofontbutton = gtk_font_button_new(); + } + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_box), prefswindow.ofontbutton, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(prefswindow.ofontbutton), "font-set", GTK_SIGNAL_FUNC(prefswindow_ofontsel), NULL); + if ( options_ocolour_gdk() == NULL ) { + prefswindow.ocolourbutton = gtk_color_button_new(); + } else { + prefswindow.ocolourbutton = gtk_color_button_new_with_color(options_ocolour_gdk()); + } + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_ofont_box), prefswindow.ocolourbutton, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(prefswindow.ocolourbutton), "color-set", GTK_SIGNAL_FUNC(prefswindow_ocolsel), NULL); + + /* Call the check button's callback to sort out greying-out of stuff as appropriate. + Results in a spurious option setting, but saves code duplication. */ + prefswindow_ofontoverride_toggle(prefs_messagewindows_ofont_override, NULL); + /* NOW connect the signal handler. Doing it any earlier makes Bad Stuff happen. */ + g_signal_connect(G_OBJECT(prefs_messagewindows_ofont_override), "toggled", GTK_SIGNAL_FUNC(prefswindow_ofontoverride_toggle), prefs_messagewindows_ofont_box); + + prefs_messagewindows_qbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_box), prefs_messagewindows_qbox, FALSE, FALSE, 12); + prefs_messagewindows_toggles_label = gtk_label_new(""); + prefs_messagewindows_toggles_label_justify = gtk_hbox_new(FALSE, 0); + gtk_label_set_markup(GTK_LABEL(prefs_messagewindows_toggles_label), "<span weight=\"bold\">Text and Window Layout</span>"); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_label_justify), prefs_messagewindows_toggles_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_qbox), prefs_messagewindows_toggles_label_justify, FALSE, FALSE, 0); + + prefs_messagewindows_toggles_vbox = gtk_vbox_new(FALSE, 0); + prefs_messagewindows_toggles_hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_qbox), prefs_messagewindows_toggles_hbox, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_hbox), prefs_messagewindows_toggles_vbox, FALSE, FALSE, 12); + + prefs_messagewindows_toggles_irc = gtk_check_button_new_with_label("Use IRC style for messages"); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_vbox), prefs_messagewindows_toggles_irc, FALSE, FALSE, 3); + prefs_messagewindows_toggles_timestamp = gtk_check_button_new_with_label("Timestamp messages"); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_vbox), prefs_messagewindows_toggles_timestamp, FALSE, FALSE, 3); + prefs_messagewindows_toggles_avatars = gtk_check_button_new_with_label("Display avatars"); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_vbox), prefs_messagewindows_toggles_avatars, FALSE, FALSE, 3); + prefs_messagewindows_toggles_emoticons = gtk_check_button_new_with_label("Display emoticons"); + gtk_box_pack_start(GTK_BOX(prefs_messagewindows_toggles_vbox), prefs_messagewindows_toggles_emoticons, FALSE, FALSE, 3); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs_messagewindows_toggles_irc), options_ircstyle()); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs_messagewindows_toggles_timestamp), options_timestamps()); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs_messagewindows_toggles_avatars), options_showavatars()); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs_messagewindows_toggles_emoticons), options_showemoticons()); + g_signal_connect(G_OBJECT(prefs_messagewindows_toggles_irc), "toggled", GTK_SIGNAL_FUNC(prefswindow_ircstyle_toggle), NULL); + g_signal_connect(G_OBJECT(prefs_messagewindows_toggles_timestamp), "toggled", GTK_SIGNAL_FUNC(prefswindow_timestamps_toggle), NULL); + g_signal_connect(G_OBJECT(prefs_messagewindows_toggles_avatars), "toggled", GTK_SIGNAL_FUNC(prefswindow_showavatars_toggle), NULL); + g_signal_connect(G_OBJECT(prefs_messagewindows_toggles_emoticons), "toggled", GTK_SIGNAL_FUNC(prefswindow_showemoticons_toggle), NULL); + + /* ****************** Authentication *************************************************************/ + + prefs_auth_label = gtk_label_new("Authentication"); + prefs_auth_box = gtk_vbox_new(FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(prefs_auth_box), 12); + gtk_notebook_append_page(GTK_NOTEBOOK(prefs_notebook), prefs_auth_box, prefs_auth_label); + + prefs_auth_wget_heading = gtk_label_new(""); + gtk_label_set_markup(GTK_LABEL(prefs_auth_wget_heading), "<span weight=\"bold\">Command for 'wget'</span>"); + prefs_auth_wget_heading_justify = gtk_hbox_new(FALSE, 0); + prefs_auth_wget_nbox = gtk_vbox_new(FALSE, 2); + gtk_box_pack_start(GTK_BOX(prefs_auth_wget_nbox), prefs_auth_wget_heading_justify, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_auth_wget_heading_justify), prefs_auth_wget_heading, FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(prefs_auth_box), prefs_auth_wget_nbox, FALSE, FALSE, 12); + prefs_auth_wget_hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(prefs_auth_wget_nbox), prefs_auth_wget_hbox, FALSE, FALSE, 0); + prefs_auth_wget_vbox = gtk_vbox_new(FALSE, 6); + gtk_box_pack_start(GTK_BOX(prefs_auth_wget_hbox), prefs_auth_wget_vbox, FALSE, FALSE, 12); + + prefs_auth_wget_label = gtk_label_new(""); + gtk_label_set_markup(GTK_LABEL(prefs_auth_wget_label), "<span style=\"italic\" weight=\"light\">TuxMessenger calls the 'wget' program to help log in to the server. You need to specify the command to run to invoke a copy of 'wget' which is capable of using HTTPS. In most cases you can simply enter 'wget' here.</span>"); + gtk_widget_set_size_request(GTK_WIDGET(prefs_auth_wget_label), 500, -1); + gtk_label_set_line_wrap(GTK_LABEL(prefs_auth_wget_label), TRUE); + gtk_box_pack_start(GTK_BOX(prefs_auth_wget_vbox), prefs_auth_wget_label, FALSE, FALSE, 0); + + prefswindow.wget = gtk_entry_new_with_max_length(128); + prefswindow_do_wget(); + gtk_box_pack_start(GTK_BOX(prefs_auth_wget_vbox), prefswindow.wget, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(prefswindow.wget), "focus-out-event", GTK_SIGNAL_FUNC(prefswindow_validate_wget), NULL); + g_signal_connect(G_OBJECT(prefswindow.wget), "activate", GTK_SIGNAL_FUNC(prefswindow_validate_wget), NULL); + + prefs_auth_wget_guess = gtk_button_new_with_label("Attempt to automatically determine correct wget configuration"); + gtk_box_pack_start(GTK_BOX(prefs_auth_wget_vbox), prefs_auth_wget_guess, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(prefs_auth_wget_guess), "clicked", GTK_SIGNAL_FUNC(prefswindow_guess_wget), NULL); + + prefs_auth_wget_check = gtk_button_new_with_label("Check wget configuration"); + gtk_box_pack_start(GTK_BOX(prefs_auth_wget_vbox), prefs_auth_wget_check, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(prefs_auth_wget_check), "clicked", GTK_SIGNAL_FUNC(prefswindow_check_wget), NULL); + + /* ************************************************************************************************/ + + gtk_window_set_title(GTK_WINDOW(prefswindow.window), "TuxMessenger Preferences"); + gtk_window_position(GTK_WINDOW(prefswindow.window), GTK_WIN_POS_MOUSE); + gtk_window_set_policy(GTK_WINDOW(prefswindow.window), FALSE, TRUE, FALSE); + + gtk_widget_show_all(prefswindow.window); + gtk_notebook_set_page(GTK_NOTEBOOK(prefs_notebook), page); + + g_signal_connect(G_OBJECT(prefswindow.window), "destroy", GTK_SIGNAL_FUNC(prefswindow_closed), NULL); + g_signal_connect(G_OBJECT(prefswindow.window), "response", GTK_SIGNAL_FUNC(gtk_widget_destroy), NULL); + + prefswindow.open = 1; + +} diff --git a/src/prefswindow.h b/src/prefswindow.h new file mode 100644 index 0000000..aa05f56 --- /dev/null +++ b/src/prefswindow.h @@ -0,0 +1,30 @@ +/* + * prefswindow.h + * + * The preferences window + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef PREFSWINDOW_H +#define PREFSWINDOW_H + +extern void prefswindow_open(void); + +#endif /* PREFSWINDOW_H */ diff --git a/src/reversewindow.c b/src/reversewindow.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/reversewindow.c diff --git a/src/reversewindow.h b/src/reversewindow.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/reversewindow.h diff --git a/src/routines.c b/src/routines.c new file mode 100644 index 0000000..8ce937b --- /dev/null +++ b/src/routines.c @@ -0,0 +1,800 @@ +/* + * routines.c + * + * Random Useful Routines and Functions + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <glob.h> +#include <gdk/gdk.h> + +#include "debug.h" + +char *routines_glob(const char *filename) { + + char *full_filename; + glob_t glob_result; + int glob_retval; + + glob_retval = glob(filename, GLOB_TILDE, NULL, &glob_result); + if ( glob_retval != 0 ) { + + debug_print("glob() failed: "); + switch ( glob_retval ) { + case GLOB_NOSPACE : debug_print("GLOB_NOSPACE\n"); break; + case GLOB_ABORTED : debug_print("GLOB_ABORTED\n"); break; + case GLOB_NOMATCH : debug_print("GLOB_NOMATCH\n"); break; + default : debug_print("Unknown!\n"); break; + } + return NULL; + + } + full_filename = malloc(strlen(glob_result.gl_pathv[0])+1); + strcpy(full_filename, glob_result.gl_pathv[0]); + globfree(&glob_result); + + return full_filename; + +} + +/* TCL-style lindex using ' ' as a separator */ +char *routines_lindex(char *string, unsigned int pos) { + + unsigned int i; + unsigned int s=0; + char *minibuffer; + unsigned int max_output_length = strlen(string); + for ( i=0; i<strlen(string); i++ ) { + + if ( string[i] == ' ' ) + s++; + if ( s == pos ) { + + char ch='z'; + unsigned int p=0; + char *result; + result = malloc(2+max_output_length); + if ( s == 0 ) + i--; + while ((ch != '\0') && (p<max_output_length)) { + + ch = string[i+p+1]; + if ( ch == ' ' ) + ch = '\0'; + if ( ch == '\r' ) + ch = '\0'; + if ( ch == '\n' ) + ch = '\0'; + result[p]=ch; + p++; + + } + result[p]='\0'; + + /* now copy the string into a buffer that's the right size */ + minibuffer=malloc(strlen(result)+sizeof(char)); + strcpy(minibuffer, result); + free(result); + return minibuffer; + + } + + } + /* whoopsie didn't find it */ + minibuffer = malloc(1); + minibuffer[0]='\0'; + return minibuffer; /* return something that can safely be freed */ + +} + +/* same as above, but returns all of the rest of the string, without stopping at the next space*/ +char *routines_lindexend(char *string, unsigned int pos) { + + unsigned int i; + unsigned int s=0; + char *minibuffer; + unsigned int max_output_length = strlen(string); + + for ( i=0; i<strlen(string); i++ ) { + if ( string[i] == ' ' ) + s++; + if ( s == pos ) { + char ch='z'; + unsigned int p=0; + char *result; + result = malloc(2+max_output_length); + if ( s == 0 ) + i--; + while ((ch != '\0') && (p<max_output_length)) { + ch = string[i+p+1]; + if ( ch == '\n' ) + ch = '\0'; + if ( ch == '\r' ) + ch = '\0'; + result[p]=ch; + p++; + } + result[p]='\0'; + + /* now copy the string into a buffer that's the right size */ + minibuffer=malloc(strlen(result)+sizeof(char)); + strcpy(minibuffer, result); + free(result); + return minibuffer; + } + } + /* whoopsie didn't find it */ + minibuffer = malloc(1); + minibuffer[0]='\0'; + return minibuffer; /* return something that can still be freed */ + +} + +/* Convert a single hex digit char to an int */ +static unsigned int routines_unhex(char ch) { + + switch (ch) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': return 10; + case 'b': return 11; + case 'c': return 12; + case 'd': return 13; + case 'e': return 14; + case 'f': return 15; + case 'A': return 10; + case 'B': return 11; + case 'C': return 12; + case 'D': return 13; + case 'E': return 14; + case 'F': return 15; /* yes I know i could have done it differently... */ + } + + return 0; /* clearly this would be the caller's fault... */ + +} + +char *routines_urldecode(const char *words) { + + char *result; + char first_char=' ', second_char=' '; + unsigned int i, s=0, j=0; + + result = malloc(strlen(words)+1); + for ( i=0; i<=strlen(words); i++ ) { + + char ch; + ch=words[i]; + + if ( isxdigit(ch) && (s==2) ) { + + unsigned int new_ch = 0; + unsigned int first_num, second_num; + j-=2; + second_char=ch; + + /* now we take our two hex digits and decimalize... */ + first_num = routines_unhex(first_char); + second_num = routines_unhex(second_char); + new_ch = (char) second_num + (16 * first_num); + if ( new_ch == '\0' ) + new_ch = ' '; /* no way... */ + if ( new_ch == '\10' ) + new_ch = ' '; /* f--k off... */ + if ( new_ch == '\13' ) + new_ch = ' '; /* grrr */ + + ch=new_ch; + s=0; + + } + if ( isxdigit(ch) && (s==1) ) { + s=2; + first_char=ch; + } + if (ch=='%') { + s=1; + } + result[j]=ch; + j++; + + } + return result; + +} + +char *routines_urlencode(const char *words) { + + char *result; + unsigned int i, p; + + result = malloc((3*strlen(words))+1); /* this is the maximum possible size it could be */ + p = 0; + for ( i=0; i<strlen(words); i++ ) { + char ch; + ch = words[i]; + if ( ch == ' ' ) { + result[p]='%'; + p++; + result[p]='2'; + p++; + result[p]='0'; + } else if ( ch == '<' ) { + result[p]='%'; + p++; + result[p]='3'; + p++; + result[p]='C'; + } else if( ch == '>' ) { + result[p]='%'; + p++; + result[p]='3'; + p++; + result[p]='E'; + } else if ( ch == '=' ) { + result[p]='%'; + p++; + result[p]='3'; + p++; + result[p]='D'; + } else if ( ch == '@' ) { + result[p]='%'; + p++; + result[p]='4'; + p++; + result[p]='0'; + } else if ( ch == '\"' ) { + result[p]='%'; + p++; + result[p]='2'; + p++; + result[p]='2'; + } else if ( ch == '/' ) { + result[p]='%'; + p++; + result[p]='2'; + p++; + result[p]='F'; + } else if ( ch == '+' ) { + result[p]='%'; + p++; + result[p]='2'; + p++; + result[p]='B'; + } else { + result[p]=ch; + } + p++; + } + result[p]='\0'; + return result; +} + +/* extract hostname from hostname:port style string */ +char *routines_hostname(const char *input) { + + unsigned int i, n=0; + char ch='z'; + char *output; + + /* First we must check that there is actually a colon present */ + for ( i=0; i<strlen(input); i++ ) { + if ( input[i] == ':' ) { + n++; + } + } + /* if there's no colon, host is just the same as the input */ + if ( n == 0 ) + return strdup(input); + + i = 0; + /* A few bytes get wasted here */ + output = malloc(strlen(input)); + while ( ch != '\0' ) { + + ch = input[i]; + if ( ch == ':' ) { + ch = '\0'; + } + output[i] = ch; + i++; + + } + return output; + /* You can free the output string when you've finished with it */ + +} + +/* extract the port number from hostname:port */ +int routines_port(const char *input) { + + char ch='z'; + unsigned int i=0, n=0; + + /* first we must check that there is actually a colon present */ + for ( i=0; i<strlen(input); i++ ) { + if ( input[i] == ':' ) + n++; + } + /* Bail out if no port specified */ + if ( n == 0 ) { + return 0; + } + + i=0; + while ( (ch != ':') && (i<=strlen(input)) ) { + ch = input[i]; + i++; + } + + return atoi(input+i); +} + +static char routines_base64char(unsigned int sextet) { + + switch (sextet) { + + case 0 : return 'A'; + case 1 : return 'B'; + case 2 : return 'C'; + case 3 : return 'D'; + case 4 : return 'E'; + case 5 : return 'F'; + case 6 : return 'G'; + case 7 : return 'H'; + case 8 : return 'I'; + case 9 : return 'J'; + case 10 : return 'K'; + case 11 : return 'L'; + case 12 : return 'M'; + case 13 : return 'N'; + case 14 : return 'O'; + case 15 : return 'P'; + case 16 : return 'Q'; + case 17 : return 'R'; + case 18 : return 'S'; + case 19 : return 'T'; + case 20 : return 'U'; + case 21 : return 'V'; + case 22 : return 'W'; + case 23 : return 'X'; + case 24 : return 'Y'; + case 25 : return 'Z'; + case 26 : return 'a'; + case 27 : return 'b'; + case 28 : return 'c'; + case 29 : return 'd'; + case 30 : return 'e'; + case 31 : return 'f'; + case 32 : return 'g'; + case 33 : return 'h'; + case 34 : return 'i'; + case 35 : return 'j'; + case 36 : return 'k'; + case 37 : return 'l'; + case 38 : return 'm'; + case 39 : return 'n'; + case 40 : return 'o'; + case 41 : return 'p'; + case 42 : return 'q'; + case 43 : return 'r'; + case 44 : return 's'; + case 45 : return 't'; + case 46 : return 'u'; + case 47 : return 'v'; + case 48 : return 'w'; + case 49 : return 'x'; + case 50 : return 'y'; + case 51 : return 'z'; + case 52 : return '0'; + case 53 : return '1'; + case 54 : return '2'; + case 55 : return '3'; + case 56 : return '4'; + case 57 : return '5'; + case 58 : return '6'; + case 59 : return '7'; + case 60 : return '8'; + case 61 : return '9'; + case 62 : return '+'; + case 63 : return '/'; + + } + + return 'A'; + +} + +static int routines_unbase64char(unsigned char sextet) { + + switch (sextet) { + + case 'A' : return 0; + case 'B' : return 1; + case 'C' : return 2; + case 'D' : return 3; + case 'E' : return 4; + case 'F' : return 5; + case 'G' : return 6; + case 'H' : return 7; + case 'I' : return 8; + case 'J' : return 9; + case 'K' : return 10; + case 'L' : return 11; + case 'M' : return 12; + case 'N' : return 13; + case 'O' : return 14; + case 'P' : return 15; + case 'Q' : return 16; + case 'R' : return 17; + case 'S' : return 18; + case 'T' : return 19; + case 'U' : return 20; + case 'V' : return 21; + case 'W' : return 22; + case 'X' : return 23; + case 'Y' : return 24; + case 'Z' : return 25; + case 'a' : return 26; + case 'b' : return 27; + case 'c' : return 28; + case 'd' : return 29; + case 'e' : return 30; + case 'f' : return 31; + case 'g' : return 32; + case 'h' : return 33; + case 'i' : return 34; + case 'j' : return 35; + case 'k' : return 36; + case 'l' : return 37; + case 'm' : return 38; + case 'n' : return 39; + case 'o' : return 40; + case 'p' : return 41; + case 'q' : return 42; + case 'r' : return 43; + case 's' : return 44; + case 't' : return 45; + case 'u' : return 46; + case 'v' : return 47; + case 'w' : return 48; + case 'x' : return 49; + case 'y' : return 50; + case 'z' : return 51; + case '0' : return 52; + case '1' : return 53; + case '2' : return 54; + case '3' : return 55; + case '4' : return 56; + case '5' : return 57; + case '6' : return 58; + case '7' : return 59; + case '8' : return 60; + case '9' : return 61; + case '+' : return 62; + case '/' : return 63; + + } + + return 0; + +} + +char *routines_base64givenlength(char *base64_input, ssize_t length) { + + ssize_t i; + char *base64_output; + unsigned int sextet; + size_t j = 0; + + /* One extra byte for \0 terminator, another for rounding error. */ + base64_output = malloc((4*length)/3 + 4); + + for ( i=0; i<=length-3; i+=3 ) { + + /* First sextet */ + sextet = (base64_input[i] & 0xfc) >> 2; + base64_output[j++] = routines_base64char(sextet); + + /* Second sextet */ + sextet = ((base64_input[i] & 0x3) << 4) + ((base64_input[i+1] & 0xf0) >> 4); + base64_output[j++] = routines_base64char(sextet); + + /* Third sextet */ + sextet = ((base64_input[i+1] & 0x0f) << 2) + ((base64_input[i+2] & 0xc0) >> 6); + base64_output[j++] = routines_base64char(sextet); + + /* Fourth sextet */ + sextet = (base64_input[i+2] & 0x3f); + base64_output[j++] = routines_base64char(sextet); + + } + + switch ((length) % 3) { + + case 1 : { + + /* One byte left over */ + i = length-1; + + sextet = (base64_input[i] & 0xfc) >> 2; + base64_output[j++] = routines_base64char(sextet); + + sextet = (base64_input[i] & 0x03) << 4; + base64_output[j++] = routines_base64char(sextet); + + base64_output[j++] = '='; + base64_output[j++] = '='; + + break; + + } + + case 2 : { + + /* Two bytes left over */ + i = length-2; + + sextet = (base64_input[i] & 0xfc) >> 2; + base64_output[j++] = routines_base64char(sextet); + + sextet = ((base64_input[i] & 0x3) << 4) + ((base64_input[i+1] & 0xf0) >> 4); + base64_output[j++] = routines_base64char(sextet); + + sextet = (base64_input[i+1] & 0x0f) << 2; + base64_output[j++] = routines_base64char(sextet); + + base64_output[j++] = '='; + + break; + + } + + } + + base64_output[j++] = '\0'; + + return base64_output; + +} + +/* Base64-encode something. Needs to have a NULL terminator. */ +char *routines_base64(char *base64_input) { + return routines_base64givenlength(base64_input, strlen(base64_input)+1); +} + +/* Decode Base64 string (with terminator) and put pointer to result at "output". Return length of result. */ +size_t routines_base64decode(const char *base64_input, char **output) { + + size_t len = 0; + size_t ptr = 0; + char *op = malloc(strlen(base64_input)); + + assert(strlen(base64_input) % 4 == 0 ); + + while ( ptr < strlen(base64_input) ) { + + op[len] = (routines_unbase64char(base64_input[ptr]) << 2) + ((routines_unbase64char(base64_input[ptr+1]) & 48) >> 4); + op[len+1] = ((routines_unbase64char(base64_input[ptr+1]) & 15) << 4) + ((routines_unbase64char(base64_input[ptr+2]) & 60) >> 2); + op[len+2] = ((routines_unbase64char(base64_input[ptr+2]) & 3) << 6) + routines_unbase64char(base64_input[ptr+3]); + + len+=3; + ptr+=4; + + } + + if ( base64_input[ptr-1] == '=' ) { + len--; + } + if ( base64_input[ptr-2] == '=' ) { + len--; + } + + *output = op; + return len; + +} + +static char *routines_randomhexbyte() { + + int j; + char *response = malloc(3); + + /* Taken from the "rand()" man page. */ + j = 1 +(int)(256.0*rand()/(RAND_MAX+1.0)); + sprintf(response, "%2hhx", j); + + return response; + +} + +/* Generate a GUID */ +char *routines_guid() { + + int i, k; + char *guid; + + guid = malloc(39); + assert(guid != NULL); + strcpy(guid, "{"); + + for ( i=0; i<4; i++ ) { + + char *byte; + byte = routines_randomhexbyte(); + strncat(guid, byte, 2); + free(byte); + + } + + for ( k=0; k<3; k++ ) { + + strcat(guid, "-"); + for ( i=0; i<2; i++ ) { + + char *byte; + byte = routines_randomhexbyte(); + strncat(guid, byte, 2); + free(byte); + + } + + } + + /* To be a valid GUID, the version and variant fields need to be set appropriately. */ + guid[15] = '4'; + guid[20] = 'a'; + + strcat(guid, "-"); + for ( i=0; i<6; i++ ) { + + char *byte; + byte = routines_randomhexbyte(); + strncat(guid, byte, 2); + free(byte); + + } + strcat(guid, "}"); + + /* Fix up spaces. */ + for ( i=0; i<(strlen(guid)-1); i++ ) { + if ( guid[i] == ' ' ) { + guid[i] = '0'; + } + } + + return guid; + +} + +/* Replace triangular brackets with something Pango can handle. */ +char *routines_killtriangles(const char *input) { + + char *output; + size_t i; + size_t j = 0; + + output = malloc(2*strlen(input)+1); + for ( i=0; i<=strlen(input); i++ ) { + if ( input[i] == '<' ) { + output[j] = '&'; j++; + output[j] = 'l'; j++; + output[j] = 't'; j++; + output[j] = ';'; j++; + } else if ( input[i] == '>' ) { + output[j] = '&'; j++; + output[j] = 'g'; j++; + output[j] = 't'; j++; + output[j] = ';'; j++; + } else { + output[j] = input[i]; j++; + } + } + + return output; + +} + +/* Replace triangular brackets and ampersands with something Pango can handle. */ +char *routines_killtriangles_and_ampersands(const char *input) { + + char *output; + size_t i; + size_t j = 0; + + output = malloc(2*strlen(input)+1); + for ( i=0; i<=strlen(input); i++ ) { + if ( input[i] == '<' ) { + output[j] = '&'; j++; + output[j] = 'l'; j++; + output[j] = 't'; j++; + output[j] = ';'; j++; + } else if ( input[i] == '>' ) { + output[j] = '&'; j++; + output[j] = 'g'; j++; + output[j] = 't'; j++; + output[j] = ';'; j++; + } else if ( input[i] == '&' ) { + output[j] = '&'; j++; + output[j] = 'a'; j++; + output[j] = 'm'; j++; + output[j] = 'p'; j++; + output[j] = ';'; j++; + } else { + output[j] = input[i]; j++; + } + } + + return output; + +} + +char *routines_flipcolour(const char *colour) { + + char *flipped = malloc(7); + char scratch; + strncpy(flipped, colour, 6); + flipped[6] = '\0'; + + scratch = flipped[0]; + flipped[0] = flipped[4]; + flipped[4] = scratch; + + scratch = flipped[1]; + flipped[1] = flipped[5]; + flipped[5] = scratch; + + return flipped; + +} + +char *routines_gdk_to_hashrgb(const GdkColor *colour) { + + char *string; + + if ( colour == NULL ) { + return NULL; + } + + string = malloc(8); + /* RGB order */ + snprintf(string, 8, "#%02hhx%02hhx%02hhx", colour->red >> 8, colour->green >> 8, colour->blue >> 8); + string[7] = '\0'; + + return string; + +} diff --git a/src/routines.h b/src/routines.h new file mode 100644 index 0000000..0d3326d --- /dev/null +++ b/src/routines.h @@ -0,0 +1,53 @@ +/* + * routines.h + * + * Random Useful Routines and Function + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef ROUTINES_H +#define ROUTINES_H + +#include <gdk/gdk.h> + +extern char *routines_lindex(char *string, unsigned int pos); +extern char *routines_lindexend(char *string, unsigned int pos); + +extern char *routines_urldecode(const char *words); +extern char *routines_urlencode(const char *words); + +extern char *routines_hostname(const char *input); +extern int routines_port(const char *input); + +extern char *routines_base64(char *base64_input); +extern char *routines_base64givenlength(char *base64_input, ssize_t length); +extern size_t routines_base64decode(const char *base64_input, char **output); + +extern char *routines_guid(void); + +extern char *routines_glob(const char *filename); + +extern char *routines_killtriangles(const char *input); +extern char *routines_killtriangles_and_ampersands(const char *input); + +extern char *routines_flipcolour(const char *colour); +extern char *routines_gdk_to_hashrgb(const GdkColor *colour); + +#endif /* ROUTINES_H */ diff --git a/src/sbprotocol.c b/src/sbprotocol.c new file mode 100644 index 0000000..1d58ac7 --- /dev/null +++ b/src/sbprotocol.c @@ -0,0 +1,1271 @@ +/* + * sbprotocol.c + * + * SB protocol handling + * + * (c) 2002-2006 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <unistd.h> +#include <fcntl.h> +#include <netdb.h> +#include <errno.h> +#include <string.h> + +#include "sbsessions.h" +#include "msnprotocol.h" +#include "error.h" +#include "debug.h" +#include "options.h" +#include "routines.h" +#include "messagewindow.h" +#include "mime.h" +#include "contactlist.h" +#include "main.h" +#include "msnp2p.h" +#include "msninvite.h" +#include "ink.h" +#include "sbprotocol.h" +#include "msngenerics.h" + +/* Maximum allowed message size, and chunk size to split large messages into. + * MAX_MSG_SIZE needs to be sufficiently bigger than MULTIPACKET_SIZE to + * fit the headers in, otherwise you get an infinite loop. */ +#define MULTIPACKET_SIZE 1024 +#define MAX_MSG_SIZE 1280 + +/* Define this if you want to see what gets written to the socket. */ +#define SBSEND_DEBUG 1 +/* Define if you want to see SB write buffer operations. */ +#undef SBWRITE_DEBUG + +/* Some prototypes */ +static void sbprotocol_sendmsg(SbSession *session, char *contenttype, char *extra_headers, char *body, ssize_t length); + +/* Called when a socket gets disconnected. Session record vanishes. */ +static void sbprotocol_disconnected(SbSession *session) { + debug_print("SB %8p: Disconnected - deleting.\n", session); + sbsessions_destroy(session); +} + +/* Write a chunk of the outgoing data queue to an SB socket. */ +static void sbprotocol_writeable(SbSession *session) { + + ssize_t wlen; + unsigned int new_wbufsize; +#ifdef SBSEND_DEBUG + char *debug_string; + unsigned int i; +#endif /* SBSEND_DEBUG */ + + wlen = write(session->socket, session->wbuffer, session->wbufsize); + +#ifdef SBSEND_DEBUG + debug_string = malloc(session->wbufsize+1); + memcpy(debug_string, session->wbuffer, session->wbufsize); + debug_string[wlen] = '\0'; + for ( i=0; i<session->wbufsize; i++ ) { + if ( debug_string[i] == '\r' ) { + debug_string[i] = 'r'; + } + if ( debug_string[i] == '\n' ) { + debug_string[i] = 'n'; + } + } + debug_print("SB %8p: Send:'%s'\n", session, debug_string); + free(debug_string); +#endif /* SBSEND_DEBUG */ + + if ( wlen > 0 ) { + +#ifdef SBWRITE_DEBUG + debug_print("SB %8p: Wrote %i bytes to socket.\n", session, wlen); +#endif /* SBWRITE_DEBUG */ + + /* wlen holds the number of bytes written. Sort the buffer out accordingly... */ + memmove(session->wbuffer, session->wbuffer + wlen, session->wbufsize - wlen); + new_wbufsize = session->wbufsize - wlen; + + session->wbuffer = realloc(session->wbuffer, new_wbufsize); + if ( new_wbufsize == 0 ) { + +#ifdef SBWRITE_DEBUG + debug_print("SB %8p: SB write buffer empty: destroying.\n", session); +#endif /* SBWRITE_DEBUG */ + gdk_input_remove(session->wcallback); + session->wcallback = 0; + session->wbuffer = NULL; + + } + session->wbufsize = new_wbufsize; + session->woffset -= wlen; + + } else { + + if ( wlen == -1 ) { + + /* Write error! :( */ + if ( errno != EAGAIN ) { /* EAGAIN should never happen here */ + + /* Something bad happened */ + int closeval; + closeval = close(session->socket); + if ( closeval != 0 ) { + /* Grrr.. now it won't close - not much we can do. */ + debug_print("SB %8p: Couldn't close socket after write error.\n", session); + } + sbprotocol_disconnected(session); + return; + + } + + } + + } + +} + +/* Queue data for sending to the server, adding a TrID - returns the TrID used for this transaction. */ +static int sbprotocol_sendtr_internal(SbSession *session, const char *instr, const char *args, size_t arglen, int newline) { + + char *trid_string; + size_t len; + + len = strlen(instr); + trid_string = malloc(8); + assert(session->trid < 999999); /* Sanity check */ + sprintf(trid_string, "%i", session->trid); + len += 1; /* Space before the TrId */ + len += strlen(trid_string); + if ( arglen > 0 ) { + len += arglen; + len += 1; /* Space after the TrID */ + } + if ( newline ) { + len += 2; + } + + if ( session->wbufsize == 0 ) { + + /* No buffer space currently exists. Create it. */ +#ifdef SBWRITE_DEBUG + debug_print("SB %8p: Creating SB write buffer: %i bytes.\n", session, len); +#endif /* SBWRITE_DEBUG */ + assert(session->wbuffer == NULL); + session->wbuffer = malloc(len); + assert(session->wbuffer != NULL); + session->wbufsize = len; + session->woffset = 0; + + } + if ( (session->wbufsize - session->woffset) < len ) { + + /* Write buffer isn't big enough. Make it bigger. */ +#ifdef SBWRITE_DEBUG + debug_print("SB %8p: Extending SB write buffer. Old size=%i (offset %i), Need %i, New size=%i\n", session, session->wbufsize, session->woffset, len, len+session->woffset); +#endif /* SBWRITE_DEBUG */ + session->wbuffer = realloc(session->wbuffer, len + session->woffset); + assert(session->wbuffer != NULL); + session->wbufsize = len + session->woffset; + assert(session->wbufsize < 1024*1024); /* Stop the buffer from getting insane */ + + } + + /* Do the write (to memory). Deliberately verbose... */ + memcpy(session->wbuffer + session->woffset, instr, strlen(instr)); + session->woffset += strlen(instr); + + *(char *)(session->wbuffer + session->woffset) = ' '; + session->woffset += 1; + + memcpy(session->wbuffer + session->woffset, trid_string, strlen(trid_string)); + session->woffset += strlen(trid_string); + free(trid_string); + + if ( arglen > 0 ) { + + *(session->wbuffer + session->woffset) = ' '; + session->woffset += 1; + memcpy(session->wbuffer + session->woffset, args, arglen); + session->woffset += arglen; + + } + + if ( newline ) { + + *(session->wbuffer + session->woffset) = '\r'; + session->woffset += 1; + *(session->wbuffer + session->woffset) = '\n'; + session->woffset += 1; + + } + /* Note the lack of a \0 terminator. It's done using records of the buffer data lengths. */ + + if ( session->wcallback == 0 ) { + session->wcallback = gdk_input_add(session->socket, GDK_INPUT_WRITE, (GdkInputFunction)sbprotocol_writeable, session); + } + + session->trid++; + return session->trid-1; + +} + +/* Send a command to the switchboard, adding a TrID and a newline */ +static int sbprotocol_sendtr(SbSession *session, const char *instr, const char *args) { + return sbprotocol_sendtr_internal(session, instr, args, strlen(args), 1); +} + +/* Send a command to the switchboard, adding a TrID but no newline. */ +int sbprotocol_sendtr_nonewline(SbSession *session, const char *instr, const char *args, ssize_t length) { + return sbprotocol_sendtr_internal(session, instr, args, length, 0); +} + +void sbprotocol_leavesession(SbSession *session) { + sbprotocol_sendtr(session, "OUT", ""); +} + +/* Split a large message up and throw it back at sbprotocol_sendmsg */ +static int sbprotocol_sendmultipacket(SbSession *session, char *contenttype, char *extra_headers, char *body, ssize_t length) { + + char *messageid = routines_guid(); + int parts = (length / MULTIPACKET_SIZE) + 1; + int i; + char *new_extra_headers; + char *packet = malloc(MULTIPACKET_SIZE); + + debug_print("SB %8p: Sending multipacket message (%i parts)\n", session, parts); + if ( parts > 9999 ) { + debug_print("SB %8p: Silly number of packets - aborting.\n", session); + return 0; + } + + /* Message-ID: Chunks: Chunk: */ + new_extra_headers = malloc(strlen(extra_headers) +12+40 +7+4 +6+4 +1); + + for ( i=0; i<parts; i++ ) { + + ssize_t size; + + strcpy(new_extra_headers, extra_headers); + strcat(new_extra_headers, "\r\nMessage-ID: "); + strcat(new_extra_headers, messageid); + + if ( i == 0 ) { + + /* First packet. Modify header to include number of chunks... */ + + char *chunks; + + strcat(new_extra_headers, "\r\nChunks: "); + chunks = malloc(5); + sprintf(chunks, "%i", parts); + strcat(new_extra_headers, chunks); + free(chunks); + + } else { + + /* Not first packet: include chunk ID */ + + char *chunk; + + strcat(new_extra_headers, "\r\nChunk: "); + chunk = malloc(5); + sprintf(chunk, "%i", i); + strcat(new_extra_headers, chunk); + free(chunk); + + } + + size = MULTIPACKET_SIZE; + if ( i == parts-1 ) { + /* Last message */ + size = length % MULTIPACKET_SIZE; + } + memcpy(packet, body+(MULTIPACKET_SIZE*i), size); + + debug_print("SB %8p: Sending packet %i of %i (bytes %i-%i of %i)\n", session, i+1, parts, MULTIPACKET_SIZE*i, size+(MULTIPACKET_SIZE*i), length); + sbprotocol_sendmsg(session, contenttype, new_extra_headers, packet, size); + + } + + return 1; + +} + +static void sbprotocol_flushcache(SbSession *session) { + + CachedMsg *cached = session->cached; + CachedMsg *next; + + while ( cached != NULL ) { + + debug_print("Sending cached message %8p.\n", cached); + sbprotocol_sendtr_nonewline(session, "MSG", cached->message, cached->length); + free(cached->message); + next = cached->next; + free(cached); + session->cached = next; + cached = next; + + } + +} + +static void sbprotocol_sendmsg(SbSession *session, char *contenttype, char *extra_headers, char *body, ssize_t mlength) { + + char *serversend; + char *length_text; + size_t length; + + length = mlength; + if ( extra_headers != NULL ) { + length += (strlen(extra_headers) + 2); + } + length += (35 + strlen(contenttype) + 2); + + /* Check if this needs to be multipacketed or not... */ + if ( length > MAX_MSG_SIZE ) { + sbprotocol_sendmultipacket(session, contenttype, extra_headers, body, mlength); + return; + } + + serversend = malloc(length+15); + + strcpy(serversend, "N "); + length_text = malloc(16); + sprintf(length_text, "%i", length); + strncat(serversend, length_text, 15); + + strcat(serversend, "\r\nMIME-Version: 1.0\r\nContent-Type: "); + strcat(serversend, contenttype); + strcat(serversend, "\r\n"); + if ( extra_headers != NULL ) { + strcat(serversend, extra_headers); + strcat(serversend, "\r\n"); + } + strcat(serversend, "\r\n"); + memcpy(serversend+strlen(serversend), body, mlength); + + if ( session->ready ) { + sbprotocol_sendtr_nonewline(session, "MSG", serversend, length+strlen(length_text)+4); + } else { + + CachedMsg *cached = malloc(sizeof(CachedMsg)); + + cached->message = malloc(length+strlen(length_text)+5); + memcpy(cached->message, serversend, length+strlen(length_text)+5); + + cached->message[length+strlen(length_text)+4] = '\0'; /* NULL-terminate! */ + + cached->length = length+strlen(length_text)+4; + + if ( session->cached == NULL ) { + session->cached = cached; + } else { + CachedMsg *find = session->cached; + while ( find->next != NULL ) { + find = find->next; + } + find->next = cached; + } + + cached->next = NULL; + + debug_print("SB %8p: Cached message for sending later (%8p).\n", session, cached); + + } + + free(length_text); + free(serversend); + +} + +void sbprotocol_sendcaps(SbSession *session) { + + char *caps_body; + + caps_body = malloc(64 + strlen(tuxmessenger_versionstring())); + strcpy(caps_body, "Client-Name: "); + strcat(caps_body, tuxmessenger_versionstring()); + strcat(caps_body, "\r\nChat-Logging: N"); /* Logging not implemented yet. */ + + sbprotocol_sendmsg(session, "text/x-clientcaps", NULL, caps_body, strlen(caps_body)); + free(caps_body); + +} + +void sbprotocol_invite(SbSession *session, const char *username) { + + sbprotocol_sendtr(session, "CAL", username); + +} + +static void sbprotocol_parseline(SbSession *session, char *line) { + + char *token; + + debug_print("SB %8p: Recv '%s'\n", session, line); + assert(session != NULL); + + token = routines_lindex(line, 0); + + if ( strcmp(token, "USR") == 0 ) { + + char *status; + + status = routines_lindex(line, 2); + + if ( strcmp(status, "OK") == 0 ) { + + if ( session->num_users != 1 ) { + /* Not quite inconceivable, but would only happen in a *really* screwed-up case. */ + debug_print("SB %8p: %i users on session record during negotiation!\n", session, session->num_users); + } + + assert(session->users != NULL); + assert(session->users->username != NULL); + debug_print("SB %8p: Inviting %s\n", session, session->users->username); + sbprotocol_invite(session, session->users->username); + if ( session->threeway_intent != NULL ) { + debug_print("SB %8p: Also inviting %s\n", session, session->threeway_intent); + sbprotocol_invite(session, session->threeway_intent); + } + + } else { + + debug_print("SB %8p: Negotiation failed\n", session); + + } + + free(status); + + } + + if ( strcmp(token, "JOI") == 0 ) { + + char *username; + char *friendlyname; + + session->ready = TRUE; + sbprotocol_sendcaps(session); + sbprotocol_flushcache(session); + + username = routines_lindex(line, 1); + friendlyname = routines_lindex(line, 2); + sbsessions_joined(session, username, friendlyname); + free(username); + free(friendlyname); + + } + + if ( strcmp(token, "IRO") == 0 ) { + + char *username; + char *friendlyname; + + username = routines_lindex(line, 4); + friendlyname = routines_lindex(line, 5); + session->ready = TRUE; + sbsessions_joined(session, username, friendlyname); + free(username); + free(friendlyname); + + } + + if ( strcmp(token, "MSG") == 0 ) { + + char *length; + + length = routines_lindex(line, 3); + session->expect_length = atoi(length); + free(length); + + session->msg_source = routines_lindex(line, 1); + + assert(session->expect_length > 0); + session->conmode = SBCONMODE_MSG; + + } + + if ( strcmp(token, "BYE") == 0 ) { + + char *username; + username = routines_lindex(line, 1); + sbsessions_left(session, username); + free(username); + + } + + if ( strcmp(token, "ANS") == 0 ) { + + char *reply; + + reply = routines_lindex(line, 2); + if ( strcmp(reply, "OK") == 0 ) { + session->ready = TRUE; /* This is probably already the case. */ + sbprotocol_sendcaps(session); + } else { + debug_print("SB %8p: ANS reply not OK!\n", session); + } + free(reply); + + } + + if ( strcmp(token, "NAK") == 0 ) { + + /* Difficult to analyse this in detail... */ + if ( session->messagewindow != NULL ) { + if ( !messagewindow_get_last_was_nak(session->messagewindow) ) { + messagewindow_addtext_system(session->messagewindow, "Connection problems at the other end?"); + messagewindow_set_last_was_nak(session->messagewindow, TRUE); + } + } + + } + + if ( strcmp(token, "217") == 0 ) { + + /* Hmmm. */ + if ( !session->ready ) { + debug_print("SB %8p: 217 before READY. Leaving...\n", session); + sbprotocol_leavesession(session); + } + + } + + free(token); + +} + +/* Deal with an incoming user IM */ +static void sbprotocol_parseim(SbSession *session, const char *msg, size_t length) { + + char *says_text; + const char *friendlyname_coded; + char *friendlyname; + SbUser *user; + char *format; + char *colour = NULL; + char *flipcolour; + char *hashcolour = NULL; + char *padcolour; + size_t i; + char *font = NULL; + + if ( session->messagewindow == NULL ) { + messagewindow_mitigate(session); + } + + friendlyname_coded = contactlist_friendlyname(session->msg_source); + friendlyname = routines_urldecode(friendlyname_coded); + says_text = malloc(strlen(friendlyname) + 7); + strcpy(says_text, friendlyname); + free(friendlyname); + strcat(says_text, " says:"); + + if ( session->messagewindow->ofontoverride ) { + hashcolour = strdup(session->messagewindow->ocolour_string); + font = session->messagewindow->ofont; + } else { + format = mime_getfield(msg, "X-MMS-IM-Format"); + debug_print("SB %8p: Got format: '%s'\n", session, format); + if ( strlen(format) > 0 ) { + + colour = strdup("000000"); + for ( i=0; i<strlen(format); i++ ) { + + if ( (format[i]=='C') && (format[i+1]=='O') && (format[i+2]=='=') ) { + + char c; + size_t j = 0; + + free(colour); + colour = malloc(7); + c = format[i+3+j]; + while ( (j<7) && (c != ';') && (c != ' ') && (i+3+j < strlen(format)-1) ) { + colour[j] = c; + j++; + c = format[i+3+j]; + } + colour[j] = '\0'; + break; + + } + + } + free(format); + assert(colour != NULL); + debug_print("SB %8p: Got colour '%s'\n", session, colour); + + if ( strlen(colour) < 6 ) { + + size_t len = strlen(colour); + padcolour = strdup("000000"); + padcolour[6-len] = '\0'; + strcat(padcolour, colour); + free(colour); + colour = padcolour; + debug_print("SB %8p: Padded: '%s'\n", session, colour); + + } + + flipcolour = routines_flipcolour(colour); + hashcolour = malloc(8); + strcpy(hashcolour, "#"); + strncat(hashcolour, flipcolour, 6); + hashcolour[7] = '\0'; + free(flipcolour); + free(colour); + debug_print("SB %8p: Flipped and hashed: '%s'\n", session, hashcolour); + } + + } + + messagewindow_addtext_system(session->messagewindow, says_text); + free(says_text); + messagewindow_addtext_user_nonewline(session->messagewindow, "\n", 1, NULL, NULL); + messagewindow_addtext_user_nonewline(session->messagewindow, mime_getbody(msg), length-mime_headerlength(msg), hashcolour, font); + free(hashcolour); + + user = sbsessions_find_username(session, session->msg_source); + if ( user == NULL ) { + debug_print("SB %8p: IM source user not found on switchboard!\n", session); + return; + } + messagewindow_stoptypingbyusername(session->messagewindow, session->msg_source); + +} + +/* Handle an "MSMsgsControl" */ +static void sbprotocol_parsemsgcontrol(SbSession *session, const char *msg) { + + char *typinguser; + + /* Check for a typing user.*/ + typinguser = mime_getfield(msg, "TypingUser"); + if ( strlen(typinguser) > 0 ) { + + if ( session->messagewindow == NULL ) { + messagewindow_mitigate(session); + } + messagewindow_starttyping(session->messagewindow, typinguser); + + } else { + debug_print("SB %8p: Unrecognised TypingUser control!\n", session); + } + + free(typinguser); + +} + +static void sbprotocol_handleclientcaps(SbSession *session, const char *msg) { + + debug_print("SB %8p: clientcaps: '%s'\n", session, msg); + +} + +static void sbprotocol_handledatacast(SbSession *session, const char *username, const char *data) { + + int id; + char *id_string = mime_getfield(data, "ID"); + + if ( id_string == NULL ) { + debug_print("SB %8p: Got datacast, couldn't find ID.\n", session); + return; + } + + id = atoi(id_string); + free(id_string); + + debug_print("SB %8p: Got datacast: ID %i.\n", session, id); + + if ( id == 1 ) { + + const char *friendlyname; + char *friendlyname_decoded; + char *text; + + friendlyname = contactlist_friendlyname(username); + friendlyname_decoded = routines_urldecode(friendlyname); + + text = malloc(strlen(friendlyname_decoded)+9); + strcpy(text, friendlyname_decoded); + strcat(text, " nudges."); + free(friendlyname_decoded); + + if ( session->messagewindow == NULL ) { + messagewindow_mitigate(session); + } + messagewindow_addtext_system(session->messagewindow, text); + free(text); + + } + +} + + +static void sbprotocol_handleink_isf(SbSession *session, const char *username, const char *ink) { + + debug_print("SB %8p: Got ISF: '%s'\n", session, ink); + +} + +static MultiPacketMsg *sbprotocol_findmultipacket(SbSession *session, const char *message_id) { + + MultiPacketMsg *msg = session->multipackets; + + while ( msg != NULL ) { + if ( strcmp(msg->message_id, message_id) == 0 ) { + return msg; + } + msg = msg->next; + } + + return NULL; + +} + +/* Currently, this assumes the packets will arrive in the correct order. */ +static int sbprotocol_checkmultipacket(SbSession *session, const char *msgdata, ssize_t msgdatalength) { + + char *chunks; + char *chunk; + + chunks = mime_getfield(msgdata, "Chunks"); + chunk = mime_getfield(msgdata, "Chunk"); + if ( (strlen(chunks)>0) || (strlen(chunk)>0) ) { + + /* This is part of a multipacketed message. */ + MultiPacketMsg *msg; + char *messageid; + + debug_print("SB %8p: Got a part of a multipacketed message.\n", session); + + messageid = mime_getfield(msgdata, "Message-ID"); + if ( strlen(messageid) == 0 ) { + debug_print("SB %8p: No Message-ID!\n", session); + free(messageid); + free(chunks); + free(chunk); + return 0; + } + + if ( (atoi(chunk) == 0) && (atoi(chunks) == 0) ) { + debug_print("SB %8p: Couldn't find either a sensible 'Chunk' or 'Chunks' value!\n", session); + free(messageid); + free(chunks); + free(chunk); + return 0; + } + + msg = sbprotocol_findmultipacket(session, messageid); + if ( msg == NULL ) { + + /* No previous record of this series, so it had *better* contain the number of chunks to expect... */ + if ( atoi(chunks) == 0 ) { + debug_print("SB %8p: Couldn't find the length of the multipacket series...\n", session); + free(messageid); + free(chunks); + free(chunk); + return 0; + } + + msg = malloc(sizeof(MultiPacketMsg)); + msg->chunks_expected = atoi(chunks); + msg->chunks_received = 1; + msg->data = mime_removeheader(msgdata, msgdatalength, "Chunks"); + msg->datalength = msgdatalength - strlen(chunks) - strlen("Chunks: rn"); + msg->message_id = messageid; + + /* Link it into the list for this session. */ + msg->next = session->multipackets; + msg->previous = NULL; + if ( session->multipackets != NULL ) { + session->multipackets->previous = msg; + } + session->multipackets = msg; + + } else { + + size_t body_size; + const char *body; + + /* Known series, so this had *better* contain a chunk ID... */ + if ( atoi(chunk) == 0 ) { + debug_print("SB %8p: Couldn't find the multipacket chunk ID...\n", session); + return 0; + } + + body = mime_getbody(msgdata); + body_size = msgdatalength - mime_headerlength(msgdata); + + msg->data = realloc(msg->data, body_size+(msg->datalength)); + memcpy((msg->data)+msg->datalength, body, body_size); + msg->datalength += body_size; + + msg->chunks_received++; + if ( msg->chunks_received == msg->chunks_expected ) { + + debug_print("SB %8p: Got all parts of a multipacket message...\n", session); + + /* Extend buffer by one byte and fix \0 terminator. */ + msg->data = realloc(msg->data, (msg->datalength)+1); + msg->data[msg->datalength] = '\0'; + + sbprotocol_parsemsg(session, msg->data, msg->datalength); + + /* Remove from list */ + if ( msg->previous != NULL ) { + msg->previous->next = msg->next; + } else { + session->multipackets = msg->next; + } + if ( msg->next != NULL ) { + msg->next->previous = msg->previous; + } + + free(msg->data); + free(msg); + + return 1; /* It's already been handled. */ + + } + + } + + } else { + + free(chunks); + free(chunk); + return 0; + + } + + free(chunks); + free(chunk); + + return 1; /* Cause sbprotocol_parsemsg to ignore this one for now. */ + +} + +/* Decide what to do with a received message. + Also called by msnp2p.c to throw us a MIME message sent via MSNP2P (Yuk!). */ +void sbprotocol_parsemsg(SbSession *session, const char *msg, ssize_t msglength) { + + char *content_type; + + debug_print("SB %8p: MSG (%i bytes): '%s'\n", session, msglength, msg); + + if ( sbprotocol_checkmultipacket(session, msg, msglength) ) { + /* Soon, my pretties... */ + return; + } + + content_type = mime_getfield(msg, "Content-Type"); + + if ( strstr(content_type, "text/plain") != NULL ) { + sbprotocol_parseim(session, msg, msglength); + } else if ( strstr(content_type, "text/x-msmsgscontrol") != NULL ) { + sbprotocol_parsemsgcontrol(session, msg); + } else if ( strstr(content_type, "application/x-msnmsgrp2p") != NULL ) { + msnp2p_parsemsg(session, session->msg_source, msg, session->expect_length); + } else if ( strstr(content_type, "text/x-msmsgsinvite") != NULL ) { + msninvite_parsemsg(session, msg); + } else if ( strstr(content_type, "text/x-clientcaps") != NULL ) { + sbprotocol_handleclientcaps(session, msg); + } else if ( strstr(content_type, "application/x-ms-ink") != NULL ) { + sbprotocol_handleink_isf(session, session->msg_source, mime_getbody(msg)); + } else if ( strstr(content_type, "text/x-msnmsgr-datacast") != NULL ) { + sbprotocol_handledatacast(session, session->msg_source, mime_getbody(msg)); + } else { + debug_print("SB %8p: No handler for this MSG (%s).\n", session, content_type); + } + + free(content_type); + free(session->msg_source); /* Can safely be freed even if routines_lindex didn't find anything earlier. */ + +} + +/* Internally called when data arrives from the SB */ +static void sbprotocol_readable(SbSession *session) { + + unsigned int i = 0; /* =0 keeps compiler happy */ + ssize_t rlen; + int no_string = 0; + + assert(session->rbufsize - session->roffset > 0); + rlen = read(session->socket, session->rbuffer + session->roffset, session->rbufsize - session->roffset); + if ( rlen > 0 ) { + session->roffset += rlen; + } + assert(session->roffset <= session->rbufsize); /* This would indicate a buffer overrun */ + + /* First, check this isn't a disconnection. rlen=0 is EOF, rlen=-1 is an error. */ + if ( (rlen == 0) || (rlen == -1) ) { + + int closeval; + + closeval = close(session->socket); + + if ( closeval != 0 ) { + debug_print("SB %8p: Couldn't close socket after read error.\n", session); + } + + sbprotocol_disconnected(session); + return; + + } + + while ( (!no_string) && (session->roffset > 0) ) { + + int block_ready = 0; + + /* See if there's a full "block" in the buffer yet */ + if ( session->conmode == SBCONMODE_LINE ) { + + for ( i=0; i<session->roffset-1; i++ ) { /* Means the last value looked at is roffset-2 */ + if ( (session->rbuffer[i] == '\r') && (session->rbuffer[i+1] == '\n') ) { + block_ready = 1; + break; + } + } + + } else if ( session->conmode == SBCONMODE_MSG ) { + + if ( session->roffset >= session->expect_length ) { + i = session->expect_length - 2; + block_ready = 1; + } + + } else { + /* "Never happens". */ + debug_print("SB %8p: Can't determine end of block for SbConMode %i!\n", session, session->conmode); + } + + if ( block_ready == 1 ) { + + char *block_buffer = NULL; + unsigned int new_rbufsize; + unsigned int endbit_length; + SbConMode next_mode = session->conmode; + + if ( session->conmode == SBCONMODE_LINE ) { + assert(session->rbuffer[i] == '\r'); + assert(session->rbuffer[i+1] == '\n'); + } + + + if ( session->conmode == SBCONMODE_LINE ) { + + block_buffer = malloc(i+1); + memcpy(block_buffer, session->rbuffer, i); + block_buffer[i] = '\0'; + + sbprotocol_parseline(session, block_buffer); + + /* Check to see if sbprotocol_parseline changed the mode. If so, make sure + it stays changed. */ + if ( session->conmode != SBCONMODE_LINE ) { + next_mode = session->conmode; + } + + } else if ( session->conmode == SBCONMODE_MSG ) { + + block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */ + memcpy(block_buffer, session->rbuffer, i+2); /* Copy it in */ + block_buffer[i+2] = '\0'; /* Terminate */ + + sbprotocol_parsemsg(session, block_buffer, i+2); + next_mode = SBCONMODE_LINE; + + } else { + debug_print("SB %8p: No handler for SbConMode %i !\n", session, session->conmode); + } + + free(block_buffer); + + /* Now the block's been parsed, it should be forgotten about */ + if ( session->conmode == SBCONMODE_LINE ) { + assert(session->rbuffer[i+1] == '\n'); /* Should still be the case. Paranoid. */ + } + endbit_length = i+2; + memmove(session->rbuffer, session->rbuffer + endbit_length, session->rbufsize - endbit_length); + session->roffset = session->roffset - endbit_length; /* Subtract the number of bytes removed */ + new_rbufsize = session->rbufsize - endbit_length; + if ( new_rbufsize == 0 ) { + new_rbufsize = 32; + } + session->rbuffer = realloc(session->rbuffer, new_rbufsize); + session->rbufsize = new_rbufsize; + session->conmode = next_mode; + + } else { + + if ( session->roffset == session->rbufsize ) { + + /* More buffer space is needed */ + session->rbuffer = realloc(session->rbuffer, session->rbufsize + 32); + session->rbufsize = session->rbufsize + 32; + /* The new space gets used at the next read, shortly... */ + + } + no_string = 1; + + } + + } + +} + +/* Internally called when the socket is connected */ +static void sbprotocol_ready(SbSession *session) { + + /* Remove the "writeable" callback, and add a "readable" callback */ + gdk_input_remove(session->wcallback); + session->wcallback = 0; + session->rcallback = gdk_input_add(session->socket, GDK_INPUT_READ, (GdkInputFunction)sbprotocol_readable, session); + + /* Begin the handshake */ + if ( session->source == SESSION_SOURCE_REMOTE ) { + + char *args; + const char *username; + + username = options_username(); + args = malloc(strlen(username) + strlen(session->cki_key) + strlen(session->sessionid) +3); + + assert(username != NULL); + assert(args != NULL); + assert(session->cki_key != NULL); + assert(session->sessionid != NULL); + + strcpy(args, username); + strcat(args, " "); + strcat(args, session->cki_key); + strcat(args, " "); + strcat(args, session->sessionid); + free(session->cki_key); + free(session->sessionid); + + sbprotocol_sendtr(session, "ANS", args); + free(args); + + } else { + + char *args; + const char *username; + + username = options_username(); + args = malloc(strlen(username) + strlen(session->cki_key) + 2); + + assert(username != NULL); + assert(args != NULL); + assert(session->cki_key != NULL); + + strcpy(args, username); + strcat(args, " "); + strcat(args, session->cki_key); + free(session->cki_key); + + sbprotocol_sendtr(session, "USR", args); + free(args); + + } + +} + +static void sbprotocol_connect(SbSession *session, const char *hostname, unsigned short int port) { + + struct sockaddr_in sa_desc; + struct hostent *server; + unsigned int sockopts; + int conn_error; + + assert(hostname != NULL); + if ( port == 0 ) { + port = DEFAULT_SB_PORT; + debug_print("SB %8p: Fixed obviously wrong port number to %i\n", session, port); + } + debug_print("SB %8p: Connecting to '%s' port %i\n", session, hostname, port); + + /* Create and configure the socket (but don't connect it yet) */ + session->socket = socket(PF_INET, SOCK_STREAM, 0); + if ( session->socket == -1 ) { + error_report("Couldn't create switchboard socket"); + return; + } + sockopts = fcntl(session->socket, F_GETFL); + fcntl(session->socket, F_SETFL, sockopts | O_NONBLOCK); + + /* Resolve the server name */ + server = gethostbyname(hostname); + if ( server == NULL ) { + error_report("Couldn't resolve switchboard hostname."); + return; + } + + memset(&sa_desc, 0, sizeof(sa_desc)); + sa_desc.sin_family = AF_INET; + memcpy(&sa_desc.sin_addr.s_addr, server->h_addr, server->h_length); + sa_desc.sin_port = htons(port); + + session->rbuffer = malloc(256); + assert(session->rbuffer != NULL); + session->rbufsize = 256; + session->roffset = 0; + session->wbufsize = 0; + session->wbuffer = NULL; + session->woffset = 0; + + conn_error = connect(session->socket, (struct sockaddr *)&sa_desc, sizeof(sa_desc))<0; + if ( (conn_error < 0) && (conn_error != EINPROGRESS) ) { + + debug_print("SB %8p: Couldn't connect to server", session); + error_report("Couldn't connect to switchboard."); + return; + + } + + /* Call back when the socket is connected */ + session->wcallback = gdk_input_add(session->socket, GDK_INPUT_WRITE, (GdkInputFunction)sbprotocol_ready, session); + + session->trid = 1; + session->conmode = SBCONMODE_LINE; + session->expect_length = 0; + +} + +/* Called from src/msnprotocol.c when the XFR SB information is ready. */ +void sbprotocol_initiate_local(unsigned int trid, const char *hostname, unsigned int port, const char *cki_key) { + + SbSession *session; + + session = sbsessions_find_trid(trid); + if ( session == NULL ) { + debug_print("SB %8p: Couldn't find session! Aborting.\n", NULL); + return; + } + + assert(session->users != NULL); + assert(session->users->username != NULL); + debug_print("SB %8p: Matched to user %s\n", session, session->users->username); + session->neg_trid = 0; /* Stop this session from being re-matched. */ + + session->cki_key = strdup(cki_key); + sbprotocol_connect(session, hostname, port); + +} + +void sbprotocol_initiate_remote(SbSession *session, const char *switchboardaddress, const char *sessionid, const char *authchallenge) { + + char *hostname; + unsigned int port; + + session->cki_key = strdup(authchallenge); + session->sessionid = strdup(sessionid); + + hostname = routines_hostname(switchboardaddress); + port = routines_port(switchboardaddress); + + sbprotocol_connect(session, hostname, port); + + free(hostname); + +} + +void sbprotocol_send(SbSession *session, char *textblock, int length) { + + if ( strlen(textblock) != 0 ) { + + char *user_header; + + session->am_typing = 0; + if ( session->am_typing_callback != 0 ) { + gtk_timeout_remove(session->am_typing_callback); + } + + /* Send "User-Agent" and text style string with normal messages. */ + user_header = malloc(256 + strlen(tuxmessenger_versionstring())); + strcpy(user_header, "User-Agent: "); + strcat(user_header, tuxmessenger_versionstring()); + + if ( session->messagewindow->localfont != NULL ) { + strcat(user_header, "\r\nX-MMS-IM-Format: "); + strcat(user_header, session->messagewindow->localfont); + if ( session->messagewindow->localcolour_string != NULL ) { + strcat(user_header, "; CO="); + strcat(user_header, session->messagewindow->localcolour_string); + } + } else { + if ( session->messagewindow->localcolour_string != NULL ) { + strcat(user_header, "\r\nX-MMS-IM-Format: CO="); + strcat(user_header, session->messagewindow->localcolour_string); + } + } + + sbprotocol_sendmsg(session, "text/plain; charset=UTF-8", user_header, textblock, strlen(textblock)); + + free(user_header); + return; + + } else { + + debug_print("SB %8p: Not sending empty message.\n", session); + return; + + } + +} + +void sbprotocol_sendnudge(SbSession *session) { + sbprotocol_sendmsg(session, "text/x-msnmsgr-datacast", NULL, "ID: 1\r\n\r\n", 9); +} + +void sbprotocol_sendtypingcontrol(SbSession *session) { + + char *control_header; + + if ( session->ready != FALSE ) { + + control_header = malloc(strlen(options_username())+16); + strcpy(control_header, "TypingUser: "); + strcat(control_header, options_username()); + sbprotocol_sendmsg(session, "text/x-msmsgscontrol", control_header, "", 0); + free(control_header); + + } else { + + debug_print("SB %8p: Not sending TypingUser control before session is ready.\n", session); + + } + +} + +void sbprotocol_close(SbSession *session) { + close(session->socket); +} diff --git a/src/sbprotocol.h b/src/sbprotocol.h new file mode 100644 index 0000000..5fb8d69 --- /dev/null +++ b/src/sbprotocol.h @@ -0,0 +1,46 @@ +/* + * sbprotocol.h + * + * SB protocol handling + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef SBPROTOCOL_H +#define SBPROTOCOL_H + +#include "sbsessions.h" + +/* Session initiation hooks. */ +extern void sbprotocol_initiate_local(unsigned int trid, const char *hostname, unsigned int port, const char *cki_key); +extern void sbprotocol_initiate_remote(SbSession *session, const char *switchboardaddress, const char *sessionid, const char *authchallenge); + +/* High-level protocol operations. */ +extern void sbprotocol_invite(SbSession *session, const char *username); +extern void sbprotocol_leavesession(SbSession *session); +extern void sbprotocol_sendtypingcontrol(SbSession *session); +extern void sbprotocol_sendnudge(SbSession *session); + +/* Low-level protocol operations. */ +extern void sbprotocol_send(SbSession *session, char *textblock, int length); +extern int sbprotocol_sendtr_nonewline(SbSession *session, const char *instr, const char *args, ssize_t length); +extern void sbprotocol_parsemsg(SbSession *session, const char *msg, ssize_t msglength); +extern void sbprotocol_close(SbSession *session); + +#endif /* SBPROTOCOL_H */ diff --git a/src/sbsessions.c b/src/sbsessions.c new file mode 100644 index 0000000..0ce353c --- /dev/null +++ b/src/sbsessions.c @@ -0,0 +1,597 @@ +/* + * sbsessions.c + * + * SB session (=>IM window) management (but not the protocol nor UI) + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "debug.h" +#include "messagewindow.h" +#include "sbsessions.h" +#include "sbprotocol.h" +#include "msnprotocol.h" +#include "contactlist.h" +#include "msngenerics.h" +#include "options.h" +#include "msnp2p.h" +#include "error.h" +#include "routines.h" +#include "fonttrans.h" + +SbSession *sessions = NULL; + +static SbSession *sbsessions_lastsession() { + + SbSession *session = sessions; + + while ( session ) { + + assert(session != NULL); + + if ( session->next == NULL ) { + return session; + } else { + session = session->next; + } + + } + + return NULL; /* If there were no sessions at all. */ + +} + +static void sbsessions_inituser(SbUser *new_user) { + +} + +static int sbsessions_ready_callback(SbSession *session) { + + if ( !session->ready ) { + debug_print("SS %8p: Not ready after 60 seconds - deleting.\n", session); + sbsessions_destroy(session); + return FALSE; + } + session->ready_timeout = 0; + + return FALSE; /* Don't repeat */ + +} + + +static SbSession *sbsessions_new(const char *username, SbSessionSource source) { + + SbSession *new_session; + SbSession *last_session; + + new_session = malloc(sizeof(SbSession)); + new_session->users = malloc(sizeof(SbUser)); + assert(new_session->users != NULL); + + new_session->messagewindow = NULL; + + new_session->users->next = NULL; + new_session->users->username = strdup(username); + sbsessions_inituser(new_session->users); + + new_session->ready = FALSE; + new_session->num_users = 1; + new_session->rbufsize = 0; + new_session->wbufsize = 0; + new_session->rbuffer = NULL; + new_session->wbuffer = NULL; + new_session->roffset = 0; + new_session->roffset = 0; + new_session->am_typing = 0; + new_session->am_typing_callback = 0; + new_session->threeway_intent = NULL; + new_session->multipackets = NULL; + new_session->trid = 1; /* Reset in sbprotocol_connect */ + new_session->cached = NULL; + + new_session->source = source; + new_session->rcallback = 0; + new_session->wcallback = 0; + + /* 60 seconds to get ready. */ + new_session->ready_timeout = gtk_timeout_add(60000, (GtkFunction)sbsessions_ready_callback, new_session); + + /* Link it into the list. */ + last_session = sbsessions_lastsession(); + if ( last_session != NULL ) { + assert(last_session->next == NULL); + last_session->next = new_session; + } else { + sessions = new_session; + } + new_session->next = NULL; + + return new_session; + +} + +SbSession *sbsessions_create_local(const char *username) { + + SbSession *session = sbsessions_new(username, SESSION_SOURCE_LOCAL); + + session->neg_trid = msnprotocol_initiatesb(); + debug_print("SS %8p: New local session for user %s.\n", session, username); + + return session; + +} + +/* Called when the user clicks on a name in the contact list. */ +SbSession *sbsessions_create_remote(char *username, char *switchboardaddress, char *sessionid, char *authchallenge) { + + SbSession *session = sbsessions_new(username, SESSION_SOURCE_REMOTE); + + session->neg_trid = 0; + sbprotocol_initiate_remote(session, switchboardaddress, sessionid, authchallenge); + debug_print("SS %8p: New remote session for user %s.\n", session, username); + + return session; + +} + +/* Create a three-way session in "one go". */ +SbSession *sbsessions_create_threeway(char *username1, char *username2) { + + SbSession *session = sbsessions_new(username1, SESSION_SOURCE_LOCAL); + + session->threeway_intent = strdup(username2); + session->neg_trid = msnprotocol_initiatesb(); + debug_print("SS %8p: New local session for user %s. Intending to invite %s too.\n", session, username1, username2); + + return session; + +} + +void sbsessions_plug(SbSession *session, MessageWindow *messagewindow) { + session->messagewindow = messagewindow; +} + +/* Unplug any SB sessions which are plugged into a given IM window */ +void sbsessions_unplug(MessageWindow *messagewindow) { + + SbSession *session = sessions; + + while ( session != NULL ) { + if ( session->messagewindow == messagewindow ) { + session->messagewindow = NULL; + } + session = session->next; + } + +} + +/* Find a headless session with this username. */ +SbSession *sbsessions_find_headless(const char *username) { + + SbSession *session = sessions; + + while ( session != NULL ) { + if ( (session->num_users == 1) && (strcmp(username, session->users->username) == 0) && (session->messagewindow == NULL) ) { + return session; + } + session = session->next; + } + + return NULL; + +} + +/* Find a session with just this user in, and noone about to randomly join (hopefully...) */ +SbSession *sbsessions_find_single_safe(const char *username) { + + SbSession *session = sessions; + + while ( session != NULL ) { + if ( (session->num_users == 1) && (strcmp(username, session->users->username) == 0) ) { + if ( session->threeway_intent == NULL ) { + return session; + } + } + session = session->next; + } + + return NULL; + +} + + +/* SbUsers are unique to a particular SbSession. Find the parent SbSession + given the SbUser. Could just keep a note of the parent record in the + SbUser record, but sbsessions_stoptyping needs to know if the SbUser + is still attached to (any) SbSession. */ +SbSession *sbsessions_find_user(SbUser *target_user) { + + SbSession *session; + + session = sessions; + while ( session != NULL ) { + SbUser *user = session->users; + while ( user != NULL ) { + if ( user == target_user ) { + return session; + } + user = user->next; + } + session = session->next; + } + + debug_print("SS %8p: Couldn't find user in all sessions.\n", NULL); + return NULL; + +} + +/* Return a session's record given "TrID". */ +SbSession *sbsessions_find_trid(unsigned int trid) { + + SbSession *session; + + session = sessions; + while ( session != NULL ) { + + assert(session != NULL); + + if ( session->neg_trid == trid ) { + return session; + } else { + session = session->next; + } + + } + + debug_print("SS %8p: Failed to find session negotiated with TrId %i\n", NULL, trid); + return NULL; + +} + +SbUser *sbsessions_find_username(SbSession *session, const char *username) { + + SbUser *user; + + assert(session != NULL); + assert(session->users != NULL); + + user = session->users; + while ( user != NULL ) { + + assert(user != NULL); + + /* Case-insensitive here. Username may have different case depending + on its source, since the servers seem to change usernames to + lower case but clients might not in (e.g.) TypingUser controls. */ + if ( strcasecmp(user->username, username) == 0 ) { + return user; + } else { + user = user->next; + } + + } + + return NULL; + +} + +/* Locate a user on a given SB session from their DP SHA1D field. */ +SbUser *sbsessions_find_dpsha1d(SbSession *session, char *sha1d) { + + SbUser *user; + + assert(session != NULL); + assert(session->users != NULL); + + user = session->users; + while ( user != NULL ) { + + assert(user != NULL); + + if ( strcmp(user->dpsha1d, sha1d) == 0 ) { + return user; + } else { + user = user->next; + } + + } + + return NULL; + +} + +void sbsessions_destroy(SbSession *session) { + + SbSession *prev_session; + SbUser *user; + CachedMsg *cached; + + /* Unplug the session and close the socket. */ + messagewindow_unplug(session); + sbprotocol_close(session); + + /* Remove the session from the list. */ + prev_session = sessions; + if ( prev_session != session ) { + while ( prev_session != NULL ) { + assert(prev_session != NULL); + if ( prev_session->next == session ) { + break; + } else { + prev_session = prev_session->next; + } + } + assert(prev_session->next == session); + /* Link it out of the list. */ + prev_session->next = session->next; + } else { + /* This session was the first on the list. */ + assert(sessions == session); /* Can't fail... */ + sessions = session->next; /* Which may be NULL if the list is now empty. */ + } + + /* Drop all MSNP2P sessions in this SB session. */ + msnp2p_abortall(session); + + /* Drop any cached messages, and inform the user. */ + cached = session->cached; + while ( cached != NULL ) { + + CachedMsg *next = cached->next; + if ( !session->messagewindow ) { + messagewindow_mitigate(session); + } + messagewindow_reportdropped(session->messagewindow, cached->message, cached->length); + free(cached->message); + free(cached); + cached = next; + messagewindow_unplug(session); /* This session doesn't exist, remember? */ + + } + + /* Remove all the users. */ + user = session->users; + while ( user != NULL ) { + + SbUser *next_user = user->next; + free(user->username); + free(user); + user = next_user; + + } + + /* Wind up the low-level business. */ + if ( session->rcallback != 0 ) { + gdk_input_remove(session->rcallback); + } + session->rcallback = 0; + if ( session->wcallback != 0 ) { + gdk_input_remove(session->wcallback); + } + session->wcallback = 0; + + if ( session->ready_timeout != 0 ) { + gtk_timeout_remove(session->ready_timeout); + } + session->ready_timeout = 0; + + if ( session->am_typing_callback != 0 ) { + gtk_timeout_remove(session->am_typing_callback); + session->am_typing = 0; + session->am_typing_callback = 0; + } + + if ( session->rbuffer != NULL ) { + free(session->rbuffer); + } + if ( session->wbuffer != NULL ) { + free(session->wbuffer); + } + + free(session); + +} + +SbUser *sbsessions_lastuser(SbSession *session) { + + SbUser *user; + + user = session->users; + while ( user != NULL ) { + + assert(user != NULL); + + if ( user->next == NULL ) { + return user; + } else { + user = user->next; + } + + } + + debug_print("SS %8p: Reached end of sbsessions_lastuser! This doesn't happen!\n", session); + return NULL; + +} + +static SbUser *sbsessions_addnewuser(SbSession *session, char *username) { + + SbUser *new_user; + SbUser *previous_user; + + new_user = malloc(sizeof(SbUser)); + assert(new_user != NULL); + new_user->next = NULL; + new_user->username = strdup(username); + sbsessions_inituser(new_user); + + previous_user = sbsessions_lastuser(session); + assert(previous_user->next == NULL); + previous_user->next = new_user; + + session->num_users++; + + return new_user; + +} + +/* Deal with any user joining a session (whether by JOI or IRO). */ +void sbsessions_joined(SbSession *session, char *username, char *friendlyname) { + + SbUser *user; + + /* "friendlyname" is from the JOI message. This user might not be in any of the + contact lists. Now would be a good time to check, and if not, create a + temporary record for them. */ + if ( contactlist_friendlyname(username) == NULL ) { + debug_print("SS %8p: Creating temporary user record for '%s'/'%s'\n", session, username, friendlyname); + contactlist_tldetails(CONTACT_SOURCE_RNG, username, friendlyname, ONLINE_NLN); + } + + user = sbsessions_find_username(session, username); + if ( user == NULL ) { + + /* User is new - probably got invited by someone else */ + debug_print("SS %8p: Couldn't find user %s in SB record - adding them.\n", session, username); + + user = sbsessions_addnewuser(session, username); + + } + + if ( session->messagewindow != NULL ) { + messagewindow_joined(session->messagewindow, username); + } + +} + +/* Deal with any user leaving a session. */ +void sbsessions_left(SbSession *session, char *username) { + + SbUser *user; + + user = sbsessions_find_username(session, username); + if ( user == NULL ) { + debug_print("SS %8p: Couldn't find leaving user %s in SB record!\n", session, username); + return; + } + + if ( session->num_users == 1 ) { + sbprotocol_leavesession(session); + } else { + + /* "Normal" situation. Remove user record, their DP and status bar. */ + + SbUser *find_user; + + session->num_users--; + if ( session->messagewindow != NULL ) { + messagewindow_removeuser(session->messagewindow, username); + } + + /* Find the preceding user in the session and link this user out. */ + find_user = session->users; + if ( find_user == user ) { + /* User was the first in the list. */ + session->users = user->next; + } else { + + while ( find_user != NULL ) { + + assert(find_user != NULL); + + if ( find_user->next == user ) { + find_user->next = user->next; + break; + } else { + find_user = find_user->next; + } + + } + + } + free(user->username); + free(user); + + } + +} + +static int sbsessions_amtyping_callback(SbSession *session) { + + session->am_typing = 0; + session->am_typing_callback = 0; + + return FALSE; /* Don't repeat */ + +} + +void sbsessions_am_typing(SbSession *session) { + + if ( !session->ready ) { + debug_print("SS %8p: Not ready - not sending TypingUser.\n", session); + return; + } + + /* Calculate timeout then send TypingUser control. */ + if ( session->am_typing == 0 ) { + + sbprotocol_sendtypingcontrol(session); + session->am_typing = 1; + session->am_typing_callback = gtk_timeout_add(5000, (GtkFunction)sbsessions_amtyping_callback, session); + + } + +} + +/* Check a session is ready for I/O. */ +int sbsessions_sessionready(SbSession *session) { + + if ( session == NULL ) { + return FALSE; + } + + return session->ready; + +} + +/* Delete all sessions. */ +void sbsessions_destroy_all() { + + SbSession *session; + SbSession *next; + + debug_print("SB %8p: destroying all sessions.\n", NULL); + + session = sessions; + while ( session != NULL ) { + next = session->next; + sbsessions_destroy(session); + session = next; + } + +} diff --git a/src/sbsessions.h b/src/sbsessions.h new file mode 100644 index 0000000..4df1507 --- /dev/null +++ b/src/sbsessions.h @@ -0,0 +1,140 @@ +/* + * sbsessions.h + * + * SB session management (but not the protocol nor UI) + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef SBSESSIONS_H +#define SBSESSIONS_H + +#include <gtk/gtk.h> + +typedef enum { + SESSION_SOURCE_LOCAL, /* Session was initiated by the user clicking on a contact. */ + SESSION_SOURCE_REMOTE /* Session was initiated by an RNG message from Outside. */ +} SbSessionSource; + +typedef enum { + SBCONMODE_LINE, /* Normal, line-based, communication. */ + SBCONMODE_MSG /* A message is coming through. */ +} SbConMode; + +typedef struct _sbuser { /* No data is here that's already held by src/contactlist.c, except the username. */ + + struct _sbuser *next; + + char *username; + + char *dpsha1d; /* SHA1D field from their picture as it appears in the IM window. */ + +} SbUser; + +/* Data structure to hold information while reconstructing a multipacketed message. */ +typedef struct _multipacketmsg { + + struct _multipacketmsg *next; + struct _multipacketmsg *previous; + + char *message_id; + int chunks_expected; + int chunks_received; + char *data; + size_t datalength; + +} MultiPacketMsg; + +typedef struct _cachedmsg { + + struct _cachedmsg *next; + char *message; + size_t length; + +} CachedMsg; + +typedef struct _sbsession { + + struct _sbsession *next; + + /* List of Users */ + SbUser *users; /* List of people in the session. */ + unsigned int num_users; /* Number of remote users in this session. Session destroyed if this = zero. */ + + /* User interface stuff */ + struct _messagewindow *messagewindow; + + /* High-level protocol stuff */ + int ready; /* Non-zero if session is up and running. */ + gint ready_timeout; /* Callback tag to timeout session if it doesn't connect. */ + unsigned int neg_trid; /* TrID used in negotiating this session. */ + unsigned int trid; /* TrID used in communicating on the SB (don't confuse with neg_trid) */ + int socket; /* Socket to the switchboard. */ + SbSessionSource source; /* Where the session was started from. */ + char *cki_key; /* Authentication data for negotiating the session. */ + char *sessionid; /* SessionID (part of authentication to the SB) */ + int first_event; /* Non-zero if message window currently contains no text */ + char *msg_source; /* Who the MSG currently being dealt with came from */ + int am_typing; /* Non-zero if the local user is currently "thought to be typing". */ + int am_typing_callback; /* Callback tag for the end of each typing period for the local user. */ + char *threeway_intent; /* Extra user to invite when ready (if any). */ + MultiPacketMsg *multipackets; /* Linked list of multipacketing records. */ + CachedMsg *cached; /* Cached messages to send when session is ready. */ + + /* Low-level protocol stuff */ + int wcallback; /* Writeable callback */ + char *wbuffer; /* Write buffer */ + unsigned int wbufsize; /* Write buffer size */ + unsigned int woffset; /* Write buffer position */ + int rcallback; /* Readable callback */ + char *rbuffer; /* Read buffer */ + unsigned int rbufsize; /* Read buffer size */ + unsigned int roffset; /* Read buffer position */ + SbConMode conmode; /* What's coming through the socket at the moment */ + unsigned int expect_length; /* Expected length of (eg) a MSG */ + +} SbSession; + +#include "messagewindow.h" + +/* Operations on SB sessions. */ +extern SbSession *sbsessions_create_local(const char *username); +extern SbSession *sbsessions_create_remote(char *username, char *switchboardaddress, char *sessionid, char *authchallenge); +extern SbSession *sbsessions_create_threeway(char *username1, char *username2); +extern void sbsessions_destroy(SbSession *session); +extern void sbsessions_plug(SbSession *session, struct _messagewindow *messagewindow); +extern void sbsessions_unplug(struct _messagewindow *messagewindow); +extern SbSession *sbsessions_find_trid(unsigned int trid); +extern SbSession *sbsessions_find_headless(const char *username); +extern SbSession *sbsessions_find_single_safe(const char *username); +extern int sbsessions_sessionready(SbSession *session); + +/* Operations on the local user in SB sessions. */ +extern void sbsessions_am_typing(SbSession *session); + +/* Operations on other users in SB sessions. */ +extern void sbsessions_joined(SbSession *session, char *username, char *friendlyname); +extern void sbsessions_left(SbSession *session, char *username); +extern SbUser *sbsessions_find_username(SbSession *session, const char *username); +extern int sbsessions_stoptyping(SbUser *user); + +/* Other stuff. */ +extern void sbsessions_destroy_all(); + +#endif /* SBSESSIONS_H */ diff --git a/src/statusicons.c b/src/statusicons.c new file mode 100644 index 0000000..0cc8ed2 --- /dev/null +++ b/src/statusicons.c @@ -0,0 +1,116 @@ +/* + * statusicons.c + * + * Little pixmaps to go next to the contacts in the list + * + * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <gdk/gdk.h> +#include <assert.h> + +#include "debug.h" +#include "mainwindow.h" + +static struct { + + GdkPixmap *online_pixmap; + GdkPixmap *offline_pixmap; + GdkPixmap *away_pixmap; + GdkPixmap *busy_pixmap; + + GdkPixmap *online_blocked_pixmap; + GdkPixmap *offline_blocked_pixmap; + GdkPixmap *away_blocked_pixmap; + GdkPixmap *busy_blocked_pixmap; + +} statusicons = { + + NULL, + NULL, + NULL, + NULL, + + NULL, + NULL, + NULL, + NULL + +}; + +/* Return the correct pixmap to pack into the contact's widget */ +GtkWidget *statusicons_pixmap(OnlineState status) { + + GdkPixmap **pixmap; + char *file; + + pixmap = NULL; + file = NULL; + + if ( status == ONLINE_NLN ) { + pixmap = &statusicons.online_pixmap; + file = DATADIR"/tuxmessenger/online.xpm"; + } else if ( status == ONLINE_IDL ) { + pixmap = &statusicons.away_pixmap; + file = DATADIR"/tuxmessenger/away.xpm"; + } else if ( status == ONLINE_AWY ) { + pixmap = &statusicons.away_pixmap; + file = DATADIR"/tuxmessenger/away.xpm"; + } else if ( status == ONLINE_BSY ) { + pixmap = &statusicons.busy_pixmap; + file = DATADIR"/tuxmessenger/occ.xpm"; + } else if ( status == ONLINE_LUN ) { + pixmap = &statusicons.away_pixmap; + file = DATADIR"/tuxmessenger/away.xpm"; + } else if ( status == ONLINE_BRB ) { + pixmap = &statusicons.away_pixmap; + file = DATADIR"/tuxmessenger/away.xpm"; + } else if ( status == ONLINE_PHN ) { + pixmap = &statusicons.busy_pixmap; + file = DATADIR"/tuxmessenger/occ.xpm"; + } else if ( status == ONLINE_FLN ) { + pixmap = &statusicons.offline_pixmap; + file = DATADIR"/tuxmessenger/offline.xpm"; + } + + assert(pixmap != NULL); + assert(file != NULL); + + if ( *pixmap == NULL ) { + + /* XPM hasn't been loaded yet. Load it. */ + GtkStyle *style; + style = mainwindow_style(); + *pixmap = gdk_pixmap_create_from_xpm(mainwindow_window(), NULL, &style->bg[GTK_STATE_NORMAL], file); + if ( *pixmap == NULL ) { + /* Contingency if the image can't be loaded. */ + debug_print("SI: Couldn't load status icon!\n"); + return gtk_label_new(""); + } + + } + + return gtk_pixmap_new(*pixmap, NULL); + +} diff --git a/src/statusicons.h b/src/statusicons.h new file mode 100644 index 0000000..d80f042 --- /dev/null +++ b/src/statusicons.h @@ -0,0 +1,32 @@ +/* + * statusicons.h + * + * Little pixmaps to go next to the contacts in the list + * + * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef STATUSICONS_H +#define STATUSICONS_H + +#include "msngenerics.h" + +extern GtkWidget *statusicons_pixmap(OnlineState status); + +#endif /* STATUSICONS_H */ diff --git a/src/twnauth.c b/src/twnauth.c new file mode 100644 index 0000000..19b9aef --- /dev/null +++ b/src/twnauth.c @@ -0,0 +1,218 @@ +/* + * twnauth.c + * + * TWN authentication + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#include "debug.h" +#include "options.h" +#include "routines.h" + +/* Nothing insecure happens if the server data won't fit in this - anything + beyond this limit simply gets ignored. */ +#define TWNAUTH_WGETSIZE 4096 +//#define TWNAUTH_DEBUG 1 + +/* Internally called to get the login URL from Nexus */ +char *twnauth_loginurl() { + + char *shellcommand; + int read_more; + FILE *wget_stream; + char *nexus = NULL; + char *wget_buffer; + int wget_offset = 0; + size_t i; + + shellcommand = malloc(1024); + strncpy(shellcommand, options_wget(), 256); + shellcommand[255] = '\0'; + strcat(shellcommand, " -S -O - https://nexus.passport.com/rdr/pprdr.asp 2>&1"); + + debug_print("twnauth: nexus: %s\n", shellcommand); + wget_stream = popen(shellcommand, "r"); + + if ( wget_stream == NULL ) { + debug_print("twnauth: Nexus failed.\n"); + return strdup(""); + } + + wget_buffer = malloc(TWNAUTH_WGETSIZE); + read_more = 1; + while ( read_more ) { + + ssize_t readval; + readval = fread(wget_buffer + wget_offset, 1, TWNAUTH_WGETSIZE-wget_offset, wget_stream); + if ( (readval != 0) && (readval != -1) && (wget_offset < TWNAUTH_WGETSIZE) ) { + wget_offset += readval; + } else { + read_more = 0; + wget_buffer[wget_offset] = '\0'; + } + + } + + pclose(wget_stream); + +#ifdef TWNAUTH_DEBUG + debug_print("'%s'\n", wget_buffer); +#endif + + /* Now try and find the new nexus in the wget output */ + for ( i=0; i<strlen(wget_buffer); i++ ) { + + if ( strncmp((wget_buffer+i), "DALogin=", 8) == 0 ) { + + size_t j; + + /* Find the end of the ticket and tie it off */ + for ( j=i+7; j<strlen(wget_buffer); j++ ) { + if ( wget_buffer[j] == ',' ) { + wget_buffer[j] = '\0'; + } + } + + nexus = strdup(wget_buffer+i+8); + debug_print("twnauth: nexus: %s\n", nexus); + + } + + } + + free(wget_buffer); + free(shellcommand); + + if ( nexus == NULL ) { + debug_print("Warning! Couldn't retrieve URL from Nexus!\n"); + nexus = strdup(""); + } + return nexus; + +} + +char *twnauth_ticket(char *authdata) { + + char *shellcommand; + int read_more; + FILE *wget_stream; + char *options_username_2; + char *auth_ticket = NULL; + char *wget_buffer; + int wget_offset = 0; + size_t i; + char *loginurl; + + assert(strlen(authdata) < 255); + + loginurl = twnauth_loginurl(); + + shellcommand = malloc(1024); + strncpy(shellcommand, options_wget(), 256); + shellcommand[255] = '\0'; + strcat(shellcommand, " -S -O - --header='Authorization: Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in="); + options_username_2 = routines_urlencode(options_username()); + strncat(shellcommand, options_username_2, 64); + shellcommand[1023] = '\0'; + free(options_username_2); + strncat(shellcommand, ",pwd=", 64); + shellcommand[1023] = '\0'; + strncat(shellcommand, options_password(), 64); + shellcommand[1023] = '\0'; + strcat(shellcommand, ","); + shellcommand[1023] = '\0'; + strncat(shellcommand, authdata, 256); + shellcommand[1023] = '\0'; + strcat(shellcommand, "' https://"); + shellcommand[1023] = '\0'; + strncat(shellcommand, loginurl, 64); + shellcommand[1023] = '\0'; + free(loginurl); + strcat(shellcommand, " 2>&1"); + shellcommand[1023] = '\0'; + +#ifdef TWNAUTH_DEBUG + debug_print("twnauth: %s\n", shellcommand); +#else + debug_print("twnauth: (sending authentication data)\n"); +#endif + wget_stream = popen(shellcommand, "r"); + + if ( wget_stream == NULL ) { + return NULL; + } + + wget_buffer = malloc(TWNAUTH_WGETSIZE); + read_more = 1; + while ( read_more ) { + + ssize_t readval; + readval = fread(wget_buffer + wget_offset, 1, TWNAUTH_WGETSIZE-wget_offset, wget_stream); + if ( (readval != 0) && (readval != -1) && (wget_offset < TWNAUTH_WGETSIZE) ) { + wget_offset += readval; + } else { + read_more = 0; + wget_buffer[wget_offset] = '\0'; + } + + } + + pclose(wget_stream); + +#ifdef TWNAUTH_DEBUG + debug_print("'%s'\n", wget_buffer); +#endif + + /* Now try and find the ticket in the wget output */ + for ( i=0; i<strlen(wget_buffer); i++ ) { + + if ( strncmp((wget_buffer+i), "from-PP='t=:", 11) == 0 ) { + + size_t j; + + /* Find the end of the ticket and tie it off */ + for ( j=i+10; j<strlen(wget_buffer); j++ ) { + if ( wget_buffer[j] == '\'' ) { + wget_buffer[j] = '\0'; + } + } + + auth_ticket = strdup(wget_buffer+i+9); + debug_print("twnauth: %s\n", auth_ticket); + + } + + } + + free(wget_buffer); + free(shellcommand); + + return auth_ticket; + +} diff --git a/src/twnauth.h b/src/twnauth.h new file mode 100644 index 0000000..32db8ee --- /dev/null +++ b/src/twnauth.h @@ -0,0 +1,31 @@ +/* + * twnauth.h + * + * TWN authentication + * + * (c) 2002-2004 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef TWNAUTH_H +#define TWNAUTH_H + +extern char *twnauth_loginurl(); +extern char *twnauth_ticket(char *authdata); + +#endif /* TWNAUTH_H */ diff --git a/src/xml.c b/src/xml.c new file mode 100644 index 0000000..4ec82b2 --- /dev/null +++ b/src/xml.c @@ -0,0 +1,186 @@ +/* + * xml.c + * + * Rudimentary XML parser + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> +#include <libxml/xmlmemory.h> +#include <libxml/parser.h> + +#include "routines.h" +#include "debug.h" + +char *xml_getfield(const char *xml, const char *field) { + + char *identifier_start; + char *value; + + identifier_start = strstr(xml, field); + + if ( identifier_start == NULL ) { + return NULL; + } + + value = routines_lindex(identifier_start + strlen(field) + 2, 0); + value[strlen(value)-1] = '\0'; + + return value; + +} + +char *xml_killillegalchars(const char *temp) { + + char *result; + unsigned int i; + + if ( temp == NULL ) { + return strdup(""); + } + + result = malloc(strlen(temp)+1); + strcpy(result, temp); + for ( i=0; i<strlen(result); i++ ) { + + if ( result[i] == '/' ) { + result[i] = '-'; + } + + /* Don't even think about directory traversal... */ + if ( result[i] == '.' ) { + result[i] = '-'; + } + + } + + return result; + +} + +/* Get the block of text between two given tags. */ +char *xml_getblock(const char *xml, size_t xmllength, const char *rootnode, const char *keyword, int entities) { + + xmlDocPtr doc; + xmlNodePtr cur; + + doc = xmlParseMemory(xml, xmllength); + + if (doc == NULL ) { + return NULL; + } + + cur = xmlDocGetRootElement(doc); + + if (cur == NULL) { + xmlFreeDoc(doc); + return NULL; + } + + if (xmlStrcmp(cur->name, rootnode)) { + xmlFreeDoc(doc); + return NULL; + } + + cur = cur->xmlChildrenNode; + while (cur != NULL) { + + if ((!xmlStrcmp(cur->name, keyword))) { + + char *resultchar; + + xmlChar *result = xmlNodeListGetString(doc, cur->xmlChildrenNode, entities); + if ( result == NULL ) { + return NULL; + } + resultchar = strdup(result); + xmlFree(result); + return resultchar; + + } + + cur = cur->next; + + } + + return NULL; + +} + +/* Set the block of text between two given tags. */ +char *xml_setblock(const char *xml, size_t xmllength, const char *rootnode, const char *keyword, const char *value) { + + xmlDocPtr doc; + xmlNodePtr cur; + + doc = xmlParseMemory(xml, xmllength); + + if (doc == NULL ) { + return NULL; + } + + cur = xmlDocGetRootElement(doc); + + if (cur == NULL) { + xmlFreeDoc(doc); + return NULL; + } + + if (xmlStrcmp(cur->name, rootnode)) { + xmlFreeDoc(doc); + return NULL; + } + + cur = cur->xmlChildrenNode; + while (cur != NULL) { + + if ((!xmlStrcmp(cur->name, keyword))) { + + xmlChar *xmlbuf; + size_t xmlbuflength; + char *text; + + xmlNewTextChild(cur->parent, NULL, keyword, value); + xmlUnlinkNode(cur); + xmlFreeNode(cur); + + xmlDocDumpFormatMemory(doc, &xmlbuf, &xmlbuflength, 0); + text = malloc(xmlbuflength+1); + memcpy(text, xmlbuf, xmlbuflength+1); + xmlFree(xmlbuf); + + return text; + + + + } + + cur = cur->next; + + } + + return NULL; + +} diff --git a/src/xml.h b/src/xml.h new file mode 100644 index 0000000..8fec651 --- /dev/null +++ b/src/xml.h @@ -0,0 +1,35 @@ +/* + * xml.h + * + * Rudimentary XML parser + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef XML_H +#define XML_H + +#include <libxml/xmlmemory.h> + +extern char *xml_getfield(const char *xml, const char *field); +extern char *xml_killillegalchars(const char *temp); +extern char *xml_getblock(const char *xml, size_t xmllength, const char *rootnode, const char *keyword, int entities); +extern char *xml_setblock(const char *xml, size_t xmllength, const char *rootnode, const char *keyword, const char *value); + +#endif /* XML_H */ diff --git a/tuxmessenger.desktop b/tuxmessenger.desktop new file mode 100644 index 0000000..6e34cb2 --- /dev/null +++ b/tuxmessenger.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=TuxMessenger +Comment=MSN Messenger client +Exec=tuxmessenger +Icon=tuxmessenger +Terminal=false +Type=Application +Categories=GTK;Network;Email;News; + |