pidgin/purple-plugin-pack
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 + 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. + 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 + 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 + 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 + 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 + 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 +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 + 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 +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 +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 + 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. + 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, + 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) +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 @@
+#************************************************************************* +#* by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 +#* Licenced under the GNU General Public Licence version 2. +#*************************************************************************/ +# Use internal timezone library rather than system one: yes or no +# Use Custom GTK widget for timezone selection: yes or no +# 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` +ifeq ($(PRIVATE_TZLIB),yes) +CFLAGS += -DPRIVATE_TZLIB +PRIVATE_TZLIB_OBJS=localtime.o +ifeq ($(CUSTOM_GTK),yes) +PRIVATE_TZLIB_OBJS += gtktimezone.o +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 $@ $^ + rm -f *.o $(LIB_TARGETS) $(EXE_TARGETS) + 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) + 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: +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. + ************************************************************************* + * 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. + ************************************************************************* + * 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 + ************************************************************************* +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: +If yes, a priviate timezone library will be used which is more efficient and +cleaner in implementation, but does duplicate some system functionality. +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. +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 @@
+/************************************************************************* + * 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 + *************************************************************************/ +#define PLUGIN "core-kleptog-buddyedit" +#include "gaim-compat.h" +#include "debug.h" /* Debug output functions */ +#include "request.h" /* Requests stuff */ +static GaimPlugin *plugin_self; +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 */ + 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)) + 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; + data = (GaimBlistNode *) newbuddy; + gaim_blist_alias_buddy(buddy, alias); + 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); + 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); + case GAIM_BLIST_CHAT_NODE: + GaimChat *chat = (GaimChat *) data; + gboolean new_chat = FALSE; + 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 +#if GAIM_MAJOR_VERSION >= 2 + if(newaccount != chat->account) + 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; /* Not yet */ + oldvalue = g_hash_table_lookup(chat->components, pce->identifier); + newvalue = gaim_request_fields_get_string(fields, pce->identifier); + if(strcmp(oldvalue, newvalue) != 0) + 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; + oldvalue = g_hash_table_lookup(chat->components, pce->identifier); + g_hash_table_replace(components, g_strdup(pce->identifier), + newvalue = gaim_request_fields_get_string(fields, pce->identifier); + g_hash_table_replace(components, g_strdup(pce->identifier), + GaimChat *newchat = gaim_chat_new(newaccount, NULL, components); + gaim_blist_add_chat(newchat, NULL, data); /* Copy it to correct location */ + data = (GaimBlistNode *) newchat; + else /* Just updating values in old chat */ + for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp)) + struct proto_chat_entry *pce = tmp->data; +#if GAIM_MAJOR_VERSION >= 2 + newvalue = gaim_request_fields_get_string(fields, pce->identifier); + g_hash_table_replace(chat->components, g_strdup(pce->identifier), + const char *alias = gaim_request_fields_get_string(fields, "alias"); + gaim_blist_alias_chat(chat, alias); + case GAIM_BLIST_OTHER_NODE: + gaim_signal_emit(gaim_blist_get_handle(), PLUGIN "-submit-fields", fields, data); + 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); + gaim_blist_schedule_save(); +static GaimAccount *buddyedit_account_filter_func_data; +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)-> +/* Node is either a contact or a buddy */ +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(); + 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"; + 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"; + 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"; + 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); + 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; +#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); + 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); + 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"; + request_title = "Edit"; + 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); +buddy_menu_cb(GaimBlistNode * node, GList ** menu, void *data) +#if GAIM_MAJOR_VERSION < 2 + GaimBlistNodeAction *action; + GaimMenuAction *action; + /* 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: + case GAIM_BLIST_OTHER_NODE: +#if GAIM_MAJOR_VERSION < 2 + action = gaim_blist_node_action_new("Edit...", buddy_edit_cb, NULL); + action = gaim_menu_action_new("Edit...", GAIM_CALLBACK(buddy_edit_cb), NULL, NULL); + *menu = g_list_append(*menu, action); +plugin_load(GaimPlugin * 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); +static GaimPluginInfo info = { + 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/", +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 + *************************************************************************/ +#define PLUGIN "core-kleptog-buddylang" +#define SETTING_NAME "language" +#define CONTROL_NAME PLUGIN "-" SETTING_NAME +#include "gaim-compat.h" +#include "debug.h" /* Debug output functions */ +#include "util.h" /* Menu functions */ +#include "request.h" /* Requests stuff */ +#include "conversation.h" /* Conversation stuff */ +#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 +buddy_get_language(GaimBlistNode * node, gboolean resolve) + GaimBlistNode *datanode = NULL; + case GAIM_BLIST_BUDDY_NODE: + datanode = (GaimBlistNode *) gaim_buddy_get_contact((GaimBuddy *) node); + case GAIM_BLIST_CONTACT_NODE: + case GAIM_BLIST_GROUP_NODE: + language = gaim_blist_node_get_string(datanode, SETTING_NAME); + if(language && strcmp(language, "none") == 0) + 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) +buddylang_createconv_cb(GaimConversation * conv, void *data) +#ifdef PIDGIN_CONVERSATION + PidginConversation *gtkconv; + GaimGtkConversation *gtkconv; +#if GAIM_MAJOR_VERSION < 2 + if(gaim_conversation_get_type(conv) != GAIM_CONV_IM) + if(gaim_conversation_get_type(conv) != GAIM_CONV_TYPE_IM) + name = gaim_conversation_get_name(conv); + buddy = gaim_find_buddy(gaim_conversation_get_account(conv), name); + language = buddy_get_language((GaimBlistNode *) buddy, TRUE); +#ifdef PIDGIN_CONVERSATION + gtkconv = PIDGIN_CONVERSATION(conv); + gtkconv = GAIM_GTK_CONVERSATION(conv); + gtkspell = gtkspell_get_from_text_view(GTK_TEXT_VIEW(gtkconv->entry)); + 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); + str = g_strdup_printf("Spellcheck language: %s", language); + gaim_conversation_write(conv, PLUGIN, str, GAIM_MESSAGE_SYSTEM, time(NULL)); +buddylang_submitfields_cb(GaimRequestFields * fields, GaimBlistNode * data) + GaimRequestField *list; + gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "buddylang_submitfields_cb(%p,%p)\n", fields, data); + case GAIM_BLIST_BUDDY_NODE: + node = (GaimBlistNode *) gaim_buddy_get_contact((GaimBuddy *) data); + case GAIM_BLIST_CONTACT_NODE: + case GAIM_BLIST_GROUP_NODE: + /* code handles either case */ + case GAIM_BLIST_CHAT_NODE: + case GAIM_BLIST_OTHER_NODE: + list = gaim_request_fields_get_field(fields, CONTROL_NAME); + sellist = gaim_request_field_list_get_selected(list); + 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"); + gaim_blist_node_remove_setting(node, SETTING_NAME); +/* Node is either a contact or a buddy */ +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; + struct AspellConfig *aspellconfig; + struct AspellDictInfoList *dictinfolist; + struct AspellDictInfoEnumeration *dictinfoenumeration; + case GAIM_BLIST_BUDDY_NODE: + case GAIM_BLIST_CONTACT_NODE: + case GAIM_BLIST_GROUP_NODE: + case GAIM_BLIST_CHAT_NODE: + case GAIM_BLIST_OTHER_NODE: + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + 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(); + gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "new_aspell_config() returned NULL\n"); + dictinfolist = get_aspell_dict_info_list(aspellconfig); + gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "get_aspell_dict_info_list() returned NULL\n"); + 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"); + /* 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) + 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(strcmp(language, "none") == 0) + gaim_request_field_list_add_selected(field, "<Disabled>"); + gaim_request_field_list_add_selected(field, language); + gaim_request_field_list_add_selected(field, "<Default>"); + gaim_request_field_group_add_field(group, field); +plugin_load(GaimPlugin * 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); +static GaimPluginInfo info = { + "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/", +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 @@
+/************************************************************************* + * 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 + *************************************************************************/ +#define PLUGIN "core-kleptog-buddynotes" +#define SETTING_NAME "notes" +#define CONTROL_NAME PLUGIN "-" SETTING_NAME +#include "gaim-compat.h" +#include "debug.h" /* Debug output functions */ +#include "util.h" /* Menu functions */ +#include "request.h" /* Requests stuff */ +#include "conversation.h" /* Conversation stuff */ +#define TIMEZONE_FLAG ((void*)1) +static GaimPlugin *plugin_self; +buddy_get_notes(GaimBlistNode * node) + GaimContact *contact = NULL; + case GAIM_BLIST_BUDDY_NODE: + contact = gaim_buddy_get_contact((GaimBuddy *) node); + case GAIM_BLIST_CONTACT_NODE: + contact = (GaimContact *) node; + return gaim_blist_node_get_string((GaimBlistNode *) contact, SETTING_NAME); +buddynotes_createconv_cb(GaimConversation * conv, void *data) +#if GAIM_MAJOR_VERSION < 2 + if(gaim_conversation_get_type(conv) != GAIM_CONV_IM) + if(gaim_conversation_get_type(conv) != GAIM_CONV_TYPE_IM) + name = gaim_conversation_get_name(conv); + buddy = gaim_find_buddy(gaim_conversation_get_account(conv), name); + notes = buddy_get_notes((GaimBlistNode *) buddy); + str = g_strdup_printf("Notes: %s", notes); + gaim_conversation_write(conv, PLUGIN, str, GAIM_MESSAGE_SYSTEM, time(NULL)); +buddy_tooltip_cb(GaimBlistNode * node, char **text, void *data) + gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "type %d\n", node->type); + notes = buddy_get_notes(node); + newtext = g_strdup_printf("%s\n<b>Notes:</b> %s", *text, notes); +buddynotes_submitfields_cb(GaimRequestFields * fields, GaimBlistNode * data) + gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "buddynotes_submitfields_cb(%p,%p)\n", fields, data); + case GAIM_BLIST_BUDDY_NODE: + contact = gaim_buddy_get_contact((GaimBuddy *) data); + case GAIM_BLIST_CONTACT_NODE: + contact = (GaimContact *) data; + notes = gaim_request_fields_get_string(fields, CONTROL_NAME); + /* Otherwise, it's fixed value and this means deletion */ + gaim_blist_node_set_string((GaimBlistNode *) contact, SETTING_NAME, notes); + gaim_blist_node_remove_setting((GaimBlistNode *) contact, SETTING_NAME); +/* Node is either a contact or a buddy */ +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; + case GAIM_BLIST_BUDDY_NODE: + case GAIM_BLIST_CONTACT_NODE: + /* Continue, code works for either */ + 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); +plugin_load(GaimPlugin * 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); +static GaimPluginInfo info = { + 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/", +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 + * 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 + *************************************************************************/ +#define PLUGIN "gtk-kleptog-buddytimezone" +#define PLUGIN "core-kleptog-buddytimezone" +#define SETTING_NAME "timezone" +#define CONTROL_NAME PLUGIN "-" SETTING_NAME +#include "debug.h" /* Debug output functions */ +#include "util.h" /* Menu functions */ +#include "request.h" /* Requests stuff */ +#include "conversation.h" /* Conversation stuff */ +#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 +buddy_get_timezone(PurpleBlistNode * node, gboolean resolve) + PurpleBlistNode *datanode = NULL; + case PURPLE_BLIST_BUDDY_NODE: + datanode = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) node); + case PURPLE_BLIST_CONTACT_NODE: + case PURPLE_BLIST_GROUP_NODE: + timezone = purple_blist_node_get_string(datanode, SETTING_NAME); + /* The effect of "none" is to stop recursion */ + if(timezone && strcmp(timezone, "none") == 0) + 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) +/* Calcuates the difference between two struct tm's. */ +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; + datediff = remote_tm->tm_mon - local_tm->tm_mon; + datediff = remote_tm->tm_mday - local_tm->tm_mday; + datediff * 24 * 60 + (remote_tm->tm_hour - local_tm->tm_hour) * 60 + (remote_tm->tm_min - + return (float)diff / 60.0; +timezone_get_time(const char *timezone, struct tm *tm, float *diff) + struct state *tzinfo = timezone_load(timezone); + localsub(&now, 0, tm, 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) + *diff = timezone_calc_difference(tm, local_tm); +timezone_get_time(const char *timezone, struct tm *tm, float *diff) + /* Store the current TZ value. */ + old_tz = g_getenv("TZ"); + g_setenv("TZ", timezone, TRUE); + tm_tmp = localtime(&now); + *tm = *tm_tmp; /* Must copy, localtime uses local buffer */ + /* Reset the old TZ value. */ + 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) + *diff = timezone_calc_difference(tm, tm_tmp); +timezone_createconv_cb(PurpleConversation * conv, void *data) + if(purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM) + name = purple_conversation_get_name(conv); + buddy = purple_find_buddy(purple_conversation_get_account(conv), name); + timezone = buddy_get_timezone((PurpleBlistNode *) buddy, TRUE); + ret = timezone_get_time(timezone, &tm, &diff); + 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)); +buddytimezone_tooltip_cb(PurpleBlistNode * node, char **text, gboolean full, void *data) + purple_debug(PURPLE_DEBUG_INFO, PLUGIN, "type %d\n", node->type); + timezone = buddy_get_timezone(node, TRUE); + ret = timezone_get_time(timezone, &tm, &diff); + newtext = g_strdup_printf("%s\n<b>Timezone:</b> %s (error)", *text, timezone); + const char *timetext = purple_time_format(&tm); + g_strdup_printf("%s\n<b>Local Time:</b> %s (%g hours %s)", *text, timetext, fabs(diff), + (diff < 0) ? "behind" : "ahead"); +buddytimezone_submitfields_cb(PurpleRequestFields * fields, PurpleBlistNode * data) + PurpleRequestField *list; + purple_debug(PURPLE_DEBUG_INFO, PLUGIN, "buddytimezone_submitfields_cb(%p,%p)\n", fields, data); + case PURPLE_BLIST_BUDDY_NODE: + node = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) data); + case PURPLE_BLIST_CONTACT_NODE: + case PURPLE_BLIST_GROUP_NODE: + /* code handles either case */ + case PURPLE_BLIST_CHAT_NODE: + case PURPLE_BLIST_OTHER_NODE: + list = purple_request_fields_get_field(fields, CONTROL_NAME); + const char *seldata = get_timezone_menu_selection(list->ui_data); + purple_blist_node_remove_setting(node, SETTING_NAME); + purple_blist_node_set_string(node, SETTING_NAME, seldata); + sellist = purple_request_field_list_get_selected(list); + 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"); + purple_blist_node_remove_setting(node, SETTING_NAME); +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); +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; + case PURPLE_BLIST_BUDDY_NODE: + case PURPLE_BLIST_CONTACT_NODE: + case PURPLE_BLIST_GROUP_NODE: + case PURPLE_BLIST_CHAT_NODE: + case PURPLE_BLIST_OTHER_NODE: + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + timezone = buddy_get_timezone(data, FALSE); + 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); + 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(strcmp(timezone, "none") == 0) + purple_request_field_list_add_selected(field, "<Disabled>"); + purple_request_field_list_add_selected(field, timezone); + purple_request_field_list_add_selected(field, "<Default>"); + purple_request_field_group_add_field(group, field); +plugin_load(PurplePlugin * 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); + 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, +static PurplePluginInfo info = { + PURPLE_PLUGIN_STANDARD, + PURPLE_PRIORITY_DEFAULT, + "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/", +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. + *************************************************************************/ +#define DISABLED_STRING "<Disabled>" +#define DEFAULT_STRING "<Default>" +#define MORE_STRING "More..." + 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)))); +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)); +menu_select_cb(GtkMenuItem * menuitem, GtkWidget * menu) + const char *label = menuitem_get_label(menuitem); + if(strcmp(label, DEFAULT_STRING) == 0) + else if(strcmp(label, DISABLED_STRING) == 0) + selection = GTK_WIDGET(menu_get_first_menuitem(menu)); + gtk_widget_hide(selection); + char *str = g_strdup(label); + GtkMenuItem *parentitem; + parent = gtk_widget_get_parent(GTK_WIDGET(menuitem)); + 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); + 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, +make_menu_cb(char *path, struct state *state) + /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */ + 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) + /* 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); + GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu; + if(i == 0 && elements[1] == NULL) + 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->stack[i].string = g_strdup(elements[i]); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), +make_timezone_menu(const char *selected) + GtkWidget *optionmenu, *menuitem, *selection; + menuitem = gtk_menu_item_new_with_label(selected); + gtk_menu_append(menu, 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.extra = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra); + 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); + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0); +get_timezone_menu_selection(void *widget) + GtkOptionMenu *menu = GTK_OPTION_MENU(widget); + int sel = gtk_option_menu_get_history(menu); + if(sel == 2) /* Default */ + if(sel == 1) /* Disabled */ + 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. + *************************************************************************/ +#define PACKAGE "Hello World" +#define DISABLED_STRING "<Disabled>" +#define DEFAULT_STRING "<Default>" +#define MORE_STRING "More..." + * Terminate the main loop. +on_destroy(GtkWidget * widget, gpointer data) + 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)))); +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)); +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(strcmp(label, DEFAULT_STRING) == 0) + else if(strcmp(label, DISABLED_STRING) == 0) + selection = menu_get_first_menuitem(GTK_MENU(menu)); + gtk_widget_hide(selection); + char *str = g_strdup(label); + GtkMenuItem *parentitem; + parent = gtk_widget_get_parent(GTK_WIDGET(menuitem)); +// printf( "parent = %s(%p)\n", G_OBJECT_TYPE_NAME(parent), parent); + 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); + 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); +make_menu_cb(char *path, struct state *state) + /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */ + 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) + /* 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); + GtkTreeIter *parent = (i == 0) ? NULL : &state->stack[i - 1].iter; + GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu; + if(i == 0 && elements[1] == NULL) + 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]); + 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->stack[i].string = g_strdup(elements[i]); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), +make_menu2(char *selected) + GtkTreeStore *store = gtk_tree_store_new(N_COLUMNS, /* Total number of columns */ + G_TYPE_STRING); /* Timezone */ + 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); + GtkWidget *optionmenu, *menuitem, *selection; + menuitem = gtk_menu_item_new_with_label(selected); + gtk_menu_append(menu, 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.extra = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra); + recurse_directory("/usr/share/zoneinfo", (DirRecurseMatch) make_menu_cb, &state); + for (i = 0; i < state.currdepth; i++) + g_free(state.stack[i].string); + 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); + 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); + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0); +main(int argc, char *argv[]) + 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); + /* make sure that everything, window and label, are visible */ + gtk_widget_show(window); + /* start the main loop */ --- /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 @@
+/************************************************************************* + * 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 +** Leap second handling from Bradley White. +** POSIX-style TZ environment variable handling from Guy Harris. +#include "float.h" /* for FLT_MAX and DBL_MAX */ +#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. +#define OPEN_MODE (O_RDONLY | O_BINARY) +#endif /* defined O_BINARY */ +#define OPEN_MODE O_RDONLY +#endif /* !defined O_BINARY */ +** Someone might make incorrect use of a time zone abbreviation: +** 1. They might reference tzname[0] before calling tzset (explicitly +** 2. They might reference tzname[1] before calling tzset (explicitly +** 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). +#endif /* !defined WILDABBR */ +static char wildabbr[] = WILDABBR; +static const char gmt[] = "GMT"; +** 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 +#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)) +#define MY_TZNAME_MAX TZNAME_MAX +#endif /* defined TZNAME_MAX */ +#define MY_TZNAME_MAX 255 +#endif /* !defined TZNAME_MAX */ + 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]; + 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, +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 * 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, +struct state *timezone_load P((const char * name)); +static struct state * gmtptr; +#endif /* defined ALL_STATE */ +static struct state gmtmem; +#define gmtptr (&gmtmem) +#define TZ_STRLEN_MAX 255 +#endif /* !defined TZ_STRLEN_MAX */ +//static char lcl_TZname[TZ_STRLEN_MAX + 1]; +//static int lcl_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. +#endif /* defined USG_COMPAT */ +#endif /* defined ALTZONE */ +const char * const codep; + result = (codep[0] & 0x80) ? ~0L : 0L; + for (i = 0; i < 4; ++i) + result = (result << 8) | (codep[i] & 0xff); +register const char * name; +register struct state * const sp; + register const char * p; + if (name == NULL && (name = TZDEFAULT) == NULL) + ** 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]; + doaccess = name[0] == '/'; + if ((p = tzdir) == NULL) + if ((strlen(p) + strlen(name) + 1) >= sizeof fullname) + (void) strcpy(fullname, p); + (void) strcat(fullname, "/"); + (void) strcat(fullname, name); + ** Set doaccess if '.' (as in "../") shows up in name. + if (strchr(name, '.') != NULL) + if (doaccess && access(name, R_OK) != 0) + if ((fid = open(name, OPEN_MODE)) == -1) + char buf[sizeof *sp + sizeof *tzhp]; + i = read(fid, u.buf, sizeof u.buf); + 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)) + 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 */ + for (i = 0; i < sp->timecnt; ++i) { + sp->ats[i] = detzcode(p); + for (i = 0; i < sp->timecnt; ++i) { + sp->types[i] = (unsigned char) *p++; + if (sp->types[i] >= sp->typecnt) + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + ttisp->tt_gmtoff = detzcode(p); + ttisp->tt_isdst = (unsigned char) *p++; + if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1) + ttisp->tt_abbrind = (unsigned char) *p++; + if (ttisp->tt_abbrind < 0 || + ttisp->tt_abbrind > sp->charcnt) + for (i = 0; i < sp->charcnt; ++i) + sp->chars[i] = '\0'; /* ensure '\0' at end */ + for (i = 0; i < sp->leapcnt; ++i) { + register struct lsinfo * lsisp; + lsisp->ls_trans = detzcode(p); + lsisp->ls_corr = detzcode(p); + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + ttisp->tt_ttisstd = FALSE; + ttisp->tt_ttisstd = *p++; + if (ttisp->tt_ttisstd != TRUE && + ttisp->tt_ttisstd != FALSE) + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + ttisp->tt_ttisgmt = FALSE; + ttisp->tt_ttisgmt = *p++; + if (ttisp->tt_ttisgmt != TRUE && + ttisp->tt_ttisgmt != FALSE) + ** 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]) { + if (TYPE_SIGNED(time_t)) { + ** Ignore the end (easy). + ** Ignore the beginning (harder). + for (j = 0; j + i < sp->timecnt; ++j) { + sp->ats[j] = sp->ats[j + i]; + sp->types[j] = sp->types[j + i]; +struct state *timezone_load(name) + struct state *sp = malloc( sizeof(struct state) ); + res = tzload( name, 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 +register const char * strp; + while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && +** 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. +getqzname(register const char *strp, const char delim) +register const char * strp; + while ((c = *strp) != '\0' && c != delim) +** 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 +** Otherwise, return a pointer to the first character not part of the number. +getnum(strp, nump, min, max) +register const char * strp; + if (strp == NULL || !is_digit(c = *strp)) + num = num * 10 + (c - '0'); + return NULL; /* illegal value */ + return NULL; /* illegal value */ +** 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 +register const char * strp; + ** `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); + *secsp = num * (long) SECSPERHOUR; + strp = getnum(strp, &num, 0, MINSPERHOUR - 1); + *secsp += num * SECSPERMIN; + /* `SECSPERMIN' allows for leap seconds. */ + strp = getnum(strp, &num, 0, SECSPERMIN); +** 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. +getoffset(strp, offsetp) +register const char * strp; + } else if (*strp == '+') + strp = getsecs(strp, offsetp); + return NULL; /* illegal time */ +** 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. +register struct rule * const rulep; + rulep->r_type = JULIAN_DAY; + strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR); + } else if (*strp == 'M') { + rulep->r_type = MONTH_NTH_DAY_OF_WEEK; + strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR); + strp = getnum(strp, &rulep->r_week, 1, 5); + strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1); + } else if (is_digit(*strp)) { + rulep->r_type = DAY_OF_YEAR; + strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1); + } else return NULL; /* invalid format */ + strp = getsecs(strp, &rulep->r_time); + } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */ +** 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. +transtime(janfirst, year, rulep, offset) +register const struct rule * const rulep; + int d, m1, yy0, yy1, yy2, dow; + leapyear = isleap(year); + switch (rulep->r_type) { + ** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap + ** 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) + ** Just add SECSPERDAY times the day number to the time of + ** January 1, midnight, to get the day. + value = janfirst + rulep->r_day * SECSPERDAY; + case MONTH_NTH_DAY_OF_WEEK: + ** Mm.n.d - nth "dth day" of month m. + 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 + m1 = (rulep->r_mon + 9) % 12 + 1; + yy0 = (rulep->r_mon <= 2) ? (year - 1) : year; + dow = ((26 * m1 - 2) / 10 + + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; + ** "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 + d = rulep->r_day - dow; + for (i = 1; i < rulep->r_week; ++i) { + mon_lengths[leapyear][rulep->r_mon - 1]) + ** "d" is the day-of-month (zero-origin) of the day we want. + value += d * SECSPERDAY; + ** "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 + return value + rulep->r_time + offset; +** Given a POSIX section 8-style TZ string, fill in the rule tables as +tzparse(name, sp, lastditch) +register struct state * const sp; + register unsigned char * typep; + register int load_result; + stdlen = strlen(name); /* length of standard zone name */ + if (stdlen >= sizeof sp->chars) + stdlen = (sizeof sp->chars) - 1; + name = getqzname(name, '>'); + stdlen = name - stdname; + stdlen = name - stdname; + name = getoffset(name, &stdoffset); + load_result = tzload(TZDEFRULES, sp); + sp->leapcnt = 0; /* so, we're off a little */ + name = getqzname(name, '>'); + dstlen = name - dstname; + dstlen = name - dstname; /* length of DST zone name */ + if (*name != '\0' && *name != ',' && *name != ';') { + name = getoffset(name, &dstoffset); + } else dstoffset = stdoffset - SECSPERHOUR; + if (*name == '\0' && load_result != 0) + name = TZDEFRULESTRING; + if (*name == ',' || *name == ';') { + register time_t janfirst; + if ((name = getrule(name, &start)) == NULL) + if ((name = getrule(name, &end)) == NULL) + 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) + 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; + for (year = EPOCH_YEAR; year <= 2037; ++year) { + starttime = transtime(janfirst, year, &start, + endtime = transtime(janfirst, year, &end, + if (starttime > endtime) { + *typep++ = 1; /* DST ends */ + *typep++ = 0; /* DST begins */ + *typep++ = 0; /* DST begins */ + *typep++ = 1; /* DST ends */ + janfirst += year_lengths[isleap(year)] * + register long theirstdoffset; + register long theirdstoffset; + register long theiroffset; + ** Initial values of theirstdoffset and theirdstoffset. + for (i = 0; i < sp->timecnt; ++i) { + if (!sp->ttis[j].tt_isdst) { + -sp->ttis[j].tt_gmtoff; + for (i = 0; i < sp->timecnt; ++i) { + if (sp->ttis[j].tt_isdst) { + -sp->ttis[j].tt_gmtoff; + ** Initially we're assumed to be in standard time. + theiroffset = theirstdoffset; + ** Now juggle transition times and types + ** tracking offsets as you do. + for (i = 0; i < sp->timecnt; ++i) { + sp->types[i] = sp->ttis[j].tt_isdst; + if (sp->ttis[j].tt_ttisgmt) { + /* No adjustment to transition time */ + ** 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 + if (isdst && !sp->ttis[j].tt_ttisstd) { + sp->ats[i] += dstoffset - + sp->ats[i] += stdoffset - + 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 = 1; /* only standard time */ + sp->ttis[0].tt_gmtoff = -stdoffset; + sp->ttis[0].tt_isdst = 0; + sp->ttis[0].tt_abbrind = 0; + sp->charcnt = stdlen + 1; + sp->charcnt += dstlen + 1; + if ((size_t) sp->charcnt > sizeof sp->chars) + (void) strncpy(cp, stdname, stdlen); + (void) strncpy(cp, dstname, dstlen); +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. +localsub(timep, offset, tmp, sp) +const time_t * const timep; + register const struct ttinfo * ttisp; + register struct tm * result; + const time_t t = *timep; + return gmtsub(timep, offset, tmp); +#endif /* defined ALL_STATE */ + if (sp->timecnt == 0 || t < sp->ats[0]) { + while (sp->ttis[i].tt_isdst) + if (++i >= sp->typecnt) { + for (i = 1; i < sp->timecnt; ++i) + i = (int) sp->types[i - 1]; + ** 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]; + tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind]; +#endif /* defined TM_ZONE */ +** gmtsub is to gmtime as localsub is to localtime. +gmtsub(timep, offset, tmp) +const time_t * const timep; + register struct tm * result; + gmtptr = (struct state *) malloc(sizeof *gmtptr); +#endif /* defined ALL_STATE */ + result = timesub(timep, offset, gmtptr, tmp); + ** 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. + tmp->TM_ZONE = wildabbr; + else tmp->TM_ZONE = gmtptr->chars; +#endif /* defined ALL_STATE */ + tmp->TM_ZONE = gmtptr->chars; +#endif /* defined TM_ZONE */ +** 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. + return (y >= 0) ? (y / 4 - y / 100 + y / 400) : + -(leaps_thru_end_of(-(y + 1)) + 1); +timesub(timep, offset, sp, tmp) +const time_t * const timep; +register const struct state * const sp; +register struct tm * const tmp; + register const struct lsinfo * lp; + register int idays; /* unsigned would be so 2003 */ + register const int * ip; + i = (sp == NULL) ? 0 : sp->leapcnt; +#endif /* defined ALL_STATE */ + 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); + sp->lsis[i].ls_trans == + sp->lsis[i - 1].ls_trans + 1 && + sp->lsis[i - 1].ls_corr + 1) { + tdays = *timep / SECSPERDAY; + rem = *timep - tdays * SECSPERDAY; + while (tdays < 0 || tdays >= year_lengths[isleap(y)]) { + register time_t tdelta; + tdelta = tdays / DAYSPERLYEAR; + if (tdelta - idelta >= 1 || idelta - tdelta >= 1) + idelta = (tdays < 0) ? -1 : 1; + if (increment_overflow(&newy, idelta)) + leapdays = leaps_thru_end_of(newy - 1) - + leaps_thru_end_of(y - 1); + tdays -= ((time_t) newy - y) * DAYSPERNYEAR; + seconds = tdays * SECSPERDAY + 0.5; + tdays = seconds / SECSPERDAY; + rem += seconds - tdays * SECSPERDAY; + ** Given the range, we can now fearlessly cast... + while (rem >= SECSPERDAY) { + if (increment_overflow(&y, -1)) + idays += year_lengths[isleap(y)]; + while (idays >= year_lengths[isleap(y)]) { + idays -= year_lengths[isleap(y)]; + if (increment_overflow(&y, 1)) + if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE)) + ** 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) + + tmp->tm_wday %= DAYSPERWEEK; + tmp->tm_wday += DAYSPERWEEK; + tmp->tm_hour = (int) (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_GMTOFF = offset; +#endif /* defined TM_GMTOFF */ +** 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). +#endif /* !defined WRONG */ +** Simplified normalize logic courtesy Paul Eggert. +increment_overflow(number, delta) + return (*number < number0) != (delta < 0); +int tz_init( const char *zoneinfo_dir ) + if( zoneinfo_dir == NULL ) + ptr = malloc( strlen(zoneinfo_dir) + 10 ); + sprintf( ptr, "%s/zone.tab", zoneinfo_dir ); + fd = open( ptr, O_RDONLY ); + tzdir = strdup(zoneinfo_dir); --- /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. + *************************************************************************/ +struct state *timezone_load (const char * name); +struct tm * gmtsub (const time_t * timep, long offset, +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 + *************************************************************************/ +** 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. +#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'. +#endif /* !defined HAVE_ADJTIME */ +#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 */ +#endif /* !defined HAVE_STRERROR */ +#endif /* !defined HAVE_SYMLINK */ +#define HAVE_SYS_STAT_H 1 +#endif /* !defined HAVE_SYS_STAT_H */ +#define HAVE_SYS_WAIT_H 1 +#endif /* !defined HAVE_SYS_WAIT_H */ +#endif /* !defined HAVE_UNISTD_H */ +#endif /* !defined HAVE_UTMPX_H */ +#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 */ +#include "sys/types.h" /* for time_t */ +#include "limits.h" /* for CHAR_BIT */ +#endif /* HAVE_GETTEXT */ +#include <sys/wait.h> /* for WIFEXITED and WEXITSTATUS */ +#endif /* HAVE_SYS_WAIT_H */ +#define WIFEXITED(status) (((status) & 0xff) == 0) +#endif /* !defined WIFEXITED */ +#define WEXITSTATUS(status) (((status) >> 8) & 0xff) +#endif /* !defined WEXITSTATUS */ +#include "unistd.h" /* for F_OK and R_OK */ +#endif /* HAVE_UNISTD_H */ +#endif /* !defined F_OK */ +#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. +** SunOS 4.1.1 headers lack EXIT_SUCCESS. +#endif /* !defined EXIT_SUCCESS */ +** SunOS 4.1.1 headers lack EXIT_FAILURE. +#endif /* !defined EXIT_FAILURE */ +** SunOS 4.1.1 headers lack FILENAME_MAX. +#endif /* defined unix */ +#endif /* !defined MAXPATHLEN */ +#define FILENAME_MAX MAXPATHLEN +#endif /* defined MAXPATHLEN */ +#define FILENAME_MAX 1024 /* Pure guesswork */ +#endif /* !defined MAXPATHLEN */ +#endif /* !defined FILENAME_MAX */ +** SunOS 4.1.1 libraries lack remove. +extern int unlink P((const char * filename)); +#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. +#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. +extern char * asctime_r(); +** 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. +#endif /* !defined TRUE */ +#endif /* !defined FALSE */ +#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT) +#endif /* !defined TYPE_BIT */ +#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. +#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 + \ +#endif /* !defined INT_STRLEN_MAXIMUM */ +#endif /* defined lint */ +#endif /* defined __GNUC__ */ +#endif /* !defined lint */ +#endif /* !defined GNUC_or_lint */ +#define INITIALIZE(x) ((x) = 0) +#endif /* defined GNUC_or_lint */ +#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. +#define _(msgid) gettext(msgid) +#else /* !HAVE_GETTEXT */ +#endif /* !HAVE_GETTEXT */ +#endif /* !defined TZ_DOMAIN */ +#if HAVE_INCOMPATIBLE_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 @@
+/************************************************************************* + * 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 + *************************************************************************/ +/* GLibc specific version. In this version, the entries are sorted */ +/* We assume dirname ends in a /, prefix also unless empty */ +recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data) + struct dirent **namelist; + if((ents = scandir(dirname, &namelist, 0, alphasort)) < 0) + for (i = 0; i < ents; i++) + asprintf(&ptr, "%s%s", dirname, ent->d_name); + asprintf(&ptr, "%s%s", prefix, ent->d_name); + 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); +/* generic version, here they are unsorted */ +/* We assume dirname ends in a /, prefix also unless empty */ +recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data) + dir = opendir(dirname); + while ((ent = readdir(dir)) != NULL) + asprintf(&ptr, "%s%s", dirname, ent->d_name); + asprintf(&ptr, "%s%s", prefix, ent->d_name); + 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); +recurse_directory(char *dirname, DirRecurseMatch func, void *data) + char *newdirname = NULL; + if(dirname[strlen(dirname) - 1] != '/') + asprintf(&newdirname, "%s/", dirname); + ret = recurse_directory_int(newdirname ? newdirname : dirname, "", func, data); --- /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. + *************************************************************************/ +process_entry(char *str, void *ptr) + recurse_directory("/usr/share/zoneinfo", process_entry, main); --- /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 @@
+/************************************************************************* + * 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. + *************************************************************************/ + time_t now = time(NULL); + state = timezone_load("Australia/Sydney"); + localsub(&now, 0, &tm, state); --- /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 + *************************************************************************/ +** 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. +** Information about time zone files. +#define TZDIR "/usr/share/zoneinfo" /* Default time zone object file directory */ +#endif /* !defined TZDIR */ +#define TZDEFAULT "localtime" +#endif /* !defined TZDEFAULT */ +#define TZDEFRULES "posixrules" +#endif /* !defined TZDEFRULES */ +** Each file begins with. . . + 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. +** 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 */ +#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ +#endif /* !defined 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 */ +#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ + /* (limited by what unsigned chars can hold) */ +#endif /* !defined TZ_MAX_CHARS */ +#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ +#endif /* !defined TZ_MAX_LEAPS */ +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY) +#define TM_YEAR_BASE 1900 +#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) +** isleap(a + b) == isleap((a + b) % 400) +** 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 */