Use pkg-config to find ncursesw
[centerim5.git] / src / CenterIM.cpp
blob9f69d607d88d92ee1e1aff1eb39482e501bc9923
1 // Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
2 // Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
3 //
4 // This file is part of CenterIM.
5 //
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/>.
19 #include "CenterIM.h"
21 #include "Accounts.h"
22 #include "BuddyList.h"
23 #include "Connections.h"
24 #include "Conversations.h"
25 #include "Footer.h"
26 #include "Header.h"
27 #include "Log.h"
28 #include "Notify.h"
29 #include "Request.h"
30 #include "Transfers.h"
32 #include "AccountStatusMenu.h"
33 #include "GeneralMenu.h"
35 #include "gettext.h"
36 #include <cerrno>
37 #include <cppconsui/ColorScheme.h>
38 #include <cppconsui/KeyConfig.h>
39 #include <cstdio>
40 #include <cstring>
41 #include <fcntl.h>
42 #include <getopt.h>
43 #include <glib/gprintf.h>
44 #include <locale.h>
45 #include <time.h>
46 #include <typeinfo>
47 #include <unistd.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_[] = {
61 "default", // -1
62 "black", // 0
63 "red", // 1
64 "green", // 2
65 "yellow", // 3
66 "blue", // 4
67 "magenta", // 5
68 "cyan", // 6
69 "white", // 7
72 const char *CenterIM::scheme_names_[] = {
73 nullptr,
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
96 "log", // SCHEME_LOG
99 CenterIM *CenterIM::my_instance_ = nullptr;
101 // Based on glibmm code.
102 class SourceConnectionNode {
103 public:
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();
113 private:
114 sigc::slot_base slot;
115 GSource *source;
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.
130 if (self->source) {
131 GSource *s = self->source;
132 self->source = nullptr;
133 g_source_destroy(s);
135 // Destroying the object triggers execution of destroy_notify_handler(),
136 // either immediately or later, so we leave that to do the deletion.
139 return nullptr;
142 void SourceConnectionNode::destroy_notify_callback(void *data)
144 SourceConnectionNode *self = reinterpret_cast<SourceConnectionNode *>(data);
146 if (self) {
147 // The GLib side is disconnected now, thus the GSource* is no longer valid.
148 self->source = nullptr;
150 delete self;
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)
165 source = nsource;
168 inline sigc::slot_base *SourceConnectionNode::get_slot()
170 return &slot;
173 CenterIM *CenterIM::instance()
175 return my_instance_;
178 bool CenterIM::processInput(const TermKeyKey &key)
180 if (idle_reporting_on_keyboard_)
181 purple_idle_touch();
182 return InputProcessor::processInput(key);
185 void CenterIM::quit()
187 g_main_loop_quit(mainloop_);
190 CppConsUI::Rect CenterIM::getScreenArea(ScreenArea area)
192 return areas_[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()
206 xmlnode *root =
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())
213 return true;
214 return false;
217 COLORSCHEME->clear();
218 bool res = false;
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."));
226 goto out;
229 int scheme = stringToScheme(scheme_name);
230 if (scheme == 0) {
231 LOG->error(_("Unrecognized scheme '%s'."), scheme_name);
232 goto out;
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."));
240 goto out;
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."));
246 goto out;
249 CppConsUI::ColorScheme::PropertyConversionResult conv_res;
250 int property;
251 int subproperty;
252 conv_res = COLORSCHEME->stringPairToPropertyPair(
253 widget_string, property_string, &property, &subproperty);
254 switch (conv_res) {
255 case CppConsUI::ColorScheme::CONVERSION_SUCCESS:
256 break;
257 case CppConsUI::ColorScheme::CONVERSION_ERROR_WIDGET:
258 LOG->error(_("Unrecognized widget '%s'."), widget_string);
259 goto out;
260 case CppConsUI::ColorScheme::CONVERSION_ERROR_PROPERTY:
261 LOG->error(_("Unrecognized property '%s'."), property_string);
262 goto out;
263 default:
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;
273 int attrs = 0;
275 if (fg_string != nullptr && !stringToColor(fg_string, &fg)) {
276 LOG->error(_("Unrecognized color '%s'."), fg_string);
277 goto out;
280 if (bg_string != nullptr && !stringToColor(bg_string, &bg)) {
281 LOG->error(_("Unrecognized color '%s'."), bg_string);
282 goto out;
285 if (attrs_string != nullptr &&
286 !stringToColorAttributes(attrs_string, &attrs)) {
287 LOG->error(_("Unrecognized attributes '%s'."), attrs_string);
288 goto out;
291 COLORSCHEME->setAttributesExt(
292 scheme, property, subproperty, fg, bg, attrs);
296 res = true;
298 out:
299 if (!res) {
300 LOG->error(_("Error parsing 'colorschemes.xml', "
301 "loading default color scheme."));
302 loadDefaultColorSchemeConfig();
305 xmlnode_free(root);
307 return res;
310 bool CenterIM::loadKeyConfig()
312 xmlnode *root =
313 purple_util_read_xml_from_file("binds.xml", _("key bindings"));
315 if (root == nullptr) {
316 // Read error, first time run?
317 loadDefaultKeyConfig();
318 if (saveKeyConfig())
319 return true;
320 return false;
323 KEYCONFIG->clear();
324 bool res = false;
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."));
331 goto out;
333 const char *action = xmlnode_get_attrib(bind_node, "action");
334 if (action == nullptr) {
335 LOG->error(_("Missing 'action' attribute in the bind definition."));
336 goto out;
338 const char *key = xmlnode_get_attrib(bind_node, "key");
339 if (key == nullptr) {
340 LOG->error(_("Missing 'key' attribute in the bind definition."));
341 goto out;
344 if (!KEYCONFIG->bindKey(context, action, key)) {
345 LOG->error(_("Unrecognized key '%s'."), key);
346 goto out;
350 res = true;
352 out:
353 if (!res) {
354 LOG->error(_("Error parsing 'binds.xml', loading default keys."));
355 loadDefaultKeyConfig();
358 xmlnode_free(root);
360 return res;
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);
381 return connection;
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);
390 CenterIM::CenterIM()
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(&centerim_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(&centerim_glib_eventloops_, 0, sizeof(centerim_glib_eventloops_));
404 int CenterIM::run(int argc, char *argv[])
406 // Init CenterIM.
407 g_assert(my_instance_ == nullptr);
408 my_instance_ = new CenterIM;
410 // Run CenterIM.
411 int res = my_instance_->runAll(argc, argv);
413 // Finalize CenterIM.
414 g_assert(my_instance_ != nullptr);
416 delete my_instance_;
417 my_instance_ = nullptr;
419 return res;
422 int CenterIM::runAll(int argc, char *argv[])
424 int res = 1;
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, "");
439 tzset();
441 #if ENABLE_NLS
442 bindtextdomain(PACKAGE_NAME, LOCALEDIR);
443 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8");
444 bind_textdomain_codeset("pidgin", "UTF-8");
445 textdomain(PACKAGE_NAME);
446 #endif
448 signal(SIGPIPE, SIG_IGN);
450 // Parse command-line arguments.
451 bool ascii = false;
452 bool offline = false;
453 const char *config_path = CIM_CONFIG_PATH;
454 int opt;
455 // clang-format off
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 }
464 // clang-format on
466 while (
467 (opt = getopt_long(argc, argv, "ahvb:o", long_options, nullptr)) != -1) {
468 switch (opt) {
469 case 'a':
470 ascii = true;
471 break;
472 case 'h':
473 printUsage(stdout, argv[0]);
474 return 0;
475 case 'v':
476 printVersion(stdout);
477 return 0;
478 case 'b':
479 config_path = optarg;
480 break;
481 case 'o':
482 offline = true;
483 break;
484 default:
485 printUsage(stderr, argv[0]);
486 return 1;
490 if (optind < argc) {
491 std::fprintf(stderr, _("%s: unexpected argument after options\n"), argv[0]);
492 printUsage(stderr, argv[0]);
493 return 1;
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.
500 Log::init();
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());
518 goto out;
520 cppconsui_input_initialized = true;
521 if (mngr_->initializeOutput(error) != 0) {
522 LOG->error("%s", error.getString());
523 goto out;
525 cppconsui_output_initialized = true;
527 // ASCII mode.
528 if (ascii)
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.
538 declareBindables();
540 // Set up screen resizing.
541 if (initializeScreenResizing() != 0) {
542 LOG->error(_("Screen resizing initialization failed."));
543 goto out;
545 screen_resizing_initialized = true;
547 // Initialize libpurple.
548 if (initializePurple(config_path) != 0) {
549 LOG->error(_("Libpurple initialization failed."));
550 goto out;
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();
563 loadKeyConfig();
565 Footer::init();
567 Accounts::init();
568 Connections::init();
569 Notify::init();
570 Request::init();
572 // Initialize UI.
573 Conversations::init();
574 Header::init();
575 // Init BuddyList last so it takes the focus.
576 BuddyList::init();
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_,
592 this, nullptr);
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();
631 Header::finalize();
632 BuddyList::finalize();
634 Accounts::finalize();
635 Connections::finalize();
636 Notify::finalize();
637 Request::finalize();
639 Footer::finalize();
641 LOG->finalizeNormalPhase();
643 if (!mainloop_error_exit_) {
644 // Everything went ok.
645 res = 0;
648 out:
649 // Finalize libpurple.
650 if (purple_initialized)
651 finalizePurple();
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.
672 Log::finalize();
674 return res;
677 void CenterIM::printUsage(FILE *out, const char *prg_name)
679 // clang-format off
680 std::fprintf(out, _(
681 "Usage: %s [option]...\n\n"
682 "Options:\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"),
688 prg_name);
689 // clang-format on
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)) {
703 // Absolute path.
704 purple_util_set_user_dir(config_path);
706 else {
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);
710 g_free(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_);
722 // Set core uiops.
723 centerim_core_ui_ops_.get_ui_info = get_ui_info;
724 purple_core_set_ui_ops(&centerim_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(&centerim_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);
736 g_free(path);
738 // Add a search path for centerim-specific plugins.
739 purple_plugins_add_search_path(PKGLIBDIR);
741 if (!purple_core_init(PACKAGE_NAME))
742 return 1;
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);
751 return 0;
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);
760 purple_core_quit();
763 void CenterIM::initializePreferences()
765 // Remove someday...
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
781 // inside libpurple.
782 purple_prefs_trigger_callback("/purple/away/idle_reporting");
785 int CenterIM::initializeScreenResizing()
787 int res;
789 // Get error message reported from the SIGWINCH handler when a write to the
790 // self-pipe fails.
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_);
800 if (res != 0) {
801 LOG->error(_("Creating a self-pipe for screen resizing failed."));
802 return 1;
805 // Set close-on-exec on both descriptors.
806 res = fcntl(resize_pipe_[0], F_GETFD);
807 g_assert(res != -1);
808 res = fcntl(resize_pipe_[0], F_SETFD, res | FD_CLOEXEC);
809 g_assert(res == 0);
810 res = fcntl(resize_pipe_[1], F_GETFD);
811 g_assert(res != -1);
812 res = fcntl(resize_pipe_[1], F_SETFD, res | FD_CLOEXEC);
813 g_assert(res == 0);
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);
820 g_assert(res == 0);
821 res = sigaction(SIGWINCH, &sig, nullptr);
822 g_assert(res == 0);
824 return 0;
827 void CenterIM::finalizeScreenResizing()
829 int res;
831 // Unregister the SIGWINCH handler.
832 struct sigaction sig;
833 sig.sa_handler = SIG_DFL;
834 sig.sa_flags = 0;
835 res = sigemptyset(&sig.sa_mask);
836 g_assert(res == 0);
837 res = sigaction(SIGWINCH, &sig, nullptr);
838 g_assert(res == 0);
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]);
845 if (res != 0)
846 LOG->error(_("Closing the self-pipe for screen resizing failed."));
847 resize_pipe_[0] = -1;
849 res = close(resize_pipe_[1]);
850 if (res != 0)
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();
862 int buddylist_width;
863 int log_height;
864 if (convs_expanded_) {
865 buddylist_width = 0;
866 log_height = 0;
868 else {
869 buddylist_width =
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);
876 bool show_header =
877 purple_prefs_get_bool(CONF_PREFIX "/dimensions/show_header");
878 int header_height;
879 if (show_header)
880 header_height = 1;
881 else
882 header_height = 0;
883 bool show_footer =
884 purple_prefs_get_bool(CONF_PREFIX "/dimensions/show_footer");
885 int footer_height;
886 if (show_footer)
887 footer_height = 1;
888 else
889 footer_height = 0;
891 size.x = 0;
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;
906 size.height =
907 screen_height - size.y - areas_[LOG_AREA].height - footer_height;
908 if (convs_expanded_) {
909 size.x -= 1;
910 size.width += 2;
912 areas_[CHAT_AREA] = size;
914 size.x = 0;
915 size.y = 0;
916 size.width = screen_width;
917 size.height = header_height;
918 areas_[HEADER_AREA] = size;
920 size.x = 0;
921 size.y = screen_height - 1;
922 size.width = screen_width;
923 size.height = footer_height;
924 areas_[FOOTER_AREA] = size;
926 size.x = 0;
927 size.y = 0;
928 size.width = screen_width;
929 size.height = screen_height;
930 areas_[WHOLE_AREA] = size;
933 void CenterIM::onTopWindowChanged()
935 if (!convs_expanded_)
936 return;
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;
950 GIOChannel *channel;
951 int cond = 0;
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,
964 io_destroy_purple);
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);
974 int purple_cond = 0;
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));
984 return TRUE;
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;
1000 int wait;
1001 CppConsUI::Error error;
1002 if (mngr_->processStandardInput(&wait, error) != 0)
1003 LOG->error("%s", error.getString());
1005 if (wait >= 0) {
1006 // Connect timeout handler.
1007 stdin_timeout_id_ = g_timeout_add_full(
1008 G_PRIORITY_DEFAULT, wait, stdin_timeout_, this, nullptr);
1011 return TRUE;
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());
1022 return FALSE;
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.
1035 char buf[1024];
1036 int res = read(resize_pipe_[0], buf, sizeof(buf));
1037 g_assert(res > 0);
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
1043 // characters.
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_);
1056 return TRUE;
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_);
1069 return FALSE;
1072 void CenterIM::sigwinch_handler(int signum)
1074 g_assert(signum == SIGWINCH);
1076 if (resize_pending_)
1077 return;
1079 int saved_errno = errno;
1080 int res = write(resize_pipe_[1], "@", 1);
1081 errno = saved_errno;
1082 if (res == 1) {
1083 resize_pending_ = true;
1084 return;
1087 // Cannot reasonably recover from this error. This should be absolutely rare.
1088 write(STDERR_FILENO, sigwinch_write_error_, sigwinch_write_error_size_);
1089 _exit(13);
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");
1120 return ui_info;
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;
1149 else
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 =
1299 si->second.begin();
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);
1311 char *str;
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);
1319 g_free(str);
1321 else
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);
1327 g_free(str);
1330 if (color.background != CppConsUI::Curses::Color::DEFAULT) {
1331 str = colorToString(color.background);
1332 xmlnode_set_attrib(color_node, "background", str);
1333 g_free(str);
1336 str = colorAttributesToString(color.attrs);
1337 if (str != nullptr) {
1338 xmlnode_set_attrib(color_node, "attributes", str);
1339 g_free(str);
1344 char *data = xmlnode_to_formatted_str(root, nullptr);
1345 bool res = true;
1346 if (!purple_util_write_data_to_file("colorschemes.xml", data, -1)) {
1347 LOG->error(_("Error saving 'colorschemes.xml'."));
1348 res = false;
1350 g_free(data);
1351 xmlnode_free(root);
1352 return res;
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)
1367 return i;
1368 return 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);
1383 *color = 0;
1385 if (g_ascii_isdigit(str[0]) || str[0] == '-') {
1386 // Numeric colors.
1387 char *endptr;
1388 long i = std::strtol(str, &endptr, 10);
1389 if (*endptr != '\0' || errno == ERANGE || i < -1 || i > INT_MAX)
1390 return false;
1391 *color = i;
1392 return true;
1395 // Symbolic colors.
1396 for (int i = -1; i < static_cast<int>(G_N_ELEMENTS(color_names_) - 1); ++i)
1397 if (!strcmp(str, color_names_[i + 1])) {
1398 *color = i;
1399 return true;
1402 return false;
1405 char *CenterIM::colorAttributesToString(int attrs)
1407 #define APPEND(str) \
1408 do { \
1409 if (s.size()) \
1410 s.append("|"); \
1411 s.append(str); \
1412 } while (0)
1414 std::string s;
1416 if (attrs == CppConsUI::Curses::Attr::NORMAL)
1417 return nullptr;
1419 if (attrs & CppConsUI::Curses::Attr::STANDOUT)
1420 APPEND("standout");
1421 if (attrs & CppConsUI::Curses::Attr::REVERSE)
1422 APPEND("reverse");
1423 if (attrs & CppConsUI::Curses::Attr::BLINK)
1424 APPEND("blink");
1425 if (attrs & CppConsUI::Curses::Attr::DIM)
1426 APPEND("dim");
1427 if (attrs & CppConsUI::Curses::Attr::BOLD)
1428 APPEND("bold");
1430 return g_strdup(s.c_str());
1431 #undef APPEND
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);
1440 *attrs = 0;
1442 bool valid = true;
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;
1446 continue;
1448 if (std::strcmp("standout", tokens[i]) == 0) {
1449 *attrs |= CppConsUI::Curses::Attr::STANDOUT;
1450 continue;
1452 if (std::strcmp("reverse", tokens[i]) == 0) {
1453 *attrs |= CppConsUI::Curses::Attr::REVERSE;
1454 continue;
1456 if (std::strcmp("blink", tokens[i]) == 0) {
1457 *attrs |= CppConsUI::Curses::Attr::BLINK;
1458 continue;
1460 if (std::strcmp("dim", tokens[i]) == 0) {
1461 *attrs |= CppConsUI::Curses::Attr::DIM;
1462 continue;
1464 if (std::strcmp("bold", tokens[i]) == 0) {
1465 *attrs |= CppConsUI::Curses::Attr::BOLD;
1466 continue;
1468 // Unrecognized attribute.
1469 valid = false;
1470 break;
1473 g_strfreev(tokens);
1475 return valid;
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
1530 // by context+key.
1531 typedef std::multimap<std::string, TermKeyKey> InvertedMap;
1532 InvertedMap inverted;
1533 for (CppConsUI::KeyConfig::KeyBindContext::const_iterator ci =
1534 bi->second.begin();
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();
1539 ++ci) {
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);
1546 delete[] key;
1549 xmlnode_insert_child(root, bind_node);
1553 char *data = xmlnode_to_formatted_str(root, nullptr);
1554 bool res = true;
1555 if (!purple_util_write_data_to_file("binds.xml", data, -1)) {
1556 LOG->error(_("Error saving 'binds.xml'."));
1557 res = false;
1559 g_free(data);
1560 xmlnode_free(root);
1561 return res;
1564 void CenterIM::actionFocusBuddyList()
1566 BUDDYLIST->show();
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))
1581 top->close();
1582 else if (top->getType() == CppConsUI::Window::TYPE_TOP)
1583 return;
1586 auto menu = new AccountStatusMenu;
1587 menu->show();
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))
1597 top->close();
1598 else if (top->getType() == CppConsUI::Window::TYPE_TOP)
1599 return;
1602 auto menu = new GeneralMenu;
1603 menu->show();
1606 void CenterIM::actionBuddyListToggleOffline()
1608 gboolean cur =
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)
1632 return;
1634 if (!convs_expanded_) {
1635 CONVERSATIONS->focusActiveConversation();
1636 top = mngr_->getTopWindow();
1637 if (top == nullptr || typeid(Conversation) != typeid(*top))
1638 return;
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: