From 91c713d27e2afbe32cbda5e805894b54e9e376b7 Mon Sep 17 00:00:00 2001 From: Jonas Date: Fri, 20 Apr 2007 08:53:50 +0000 Subject: [PATCH] Initial import git-svn-id: http://sanoi.webfactional.com/trunk@3 79def182-f41f-0410-b320-e94a04284523 --- COPYING | 340 +++++++++++++++++++++++++++++++++++++++++ ChangeLog | 6 + README | 32 ++++ SConstruct | 76 +++++++++ build_functions.py | 39 +++++ doc/indent.txt | 1 + src/SConscript | 32 ++++ src/common.h | 131 ++++++++++++++++ src/fama-config.h.in | 8 + src/indent.sh | 8 + src/main.c | 165 ++++++++++++++++++++ src/ui-color.c | 202 ++++++++++++++++++++++++ src/ui-command-line.c | 116 ++++++++++++++ src/ui-command.c | 129 ++++++++++++++++ src/ui-contactlist.c | 235 ++++++++++++++++++++++++++++ src/ui-interface.c | 107 +++++++++++++ src/ui-keyfile.c | 95 ++++++++++++ src/ui-log.c | 75 +++++++++ src/ui-signal.c | 119 +++++++++++++++ src/ui-stdin-handler.c | 128 ++++++++++++++++ src/ui-textwrap.c | 98 ++++++++++++ src/ui-utf8.c | 100 ++++++++++++ src/ui-window.c | 218 ++++++++++++++++++++++++++ 23 files changed, 2460 insertions(+) create mode 100755 COPYING create mode 100755 ChangeLog create mode 100755 README create mode 100755 SConstruct create mode 100755 build_functions.py create mode 100755 doc/indent.txt create mode 100755 src/SConscript create mode 100755 src/common.h create mode 100755 src/fama-config.h.in create mode 100755 src/indent.sh create mode 100755 src/main.c create mode 100755 src/ui-color.c create mode 100755 src/ui-command-line.c create mode 100755 src/ui-command.c create mode 100755 src/ui-contactlist.c create mode 100755 src/ui-interface.c create mode 100755 src/ui-keyfile.c create mode 100755 src/ui-log.c create mode 100755 src/ui-signal.c create mode 100755 src/ui-stdin-handler.c create mode 100755 src/ui-textwrap.c create mode 100755 src/ui-utf8.c create mode 100755 src/ui-window.c diff --git a/COPYING b/COPYING new file mode 100755 index 0000000..d60c31a --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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) year 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. + + , 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. diff --git a/ChangeLog b/ChangeLog new file mode 100755 index 0000000..1d720ce --- /dev/null +++ b/ChangeLog @@ -0,0 +1,6 @@ +0.0.1 Sat Oct 14 13:56:06 CEST 2006 - Jonas Broms + * Initial SVN import + * Added scons build scripts (originally from telepathy-inspector) + * Added functions in fama-conn-manager.c + * Added functions in fama-dbus.c + * Added data-type FamaConnectionManager in fama-common.h diff --git a/README b/README new file mode 100755 index 0000000..8fc235a --- /dev/null +++ b/README @@ -0,0 +1,32 @@ +How to install (the flags are optional): + +$ scons PREFIX=/what/ever DEBUG=yes +$ scons install + + +If you see this error: +------------------------------------------------------------------ +Generating configuration header src/fama-config.h from +src/fama-config.h.in...scons: *** [src/fama-config.h] Exception +Traceback (most recent call last): + File "/usr/lib/scons/SCons/Taskmaster.py", line 171, in execute + self.targets[0].build() + File "/usr/lib/scons/SCons/Node/__init__.py", line 297, in build + apply(executor, (self, exitstatfunc), kw) + File "/usr/lib/scons/SCons/Executor.py", line 125, in __call__ + self.do_execute(target, exitstatfunc, kw) + File "/usr/lib/scons/SCons/Executor.py", line 118, in do_execute + kw) + File "/usr/lib/scons/SCons/Action.py", line 342, in __call__ + stat = self.execute(target, source, env) + File "/usr/lib/scons/SCons/Action.py", line 674, in execute + result = self.execfunction(target=target, source=rsources, env=env) + File "/home/yourname/fama-interface/build_functions.py", line 28, in +BuildConfigHeader + config_h.write(config_h_in.read() % env.config_header_vars) +AttributeError: 'SConsEnvironment' object has no attribute +'config_header_vars' +scons: building terminated because of errors. +------------------------------------------------------------ + +It means you have to re-configure, use "scons CONFIGURE=yes" diff --git a/SConstruct b/SConstruct new file mode 100755 index 0000000..6e498f8 --- /dev/null +++ b/SConstruct @@ -0,0 +1,76 @@ +import os +from build_functions import CheckPKGConfig, CheckPKG, CheckPackages, SaveDictionary + +SConsignFile() + +env = Environment(ENV = os.environ) + +# Variables + +# [library_name, lirary_minimum_version] +libraries = [ + ['glib-2.0', '2.0.0'], + ['gobject-2.0', '2.0.0'], + ] + +options_filename = 'build_options' + +application_version_string = '0.0.1-pre-alpha' + +# Get our configuration options: +opts = Options(options_filename) +opts.Add('PREFIX', 'Directory to install under', '/usr/local') +opts.Add('NCURSESW', 'Path to the ncursesw include path', '/usr/include/ncursesw') +opts.Add(BoolOption('CONFIGURE', 'Whether the build should be (re)configured', 'yes')) +opts.Add(BoolOption('DEBUG', 'Whether debugging information should be produced', 'no')) +opts.Update(env) +opts.Save(options_filename, env) + +Help(opts.GenerateHelpText(env)) + +# Compiler options + +if env['CC'] == 'gcc': + + env['CCFLAGS'] += ' -Wall' + + if env['DEBUG'] == True: + env['CCFLAGS'] += ' -g' + + +# Configuration: + +if env['CONFIGURE'] == True: + conf = Configure (env, custom_tests = { 'CheckPKGConfig' : CheckPKGConfig, + 'CheckPKG' : CheckPKG }) + + if not conf.CheckPKGConfig('0.15.0'): + print 'pkg-config >= 0.15.0 not found.' + Exit(1) + + if not CheckPackages (conf, libraries): + Exit (1) + + env = conf.Finish() + + env['CONFIGURE'] = 'no' # If this point has been reached it's because the configuration was successful + opts.Save(options_filename, env) + + # Configuration header file. + env.config_header_vars = { + # This is where you put all of your custom configuration values. + 'data_dir_prefix' : os.path.join (env['PREFIX'], 'share', 'fama', ''), + 'version_string' : application_version_string + } + SaveDictionary ('config_header_vars', env.config_header_vars) + +# Now, build: + +for library_name, library_version in libraries: + env.ParseConfig ('pkg-config --cflags --libs ' + library_name) + + +# Append path to ncursesw directory + + +SConscript(['src/SConscript'], 'env options_filename') diff --git a/build_functions.py b/build_functions.py new file mode 100755 index 0000000..9af2eaa --- /dev/null +++ b/build_functions.py @@ -0,0 +1,39 @@ +# Check pgk-config existence and its version +def CheckPKGConfig(context, version): + context.Message( 'Checking for pkg-config... ' ) + ret = context.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0] + context.Result( ret ) + return ret + +# Check whether a given package is installed using pkg-config +def CheckPKG (context, library_name, library_version): + context.Message ('Checking for %s >= %s...' % (library_name, library_version) ) + ret = context.TryAction ('pkg-config --print-errors --exists \'%s >= %s\'' % (library_name, library_version))[0] + context.Result (ret) + return ret + +# Check a list of package names +def CheckPackages (conf, library_list): + for library_name, library_version in library_list: + if not conf.CheckPKG(library_name, library_version): + return False + return True + +def BuildConfigHeader (target, source, env): + + for a_target, a_source in zip(target, source): + print "Generating configuration header %s from %s..." % (a_target, a_source), + config_h = file(str(a_target), "w") + config_h_in = file(str(a_source), "r") + config_h.write(config_h_in.read() % env.config_header_vars) + config_h_in.close() + config_h.close() + print "done" + +def SaveDictionary (filename, dic): + dic_file = file (filename, 'w') + + for key, val in dic.items(): + dic_file.write ("%s - %s\n" % (key, val)) + + dic_file.close () diff --git a/doc/indent.txt b/doc/indent.txt new file mode 100755 index 0000000..523dcbb --- /dev/null +++ b/doc/indent.txt @@ -0,0 +1 @@ + -bad -bap -bbo -nbc -br -ce -cdw -i4 -ip4 -sob -ts4 -ut -ts4 -ncs -nbc -di1 -lp -ppi0 -l130 -bbo diff --git a/src/SConscript b/src/SConscript new file mode 100755 index 0000000..ea984a7 --- /dev/null +++ b/src/SConscript @@ -0,0 +1,32 @@ +import os + +from build_functions import BuildConfigHeader + + +source_files = Split("""ui-contactlist.c + ui-interface.c + ui-keyfile.c + main.c + ui-signal.c + ui-color.c + ui-log.c + ui-textwrap.c + ui-stdin-handler.c + ui-command-line.c + ui-command.c + ui-window.c + ui-utf8.c""") + +Import ('env options_filename') + +env.Append (LIBS = ['panelw', 'ncursesw']) +env.Append(CPPPATH = env['NCURSESW']) + +install_dir = os.path.join (env['PREFIX'], 'bin') + +env.Depends('fama-config.h', os.path.join ('..', 'config_header_vars')) +env.Command('fama-config.h', 'fama-config.h.in', BuildConfigHeader) + +program = env.Program ('fama', source_files) +program_install = env.Install (install_dir, program) +env.Alias ('install', program_install) diff --git a/src/common.h b/src/common.h new file mode 100755 index 0000000..f95def3 --- /dev/null +++ b/src/common.h @@ -0,0 +1,131 @@ +#ifndef COMMON_H +#define COMMON_H 1 + +#define _GNU_SOURCE 1 + +#include +#include + +#include +#include +#include + +typedef enum { + WindowTypeMain, + WindowTypeConversation, +} FamaWindowType; + +typedef struct { + WINDOW *ncwin; + PANEL *ncpanel; + + wchar_t *title; + GPtrArray *messages; + FamaWindowType type; + gboolean is_updated; +} FamaWindow; + +typedef struct { + gint attr; + wchar_t *title; + wchar_t *message; +} FamaMessage; + + +typedef struct { + gint borders; + gint command_line; + gint window_title; + gint message_heading; + gint message_text; + gint status_available; + gint status_away; + gint status_busy; + gint status_idle; + gint status_offline; + gint status_other; +} ColorSettings; + +typedef gboolean(*CommandFunc) (gint argc, gchar ** argv); + +/* Main.c */ +gboolean init_all(gpointer data); +void stop_main_loop(void); + +/* Interface.c */ + +void destroy_interface(); +void init_interface(); +void draw_interface(); +void redraw_interface(); + +int get_max_y(); +int get_max_x(); + +/* Contactlist.c */ +void contactlist_set_width(gint); +gint contactlist_get_width(); +void contactlist_draw(); +void contactlist_destroy(); +void contactlist_add_category(const wchar_t *); +void contactlist_add_item(guint, const wchar_t *, int); +void contactlist_scroll(gint); + +/* Utf8.c */ +wchar_t *utf8_to_wchar(const gchar *, wchar_t *, gsize); +gchar *utf8_from_wchar(const wchar_t *, gchar *, gsize); +void set_interface_encoding(gchar *); +gchar *get_interface_encoding(void); + + +/* Message.c */ +GPtrArray *textwrap_new(wchar_t *); +void textwrap_destroy(); +gint textwrap_append(GPtrArray *, const wchar_t *, guint); + +/* Signal.c */ +void signal_handler_setup(); + +/* Stdin-handler.c */ +void stdin_handler_setup(); + +/* Command-line.c */ +void commandline_init(); +void commandline_add_wch(wchar_t); +void commandline_update_width(); +wchar_t *commandline_get_buffer(); +void commandline_draw(); +void commandline_move_cursor(gint); +void commandline_delete(); + +/* Keyfile.c */ +gint keyfile_read(); +gint keyfile_write(); +GKeyFile *keyfile_get(); + +/* Window.c */ +FamaWindow *window_new(FamaWindowType); +void window_append_rows(FamaWindow *, GPtrArray *, gint); +void window_add_message(FamaWindow *, wchar_t *, gint, wchar_t *); +FamaWindow *window_get_current(); +FamaWindow *window_get_index(gint); +void window_set_current(FamaWindow *); +void window_set_title(FamaWindow *, wchar_t *); +void window_draw_title_bar(); +void window_destroy(FamaWindow *); +void window_resize_all(); + +/* Log.c */ +void log_init(); +void log_get_time(gchar *, gsize); + +/* Command.c */ +void command_init(); +void command_add(gchar *, CommandFunc); +gboolean command_execute(gint, gchar **); + +/* Color.c */ +void color_init(); +ColorSettings *color_get(); + +#endif diff --git a/src/fama-config.h.in b/src/fama-config.h.in new file mode 100755 index 0000000..65e5d0a --- /dev/null +++ b/src/fama-config.h.in @@ -0,0 +1,8 @@ +#ifndef FAMA_CONFIG_H +#define FAMA_CONFIG_H + +#define DATA_DIR_PREFIX "%(data_dir_prefix)s" + +#define VERSION_STRING "%(version_string)s" + +#endif diff --git a/src/indent.sh b/src/indent.sh new file mode 100755 index 0000000..ba2ddbd --- /dev/null +++ b/src/indent.sh @@ -0,0 +1,8 @@ +for i in `find ./ -name "*.[hc]"`; do + echo $i; + indent -br -brs -bad -bap -bbb -ncs -ce -npcs -nbbo -cdb -sc -i8 -l80 "$i"; +done; + +for i in `find ./ -name "*~"`; do + rm "$i" +done diff --git a/src/main.c b/src/main.c new file mode 100755 index 0000000..db61656 --- /dev/null +++ b/src/main.c @@ -0,0 +1,165 @@ +#include + +#include "common.h" +#include "fama-config.h" + +GMainLoop *loop; + +int +main(int argc, char **argv) +{ + g_type_init(); + + loop = g_main_loop_new(NULL, FALSE); + + g_idle_add(init_all, NULL); + + g_main_loop_run(loop); + g_main_loop_unref(loop); + + destroy_interface(); + + return 0; +} + + +void +stop_main_loop() +{ + g_main_loop_quit(loop); +} + +gboolean +init_all(gpointer data) +{ + GError *err = NULL; + gint a; + gchar *c; + const gchar *charset; + + /* + * Set locale + */ + + if (!setlocale(LC_ALL, "")) { + g_error("Can't set the specified locale!\n"); + return FALSE; + } + + /* + * Print version information + */ + + g_message("Fama v%s (c) 2007 Jonas Broms", VERSION_STRING); + + /* + * Read configuration file (and act accordingly) + */ + + if (keyfile_read() == FALSE) + return FALSE; + + a = g_key_file_get_integer(keyfile_get(), "ncurses", + "contact_list_width", &err); + if (err != NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_warning("cannot get contact_list_width: %s", + err->message); + + g_clear_error(&err); + } else { + contactlist_set_width(a); + } + + g_get_charset(&charset); + set_interface_encoding(g_strdup(charset)); + c = g_key_file_get_string(keyfile_get(), "ncurses", "charset", &err); + if (err != NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_warning("cannot get character charset: %s", + err->message); + + g_clear_error(&err); + } else { + set_interface_encoding(c); + } + g_message("Using charset '%s'", get_interface_encoding()); + + + /* + * Initialize the interface + */ + + init_interface(); + + /* + * Initialize message-logger + */ + log_init(); + + /* + * Call redraw_interface on SIGWINCH + */ + + signal_handler_setup(); + + /* + * Registering commands + */ + + command_init(); + + /* + * Call stdin_handle_input when there's input from stdin + */ + + stdin_handler_setup(); + + /* + * Initialize the command-line buffer + */ + + commandline_init(); + + /* + * Draw the interface + */ + + /* + * Test stuff + */ + contactlist_add_category(L"MSN"); + contactlist_add_item(0, L"A 0 只B只C只只只只只只只只只", 0); + contactlist_add_item(0, L"Hello", 0); + + contactlist_add_category(L"Jabber"); + contactlist_add_item(1, L"Lalalal alal", 0); + contactlist_add_item(1, L"Bababab", 0); + + contactlist_add_category(L"ICQ"); + contactlist_add_item(2, L"asdasddlo", 0); + contactlist_add_item(2, L"Mahatmao", 0); + contactlist_add_item(2, L"Leningrad", 0); + contactlist_add_item(2, L"asdasddlo", 0); + contactlist_add_item(2, L"Mahatmao", 0); + contactlist_add_item(2, L"Leningrad", 0); + contactlist_add_item(2, L"asdasddlo", 0); + contactlist_add_item(2, L"Mahatmao", 0); + contactlist_add_item(2, L"Leningrad", 0); + contactlist_add_item(2, L"asdasddlo", 0); + contactlist_add_item(2, L"Mahatmao", 0); + contactlist_add_item(2, L"Leningrad", 0); + contactlist_add_item(2, L"asdasddlo", 0); + contactlist_add_item(2, L"Mahatmao", 0); + contactlist_add_item(2, L"Leningrad", 0); + contactlist_add_item(2, L"asdasddlo", 0); + contactlist_add_item(2, L"Mahatmao", 0); + contactlist_add_item(2, L"Leningrad", 0); + + draw_interface(); + + /* + * Return FALSE so that the function isn't ran again + */ + return FALSE; +} diff --git a/src/ui-color.c b/src/ui-color.c new file mode 100755 index 0000000..a6b8e4c --- /dev/null +++ b/src/ui-color.c @@ -0,0 +1,202 @@ +#include "common.h" + +ColorSettings settings; + +gint +color_str_to_int(gchar * str) +{ + if (g_ascii_strcasecmp(str, "black") == 0) + return 0; + if (g_ascii_strcasecmp(str, "red") == 0) + return 1; + if (g_ascii_strcasecmp(str, "green") == 0) + return 2; + if (g_ascii_strcasecmp(str, "yellow") == 0) + return 3; + if (g_ascii_strcasecmp(str, "blue") == 0) + return 4; + if (g_ascii_strcasecmp(str, "magenta") == 0) + return 5; + if (g_ascii_strcasecmp(str, "cyan") == 0) + return 6; + if (g_ascii_strcasecmp(str, "white") == 0) + return 7; + if (g_ascii_strcasecmp(str, "default") == 0) + return 8; + + g_error("No such color: %s!\n", str); + return -1; +} + +void +color_init() +{ + gchar *tmp; + GError *err = NULL; + gint background; + + tmp = g_key_file_get_string(keyfile_get(), "colors", "background", + &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + background = -1; + } else { + if ((background = color_str_to_int(tmp)) == 8) + background = -1; + + g_free(tmp); + } + + start_color(); + use_default_colors(); + + init_pair(0, COLOR_BLACK, background); + init_pair(1, COLOR_RED, background); + init_pair(2, COLOR_GREEN, background); + init_pair(3, COLOR_YELLOW, background); + init_pair(4, COLOR_BLUE, background); + init_pair(5, COLOR_MAGENTA, background); + init_pair(6, COLOR_CYAN, background); + init_pair(7, COLOR_WHITE, background); + init_pair(8, -1, background); + + tmp = g_key_file_get_string(keyfile_get(), "colors", "borders", &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + settings.borders = COLOR_PAIR(6); + } else { + settings.borders = COLOR_PAIR(color_str_to_int(tmp)); + g_free(tmp); + } + + tmp = g_key_file_get_string(keyfile_get(), "colors", "command_line", + &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + settings.command_line = 0; + } else { + settings.command_line = COLOR_PAIR(color_str_to_int(tmp)); + g_free(tmp); + } + + tmp = g_key_file_get_string(keyfile_get(), "colors", "window_title", + &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + settings.window_title = COLOR_PAIR(3); + } else { + settings.window_title = COLOR_PAIR(color_str_to_int(tmp)); + g_free(tmp); + } + + tmp = g_key_file_get_string(keyfile_get(), "colors", "message_heading", + &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + settings.message_heading = COLOR_PAIR(2); + } else { + settings.message_heading = COLOR_PAIR(color_str_to_int(tmp)); + g_free(tmp); + } + + tmp = g_key_file_get_string(keyfile_get(), "colors", "message_text", + &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + settings.message_text = 0; + } else { + settings.message_text = COLOR_PAIR(color_str_to_int(tmp)); + g_free(tmp); + } + + tmp = g_key_file_get_string(keyfile_get(), "colors", "status_available", + &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + settings.status_available = COLOR_PAIR(2); + } else { + settings.status_available = COLOR_PAIR(color_str_to_int(tmp)); + g_free(tmp); + } + + tmp = g_key_file_get_string(keyfile_get(), "colors", "status_away", + &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + settings.status_away = COLOR_PAIR(6); + } else { + settings.status_away = COLOR_PAIR(color_str_to_int(tmp)); + g_free(tmp); + } + + tmp = g_key_file_get_string(keyfile_get(), "colors", "status_idle", + &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + settings.status_idle = COLOR_PAIR(3); + } else { + settings.status_idle = COLOR_PAIR(color_str_to_int(tmp)); + g_free(tmp); + } + + tmp = g_key_file_get_string(keyfile_get(), "colors", "status_busy", + &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + settings.status_busy = COLOR_PAIR(5); + } else { + settings.status_busy = COLOR_PAIR(color_str_to_int(tmp)); + g_free(tmp); + } + + tmp = g_key_file_get_string(keyfile_get(), "colors", "status_offline", + &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + settings.status_offline = 0; + } else { + settings.status_offline = COLOR_PAIR(color_str_to_int(tmp)); + g_free(tmp); + } + + tmp = g_key_file_get_string(keyfile_get(), "colors", "status_other", + &err); + if (tmp == NULL) { + if (err->code == G_KEY_FILE_ERROR_INVALID_VALUE) + g_error("invalid value: %s", err->message); + g_clear_error(&err); + settings.status_other = COLOR_PAIR(1); + } else { + settings.status_other = COLOR_PAIR(color_str_to_int(tmp)); + g_free(tmp); + } + +} + +ColorSettings * +color_get(void) +{ + return &settings; +} diff --git a/src/ui-command-line.c b/src/ui-command-line.c new file mode 100755 index 0000000..983a068 --- /dev/null +++ b/src/ui-command-line.c @@ -0,0 +1,116 @@ +#include "common.h" + +#define COMMAND_LINE_MAX_LENGHT 512 + +gint offset, len, ptr, max_width; + +wchar_t cmdbuf[COMMAND_LINE_MAX_LENGHT]; + +wchar_t * +commandline_get_buffer() +{ + return cmdbuf; +} + + +void +commandline_update_width() +{ + max_width = get_max_x() - 2; +} + +void +commandline_init() +{ + cmdbuf[0] = L'\0'; + offset = ptr = len = 0; + commandline_update_width(); +} + +void +commandline_draw() +{ + ColorSettings *c = color_get(); + gint i, width, cursor_pos = 0; + + /* + * clear + */ + mvhline(get_max_y() - 1, 0, ' ', get_max_x()); + + while (ptr < offset) + offset -= (max_width / 2); + + + for (i = offset, width = 0; i <= len && width <= max_width; i++) { + if (ptr == i) + cursor_pos = width; + + width += wcwidth(cmdbuf[i]); + } + + attron(c->command_line); + mvaddnwstr(get_max_y() - 1, 1, &cmdbuf[offset], i); + attroff(c->command_line); + + if (cursor_pos >= max_width - 1) + offset += (max_width / 2); + + update_panels(); + move(get_max_y() - 1, 1 + cursor_pos); + doupdate(); +} + +void +commandline_move_cursor(gint m) +{ + ptr += m; + + if (ptr < 0) + ptr = 0; + if (ptr >= len) + ptr = len; + + commandline_draw(); +} + +void +commandline_delete() +{ + gint i; + + if (ptr <= 0) + return; + + ptr--; + len--; + + for (i = ptr; i < len; i++) + cmdbuf[i] = cmdbuf[i + 1]; + + cmdbuf[i] = 0x0; + + commandline_draw(); + +} + +void +commandline_add_wch(wchar_t c) +{ + gint i; + + if (len >= COMMAND_LINE_MAX_LENGHT - 1) + return; + + + for (i = len; i > ptr; i--) + cmdbuf[i] = cmdbuf[i - 1]; + + cmdbuf[ptr] = c; + cmdbuf[len + 1] = L'\0'; + + len++; + ptr++; + + commandline_draw(); +} diff --git a/src/ui-command.c b/src/ui-command.c new file mode 100755 index 0000000..96c1f2b --- /dev/null +++ b/src/ui-command.c @@ -0,0 +1,129 @@ +#include "common.h" +#include +#include + +GHashTable *table = NULL; + + +/* + * This function is for command_func_help()'s private use + */ +void +_concat_command_str(gpointer key, gpointer value, gpointer user_data) +{ + gchar *tmp; + gchar **input = (gchar **) user_data; + + tmp = g_strjoin(", ", (gchar *) key, *input, NULL); + + if (*input != NULL) + g_free(*input); + + *input = tmp; +} + +/* + * List all registered commands + */ +gboolean +command_func_help(gint argc, gchar ** argv) +{ + wchar_t *outbuf; + gchar *mbseq; + + if (argc > 1) { + g_warning("help: at this time, displaying help for other " + "commands is not implemented"); + return FALSE; + } + + /* + * Display a list of available commands + */ + mbseq = NULL; + g_hash_table_foreach(table, _concat_command_str, &mbseq); + outbuf = g_new(wchar_t, strlen(mbseq) + 1); + utf8_to_wchar(mbseq, outbuf, strlen(mbseq)); + + window_add_message(window_get_current(), L"List of commands", A_BOLD, + outbuf); + + return TRUE; +} + +/* + * Exit the main-loop + */ +gboolean +command_func_quit(gint argc, gchar ** argv) +{ + stop_main_loop(); + return TRUE; +} + +/* + * Switch between windows + */ +gboolean +command_func_window(gint argc, gchar **argv) +{ + FamaWindow *w; + + if (argc != 2) { + g_warning("usage: /window "); + return FALSE; + } + + w = window_get_index(atoi(argv[1])); + + if (w == NULL) { + g_warning("No such window!"); + return FALSE; + } + + window_set_current(w); + + return TRUE; +} + +/* + * Add a new command + */ +void +command_add(gchar * command, CommandFunc func) +{ + g_hash_table_insert(table, (gpointer) command, (gpointer) func); +} + +/* + * Initialize the command hash-table + */ +void +command_init() +{ + table = g_hash_table_new(g_str_hash, g_str_equal); + command_add("help", command_func_help); + command_add("quit", command_func_quit); + command_add("window", command_func_window); +} + +/* + * Execute argv[0] as a command + */ +gboolean +command_execute(gint argc, gchar ** argv) +{ + CommandFunc func; + + if (argc < 1) + return FALSE; + + func = (CommandFunc) g_hash_table_lookup(table, argv[0]); + + if (func == NULL) + return FALSE; + + func(argc, argv); + + return TRUE; +} diff --git a/src/ui-contactlist.c b/src/ui-contactlist.c new file mode 100755 index 0000000..2f0b65e --- /dev/null +++ b/src/ui-contactlist.c @@ -0,0 +1,235 @@ +#include "common.h" + +struct list_item { + wchar_t *text; + gint attr; + // There should be a FamaContact datatype here in the future +}; + +GSList *categories = NULL; +WINDOW *clistwin = NULL; +PANEL *clistpanel = NULL; + +gint contactlist_width = 30; +int list_offset = 0, list_marked = 0; + +gint +contactlist_count_rows() +{ + GSList *categoryTmp; + gint count = 0; + + categoryTmp = categories; + do { + count += ((GPtrArray *) categoryTmp->data)->len; + } while ((categoryTmp = g_slist_next(categoryTmp)) != NULL); + + return count; +} + + + +void +contactlist_draw() +{ + GSList *categoryTmp; + GPtrArray *array; + struct list_item *item; + int i, a, str_len, str_width; + + if (clistwin == NULL) { + clistwin = newwin(get_max_y() - 3, + contactlist_width - 3, + 1, get_max_x() - contactlist_width + 2); + clistpanel = new_panel(clistwin); + + g_assert(clistwin != NULL && clistpanel != NULL); + } + + /* + * Clear the window + */ + werase(clistwin); + + if ((categoryTmp = categories) == NULL) + return; + + /* + * Skip a number of categories and items depending on offset + */ + i = a = 0; + do { + array = (GPtrArray *) categoryTmp->data; + if (i + array->len > list_offset) { + a = list_offset - i; + break; + } + i += array->len; + } while ((categoryTmp = g_slist_next(categoryTmp)) != NULL); + + /* + * Now categoryTmp will point to the category of which we will + * start to print items from, the integer 'a' is the index in + * the GPtrArray where we start. + */ + + i = 0; + while (categoryTmp != NULL && i < get_max_y() - 2) { + array = (GPtrArray *) categoryTmp->data; + for (; a < array->len; a++, i++) { + item = g_ptr_array_index(array, a); + g_assert(item != NULL); + + /* + * If we are at the selected item then + * * add reversed colors + */ + if (i == (list_marked - list_offset)) + item->attr |= A_REVERSE; + + wattron(clistwin, item->attr); + + /* + * If colors are reversed or underlined, + * * then fill the whole line to give a better effect. + */ + if ((item->attr & A_REVERSE) || + (item->attr & A_UNDERLINE)) + mvwhline(clistwin, i, 0, ' ', + contactlist_width - 3); + + + /* + * Make sure we don't print outside the window + */ + for (str_len = 0, str_width = 0; + str_width <= contactlist_width - 3 && + item->text[str_len] != L'\0'; str_len++) + str_width += wcwidth(item->text[str_len]); + + mvwaddnwstr(clistwin, i, 0, item->text, str_len); + wattrset(clistwin, A_NORMAL); + + if (i == (list_marked - list_offset)) + item->attr &= ~A_REVERSE; + + } + + /* + * reset for next category + */ + a = 0; + + categoryTmp = g_slist_next(categoryTmp); + } + + update_panels(); + doupdate(); +} + +void +contactlist_scroll(gint m) +{ + gint rows = contactlist_count_rows(); + + list_marked += m; + + if (list_marked < 0) + list_marked = 0; + if (list_marked >= rows - 1) + list_marked = rows - 1; + + while (list_marked >= (list_offset + get_max_y() - 3)) + list_offset++; + + if (list_marked < list_offset) + list_offset = list_marked; + + contactlist_draw(); +} + +void +contactlist_destroy() +{ + werase(clistwin); + update_panels(); + doupdate(); + del_panel(clistpanel); + delwin(clistwin); + clistpanel = NULL; + clistwin = NULL; +} + +gint +sort_function(gconstpointer a, gconstpointer b) +{ + gchar *aNew, *bNew; + gint r; + + if (a == b) + return TRUE; + + if (!a || !b) + return FALSE; + + aNew = g_utf8_casefold((gchar *) a, -1); + bNew = g_utf8_casefold((gchar *) b, -1); + + r = g_utf8_collate(aNew, bNew); + + g_free(aNew); + g_free(bNew); + + return r; +} + +/* Make a new GPtrArray of list_items and append the + * array to the GSList 'categories'. + */ + +void +contactlist_add_category(const wchar_t * title) +{ + struct list_item *c; + GPtrArray *array = NULL; + + c = (struct list_item *)g_malloc(sizeof(struct list_item)); + c->text = g_new(wchar_t, wcslen(title) + 1); + wcscpy(c->text, title); + + c->attr = A_UNDERLINE | A_BOLD; + + array = g_ptr_array_new(); + g_ptr_array_add(array, (gpointer) c); + categories = g_slist_append(categories, array); +} + +void +contactlist_add_item(guint category_index, const wchar_t * text, int attr) +{ + struct list_item *c; + GPtrArray *array = NULL; + + c = g_new(struct list_item, 1); + + c->text = g_new(wchar_t, wcslen(text) + 1); + wcscpy(c->text, text); + + c->attr = attr; + + array = (GPtrArray *) g_slist_nth_data(categories, category_index); + g_assert(array != NULL); + g_ptr_array_add(array, (gpointer) c); +} + +void +contactlist_set_width(gint a) +{ + contactlist_width = a; +} + +gint +contactlist_get_width() +{ + return contactlist_width; +} diff --git a/src/ui-interface.c b/src/ui-interface.c new file mode 100755 index 0000000..6bf0892 --- /dev/null +++ b/src/ui-interface.c @@ -0,0 +1,107 @@ +#include "common.h" + +#define BORDER ' ' + +int max_x, max_y; + +void +init_interface() +{ + FamaWindow *mainWin; + + stdscr = initscr(); + keypad(stdscr, TRUE); + noecho(); + + refresh(); + + getmaxyx(stdscr, max_y, max_x); + + color_init(); + commandline_init(); + + /* + * Only create a new main-window if there is no + * existing one already. + */ + if (window_get_current() == NULL) { + mainWin = window_new(WindowTypeMain); + window_set_title(mainWin, L"Console window"); + window_set_current(mainWin); + + // Remove following rows later + mainWin = window_new(WindowTypeConversation); + window_set_title(mainWin, L"Test window 1"); + mainWin = window_new(WindowTypeConversation); + window_set_title(mainWin, L"Test window 2"); + mainWin = window_new(WindowTypeConversation); + window_set_title(mainWin, L"Test window 3"); + mainWin = window_new(WindowTypeConversation); + window_set_title(mainWin, L"Test window 4"); + } + +} + +void +destroy_interface() +{ + contactlist_destroy(); + + erase(); + refresh(); + endwin(); +} + +void +draw_interface() +{ + ColorSettings *c = color_get(); + + /* + * Draw all borders + */ + + attron(c->borders); + mvvline(0, max_x - contactlist_get_width(), 0, max_y); + + attron(A_REVERSE); + mvhline(0, 0, BORDER, max_x); + mvhline(max_y - 2, 0, BORDER, max_x); + mvaddwstr(0, (max_x - 12) / 2, L"F A M A I M"); + attroff(A_REVERSE | c->borders); + update_panels(); + doupdate(); + + window_draw_title_bar(); + window_resize_all(); + + /* + * Draw contact list + */ + contactlist_draw(); + + /* + * Draw command-line + */ + commandline_draw(); +} + +gint +get_max_x() +{ + return max_x; +} + +gint +get_max_y() +{ + return max_y; +} + +void +redraw_interface() +{ + destroy_interface(); + init_interface(); + draw_interface(); +} diff --git a/src/ui-keyfile.c b/src/ui-keyfile.c new file mode 100755 index 0000000..92bf6aa --- /dev/null +++ b/src/ui-keyfile.c @@ -0,0 +1,95 @@ +#include +#include +#include "common.h" + +#define CONFIG_FILE_NAME ".famaconfig" + +const gchar *default_keyfile = "[ncurses]\n" + "contact_list_width=30\n" + "\n# If commented, Fama uses the charset of the current locale\n" + "#charset=UTF-8\n" + "[colors]\n" + "# Available colors are\n" + "# red, green, blue, black, white\n" + "# cyan, yellow, magenta and default\n" + "borders=green\n" + "command_line=default\n" + "window_title=cyan\n" + "message_heading=green\n" + "message_text=default\n" + "status_available=green\n" + "status_away=cyan\n" + "status_busy=magenta\n" + "status_idle=yellow\n" "status_offline=default\n" "status_other=red\n"; + + +GKeyFile *keyFile = NULL; + +GKeyFile * +keyfile_get() +{ + return keyFile; +} + +gint +keyfile_read() +{ + gchar *path; + GError *err = NULL; + gint flags = G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS; + + path = g_strdup_printf("%s/%s", g_get_home_dir(), CONFIG_FILE_NAME); + g_assert(path != NULL); + + keyFile = g_key_file_new(); + + g_key_file_load_from_file(keyFile, path, flags, &err); + + if (err != NULL) { + if (err->code == G_KEY_FILE_ERROR_NOT_FOUND || + err->code == G_FILE_ERROR_NOENT) { + g_clear_error(&err); + g_warning("Keyfile not found, creating a new one"); + g_key_file_load_from_data(keyFile, default_keyfile, + strlen(default_keyfile), + flags, &err); + + keyfile_write(); + } + + if (err != NULL) { + g_warning("Unable to read config: %s\n", err->message); + g_clear_error(&err); + return FALSE; + } + } + + return TRUE; +} + +gint +keyfile_write() +{ + GError *err = NULL; + gchar *data, *path; + gsize length; + + data = g_key_file_to_data(keyFile, &length, &err); + if (data == NULL) { + g_warning("Cannot get keyfile data: %s", err->message); + return FALSE; + } + + path = g_strdup_printf("%s/%s", g_get_home_dir(), CONFIG_FILE_NAME); + if (g_file_set_contents(path, data, length, &err) == FALSE) { + g_warning("Cannot write to %s: %s", path, err->message); + g_free(data); + g_free(path); + return FALSE; + } + + g_free(data); + g_free(path); + + return TRUE; +} diff --git a/src/ui-log.c b/src/ui-log.c new file mode 100755 index 0000000..0699848 --- /dev/null +++ b/src/ui-log.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include "common.h" + +void +log_get_time(gchar *buf, gsize size) +{ + struct tm *ptr; + time_t tm; + + tm = time(NULL); + ptr = localtime(&tm); + strftime(buf, size, "%H:%M:%S", ptr); +} + +void +log_function(const gchar * log_domain, GLogLevelFlags log_level, + const gchar * message, gpointer user_data) +{ + FamaWindow *w; + gchar time[32]; + wchar_t *wcs, title[128]; + gint slen, wlen; + + log_get_time(time, sizeof(time) - 1); + + /* + * Log to stderr + */ + if ((w = window_get_index(0)) == NULL) { + if (log_level == G_LOG_LEVEL_WARNING) + fprintf(stderr, "** %s Warning: %s\n", time, message); + else + fprintf(stderr, "** %s Message: %s\n", time, message); + + return; + } + + /* + * Log to NCurses main window + */ + + slen = strlen(message); + wcs = g_new(wchar_t, slen + 1); + wlen = mbstowcs(wcs, message, slen); + g_assert(wlen != (size_t) - 1); + + wcs[wlen] = L'\0'; + + if (log_level == G_LOG_LEVEL_WARNING) { + swprintf(title, sizeof(title) - 1, L"[%s] Warning", time); + window_add_message(w, title, A_BOLD | COLOR_PAIR(1), wcs); + } else { + swprintf(title, sizeof(title) - 1, L"[%s] Message", time); + window_add_message(w, title, A_BOLD | COLOR_PAIR(3), wcs); + } + + g_free(wcs); +} + + +void +log_init() +{ + /* + * Ncurses must be initialized before calling this function + */ + g_assert(stdscr != NULL); + + g_log_set_handler(G_LOG_DOMAIN, + G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE, + log_function, NULL); +} diff --git a/src/ui-signal.c b/src/ui-signal.c new file mode 100755 index 0000000..1c769d3 --- /dev/null +++ b/src/ui-signal.c @@ -0,0 +1,119 @@ +#include +#include +#include +#include + +#include "common.h" + +int signal_pipe[2]; + +void +pipe_signals(int signal) +{ + if (write(signal_pipe[1], &signal, sizeof(int)) != sizeof(int)) { + g_warning("Unix signal %d lost\n", signal); + } +} + +gboolean +deliver_signal(GIOChannel * source, GIOCondition cond, gpointer d) +{ + GError *error = NULL; /* for error handling */ + + union { + gchar chars[sizeof(int)]; + int signal; + } buf; + GIOStatus status; /* save the reading status */ + gsize bytes_read; /* save the number of chars read */ + + /* + * Read from the pipe as long as data is available. The reading end is + * also in non-blocking mode, so if we have consumed all unix signals, + * the read returns G_IO_STATUS_AGAIN. + */ + while ((status = g_io_channel_read_chars(source, buf.chars, + sizeof(int), &bytes_read, + &error)) == + G_IO_STATUS_NORMAL) { + g_assert(error == NULL); /* no error if reading returns normal */ + + /* + * There might be some problem resulting in too few char's read. + * Check it. + */ + if (bytes_read != sizeof(int)) { + g_warning + ("lost data in signal pipe (expected %u, received %d)\n", + sizeof(int), (int)bytes_read); + continue; /* discard the garbage and keep fingers crossed */ + } + + switch (buf.signal) { + case SIGWINCH: + redraw_interface(); + break; + default: + break; + + } + + } + + if (error != NULL) { + g_error("reading signal pipe failed: %s\n", error->message); + } + + if (status == G_IO_STATUS_EOF) { + g_error("signal pipe has been closed\n"); + } + + g_assert(status == G_IO_STATUS_AGAIN); + return (TRUE); +} + + +void +signal_handler_setup() +{ + GIOChannel *g_signal_in; + GError *error = NULL; /* handle errors */ + long fd_flags; /* used to change the pipe into non-blocking mode */ + + g_assert(pipe(signal_pipe) == 0); + + fd_flags = fcntl(signal_pipe[1], F_GETFL); + g_assert(fd_flags != -1); + + g_assert(fcntl(signal_pipe[1], F_SETFL, fd_flags | O_NONBLOCK) != -1); + + signal(SIGWINCH, pipe_signals); + + g_signal_in = g_io_channel_unix_new(signal_pipe[0]); + + /* + * we only read raw binary data from the pipe, + * therefore clear any encoding on the channel + */ + g_io_channel_set_encoding(g_signal_in, NULL, &error); + if (error != NULL) { /* handle potential errors */ + g_error("g_io_channel_set_encoding failed %s\n", + error->message); + } + + /* + * put the reading end also into non-blocking mode + */ + g_io_channel_set_flags(g_signal_in, + g_io_channel_get_flags(g_signal_in) | + G_IO_FLAG_NONBLOCK, &error); + + if (error != NULL) { /* tread errors */ + g_error("g_io_set_flags failed %s\n", error->message); + } + + /* + * register the reading end with the event loop + */ + g_io_add_watch(g_signal_in, G_IO_IN | G_IO_PRI, deliver_signal, NULL); +} diff --git a/src/ui-stdin-handler.c b/src/ui-stdin-handler.c new file mode 100755 index 0000000..5bb5034 --- /dev/null +++ b/src/ui-stdin-handler.c @@ -0,0 +1,128 @@ +#include "common.h" + +gboolean +stdin_handle_input(GIOChannel * source, GIOCondition cond, gpointer d) +{ + GError *err = NULL; + gunichar unichar; + gchar mbseq[32], *mbscmd, **argv; + wchar_t wchar[3], *cmdbuf; + gsize size; + gint argc; + + /* + * Read from the stdin as long as data is available. The reading end is + * also in non-blocking mode, so if we have consumed all data, + * the read returns G_IO_STATUS_AGAIN. + */ + while (get_wch(&unichar) != ERR) { + + if (unichar == 0x0a || unichar == KEY_ENTER) { + /* + * Do stuff with the buffer + */ + cmdbuf = commandline_get_buffer(); + + if (cmdbuf[0] == L'\0') { + /* + * Start conversation with selected contact + */ + g_message + ("In the future this will start a new chat" + " with the selected contact"); + } else if (cmdbuf[0] != L'/') { + /* + * Send message to the conversation at the current + * window. + */ + window_add_message(window_get_current(), L"Test output", A_BOLD, cmdbuf); + } else { + /* + * Convert wchar_t to gchar and then into an argument list + */ + mbscmd = utf8_from_wchar(cmdbuf, NULL, 0); + if (g_shell_parse_argv + (&mbscmd[1], &argc, &argv, &err) + == FALSE) { + g_warning("Could not parse command: %s", + err->message); + g_clear_error(&err); + } else { + /* + * Execute command!! + */ + if (command_execute(argc, argv) == + FALSE) + g_warning("Command '%s' not found!", argv[0]); + + g_strfreev(argv); + } + g_free(mbscmd); + } + + + /* + * Re-initialize the command-line + */ + commandline_init(); + commandline_draw(); + } else if (unichar == KEY_BACKSPACE) { + commandline_delete(); + } else if (unichar == KEY_LEFT) { + commandline_move_cursor(-1); + } else if (unichar == KEY_RIGHT) { + commandline_move_cursor(1); + } else if (unichar == KEY_UP) { + contactlist_scroll(-1); + } else if (unichar == KEY_DOWN) { + contactlist_scroll(1); + } else if (unichar == '\t') { + /* + * Ignore for the time being + */ + } else { + size = g_unichar_to_utf8(unichar, mbseq); + mbseq[size] = '\0'; + + utf8_to_wchar(mbseq, wchar, 2); + + /* + * Add character to command-line buffer + */ + commandline_add_wch(wchar[0]); + } + + } + return (TRUE); +} + + +void +stdin_handler_setup() +{ + GIOChannel *g_stdin; + GError *err = NULL; + + g_stdin = g_io_channel_unix_new(fileno(stdin)); + + g_io_channel_set_encoding(g_stdin, get_interface_encoding(), &err); + if (err != NULL) { + g_error("g_io_channel_set_encoding failed %s\n", err->message); + } + + /* + * put the reading end also into non-blocking mode + */ + g_io_channel_set_flags(g_stdin, + g_io_channel_get_flags(g_stdin) | + G_IO_FLAG_NONBLOCK, &err); + + if (err != NULL) { + g_error("g_io_set_flags failed %s\n", err->message); + } + + /* + * register the reading end with the event loop + */ + g_io_add_watch(g_stdin, G_IO_IN | G_IO_PRI, stdin_handle_input, NULL); +} diff --git a/src/ui-textwrap.c b/src/ui-textwrap.c new file mode 100755 index 0000000..8d34a50 --- /dev/null +++ b/src/ui-textwrap.c @@ -0,0 +1,98 @@ +#include "common.h" + +#define TEXTWRAP_MAX_ROWS 256 + +GPtrArray * +textwrap_new(wchar_t * title) +{ + wchar_t *first_row; + GPtrArray *m; + gint slen; + + m = g_ptr_array_new(); + + if (title == NULL) + return m; + + slen = wcslen(title); + + first_row = g_new(wchar_t, slen + 1); + wcsncpy(first_row, title, slen); + first_row[slen] = '\0'; + + g_ptr_array_add(m, first_row); + + return m; +} + +void +textwrap_destroy(GPtrArray * m) +{ + g_ptr_array_free(m, TRUE); +} + +void +textwrap_addrow(GPtrArray * m, const wchar_t * str, gint a, gint b) +{ + wchar_t *row; + + if (a == b) + return; + + while (m->len >= TEXTWRAP_MAX_ROWS) { + g_free(g_ptr_array_index(m, 0)); + g_ptr_array_remove_index(m, 0); + } + + row = g_new(wchar_t, b - a + 1); + wcsncpy(row, &str[a], b - a); + row[b - a] = '\0'; + + g_ptr_array_add(m, row); +} + +gint +textwrap_append(GPtrArray * m, const wchar_t * text, guint max_width) +{ + gint c, width, last_word, start, text_len; + + text_len = wcslen(text); + + for (c = 0, last_word = 0, start = 0, width = 0; c < text_len; c++) { + + if (text[c] == L'\n') { + textwrap_addrow(m, text, start, c); + last_word = start = ++c; + width = 0; + continue; + } + + if (text[c] == L' ') + last_word = c + 1; + + width += wcwidth(text[c]); + + if (width >= max_width) { + if (start != last_word) { + textwrap_addrow(m, text, start, last_word); + width = wcswidth(&text[last_word], + c - last_word); + start = last_word; + } else { + textwrap_addrow(m, text, start, c); + start = last_word = c; + width = 0; + } + } + } + + textwrap_addrow(m, text, start, c); + + /* + * Return number of rows in the textwrap + */ + + return m->len; +} + + diff --git a/src/ui-utf8.c b/src/ui-utf8.c new file mode 100755 index 0000000..102d896 --- /dev/null +++ b/src/ui-utf8.c @@ -0,0 +1,100 @@ +#include +#include + +#include "common.h" + +#define UNLIMITED 32000 + +gchar *encoding = NULL; + +void +set_interface_encoding(gchar * enc) +{ + if (encoding != NULL) + g_free(encoding); + + encoding = enc; +} + +gchar * +get_interface_encoding(void) +{ + return encoding; +} + + +wchar_t * +utf8_to_wchar(const gchar * str, wchar_t * outbuf, gsize outbuf_size) +{ + gchar *locale_str; + gsize bytes_read, bytes_written, numwcs; + + if (str == NULL) { + *outbuf = L'\0'; + return outbuf; + } + + g_assert(g_utf8_validate(str, -1, NULL) == TRUE); + + locale_str = + g_convert_with_fallback(str, -1, encoding, "UTF-8", "?", + &bytes_read, &bytes_written, NULL); + g_assert(locale_str != NULL); + + numwcs = mbstowcs(outbuf, locale_str, outbuf_size); + g_free(locale_str); + + g_assert(numwcs != (size_t) - 1); + + outbuf[numwcs] = L'\0'; + + return outbuf; +} + + +/* + * If outbuf == NULL, then a buffer will be allocated dynamically + */ + +gchar * +utf8_from_wchar(const wchar_t * str, gchar * outbuf, gsize outbuf_size) +{ + gchar *utf8_str, *tmp; + gsize bytes_read, bytes_written; + + if (str == NULL) { + if (outbuf == NULL) + return NULL; + + *outbuf = '\0'; + return outbuf; + } + + if (outbuf == NULL) { + bytes_written = wcsrtombs(NULL, &str, 0, NULL); + tmp = g_new(gchar, bytes_written + 1); + outbuf_size = UNLIMITED; + } else { + tmp = g_new(gchar, outbuf_size + 1); + } + + bytes_written = wcsrtombs(tmp, &str, outbuf_size, NULL); + g_assert(bytes_written != (size_t) - 1); + + utf8_str = + g_convert_with_fallback(tmp, -1, "UTF-8", encoding, "?", + &bytes_read, &bytes_written, NULL); + g_assert(utf8_str != NULL); + + g_free(tmp); + + if (outbuf == NULL) + return utf8_str; + + strncpy(outbuf, utf8_str, outbuf_size); + outbuf[bytes_written] = L'\0'; + + g_free(utf8_str); + + return outbuf; +} diff --git a/src/ui-window.c b/src/ui-window.c new file mode 100755 index 0000000..9d2866d --- /dev/null +++ b/src/ui-window.c @@ -0,0 +1,218 @@ +#include "common.h" + +#define WINDOW_WIDTH (get_max_x() - contactlist_get_width()) + +GPtrArray *window_list = NULL; +FamaWindow *current_window = NULL; + +void +window_draw_title_bar() +{ + gint l, w; + ColorSettings *c = color_get(); + + attron(A_UNDERLINE | A_BOLD | c->window_title); + mvhline(1, 0, ' ', WINDOW_WIDTH); + + if (current_window->title != NULL) { + + for (l = 0, w = 2; + w < WINDOW_WIDTH && current_window->title[l] != L'\0'; l++) + w += wcwidth(current_window->title[l]); + + mvaddnwstr(1, 2, current_window->title, l); + } + attrset(A_NORMAL); +} + +void +window_set_title(FamaWindow * w, wchar_t * title) +{ + if (w->title != NULL) + g_free(w->title); + + w->title = g_new(wchar_t, wcslen(title) + 1); + wcscpy(w->title, title); +} + +void +window_set_current(FamaWindow * w) +{ + g_assert(w != NULL); + + current_window = w; + w->is_updated = FALSE; + + top_panel(w->ncpanel); + window_draw_title_bar(); + + update_panels(); + doupdate(); +} + +FamaWindow * +window_get_index(gint i) +{ + if (i < 0 || i >= window_list->len) + return NULL; + + return g_ptr_array_index(window_list, i); +} + +FamaWindow * +window_get_current() +{ + return current_window; +} + + +FamaWindow * +window_new(FamaWindowType type) +{ + FamaWindow *w; + + if (window_list == NULL) + window_list = g_ptr_array_new(); + + w = g_new(FamaWindow, 1); + w->ncwin = newwin(get_max_y() - 4, WINDOW_WIDTH, 2, 0); + g_assert(w->ncwin != NULL); + + scrollok(w->ncwin, TRUE); + + w->ncpanel = new_panel(w->ncwin); + g_assert(w->ncpanel != NULL); + + w->messages = g_ptr_array_new(); + w->type = type; + w->title = NULL; + w->is_updated = TRUE; + + g_ptr_array_add(window_list, w); + + bottom_panel(w->ncpanel); + + return w; +} + +void +window_append_rows(FamaWindow * w, GPtrArray * m, gint attr) +{ + ColorSettings *c = color_get(); + gint i; + + for (i = 0; i < m->len; i++) { + waddwstr(w->ncwin, L"\n"); + + if (i == 0) + wattron(w->ncwin, attr); + else + wattron(w->ncwin, c->message_text); + + waddwstr(w->ncwin, g_ptr_array_index(m, i)); + + wattrset(w->ncwin, A_NORMAL); + + } +} + +/* + * window_add_message is a wrapper function to window_append_rows + */ +void +window_add_message(FamaWindow * w, wchar_t * title, gint attr, wchar_t * str) +{ + GPtrArray *rows; + FamaMessage *message; + + message = g_new(FamaMessage, 1); + + message->title = g_new(wchar_t, wcslen(title) + 1); + message->message = g_new(wchar_t, wcslen(str) + 1); + wcscpy(message->title, title); + wcscpy(message->message, str); + + message->attr = attr; + + g_ptr_array_add(w->messages, message); + + w->is_updated = (current_window == w) ? TRUE : FALSE; + + rows = textwrap_new(title); + textwrap_append(rows, str, WINDOW_WIDTH - 2); + window_append_rows(w, rows, attr); + textwrap_destroy(rows); + + update_panels(); + doupdate(); +} + +void +window_destroy_helper_func(gpointer data, gpointer user_data) +{ + FamaMessage *m = (FamaMessage *) data; + + g_free(m->message); + g_free(m->title); +} + +void +window_destroy(FamaWindow * w) +{ + if (w->type == WindowTypeMain || w->type == WindowTypeConversation) { + g_ptr_array_foreach(w->messages, window_destroy_helper_func, + NULL); + g_ptr_array_free(w->messages, FALSE); + } + + del_panel(w->ncpanel); + delwin(w->ncwin); + + g_ptr_array_remove(window_list, w); + window_set_current(g_ptr_array_index(window_list, 0)); +} + +void +window_resize_helper_func(gpointer data, gpointer user_data) +{ + FamaWindow *w = (FamaWindow *) data; + FamaMessage *m; + GPtrArray *rows; + WINDOW *new_win; + gint i; + + new_win = newwin(get_max_y() - 4, WINDOW_WIDTH, 2, 0); + g_assert(new_win != NULL); + scrollok(new_win, TRUE); + + replace_panel(w->ncpanel, new_win); + delwin(w->ncwin); + w->ncwin = new_win; + + werase(w->ncwin); + + + /* + * Resize messages + */ + + if ((w->type != WindowTypeMain && + w->type != WindowTypeConversation) || w->messages == NULL) + return; + + for (i = 0; i < w->messages->len; i++) { + m = g_ptr_array_index(w->messages, i); + rows = textwrap_new(m->title); + textwrap_append(rows, m->message, WINDOW_WIDTH - 2); + window_append_rows(w, rows, m->attr); + textwrap_destroy(rows); + } +} + +void +window_resize_all() +{ + g_ptr_array_foreach(window_list, window_resize_helper_func, NULL); + update_panels(); + doupdate(); +}