pidgin/purple-plugin-pack

Start merging buddytools into the Plugin Pack.
org.guifications.plugins
2007-10-21, rlaager
cba4b1cd9f0e
Start merging buddytools into the Plugin Pack.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/COPYING Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program 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 program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/Changelog Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,37 @@
+version 0.6 (3 Sep 2007)
+ * Support Pidgin (rlaager)
+
+version 0.5 (9 Apr 2006)
+ * Add initial "Edit Chat" functionality. Works at least for IRC. (kleptog)
+ * The usual bug fixes
+
+version 0.4pre2 (9 Apr 2006)
+ * Fix compilation issues for Gaim 2 (kleptog)
+ * Update Makefile to allow compilation against multiple versions of
+ gaim easily. New compiletest target.
+ * Integrate custom GTK widget (optional) for timezone selection (kleptog)
+
+version 0.4pre1 (7 Apr 2006)
+ * Various cleanups (rlaager)
+ * Fix so time only displays when different from system time
+ * Display time difference as well as time.
+ * Attach copyright notices and clarify GPLv2+
+ * Makefile cleanups (rlaager)
+
+version 0.3 (6 Apr 2006)
+ * Now have editing for all node types, but Chat is a bit lame so far
+ * Have made gtkwidget to will be successor to huge list box for
+ timezones, now just need to add it somehow.
+ * Groups can now have default timezones and languages.
+ * You can now select Disabled as well as Default, if you don't want
+ to display and spellcheck anything ever.
+ * You can now optionally use the system timezone code.
+
+version 0.2 (4 Apr 2006)
+ * Patches and improvements by rlaager. It should now compile and run
+ with Gaim 2.0.0 (can't test it myself though).
+ * Several suggestions on IRC regard naming of modules and signals.
+ * Changed signals to names including the plugin name.
+
+version 0.1 (4 Apr 2006)
+ * Initial release with all 4 modules in good working order.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/Makefile Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,80 @@
+#*************************************************************************
+#* Buddy Edit Makefile
+#* by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+#* Licenced under the GNU General Public Licence version 2.
+#*************************************************************************/
+
+VERSION=0.5
+
+# Use internal timezone library rather than system one: yes or no
+PRIVATE_TZLIB ?= yes
+
+# Use Custom GTK widget for timezone selection: yes or no
+CUSTOM_GTK ?= yes
+
+# For choosing which version to compile against
+
+# Where to install the modules
+INSTALL_PATH=$(HOME)/.purple/plugins/
+
+CFLAGS=-O2 -g -Wall `pkg-config --cflags glib-2.0 purple pidgin` -DPLUGIN_VERSION=$(VERSION)
+LDFLAGS=`pkg-config --libs glib-2.0 purple pidgin`
+CC=gcc
+
+ifeq ($(PRIVATE_TZLIB),yes)
+CFLAGS += -DPRIVATE_TZLIB
+PRIVATE_TZLIB_OBJS=localtime.o
+endif
+
+ifeq ($(CUSTOM_GTK),yes)
+CFLAGS += -DCUSTOM_GTK
+PRIVATE_TZLIB_OBJS += gtktimezone.o
+endif
+
+LIB_TARGETS=buddyedit.so buddytimezone.so buddynotes.so buddylang.so
+EXE_TARGETS=timetest recursetest gtktimezonetest
+
+all: $(LIB_TARGETS) $(EXE_TARGETS)
+
+recursetest: recurse.o recursetest.o
+
+timetest: timetest.o localtime.o
+
+gtktimezonetest.o: gtktimezonetest.c
+ $(CC) -c $(CFLAGS) `pkg-config --cflags gtk+-2.0` -o $@ $<
+
+gtktimezone.o: gtktimezone.c
+ $(CC) -c $(CFLAGS) `pkg-config --cflags gtk+-2.0` -o $@ $<
+
+gtktimezonetest: gtktimezonetest.o recurse.o
+ $(CC) $(LDFLAGS) -lgtk-x11-2.0 -lgdk-x11-2.0 -o $@ $^
+
+buddytimezone.so: buddytimezone.o $(PRIVATE_TZLIB_OBJS) recurse.o
+ $(CC) -shared $(LDFLAGS) -o $@ $^
+
+buddyedit.so: buddyedit.o
+ $(CC) -shared $(LDFLAGS) -o $@ $^
+
+buddynotes.so: buddynotes.o
+ $(CC) -shared $(LDFLAGS) -o $@ $^
+
+buddylang.o: buddylang.c
+ $(CC) -c $(CFLAGS) `pkg-config --cflags gtkspell-2.0` -o $@ $<
+buddylang.so: buddylang.o
+ $(CC) -shared $(LDFLAGS) -lgtkspell -laspell -o $@ $^
+
+clean:
+ rm -f *.o $(LIB_TARGETS) $(EXE_TARGETS)
+
+install: all
+ install -m 755 -d $(INSTALL_PATH)
+ install -m 644 buddytimezone.so $(INSTALL_PATH)
+ install -m 644 buddyedit.so $(INSTALL_PATH)
+ install -m 644 buddynotes.so $(INSTALL_PATH)
+ install -m 644 buddylang.so $(INSTALL_PATH)
+
+bundle:
+ ln -s . buddyedit-$(VERSION)
+ tar cvzf ../buddyedit-$(VERSION).tar.gz buddyedit-$(VERSION)/{*.c,*.h,Makefile,README,COPYING,Changelog}
+ rm buddyedit-$(VERSION)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/README Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,71 @@
+This package contains Gaim modules created by
+
+Martijn van Oosterhout <kleptog@svana.org>
+http://svana.org/kleptog/
+
+All the files in this package are licenced under the GNU General Public
+Licence version 2 (see COPYING file), or (at your option) any later
+version, with the exception of the files:
+
+localtime.c
+tzfile.h
+private.h
+
+These files, although modified, were copied from the Olson Timezone code
+(http://www.twinsun.com/tz/tz-link.htm) and remain under the public domain.
+
+The modules are:
+ *************************************************************************
+ * Buddy Edit Module
+ *
+ * A Gaim plugin that adds an edit to to buddies allowing you to change
+ * various details you can't normally change. It also provides a mechanism
+ * for subsequent plugins to add themselves to that dialog.
+ *************************************************************************
+ * Buddy Language Module
+ *
+ * A Gaim plugin that allows you to configure the language of the spelling
+ * control on the conversation screen on a per-contact basis.
+ *************************************************************************
+ * Buddy Notes Module
+ *
+ * A Gaim plugin the allows you to add notes to contacts which will be
+ * displayed in the conversation screen as well as the hover tooltip.
+ *************************************************************************
+ * Buddy Timezone Module
+ *
+ * A Gaim plugin that allows you to configure a timezone on a per-contact
+ * basis so it can display the localtime of your contact when a conversation
+ * starts. Convenient if you deal with contacts from many parts of the
+ * world.
+ *************************************************************************
+
+COMPILATION:
+
+To build them, just unpack this package and run "make && make install". You
+will need the gaim header files, which for major distributions are shipped
+in the gaim-dev package. For the spelling control module you will also need
+gtkspell-dev and gtk2-dev.
+
+This will install the module in ~/.gaim/plugins and they will be available
+next time you start Gaim.
+
+There are three options you can control in the compilation phase:
+
+- PRIVATE_TZLIB=yes/no
+
+If yes, a priviate timezone library will be used which is more efficient and
+cleaner in implementation, but does duplicate some system functionality.
+
+- CUSTOM_GTK=yes/no
+
+If yes, the timezone plugin will include a custom GTK widget for timezone
+selection. While this widget is easier to use, it does make the plugin
+depend on gtk. If you select no it will only use standard Gaim components.
+Note, the custom GTK widget only works with Gaim2.
+
+- GAIM_NAME=gaim
+
+If you have multiple versions of Gaim installed you can use this variable to
+select which version to compile against. It should correspond to the name
+used in the .pc file. It also changes the install directory.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/buddyedit.c Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,429 @@
+/*************************************************************************
+ * Buddy Edit Module
+ *
+ * A Gaim plugin that adds an edit to to buddies allowing you to change
+ * various details you can't normally change. It also provides a mechanism
+ * for subsequent plugins to add themselves to that dialog.
+ *
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Some code copyright (C) 2006, Richard Laager <rlaager@users.sf.net>
+ *
+ * This program 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; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *************************************************************************/
+
+#define GAIM_PLUGINS
+#define PLUGIN "core-kleptog-buddyedit"
+
+#include <glib.h>
+#include <string.h>
+
+#include "gaim-compat.h"
+
+#include "notify.h"
+#include "plugin.h"
+#include "util.h"
+#include "version.h"
+
+#include "debug.h" /* Debug output functions */
+#include "request.h" /* Requests stuff */
+
+static GaimPlugin *plugin_self;
+
+static void
+buddyedit_editcomplete_cb(GaimBlistNode * data, GaimRequestFields * fields)
+{
+ gboolean blist_destroy = FALSE;
+
+ GaimBlistNode *olddata = data; /* Keep pointer in case we need to destroy it */
+
+ /* Account detail stuff */
+ switch (data->type)
+ {
+ case GAIM_BLIST_BUDDY_NODE:
+ {
+ GaimBuddy *buddy = (GaimBuddy *) data;
+ GaimAccount *account = gaim_request_fields_get_account(fields, "account");
+ const char *name = gaim_request_fields_get_string(fields, "name");
+ const char *alias = gaim_request_fields_get_string(fields, "alias");
+
+ /* If any details changes, create the buddy */
+ if((account != buddy->account) || strcmp(name, buddy->name))
+ {
+ GHashTable *tmp;
+ GaimBuddy *newbuddy = gaim_buddy_new(account, name, alias);
+ gaim_blist_add_buddy(newbuddy, NULL, NULL, data); /* Copy it to correct location */
+
+ /* Now this is ugly, but we want to copy the settings and avoid issues with memory management */
+ tmp = ((GaimBlistNode *) buddy)->settings;
+ ((GaimBlistNode *) buddy)->settings = ((GaimBlistNode *) newbuddy)->settings;
+ ((GaimBlistNode *) newbuddy)->settings = tmp;
+
+ blist_destroy = TRUE;
+ data = (GaimBlistNode *) newbuddy;
+ }
+ else
+ gaim_blist_alias_buddy(buddy, alias);
+ break;
+ }
+ case GAIM_BLIST_CONTACT_NODE:
+ {
+ GaimContact *contact = (GaimContact *) data;
+ const char *alias = gaim_request_fields_get_string(fields, "alias");
+ gaim_contact_set_alias(contact, alias);
+ break;
+ }
+ case GAIM_BLIST_GROUP_NODE:
+ {
+ GaimGroup *group = (GaimGroup *) data;
+ const char *alias = gaim_request_fields_get_string(fields, "alias");
+ gaim_blist_rename_group(group, alias);
+ break;
+ }
+ case GAIM_BLIST_CHAT_NODE:
+ {
+ GaimChat *chat = (GaimChat *) data;
+ gboolean new_chat = FALSE;
+
+ GaimConnection *gc;
+ GList *list = NULL, *tmp;
+ gc = gaim_account_get_connection(chat->account);
+ if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
+ list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
+
+ GaimAccount *newaccount = gaim_request_fields_get_account(fields, "account");
+
+ /* In Gaim2 each prot_chat_entry has a field "required". We use
+ * this to determine if a field is important enough to recreate
+ * the chat if it changes. Non-required fields we jsut change
+ * in-situ. In Gaim1.5 this field doesn't exist so we always
+ * recreate */
+#if GAIM_MAJOR_VERSION >= 2
+ if(newaccount != chat->account)
+ new_chat = TRUE;
+ else
+ {
+ const char *oldvalue, *newvalue;
+
+ for (tmp = g_list_first(list); tmp && !new_chat; tmp = g_list_next(tmp))
+ {
+ struct proto_chat_entry *pce = tmp->data;
+ if(!pce->required) /* Only checking required fields at this point */
+ continue;
+ if(pce->is_int)
+ continue; /* Not yet */
+ oldvalue = g_hash_table_lookup(chat->components, pce->identifier);
+ newvalue = gaim_request_fields_get_string(fields, pce->identifier);
+
+ if(oldvalue == NULL)
+ oldvalue = "";
+ if(newvalue == NULL)
+ newvalue = "";
+ if(strcmp(oldvalue, newvalue) != 0)
+ new_chat = TRUE;
+ }
+ }
+#else
+ new_chat = TRUE;
+#endif
+
+ if(new_chat)
+ {
+ const char *oldvalue, *newvalue;
+ GHashTable *components =
+ g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+ for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp))
+ {
+ struct proto_chat_entry *pce = tmp->data;
+
+ if(pce->is_int)
+ {
+ oldvalue = g_hash_table_lookup(chat->components, pce->identifier);
+ g_hash_table_replace(components, g_strdup(pce->identifier),
+ g_strdup(oldvalue));
+ }
+ else
+ {
+ newvalue = gaim_request_fields_get_string(fields, pce->identifier);
+ g_hash_table_replace(components, g_strdup(pce->identifier),
+ g_strdup(newvalue));
+ }
+ }
+ GaimChat *newchat = gaim_chat_new(newaccount, NULL, components);
+ gaim_blist_add_chat(newchat, NULL, data); /* Copy it to correct location */
+ data = (GaimBlistNode *) newchat;
+ blist_destroy = TRUE;
+ }
+ else /* Just updating values in old chat */
+ {
+ const char *newvalue;
+ for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp))
+ {
+ struct proto_chat_entry *pce = tmp->data;
+#if GAIM_MAJOR_VERSION >= 2
+ if(pce->required)
+ continue;
+#endif
+ if(pce->is_int)
+ {
+ /* Do nothing, yet */
+ }
+ else
+ {
+ newvalue = gaim_request_fields_get_string(fields, pce->identifier);
+ g_hash_table_replace(chat->components, g_strdup(pce->identifier),
+ g_strdup(newvalue));
+ }
+ }
+ }
+
+ const char *alias = gaim_request_fields_get_string(fields, "alias");
+ gaim_blist_alias_chat(chat, alias);
+ break;
+ }
+ case GAIM_BLIST_OTHER_NODE:
+ break;
+ }
+
+ gaim_signal_emit(gaim_blist_get_handle(), PLUGIN "-submit-fields", fields, data);
+
+ if(blist_destroy)
+ {
+ if(olddata->type == GAIM_BLIST_BUDDY_NODE)
+ gaim_blist_remove_buddy((GaimBuddy *) olddata);
+ else if(olddata->type == GAIM_BLIST_CHAT_NODE)
+ gaim_blist_remove_chat((GaimChat *) olddata);
+ }
+
+ /* Save any changes */
+ gaim_blist_schedule_save();
+}
+
+static GaimAccount *buddyedit_account_filter_func_data;
+
+static gboolean
+buddyedit_account_filter_func(GaimAccount * account)
+{
+ GaimPluginProtocolInfo *gppi1 =
+ GAIM_PLUGIN_PROTOCOL_INFO(gaim_account_get_connection(account)->prpl);
+ GaimPluginProtocolInfo *gppi2 =
+ GAIM_PLUGIN_PROTOCOL_INFO(gaim_account_get_connection(buddyedit_account_filter_func_data)->
+ prpl);
+
+ return gppi1 == gppi2;
+}
+
+/* Node is either a contact or a buddy */
+static void
+buddy_edit_cb(GaimBlistNode * node, gpointer data)
+{
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "buddy_edit_cb(%p)\n", node);
+ GaimRequestFields *fields;
+ GaimRequestField *field;
+ GaimRequestFieldGroup *group;
+ char *request_title = NULL;
+
+ fields = gaim_request_fields_new();
+
+ switch (node->type)
+ {
+ case GAIM_BLIST_BUDDY_NODE:
+ {
+ GaimBuddy *buddy = (GaimBuddy *) node;
+ group = gaim_request_field_group_new("Buddy Details");
+ gaim_request_fields_add_group(fields, group);
+
+ field = gaim_request_field_account_new("account", "Account", buddy->account);
+ gaim_request_field_account_set_show_all(field, TRUE);
+ gaim_request_field_group_add_field(group, field);
+
+ field = gaim_request_field_string_new("name", "Name", buddy->name, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ field = gaim_request_field_string_new("alias", "Alias", buddy->alias, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ request_title = "Edit Buddy";
+ break;
+ }
+ case GAIM_BLIST_CONTACT_NODE:
+ {
+ GaimContact *contact = (GaimContact *) node;
+ group = gaim_request_field_group_new("Contact Details");
+ gaim_request_fields_add_group(fields, group);
+
+ field = gaim_request_field_string_new("alias", "Alias", contact->alias, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ request_title = "Edit Contact";
+ break;
+ }
+ case GAIM_BLIST_GROUP_NODE:
+ {
+ GaimGroup *grp = (GaimGroup *) node;
+ group = gaim_request_field_group_new("Group Details");
+ gaim_request_fields_add_group(fields, group);
+
+ field = gaim_request_field_string_new("alias", "Name", grp->name, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ request_title = "Edit Group";
+ break;
+ }
+ case GAIM_BLIST_CHAT_NODE:
+ {
+ GaimChat *chat = (GaimChat *) node;
+ group = gaim_request_field_group_new("Chat Details");
+ gaim_request_fields_add_group(fields, group);
+
+ field = gaim_request_field_account_new("account", "Account", chat->account);
+ gaim_request_field_account_set_filter(field, buddyedit_account_filter_func);
+ buddyedit_account_filter_func_data = chat->account;
+ gaim_request_field_group_add_field(group, field);
+ GaimConnection *gc;
+ GList *list = NULL, *tmp;
+ gc = gaim_account_get_connection(chat->account);
+ if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
+ list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
+
+ for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp))
+ {
+ struct proto_chat_entry *pce = tmp->data;
+ const char *value;
+#if GAIM_MAJOR_VERSION >= 2
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN,
+ "identifier=%s, label=%s, is_int=%d, required=%d\n", pce->identifier,
+ pce->label, pce->is_int, pce->required);
+#endif
+ if(pce->is_int)
+ continue; /* Not yet */
+ value = g_hash_table_lookup(chat->components, pce->identifier);
+ field = gaim_request_field_string_new(pce->identifier, pce->label, value, FALSE);
+#if GAIM_MAJOR_VERSION >= 2
+ gaim_request_field_set_required(field, pce->required);
+#endif
+ gaim_request_field_group_add_field(group, field);
+ }
+ field = gaim_request_field_string_new("alias", "Alias", chat->alias, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ request_title = "Edit Chat";
+ break;
+ }
+ default:
+ request_title = "Edit";
+ break;
+ }
+
+ gaim_signal_emit(gaim_blist_get_handle(), PLUGIN "-create-fields", fields, node);
+
+ gaim_request_fields(plugin_self,
+ request_title, NULL, NULL,
+ fields, "OK", G_CALLBACK(buddyedit_editcomplete_cb), "Cancel", NULL, node);
+}
+
+static void
+buddy_menu_cb(GaimBlistNode * node, GList ** menu, void *data)
+{
+#if GAIM_MAJOR_VERSION < 2
+ GaimBlistNodeAction *action;
+#else
+ GaimMenuAction *action;
+#endif
+
+ switch (node->type)
+ {
+ /* These are the types we handle */
+ case GAIM_BLIST_BUDDY_NODE:
+ case GAIM_BLIST_CONTACT_NODE:
+ case GAIM_BLIST_GROUP_NODE:
+ case GAIM_BLIST_CHAT_NODE:
+ break;
+
+ case GAIM_BLIST_OTHER_NODE:
+ default:
+ return;
+ }
+
+#if GAIM_MAJOR_VERSION < 2
+ action = gaim_blist_node_action_new("Edit...", buddy_edit_cb, NULL);
+#else
+ action = gaim_menu_action_new("Edit...", GAIM_CALLBACK(buddy_edit_cb), NULL, NULL);
+#endif
+ *menu = g_list_append(*menu, action);
+}
+
+static gboolean
+plugin_load(GaimPlugin * plugin)
+{
+
+ plugin_self = plugin;
+
+ void *blist_handle = gaim_blist_get_handle();
+
+ gaim_signal_register(blist_handle, PLUGIN "-create-fields", /* Called when about to create dialog */
+ gaim_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */
+ gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_TYPE_POINTER), /* FieldList */
+ gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_BLIST));
+
+ gaim_signal_register(blist_handle, PLUGIN "-submit-fields", /* Called when dialog submitted */
+ gaim_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */
+ gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_TYPE_POINTER), /* FieldList */
+ gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_BLIST));
+
+ gaim_signal_connect(blist_handle, "blist-node-extended-menu", plugin,
+ GAIM_CALLBACK(buddy_menu_cb), NULL);
+
+ return TRUE;
+}
+
+
+static GaimPluginInfo info = {
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD,
+ NULL,
+ 0,
+ NULL,
+ GAIM_PRIORITY_DEFAULT,
+
+ PLUGIN,
+ "Buddy Edit Module",
+ G_STRINGIFY(PLUGIN_VERSION),
+
+ "Enable editing of buddy properties",
+ "A plugin that adds an edit to to buddies allowing you to change various details you can't normally change. "
+ "It also provides a mechanism for subsequent plugins to add themselves to that dialog. ",
+ "Martijn van Oosterhout <kleptog@svana.org>",
+ "http://svana.org/kleptog/gaim/",
+
+ plugin_load,
+ NULL,
+ NULL,
+
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin * plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(buddyedit, init_plugin, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/buddylang.c Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,347 @@
+/*************************************************************************
+ * Buddy Language Module
+ *
+ * A Gaim plugin that allows you to configure the language of the spelling
+ * control on the conversation screen on a per-contact basis.
+ *
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ *
+ * This program 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; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *************************************************************************/
+
+#define GAIM_PLUGINS
+#define PLUGIN "core-kleptog-buddylang"
+#define SETTING_NAME "language"
+#define CONTROL_NAME PLUGIN "-" SETTING_NAME
+
+#include <glib.h>
+#include <string.h>
+
+#include "gaim-compat.h"
+
+#include "notify.h"
+#include "plugin.h"
+#include "version.h"
+
+#include "debug.h" /* Debug output functions */
+#include "util.h" /* Menu functions */
+#include "request.h" /* Requests stuff */
+#include "conversation.h" /* Conversation stuff */
+
+#include "gtkconv.h"
+
+#include "aspell.h"
+#include "gtkspell/gtkspell.h"
+
+static GaimPlugin *plugin_self;
+
+#define LANGUAGE_FLAG ((void*)1)
+#define DISABLED_FLAG ((void*)2)
+
+/* Resolve specifies what the return value should mean:
+ *
+ * If TRUE, it's for display, we want to know the *effect* thus hiding the
+ * "none" value and going to going level to find the default
+ *
+ * If false, we only want what the user enter, thus the string "none" if
+ * that's what it is
+ */
+static const char *
+buddy_get_language(GaimBlistNode * node, gboolean resolve)
+{
+ GaimBlistNode *datanode = NULL;
+ const char *language;
+
+ switch (node->type)
+ {
+ case GAIM_BLIST_BUDDY_NODE:
+ datanode = (GaimBlistNode *) gaim_buddy_get_contact((GaimBuddy *) node);
+ break;
+ case GAIM_BLIST_CONTACT_NODE:
+ datanode = node;
+ break;
+ case GAIM_BLIST_GROUP_NODE:
+ datanode = node;
+ break;
+ default:
+ return NULL;
+ }
+
+ language = gaim_blist_node_get_string(datanode, SETTING_NAME);
+
+ if(!resolve)
+ return language;
+
+ if(language && strcmp(language, "none") == 0)
+ return NULL;
+
+ if(language)
+ return language;
+
+ if(datanode->type == GAIM_BLIST_CONTACT_NODE)
+ {
+ /* There is no gaim_blist_contact_get_group(), though there probably should be */
+ datanode = datanode->parent;
+ language = gaim_blist_node_get_string(datanode, SETTING_NAME);
+ }
+
+ if(language && strcmp(language, "none") == 0)
+ return NULL;
+
+ return language;
+}
+
+static void
+buddylang_createconv_cb(GaimConversation * conv, void *data)
+{
+ const char *name;
+ GaimBuddy *buddy;
+ const char *language;
+#ifdef PIDGIN_CONVERSATION
+ PidginConversation *gtkconv;
+#else
+ GaimGtkConversation *gtkconv;
+#endif
+ GtkSpell *gtkspell;
+ char *str;
+ GError *error = NULL;
+
+#if GAIM_MAJOR_VERSION < 2
+ if(gaim_conversation_get_type(conv) != GAIM_CONV_IM)
+ return;
+#else
+ if(gaim_conversation_get_type(conv) != GAIM_CONV_TYPE_IM)
+ return;
+#endif
+
+ name = gaim_conversation_get_name(conv);
+ buddy = gaim_find_buddy(gaim_conversation_get_account(conv), name);
+ if(!buddy)
+ return;
+
+ language = buddy_get_language((GaimBlistNode *) buddy, TRUE);
+
+ if(!language)
+ return;
+
+#ifdef PIDGIN_CONVERSATION
+ gtkconv = PIDGIN_CONVERSATION(conv);
+#else
+ gtkconv = GAIM_GTK_CONVERSATION(conv);
+#endif
+ gtkspell = gtkspell_get_from_text_view(GTK_TEXT_VIEW(gtkconv->entry));
+
+ if(!gtkspell)
+ return;
+
+ if(strcmp(language, "none"))
+ gtkspell_detach(gtkspell);
+ else if(!gtkspell_set_language(gtkspell, language, &error) && error)
+ {
+ gaim_debug_warning(PLUGIN, "Failed to configure GtkSpell for language %s: %s\n",
+ language, error->message);
+ g_error_free(error);
+ }
+ str = g_strdup_printf("Spellcheck language: %s", language);
+ gaim_conversation_write(conv, PLUGIN, str, GAIM_MESSAGE_SYSTEM, time(NULL));
+ g_free(str);
+}
+
+static void
+buddylang_submitfields_cb(GaimRequestFields * fields, GaimBlistNode * data)
+{
+ GaimBlistNode *node;
+ GaimRequestField *list;
+ const GList *sellist;
+ void *seldata = NULL;
+
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "buddylang_submitfields_cb(%p,%p)\n", fields, data);
+
+ switch (data->type)
+ {
+ case GAIM_BLIST_BUDDY_NODE:
+ node = (GaimBlistNode *) gaim_buddy_get_contact((GaimBuddy *) data);
+ break;
+ case GAIM_BLIST_CONTACT_NODE:
+ case GAIM_BLIST_GROUP_NODE:
+ /* code handles either case */
+ node = data;
+ break;
+ case GAIM_BLIST_CHAT_NODE:
+ case GAIM_BLIST_OTHER_NODE:
+ default:
+ /* Not applicable */
+ return;
+ }
+
+ list = gaim_request_fields_get_field(fields, CONTROL_NAME);
+ sellist = gaim_request_field_list_get_selected(list);
+ if(sellist)
+ seldata = gaim_request_field_list_get_data(list, sellist->data);
+
+ /* Otherwise, it's fixed value and this means deletion */
+ if(seldata == LANGUAGE_FLAG)
+ gaim_blist_node_set_string(node, SETTING_NAME, sellist->data);
+ else if(seldata == DISABLED_FLAG)
+ gaim_blist_node_set_string(node, SETTING_NAME, "none");
+ else
+ gaim_blist_node_remove_setting(node, SETTING_NAME);
+}
+
+/* Node is either a contact or a buddy */
+static void
+buddylang_createfields_cb(GaimRequestFields * fields, GaimBlistNode * data)
+{
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "buddylang_createfields_cb(%p,%p)\n", fields, data);
+ GaimRequestField *field;
+ GaimRequestFieldGroup *group;
+ const char *language;
+ gboolean is_default;
+
+ struct AspellConfig *aspellconfig;
+ struct AspellDictInfoList *dictinfolist;
+ struct AspellDictInfoEnumeration *dictinfoenumeration;
+
+ switch (data->type)
+ {
+ case GAIM_BLIST_BUDDY_NODE:
+ case GAIM_BLIST_CONTACT_NODE:
+ is_default = FALSE;
+ break;
+ case GAIM_BLIST_GROUP_NODE:
+ is_default = TRUE;
+ break;
+ case GAIM_BLIST_CHAT_NODE:
+ case GAIM_BLIST_OTHER_NODE:
+ default:
+ /* Not applicable */
+ return;
+ }
+
+ group = gaim_request_field_group_new(NULL);
+ gaim_request_fields_add_group(fields, group);
+
+ field =
+ gaim_request_field_list_new(CONTROL_NAME,
+ is_default ? "Default spellcheck language for group" :
+ "Spellcheck language");
+ gaim_request_field_list_set_multi_select(field, FALSE);
+ gaim_request_field_list_add(field, "<Default>", "");
+ gaim_request_field_list_add(field, "<Disabled>", DISABLED_FLAG);
+
+ /* I'd love to be able to access the gtkspell one, but it's hidden, so we create our own */
+ aspellconfig = new_aspell_config();
+ if(!aspellconfig)
+ {
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "new_aspell_config() returned NULL\n");
+ return;
+ }
+ dictinfolist = get_aspell_dict_info_list(aspellconfig);
+ if(!dictinfolist)
+ {
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "get_aspell_dict_info_list() returned NULL\n");
+ return;
+ }
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "dictinfo size = %d\n",
+ aspell_dict_info_list_size(dictinfolist));
+ dictinfoenumeration = aspell_dict_info_list_elements(dictinfolist);
+ if(!dictinfoenumeration)
+ {
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "aspell_dict_info_list_elements() returned NULL\n");
+ return;
+ }
+
+ /* We have a list of dictionaries, but we can only specify a language
+ * (like en_GB). Since the language can appear multiple times on the
+ * list, we check before adding to avoid duplicates */
+ while (!aspell_dict_info_enumeration_at_end(dictinfoenumeration))
+ {
+ const struct AspellDictInfo *dictinfo =
+ aspell_dict_info_enumeration_next(dictinfoenumeration);
+
+ if(gaim_request_field_list_get_data(field, dictinfo->code) != NULL)
+ continue;
+ gaim_request_field_list_add(field, dictinfo->code, LANGUAGE_FLAG);
+ }
+ delete_aspell_dict_info_enumeration(dictinfoenumeration);
+ delete_aspell_config(aspellconfig);
+
+ language = buddy_get_language(data, FALSE);
+ if(language)
+ {
+ if(strcmp(language, "none") == 0)
+ gaim_request_field_list_add_selected(field, "<Disabled>");
+ else
+ gaim_request_field_list_add_selected(field, language);
+ }
+ else
+ gaim_request_field_list_add_selected(field, "<Default>");
+
+ gaim_request_field_group_add_field(group, field);
+}
+
+static gboolean
+plugin_load(GaimPlugin * plugin)
+{
+
+ plugin_self = plugin;
+
+ gaim_signal_connect(gaim_blist_get_handle(), "core-kleptog-buddyedit-create-fields", plugin,
+ GAIM_CALLBACK(buddylang_createfields_cb), NULL);
+ gaim_signal_connect(gaim_blist_get_handle(), "core-kleptog-buddyedit-submit-fields", plugin,
+ GAIM_CALLBACK(buddylang_submitfields_cb), NULL);
+ gaim_signal_connect(gaim_conversations_get_handle(), "conversation-created", plugin,
+ GAIM_CALLBACK(buddylang_createconv_cb), NULL);
+
+ return TRUE;
+}
+
+static GaimPluginInfo info = {
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD,
+ NULL,
+ 0,
+ NULL,
+ GAIM_PRIORITY_DEFAULT,
+
+ PLUGIN,
+ "Buddy Language Module",
+ G_STRINGIFY(PLUGIN_VERSION),
+
+ "Configure spell-check language per buddy",
+ "This plugin allows you to configure the language of the spelling control on the conversation screen on a per-contact basis.",
+ "Martijn van Oosterhout <kleptog@svana.org>",
+ "http://svana.org/kleptog/gaim/",
+
+ plugin_load,
+ NULL,
+ NULL,
+
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin * plugin)
+{
+ info.dependencies = g_list_append(info.dependencies, "core-kleptog-buddyedit");
+}
+
+GAIM_INIT_PLUGIN(buddylang, init_plugin, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/buddynotes.c Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,230 @@
+/*************************************************************************
+ * Buddy Notes Module
+ *
+ * A Gaim plugin the allows you to add notes to contacts which will be
+ * displayed in the conversation screen as well as the hover tooltip.
+ *
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ *
+ * This program 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; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *************************************************************************/
+
+#define GAIM_PLUGINS
+#define PLUGIN "core-kleptog-buddynotes"
+#define SETTING_NAME "notes"
+#define CONTROL_NAME PLUGIN "-" SETTING_NAME
+
+#include <glib.h>
+
+#include "gaim-compat.h"
+
+#include "notify.h"
+#include "plugin.h"
+#include "version.h"
+
+#include "debug.h" /* Debug output functions */
+#include "util.h" /* Menu functions */
+#include "request.h" /* Requests stuff */
+#include "conversation.h" /* Conversation stuff */
+
+#include "gtkblist.h"
+
+#define TIMEZONE_FLAG ((void*)1)
+
+static GaimPlugin *plugin_self;
+
+static const char *
+buddy_get_notes(GaimBlistNode * node)
+{
+ GaimContact *contact = NULL;
+ switch (node->type)
+ {
+ case GAIM_BLIST_BUDDY_NODE:
+ contact = gaim_buddy_get_contact((GaimBuddy *) node);
+ break;
+ case GAIM_BLIST_CONTACT_NODE:
+ contact = (GaimContact *) node;
+ break;
+ default:
+ return NULL;
+ }
+
+ return gaim_blist_node_get_string((GaimBlistNode *) contact, SETTING_NAME);
+}
+
+static void
+buddynotes_createconv_cb(GaimConversation * conv, void *data)
+{
+ const char *name;
+ GaimBuddy *buddy;
+ const char *notes;
+ char *str;
+
+#if GAIM_MAJOR_VERSION < 2
+ if(gaim_conversation_get_type(conv) != GAIM_CONV_IM)
+ return;
+#else
+ if(gaim_conversation_get_type(conv) != GAIM_CONV_TYPE_IM)
+ return;
+#endif
+
+ name = gaim_conversation_get_name(conv);
+ buddy = gaim_find_buddy(gaim_conversation_get_account(conv), name);
+ if(!buddy)
+ return;
+
+ notes = buddy_get_notes((GaimBlistNode *) buddy);
+
+ if(!notes)
+ return;
+
+ str = g_strdup_printf("Notes: %s", notes);
+
+ gaim_conversation_write(conv, PLUGIN, str, GAIM_MESSAGE_SYSTEM, time(NULL));
+
+ g_free(str);
+}
+
+static void
+buddy_tooltip_cb(GaimBlistNode * node, char **text, void *data)
+{
+ char *newtext;
+ const char *notes;
+
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "type %d\n", node->type);
+ notes = buddy_get_notes(node);
+ if(!notes)
+ return;
+
+ newtext = g_strdup_printf("%s\n<b>Notes:</b> %s", *text, notes);
+
+ g_free(*text);
+ *text = newtext;
+}
+
+static void
+buddynotes_submitfields_cb(GaimRequestFields * fields, GaimBlistNode * data)
+{
+ GaimContact *contact;
+ const char *notes;
+
+ /* buddynotes stuff */
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "buddynotes_submitfields_cb(%p,%p)\n", fields, data);
+ switch (data->type)
+ {
+ case GAIM_BLIST_BUDDY_NODE:
+ contact = gaim_buddy_get_contact((GaimBuddy *) data);
+ break;
+ case GAIM_BLIST_CONTACT_NODE:
+ contact = (GaimContact *) data;
+ break;
+ default:
+ /* Not applicable */
+ return;
+ }
+
+ notes = gaim_request_fields_get_string(fields, CONTROL_NAME);
+
+ /* Otherwise, it's fixed value and this means deletion */
+ if(notes && notes[0])
+ gaim_blist_node_set_string((GaimBlistNode *) contact, SETTING_NAME, notes);
+ else
+ gaim_blist_node_remove_setting((GaimBlistNode *) contact, SETTING_NAME);
+}
+
+/* Node is either a contact or a buddy */
+static void
+buddynotes_createfields_cb(GaimRequestFields * fields, GaimBlistNode * data)
+{
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "buddynotes_createfields_cb(%p,%p)\n", fields, data);
+ GaimRequestField *field;
+ GaimRequestFieldGroup *group;
+ const char *notes;
+
+ switch (data->type)
+ {
+ case GAIM_BLIST_BUDDY_NODE:
+ case GAIM_BLIST_CONTACT_NODE:
+ /* Continue, code works for either */
+ break;
+ default:
+ /* Not applicable */
+ return;
+ }
+ group = gaim_request_field_group_new(NULL);
+ gaim_request_fields_add_group(fields, group);
+
+ notes = buddy_get_notes(data);
+
+ field = gaim_request_field_string_new(CONTROL_NAME, "Notes", notes, FALSE);
+
+ gaim_request_field_group_add_field(group, field);
+}
+
+static gboolean
+plugin_load(GaimPlugin * plugin)
+{
+
+ plugin_self = plugin;
+
+ gaim_signal_connect(gaim_blist_get_handle(), "core-kleptog-buddyedit-create-fields", plugin,
+ GAIM_CALLBACK(buddynotes_createfields_cb), NULL);
+ gaim_signal_connect(gaim_blist_get_handle(), "core-kleptog-buddyedit-submit-fields", plugin,
+ GAIM_CALLBACK(buddynotes_submitfields_cb), NULL);
+ gaim_signal_connect(gaim_gtk_blist_get_handle(), "drawing-tooltip", plugin,
+ GAIM_CALLBACK(buddy_tooltip_cb), NULL);
+ gaim_signal_connect(gaim_conversations_get_handle(), "conversation-created", plugin,
+ GAIM_CALLBACK(buddynotes_createconv_cb), NULL);
+
+ return TRUE;
+}
+
+static GaimPluginInfo info = {
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD,
+ NULL,
+ 0,
+ NULL,
+ GAIM_PRIORITY_DEFAULT,
+
+ PLUGIN,
+ "Buddy Notes Module",
+ G_STRINGIFY(PLUGIN_VERSION),
+
+ "Store notes about your buddy",
+ "This plugin allows you to set a notes field for each buddy and will display it at various points",
+ "Martijn van Oosterhout <kleptog@svana.org>",
+ "http://svana.org/kleptog/gaim/",
+
+ plugin_load,
+ NULL,
+ NULL,
+
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin * plugin)
+{
+ info.dependencies = g_list_append(info.dependencies, "core-kleptog-buddyedit");
+}
+
+GAIM_INIT_PLUGIN(buddynotes, init_plugin, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/buddytimezone.c Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,461 @@
+/*************************************************************************
+ * Buddy Timezone Module
+ *
+ * A Purple plugin that allows you to configure a timezone on a per-contact
+ * basis so it can display the localtime of your contact when a conversation
+ * starts. Convenient if you deal with contacts from many parts of the
+ * world.
+ *
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Some code copyright (C) 2006, Richard Laager <rlaager@users.sf.net>
+ *
+ * This program 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; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *************************************************************************/
+
+#define PURPLE_PLUGINS
+
+#ifdef CUSTOM_GTK
+#define PLUGIN "gtk-kleptog-buddytimezone"
+#else
+#define PLUGIN "core-kleptog-buddytimezone"
+#endif
+
+#define SETTING_NAME "timezone"
+#define CONTROL_NAME PLUGIN "-" SETTING_NAME
+
+#include <glib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <math.h>
+
+#include "notify.h"
+#include "plugin.h"
+#include "version.h"
+
+#include "debug.h" /* Debug output functions */
+#include "util.h" /* Menu functions */
+#include "request.h" /* Requests stuff */
+#include "conversation.h" /* Conversation stuff */
+
+#include "localtime.h"
+#include "recurse.h"
+
+#include "gtkblist.h"
+
+#define TIMEZONE_FLAG ((void*)1)
+#define DISABLED_FLAG ((void*)2)
+
+#define TIME_FORMAT "%X"
+/* Another possible format (hides seconds) */
+//#define TIME_FORMAT "%H:%M"
+
+static PurplePlugin *plugin_self;
+void *make_timezone_menu(const char *selected);
+const char *get_timezone_menu_selection(void *widget);
+
+/* Resolve specifies what the return value should mean:
+ *
+ * If TRUE, it's for display, we want to know the *effect* thus hiding the
+ * "none" value and going to going level to find the default
+ *
+ * If false, we only want what the user enter, thus the string "none" if
+ * that's what it is
+ */
+static const char *
+buddy_get_timezone(PurpleBlistNode * node, gboolean resolve)
+{
+ PurpleBlistNode *datanode = NULL;
+ const char *timezone;
+
+ switch (node->type)
+ {
+ case PURPLE_BLIST_BUDDY_NODE:
+ datanode = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) node);
+ break;
+ case PURPLE_BLIST_CONTACT_NODE:
+ datanode = node;
+ break;
+ case PURPLE_BLIST_GROUP_NODE:
+ datanode = node;
+ break;
+ default:
+ return NULL;
+ }
+
+ timezone = purple_blist_node_get_string(datanode, SETTING_NAME);
+
+ if(!resolve)
+ return timezone;
+
+ /* The effect of "none" is to stop recursion */
+ if(timezone && strcmp(timezone, "none") == 0)
+ return NULL;
+
+ if(timezone)
+ return timezone;
+
+ if(datanode->type == PURPLE_BLIST_CONTACT_NODE)
+ {
+ /* There is no purple_blist_contact_get_group(), though there probably should be */
+ datanode = datanode->parent;
+ timezone = purple_blist_node_get_string(datanode, SETTING_NAME);
+ }
+
+ if(timezone && strcmp(timezone, "none") == 0)
+ return NULL;
+
+ return timezone;
+}
+
+/* Calcuates the difference between two struct tm's. */
+static float
+timezone_calc_difference(struct tm *remote_tm, struct tm *local_tm)
+{
+ int datediff = 0, diff;
+
+ /* Tries to calculate the difference. Note this only works because the
+ * times are with 24 hours of eachother! */
+ if(remote_tm->tm_mday != local_tm->tm_mday)
+ {
+ datediff = remote_tm->tm_year - local_tm->tm_year;
+ if(datediff == 0)
+ datediff = remote_tm->tm_mon - local_tm->tm_mon;
+ if(datediff == 0)
+ datediff = remote_tm->tm_mday - local_tm->tm_mday;
+ }
+ diff =
+ datediff * 24 * 60 + (remote_tm->tm_hour - local_tm->tm_hour) * 60 + (remote_tm->tm_min -
+ local_tm->tm_min);
+
+ return (float)diff / 60.0;
+}
+
+#ifdef PRIVATE_TZLIB
+static int
+timezone_get_time(const char *timezone, struct tm *tm, float *diff)
+{
+ struct state *tzinfo = timezone_load(timezone);
+ struct tm *local_tm;
+ time_t now;
+
+ if(!tzinfo)
+ return -1;
+
+ time(&now);
+ localsub(&now, 0, tm, tzinfo);
+ free(tzinfo);
+
+ /* Calculate user's localtime, and compare. If same, no output */
+ local_tm = localtime(&now);
+
+ if(local_tm->tm_hour == tm->tm_hour && local_tm->tm_min == tm->tm_min)
+ return 1;
+
+ *diff = timezone_calc_difference(tm, local_tm);
+ return 0;
+}
+#else
+static int
+timezone_get_time(const char *timezone, struct tm *tm, float *diff)
+{
+ time_t now;
+ struct tm *tm_tmp;
+ const gchar *old_tz;
+
+ /* Store the current TZ value. */
+ old_tz = g_getenv("TZ");
+
+ g_setenv("TZ", timezone, TRUE);
+
+ time(&now);
+ tm_tmp = localtime(&now);
+ *tm = *tm_tmp; /* Must copy, localtime uses local buffer */
+
+ /* Reset the old TZ value. */
+ if(old_tz == NULL)
+ g_unsetenv("TZ");
+ else
+ g_setenv("TZ", old_tz, TRUE);
+
+ /* Calculate user's localtime, and compare. If same, no output */
+ tm_tmp = localtime(&now);
+
+ if(tm_tmp->tm_hour == tm->tm_hour && tm_tmp->tm_min == tm->tm_min)
+ return 1;
+
+ *diff = timezone_calc_difference(tm, tm_tmp);
+ return 0;
+}
+#endif
+
+static void
+timezone_createconv_cb(PurpleConversation * conv, void *data)
+{
+ const char *name;
+ PurpleBuddy *buddy;
+ struct tm tm;
+ const char *timezone;
+ float diff;
+ int ret;
+
+ if(purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM)
+ return;
+
+ name = purple_conversation_get_name(conv);
+ buddy = purple_find_buddy(purple_conversation_get_account(conv), name);
+ if(!buddy)
+ return;
+
+ timezone = buddy_get_timezone((PurpleBlistNode *) buddy, TRUE);
+
+ if(!timezone)
+ return;
+
+ ret = timezone_get_time(timezone, &tm, &diff);
+
+ if(ret == 0)
+ {
+ const char *text = purple_time_format(&tm);
+
+ char *str = g_strdup_printf("Remote Local Time: %s (%g hours %s)", text, fabs(diff),
+ (diff < 0) ? "behind" : "ahead");
+
+ purple_conversation_write(conv, PLUGIN, str, PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+ g_free(str);
+ }
+}
+
+static void
+buddytimezone_tooltip_cb(PurpleBlistNode * node, char **text, gboolean full, void *data)
+{
+ char *newtext;
+ const char *timezone;
+ struct tm tm;
+ float diff;
+ int ret;
+
+ if(!full)
+ return;
+
+ purple_debug(PURPLE_DEBUG_INFO, PLUGIN, "type %d\n", node->type);
+ timezone = buddy_get_timezone(node, TRUE);
+ if(!timezone)
+ return;
+
+ ret = timezone_get_time(timezone, &tm, &diff);
+ if(ret < 0)
+ newtext = g_strdup_printf("%s\n<b>Timezone:</b> %s (error)", *text, timezone);
+ else if(ret == 0)
+ {
+ const char *timetext = purple_time_format(&tm);
+
+ newtext =
+ g_strdup_printf("%s\n<b>Local Time:</b> %s (%g hours %s)", *text, timetext, fabs(diff),
+ (diff < 0) ? "behind" : "ahead");
+ }
+ else
+ return;
+
+ g_free(*text);
+ *text = newtext;
+}
+
+static void
+buddytimezone_submitfields_cb(PurpleRequestFields * fields, PurpleBlistNode * data)
+{
+ PurpleBlistNode *node;
+ PurpleRequestField *list;
+
+ /* timezone stuff */
+ purple_debug(PURPLE_DEBUG_INFO, PLUGIN, "buddytimezone_submitfields_cb(%p,%p)\n", fields, data);
+
+ switch (data->type)
+ {
+ case PURPLE_BLIST_BUDDY_NODE:
+ node = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) data);
+ break;
+ case PURPLE_BLIST_CONTACT_NODE:
+ case PURPLE_BLIST_GROUP_NODE:
+ /* code handles either case */
+ node = data;
+ break;
+ case PURPLE_BLIST_CHAT_NODE:
+ case PURPLE_BLIST_OTHER_NODE:
+ default:
+ /* Not applicable */
+ return;
+ }
+
+ list = purple_request_fields_get_field(fields, CONTROL_NAME);
+#ifdef CUSTOM_GTK
+ const char *seldata = get_timezone_menu_selection(list->ui_data);
+ if(seldata == NULL)
+ purple_blist_node_remove_setting(node, SETTING_NAME);
+ else
+ purple_blist_node_set_string(node, SETTING_NAME, seldata);
+#else
+ const GList *sellist;
+ void *seldata = NULL;
+ sellist = purple_request_field_list_get_selected(list);
+ if(sellist)
+ seldata = purple_request_field_list_get_data(list, sellist->data);
+
+ /* Otherwise, it's fixed value and this means deletion */
+ if(seldata == TIMEZONE_FLAG)
+ purple_blist_node_set_string(node, SETTING_NAME, sellist->data);
+ else if(seldata == DISABLED_FLAG)
+ purple_blist_node_set_string(node, SETTING_NAME, "none");
+ else
+ purple_blist_node_remove_setting(node, SETTING_NAME);
+#endif
+}
+
+#ifndef CUSTOM_GTK
+static int
+buddy_add_timezone_cb(char *filename, void *data)
+{
+ PurpleRequestField *field = (PurpleRequestField *) data;
+ if(isupper(filename[0]))
+ purple_request_field_list_add(field, filename, TIMEZONE_FLAG);
+ return 0;
+}
+#endif
+
+static void
+buddytimezone_createfields_cb(PurpleRequestFields * fields, PurpleBlistNode * data)
+{
+ purple_debug(PURPLE_DEBUG_INFO, PLUGIN, "buddytimezone_createfields_cb(%p,%p)\n", fields, data);
+ PurpleRequestField *field;
+ PurpleRequestFieldGroup *group;
+ const char *timezone;
+ gboolean is_default;
+
+ switch (data->type)
+ {
+ case PURPLE_BLIST_BUDDY_NODE:
+ case PURPLE_BLIST_CONTACT_NODE:
+ is_default = FALSE;
+ break;
+ case PURPLE_BLIST_GROUP_NODE:
+ is_default = TRUE;
+ break;
+ case PURPLE_BLIST_CHAT_NODE:
+ case PURPLE_BLIST_OTHER_NODE:
+ default:
+ /* Not applicable */
+ return;
+ }
+
+ group = purple_request_field_group_new(NULL);
+ purple_request_fields_add_group(fields, group);
+
+ timezone = buddy_get_timezone(data, FALSE);
+
+#ifdef CUSTOM_GTK
+ field =
+ purple_request_field_new(CONTROL_NAME,
+ is_default ? "Default timezone for group" : "Timezone of contact",
+ PURPLE_REQUEST_FIELD_LIST);
+ field->ui_data = make_timezone_menu(timezone);
+#else
+ field =
+ purple_request_field_list_new(CONTROL_NAME,
+ is_default ? "Default timezone for group" :
+ "Timezone of contact (type to select)");
+ purple_request_field_list_set_multi_select(field, FALSE);
+ purple_request_field_list_add(field, "<Default>", "");
+ purple_request_field_list_add(field, "<Disabled>", DISABLED_FLAG);
+
+ recurse_directory("/usr/share/zoneinfo/", buddy_add_timezone_cb, field);
+
+ if(timezone)
+ {
+ if(strcmp(timezone, "none") == 0)
+ purple_request_field_list_add_selected(field, "<Disabled>");
+ else
+ purple_request_field_list_add_selected(field, timezone);
+ }
+ else
+ purple_request_field_list_add_selected(field, "<Default>");
+#endif
+
+ purple_request_field_group_add_field(group, field);
+}
+
+static gboolean
+plugin_load(PurplePlugin * plugin)
+{
+ plugin_self = plugin;
+
+ purple_signal_connect(purple_blist_get_handle(), "core-kleptog-buddyedit-create-fields", plugin,
+ PURPLE_CALLBACK(buddytimezone_createfields_cb), NULL);
+ purple_signal_connect(purple_blist_get_handle(), "core-kleptog-buddyedit-submit-fields", plugin,
+ PURPLE_CALLBACK(buddytimezone_submitfields_cb), NULL);
+ purple_signal_connect(pidgin_blist_get_handle(), "drawing-tooltip", plugin,
+ PURPLE_CALLBACK(buddytimezone_tooltip_cb), NULL);
+ purple_signal_connect(purple_conversations_get_handle(), "conversation-created", plugin,
+ PURPLE_CALLBACK(timezone_createconv_cb), NULL);
+
+#ifdef PRIVATE_TZLIB
+ const char *zoneinfo_dir = purple_prefs_get_string("/plugins/timezone/zoneinfo_dir");
+ if(tz_init(zoneinfo_dir) < 0)
+ purple_debug_error(PLUGIN, "Problem opening zoneinfo dir (%s): %s\n", zoneinfo_dir,
+ strerror(errno));
+#endif
+
+ return TRUE;
+}
+
+static PurplePluginInfo info = {
+ PURPLE_PLUGIN_MAGIC,
+ PURPLE_MAJOR_VERSION,
+ 0,
+ PURPLE_PLUGIN_STANDARD,
+ NULL,
+ 0,
+ NULL,
+ PURPLE_PRIORITY_DEFAULT,
+
+ PLUGIN,
+ "Buddy Timezone Module",
+ G_STRINGIFY(PLUGIN_VERSION),
+
+ "Quickly see the local time of a buddy",
+ "A Purple plugin that allows you to configure a timezone on a per-contact "
+ "basis so it can display the localtime of your contact when a conversation "
+ "starts. Convenient if you deal with contacts from many parts of the " "world.",
+ "Martijn van Oosterhout <kleptog@svana.org>",
+ "http://svana.org/kleptog/purple/",
+
+ plugin_load,
+ NULL,
+ NULL,
+
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(PurplePlugin * plugin)
+{
+ info.dependencies = g_list_append(info.dependencies, "core-kleptog-buddyedit");
+}
+
+PURPLE_INIT_PLUGIN(buddytimezone, init_plugin, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/gtktimezone.c Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,237 @@
+/*************************************************************************
+ * GTK Timezone widget module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *
+ * This module creates the GTK widget used to select timezones. It's here to
+ * clearly seperate the GTK stuff from the plugin itself.
+ *************************************************************************/
+
+#include <gtk/gtk.h>
+#include <string.h>
+#include <ctype.h>
+#include "recurse.h"
+
+#define DISABLED_STRING "<Disabled>"
+#define DEFAULT_STRING "<Default>"
+#define MORE_STRING "More..."
+
+struct nodestate
+{
+ GtkWidget *submenu;
+ gchar *string;
+};
+
+struct state
+{
+ GtkWidget *base;
+ GtkWidget *extra;
+ int currdepth;
+ struct nodestate stack[4];
+};
+
+static inline const char *
+menuitem_get_label(GtkMenuItem * menuitem)
+{
+ return gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem))));
+}
+
+static GtkMenuItem *
+menu_get_first_menuitem(GtkWidget * menu)
+{
+ GList *list = gtk_container_get_children(GTK_CONTAINER(menu));
+ GtkMenuItem *selection = GTK_MENU_ITEM(g_list_nth_data(list, 0));
+ g_list_free(list);
+ return selection;
+}
+
+static int
+menu_select_cb(GtkMenuItem * menuitem, GtkWidget * menu)
+{
+ const char *label = menuitem_get_label(menuitem);
+
+ if(label[0] == '<')
+ {
+ GtkWidget *selection;
+ gchar *selstring;
+
+ if(strcmp(label, DEFAULT_STRING) == 0)
+ selstring = "";
+ else if(strcmp(label, DISABLED_STRING) == 0)
+ selstring = "none";
+
+ selection = GTK_WIDGET(menu_get_first_menuitem(menu));
+ gtk_widget_hide(selection);
+ }
+ else
+ {
+ char *str = g_strdup(label);
+
+ GtkWidget *parent;
+
+ for (;;)
+ {
+ GtkMenuItem *parentitem;
+ const char *label2;
+ char *temp;
+
+ parent = gtk_widget_get_parent(GTK_WIDGET(menuitem));
+ if(menu == parent)
+ break;
+
+ parentitem = GTK_MENU_ITEM(gtk_menu_get_attach_widget(GTK_MENU(parent)));
+ label2 = menuitem_get_label(parentitem);
+ if(strcmp(label2, MORE_STRING) != 0)
+ {
+ temp = g_strconcat(label2, "/", str, NULL);
+ g_free(str);
+ str = temp;
+ }
+
+ menuitem = parentitem;
+ }
+ {
+ GtkLabel *label;
+
+ GtkMenuItem *selection = menu_get_first_menuitem(menu);
+ GtkOptionMenu *optionmenu = GTK_OPTION_MENU(gtk_menu_get_attach_widget(GTK_MENU(menu)));
+
+ label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(selection)));
+
+ gtk_label_set_text(label, str);
+ gtk_widget_show(GTK_WIDGET(selection));
+ gtk_option_menu_set_history(optionmenu, 0);
+
+ printf("optionmenu=%p, menu=%p, menuitem=%p, label=%p\n", optionmenu, menu, selection,
+ label);
+ g_free(str);
+ }
+ }
+ return 0;
+}
+
+static int
+make_menu_cb(char *path, struct state *state)
+{
+ int i, j;
+
+ char **elements;
+
+ /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */
+ if(!isupper(path[0]))
+ return 0;
+
+ elements = g_strsplit(path, "/", 4);
+
+ for (i = 0; i < state->currdepth && state->stack[i].string; i++)
+ {
+ if(strcmp(elements[i], state->stack[i].string) != 0)
+ break;
+ }
+ /* i is now the index of the first non-matching element, so free the rest */
+ for (j = i; j < state->currdepth; j++)
+ g_free(state->stack[j].string);
+ state->currdepth = i;
+
+ while (elements[i])
+ {
+ GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu;
+ GtkWidget *menuitem;
+
+ if(i == 0 && elements[1] == NULL)
+ parent = state->extra;
+
+ menuitem = gtk_menu_item_new_with_label(elements[i]);
+ gtk_menu_append(parent, menuitem);
+
+ if(elements[i + 1] != NULL) /* Has submenu */
+ {
+ state->stack[i].submenu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state->stack[i].submenu);
+
+ state->currdepth++;
+ state->stack[i].string = g_strdup(elements[i]);
+ }
+ else
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb),
+ state->base);
+
+ i++;
+ }
+ g_strfreev(elements);
+ return 0;
+}
+
+void *
+make_timezone_menu(const char *selected)
+{
+ int i;
+ struct state state;
+
+ GtkWidget *menu;
+ GtkWidget *optionmenu, *menuitem, *selection;
+
+ if(selected == NULL)
+ selected = "";
+
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label(selected);
+ gtk_menu_append(menu, menuitem);
+ selection = menuitem;
+
+ menuitem = gtk_menu_item_new_with_label(DISABLED_STRING);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
+ gtk_menu_append(menu, menuitem);
+ menuitem = gtk_menu_item_new_with_label(DEFAULT_STRING);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
+ gtk_menu_append(menu, menuitem);
+ menuitem = gtk_menu_item_new_with_label(MORE_STRING);
+ gtk_menu_append(menu, menuitem);
+
+ state.base = menu;
+ state.extra = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra);
+
+ state.currdepth = 0;
+
+ recurse_directory("/usr/share/zoneinfo", (DirRecurseMatch) make_menu_cb, &state);
+
+ for (i = 0; i < state.currdepth; i++)
+ g_free(state.stack[i].string);
+
+ optionmenu = gtk_option_menu_new();
+ gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu);
+ gtk_widget_show_all(optionmenu);
+
+ if(strcmp(selected, "") == 0)
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 2);
+ gtk_widget_hide(selection);
+ }
+ else if(strcmp(selected, "none") == 0)
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 1);
+ gtk_widget_hide(selection);
+ }
+ else
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0);
+ }
+
+ return optionmenu;
+}
+
+const char *
+get_timezone_menu_selection(void *widget)
+{
+ GtkOptionMenu *menu = GTK_OPTION_MENU(widget);
+
+ int sel = gtk_option_menu_get_history(menu);
+ if(sel == 2) /* Default */
+ return NULL;
+ if(sel == 1) /* Disabled */
+ return "none";
+
+ GtkLabel *l = GTK_LABEL(gtk_bin_get_child(GTK_BIN(menu)));
+ return gtk_label_get_text(l);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/gtktimezonetest.c Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,343 @@
+/*************************************************************************
+ * GTK Timezone test program
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *
+ * A test program to play with different ways that user could select from
+ * the huge list of timezones. Eventually things tested here should migrate
+ * to the module itself.
+ *************************************************************************/
+
+#include <gtk/gtk.h>
+#include <string.h>
+#include <ctype.h>
+#include "recurse.h"
+
+#define PACKAGE "Hello World"
+#define VERSION "0.1"
+
+#define DISABLED_STRING "<Disabled>"
+#define DEFAULT_STRING "<Default>"
+#define MORE_STRING "More..."
+/*
+ * Terminate the main loop.
+ */
+static void
+on_destroy(GtkWidget * widget, gpointer data)
+{
+ gtk_main_quit();
+}
+
+enum
+{
+ STRING_COLUMN,
+ N_COLUMNS
+};
+
+struct nodestate
+{
+#ifdef USE_COMBOBOX
+ GtkTreeIter iter;
+#else
+ GtkWidget *submenu;
+#endif
+ gchar *string;
+};
+
+struct state
+{
+#ifdef USE_COMBOBOX
+ GtkTreeStore *store;
+ GtkTreeIter *extra;
+#else
+ GtkWidget *base;
+ GtkWidget *extra;
+#endif
+ int currdepth;
+ struct nodestate stack[4];
+};
+
+static inline const char *
+menuitem_get_label(GtkMenuItem * menuitem)
+{
+ return gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem))));
+}
+
+static GtkWidget *
+menu_get_first_menuitem(GtkMenu * menu)
+{
+ GList *list = gtk_container_get_children(GTK_CONTAINER(menu));
+ GtkWidget *selection = GTK_WIDGET(g_list_nth_data(list, 0));
+ g_list_free(list);
+ return selection;
+}
+
+int
+menu_select_cb(GtkMenuItem * menuitem, GtkWidget * menu)
+{
+ const char *label = menuitem_get_label(menuitem);
+
+// printf( "menuitem = %s(%p), menu = %s(%p)\n", G_OBJECT_TYPE_NAME(menuitem), menuitem, G_OBJECT_TYPE_NAME(menu), menu );
+
+ if(label[0] == '<')
+ {
+ GtkWidget *selection;
+ gchar *selstring;
+
+ if(strcmp(label, DEFAULT_STRING) == 0)
+ selstring = "";
+ else if(strcmp(label, DISABLED_STRING) == 0)
+ selstring = "none";
+
+ selection = menu_get_first_menuitem(GTK_MENU(menu));
+ gtk_widget_hide(selection);
+ }
+ else
+ {
+ char *str = g_strdup(label);
+
+ GtkWidget *parent;
+
+ for (;;)
+ {
+ GtkMenuItem *parentitem;
+ const char *label2;
+ char *temp;
+
+ parent = gtk_widget_get_parent(GTK_WIDGET(menuitem));
+// printf( "parent = %s(%p)\n", G_OBJECT_TYPE_NAME(parent), parent);
+ if(menu == parent)
+ break;
+
+ parentitem = GTK_MENU_ITEM(gtk_menu_get_attach_widget(GTK_MENU(parent)));
+// printf( "parentitem = %s(%p)\n", G_OBJECT_TYPE_NAME(parentitem), parentitem);
+ label2 = menuitem_get_label(parentitem);
+ if(strcmp(label2, MORE_STRING) != 0)
+ {
+ temp = g_strconcat(label2, "/", str, NULL);
+ g_free(str);
+ str = temp;
+ }
+
+ menuitem = parentitem;
+ }
+ {
+ GtkLabel *label;
+
+ GtkWidget *selection = menu_get_first_menuitem(GTK_MENU(menu));
+ GtkOptionMenu *optionmenu = GTK_OPTION_MENU(gtk_menu_get_attach_widget(GTK_MENU(menu)));
+
+ label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(selection)));
+
+ gtk_label_set_text(label, str);
+ gtk_widget_show(GTK_WIDGET(selection));
+ gtk_option_menu_set_history(optionmenu, 0);
+ g_free(str);
+ }
+ }
+ return 0;
+}
+
+int
+make_menu_cb(char *path, struct state *state)
+{
+ int i, j;
+
+ char **elements;
+
+ /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */
+ if(!isupper(path[0]))
+ return 0;
+
+ elements = g_strsplit(path, "/", 4);
+
+ for (i = 0; i < state->currdepth && state->stack[i].string; i++)
+ {
+ if(strcmp(elements[i], state->stack[i].string) != 0)
+ break;
+ }
+ /* i is now the index of the first non-matching element, so free the rest */
+ for (j = i; j < state->currdepth; j++)
+ g_free(state->stack[j].string);
+ state->currdepth = i;
+
+ while (elements[i])
+ {
+#ifdef USE_COMBOBOX
+ GtkTreeIter *parent = (i == 0) ? NULL : &state->stack[i - 1].iter;
+#else
+ GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu;
+ GtkWidget *menuitem;
+#endif
+
+ if(i == 0 && elements[1] == NULL)
+ parent = state->extra;
+
+#ifdef USE_COMBOBOX
+ gtk_tree_store_append(state->store, &state->stack[i].iter, parent);
+ gtk_tree_store_set(state->store, &state->stack[i].iter, STRING_COLUMN, elements[i], -1);
+ state->stack[i].string = g_strdup(elements[i]);
+ state->currdepth++;
+#else
+ menuitem = gtk_menu_item_new_with_label(elements[i]);
+ gtk_menu_append(parent, menuitem);
+
+ if(elements[i + 1] != NULL) /* Has submenu */
+ {
+ state->stack[i].submenu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state->stack[i].submenu);
+
+ state->currdepth++;
+ state->stack[i].string = g_strdup(elements[i]);
+ }
+ else
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb),
+ state->base);
+
+#endif
+
+ i++;
+ }
+ g_strfreev(elements);
+ return 0;
+}
+
+GtkWidget *
+make_menu2(char *selected)
+{
+ int i;
+ struct state state;
+
+#ifdef USE_COMBOBOX
+ GtkTreeStore *store = gtk_tree_store_new(N_COLUMNS, /* Total number of columns */
+ G_TYPE_STRING); /* Timezone */
+ GtkWidget *tree;
+
+ GtkCellRenderer *renderer;
+ GtkTreeIter iter1, iter2;
+
+ gtk_tree_store_append(store, &iter1, NULL); /* Acquire an iterator */
+ gtk_tree_store_set(store, &iter1, STRING_COLUMN, DISABLED_STRING, -1);
+ gtk_tree_store_append(store, &iter1, NULL); /* Acquire an iterator */
+ gtk_tree_store_set(store, &iter1, STRING_COLUMN, DEFAULT_STRING, -1);
+ gtk_tree_store_append(store, &iter2, &iter1);
+ gtk_tree_store_set(store, &iter1, STRING_COLUMN, MORE_STRING, -1);
+
+ state.store = store;
+ state.extra = &iter1;
+#else
+
+ GtkWidget *menu;
+ GtkWidget *optionmenu, *menuitem, *selection;
+
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label(selected);
+ gtk_menu_append(menu, menuitem);
+ selection = menuitem;
+
+ menuitem = gtk_menu_item_new_with_label(DISABLED_STRING);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
+ gtk_menu_append(menu, menuitem);
+ menuitem = gtk_menu_item_new_with_label(DEFAULT_STRING);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
+ gtk_menu_append(menu, menuitem);
+ menuitem = gtk_menu_item_new_with_label(MORE_STRING);
+ gtk_menu_append(menu, menuitem);
+
+ state.base = menu;
+ state.extra = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra);
+#endif
+ state.currdepth = 0;
+
+ recurse_directory("/usr/share/zoneinfo", (DirRecurseMatch) make_menu_cb, &state);
+
+ for (i = 0; i < state.currdepth; i++)
+ g_free(state.stack[i].string);
+
+#ifdef USE_COMBOBOX
+ tree = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(tree), renderer, TRUE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(tree), renderer, "text", 0, NULL);
+
+ gtk_widget_show_all(tree);
+ return tree;
+#else
+ optionmenu = gtk_option_menu_new();
+ gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu);
+ gtk_widget_show_all(optionmenu);
+
+ if(strcmp(selected, "") == 0)
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 2);
+ gtk_widget_hide(selection);
+ }
+ else if(strcmp(selected, "none") == 0)
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 1);
+ gtk_widget_hide(selection);
+ }
+ else
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0);
+ }
+
+ return optionmenu;
+#endif
+
+}
+
+int
+main(int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *label;
+ GtkWidget *menu;
+ GtkWidget *frame;
+
+ gtk_init(&argc, &argv);
+
+ /* create the main, top level, window */
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+
+ /* give the window a 20px wide border */
+ gtk_container_set_border_width(GTK_CONTAINER(window), 20);
+
+ /* give it the title */
+ gtk_window_set_title(GTK_WINDOW(window), PACKAGE " " VERSION);
+
+ /* open it a bit wider so that both the label and title show up */
+ gtk_window_set_default_size(GTK_WINDOW(window), 200, 50);
+
+ /* Connect the destroy event of the window with our on_destroy function
+ * When the window is about to be destroyed we get a notificaiton and
+ * stop the main GTK loop
+ */
+ g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(on_destroy), NULL);
+
+ /* Create the "Hello, World" label */
+ label = gtk_label_new("Select a timezone:");
+ gtk_widget_show(label);
+
+ frame = gtk_vbox_new(FALSE, 0);
+ gtk_widget_show(frame);
+
+ /* and insert it into the main window */
+ gtk_container_add(GTK_CONTAINER(window), frame);
+ gtk_container_add(GTK_CONTAINER(frame), label);
+
+ menu = make_menu2("none");
+
+ gtk_container_add(GTK_CONTAINER(frame), menu);
+ gtk_widget_show(menu);
+
+ /* make sure that everything, window and label, are visible */
+ gtk_widget_show(window);
+
+ /* start the main loop */
+ gtk_main();
+
+ return 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/localtime.c Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,1250 @@
+/*************************************************************************
+ * Timezone Module
+ * copied from the Olson Timezone code, licence unchanged.
+ * by Martijn van Oosterhout <kleptog@svana.org> April 2006
+ * Original Licence below (public domain).
+ *
+ * This code has been copied from the Olson Timezone code, but heavily
+ * adapted to meet my needs. In particular, you can load multiple timezones
+ * and specify which timezone to convert with.
+ *************************************************************************/
+
+/*
+** This file is in the public domain, so clarified as of
+** 1996-06-05 by Arthur David Olson.
+*/
+
+#define TM_GMTOFF tm_gmtoff
+#define TM_ZONE tm_zone
+
+/*
+** Leap second handling from Bradley White.
+** POSIX-style TZ environment variable handling from Guy Harris.
+*/
+
+/*LINTLIBRARY*/
+
+#include "private.h"
+#include "tzfile.h"
+#include "fcntl.h"
+#include "float.h" /* for FLT_MAX and DBL_MAX */
+
+#ifndef TZ_ABBR_MAX_LEN
+#define TZ_ABBR_MAX_LEN 16
+#endif /* !defined TZ_ABBR_MAX_LEN */
+
+#ifndef TZ_ABBR_CHAR_SET
+#define TZ_ABBR_CHAR_SET \
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._"
+#endif /* !defined TZ_ABBR_CHAR_SET */
+
+#ifndef TZ_ABBR_ERR_CHAR
+#define TZ_ABBR_ERR_CHAR '_'
+#endif /* !defined TZ_ABBR_ERR_CHAR */
+
+/*
+** SunOS 4.1.1 headers lack O_BINARY.
+*/
+
+#ifdef O_BINARY
+#define OPEN_MODE (O_RDONLY | O_BINARY)
+#endif /* defined O_BINARY */
+#ifndef O_BINARY
+#define OPEN_MODE O_RDONLY
+#endif /* !defined O_BINARY */
+
+#ifndef WILDABBR
+/*
+** Someone might make incorrect use of a time zone abbreviation:
+** 1. They might reference tzname[0] before calling tzset (explicitly
+** or implicitly).
+** 2. They might reference tzname[1] before calling tzset (explicitly
+** or implicitly).
+** 3. They might reference tzname[1] after setting to a time zone
+** in which Daylight Saving Time is never observed.
+** 4. They might reference tzname[0] after setting to a time zone
+** in which Standard Time is never observed.
+** 5. They might reference tm.TM_ZONE after calling offtime.
+** What's best to do in the above cases is open to debate;
+** for now, we just set things up so that in any of the five cases
+** WILDABBR is used. Another possibility: initialize tzname[0] to the
+** string "tzname[0] used before set", and similarly for the other cases.
+** And another: initialize tzname[0] to "ERA", with an explanation in the
+** manual page of what this "time zone abbreviation" means (doing this so
+** that tzname[0] has the "normal" length of three characters).
+*/
+#define WILDABBR " "
+#endif /* !defined WILDABBR */
+
+static char wildabbr[] = WILDABBR;
+
+static const char gmt[] = "GMT";
+
+static char *tzdir;
+
+/*
+** The DST rules to use if TZ has no rules and we can't load TZDEFRULES.
+** We default to US rules as of 1999-08-17.
+** POSIX 1003.1 section 8.1.1 says that the default DST rules are
+** implementation dependent; for historical reasons, US rules are a
+** common default.
+*/
+#ifndef TZDEFRULESTRING
+#define TZDEFRULESTRING ",M4.1.0,M10.5.0"
+#endif /* !defined TZDEFDST */
+
+struct ttinfo { /* time type information */
+ long tt_gmtoff; /* UTC offset in seconds */
+ int tt_isdst; /* used to set tm_isdst */
+ int tt_abbrind; /* abbreviation list index */
+ int tt_ttisstd; /* TRUE if transition is std time */
+ int tt_ttisgmt; /* TRUE if transition is UTC */
+};
+
+struct lsinfo { /* leap second information */
+ time_t ls_trans; /* transition time */
+ long ls_corr; /* correction to apply */
+};
+
+#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b))
+
+#ifdef TZNAME_MAX
+#define MY_TZNAME_MAX TZNAME_MAX
+#endif /* defined TZNAME_MAX */
+#ifndef TZNAME_MAX
+#define MY_TZNAME_MAX 255
+#endif /* !defined TZNAME_MAX */
+
+struct state {
+ int leapcnt;
+ int timecnt;
+ int typecnt;
+ int charcnt;
+ time_t ats[TZ_MAX_TIMES];
+ unsigned char types[TZ_MAX_TIMES];
+ struct ttinfo ttis[TZ_MAX_TYPES];
+ char chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, sizeof gmt),
+ (2 * (MY_TZNAME_MAX + 1)))];
+ struct lsinfo lsis[TZ_MAX_LEAPS];
+};
+
+struct rule {
+ int r_type; /* type of rule--see below */
+ int r_day; /* day number of rule */
+ int r_week; /* week number of rule */
+ int r_mon; /* month number of rule */
+ long r_time; /* transition time of rule */
+};
+
+#define JULIAN_DAY 0 /* Jn - Julian day */
+#define DAY_OF_YEAR 1 /* n - day of year */
+#define MONTH_NTH_DAY_OF_WEEK 2 /* Mm.n.d - month, week, day of week */
+
+/*
+** Prototypes for static functions.
+*/
+
+static long detzcode P((const char * codep));
+static const char * getzname P((const char * strp));
+static const char * getqzname P((const char * strp, const char delim));
+static const char * getnum P((const char * strp, int * nump, int min,
+ int max));
+static const char * getsecs P((const char * strp, long * secsp));
+static const char * getoffset P((const char * strp, long * offsetp));
+static const char * getrule P((const char * strp, struct rule * rulep));
+static void gmtload P((struct state * sp));
+struct tm * gmtsub P((const time_t * timep, long offset,
+ struct tm * tmp));
+struct tm * localsub P((const time_t * timep, long offset,
+ struct tm * tmp, struct state *sp));
+static int increment_overflow P((int * number, int delta));
+static int leaps_thru_end_of P((int y));
+static struct tm * timesub P((const time_t * timep, long offset,
+ const struct state * sp, struct tm * tmp));
+static time_t transtime P((time_t janfirst, int year,
+ const struct rule * rulep, long offset));
+static int tzload P((const char * name, struct state * sp));
+static int tzparse P((const char * name, struct state * sp,
+ int lastditch));
+
+struct state *timezone_load P((const char * name));
+
+#ifdef ALL_STATE
+static struct state * gmtptr;
+#endif /* defined ALL_STATE */
+
+#ifndef ALL_STATE
+static struct state gmtmem;
+#define gmtptr (&gmtmem)
+#endif /* State Farm */
+
+#ifndef TZ_STRLEN_MAX
+#define TZ_STRLEN_MAX 255
+#endif /* !defined TZ_STRLEN_MAX */
+
+//static char lcl_TZname[TZ_STRLEN_MAX + 1];
+//static int lcl_is_set;
+static int gmt_is_set;
+
+/*
+** Section 4.12.3 of X3.159-1989 requires that
+** Except for the strftime function, these functions [asctime,
+** ctime, gmtime, localtime] return values in one of two static
+** objects: a broken-down time structure and an array of char.
+** Thanks to Paul Eggert for noting this.
+*/
+
+//static struct tm tm;
+
+#ifdef USG_COMPAT
+time_t timezone = 0;
+int daylight = 0;
+#endif /* defined USG_COMPAT */
+
+#ifdef ALTZONE
+time_t altzone = 0;
+#endif /* defined ALTZONE */
+
+static long
+detzcode(codep)
+const char * const codep;
+{
+ register long result;
+ register int i;
+
+ result = (codep[0] & 0x80) ? ~0L : 0L;
+ for (i = 0; i < 4; ++i)
+ result = (result << 8) | (codep[i] & 0xff);
+ return result;
+}
+
+static int
+tzload(name, sp)
+register const char * name;
+register struct state * const sp;
+{
+ register const char * p;
+ register int i;
+ register int fid;
+
+ if (name == NULL && (name = TZDEFAULT) == NULL)
+ return -1;
+ {
+ register int doaccess;
+ /*
+ ** Section 4.9.1 of the C standard says that
+ ** "FILENAME_MAX expands to an integral constant expression
+ ** that is the size needed for an array of char large enough
+ ** to hold the longest file name string that the implementation
+ ** guarantees can be opened."
+ */
+ char fullname[FILENAME_MAX + 1];
+
+ if (name[0] == ':')
+ ++name;
+ doaccess = name[0] == '/';
+ if (!doaccess) {
+ if ((p = tzdir) == NULL)
+ return -1;
+ if ((strlen(p) + strlen(name) + 1) >= sizeof fullname)
+ return -1;
+ (void) strcpy(fullname, p);
+ (void) strcat(fullname, "/");
+ (void) strcat(fullname, name);
+ /*
+ ** Set doaccess if '.' (as in "../") shows up in name.
+ */
+ if (strchr(name, '.') != NULL)
+ doaccess = TRUE;
+ name = fullname;
+ }
+ if (doaccess && access(name, R_OK) != 0)
+ return -1;
+ if ((fid = open(name, OPEN_MODE)) == -1)
+ return -1;
+ }
+ {
+ struct tzhead * tzhp;
+ union {
+ struct tzhead tzhead;
+ char buf[sizeof *sp + sizeof *tzhp];
+ } u;
+ int ttisstdcnt;
+ int ttisgmtcnt;
+
+ i = read(fid, u.buf, sizeof u.buf);
+ if (close(fid) != 0)
+ return -1;
+ ttisstdcnt = (int) detzcode(u.tzhead.tzh_ttisstdcnt);
+ ttisgmtcnt = (int) detzcode(u.tzhead.tzh_ttisgmtcnt);
+ sp->leapcnt = (int) detzcode(u.tzhead.tzh_leapcnt);
+ sp->timecnt = (int) detzcode(u.tzhead.tzh_timecnt);
+ sp->typecnt = (int) detzcode(u.tzhead.tzh_typecnt);
+ sp->charcnt = (int) detzcode(u.tzhead.tzh_charcnt);
+ p = u.tzhead.tzh_charcnt + sizeof u.tzhead.tzh_charcnt;
+ if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS ||
+ sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES ||
+ sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES ||
+ sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS ||
+ (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) ||
+ (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0))
+ return -1;
+ if (i - (p - u.buf) < sp->timecnt * 4 + /* ats */
+ sp->timecnt + /* types */
+ sp->typecnt * (4 + 2) + /* ttinfos */
+ sp->charcnt + /* chars */
+ sp->leapcnt * (4 + 4) + /* lsinfos */
+ ttisstdcnt + /* ttisstds */
+ ttisgmtcnt) /* ttisgmts */
+ return -1;
+ for (i = 0; i < sp->timecnt; ++i) {
+ sp->ats[i] = detzcode(p);
+ p += 4;
+ }
+ for (i = 0; i < sp->timecnt; ++i) {
+ sp->types[i] = (unsigned char) *p++;
+ if (sp->types[i] >= sp->typecnt)
+ return -1;
+ }
+ for (i = 0; i < sp->typecnt; ++i) {
+ register struct ttinfo * ttisp;
+
+ ttisp = &sp->ttis[i];
+ ttisp->tt_gmtoff = detzcode(p);
+ p += 4;
+ ttisp->tt_isdst = (unsigned char) *p++;
+ if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1)
+ return -1;
+ ttisp->tt_abbrind = (unsigned char) *p++;
+ if (ttisp->tt_abbrind < 0 ||
+ ttisp->tt_abbrind > sp->charcnt)
+ return -1;
+ }
+ for (i = 0; i < sp->charcnt; ++i)
+ sp->chars[i] = *p++;
+ sp->chars[i] = '\0'; /* ensure '\0' at end */
+ for (i = 0; i < sp->leapcnt; ++i) {
+ register struct lsinfo * lsisp;
+
+ lsisp = &sp->lsis[i];
+ lsisp->ls_trans = detzcode(p);
+ p += 4;
+ lsisp->ls_corr = detzcode(p);
+ p += 4;
+ }
+ for (i = 0; i < sp->typecnt; ++i) {
+ register struct ttinfo * ttisp;
+
+ ttisp = &sp->ttis[i];
+ if (ttisstdcnt == 0)
+ ttisp->tt_ttisstd = FALSE;
+ else {
+ ttisp->tt_ttisstd = *p++;
+ if (ttisp->tt_ttisstd != TRUE &&
+ ttisp->tt_ttisstd != FALSE)
+ return -1;
+ }
+ }
+ for (i = 0; i < sp->typecnt; ++i) {
+ register struct ttinfo * ttisp;
+
+ ttisp = &sp->ttis[i];
+ if (ttisgmtcnt == 0)
+ ttisp->tt_ttisgmt = FALSE;
+ else {
+ ttisp->tt_ttisgmt = *p++;
+ if (ttisp->tt_ttisgmt != TRUE &&
+ ttisp->tt_ttisgmt != FALSE)
+ return -1;
+ }
+ }
+ /*
+ ** Out-of-sort ats should mean we're running on a
+ ** signed time_t system but using a data file with
+ ** unsigned values (or vice versa).
+ */
+ for (i = 0; i < sp->timecnt - 2; ++i)
+ if (sp->ats[i] > sp->ats[i + 1]) {
+ ++i;
+ if (TYPE_SIGNED(time_t)) {
+ /*
+ ** Ignore the end (easy).
+ */
+ sp->timecnt = i;
+ } else {
+ /*
+ ** Ignore the beginning (harder).
+ */
+ register int j;
+
+ for (j = 0; j + i < sp->timecnt; ++j) {
+ sp->ats[j] = sp->ats[j + i];
+ sp->types[j] = sp->types[j + i];
+ }
+ sp->timecnt = j;
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+struct state *timezone_load(name)
+const char * name;
+{
+ struct state *sp = malloc( sizeof(struct state) );
+ int res;
+
+ if( !sp )
+ return NULL;
+ res = tzload( name, sp );
+ if( res < 0 )
+ {
+ free(sp);
+ return NULL;
+ }
+ return sp;
+}
+
+
+static const int mon_lengths[2][MONSPERYEAR] = {
+ { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+ { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+};
+
+static const int year_lengths[2] = {
+ DAYSPERNYEAR, DAYSPERLYEAR
+};
+
+/*
+** Given a pointer into a time zone string, scan until a character that is not
+** a valid character in a zone name is found. Return a pointer to that
+** character.
+*/
+
+static const char *
+getzname(strp)
+register const char * strp;
+{
+ register char c;
+
+ while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' &&
+ c != '+')
+ ++strp;
+ return strp;
+}
+
+/*
+** Given a pointer into an extended time zone string, scan until the ending
+** delimiter of the zone name is located. Return a pointer to the delimiter.
+**
+** As with getzname above, the legal character set is actually quite
+** restricted, with other characters producing undefined results.
+** We choose not to care - allowing almost anything to be in the zone abbrev.
+*/
+
+static const char *
+#if __STDC__
+getqzname(register const char *strp, const char delim)
+#else /* !__STDC__ */
+getqzname(strp, delim)
+register const char * strp;
+const char delim;
+#endif /* !__STDC__ */
+{
+ register char c;
+
+ while ((c = *strp) != '\0' && c != delim)
+ ++strp;
+ return strp;
+}
+
+/*
+** Given a pointer into a time zone string, extract a number from that string.
+** Check that the number is within a specified range; if it is not, return
+** NULL.
+** Otherwise, return a pointer to the first character not part of the number.
+*/
+
+static const char *
+getnum(strp, nump, min, max)
+register const char * strp;
+int * const nump;
+const int min;
+const int max;
+{
+ register char c;
+ register int num;
+
+ if (strp == NULL || !is_digit(c = *strp))
+ return NULL;
+ num = 0;
+ do {
+ num = num * 10 + (c - '0');
+ if (num > max)
+ return NULL; /* illegal value */
+ c = *++strp;
+ } while (is_digit(c));
+ if (num < min)
+ return NULL; /* illegal value */
+ *nump = num;
+ return strp;
+}
+
+/*
+** Given a pointer into a time zone string, extract a number of seconds,
+** in hh[:mm[:ss]] form, from the string.
+** If any error occurs, return NULL.
+** Otherwise, return a pointer to the first character not part of the number
+** of seconds.
+*/
+
+static const char *
+getsecs(strp, secsp)
+register const char * strp;
+long * const secsp;
+{
+ int num;
+
+ /*
+ ** `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
+ ** "M10.4.6/26", which does not conform to Posix,
+ ** but which specifies the equivalent of
+ ** ``02:00 on the first Sunday on or after 23 Oct''.
+ */
+ strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1);
+ if (strp == NULL)
+ return NULL;
+ *secsp = num * (long) SECSPERHOUR;
+ if (*strp == ':') {
+ ++strp;
+ strp = getnum(strp, &num, 0, MINSPERHOUR - 1);
+ if (strp == NULL)
+ return NULL;
+ *secsp += num * SECSPERMIN;
+ if (*strp == ':') {
+ ++strp;
+ /* `SECSPERMIN' allows for leap seconds. */
+ strp = getnum(strp, &num, 0, SECSPERMIN);
+ if (strp == NULL)
+ return NULL;
+ *secsp += num;
+ }
+ }
+ return strp;
+}
+
+/*
+** Given a pointer into a time zone string, extract an offset, in
+** [+-]hh[:mm[:ss]] form, from the string.
+** If any error occurs, return NULL.
+** Otherwise, return a pointer to the first character not part of the time.
+*/
+
+static const char *
+getoffset(strp, offsetp)
+register const char * strp;
+long * const offsetp;
+{
+ register int neg = 0;
+
+ if (*strp == '-') {
+ neg = 1;
+ ++strp;
+ } else if (*strp == '+')
+ ++strp;
+ strp = getsecs(strp, offsetp);
+ if (strp == NULL)
+ return NULL; /* illegal time */
+ if (neg)
+ *offsetp = -*offsetp;
+ return strp;
+}
+
+/*
+** Given a pointer into a time zone string, extract a rule in the form
+** date[/time]. See POSIX section 8 for the format of "date" and "time".
+** If a valid rule is not found, return NULL.
+** Otherwise, return a pointer to the first character not part of the rule.
+*/
+
+static const char *
+getrule(strp, rulep)
+const char * strp;
+register struct rule * const rulep;
+{
+ if (*strp == 'J') {
+ /*
+ ** Julian day.
+ */
+ rulep->r_type = JULIAN_DAY;
+ ++strp;
+ strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR);
+ } else if (*strp == 'M') {
+ /*
+ ** Month, week, day.
+ */
+ rulep->r_type = MONTH_NTH_DAY_OF_WEEK;
+ ++strp;
+ strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR);
+ if (strp == NULL)
+ return NULL;
+ if (*strp++ != '.')
+ return NULL;
+ strp = getnum(strp, &rulep->r_week, 1, 5);
+ if (strp == NULL)
+ return NULL;
+ if (*strp++ != '.')
+ return NULL;
+ strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1);
+ } else if (is_digit(*strp)) {
+ /*
+ ** Day of year.
+ */
+ rulep->r_type = DAY_OF_YEAR;
+ strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1);
+ } else return NULL; /* invalid format */
+ if (strp == NULL)
+ return NULL;
+ if (*strp == '/') {
+ /*
+ ** Time specified.
+ */
+ ++strp;
+ strp = getsecs(strp, &rulep->r_time);
+ } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */
+ return strp;
+}
+
+/*
+** Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the
+** year, a rule, and the offset from UTC at the time that rule takes effect,
+** calculate the Epoch-relative time that rule takes effect.
+*/
+
+static time_t
+transtime(janfirst, year, rulep, offset)
+const time_t janfirst;
+const int year;
+register const struct rule * const rulep;
+const long offset;
+{
+ register int leapyear;
+ register time_t value;
+ register int i;
+ int d, m1, yy0, yy1, yy2, dow;
+
+ INITIALIZE(value);
+ leapyear = isleap(year);
+ switch (rulep->r_type) {
+
+ case JULIAN_DAY:
+ /*
+ ** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap
+ ** years.
+ ** In non-leap years, or if the day number is 59 or less, just
+ ** add SECSPERDAY times the day number-1 to the time of
+ ** January 1, midnight, to get the day.
+ */
+ value = janfirst + (rulep->r_day - 1) * SECSPERDAY;
+ if (leapyear && rulep->r_day >= 60)
+ value += SECSPERDAY;
+ break;
+
+ case DAY_OF_YEAR:
+ /*
+ ** n - day of year.
+ ** Just add SECSPERDAY times the day number to the time of
+ ** January 1, midnight, to get the day.
+ */
+ value = janfirst + rulep->r_day * SECSPERDAY;
+ break;
+
+ case MONTH_NTH_DAY_OF_WEEK:
+ /*
+ ** Mm.n.d - nth "dth day" of month m.
+ */
+ value = janfirst;
+ for (i = 0; i < rulep->r_mon - 1; ++i)
+ value += mon_lengths[leapyear][i] * SECSPERDAY;
+
+ /*
+ ** Use Zeller's Congruence to get day-of-week of first day of
+ ** month.
+ */
+ m1 = (rulep->r_mon + 9) % 12 + 1;
+ yy0 = (rulep->r_mon <= 2) ? (year - 1) : year;
+ yy1 = yy0 / 100;
+ yy2 = yy0 % 100;
+ dow = ((26 * m1 - 2) / 10 +
+ 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
+ if (dow < 0)
+ dow += DAYSPERWEEK;
+
+ /*
+ ** "dow" is the day-of-week of the first day of the month. Get
+ ** the day-of-month (zero-origin) of the first "dow" day of the
+ ** month.
+ */
+ d = rulep->r_day - dow;
+ if (d < 0)
+ d += DAYSPERWEEK;
+ for (i = 1; i < rulep->r_week; ++i) {
+ if (d + DAYSPERWEEK >=
+ mon_lengths[leapyear][rulep->r_mon - 1])
+ break;
+ d += DAYSPERWEEK;
+ }
+
+ /*
+ ** "d" is the day-of-month (zero-origin) of the day we want.
+ */
+ value += d * SECSPERDAY;
+ break;
+ }
+
+ /*
+ ** "value" is the Epoch-relative time of 00:00:00 UTC on the day in
+ ** question. To get the Epoch-relative time of the specified local
+ ** time on that day, add the transition time and the current offset
+ ** from UTC.
+ */
+ return value + rulep->r_time + offset;
+}
+
+/*
+** Given a POSIX section 8-style TZ string, fill in the rule tables as
+** appropriate.
+*/
+
+static int
+tzparse(name, sp, lastditch)
+const char * name;
+register struct state * const sp;
+const int lastditch;
+{
+ const char * stdname;
+ const char * dstname;
+ size_t stdlen;
+ size_t dstlen;
+ long stdoffset;
+ long dstoffset;
+ register time_t * atp;
+ register unsigned char * typep;
+ register char * cp;
+ register int load_result;
+
+ INITIALIZE(dstname);
+ stdname = name;
+ if (lastditch) {
+ stdlen = strlen(name); /* length of standard zone name */
+ name += stdlen;
+ if (stdlen >= sizeof sp->chars)
+ stdlen = (sizeof sp->chars) - 1;
+ stdoffset = 0;
+ } else {
+ if (*name == '<') {
+ name++;
+ stdname = name;
+ name = getqzname(name, '>');
+ if (*name != '>')
+ return (-1);
+ stdlen = name - stdname;
+ name++;
+ } else {
+ name = getzname(name);
+ stdlen = name - stdname;
+ }
+ if (*name == '\0')
+ return -1;
+ name = getoffset(name, &stdoffset);
+ if (name == NULL)
+ return -1;
+ }
+ load_result = tzload(TZDEFRULES, sp);
+ if (load_result != 0)
+ sp->leapcnt = 0; /* so, we're off a little */
+ if (*name != '\0') {
+ if (*name == '<') {
+ dstname = ++name;
+ name = getqzname(name, '>');
+ if (*name != '>')
+ return -1;
+ dstlen = name - dstname;
+ name++;
+ } else {
+ dstname = name;
+ name = getzname(name);
+ dstlen = name - dstname; /* length of DST zone name */
+ }
+ if (*name != '\0' && *name != ',' && *name != ';') {
+ name = getoffset(name, &dstoffset);
+ if (name == NULL)
+ return -1;
+ } else dstoffset = stdoffset - SECSPERHOUR;
+ if (*name == '\0' && load_result != 0)
+ name = TZDEFRULESTRING;
+ if (*name == ',' || *name == ';') {
+ struct rule start;
+ struct rule end;
+ register int year;
+ register time_t janfirst;
+ time_t starttime;
+ time_t endtime;
+
+ ++name;
+ if ((name = getrule(name, &start)) == NULL)
+ return -1;
+ if (*name++ != ',')
+ return -1;
+ if ((name = getrule(name, &end)) == NULL)
+ return -1;
+ if (*name != '\0')
+ return -1;
+ sp->typecnt = 2; /* standard time and DST */
+ /*
+ ** Two transitions per year, from EPOCH_YEAR to 2037.
+ */
+ sp->timecnt = 2 * (2037 - EPOCH_YEAR + 1);
+ if (sp->timecnt > TZ_MAX_TIMES)
+ return -1;
+ sp->ttis[0].tt_gmtoff = -dstoffset;
+ sp->ttis[0].tt_isdst = 1;
+ sp->ttis[0].tt_abbrind = stdlen + 1;
+ sp->ttis[1].tt_gmtoff = -stdoffset;
+ sp->ttis[1].tt_isdst = 0;
+ sp->ttis[1].tt_abbrind = 0;
+ atp = sp->ats;
+ typep = sp->types;
+ janfirst = 0;
+ for (year = EPOCH_YEAR; year <= 2037; ++year) {
+ starttime = transtime(janfirst, year, &start,
+ stdoffset);
+ endtime = transtime(janfirst, year, &end,
+ dstoffset);
+ if (starttime > endtime) {
+ *atp++ = endtime;
+ *typep++ = 1; /* DST ends */
+ *atp++ = starttime;
+ *typep++ = 0; /* DST begins */
+ } else {
+ *atp++ = starttime;
+ *typep++ = 0; /* DST begins */
+ *atp++ = endtime;
+ *typep++ = 1; /* DST ends */
+ }
+ janfirst += year_lengths[isleap(year)] *
+ SECSPERDAY;
+ }
+ } else {
+ register long theirstdoffset;
+ register long theirdstoffset;
+ register long theiroffset;
+ register int isdst;
+ register int i;
+ register int j;
+
+ if (*name != '\0')
+ return -1;
+ /*
+ ** Initial values of theirstdoffset and theirdstoffset.
+ */
+ theirstdoffset = 0;
+ for (i = 0; i < sp->timecnt; ++i) {
+ j = sp->types[i];
+ if (!sp->ttis[j].tt_isdst) {
+ theirstdoffset =
+ -sp->ttis[j].tt_gmtoff;
+ break;
+ }
+ }
+ theirdstoffset = 0;
+ for (i = 0; i < sp->timecnt; ++i) {
+ j = sp->types[i];
+ if (sp->ttis[j].tt_isdst) {
+ theirdstoffset =
+ -sp->ttis[j].tt_gmtoff;
+ break;
+ }
+ }
+ /*
+ ** Initially we're assumed to be in standard time.
+ */
+ isdst = FALSE;
+ theiroffset = theirstdoffset;
+ /*
+ ** Now juggle transition times and types
+ ** tracking offsets as you do.
+ */
+ for (i = 0; i < sp->timecnt; ++i) {
+ j = sp->types[i];
+ sp->types[i] = sp->ttis[j].tt_isdst;
+ if (sp->ttis[j].tt_ttisgmt) {
+ /* No adjustment to transition time */
+ } else {
+ /*
+ ** If summer time is in effect, and the
+ ** transition time was not specified as
+ ** standard time, add the summer time
+ ** offset to the transition time;
+ ** otherwise, add the standard time
+ ** offset to the transition time.
+ */
+ /*
+ ** Transitions from DST to DDST
+ ** will effectively disappear since
+ ** POSIX provides for only one DST
+ ** offset.
+ */
+ if (isdst && !sp->ttis[j].tt_ttisstd) {
+ sp->ats[i] += dstoffset -
+ theirdstoffset;
+ } else {
+ sp->ats[i] += stdoffset -
+ theirstdoffset;
+ }
+ }
+ theiroffset = -sp->ttis[j].tt_gmtoff;
+ if (sp->ttis[j].tt_isdst)
+ theirdstoffset = theiroffset;
+ else theirstdoffset = theiroffset;
+ }
+ /*
+ ** Finally, fill in ttis.
+ ** ttisstd and ttisgmt need not be handled.
+ */
+ sp->ttis[0].tt_gmtoff = -stdoffset;
+ sp->ttis[0].tt_isdst = FALSE;
+ sp->ttis[0].tt_abbrind = 0;
+ sp->ttis[1].tt_gmtoff = -dstoffset;
+ sp->ttis[1].tt_isdst = TRUE;
+ sp->ttis[1].tt_abbrind = stdlen + 1;
+ sp->typecnt = 2;
+ }
+ } else {
+ dstlen = 0;
+ sp->typecnt = 1; /* only standard time */
+ sp->timecnt = 0;
+ sp->ttis[0].tt_gmtoff = -stdoffset;
+ sp->ttis[0].tt_isdst = 0;
+ sp->ttis[0].tt_abbrind = 0;
+ }
+ sp->charcnt = stdlen + 1;
+ if (dstlen != 0)
+ sp->charcnt += dstlen + 1;
+ if ((size_t) sp->charcnt > sizeof sp->chars)
+ return -1;
+ cp = sp->chars;
+ (void) strncpy(cp, stdname, stdlen);
+ cp += stdlen;
+ *cp++ = '\0';
+ if (dstlen != 0) {
+ (void) strncpy(cp, dstname, dstlen);
+ *(cp + dstlen) = '\0';
+ }
+ return 0;
+}
+
+static void
+gmtload(sp)
+struct state * const sp;
+{
+ if (tzload(gmt, sp) != 0)
+ (void) tzparse(gmt, sp, TRUE);
+}
+
+/*
+** The easy way to behave "as if no library function calls" localtime
+** is to not call it--so we drop its guts into "localsub", which can be
+** freely called. (And no, the PANS doesn't require the above behavior--
+** but it *is* desirable.)
+**
+** The unused offset argument is for the benefit of mktime variants.
+*/
+
+struct tm *
+localsub(timep, offset, tmp, sp)
+const time_t * const timep;
+const long offset;
+struct tm * const tmp;
+struct state * sp;
+{
+ register const struct ttinfo * ttisp;
+ register int i;
+ register struct tm * result;
+ const time_t t = *timep;
+
+#ifdef ALL_STATE
+ if (sp == NULL)
+ return gmtsub(timep, offset, tmp);
+#endif /* defined ALL_STATE */
+ if (sp->timecnt == 0 || t < sp->ats[0]) {
+ i = 0;
+ while (sp->ttis[i].tt_isdst)
+ if (++i >= sp->typecnt) {
+ i = 0;
+ break;
+ }
+ } else {
+ for (i = 1; i < sp->timecnt; ++i)
+ if (t < sp->ats[i])
+ break;
+ i = (int) sp->types[i - 1];
+ }
+ ttisp = &sp->ttis[i];
+ /*
+ ** To get (wrong) behavior that's compatible with System V Release 2.0
+ ** you'd replace the statement below with
+ ** t += ttisp->tt_gmtoff;
+ ** timesub(&t, 0L, sp, tmp);
+ */
+ result = timesub(&t, ttisp->tt_gmtoff, sp, tmp);
+ tmp->tm_isdst = ttisp->tt_isdst;
+ tzname[tmp->tm_isdst] = &sp->chars[ttisp->tt_abbrind];
+#ifdef TM_ZONE
+ tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind];
+#endif /* defined TM_ZONE */
+ return result;
+}
+
+/*
+** gmtsub is to gmtime as localsub is to localtime.
+*/
+
+struct tm *
+gmtsub(timep, offset, tmp)
+const time_t * const timep;
+const long offset;
+struct tm * const tmp;
+{
+ register struct tm * result;
+
+ if (!gmt_is_set) {
+ gmt_is_set = TRUE;
+#ifdef ALL_STATE
+ gmtptr = (struct state *) malloc(sizeof *gmtptr);
+ if (gmtptr != NULL)
+#endif /* defined ALL_STATE */
+ gmtload(gmtptr);
+ }
+ result = timesub(timep, offset, gmtptr, tmp);
+#ifdef TM_ZONE
+ /*
+ ** Could get fancy here and deliver something such as
+ ** "UTC+xxxx" or "UTC-xxxx" if offset is non-zero,
+ ** but this is no time for a treasure hunt.
+ */
+ if (offset != 0)
+ tmp->TM_ZONE = wildabbr;
+ else {
+#ifdef ALL_STATE
+ if (gmtptr == NULL)
+ tmp->TM_ZONE = gmt;
+ else tmp->TM_ZONE = gmtptr->chars;
+#endif /* defined ALL_STATE */
+#ifndef ALL_STATE
+ tmp->TM_ZONE = gmtptr->chars;
+#endif /* State Farm */
+ }
+#endif /* defined TM_ZONE */
+ return result;
+}
+
+/*
+** Return the number of leap years through the end of the given year
+** where, to make the math easy, the answer for year zero is defined as zero.
+*/
+
+static int
+leaps_thru_end_of(y)
+register const int y;
+{
+ return (y >= 0) ? (y / 4 - y / 100 + y / 400) :
+ -(leaps_thru_end_of(-(y + 1)) + 1);
+}
+
+static struct tm *
+timesub(timep, offset, sp, tmp)
+const time_t * const timep;
+const long offset;
+register const struct state * const sp;
+register struct tm * const tmp;
+{
+ register const struct lsinfo * lp;
+ register time_t tdays;
+ register int idays; /* unsigned would be so 2003 */
+ register long rem;
+ int y;
+ register const int * ip;
+ register long corr;
+ register int hit;
+ register int i;
+
+ corr = 0;
+ hit = 0;
+#ifdef ALL_STATE
+ i = (sp == NULL) ? 0 : sp->leapcnt;
+#endif /* defined ALL_STATE */
+#ifndef ALL_STATE
+ i = sp->leapcnt;
+#endif /* State Farm */
+ while (--i >= 0) {
+ lp = &sp->lsis[i];
+ if (*timep >= lp->ls_trans) {
+ if (*timep == lp->ls_trans) {
+ hit = ((i == 0 && lp->ls_corr > 0) ||
+ lp->ls_corr > sp->lsis[i - 1].ls_corr);
+ if (hit)
+ while (i > 0 &&
+ sp->lsis[i].ls_trans ==
+ sp->lsis[i - 1].ls_trans + 1 &&
+ sp->lsis[i].ls_corr ==
+ sp->lsis[i - 1].ls_corr + 1) {
+ ++hit;
+ --i;
+ }
+ }
+ corr = lp->ls_corr;
+ break;
+ }
+ }
+ y = EPOCH_YEAR;
+ tdays = *timep / SECSPERDAY;
+ rem = *timep - tdays * SECSPERDAY;
+ while (tdays < 0 || tdays >= year_lengths[isleap(y)]) {
+ int newy;
+ register time_t tdelta;
+ register int idelta;
+ register int leapdays;
+
+ tdelta = tdays / DAYSPERLYEAR;
+ idelta = tdelta;
+ if (tdelta - idelta >= 1 || idelta - tdelta >= 1)
+ return NULL;
+ if (idelta == 0)
+ idelta = (tdays < 0) ? -1 : 1;
+ newy = y;
+ if (increment_overflow(&newy, idelta))
+ return NULL;
+ leapdays = leaps_thru_end_of(newy - 1) -
+ leaps_thru_end_of(y - 1);
+ tdays -= ((time_t) newy - y) * DAYSPERNYEAR;
+ tdays -= leapdays;
+ y = newy;
+ }
+ {
+ register long seconds;
+
+ seconds = tdays * SECSPERDAY + 0.5;
+ tdays = seconds / SECSPERDAY;
+ rem += seconds - tdays * SECSPERDAY;
+ }
+ /*
+ ** Given the range, we can now fearlessly cast...
+ */
+ idays = tdays;
+ rem += offset - corr;
+ while (rem < 0) {
+ rem += SECSPERDAY;
+ --idays;
+ }
+ while (rem >= SECSPERDAY) {
+ rem -= SECSPERDAY;
+ ++idays;
+ }
+ while (idays < 0) {
+ if (increment_overflow(&y, -1))
+ return NULL;
+ idays += year_lengths[isleap(y)];
+ }
+ while (idays >= year_lengths[isleap(y)]) {
+ idays -= year_lengths[isleap(y)];
+ if (increment_overflow(&y, 1))
+ return NULL;
+ }
+ tmp->tm_year = y;
+ if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
+ return NULL;
+ tmp->tm_yday = idays;
+ /*
+ ** The "extra" mods below avoid overflow problems.
+ */
+ tmp->tm_wday = EPOCH_WDAY +
+ ((y - EPOCH_YEAR) % DAYSPERWEEK) *
+ (DAYSPERNYEAR % DAYSPERWEEK) +
+ leaps_thru_end_of(y - 1) -
+ leaps_thru_end_of(EPOCH_YEAR - 1) +
+ idays;
+ tmp->tm_wday %= DAYSPERWEEK;
+ if (tmp->tm_wday < 0)
+ tmp->tm_wday += DAYSPERWEEK;
+ tmp->tm_hour = (int) (rem / SECSPERHOUR);
+ rem %= SECSPERHOUR;
+ tmp->tm_min = (int) (rem / SECSPERMIN);
+ /*
+ ** A positive leap second requires a special
+ ** representation. This uses "... ??:59:60" et seq.
+ */
+ tmp->tm_sec = (int) (rem % SECSPERMIN) + hit;
+ ip = mon_lengths[isleap(y)];
+ for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon))
+ idays -= ip[tmp->tm_mon];
+ tmp->tm_mday = (int) (idays + 1);
+ tmp->tm_isdst = 0;
+#ifdef TM_GMTOFF
+ tmp->TM_GMTOFF = offset;
+#endif /* defined TM_GMTOFF */
+ return tmp;
+}
+
+
+/*
+** Adapted from code provided by Robert Elz, who writes:
+** The "best" way to do mktime I think is based on an idea of Bob
+** Kridle's (so its said...) from a long time ago.
+** It does a binary search of the time_t space. Since time_t's are
+** just 32 bits, its a max of 32 iterations (even at 64 bits it
+** would still be very reasonable).
+*/
+
+#ifndef WRONG
+#define WRONG (-1)
+#endif /* !defined WRONG */
+
+/*
+** Simplified normalize logic courtesy Paul Eggert.
+*/
+
+static int
+increment_overflow(number, delta)
+int * number;
+int delta;
+{
+ int number0;
+
+ number0 = *number;
+ *number += delta;
+ return (*number < number0) != (delta < 0);
+}
+
+int tz_init( const char *zoneinfo_dir )
+{
+ char *ptr;
+ int fd;
+
+ if( zoneinfo_dir == NULL )
+ zoneinfo_dir = TZDIR;
+
+ ptr = malloc( strlen(zoneinfo_dir) + 10 );
+ sprintf( ptr, "%s/zone.tab", zoneinfo_dir );
+ fd = open( ptr, O_RDONLY );
+ free(ptr);
+
+ if( fd < 0 )
+ return -1;
+ close(fd);
+ if( tzdir )
+ free(tzdir);
+ tzdir = strdup(zoneinfo_dir);
+ return 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/localtime.h Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,16 @@
+/*************************************************************************
+ * Header file for timezone module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *************************************************************************/
+
+#include <time.h>
+
+struct state;
+
+struct state *timezone_load (const char * name);
+struct tm * gmtsub (const time_t * timep, long offset,
+ struct tm * tmp);
+struct tm * localsub (const time_t * timep, long offset,
+ struct tm * tmp, struct state *sp);
+int tz_init(const char *zoneinfo_dir);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/private.h Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,316 @@
+/*************************************************************************
+ * Private Header file for timezone module
+ * copied from Olson timezone code, licence unchanged
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ *************************************************************************/
+
+#ifndef PRIVATE_H
+
+#define PRIVATE_H
+
+/*
+** This file is in the public domain, so clarified as of
+** 1996-06-05 by Arthur David Olson.
+*/
+
+/*
+** This header is for use ONLY with the time conversion code.
+** There is no guarantee that it will remain unchanged,
+** or that it will remain at all.
+** Do NOT copy it to any system include directory.
+** Thank you!
+*/
+
+/*
+** ID
+*/
+
+#define GRANDPARENTED "Local time zone must be set--see zic manual page"
+
+/*
+** Defaults for preprocessor symbols.
+** You can override these in your C compiler options, e.g. `-DHAVE_ADJTIME=0'.
+*/
+
+#ifndef HAVE_ADJTIME
+#define HAVE_ADJTIME 1
+#endif /* !defined HAVE_ADJTIME */
+
+#ifndef HAVE_GETTEXT
+#define HAVE_GETTEXT 0
+#endif /* !defined HAVE_GETTEXT */
+
+#ifndef HAVE_INCOMPATIBLE_CTIME_R
+#define HAVE_INCOMPATIBLE_CTIME_R 0
+#endif /* !defined INCOMPATIBLE_CTIME_R */
+
+#ifndef HAVE_SETTIMEOFDAY
+#define HAVE_SETTIMEOFDAY 3
+#endif /* !defined HAVE_SETTIMEOFDAY */
+
+#ifndef HAVE_STRERROR
+#define HAVE_STRERROR 1
+#endif /* !defined HAVE_STRERROR */
+
+#ifndef HAVE_SYMLINK
+#define HAVE_SYMLINK 1
+#endif /* !defined HAVE_SYMLINK */
+
+#ifndef HAVE_SYS_STAT_H
+#define HAVE_SYS_STAT_H 1
+#endif /* !defined HAVE_SYS_STAT_H */
+
+#ifndef HAVE_SYS_WAIT_H
+#define HAVE_SYS_WAIT_H 1
+#endif /* !defined HAVE_SYS_WAIT_H */
+
+#ifndef HAVE_UNISTD_H
+#define HAVE_UNISTD_H 1
+#endif /* !defined HAVE_UNISTD_H */
+
+#ifndef HAVE_UTMPX_H
+#define HAVE_UTMPX_H 0
+#endif /* !defined HAVE_UTMPX_H */
+
+#ifndef LOCALE_HOME
+#define LOCALE_HOME "/usr/lib/locale"
+#endif /* !defined LOCALE_HOME */
+
+#if HAVE_INCOMPATIBLE_CTIME_R
+#define asctime_r _incompatible_asctime_r
+#define ctime_r _incompatible_ctime_r
+#endif /* HAVE_INCOMPATIBLE_CTIME_R */
+
+/*
+** Nested includes
+*/
+
+#include "sys/types.h" /* for time_t */
+#include "stdio.h"
+#include "errno.h"
+#include "string.h"
+#include "limits.h" /* for CHAR_BIT */
+#include "time.h"
+#include "stdlib.h"
+
+#if HAVE_GETTEXT
+#include "libintl.h"
+#endif /* HAVE_GETTEXT */
+
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h> /* for WIFEXITED and WEXITSTATUS */
+#endif /* HAVE_SYS_WAIT_H */
+
+#ifndef WIFEXITED
+#define WIFEXITED(status) (((status) & 0xff) == 0)
+#endif /* !defined WIFEXITED */
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(status) (((status) >> 8) & 0xff)
+#endif /* !defined WEXITSTATUS */
+
+#if HAVE_UNISTD_H
+#include "unistd.h" /* for F_OK and R_OK */
+#endif /* HAVE_UNISTD_H */
+
+#if !HAVE_UNISTD_H
+#ifndef F_OK
+#define F_OK 0
+#endif /* !defined F_OK */
+#ifndef R_OK
+#define R_OK 4
+#endif /* !defined R_OK */
+#endif /* !HAVE_UNISTD_H */
+
+/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
+#define is_digit(c) ((unsigned)(c) - '0' <= 9)
+
+/*
+** Workarounds for compilers/systems.
+*/
+
+/*
+** SunOS 4.1.1 cc lacks prototypes.
+*/
+
+#ifndef P
+#if __STDC__
+#define P(x) x
+#else /* !__STDC__ */
+#define P(x) ()
+#endif /* !__STDC__ */
+#endif /* !defined P */
+
+/*
+** SunOS 4.1.1 headers lack EXIT_SUCCESS.
+*/
+
+#ifndef EXIT_SUCCESS
+#define EXIT_SUCCESS 0
+#endif /* !defined EXIT_SUCCESS */
+
+/*
+** SunOS 4.1.1 headers lack EXIT_FAILURE.
+*/
+
+#ifndef EXIT_FAILURE
+#define EXIT_FAILURE 1
+#endif /* !defined EXIT_FAILURE */
+
+/*
+** SunOS 4.1.1 headers lack FILENAME_MAX.
+*/
+
+#ifndef FILENAME_MAX
+
+#ifndef MAXPATHLEN
+#ifdef unix
+#include "sys/param.h"
+#endif /* defined unix */
+#endif /* !defined MAXPATHLEN */
+
+#ifdef MAXPATHLEN
+#define FILENAME_MAX MAXPATHLEN
+#endif /* defined MAXPATHLEN */
+#ifndef MAXPATHLEN
+#define FILENAME_MAX 1024 /* Pure guesswork */
+#endif /* !defined MAXPATHLEN */
+
+#endif /* !defined FILENAME_MAX */
+
+/*
+** SunOS 4.1.1 libraries lack remove.
+*/
+
+#ifndef remove
+extern int unlink P((const char * filename));
+#define remove unlink
+#endif /* !defined remove */
+
+/*
+** Some ancient errno.h implementations don't declare errno.
+** But some newer errno.h implementations define it as a macro.
+** Fix the former without affecting the latter.
+*/
+
+#ifndef errno
+extern int errno;
+#endif /* !defined errno */
+
+/*
+** Some time.h implementations don't declare asctime_r.
+** Others might define it as a macro.
+** Fix the former without affecting the latter.
+*/
+
+#ifndef asctime_r
+extern char * asctime_r();
+#endif
+
+/*
+** Private function declarations.
+*/
+
+char * icalloc P((int nelem, int elsize));
+char * icatalloc P((char * old, const char * new));
+char * icpyalloc P((const char * string));
+char * imalloc P((int n));
+void * irealloc P((void * pointer, int size));
+void icfree P((char * pointer));
+void ifree P((char * pointer));
+const char *scheck P((const char *string, const char *format));
+
+/*
+** Finally, some convenience items.
+*/
+
+#ifndef TRUE
+#define TRUE 1
+#endif /* !defined TRUE */
+
+#ifndef FALSE
+#define FALSE 0
+#endif /* !defined FALSE */
+
+#ifndef TYPE_BIT
+#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT)
+#endif /* !defined TYPE_BIT */
+
+#ifndef TYPE_SIGNED
+#define TYPE_SIGNED(type) (((type) -1) < 0)
+#endif /* !defined TYPE_SIGNED */
+
+/*
+** Since the definition of TYPE_INTEGRAL contains floating point numbers,
+** it cannot be used in preprocessor directives.
+*/
+
+#ifndef TYPE_INTEGRAL
+#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5)
+#endif /* !defined TYPE_INTEGRAL */
+
+#ifndef INT_STRLEN_MAXIMUM
+/*
+** 302 / 1000 is log10(2.0) rounded up.
+** Subtract one for the sign bit if the type is signed;
+** add one for integer division truncation;
+** add one more for a minus sign if the type is signed.
+*/
+#define INT_STRLEN_MAXIMUM(type) \
+ ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \
+ 1 + TYPE_SIGNED(type))
+#endif /* !defined INT_STRLEN_MAXIMUM */
+
+/*
+** INITIALIZE(x)
+*/
+
+#ifndef GNUC_or_lint
+#ifdef lint
+#define GNUC_or_lint
+#endif /* defined lint */
+#ifndef lint
+#ifdef __GNUC__
+#define GNUC_or_lint
+#endif /* defined __GNUC__ */
+#endif /* !defined lint */
+#endif /* !defined GNUC_or_lint */
+
+#ifndef INITIALIZE
+#ifdef GNUC_or_lint
+#define INITIALIZE(x) ((x) = 0)
+#endif /* defined GNUC_or_lint */
+#ifndef GNUC_or_lint
+#define INITIALIZE(x)
+#endif /* !defined GNUC_or_lint */
+#endif /* !defined INITIALIZE */
+
+/*
+** For the benefit of GNU folk...
+** `_(MSGID)' uses the current locale's message library string for MSGID.
+** The default is to use gettext if available, and use MSGID otherwise.
+*/
+
+#ifndef _
+#if HAVE_GETTEXT
+#define _(msgid) gettext(msgid)
+#else /* !HAVE_GETTEXT */
+#define _(msgid) msgid
+#endif /* !HAVE_GETTEXT */
+#endif /* !defined _ */
+
+#ifndef TZ_DOMAIN
+#define TZ_DOMAIN "tz"
+#endif /* !defined TZ_DOMAIN */
+
+#if HAVE_INCOMPATIBLE_CTIME_R
+#undef asctime_r
+#undef ctime_r
+char *asctime_r P((struct tm const *, char *));
+char *ctime_r P((time_t const *, char *));
+#endif /* HAVE_INCOMPATIBLE_CTIME_R */
+
+/*
+** UNIX was a registered trademark of The Open Group in 2003.
+*/
+
+#endif /* !defined PRIVATE_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/recurse.c Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,141 @@
+/*************************************************************************
+ * Recursion module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *
+ * Provides a function to recurse a directory and call a callback for each
+ * file found.
+ *************************************************************************/
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "recurse.h"
+
+#if 1
+/* GLibc specific version. In this version, the entries are sorted */
+/* We assume dirname ends in a /, prefix also unless empty */
+static int
+recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data)
+{
+ struct dirent **namelist;
+ int ents;
+ struct dirent *ent;
+ int i;
+ int ret = 0;
+
+ if((ents = scandir(dirname, &namelist, 0, alphasort)) < 0)
+ return -1;
+
+ for (i = 0; i < ents; i++)
+ {
+ char *ptr;
+ struct stat s;
+
+ ent = namelist[i];
+ asprintf(&ptr, "%s%s", dirname, ent->d_name);
+ if(stat(ptr, &s) < 0)
+ {
+ free(ptr);
+ continue;
+ }
+
+ if(S_ISREG(s.st_mode))
+ {
+ free(ptr);
+ asprintf(&ptr, "%s%s", prefix, ent->d_name);
+ ret = func(ptr, data);
+ }
+ else if(S_ISDIR(s.st_mode))
+ {
+ char *newdirname, *newprefix;
+
+ if(ent->d_name[0] != '.')
+ {
+ asprintf(&newdirname, "%s%s/", dirname, ent->d_name);
+ asprintf(&newprefix, "%s%s/", prefix, ent->d_name);
+ ret = recurse_directory_int(newdirname, newprefix, func, data);
+ free(newdirname);
+ free(newprefix);
+ }
+ }
+ free(ptr);
+ if(ret < 0)
+ break;
+ }
+ free(namelist);
+ return 0;
+}
+#else
+/* generic version, here they are unsorted */
+/* We assume dirname ends in a /, prefix also unless empty */
+static int
+recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data)
+{
+ DIR *dir;
+ struct dirent *ent;
+ int ret = 0;
+
+ dir = opendir(dirname);
+ if(!dir)
+ return -1;
+ while ((ent = readdir(dir)) != NULL)
+ {
+ char *ptr;
+ struct stat s;
+
+ asprintf(&ptr, "%s%s", dirname, ent->d_name);
+ if(stat(ptr, &s) < 0)
+ {
+ free(ptr);
+ continue;
+ }
+
+ if(S_ISREG(s.st_mode))
+ {
+ free(ptr);
+ asprintf(&ptr, "%s%s", prefix, ent->d_name);
+ ret = func(ptr, data);
+ }
+ else if(S_ISDIR(s.st_mode))
+ {
+ char *newdirname, *newprefix;
+
+ if(ent->d_name[0] != '.')
+ {
+ asprintf(&newdirname, "%s%s/", dirname, ent->d_name);
+ asprintf(&newprefix, "%s%s/", prefix, ent->d_name);
+ ret = recurse_directory_int(newdirname, newprefix, func, data);
+ free(newdirname);
+ free(newprefix);
+ }
+ }
+ free(ptr);
+ if(ret < 0)
+ break;
+ }
+ closedir(dir);
+ return ret;
+}
+#endif
+
+int
+recurse_directory(char *dirname, DirRecurseMatch func, void *data)
+{
+ char *newdirname = NULL;
+ int ret;
+
+ if(dirname[strlen(dirname) - 1] != '/')
+ asprintf(&newdirname, "%s/", dirname);
+
+ ret = recurse_directory_int(newdirname ? newdirname : dirname, "", func, data);
+
+ if(newdirname)
+ free(newdirname);
+ return ret;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/recurse.h Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,9 @@
+/*************************************************************************
+ * Header file for recursion module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *************************************************************************/
+
+typedef int (*DirRecurseMatch)(char *filename, void *data);
+int recurse_directory( char *dirname, DirRecurseMatch func, void *data );
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/recursetest.c Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,25 @@
+/*************************************************************************
+ * Recursion test module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *
+ * Code to test the recursion module.
+ *************************************************************************/
+
+#include <stdio.h>
+
+#include "recurse.h"
+
+int
+process_entry(char *str, void *ptr)
+{
+ printf("%s\n", str);
+ return 0;
+}
+
+int
+main()
+{
+ recurse_directory("/usr/share/zoneinfo", process_entry, main);
+ return 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/timetest.c Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,28 @@
+/*************************************************************************
+ * Timezone test module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *
+ * Code to test the timezone module.
+ *************************************************************************/
+
+//#include "private.h"
+//#include "tzfile.h"
+#include "localtime.h"
+
+int
+main()
+{
+ struct state *state;
+ time_t now = time(NULL);
+ struct tm tm;
+
+ state = timezone_load("Australia/Sydney");
+
+ if(!state)
+ return 0;
+
+ localsub(&now, 0, &tm, state);
+ gmtsub(&now, 0, &tm);
+ return 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytools/tzfile.h Sun Oct 21 19:00:33 2007 -0400
@@ -0,0 +1,175 @@
+/*************************************************************************
+ * Private Header file for timezone module
+ * copied from Olson timezone code, licence unchanged
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ *************************************************************************/
+
+#ifndef TZFILE_H
+
+#define TZFILE_H
+
+/*
+** This file is in the public domain, so clarified as of
+** 1996-06-05 by Arthur David Olson.
+*/
+
+/*
+** This header is for use ONLY with the time conversion code.
+** There is no guarantee that it will remain unchanged,
+** or that it will remain at all.
+** Do NOT copy it to any system include directory.
+** Thank you!
+*/
+
+/*
+** ID
+*/
+
+/*
+** Information about time zone files.
+*/
+
+#ifndef TZDIR
+#define TZDIR "/usr/share/zoneinfo" /* Default time zone object file directory */
+#endif /* !defined TZDIR */
+
+#ifndef TZDEFAULT
+#define TZDEFAULT "localtime"
+#endif /* !defined TZDEFAULT */
+
+#ifndef TZDEFRULES
+#define TZDEFRULES "posixrules"
+#endif /* !defined TZDEFRULES */
+
+/*
+** Each file begins with. . .
+*/
+
+#define TZ_MAGIC "TZif"
+
+struct tzhead {
+ char tzh_magic[4]; /* TZ_MAGIC */
+ char tzh_reserved[16]; /* reserved for future use */
+ char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */
+ char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
+ char tzh_leapcnt[4]; /* coded number of leap seconds */
+ char tzh_timecnt[4]; /* coded number of transition times */
+ char tzh_typecnt[4]; /* coded number of local time types */
+ char tzh_charcnt[4]; /* coded number of abbr. chars */
+};
+
+/*
+** . . .followed by. . .
+**
+** tzh_timecnt (char [4])s coded transition times a la time(2)
+** tzh_timecnt (unsigned char)s types of local time starting at above
+** tzh_typecnt repetitions of
+** one (char [4]) coded UTC offset in seconds
+** one (unsigned char) used to set tm_isdst
+** one (unsigned char) that's an abbreviation list index
+** tzh_charcnt (char)s '\0'-terminated zone abbreviations
+** tzh_leapcnt repetitions of
+** one (char [4]) coded leap second transition times
+** one (char [4]) total correction after above
+** tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition
+** time is standard time, if FALSE,
+** transition time is wall clock time
+** if absent, transition times are
+** assumed to be wall clock time
+** tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition
+** time is UTC, if FALSE,
+** transition time is local time
+** if absent, transition times are
+** assumed to be local time
+*/
+
+/*
+** In the current implementation, "tzset()" refuses to deal with files that
+** exceed any of the limits below.
+*/
+
+#ifndef TZ_MAX_TIMES
+/*
+** The TZ_MAX_TIMES value below is enough to handle a bit more than a
+** year's worth of solar time (corrected daily to the nearest second) or
+** 138 years of Pacific Presidential Election time
+** (where there are three time zone transitions every fourth year).
+*/
+#define TZ_MAX_TIMES 370
+#endif /* !defined TZ_MAX_TIMES */
+
+#ifndef TZ_MAX_TYPES
+#ifndef NOSOLAR
+#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */
+#endif /* !defined NOSOLAR */
+#ifdef NOSOLAR
+/*
+** Must be at least 14 for Europe/Riga as of Jan 12 1995,
+** as noted by Earl Chew.
+*/
+#define TZ_MAX_TYPES 20 /* Maximum number of local time types */
+#endif /* !defined NOSOLAR */
+#endif /* !defined TZ_MAX_TYPES */
+
+#ifndef TZ_MAX_CHARS
+#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */
+ /* (limited by what unsigned chars can hold) */
+#endif /* !defined TZ_MAX_CHARS */
+
+#ifndef TZ_MAX_LEAPS
+#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */
+#endif /* !defined TZ_MAX_LEAPS */
+
+#define SECSPERMIN 60
+#define MINSPERHOUR 60
+#define HOURSPERDAY 24
+#define DAYSPERWEEK 7
+#define DAYSPERNYEAR 365
+#define DAYSPERLYEAR 366
+#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
+#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY)
+#define MONSPERYEAR 12
+
+#define TM_SUNDAY 0
+#define TM_MONDAY 1
+#define TM_TUESDAY 2
+#define TM_WEDNESDAY 3
+#define TM_THURSDAY 4
+#define TM_FRIDAY 5
+#define TM_SATURDAY 6
+
+#define TM_JANUARY 0
+#define TM_FEBRUARY 1
+#define TM_MARCH 2
+#define TM_APRIL 3
+#define TM_MAY 4
+#define TM_JUNE 5
+#define TM_JULY 6
+#define TM_AUGUST 7
+#define TM_SEPTEMBER 8
+#define TM_OCTOBER 9
+#define TM_NOVEMBER 10
+#define TM_DECEMBER 11
+
+#define TM_YEAR_BASE 1900
+
+#define EPOCH_YEAR 1970
+#define EPOCH_WDAY TM_THURSDAY
+
+#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
+
+/*
+** Since everything in isleap is modulo 400 (or a factor of 400), we know that
+** isleap(y) == isleap(y % 400)
+** and so
+** isleap(a + b) == isleap((a + b) % 400)
+** or
+** isleap(a + b) == isleap(a % 400 + b % 400)
+** This is true even if % means modulo rather than Fortran remainder
+** (which is allowed by C89 but not C99).
+** We use this to avoid addition overflow problems.
+*/
+
+#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400)
+
+#endif /* !defined TZFILE_H */