1 // Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
2 // Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
4 // This file is part of CenterIM.
6 // CenterIM is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // CenterIM is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with CenterIM. If not, see <http://www.gnu.org/licenses/>.
22 #include "BuddyList.h"
23 #include "Connections.h"
24 #include "Conversations.h"
30 #include "Transfers.h"
32 #include "AccountStatusMenu.h"
33 #include "GeneralMenu.h"
37 #include <cppconsui/ColorScheme.h>
38 #include <cppconsui/KeyConfig.h>
43 #include <glib/gprintf.h>
49 #define CIM_CONFIG_PATH ".centerim5"
51 #define GLIB_IO_READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR)
52 #define GLIB_IO_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL)
54 // Program's entry point.
55 int main(int argc
, char *argv
[])
57 return CenterIM::run(argc
, argv
);
60 const char *CenterIM::color_names_
[] = {
72 const char *CenterIM::scheme_names_
[] = {
74 "accountstatusmenu", // SCHEME_ACCOUNTSTATUSMENU
75 "buddylist", // SCHEME_BUDDYLIST
76 "buddylistbuddy", // SCHEME_BUDDYLISTBUDDY
77 "buddylistbuddy_away", // SCHEME_BUDDYLISTBUDDY_AWAY
78 "buddylistbuddy_na", // SCHEME_BUDDYLISTBUDDY_NA
79 "buddylistbuddy_offline", // SCHEME_BUDDYLISTBUDDY_OFFLINE
80 "buddylistbuddy_online", // SCHEME_BUDDYLISTBUDDY_ONLINE
81 "buddylistchat", // SCHEME_BUDDYLISTCHAT
82 "buddylistcontact", // SCHEME_BUDDYLISTCONTACT
83 "buddylistcontact_away", // SCHEME_BUDDYLISTCONTACT_AWAY
84 "buddylistcontact_na", // SCHEME_BUDDYLISTCONTACT_NA
85 "buddylistcontact_offline", // SCHEME_BUDDYLISTCONTACT_OFFLINE
86 "buddylistcontact_online", // SCHEME_BUDDYLISTCONTACT_ONLINE
87 "buddylistgroup", // SCHEME_BUDDYLISTGROUP
88 "conversation", // SCHEME_CONVERSATION
89 "conversation-active", // SCHEME_CONVERSATION_ACTIVE
90 "conversation-new", // SCHEME_CONVERSATION_NEW
91 "footer", // SCHEME_FOOTER
92 "generalmenu", // SCHEME_GENERALMENU
93 "generalwindow", // SCHEME_GENERALWINDOW
94 "header", // SCHEME_HEADER
95 "header-request", // SCHEME_HEADER_REQUEST
99 CenterIM
*CenterIM::my_instance_
= nullptr;
101 // Based on glibmm code.
102 class SourceConnectionNode
{
104 explicit inline SourceConnectionNode(const sigc::slot_base
&nslot
);
106 static void *notify(void *data
);
107 static void destroy_notify_callback(void *data
);
108 static gboolean
source_callback(void *data
);
110 inline void install(GSource
*nsource
);
111 inline sigc::slot_base
*get_slot();
114 sigc::slot_base slot
;
118 inline SourceConnectionNode::SourceConnectionNode(const sigc::slot_base
&nslot
)
119 : slot(nslot
), source(nullptr)
121 slot
.set_parent(this, &SourceConnectionNode::notify
);
124 void *SourceConnectionNode::notify(void *data
)
126 SourceConnectionNode
*self
= reinterpret_cast<SourceConnectionNode
*>(data
);
128 // If there is no object, this call was triggered from
129 // destroy_notify_handler(), because we set self->source to 0 there.
131 GSource
*s
= self
->source
;
132 self
->source
= nullptr;
135 // Destroying the object triggers execution of destroy_notify_handler(),
136 // either immediately or later, so we leave that to do the deletion.
142 void SourceConnectionNode::destroy_notify_callback(void *data
)
144 SourceConnectionNode
*self
= reinterpret_cast<SourceConnectionNode
*>(data
);
147 // The GLib side is disconnected now, thus the GSource* is no longer valid.
148 self
->source
= nullptr;
154 gboolean
SourceConnectionNode::source_callback(void *data
)
156 SourceConnectionNode
*conn_data
=
157 reinterpret_cast<SourceConnectionNode
*>(data
);
159 // Recreate the specific slot from the generic slot node.
160 return (*static_cast<sigc::slot
<bool> *>(conn_data
->get_slot()))();
163 inline void SourceConnectionNode::install(GSource
*nsource
)
168 inline sigc::slot_base
*SourceConnectionNode::get_slot()
173 CenterIM
*CenterIM::instance()
178 bool CenterIM::processInput(const TermKeyKey
&key
)
180 if (idle_reporting_on_keyboard_
)
182 return InputProcessor::processInput(key
);
185 void CenterIM::quit()
187 g_main_loop_quit(mainloop_
);
190 CppConsUI::Rect
CenterIM::getScreenArea(ScreenArea area
)
195 CppConsUI::Rect
CenterIM::getScreenAreaCentered(ScreenArea area
)
197 CppConsUI::Rect s
= areas_
[WHOLE_AREA
];
198 CppConsUI::Rect r
= areas_
[area
];
199 int x
= (s
.width
- r
.width
) / 2;
200 int y
= (s
.height
- r
.height
) / 2;
201 return CppConsUI::Rect(x
, y
, r
.width
, r
.height
);
204 bool CenterIM::loadColorSchemeConfig()
207 purple_util_read_xml_from_file("colorschemes.xml", _("color schemes"));
209 if (root
== nullptr) {
210 // Read error, first time run?
211 loadDefaultColorSchemeConfig();
212 if (saveColorSchemeConfig())
217 COLORSCHEME
->clear();
220 for (xmlnode
*scheme_node
= xmlnode_get_child(root
, "scheme");
221 scheme_node
!= nullptr;
222 scheme_node
= xmlnode_get_next_twin(scheme_node
)) {
223 const char *scheme_name
= xmlnode_get_attrib(scheme_node
, "name");
224 if (scheme_name
== nullptr) {
225 LOG
->error(_("Missing 'name' attribute in the scheme definition."));
229 int scheme
= stringToScheme(scheme_name
);
231 LOG
->error(_("Unrecognized scheme '%s'."), scheme_name
);
235 for (xmlnode
*color
= xmlnode_get_child(scheme_node
, "color"); color
;
236 color
= xmlnode_get_next_twin(color
)) {
237 const char *widget_string
= xmlnode_get_attrib(color
, "widget");
238 if (widget_string
== nullptr) {
239 LOG
->error(_("Missing 'widget' attribute in the color definition."));
243 const char *property_string
= xmlnode_get_attrib(color
, "property");
244 if (property_string
== nullptr) {
245 LOG
->error(_("Missing 'property' attribute in the color definition."));
249 CppConsUI::ColorScheme::PropertyConversionResult conv_res
;
252 conv_res
= COLORSCHEME
->stringPairToPropertyPair(
253 widget_string
, property_string
, &property
, &subproperty
);
255 case CppConsUI::ColorScheme::CONVERSION_SUCCESS
:
257 case CppConsUI::ColorScheme::CONVERSION_ERROR_WIDGET
:
258 LOG
->error(_("Unrecognized widget '%s'."), widget_string
);
260 case CppConsUI::ColorScheme::CONVERSION_ERROR_PROPERTY
:
261 LOG
->error(_("Unrecognized property '%s'."), property_string
);
264 g_assert_not_reached();
267 const char *fg_string
= xmlnode_get_attrib(color
, "foreground");
268 const char *bg_string
= xmlnode_get_attrib(color
, "background");
269 const char *attrs_string
= xmlnode_get_attrib(color
, "attributes");
271 int fg
= CppConsUI::Curses::Color::DEFAULT
;
272 int bg
= CppConsUI::Curses::Color::DEFAULT
;
275 if (fg_string
!= nullptr && !stringToColor(fg_string
, &fg
)) {
276 LOG
->error(_("Unrecognized color '%s'."), fg_string
);
280 if (bg_string
!= nullptr && !stringToColor(bg_string
, &bg
)) {
281 LOG
->error(_("Unrecognized color '%s'."), bg_string
);
285 if (attrs_string
!= nullptr &&
286 !stringToColorAttributes(attrs_string
, &attrs
)) {
287 LOG
->error(_("Unrecognized attributes '%s'."), attrs_string
);
291 COLORSCHEME
->setAttributesExt(
292 scheme
, property
, subproperty
, fg
, bg
, attrs
);
300 LOG
->error(_("Error parsing 'colorschemes.xml', "
301 "loading default color scheme."));
302 loadDefaultColorSchemeConfig();
310 bool CenterIM::loadKeyConfig()
313 purple_util_read_xml_from_file("binds.xml", _("key bindings"));
315 if (root
== nullptr) {
316 // Read error, first time run?
317 loadDefaultKeyConfig();
326 for (xmlnode
*bind_node
= xmlnode_get_child(root
, "bind");
327 bind_node
!= nullptr; bind_node
= xmlnode_get_next_twin(bind_node
)) {
328 const char *context
= xmlnode_get_attrib(bind_node
, "context");
329 if (context
== nullptr) {
330 LOG
->error(_("Missing 'context' attribute in the bind definition."));
333 const char *action
= xmlnode_get_attrib(bind_node
, "action");
334 if (action
== nullptr) {
335 LOG
->error(_("Missing 'action' attribute in the bind definition."));
338 const char *key
= xmlnode_get_attrib(bind_node
, "key");
339 if (key
== nullptr) {
340 LOG
->error(_("Missing 'key' attribute in the bind definition."));
344 if (!KEYCONFIG
->bindKey(context
, action
, key
)) {
345 LOG
->error(_("Unrecognized key '%s'."), key
);
354 LOG
->error(_("Error parsing 'binds.xml', loading default keys."));
355 loadDefaultKeyConfig();
363 sigc::connection
CenterIM::timeoutConnect(
364 const sigc::slot
<bool> &slot
, unsigned interval
, int priority
)
366 auto conn_node
= new SourceConnectionNode(slot
);
367 sigc::connection
connection(*conn_node
->get_slot());
369 GSource
*source
= g_timeout_source_new(interval
);
371 if (priority
!= G_PRIORITY_DEFAULT
)
372 g_source_set_priority(source
, priority
);
374 g_source_set_callback(source
, &SourceConnectionNode::source_callback
,
375 conn_node
, &SourceConnectionNode::destroy_notify_callback
);
377 g_source_attach(source
, nullptr);
378 g_source_unref(source
); // GMainContext holds a reference.
380 conn_node
->install(source
);
384 sigc::connection
CenterIM::timeoutOnceConnect(
385 const sigc::slot
<void> &slot
, unsigned interval
, int priority
)
387 return timeoutConnect(sigc::bind_return(slot
, FALSE
), interval
, priority
);
391 : mainloop_(nullptr), mainloop_error_exit_(false), mngr_(nullptr),
392 convs_expanded_(false), idle_reporting_on_keyboard_(false),
393 stdin_timeout_id_(0), resize_pending_(false),
394 sigwinch_write_error_(nullptr), sigwinch_write_error_size_(0)
396 resize_pipe_
[0] = -1;
397 resize_pipe_
[1] = -1;
399 std::memset(¢erim_core_ui_ops_
, 0, sizeof(centerim_core_ui_ops_
));
400 std::memset(&logbuf_debug_ui_ops_
, 0, sizeof(logbuf_debug_ui_ops_
));
401 std::memset(¢erim_glib_eventloops_
, 0, sizeof(centerim_glib_eventloops_
));
404 int CenterIM::run(int argc
, char *argv
[])
407 g_assert(my_instance_
== nullptr);
408 my_instance_
= new CenterIM
;
411 int res
= my_instance_
->runAll(argc
, argv
);
413 // Finalize CenterIM.
414 g_assert(my_instance_
!= nullptr);
417 my_instance_
= nullptr;
422 int CenterIM::runAll(int argc
, char *argv
[])
425 bool cppconsui_input_initialized
= false;
426 bool cppconsui_output_initialized
= false;
427 bool screen_resizing_initialized
= false;
428 bool purple_initialized
= false;
429 CppConsUI::Error error
;
430 guint stdin_watch_handle
;
431 guint resize_watch_handle
;
432 sigc::connection resize_conn
;
433 sigc::connection top_window_change_conn
;
435 // Set program name for glib.
436 g_set_prgname(PACKAGE_NAME
);
438 setlocale(LC_ALL
, "");
442 bindtextdomain(PACKAGE_NAME
, LOCALEDIR
);
443 bind_textdomain_codeset(PACKAGE_NAME
, "UTF-8");
444 bind_textdomain_codeset("pidgin", "UTF-8");
445 textdomain(PACKAGE_NAME
);
448 signal(SIGPIPE
, SIG_IGN
);
450 // Parse command-line arguments.
452 bool offline
= false;
453 const char *config_path
= CIM_CONFIG_PATH
;
456 struct option long_options
[] = {
457 {"ascii", no_argument
, nullptr, 'a'},
458 {"help", no_argument
, nullptr, 'h'},
459 {"version", no_argument
, nullptr, 'v'},
460 {"basedir", required_argument
, nullptr, 'b'},
461 {"offline", no_argument
, nullptr, 'o'},
462 {nullptr, 0, nullptr, 0 }
467 (opt
= getopt_long(argc
, argv
, "ahvb:o", long_options
, nullptr)) != -1) {
473 printUsage(stdout
, argv
[0]);
476 printVersion(stdout
);
479 config_path
= optarg
;
485 printUsage(stderr
, argv
[0]);
491 std::fprintf(stderr
, _("%s: unexpected argument after options\n"), argv
[0]);
492 printUsage(stderr
, argv
[0]);
496 // Initialize the internal logger. It will buffer all messages produced by
497 // GLib, libpurple, or CppConsUI until it is possible to output them on the
498 // screen (in the log window). If any part of the initialization fails the
499 // buffered messages will be printed on stderr.
502 // Create the main loop.
503 mainloop_
= g_main_loop_new(nullptr, FALSE
);
505 // Initialize CppConsUI.
506 CppConsUI::AppInterface interface
= {
507 sigc::mem_fun(this, &CenterIM::redraw_cppconsui
),
508 sigc::mem_fun(this, &CenterIM::log_debug_cppconsui
)};
509 CppConsUI::initializeConsUI(interface
);
511 // Get the CoreManager instance.
512 mngr_
= CppConsUI::getCoreManagerInstance();
513 g_assert(mngr_
!= nullptr);
515 // Initialize CppConsUI input and output.
516 if (mngr_
->initializeInput(error
) != 0) {
517 LOG
->error("%s", error
.getString());
520 cppconsui_input_initialized
= true;
521 if (mngr_
->initializeOutput(error
) != 0) {
522 LOG
->error("%s", error
.getString());
525 cppconsui_output_initialized
= true;
529 CppConsUI::Curses::setAsciiMode(ascii
);
531 // Register for some signals.
532 resize_conn
= mngr_
->signal_resize
.connect(
533 sigc::mem_fun(this, &CenterIM::onScreenResized
));
534 top_window_change_conn
= mngr_
->signal_top_window_change
.connect(
535 sigc::mem_fun(this, &CenterIM::onTopWindowChanged
));
537 // Declare CenterIM bindables.
540 // Set up screen resizing.
541 if (initializeScreenResizing() != 0) {
542 LOG
->error(_("Screen resizing initialization failed."));
545 screen_resizing_initialized
= true;
547 // Initialize libpurple.
548 if (initializePurple(config_path
) != 0) {
549 LOG
->error(_("Libpurple initialization failed."));
552 purple_initialized
= true;
554 // Initialize global preferences.
555 initializePreferences();
557 // Initialize the log window.
558 LOG
->initNormalPhase();
560 // Init colorschemes and keybinds after the Log is initialized so the user can
561 // see if there is any error in the configs.
562 loadColorSchemeConfig();
573 Conversations::init();
575 // Init BuddyList last so it takes the focus.
578 LOG
->info(_("Welcome to CenterIM 5. Press %s to display main menu."),
579 KEYCONFIG
->getKeyBind("centerim", "generalmenu"));
581 // Restore last know status on all accounts.
582 ACCOUNTS
->restoreStatuses(offline
);
584 mngr_
->setTopInputProcessor(*this);
585 mngr_
->onScreenResized();
587 // Initialize input processing.
589 GIOChannel
*stdin_channel
= g_io_channel_unix_new(STDIN_FILENO
);
590 stdin_watch_handle
= g_io_add_watch_full(stdin_channel
, G_PRIORITY_DEFAULT
,
591 static_cast<GIOCondition
>(GLIB_IO_READ_COND
), stdin_bytes_available_
,
593 g_io_channel_unref(stdin_channel
);
596 // Add a watch of the self-pipe for screen resizing.
598 GIOChannel
*resize_channel
= g_io_channel_unix_new(resize_pipe_
[0]);
599 resize_watch_handle
= g_io_add_watch_full(resize_channel
,
600 G_PRIORITY_DEFAULT
, static_cast<GIOCondition
>(GLIB_IO_READ_COND
),
601 resize_bytes_available_
, this, nullptr);
602 g_io_channel_unref(resize_channel
);
605 // Start the main loop.
606 g_main_loop_run(mainloop_
);
608 if (!mainloop_error_exit_
) {
609 // If the program is not exiting with an error then clear all buffered
610 // messages because the user could already see them in the Log window.
611 LOG
->clearAllBufferedMessages();
614 // Finalize input processing.
615 g_source_remove(stdin_watch_handle
);
616 // Also remove any stdin timeout source.
617 if (stdin_timeout_id_
!= 0) {
618 g_source_remove(stdin_timeout_id_
);
619 stdin_timeout_id_
= 0;
622 // Remove the self-pipe watch.
623 g_source_remove(resize_watch_handle
);
625 purple_prefs_disconnect_by_handle(this);
627 resize_conn
.disconnect();
628 top_window_change_conn
.disconnect();
630 Conversations::finalize();
632 BuddyList::finalize();
634 Accounts::finalize();
635 Connections::finalize();
641 LOG
->finalizeNormalPhase();
643 if (!mainloop_error_exit_
) {
644 // Everything went ok.
649 // Finalize libpurple.
650 if (purple_initialized
)
653 // Finalize screen resizing.
654 if (screen_resizing_initialized
)
655 finalizeScreenResizing();
657 // Finalize CppConsUI input and output.
658 if (cppconsui_output_initialized
&& mngr_
->finalizeOutput(error
) != 0)
659 LOG
->error("%s", error
.getString());
660 if (cppconsui_input_initialized
&& mngr_
->finalizeInput(error
) != 0)
661 LOG
->error("%s", error
.getString());
663 // Finalize CppConsUI.
664 CppConsUI::finalizeConsUI();
666 // Destroy the main loop.
667 if (mainloop_
!= nullptr)
668 g_main_loop_unref(mainloop_
);
670 // Finalize the log component. It will output all buffered messages (if there
671 // are any) on stderr.
677 void CenterIM::printUsage(FILE *out
, const char *prg_name
)
681 "Usage: %s [option]...\n\n"
683 " -a, --ascii use ASCII characters to draw lines and boxes\n"
684 " -h, --help display command line usage\n"
685 " -v, --version show the program version info\n"
686 " -b, --basedir <directory> specify another base directory\n"
687 " -o, --offline start with all accounts set offline\n"),
692 void CenterIM::printVersion(FILE *out
)
694 std::fprintf(out
, "CenterIM %s\n", version_
);
697 int CenterIM::initializePurple(const char *config_path
)
699 g_assert(config_path
!= nullptr);
701 // Build config path.
702 if (g_path_is_absolute(config_path
)) {
704 purple_util_set_user_dir(config_path
);
707 char *path
= g_build_filename(purple_home_dir(), config_path
, nullptr);
708 g_assert(g_path_is_absolute(path
));
709 purple_util_set_user_dir(path
);
713 // This does not disable debugging, but rather it disables printing to stdout.
714 // Do not change this to TRUE or things will get messy.
715 purple_debug_set_enabled(FALSE
);
717 // Catch libpurple messages.
718 logbuf_debug_ui_ops_
.print
= purple_print
;
719 logbuf_debug_ui_ops_
.is_enabled
= purple_is_enabled
;
720 purple_debug_set_ui_ops(&logbuf_debug_ui_ops_
);
723 centerim_core_ui_ops_
.get_ui_info
= get_ui_info
;
724 purple_core_set_ui_ops(¢erim_core_ui_ops_
);
726 // Set the uiops for the eventloop.
727 centerim_glib_eventloops_
.timeout_add
= g_timeout_add
;
728 centerim_glib_eventloops_
.timeout_remove
= g_source_remove
;
729 centerim_glib_eventloops_
.input_add
= input_add_purple
;
730 centerim_glib_eventloops_
.input_remove
= g_source_remove
;
731 purple_eventloop_set_ui_ops(¢erim_glib_eventloops_
);
733 // Add a search path for user-specific plugins.
734 char *path
= g_build_filename(purple_user_dir(), "plugins", nullptr);
735 purple_plugins_add_search_path(path
);
738 // Add a search path for centerim-specific plugins.
739 purple_plugins_add_search_path(PKGLIBDIR
);
741 if (!purple_core_init(PACKAGE_NAME
))
744 purple_prefs_add_none(CONF_PREFIX
);
745 purple_prefs_add_none(CONF_PLUGINS_PREF
);
747 // Load the desired plugins.
748 if (purple_prefs_exists(CONF_PLUGINS_SAVE_PREF
))
749 purple_plugins_load_saved(CONF_PLUGINS_SAVE_PREF
);
754 void CenterIM::finalizePurple()
756 purple_plugins_save_loaded(CONF_PLUGINS_SAVE_PREF
);
758 purple_core_set_ui_ops(nullptr);
759 // purple_eventloop_set_ui_ops(nullptr);
763 void CenterIM::initializePreferences()
766 if (purple_prefs_exists("/centerim"))
767 purple_prefs_rename("/centerim", CONF_PREFIX
);
769 // Initialize preferences.
770 purple_prefs_add_none(CONF_PREFIX
"/dimensions");
771 purple_prefs_add_int(CONF_PREFIX
"/dimensions/buddylist_width", 20);
772 purple_prefs_add_int(CONF_PREFIX
"/dimensions/log_height", 25);
773 purple_prefs_add_bool(CONF_PREFIX
"/dimensions/show_header", true);
774 purple_prefs_add_bool(CONF_PREFIX
"/dimensions/show_footer", true);
775 purple_prefs_connect_callback(
776 this, CONF_PREFIX
"/dimensions", dimensions_change_
, this);
778 purple_prefs_connect_callback(
779 this, "/purple/away/idle_reporting", idle_reporting_change_
, this);
780 // Trigger the callback. Note: This potentially triggers other callbacks
782 purple_prefs_trigger_callback("/purple/away/idle_reporting");
785 int CenterIM::initializeScreenResizing()
789 // Get error message reported from the SIGWINCH handler when a write to the
791 sigwinch_write_error_
=
792 _("Write to the self-pipe for screen resizing failed.\n");
793 sigwinch_write_error_size_
= std::strlen(sigwinch_write_error_
) + 1;
795 // Create a self-pipe.
796 g_assert(resize_pipe_
[0] == -1);
797 g_assert(resize_pipe_
[1] == -1);
799 res
= pipe(resize_pipe_
);
801 LOG
->error(_("Creating a self-pipe for screen resizing failed."));
805 // Set close-on-exec on both descriptors.
806 res
= fcntl(resize_pipe_
[0], F_GETFD
);
808 res
= fcntl(resize_pipe_
[0], F_SETFD
, res
| FD_CLOEXEC
);
810 res
= fcntl(resize_pipe_
[1], F_GETFD
);
812 res
= fcntl(resize_pipe_
[1], F_SETFD
, res
| FD_CLOEXEC
);
815 // Register a SIGWINCH handler.
816 struct sigaction sig
;
817 sig
.sa_handler
= sigwinch_handler_
;
818 sig
.sa_flags
= SA_RESTART
;
819 res
= sigemptyset(&sig
.sa_mask
);
821 res
= sigaction(SIGWINCH
, &sig
, nullptr);
827 void CenterIM::finalizeScreenResizing()
831 // Unregister the SIGWINCH handler.
832 struct sigaction sig
;
833 sig
.sa_handler
= SIG_DFL
;
835 res
= sigemptyset(&sig
.sa_mask
);
837 res
= sigaction(SIGWINCH
, &sig
, nullptr);
840 // Destroy the self-pipe.
841 g_assert(resize_pipe_
[0] != -1);
842 g_assert(resize_pipe_
[1] != -1);
844 res
= close(resize_pipe_
[0]);
846 LOG
->error(_("Closing the self-pipe for screen resizing failed."));
847 resize_pipe_
[0] = -1;
849 res
= close(resize_pipe_
[1]);
851 LOG
->error(_("Closing the self-pipe for screen resizing failed."));
852 resize_pipe_
[1] = -1;
855 void CenterIM::onScreenResized()
857 CppConsUI::Rect size
;
859 int screen_width
= CppConsUI::Curses::getWidth();
860 int screen_height
= CppConsUI::Curses::getHeight();
864 if (convs_expanded_
) {
870 purple_prefs_get_int(CONF_PREFIX
"/dimensions/buddylist_width");
871 buddylist_width
= CLAMP(buddylist_width
, 0, 50);
872 log_height
= purple_prefs_get_int(CONF_PREFIX
"/dimensions/log_height");
873 log_height
= CLAMP(log_height
, 0, 50);
877 purple_prefs_get_bool(CONF_PREFIX
"/dimensions/show_header");
884 purple_prefs_get_bool(CONF_PREFIX
"/dimensions/show_footer");
892 size
.y
= header_height
;
893 size
.width
= screen_width
/ 100.0 * buddylist_width
;
894 size
.height
= screen_height
- header_height
- footer_height
;
895 areas_
[BUDDY_LIST_AREA
] = size
;
897 size
.x
= areas_
[BUDDY_LIST_AREA
].width
;
898 size
.width
= screen_width
- size
.x
;
899 size
.height
= screen_height
/ 100.0 * log_height
;
900 size
.y
= screen_height
- size
.height
- footer_height
;
901 areas_
[LOG_AREA
] = size
;
903 size
.x
= areas_
[BUDDY_LIST_AREA
].width
;
904 size
.y
= header_height
;
905 size
.width
= screen_width
- size
.x
;
907 screen_height
- size
.y
- areas_
[LOG_AREA
].height
- footer_height
;
908 if (convs_expanded_
) {
912 areas_
[CHAT_AREA
] = size
;
916 size
.width
= screen_width
;
917 size
.height
= header_height
;
918 areas_
[HEADER_AREA
] = size
;
921 size
.y
= screen_height
- 1;
922 size
.width
= screen_width
;
923 size
.height
= footer_height
;
924 areas_
[FOOTER_AREA
] = size
;
928 size
.width
= screen_width
;
929 size
.height
= screen_height
;
930 areas_
[WHOLE_AREA
] = size
;
933 void CenterIM::onTopWindowChanged()
935 if (!convs_expanded_
)
938 CppConsUI::Window
*top
= mngr_
->getTopWindow();
939 if (top
!= nullptr && typeid(Conversation
) != typeid(*top
)) {
940 convs_expanded_
= false;
941 CONVERSATIONS
->setExpandedConversations(convs_expanded_
);
942 mngr_
->onScreenResized();
946 guint
CenterIM::input_add_purple(int fd
, PurpleInputCondition condition
,
947 PurpleInputFunction function
, gpointer data
)
949 auto closure
= new IOClosurePurple
;
953 closure
->function
= function
;
954 closure
->data
= data
;
956 if (condition
& PURPLE_INPUT_READ
)
957 cond
|= GLIB_IO_READ_COND
;
958 if (condition
& PURPLE_INPUT_WRITE
)
959 cond
|= GLIB_IO_WRITE_COND
;
961 channel
= g_io_channel_unix_new(fd
);
962 closure
->result
= g_io_add_watch_full(channel
, G_PRIORITY_DEFAULT
,
963 static_cast<GIOCondition
>(cond
), io_input_purple
, closure
,
966 g_io_channel_unref(channel
);
967 return closure
->result
;
970 gboolean
CenterIM::io_input_purple(
971 GIOChannel
*source
, GIOCondition condition
, gpointer data
)
973 IOClosurePurple
*closure
= static_cast<IOClosurePurple
*>(data
);
976 if (condition
& G_IO_IN
)
977 purple_cond
|= PURPLE_INPUT_READ
;
978 if (condition
& G_IO_OUT
)
979 purple_cond
|= PURPLE_INPUT_WRITE
;
981 closure
->function(closure
->data
, g_io_channel_unix_get_fd(source
),
982 static_cast<PurpleInputCondition
>(purple_cond
));
987 void CenterIM::io_destroy_purple(gpointer data
)
989 delete static_cast<IOClosurePurple
*>(data
);
992 gboolean
CenterIM::stdin_bytes_available()
994 // Disconnect any timeout handler.
995 if (stdin_timeout_id_
!= 0) {
996 g_source_remove(stdin_timeout_id_
);
997 stdin_timeout_id_
= 0;
1001 CppConsUI::Error error
;
1002 if (mngr_
->processStandardInput(&wait
, error
) != 0)
1003 LOG
->error("%s", error
.getString());
1006 // Connect timeout handler.
1007 stdin_timeout_id_
= g_timeout_add_full(
1008 G_PRIORITY_DEFAULT
, wait
, stdin_timeout_
, this, nullptr);
1014 gboolean
CenterIM::stdin_timeout()
1016 stdin_timeout_id_
= 0;
1018 CppConsUI::Error error
;
1019 if (mngr_
->processStandardInputTimeout(error
) != 0)
1020 LOG
->error("%s", error
.getString());
1025 gboolean
CenterIM::resize_bytes_available()
1027 // An obvious thing here would be to read a single character because
1028 // sigwinch_handler() never writes more than one character into the pipe.
1029 // (Additional writes are protected by setting the resize_pending_ flag.)
1030 // However, it is possible that SIGWINCH was received after a fork() call but
1031 // before the child exited or exec'ed. In this case, both the parent and the
1032 // child will receive the signal and write in the pipe. The code should
1033 // attempt to read out all characters from the pipe so the resizing is not
1034 // unnecessarily done multiple times.
1036 int res
= read(resize_pipe_
[0], buf
, sizeof(buf
));
1039 // The following assertion should generally hold. However, in a very unlikely
1040 // case when the pipe contains more than one character and the read above gets
1041 // interrupted and does not receive all data it will fail when
1042 // resize_bytes_available() is called again because of the remaining
1044 // g_assert(resize_pending_);
1046 resize_pending_
= false;
1048 CppConsUI::Error error
;
1049 if (mngr_
->resize(error
) != 0) {
1050 LOG
->error("%s", error
.getString());
1052 // Exit the program.
1053 mainloop_error_exit_
= true;
1054 g_main_loop_quit(mainloop_
);
1059 gboolean
CenterIM::draw()
1061 CppConsUI::Error error
;
1062 if (mngr_
->draw(error
) != 0) {
1063 LOG
->error("%s", error
.getString());
1065 // Exit the program.
1066 mainloop_error_exit_
= true;
1067 g_main_loop_quit(mainloop_
);
1072 void CenterIM::sigwinch_handler(int signum
)
1074 g_assert(signum
== SIGWINCH
);
1076 if (resize_pending_
)
1079 int saved_errno
= errno
;
1080 int res
= write(resize_pipe_
[1], "@", 1);
1081 errno
= saved_errno
;
1083 resize_pending_
= true;
1087 // Cannot reasonably recover from this error. This should be absolutely rare.
1088 write(STDERR_FILENO
, sigwinch_write_error_
, sigwinch_write_error_size_
);
1092 void CenterIM::redraw_cppconsui()
1094 g_timeout_add_full(G_PRIORITY_DEFAULT
, 0, draw_
, this, nullptr);
1097 void CenterIM::log_debug_cppconsui(const char *message
)
1099 LOG
->debug("%s", message
);
1102 GHashTable
*CenterIM::get_ui_info()
1104 static GHashTable
*ui_info
= nullptr;
1106 if (ui_info
== nullptr) {
1107 ui_info
= g_hash_table_new(g_str_hash
, g_str_equal
);
1109 // Note: the C-style casts are used below because otherwise we would need to
1110 // use const_cast and reinterpret_cast together (which is too much typing).
1111 g_hash_table_insert(ui_info
, (void *)"name", (void *)PACKAGE_NAME
);
1112 g_hash_table_insert(ui_info
, (void *)"version", (void *)version_
);
1113 g_hash_table_insert(ui_info
, (void *)"website", (void *)PACKAGE_URL
);
1115 g_hash_table_insert(
1116 ui_info
, (void *)"dev_website", (void *)PACKAGE_BUGREPORT
);
1117 g_hash_table_insert(ui_info
, (void *)"client_type", (void *)"pc");
1123 void CenterIM::purple_print(
1124 PurpleDebugLevel level
, const char *category
, const char *arg_s
)
1126 LOG
->purple_print(level
, category
, arg_s
);
1129 gboolean
CenterIM::purple_is_enabled(
1130 PurpleDebugLevel level
, const char *category
)
1132 return LOG
->purple_is_enabled(level
, category
);
1135 void CenterIM::dimensions_change(
1136 const char * /*name*/, PurplePrefType
/*type*/, gconstpointer
/*val*/)
1138 mngr_
->onScreenResized();
1141 void CenterIM::idle_reporting_change(
1142 const char * /*name*/, PurplePrefType type
, gconstpointer val
)
1144 g_return_if_fail(type
== PURPLE_PREF_STRING
);
1146 const char *value
= static_cast<const char *>(val
);
1147 if (std::strcmp(value
, "system") == 0)
1148 idle_reporting_on_keyboard_
= true;
1150 idle_reporting_on_keyboard_
= false;
1153 void CenterIM::loadDefaultColorSchemeConfig()
1155 COLORSCHEME
->clear();
1157 // Inititialize default color schemes.
1158 COLORSCHEME
->setAttributes(SCHEME_ACCOUNTSTATUSMENU
,
1159 CppConsUI::ColorScheme::PROPERTY_PANEL_LINE
, CppConsUI::Curses::Color::CYAN
,
1160 CppConsUI::Curses::Color::DEFAULT
);
1161 COLORSCHEME
->setAttributes(SCHEME_ACCOUNTSTATUSMENU
,
1162 CppConsUI::ColorScheme::PROPERTY_HORIZONTALLINE_LINE
,
1163 CppConsUI::Curses::Color::CYAN
, CppConsUI::Curses::Color::DEFAULT
);
1164 COLORSCHEME
->setAttributes(SCHEME_ACCOUNTSTATUSMENU
,
1165 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1166 CppConsUI::Curses::Color::CYAN
, CppConsUI::Curses::Color::DEFAULT
);
1168 COLORSCHEME
->setAttributes(SCHEME_BUDDYLIST
,
1169 CppConsUI::ColorScheme::PROPERTY_TREEVIEW_LINE
,
1170 CppConsUI::Curses::Color::GREEN
, CppConsUI::Curses::Color::DEFAULT
);
1171 COLORSCHEME
->setAttributes(SCHEME_BUDDYLIST
,
1172 CppConsUI::ColorScheme::PROPERTY_PANEL_LINE
, CppConsUI::Curses::Color::BLUE
,
1173 CppConsUI::Curses::Color::DEFAULT
, CppConsUI::Curses::Attr::BOLD
);
1174 COLORSCHEME
->setAttributes(SCHEME_BUDDYLIST
,
1175 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1176 CppConsUI::Curses::Color::GREEN
, CppConsUI::Curses::Color::DEFAULT
);
1177 COLORSCHEME
->setAttributes(SCHEME_BUDDYLISTGROUP
,
1178 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1179 CppConsUI::Curses::Color::MAGENTA
, CppConsUI::Curses::Color::DEFAULT
);
1181 COLORSCHEME
->setAttributes(SCHEME_BUDDYLISTBUDDY_AWAY
,
1182 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1183 CppConsUI::Curses::Color::BLUE
, CppConsUI::Curses::Color::DEFAULT
);
1184 COLORSCHEME
->setAttributes(SCHEME_BUDDYLISTBUDDY_NA
,
1185 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1186 CppConsUI::Curses::Color::MAGENTA
, CppConsUI::Curses::Color::DEFAULT
);
1187 COLORSCHEME
->setAttributes(SCHEME_BUDDYLISTBUDDY_OFFLINE
,
1188 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1189 CppConsUI::Curses::Color::RED
, CppConsUI::Curses::Color::DEFAULT
);
1190 COLORSCHEME
->setAttributes(SCHEME_BUDDYLISTBUDDY_ONLINE
,
1191 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1192 CppConsUI::Curses::Color::GREEN
, CppConsUI::Curses::Color::DEFAULT
);
1194 COLORSCHEME
->setAttributes(SCHEME_BUDDYLISTCONTACT_AWAY
,
1195 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1196 CppConsUI::Curses::Color::BLUE
, CppConsUI::Curses::Color::DEFAULT
);
1197 COLORSCHEME
->setAttributes(SCHEME_BUDDYLISTCONTACT_NA
,
1198 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1199 CppConsUI::Curses::Color::MAGENTA
, CppConsUI::Curses::Color::DEFAULT
);
1200 COLORSCHEME
->setAttributes(SCHEME_BUDDYLISTCONTACT_OFFLINE
,
1201 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1202 CppConsUI::Curses::Color::RED
, CppConsUI::Curses::Color::DEFAULT
);
1203 COLORSCHEME
->setAttributes(SCHEME_BUDDYLISTCONTACT_ONLINE
,
1204 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1205 CppConsUI::Curses::Color::GREEN
, CppConsUI::Curses::Color::DEFAULT
);
1207 COLORSCHEME
->setAttributes(SCHEME_CONVERSATION
,
1208 CppConsUI::ColorScheme::PROPERTY_TEXTVIEW_TEXT
,
1209 CppConsUI::Curses::Color::MAGENTA
, CppConsUI::Curses::Color::DEFAULT
);
1210 COLORSCHEME
->setAttributesExt(SCHEME_CONVERSATION
,
1211 CppConsUI::ColorScheme::PROPERTY_TEXTVIEW_TEXT
, 1,
1212 CppConsUI::Curses::Color::CYAN
, CppConsUI::Curses::Color::DEFAULT
);
1213 COLORSCHEME
->setAttributesExt(SCHEME_CONVERSATION
,
1214 CppConsUI::ColorScheme::PROPERTY_TEXTVIEW_TEXT
, 2,
1215 CppConsUI::Curses::Color::MAGENTA
, CppConsUI::Curses::Color::DEFAULT
);
1216 COLORSCHEME
->setAttributes(SCHEME_CONVERSATION
,
1217 CppConsUI::ColorScheme::PROPERTY_PANEL_LINE
, CppConsUI::Curses::Color::BLUE
,
1218 CppConsUI::Curses::Color::DEFAULT
, CppConsUI::Curses::Attr::BOLD
);
1219 COLORSCHEME
->setAttributes(SCHEME_CONVERSATION
,
1220 CppConsUI::ColorScheme::PROPERTY_HORIZONTALLINE_LINE
,
1221 CppConsUI::Curses::Color::BLUE
, CppConsUI::Curses::Color::DEFAULT
,
1222 CppConsUI::Curses::Attr::BOLD
);
1223 COLORSCHEME
->setAttributes(SCHEME_CONVERSATION
,
1224 CppConsUI::ColorScheme::PROPERTY_VERTICALLINE_LINE
,
1225 CppConsUI::Curses::Color::BLUE
, CppConsUI::Curses::Color::DEFAULT
,
1226 CppConsUI::Curses::Attr::BOLD
);
1227 COLORSCHEME
->setAttributes(SCHEME_CONVERSATION
,
1228 CppConsUI::ColorScheme::PROPERTY_TEXTEDIT_TEXT
,
1229 CppConsUI::Curses::Color::CYAN
, CppConsUI::Curses::Color::DEFAULT
);
1231 COLORSCHEME
->setAttributes(SCHEME_CONVERSATION
,
1232 CppConsUI::ColorScheme::PROPERTY_LABEL_TEXT
, CppConsUI::Curses::Color::CYAN
,
1233 CppConsUI::Curses::Color::DEFAULT
);
1234 COLORSCHEME
->setAttributes(SCHEME_CONVERSATION_ACTIVE
,
1235 CppConsUI::ColorScheme::PROPERTY_LABEL_TEXT
,
1236 CppConsUI::Curses::Color::MAGENTA
, CppConsUI::Curses::Color::DEFAULT
);
1237 COLORSCHEME
->setAttributes(SCHEME_CONVERSATION_NEW
,
1238 CppConsUI::ColorScheme::PROPERTY_LABEL_TEXT
, CppConsUI::Curses::Color::CYAN
,
1239 CppConsUI::Curses::Color::DEFAULT
, CppConsUI::Curses::Attr::BOLD
);
1241 COLORSCHEME
->setAttributes(SCHEME_FOOTER
,
1242 CppConsUI::ColorScheme::PROPERTY_LABEL_TEXT
,
1243 CppConsUI::Curses::Color::BLACK
, CppConsUI::Curses::Color::WHITE
);
1244 COLORSCHEME
->setAttributes(SCHEME_FOOTER
,
1245 CppConsUI::ColorScheme::PROPERTY_CONTAINER_BACKGROUND
,
1246 CppConsUI::Curses::Color::BLACK
, CppConsUI::Curses::Color::WHITE
);
1248 COLORSCHEME
->setAttributes(SCHEME_GENERALMENU
,
1249 CppConsUI::ColorScheme::PROPERTY_PANEL_LINE
, CppConsUI::Curses::Color::CYAN
,
1250 CppConsUI::Curses::Color::DEFAULT
);
1251 COLORSCHEME
->setAttributes(SCHEME_GENERALMENU
,
1252 CppConsUI::ColorScheme::PROPERTY_HORIZONTALLINE_LINE
,
1253 CppConsUI::Curses::Color::CYAN
, CppConsUI::Curses::Color::DEFAULT
);
1254 COLORSCHEME
->setAttributes(SCHEME_GENERALMENU
,
1255 CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
,
1256 CppConsUI::Curses::Color::CYAN
, CppConsUI::Curses::Color::DEFAULT
);
1258 COLORSCHEME
->setAttributes(SCHEME_GENERALWINDOW
,
1259 CppConsUI::ColorScheme::PROPERTY_PANEL_LINE
, CppConsUI::Curses::Color::CYAN
,
1260 CppConsUI::Curses::Color::DEFAULT
);
1261 COLORSCHEME
->setAttributes(SCHEME_GENERALWINDOW
,
1262 CppConsUI::ColorScheme::PROPERTY_HORIZONTALLINE_LINE
,
1263 CppConsUI::Curses::Color::CYAN
, CppConsUI::Curses::Color::DEFAULT
);
1264 COLORSCHEME
->setAttributes(SCHEME_GENERALWINDOW
,
1265 CppConsUI::ColorScheme::PROPERTY_VERTICALLINE_LINE
,
1266 CppConsUI::Curses::Color::CYAN
, CppConsUI::Curses::Color::DEFAULT
);
1268 COLORSCHEME
->setAttributes(SCHEME_LOG
,
1269 CppConsUI::ColorScheme::PROPERTY_PANEL_LINE
, CppConsUI::Curses::Color::BLUE
,
1270 CppConsUI::Curses::Color::DEFAULT
, CppConsUI::Curses::Attr::BOLD
);
1271 COLORSCHEME
->setAttributes(SCHEME_LOG
,
1272 CppConsUI::ColorScheme::PROPERTY_TEXTVIEW_TEXT
,
1273 CppConsUI::Curses::Color::CYAN
, CppConsUI::Curses::Color::DEFAULT
);
1275 COLORSCHEME
->setAttributes(SCHEME_HEADER
,
1276 CppConsUI::ColorScheme::PROPERTY_LABEL_TEXT
,
1277 CppConsUI::Curses::Color::BLACK
, CppConsUI::Curses::Color::WHITE
);
1278 COLORSCHEME
->setAttributes(SCHEME_HEADER
,
1279 CppConsUI::ColorScheme::PROPERTY_CONTAINER_BACKGROUND
,
1280 CppConsUI::Curses::Color::BLACK
, CppConsUI::Curses::Color::WHITE
);
1281 COLORSCHEME
->setAttributes(SCHEME_HEADER_REQUEST
,
1282 CppConsUI::ColorScheme::PROPERTY_LABEL_TEXT
, CppConsUI::Curses::Color::RED
,
1283 CppConsUI::Curses::Color::WHITE
);
1286 bool CenterIM::saveColorSchemeConfig()
1288 xmlnode
*root
= xmlnode_new("colorscheme");
1289 xmlnode_set_attrib(root
, "version", "1.0");
1291 for (CppConsUI::ColorScheme::Schemes::const_iterator si
=
1292 COLORSCHEME
->getSchemes().begin();
1293 si
!= COLORSCHEME
->getSchemes().end(); ++si
) {
1294 xmlnode
*scheme_node
= xmlnode_new("scheme");
1295 xmlnode_set_attrib(scheme_node
, "name", schemeToString(si
->first
));
1296 xmlnode_insert_child(root
, scheme_node
);
1298 for (CppConsUI::ColorScheme::Properties::const_iterator pi
=
1300 pi
!= si
->second
.end(); ++pi
) {
1301 xmlnode
*color_node
= xmlnode_new("color");
1302 xmlnode_insert_child(scheme_node
, color_node
);
1304 CppConsUI::ColorScheme::PropertyPair pair
= pi
->first
;
1305 CppConsUI::ColorScheme::Color color
= pi
->second
;
1307 const char *widget_string
= COLORSCHEME
->propertyToWidgetName(pair
.first
);
1308 assert(widget_string
!= nullptr);
1309 xmlnode_set_attrib(color_node
, "widget", widget_string
);
1313 const char *property_string
=
1314 COLORSCHEME
->propertyToPropertyName(pair
.first
);
1315 assert(property_string
!= nullptr);
1316 if (pair
.second
!= 0) {
1317 str
= g_strdup_printf("%s_%d", property_string
, pair
.second
);
1318 xmlnode_set_attrib(color_node
, "property", str
);
1322 xmlnode_set_attrib(color_node
, "property", property_string
);
1324 if (color
.foreground
!= CppConsUI::Curses::Color::DEFAULT
) {
1325 str
= colorToString(color
.foreground
);
1326 xmlnode_set_attrib(color_node
, "foreground", str
);
1330 if (color
.background
!= CppConsUI::Curses::Color::DEFAULT
) {
1331 str
= colorToString(color
.background
);
1332 xmlnode_set_attrib(color_node
, "background", str
);
1336 str
= colorAttributesToString(color
.attrs
);
1337 if (str
!= nullptr) {
1338 xmlnode_set_attrib(color_node
, "attributes", str
);
1344 char *data
= xmlnode_to_formatted_str(root
, nullptr);
1346 if (!purple_util_write_data_to_file("colorschemes.xml", data
, -1)) {
1347 LOG
->error(_("Error saving 'colorschemes.xml'."));
1355 const char *CenterIM::schemeToString(int scheme
)
1357 static_assert(G_N_ELEMENTS(scheme_names_
) == SCHEME_END
,
1358 "Incorrect number of elements in array scheme_names_");
1359 assert(scheme
>= 0 && scheme
< SCHEME_END
);
1360 return scheme_names_
[scheme
];
1363 int CenterIM::stringToScheme(const char *str
)
1365 for (int i
= SCHEME_BEGIN
; i
< SCHEME_END
; ++i
)
1366 if (std::strcmp(str
, scheme_names_
[i
]) == 0)
1371 char *CenterIM::colorToString(int color
)
1373 if (color
>= -1 && color
< static_cast<int>(G_N_ELEMENTS(color_names_
) - 1))
1374 return g_strdup(color_names_
[color
+ 1]);
1375 return g_strdup_printf("%d", color
);
1378 bool CenterIM::stringToColor(const char *str
, int *color
)
1380 g_assert(str
!= nullptr);
1381 g_assert(color
!= nullptr);
1385 if (g_ascii_isdigit(str
[0]) || str
[0] == '-') {
1388 long i
= std::strtol(str
, &endptr
, 10);
1389 if (*endptr
!= '\0' || errno
== ERANGE
|| i
< -1 || i
> INT_MAX
)
1396 for (int i
= -1; i
< static_cast<int>(G_N_ELEMENTS(color_names_
) - 1); ++i
)
1397 if (!strcmp(str
, color_names_
[i
+ 1])) {
1405 char *CenterIM::colorAttributesToString(int attrs
)
1407 #define APPEND(str) \
1416 if (attrs
== CppConsUI::Curses::Attr::NORMAL
)
1419 if (attrs
& CppConsUI::Curses::Attr::STANDOUT
)
1421 if (attrs
& CppConsUI::Curses::Attr::REVERSE
)
1423 if (attrs
& CppConsUI::Curses::Attr::BLINK
)
1425 if (attrs
& CppConsUI::Curses::Attr::DIM
)
1427 if (attrs
& CppConsUI::Curses::Attr::BOLD
)
1430 return g_strdup(s
.c_str());
1434 bool CenterIM::stringToColorAttributes(const char *str
, int *attrs
)
1436 g_assert(str
!= nullptr);
1437 g_assert(attrs
!= nullptr);
1439 gchar
**tokens
= g_strsplit(str
, "|", 0);
1443 for (std::size_t i
= 0; tokens
[i
] != nullptr; ++i
) {
1444 if (std::strcmp("normal", tokens
[i
]) == 0) {
1445 *attrs
|= CppConsUI::Curses::Attr::NORMAL
;
1448 if (std::strcmp("standout", tokens
[i
]) == 0) {
1449 *attrs
|= CppConsUI::Curses::Attr::STANDOUT
;
1452 if (std::strcmp("reverse", tokens
[i
]) == 0) {
1453 *attrs
|= CppConsUI::Curses::Attr::REVERSE
;
1456 if (std::strcmp("blink", tokens
[i
]) == 0) {
1457 *attrs
|= CppConsUI::Curses::Attr::BLINK
;
1460 if (std::strcmp("dim", tokens
[i
]) == 0) {
1461 *attrs
|= CppConsUI::Curses::Attr::DIM
;
1464 if (std::strcmp("bold", tokens
[i
]) == 0) {
1465 *attrs
|= CppConsUI::Curses::Attr::BOLD
;
1468 // Unrecognized attribute.
1478 void CenterIM::loadDefaultKeyConfig()
1480 // Clear current bindings and load default ones.
1481 KEYCONFIG
->loadDefaultKeyConfig();
1483 KEYCONFIG
->bindKey("centerim", "quit", "Ctrl-q");
1484 KEYCONFIG
->bindKey("centerim", "buddylist", "F1");
1485 KEYCONFIG
->bindKey("centerim", "conversation-active", "F2");
1486 KEYCONFIG
->bindKey("centerim", "accountstatusmenu", "F3");
1487 KEYCONFIG
->bindKey("centerim", "generalmenu", "F4");
1488 KEYCONFIG
->bindKey("centerim", "generalmenu", "Ctrl-g");
1489 KEYCONFIG
->bindKey("centerim", "buddylist-toggle-offline", "F5");
1490 KEYCONFIG
->bindKey("centerim", "conversation-expand", "F6");
1492 KEYCONFIG
->bindKey("centerim", "conversation-prev", "Ctrl-p");
1493 KEYCONFIG
->bindKey("centerim", "conversation-next", "Ctrl-n");
1494 KEYCONFIG
->bindKey("centerim", "conversation-number1", "Alt-1");
1495 KEYCONFIG
->bindKey("centerim", "conversation-number2", "Alt-2");
1496 KEYCONFIG
->bindKey("centerim", "conversation-number3", "Alt-3");
1497 KEYCONFIG
->bindKey("centerim", "conversation-number4", "Alt-4");
1498 KEYCONFIG
->bindKey("centerim", "conversation-number5", "Alt-5");
1499 KEYCONFIG
->bindKey("centerim", "conversation-number6", "Alt-6");
1500 KEYCONFIG
->bindKey("centerim", "conversation-number7", "Alt-7");
1501 KEYCONFIG
->bindKey("centerim", "conversation-number8", "Alt-8");
1502 KEYCONFIG
->bindKey("centerim", "conversation-number9", "Alt-9");
1503 KEYCONFIG
->bindKey("centerim", "conversation-number10", "Alt-0");
1504 KEYCONFIG
->bindKey("centerim", "conversation-number11", "Alt-q");
1505 KEYCONFIG
->bindKey("centerim", "conversation-number12", "Alt-w");
1506 KEYCONFIG
->bindKey("centerim", "conversation-number13", "Alt-e");
1507 KEYCONFIG
->bindKey("centerim", "conversation-number14", "Alt-r");
1508 KEYCONFIG
->bindKey("centerim", "conversation-number15", "Alt-t");
1509 KEYCONFIG
->bindKey("centerim", "conversation-number16", "Alt-y");
1510 KEYCONFIG
->bindKey("centerim", "conversation-number17", "Alt-u");
1511 KEYCONFIG
->bindKey("centerim", "conversation-number18", "Alt-i");
1512 KEYCONFIG
->bindKey("centerim", "conversation-number19", "Alt-o");
1513 KEYCONFIG
->bindKey("centerim", "conversation-number20", "Alt-p");
1515 KEYCONFIG
->bindKey("buddylist", "contextmenu", "Ctrl-d");
1516 KEYCONFIG
->bindKey("buddylist", "filter", "/");
1518 KEYCONFIG
->bindKey("conversation", "send", "Ctrl-x");
1521 bool CenterIM::saveKeyConfig()
1523 xmlnode
*root
= xmlnode_new("keyconfig");
1524 xmlnode_set_attrib(root
, "version", "1.0");
1526 const CppConsUI::KeyConfig::KeyBinds
*binds
= KEYCONFIG
->getKeyBinds();
1527 for (CppConsUI::KeyConfig::KeyBinds::const_iterator bi
= binds
->begin();
1528 bi
!= binds
->end(); ++bi
) {
1529 // Invert the map because the output should be sorted by context+action, not
1531 typedef std::multimap
<std::string
, TermKeyKey
> InvertedMap
;
1532 InvertedMap inverted
;
1533 for (CppConsUI::KeyConfig::KeyBindContext::const_iterator ci
=
1535 ci
!= bi
->second
.end(); ++ci
)
1536 inverted
.insert(std::make_pair(ci
->second
, ci
->first
));
1538 for (InvertedMap::iterator ci
= inverted
.begin(); ci
!= inverted
.end();
1540 xmlnode
*bind_node
= xmlnode_new("bind");
1541 xmlnode_set_attrib(bind_node
, "context", bi
->first
.c_str());
1542 xmlnode_set_attrib(bind_node
, "action", ci
->first
.c_str());
1543 char *key
= KEYCONFIG
->termKeyToString(ci
->second
);
1544 if (key
!= nullptr) {
1545 xmlnode_set_attrib(bind_node
, "key", key
);
1549 xmlnode_insert_child(root
, bind_node
);
1553 char *data
= xmlnode_to_formatted_str(root
, nullptr);
1555 if (!purple_util_write_data_to_file("binds.xml", data
, -1)) {
1556 LOG
->error(_("Error saving 'binds.xml'."));
1564 void CenterIM::actionFocusBuddyList()
1569 void CenterIM::actionFocusActiveConversation()
1571 CONVERSATIONS
->focusActiveConversation();
1574 void CenterIM::actionOpenAccountStatusMenu()
1576 // Do not allow to open the account status menu if there is any 'top' window
1577 // (except general menu, we can close that).
1578 CppConsUI::Window
*top
= mngr_
->getTopWindow();
1579 if (top
!= nullptr) {
1580 if (dynamic_cast<GeneralMenu
*>(top
))
1582 else if (top
->getType() == CppConsUI::Window::TYPE_TOP
)
1586 auto menu
= new AccountStatusMenu
;
1590 void CenterIM::actionOpenGeneralMenu()
1592 // Do not allow to open the general menu if there is any 'top' window (except
1593 // account status menu, we can close that).
1594 CppConsUI::Window
*top
= mngr_
->getTopWindow();
1595 if (top
!= nullptr) {
1596 if (dynamic_cast<AccountStatusMenu
*>(top
))
1598 else if (top
->getType() == CppConsUI::Window::TYPE_TOP
)
1602 auto menu
= new GeneralMenu
;
1606 void CenterIM::actionBuddyListToggleOffline()
1609 purple_prefs_get_bool(CONF_PREFIX
"/blist/show_offline_buddies");
1610 purple_prefs_set_bool(CONF_PREFIX
"/blist/show_offline_buddies", !cur
);
1613 void CenterIM::actionFocusPrevConversation()
1615 CONVERSATIONS
->focusPrevConversation();
1618 void CenterIM::actionFocusNextConversation()
1620 CONVERSATIONS
->focusNextConversation();
1623 void CenterIM::actionFocusConversation(int i
)
1625 CONVERSATIONS
->focusConversation(i
);
1628 void CenterIM::actionExpandConversation()
1630 CppConsUI::Window
*top
= mngr_
->getTopWindow();
1631 if (top
!= nullptr && top
->getType() == CppConsUI::Window::TYPE_TOP
)
1634 if (!convs_expanded_
) {
1635 CONVERSATIONS
->focusActiveConversation();
1636 top
= mngr_
->getTopWindow();
1637 if (top
== nullptr || typeid(Conversation
) != typeid(*top
))
1641 convs_expanded_
= !convs_expanded_
;
1642 CONVERSATIONS
->setExpandedConversations(convs_expanded_
);
1643 mngr_
->onScreenResized();
1646 void CenterIM::declareBindables()
1648 declareBindable("centerim", "quit", sigc::mem_fun(this, &CenterIM::quit
),
1649 InputProcessor::BINDABLE_OVERRIDE
);
1650 declareBindable("centerim", "buddylist",
1651 sigc::mem_fun(this, &CenterIM::actionFocusBuddyList
),
1652 InputProcessor::BINDABLE_OVERRIDE
);
1653 declareBindable("centerim", "conversation-active",
1654 sigc::mem_fun(this, &CenterIM::actionFocusActiveConversation
),
1655 InputProcessor::BINDABLE_OVERRIDE
);
1656 declareBindable("centerim", "accountstatusmenu",
1657 sigc::mem_fun(this, &CenterIM::actionOpenAccountStatusMenu
),
1658 InputProcessor::BINDABLE_OVERRIDE
);
1659 declareBindable("centerim", "generalmenu",
1660 sigc::mem_fun(this, &CenterIM::actionOpenGeneralMenu
),
1661 InputProcessor::BINDABLE_OVERRIDE
);
1662 declareBindable("centerim", "buddylist-toggle-offline",
1663 sigc::mem_fun(this, &CenterIM::actionBuddyListToggleOffline
),
1664 InputProcessor::BINDABLE_OVERRIDE
);
1665 declareBindable("centerim", "conversation-prev",
1666 sigc::mem_fun(this, &CenterIM::actionFocusPrevConversation
),
1667 InputProcessor::BINDABLE_OVERRIDE
);
1668 declareBindable("centerim", "conversation-next",
1669 sigc::mem_fun(this, &CenterIM::actionFocusNextConversation
),
1670 InputProcessor::BINDABLE_OVERRIDE
);
1671 char action
[] = "conversation-numberXX";
1672 for (int i
= 1; i
<= 20; ++i
) {
1673 g_sprintf(action
+ sizeof(action
) - 3, "%d", i
);
1674 declareBindable("centerim", action
,
1675 sigc::bind(sigc::mem_fun(this, &CenterIM::actionFocusConversation
), i
),
1676 InputProcessor::BINDABLE_OVERRIDE
);
1678 declareBindable("centerim", "conversation-expand",
1679 sigc::mem_fun(this, &CenterIM::actionExpandConversation
),
1680 InputProcessor::BINDABLE_OVERRIDE
);
1683 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: