diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..ef516ad --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: -ferror-limit=0 diff --git a/.dir-locals.el b/.dir-locals.el index 3b8086c..dfda52f 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -11,5 +11,6 @@ " "))) (flycheck-clang-warnings . ("all" "extra" "error-implicit-function-declaration" "no-missing-field-initializers")) (flycheck-clang-language-standard . "c++17") - (flycheck-checker . c/c++-clang) + (flycheck-gcc-language-standard . "c++17") + (flycheck-checker . c/c++-gcc) (projectile-project-compilation-cmd . "make && (make test || true)"))) diff --git a/.envrc b/.envrc index 11db241..7da69a7 100644 --- a/.envrc +++ b/.envrc @@ -30,11 +30,15 @@ PACKAGES=( rnp # Dep (rnpgp) ) +echo direnv: fetching source - weechat +mkdir -p /tmp/guix-build-weechat-3.2.drv-0 +tar -C /tmp/guix-build-weechat-3.2.drv-0 -xJf $(guix build --source weechat) + use guix \ ${ENVIRONMENTS[@]} --ad-hoc ${PACKAGES[@]} \ - --with-debug-info=weechat\ - --with-debug-info=libstrophe\ - --with-debug-info=libsignal-protocol-c\ - --with-debug-info=lmdb\ - --with-debug-info=rnp\ - clang:extra gdb + --with-debug-info=weechat \ + --with-debug-info=libstrophe \ + --with-debug-info=libsignal-protocol-c \ + --with-debug-info=lmdb \ + --with-debug-info=rnp \ + clang clang:extra ccls gdb diff --git a/account.cpp b/account.cpp new file mode 100644 index 0000000..553a29e --- /dev/null +++ b/account.cpp @@ -0,0 +1,445 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, version 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// #include +// #include +// #include +// #include +// #include +#include + +#include "account.hh" +#include "weechat.hh" +#include "strophe.hh" +#include "strophe.ipp" +#include "config.hh" + +// #include "plugin.h" +// #include "input.h" +// #include "omemo.h" +// #include "connection.h" +// #include "user.h" +// #include "channel.h" +// #include "buffer.h" +#include "stubs.cpp" + +std::map weechat::xmpp::globals::accounts; + +std::map weechat::xmpp::account::m_default_options; +std::map weechat::xmpp::account::m_option_defaults { + { "jid", {"string", "XMPP Account JID", "", ""} }, + { "password", {"string", "XMPP Account Password", "", ""} }, + { "tls", {"integer", "XMPP Server TLS Policy", "normal", "disable|normal|trust"} }, + { "nickname", {"string", "XMPP Account Nickname", "", ""} }, + { "autoconnect", {"boolean", "Autoconnect XMPP Account", "", ""} }, + { "resource", {"string", "XMPP Account Resource", "", ""} }, + { "status", {"string", "XMPP Account Login Status", "probably about to segfault", ""} }, + { "pgp_pubring_path", {"string", "XMPP Account PGP Public Keyring Path", + "${weechat_data_dir}/pubring.gpg", ""} }, + { "pgp_secring_path", {"string", "XMPP Account PGP Secure Keyring Path", + "${weechat_data_dir}/secring.gpg", ""} }, + { "pgp_keyid", {"string", "XMPP Account PGP Key ID", "", ""} }, +}; + +template xmpp::context::context(weechat::xmpp::account&); + +template<> +void xmpp::logger::emit_weechat( + weechat::xmpp::account& account, const level level, + std::string_view area, std::string_view msg) +{ + using logger = xmpp::logger; + + const char *tags = level > logger::debug ? "no_log" : nullptr; + + std::string_view xml; + if (std::size_t xmlpos = msg.find('<'); (level == logger::debug) && + (xmlpos != msg.npos)) + { + xml = msg.substr(xmlpos); + + auto nullfd = std::unique_ptr{ + std::fopen("/dev/null", "w+"), + std::fclose + }; + xml::set_error_context(&*nullfd); + + std::string_view header = msg.substr(0, xmlpos); + xml::document doc(xml); + if (!doc) { + weechat::printf(account.buffer, + "xml: Error parsing the xml document"); + return; + } + const char *colour = weechat::color("blue"); + if (auto root = doc.root()) + { + std::string tag = root->name(); + if (tag == "message") + { + colour = weechat::color("green"); + } + else if (tag == "presence") + { + colour = weechat::color("yellow"); + } + else if (tag == "iq") + { + colour = weechat::color("red"); + } + } + std::string formatted = doc.format(); + if (formatted.empty()) { + weechat::printf(account.buffer, + "xml: Error formatting the xml document"); + return; + } + int size = 0; + auto lines = std::unique_ptr{ + weechat::string_split(formatted.data(), "\r\n", nullptr, 0, 0, &size), + weechat::string_free_split + }; + if (lines[size-1][0] == 0) + lines[--size] = 0; + weechat::printf_date_tags(account.buffer, 0, tags, + weechat::gettext("%s%s (%s): %s"), + weechat::prefix("network"), area, + level.name(), header); + for (int i = 1; i < size; i++) + weechat::printf_date_tags(account.buffer, 0, tags, + weechat::gettext("%s%s"), + colour, lines[i]); + } + else + { + weechat::printf_date_tags(account.buffer, 0, tags, + weechat::gettext("%s%s (%s): %s"), + weechat::prefix("network"), area, + level.name(), msg); + } +} + +weechat::xmpp::account::account(std::string name) + : context(*this) + , connection(this->context) + , buffer(name, {}, {}) +{ + this->name = name; + + this->ready = false; + this->active = false; + + this->current_retry = 0; + this->reconnect_delay = 0; + this->reconnect_start = 0; + + this->omemo = nullptr; + this->pgp = nullptr; + + for (auto it = this->m_option_defaults.begin(); it != this->m_option_defaults.end(); it++) + { + std::string option_name = this->name + '.' + it->first + + " << xmpp.account_default." + it->first; + + auto [option, success] = this->m_options.try_emplace(it->first, + weechat::globals::plugin.config().file(), + weechat::globals::plugin.config().section_account(), + option_name, it->second.type, + weechat::gettext(it->second.description.data()), + it->second.range, 0, 0, it->second.value, it->second.value, false, + std::function([this](weechat::config_option&, std::string){ return true; }), + std::function([this](weechat::config_option&){ }), + std::function([this](weechat::config_option&){ })); + if (!success) + throw weechat::error("duplicate option key"); + //option.change_cb(it->first, nullptr, this->m_options[it->first]); + } +} + +bool weechat::xmpp::account::connected() +{ + return xmpp::xmpp_conn_is_connected(this->connection); +} + +void weechat::xmpp::account::disconnect(bool reconnect) +{ + if (this->connected()) + { + /* + * remove all nicks and write disconnection message on each + * channel/private buffer + */ + c::user__free_all(*this); + weechat::nicklist_remove_all(this->buffer); + for (auto& [name, channel] : this->channels) + { + weechat::nicklist_remove_all(channel->buffer); + weechat::printf(channel->buffer, + weechat::gettext("%s%s: disconnected from account"), + weechat::prefix("network"), weechat::globals::plugin.name()); + } + /* remove away status on account buffer */ + //weechat::buffer_set(this->buffer, "localvar_del_away", ""); + } + + this->close_connection(); + + if (this->buffer) + { + weechat::printf(this->buffer, + weechat::gettext("%s%s: disconnected from account"), + weechat::prefix("network"), weechat::globals::plugin.name()); + } + + if (reconnect) + { + if (this->current_retry++ == 0) + { + this->reconnect_delay = 5; + this->reconnect_start = time(nullptr) + this->reconnect_delay; + } + this->current_retry %= 5; + } + else + { + this->current_retry = 0; + this->reconnect_delay = 0; + this->reconnect_start = 0; + } + + this->active = reconnect; + + /* send signal "account_disconnected" with account name */ + weechat::hook_signal_send("xmpp_account_disconnected", + WEECHAT_HOOK_SIGNAL_STRING, this->name.data()); +} + +weechat::gui_buffer weechat::xmpp::account::create_buffer() +{ + std::string name = "account." + this->name; + this->buffer = weechat::gui_buffer(name.data(), + weechat::gui_buffer::input_callback(), + weechat::gui_buffer::close_callback()); + if (!this->buffer) + throw weechat::error("failed to create account buffer"); + + if (!weechat::buffer_get_integer(this->buffer, "short_name_is_set")) + weechat::buffer_set(this->buffer, "short_name", this->name.data()); + weechat::buffer_set(this->buffer, "localvar_set_type", "server"); + weechat::buffer_set(this->buffer, "localvar_set_server", this->name.data()); + weechat::buffer_set(this->buffer, "localvar_set_channel", this->name.data()); + std::string charset_modifier = "account." + this->name; + weechat::buffer_set(this->buffer, "localvar_set_charset_modifier", + charset_modifier.data()); + weechat::buffer_set(this->buffer, "title", this->name.data()); + + weechat::buffer_set(this->buffer, "nicklist", "1"); + weechat::buffer_set(this->buffer, "nicklist_display_groups", "0"); + weechat::buffer_set_pointer(this->buffer, "nicklist_callback", + reinterpret_cast(static_cast( + [](const void *pointer, void*, + struct t_gui_buffer *buffer, + const char *nick1, const char *nick2) { + auto& account = *reinterpret_cast< + const weechat::xmpp::account*>(pointer); + (void) account; + c::buffer__nickcmp_cb((c::t_gui_buffer*)buffer, + nick1, nick2); + }))); + weechat::buffer_set_pointer(this->buffer, "nicklist_callback_pointer", + this); + + return this->buffer; +} + +void weechat::xmpp::account::close_connection() +{ + if (this->connection) + { + if (xmpp::xmpp_conn_is_connected(this->connection)) + xmpp::xmpp_disconnect(this->connection); + } +} + +bool weechat::xmpp::account::connect() +{ + if (!this->buffer) + { + if (!this->create_buffer()) + return false; + weechat::buffer_set(this->buffer, "display", "auto"); + } + + this->close_connection(); + + c::connection__connect(*this, this->connection, this->jid(), + this->password(), this->tls()); + + weechat::hook_signal_send("xmpp_account_connecting", + WEECHAT_HOOK_SIGNAL_STRING, this->name.data()); + + return true; +} + +bool weechat::xmpp::account::timer_cb(int) +{ + for (auto& [name, account] : weechat::xmpp::globals::accounts) + { + if (xmpp_conn_is_connecting(account.connection) + || xmpp_conn_is_connected(account.connection)) + c::connection__process(account.context, account.connection, 10); + else if (account.active && account.reconnect_start > 0 + && account.reconnect_start < time(nullptr)) + { + account.connect(); + } + } + + return true; +} + +void weechat::xmpp::account::disconnect_all() +{ + for (auto it = weechat::xmpp::globals::accounts.begin(); + it != weechat::xmpp::globals::accounts.end(); it++) + { + it->second.disconnect(false); + } +} + +std::pair::iterator, bool> +weechat::xmpp::account::create(std::string name) +{ + return weechat::xmpp::globals::accounts.try_emplace(name, name); +} + +void weechat::xmpp::account::init_defaults(config_file& config_file, config_section& section) +{ + for (auto& [name, option_data] : weechat::xmpp::account::m_option_defaults) + { + weechat::xmpp::account::m_default_options.try_emplace(name, + weechat::config_option( + config_file, section, name, + option_data.type, option_data.description, option_data.range, + 0, 0, option_data.value, option_data.value, true, {}, {}, {})).first->second; + } +} + +bool weechat::xmpp::account::reload(config_file&) +{ + for (auto& [_, account] : weechat::xmpp::globals::accounts) + account.ready = false; + + if (!weechat::config_reload(weechat::globals::plugin.config().file())) + return false; + + for (auto& [_, account] : weechat::xmpp::globals::accounts) + { + account.ready = true; + + std::string ac_global = weechat::info_get("auto_connect", nullptr); + bool ac_local = account.autoconnect(); + if (ac_local && ac_global == "1") + account.connect(); + } + + return true; +} + +int weechat::xmpp::account::read_cb(config_file& config_file, config_section& section, + std::string option_name, std::string value) +{ + int rc = WEECHAT_CONFIG_OPTION_SET_ERROR; + + if (!option_name.empty()) + { + if (std::size_t pos_option = option_name.find('.'); + pos_option != option_name.npos) + { + std::string account_name = option_name.substr(0, pos_option); + std::string option_id = option_name.substr(++pos_option); + auto data_it = weechat::xmpp::account::m_option_defaults.find(option_id); + if (data_it == weechat::xmpp::account::m_option_defaults.end()) + { + rc = WEECHAT_CONFIG_OPTION_SET_OPTION_NOT_FOUND; + return rc; + } + auto& option_data = data_it->second; + + if (account_name == "account_default") + + { + auto& option = + weechat::xmpp::account::m_default_options.try_emplace(option_id, + weechat::config_option( + config_file, section, option_id, + option_data.type, option_data.description, option_data.range, + 0, 0, option_data.value, option_data.value, true, {}, {}, {})).first->second; + + rc = weechat::config_option_set(option, value.data(), true); + return rc; + } + + const auto& item = weechat::xmpp::account::create(account_name).first; + weechat::xmpp::account& account = item->second; + + auto& option = account.m_options.try_emplace(option_id, + weechat::config_option( + config_file, section, option_id, + option_data.type, option_data.description, option_data.range, + 0, 0, option_data.value, option_data.value, true, {}, {}, {})).first->second; + + rc = weechat::config_option_set(option, value.data(), true); + } + } + + if (rc == WEECHAT_CONFIG_OPTION_SET_ERROR) + { + weechat::printf(nullptr, + weechat::gettext("%s%s: error creating account option \"%s\""), + weechat::prefix("error"), weechat::globals::plugin.name().data(), + option_name); + } + + return rc; +} + +int weechat::xmpp::account::write_cb(config_file& config_file, std::string section_name) +{ + if (!weechat::config_write_line(config_file, section_name.data(), nullptr)) + return WEECHAT_CONFIG_WRITE_ERROR; + + for (auto& [_, account] : weechat::xmpp::globals::accounts) + { + for (auto& [name, option] : account.m_options) + { + if (!weechat::config_write_option(config_file, option)) + return WEECHAT_CONFIG_WRITE_ERROR; + } + } + + return WEECHAT_CONFIG_WRITE_OK; +} + +void weechat::xmpp::account::change_cb(config_option& option) +{ + std::string name = option.string("name"); + std::string value = option.string("value"); + + int split_num; + char **split = weechat::string_split(name.data(), ".", nullptr, 0, 2, &split_num); + auto it = weechat::xmpp::globals::accounts.find(split[0]); + if (split_num >= 2 && it != weechat::xmpp::globals::accounts.end()) + { + std::string key = split[1]; + + (void) key; + (void) value; + } + + weechat::string_free_split(split); +} diff --git a/account.hh b/account.hh new file mode 100644 index 0000000..0e54201 --- /dev/null +++ b/account.hh @@ -0,0 +1,147 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, version 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "plugin.hh" +#include "strophe.hh" +#include "config.hh" + +namespace weechat::xmpp { + namespace xmpp = ::xmpp; + + class account { + public: + explicit account(std::string name); + + struct option_data { + std::string type; + std::string description; + std::string value; + std::string range; + }; + + struct device + { + int id; + std::string name; + }; + + struct mam_query + { + std::string id; + std::string with; + std::optional start; + std::optional end; + }; + + std::string name; + + bool ready; + bool active; + + int current_retry; + int reconnect_delay; + std::time_t reconnect_start; + + xmpp::context context; + xmpp::connection connection; + + weechat::gui_buffer buffer; + + struct t_omemo *omemo; + struct t_pgp *pgp; + + std::map devices; + std::map mam_queries; + std::map users; + std::map channels; + + inline std::string jid() { + return this->connection && xmpp_conn_is_connected(this->connection) + ? xmpp_jid_bare(this->context, + xmpp_conn_get_bound_jid(this->connection)) + : weechat::config_string(this->m_options.at("jid")); + } + inline std::string jid_device() { + return this->connection && xmpp_conn_is_connected(this->connection) + ? xmpp_conn_get_bound_jid(this->connection) + : xmpp_jid_new(this->context, + xmpp_jid_node( + this->context, + weechat::config_string( + this->m_options.at("jid"))), + xmpp_jid_domain( + this->context, + weechat::config_string( + this->m_options.at("jid"))), + "weechat"); + } + inline weechat::config_option password() { + return weechat::config_option(this->m_options.at("password")); + } + inline weechat::config_option tls() { + return weechat::config_option(this->m_options.at("tls")); + } + inline weechat::config_option nickname() { + return weechat::config_option(this->m_options.at("nickname")); + } + inline weechat::config_option autoconnect() { + return weechat::config_option(this->m_options.at("autoconnect")); + } + inline weechat::config_option resource() { + return weechat::config_option(this->m_options.at("resource")); + } + inline weechat::config_option status() { + return weechat::config_option(this->m_options.at("status")); + } + inline weechat::config_option pgp_pubring_path() { + return weechat::config_option(this->m_options.at("pgp_pubring_path")); + } + inline weechat::config_option pgp_secring_path() { + return weechat::config_option(this->m_options.at("pgp_secring_path")); + } + inline weechat::config_option pgp_keyid() { + return weechat::config_option(this->m_options.at("pgp_keyid")); + } + + bool connected(); + void disconnect(bool reconnect); + weechat::gui_buffer create_buffer(); + void close_connection(); + bool connect(); + bool timer_cb(int remaining_calls); + + static void disconnect_all(); + + static std::pair::iterator, bool> create(std::string name); + + static void init_defaults(config_file& config_file, config_section& section); + static bool reload(config_file& config_file); + static int read_cb(config_file& config_file, config_section& section, + std::string option_name, std::string value); + static int write_cb(config_file& config_file, std::string section_name); + static void change_cb(config_option& option); + + private: + std::map m_options; + + static std::map m_default_options; + static std::map m_option_defaults; + + friend class weechat::xmpp::config; + }; + + namespace globals { + extern std::map accounts; + } +} diff --git a/config.cpp b/config.cpp new file mode 100644 index 0000000..8efa07f --- /dev/null +++ b/config.cpp @@ -0,0 +1,43 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, version 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "config.hh" +#include "plugin.hh" +#include "account.hh" + +weechat::xmpp::config::config(std::string name) + : m_name(name) + , m_file(weechat::config_file(name, std::function( + [](weechat::config_file& file) { + return weechat::xmpp::account::reload(file); + }))) + , m_section_look(weechat::config_section( + this->m_file, "look", false, false, {}, {}, {}, {}, {})) + , m_section_account_default(weechat::config_section( + this->m_file, "account_default", false, false, {}, {}, {}, {}, {})) + , m_section_account(weechat::config_section( + this->m_file, "account", false, false, + &weechat::xmpp::account::read_cb, + &weechat::xmpp::account::write_cb, + {}, {}, {})) + , m_look_nick_completion_smart(weechat::config_option( + this->m_file, this->m_section_look, + "nick_completion_smart", "integer", + weechat::gettext("smart completion for nicks (completes first with last speakers): " + "speakers = all speakers (including highlights), " + "speakers_highlights = only speakers with highlight"), + "off|speakers|speakers_highlights", 0, 0, "speakers", "", false, + {}, {}, {})) { + weechat::xmpp::account::init_defaults(this->m_file, this->m_section_account_default); +} + +bool weechat::xmpp::config::read() +{ + return weechat::config_read(this->m_file) == weechat::ok; +} + +bool weechat::xmpp::config::write() +{ + return weechat::config_write(this->m_file) == weechat::ok; +} diff --git a/config.hh b/config.hh new file mode 100644 index 0000000..91c695e --- /dev/null +++ b/config.hh @@ -0,0 +1,69 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, version 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "weechat.hh" +#include "strophe.hh" + +namespace weechat::xmpp { + namespace xmpp = ::xmpp; + + class config { + public: + inline config() : config(config::default_name) {} + explicit config(std::string name); + + enum class nick_completion + { + SMART_OFF = 0, + SMART_SPEAKERS, + SMART_SPEAKERS_HIGHLIGHTS, + }; + + struct option_data { + std::string type; + std::string description; + std::string value; + std::string range; + }; + + bool read(); + bool write(); + + inline std::string& name() { return this->m_name; } + + inline weechat::config_file& file() { return this->m_file; } + inline weechat::config_section& section_account() { return this->m_section_account; } + inline weechat::config_section& section_account_default() { return this->m_section_account_default; } + + inline nick_completion look_nick_completion_smart() { + int value = this->m_look_nick_completion_smart; + return static_cast(value);; + } + + static inline const std::string default_name = "xmpp"; + + private: + std::string m_name; + + weechat::config_file m_file; + + weechat::config_section m_section_look; + weechat::config_section m_section_account_default; + weechat::config_section m_section_account; + + weechat::config_option m_look_nick_completion_smart; + + std::map m_account_default; + }; +} diff --git a/deps/weechat.scm b/deps/weechat.scm new file mode 100644 index 0000000..8bc8206 --- /dev/null +++ b/deps/weechat.scm @@ -0,0 +1,84 @@ +(define-module (weechat) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix download) + #:use-module (guix git-download) + #:use-module (guix utils) + #:use-module (guix packages) + #:use-module (guix utils) + #:use-module (guix build-system cmake) + #:use-module (guix build-system gnu) + #:use-module (guix build-system meson) + #:use-module (guix build-system python) + #:use-module (guix build-system qt) + #:use-module (gnu packages) + #:use-module (gnu packages admin) + #:use-module (gnu packages aspell) + #:use-module (gnu packages autogen) + #:use-module (gnu packages autotools) + #:use-module (gnu packages base) + #:use-module (gnu packages backup) + #:use-module (gnu packages check) + #:use-module (gnu packages compression) + #:use-module (gnu packages curl) + #:use-module (gnu packages cyrus-sasl) + #:use-module (gnu packages databases) + #:use-module (gnu packages file) + #:use-module (gnu packages gettext) + #:use-module (gnu packages geo) + #:use-module (gnu packages glib) + #:use-module (gnu packages gnome) + #:use-module (gnu packages gnupg) + #:use-module (gnu packages gtk) + #:use-module (gnu packages guile) + #:use-module (gnu packages irc) + #:use-module (gnu packages lua) + #:use-module (gnu packages lxqt) + #:use-module (gnu packages ncurses) + #:use-module (gnu packages openldap) + #:use-module (gnu packages kde) + #:use-module (gnu packages kde-frameworks) + #:use-module (gnu packages password-utils) + #:use-module (gnu packages pcre) + #:use-module (gnu packages perl) + #:use-module (gnu packages pkg-config) + #:use-module (gnu packages python) + #:use-module (gnu packages python-crypto) + #:use-module (gnu packages python-xyz) + #:use-module (gnu packages regex) + #:use-module (gnu packages ruby) + #:use-module (gnu packages sphinx) + #:use-module (gnu packages sqlite) + #:use-module (gnu packages qt) + #:use-module (gnu packages tcl) + #:use-module (gnu packages textutils) + #:use-module (gnu packages time) + #:use-module (gnu packages tls) + #:use-module (gnu packages web) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26)) + +(define-public weechat-dbg + (package + (inherit weechat) + (outputs '("out" "debug")) + (arguments + (substitute-keyword-arguments (package-arguments weechat) + ((#:configure-flags configure-flags) + `(cons* + "-DCMAKE_BUILD_TYPE=DEBUG" + "-DCMAKE_C_FLAGS_DEBUG=\"-O0\"" + "-DCMAKE_CXX_FLAGS_DEBUG=\"-O0\"" + ,configure-flags)) + ((#:phases phases) + `(modify-phases ,phases + (add-before 'configure 'setenv + (lambda* (#:key outputs #:allow-other-keys) + (let* ((out (assoc-ref outputs "out")) + (gcc (assoc-ref inputs "gcc-toolchain")) + (cppflags (string-append " -gdwarf-4 " (or (getenv "CXXFLAGS") ""))) + (cflags (string-append " -gdwarf-4 " (or (getenv "CFLAGS") "")))) + (setenv "CXX" (string-append gcc "/bin/gcc " cppflags)) + (setenv "CC" (string-append gcc "/bin/gcc " cflags)) + #t))))))))) + +weechat-dbg diff --git a/Makefile b/makefile similarity index 78% rename from Makefile rename to makefile index 34ccede..9ad8223 100644 --- a/Makefile +++ b/makefile @@ -13,7 +13,7 @@ INCLUDES=-Ilibstrophe \ CFLAGS+=$(DBGCFLAGS) \ -fno-omit-frame-pointer -fPIC \ -std=gnu99 -gdwarf-4 \ - -Wall -Wextra \ + -Wall -Wextra -pedantic \ -Werror-implicit-function-declaration \ -Wno-missing-field-initializers \ -D_XOPEN_SOURCE=700 \ @@ -21,9 +21,10 @@ CFLAGS+=$(DBGCFLAGS) \ CPPFLAGS+=$(DBGCFLAGS) \ -fno-omit-frame-pointer -fPIC \ -std=c++17 -gdwarf-4 \ - -Wall -Wextra \ + -Wall -Wextra -pedantic \ -Wno-missing-field-initializers \ $(INCLUDES) +# -DDOCTEST_CONFIG_DISABLE LDFLAGS+=$(DBGLDFLAGS) \ -shared -gdwarf-4 \ $(DBGCFLAGS) @@ -39,46 +40,50 @@ PREFIX ?= /usr/local LIBDIR ?= $(PREFIX)/lib HDRS=plugin.hh \ - plugin.h \ + weechat.hh \ strophe.hh \ - account.h \ - buffer.h \ - channel.h \ - command.h \ - completion.h \ - config.h \ - connection.h \ - input.h \ - message.h \ - omemo.h \ - pgp.h \ - user.h \ - util.h \ - xmpp/stanza.h \ + account.hh \ + config.hh \ +#### plugin.h \ +#### account.h \ +#### buffer.h \ +#### channel.h \ +#### command.h \ +#### completion.h \ +#### config.h \ +#### connection.h \ +#### input.h \ +#### message.h \ +#### omemo.h \ +#### pgp.h \ +#### user.h \ +#### util.h \ +#### xmpp/stanza.h \ SRCS=plugin.cpp \ + weechat.cpp \ strophe.cpp \ - account.c \ - buffer.c \ - channel.c \ - command.c \ - completion.c \ - config.c \ - connection.c \ - input.c \ - message.c \ - omemo.c \ - pgp.c \ - user.c \ - util.c \ - xmpp/presence.c \ - xmpp/iq.c \ + account.cpp \ + config.cpp \ +#### account.c \ +#### buffer.c \ +#### channel.c \ +#### command.c \ +#### completion.c \ +#### config.c \ +#### connection.c \ +#### input.c \ +#### message.c \ +#### omemo.c \ +#### pgp.c \ +#### user.c \ +#### util.c \ +#### xmpp/presence.c \ +#### xmpp/iq.c \ DEPS=deps/diff/libdiff.a \ -TSTS=$(patsubst %.cpp,tests/%.cc,$(filter %.cpp,$(SRCS))) tests/main.cc OBJS=$(patsubst %.cpp,.%.o,$(patsubst %.c,.%.o,$(patsubst xmpp/%.c,xmpp/.%.o,$(SRCS)))) -JOBS=$(patsubst tests/%.cc,tests/.%.o,$(TSTS)) all: make depend @@ -87,7 +92,7 @@ all: weechat-xmpp: $(DEPS) xmpp.so xmpp.so: $(OBJS) $(DEPS) $(HDRS) - $(CXX) $(LDFLAGS) -o $@ $(OBJS) $(DEPS) $(LDLIBS) + $(CXX) $(LDFLAGS) -o .$@ $(OBJS) $(DEPS) $(LDLIBS) which patchelf >/dev/null && \ patchelf --set-rpath $(LIBRARY_PATH):$(shell realpath $(shell dirname $(shell gcc --print-libgcc-file-name))/../../../) xmpp.so && \ patchelf --shrink-rpath xmpp.so || true @@ -101,17 +106,14 @@ xmpp.so: $(OBJS) $(DEPS) $(HDRS) xmpp/.%.o: xmpp/%.c @$(CC) $(CFLAGS) -c $< -o $@ -tests/.%.o: tests/%.cc - @$(CXX) $(CPPFLAGS) -c $< -o $@ - deps/diff/libdiff.a: git submodule update --init --recursive - cd deps/diff && ./configure + cd deps/diff && env -u MAKEFLAGS ./configure $(MAKE) -C deps/diff CFLAGS=-fPIC diff: deps/diff/libdiff.a -tests/run: xmpp.so $(JOBS) - $(CXX) $(CPPFLAGS) -o tests/run xmpp.so $(JOBS) $(LDLIBS) +tests/run: xmpp.so tests/main.cpp + $(CXX) $(CPPFLAGS) -o tests/run xmpp.so tests/main.cpp $(LDLIBS) which patchelf >/dev/null && \ patchelf --set-rpath $(PWD):$(LIBRARY_PATH):$(shell realpath $(shell dirname $(shell gcc --print-libgcc-file-name))/../../../) tests/run && \ patchelf --shrink-rpath tests/run || true diff --git a/plugin.cpp b/plugin.cpp index a1e24fe..f44ff37 100644 --- a/plugin.cpp +++ b/plugin.cpp @@ -3,7 +3,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include "plugin.hh" +#include "weechat.hh" #include "strophe.hh" +#include "config.hh" +#include "account.hh" #define WEECHAT_XMPP_PLUGIN_NAME "xmpp" #define WEECHAT_XMPP_PLUGIN_VERSION "0.2.0" @@ -11,23 +14,27 @@ namespace c { extern "C" { #include "plugin.h" -#include "config.h" -#include "account.h" -#include "connection.h" -#include "command.h" -#include "input.h" -#include "buffer.h" -#include "completion.h" +// #include "connection.h" + void connection__init(); +// #include "command.h" + void command__init(); +// #include "input.h" + int input__text_changed_cb(const void*, void*, const char*, const char*, void*); +// #include "buffer.h" + std::string buffer__typing_bar_cb(weechat::gui_bar_item&, weechat::gui_window&, + weechat::gui_buffer&, weechat::hashtable&); +// #include "completion.h" + void completion__init(); struct t_weechat_plugin *weechat_xmpp_plugin() { return (struct t_weechat_plugin*)&*weechat::globals::plugin; - }; + } const char *weechat_xmpp_plugin_name() { return WEECHAT_XMPP_PLUGIN_NAME; - }; + } const char *weechat_xmpp_plugin_version() { return WEECHAT_XMPP_PLUGIN_VERSION; - }; + } } } @@ -42,24 +49,21 @@ namespace weechat { } bool plugin::init(std::vector) { - if (!c::config__init()) + if (!this->m_config.emplace().read()) { weechat::printf(nullptr, "%s: Error during config init", this->name()); return false; } - c::config__read(); - c::connection__init(); c::command__init(); c::completion__init(); - this->m_process_timer = - weechat::hook_timer(plugin::timer_interval_sec * 1000, 0, 0, - &c::account__timer_cb); + this->m_process_timer.emplace(plugin::timer_interval_sec * 1000, 0, 0, + [](int) { return weechat::errc::ok; }); if (!weechat::bar_search("typing")) { @@ -69,12 +73,7 @@ namespace weechat { "off", "xmpp_typing"); } - this->m_typing_bar_item = - weechat::bar_item_new("xmpp_typing", - (char* (*)(const void*, void*, - t_gui_bar_item*, t_gui_window*, - t_gui_buffer*, t_hashtable*))( - &c::buffer__typing_bar_cb)); + this->m_typing_bar_item.emplace("xmpp_typing", weechat::gui_bar_item::build_callback()); weechat::hook_signal("input_text_changed", &c::input__text_changed_cb); @@ -86,13 +85,13 @@ namespace weechat { this->m_process_timer.reset(); - c::config__write(); + this->m_config->write(); - c::account__disconnect_all(); + weechat::xmpp::account::disconnect_all(); - c::account__free_all(); + weechat::xmpp::globals::accounts.clear(); - xmpp::shutdown(); + ::xmpp::shutdown(); return true; } @@ -101,44 +100,32 @@ namespace weechat { return plugin_get_name(*this); } - weechat::plugin globals::plugin; - - hook::hook(struct t_hook* hook) - : std::reference_wrapper(*hook) { - } - - hook::~hook() { - weechat::unhook(*this); - } - - gui_bar_item::gui_bar_item(struct t_gui_bar_item* item) - : std::reference_wrapper(*item) { + weechat::xmpp::config& plugin::config() { + return *this->m_config; } - gui_bar_item::~gui_bar_item() { - weechat::bar_item_remove(*this); - } + weechat::plugin globals::plugin; } extern "C" { - WEECHAT_PLUGIN_NAME(WEECHAT_XMPP_PLUGIN_NAME); - WEECHAT_PLUGIN_DESCRIPTION(N_("XMPP client protocol")); - WEECHAT_PLUGIN_AUTHOR("bqv "); - WEECHAT_PLUGIN_VERSION(WEECHAT_XMPP_PLUGIN_VERSION); - WEECHAT_PLUGIN_LICENSE("MPL2"); - WEECHAT_PLUGIN_PRIORITY(5500); - - weechat::rc weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[]) + WEECHAT_PLUGIN_NAME(WEECHAT_XMPP_PLUGIN_NAME) + WEECHAT_PLUGIN_DESCRIPTION(N_("XMPP client protocol")) + WEECHAT_PLUGIN_AUTHOR("bqv ") + WEECHAT_PLUGIN_VERSION(WEECHAT_XMPP_PLUGIN_VERSION) + WEECHAT_PLUGIN_LICENSE("MPL2") + WEECHAT_PLUGIN_PRIORITY(5500) + + weechat::errc weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[]) { weechat::globals::plugin = (struct weechat::t_weechat_plugin*)plugin; std::vector args(argv, argv+argc); return weechat::globals::plugin.init(args) - ? WEECHAT_RC_OK : WEECHAT_RC_ERROR; + ? weechat::ok : weechat::err; } - weechat::rc weechat_plugin_end(struct t_weechat_plugin *) + weechat::errc weechat_plugin_end(struct t_weechat_plugin *) { return weechat::globals::plugin.end() - ? WEECHAT_RC_OK : WEECHAT_RC_ERROR; + ? weechat::ok : weechat::err; } } diff --git a/plugin.hh b/plugin.hh index 56fa574..2c3f968 100644 --- a/plugin.hh +++ b/plugin.hh @@ -10,71 +10,40 @@ #include #include #include +#include +#include +#include -namespace weechat { - extern "C" { -#include - - typedef int rc; - typedef struct t_config_option config_option; - typedef struct t_config_section config_section; - typedef struct t_config_file config_file; - typedef struct t_gui_window gui_window; - typedef struct t_gui_buffer gui_buffer; - typedef struct t_gui_bar gui_bar; - //typedef struct t_gui_bar_item gui_bar_item; - typedef struct t_gui_bar_window gui_bar_window; - typedef struct t_gui_completion gui_completion; - typedef struct t_gui_nick gui_nick; - typedef struct t_gui_nick_group gui_nick_group; - typedef struct t_infolist infolist; - typedef struct t_infolist_item infolist_item; - typedef struct t_upgrade_file upgrade_file; - typedef struct t_weelist weelist; - typedef struct t_weelist_item weelist_item; - typedef struct t_arraylist arraylist; - typedef struct t_hashtable hashtable; - typedef struct t_hdata hdata; - //typedef struct t_weechat_plugin weechat_plugin; - } +#include "weechat.hh" +#include "config.hh" - class gui_bar_item : public std::reference_wrapper { - public: - gui_bar_item(struct t_gui_bar_item* item); - ~gui_bar_item(); +template> +constexpr Or_Output operator>>= (Or_Input input, Func f) { + static_assert(std::is_invocable_v, + "The function passed in must take type " + "(Or_Input::value_type) as its argument"); - inline operator struct t_gui_bar_item* () const { return &this->get(); } - inline gui_bar_item& operator= (struct t_gui_bar_item* item_ptr) { - *this = std::move(gui_bar_item(item_ptr)); - return *this; - } - }; - - class hook : public std::reference_wrapper { - public: - hook(struct t_hook* hook); - ~hook(); - - inline operator struct t_hook* () const { return &this->get(); } - inline hook& operator= (struct t_hook* hook_ptr) { - *this = std::move(hook(hook_ptr)); - return *this; - } - }; + return input ? std::invoke(f, *input) : std::nullopt; +} +namespace weechat { class plugin : public std::reference_wrapper { public: plugin(); - plugin(struct t_weechat_plugin* plugin); + explicit plugin(struct t_weechat_plugin* plugin); bool init(std::vector args); bool end(); std::string_view name() const; + weechat::xmpp::config& config(); inline operator struct t_weechat_plugin* () const { return &this->get(); } inline struct t_weechat_plugin* operator-> () const { return &this->get(); } inline plugin& operator= (struct t_weechat_plugin* plugin_ptr) { - *this = std::move(plugin(plugin_ptr)); + std::reference_wrapper::operator=(*plugin_ptr); return *this; } @@ -83,13 +52,14 @@ namespace weechat { private: std::optional m_process_timer; std::optional m_typing_bar_item; + std::optional m_config; }; namespace globals { extern weechat::plugin plugin; } - inline std::string_view plugin_get_name(struct t_weechat_plugin *plugin) { + inline const char *plugin_get_name(struct t_weechat_plugin *plugin) { return globals::plugin->plugin_get_name(plugin); } @@ -356,7 +326,9 @@ namespace weechat { inline int util_timeval_cmp(struct timeval *tv1, struct timeval *tv2) { return globals::plugin->util_timeval_cmp(tv1, tv2); } - long long (*util_timeval_diff) (struct timeval *tv1, struct timeval *tv2); + inline long long util_timeval_diff(struct timeval *tv1, struct timeval *tv2) { + return globals::plugin->util_timeval_diff(tv1, tv2); + } inline void util_timeval_add(struct timeval *tv, long long interval) { return globals::plugin->util_timeval_add(tv, interval); } @@ -555,53 +527,58 @@ namespace weechat { } inline struct t_config_file *config_new(const char *name, - int (*callback_reload)(const void *pointer, - void *data, - struct t_config_file *config_file), - const void *callback_reload_pointer, - void *callback_reload_data) { - return globals::plugin->config_new(globals::plugin, name, callback_reload, callback_reload_pointer, callback_reload_data); - } - inline struct t_config_section *config_new_section(struct t_config_file *config_file, + config_file::reload_callback& reload_cb) { + return globals::plugin->config_new( + globals::plugin, name, + [] (const void *pointer, void *, struct t_config_file *file) { + auto func = *reinterpret_cast(pointer); + config_file file_(file); + return func(file_); + }, &reload_cb, nullptr); + } + inline struct t_config_section *config_new_section(struct t_config_file *file, const char *name, - int user_can_add_options, - int user_can_delete_options, - int (*callback_read)(const void *pointer, - void *data, - struct t_config_file *config_file, - struct t_config_section *section, - const char *option_name, - const char *value), - const void *callback_read_pointer, - void *callback_read_data, - int (*callback_write)(const void *pointer, - void *data, - struct t_config_file *config_file, - const char *section_name), - const void *callback_write_pointer, - void *callback_write_data, - int (*callback_write_default)(const void *pointer, - void *data, - struct t_config_file *config_file, - const char *section_name), - const void *callback_write_default_pointer, - void *callback_write_default_data, - int (*callback_create_option)(const void *pointer, - void *data, - struct t_config_file *config_file, - struct t_config_section *section, - const char *option_name, - const char *value), - const void *callback_create_option_pointer, - void *callback_create_option_data, - int (*callback_delete_option)(const void *pointer, - void *data, - struct t_config_file *config_file, - struct t_config_section *section, - struct t_config_option *option), - const void *callback_delete_option_pointer, - void *callback_delete_option_data) { - return globals::plugin->config_new_section(config_file, name, user_can_add_options, user_can_delete_options, callback_read, callback_read_pointer, callback_read_data, callback_write, callback_write_pointer, callback_write_data, callback_write_default, callback_write_default_pointer, callback_write_default_data, callback_create_option, callback_create_option_pointer, callback_create_option_data, callback_delete_option, callback_delete_option_pointer, callback_delete_option_data); + bool user_can_add_options, + bool user_can_delete_options, + config_section::read_callback& read_cb, + config_section::write_callback& write_cb, + config_section::write_default_callback& write_default_cb, + config_section::create_option_callback& create_cb, + config_section::delete_option_callback& delete_cb) { + return globals::plugin->config_new_section( + file, name, user_can_add_options, user_can_delete_options, + [] (const void *pointer, void *, struct t_config_file *file, + struct t_config_section *section, const char *key, const char *value) { + auto func = *reinterpret_cast(pointer); + config_file file_(file); + config_section section_(section); + return func(file_, section_, key, value); + }, &read_cb, nullptr, + [] (const void *pointer, void *, struct t_config_file *file, const char *name) { + auto func = *reinterpret_cast(pointer); + config_file file_(file); + return func(file_, name); + }, &write_cb, nullptr, + [] (const void *pointer, void *, struct t_config_file *file, const char *name) { + auto func = *reinterpret_cast(pointer); + config_file file_(file); + return func(file_, name); + }, &write_default_cb, nullptr, + [] (const void *pointer, void *, struct t_config_file *file, + struct t_config_section *section, const char *key, const char *value) { + auto func = *reinterpret_cast(pointer); + config_file file_(file); + config_section section_(section); + return func(file_, section_, key, value); + }, &create_cb, nullptr, + [] (const void *pointer, void *, struct t_config_file *file, + struct t_config_section *section, struct t_config_option *option) { + auto func = *reinterpret_cast(pointer); + config_file file_(file); + config_section section_(section); + config_option option_(option); + return func(file_, section_, option_); + }, &delete_cb, nullptr); } inline struct t_config_section *config_search_section(struct t_config_file *config_file, const char *section_name) { @@ -609,32 +586,35 @@ namespace weechat { } inline struct t_config_option *config_new_option(struct t_config_file *config_file, struct t_config_section *section, - const char *name, - const char *type, + const char *name, const char *type, const char *description, const char *string_values, - int min, - int max, + int min, int max, const char *default_value, const char *value, - int null_value_allowed, - int (*callback_check_value)(const void *pointer, - void *data, - struct t_config_option *option, - const char *value), - const void *callback_check_value_pointer, - void *callback_check_value_data, - void (*callback_change)(const void *pointer, - void *data, - struct t_config_option *option), - const void *callback_change_pointer, - void *callback_change_data, - void (*callback_delete)(const void *pointer, - void *data, - struct t_config_option *option), - const void *callback_delete_pointer, - void *callback_delete_data) { - return globals::plugin->config_new_option(config_file, section, name, type, description, string_values, min, max, default_value, value, null_value_allowed, callback_check_value, callback_check_value_pointer, callback_check_value_data, callback_change, callback_change_pointer, callback_change_data, callback_delete, callback_delete_pointer, callback_delete_data); + bool null_value_allowed, + config_option::check_callback& check_value_cb, + config_option::change_callback& change_cb, + config_option::delete_callback& delete_cb) { + return globals::plugin->config_new_option( + config_file, section, + name, type, description, string_values, + min, max, default_value, value, null_value_allowed, + [] (const void *pointer, void *, struct t_config_option *option, const char *value) { + auto func = *reinterpret_cast(pointer); + config_option option_(option); + return static_cast(func(option_, value)); + }, &check_value_cb, nullptr, + [] (const void *pointer, void *, struct t_config_option *option) { + auto func = *reinterpret_cast(pointer); + config_option option_(option); + return func(option_); + }, &change_cb, nullptr, + [] (const void *pointer, void *, struct t_config_option *option) { + auto func = *reinterpret_cast(pointer); + config_option option_(option); + return func(option_); + }, &delete_cb, nullptr); } inline struct t_config_option *config_search_option(struct t_config_file *config_file, struct t_config_section *section, @@ -784,7 +764,7 @@ namespace weechat { template inline void printf(struct t_gui_buffer *buffer, const char *message, Args... args) { return globals::plugin->printf_date_tags( - buffer, 0, NULL, message, args...); + buffer, 0, nullptr, message, args...); } template inline void printf_date_tags(struct t_gui_buffer *buffer, time_t date, @@ -824,21 +804,18 @@ namespace weechat { void *callback_data) { return globals::plugin->hook_command_run(globals::plugin, command, callback, callback_pointer, callback_data); } - inline std::optional hook_timer( + inline struct t_hook *hook_timer( long interval, int align_second, int max_calls, - int (*callback)(const void *pointer, - void *data, - int remaining_calls), - const void *callback_pointer = nullptr, - void *callback_data = nullptr) { - if (auto ptr = globals::plugin->hook_timer( - globals::plugin, - interval, align_second, max_calls, - callback, callback_pointer, callback_data)) - return std::make_optional(hook(ptr)); - return std::nullopt; + hook::timer_callback *callback) { + return globals::plugin->hook_timer( + globals::plugin, + interval, align_second, max_calls, + callback ? static_cast([] (const void *pointer, void *, int remaining_calls) { + auto func = *reinterpret_cast(pointer); + return static_cast(func(remaining_calls)); + }) : nullptr, callback, nullptr); } inline struct t_hook *hook_fd(int fd, int flag_read, @@ -934,8 +911,9 @@ namespace weechat { void *callback_data = nullptr) { return globals::plugin->hook_signal(globals::plugin, signal, callback, callback_pointer, callback_data); } + template inline int hook_signal_send(const char *signal, const char *type_data, - void *signal_data) { + T signal_data) { return globals::plugin->hook_signal_send(signal, type_data, signal_data); } inline struct t_hook *hook_hsignal(const char *signal, @@ -1061,18 +1039,24 @@ namespace weechat { } inline struct t_gui_buffer *buffer_new(const char *name, - int (*input_callback)(const void *pointer, - void *data, - struct t_gui_buffer *buffer, - const char *input_data), - const void *input_callback_pointer, - void *input_callback_data, - int (*close_callback)(const void *pointer, - void *data, - struct t_gui_buffer *buffer), - const void *close_callback_pointer, - void *close_callback_data) { - return globals::plugin->buffer_new(globals::plugin, name, input_callback, input_callback_pointer, input_callback_data, close_callback, close_callback_pointer, close_callback_data); + gui_buffer::input_callback& input_cb, + gui_buffer::close_callback& close_cb) { + return globals::plugin->buffer_new( + globals::plugin, + name, + [] (const void *pointer, void *, + struct t_gui_buffer *buffer, + const char *input_data) { + auto func = *reinterpret_cast(pointer); + gui_buffer buffer_(buffer); + return static_cast(func(buffer_, input_data)); + }, &input_cb, nullptr, + [] (const void *pointer, void *, + struct t_gui_buffer *buffer) { + auto func = *reinterpret_cast(pointer); + gui_buffer buffer_(buffer); + return static_cast(func(buffer_)); + }, &close_cb, nullptr); } inline struct t_gui_buffer *buffer_search(const char *plugin, const char *name) { return globals::plugin->buffer_search(plugin, name); @@ -1238,22 +1222,26 @@ namespace weechat { inline struct t_gui_bar_item *bar_item_search(const char *name) { return globals::plugin->bar_item_search(name); } - inline std::optional bar_item_new( + inline struct t_gui_bar_item *bar_item_new( const char *name, - char *(*build_callback)(const void *pointer, - void *data, - struct t_gui_bar_item *item, - struct t_gui_window *window, - struct t_gui_buffer *buffer, - struct t_hashtable *extra_info), - const void *build_callback_pointer = nullptr, - void *build_callback_data = nullptr) { - if (auto ptr = globals::plugin->bar_item_new( - globals::plugin, - name, build_callback, - build_callback_pointer, build_callback_data)) - return std::make_optional(gui_bar_item(ptr)); - return std::nullopt; + gui_bar_item::build_callback& build_callback) { + return globals::plugin->bar_item_new( + globals::plugin, + name, [] (const void *pointer, void *, + struct t_gui_bar_item *item, + struct t_gui_window *window, + struct t_gui_buffer *buffer, + struct t_hashtable *extra_args) { + auto func = *reinterpret_cast(pointer); + gui_bar_item item_(item); + gui_buffer buffer_(buffer); + auto res = func(item_, window, buffer_, extra_args); + char *str = reinterpret_cast( + std::calloc(res.size() + 1, sizeof(char))); + std::copy(res.begin(), res.end(), str); + str[res.size()] = '\0'; + return str; + }, &build_callback, nullptr); } inline void bar_item_update(const char *name) { return globals::plugin->bar_item_update(name); diff --git a/strophe.cpp b/strophe.cpp index e9db828..7d76e19 100644 --- a/strophe.cpp +++ b/strophe.cpp @@ -2,13 +2,14 @@ // License, version 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -#include "strophe.hh" +#include -xmpp_log_t* logger = nullptr; +#include "strophe.hh" +#include "strophe.ipp" namespace xmpp { - context::context() - : context(xmpp_ctx_new(nullptr, logger)) { + extern "C" { +#include } context::context(xmpp_ctx_ptr ptr) @@ -16,18 +17,80 @@ namespace xmpp { } context::context(xmpp_ctx_t *ptr) - : context(std::move(xmpp_ctx_ptr( - ptr, [this] (xmpp_ctx_t *ctx) { - xmpp_ctx_free(ctx); - } - ))) { + : xmpp_ctx_ptr(ptr, xmpp_ctx_free) { } context::~context() { this->reset(nullptr); } + connection::connection(const context& context) + : connection(xmpp_conn_new(&*context)) { + } + + connection::connection(xmpp_conn_ptr ptr) + : xmpp_conn_ptr(std::move(ptr)) { + } + + connection::connection(xmpp_conn_t *ptr) + : xmpp_conn_ptr(ptr, xmpp_conn_release) { + } + + connection::~connection() { + this->reset(nullptr); + } + void shutdown() { xmpp_shutdown(); } } + +namespace xml { + extern "C" { +#include + } + + template void set_error_context(FILE*); + + document::document(std::string_view text) + : m_ptr(xmlRecoverMemory(text.data(), text.size())) + , m_size(text.size()) { + } + + document::~document() { + xmlFreeDoc(this->m_ptr); + } + + document::node::node(xmlNodePtr ptr) + : m_ptr(ptr) { + } + + std::string document::node::name() const { + return reinterpret_cast(this->m_ptr->name); + } + + std::optional document::root() { + xmlNodePtr root = xmlDocGetRootElement(this->m_ptr); + if (root) + return document::node(root); + else + return {}; + } + + document::operator bool () const { + return this->m_ptr; + } + + std::string document::format() const { + if (!this->m_ptr) + throw xml::error("failed to parse xml"); + + std::unique_ptr buf( + new xmlChar[this->m_size * 2]); + int size = -1; + xmlChar *bufPtr = &*buf; + xmlDocDumpFormatMemory(this->m_ptr, &bufPtr, &size, 1); + + return std::string(bufPtr, bufPtr + size); + } +} diff --git a/strophe.hh b/strophe.hh index c3ec0ca..86af3fb 100644 --- a/strophe.hh +++ b/strophe.hh @@ -6,23 +6,128 @@ #include #include +#include +#include +#include +#include +#include -extern "C" { +namespace xmpp { + extern "C" { #include -} + } -namespace xmpp { - typedef std::unique_ptr< - xmpp_ctx_t, - std::function> xmpp_ctx_ptr; + template + class logger : public xmpp_log_t { + private: + UserData& m_data; + public: + explicit logger(UserData& data); + + class level { + public: + level() = default; + constexpr level(const xmpp_log_level_t lvl) : value(lvl) { } + + inline operator xmpp_log_level_t () const { return this->value; } + explicit operator bool () = delete; + constexpr bool operator== (level lvl) const { return this->value == lvl.value; } + constexpr bool operator!= (level lvl) const { return this->value != lvl.value; } + constexpr bool operator<= (level lvl) const { return this->value <= lvl.value; } + constexpr bool operator>= (level lvl) const { return this->value >= lvl.value; } + constexpr bool operator< (level lvl) const { return this->value < lvl.value; } + constexpr bool operator> (level lvl) const { return this->value > lvl.value; } + + inline const char *name() const { + static const char *names[] = {"debug", "info", "warn", "error", nullptr}; + + return names[this->value]; + } + private: + xmpp_log_level_t value; + }; + + inline static level debug = level(XMPP_LEVEL_DEBUG); + inline static level info = level(XMPP_LEVEL_INFO); + inline static level warn = level(XMPP_LEVEL_WARN); + inline static level error = level(XMPP_LEVEL_ERROR); + + static void emit_weechat(UserData& data, const level level, + std::string_view area, std::string_view msg); + }; + + typedef std::unique_ptr> xmpp_ctx_ptr; class context : public xmpp_ctx_ptr { public: - context(); - context(xmpp_ctx_ptr ptr); - context(xmpp_ctx_t *ptr); + template + explicit context(UserData& logger_data); + explicit context(xmpp_ctx_ptr ptr); + explicit context(xmpp_ctx_t *ptr); ~context(); + + inline operator xmpp_ctx_t* () { return this->get(); } + + private: + std::any m_logger; + }; + + typedef std::unique_ptr> xmpp_conn_ptr; + + class connection : public xmpp_conn_ptr { + public: + explicit connection(const context& context); + explicit connection(xmpp_conn_ptr ptr); + explicit connection(xmpp_conn_t *ptr); + ~connection(); + + inline operator xmpp_conn_t* () { return this->get(); } }; void shutdown(); } + +namespace xml { + extern "C" { +#include + } + + class error : virtual public std::runtime_error { + public: + explicit inline error(const std::string_view subject) + : std::runtime_error(std::string(subject)) { + } + virtual ~error() throw () {} + }; + + + template + void set_error_context(T *context); + + class document { + protected: + class node { + public: + explicit node(xmlNodePtr ptr); + std::string name() const; + + private: + const xmlNodePtr m_ptr; + }; + + public: + explicit document(std::string_view text); + ~document(); + + std::optional root(); + std::string format() const; + + operator bool () const; + + private: + const xmlDocPtr m_ptr; + const std::size_t m_size; + }; +} diff --git a/strophe.ipp b/strophe.ipp new file mode 100644 index 0000000..e467516 --- /dev/null +++ b/strophe.ipp @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, version 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "strophe.hh" + +namespace xmpp { + extern "C" { +#include + } + + template + context::context(UserData& data) + : context(xmpp_ctx_new(nullptr, const_cast(static_cast(std::any_cast>(&this->m_logger))))) { + this->m_logger = logger(data); + } + + template + logger::logger(UserData& data) + : m_data(data) { + this->handler = [] (void *const userdata, const xmpp_log_level_t level, + const char *const area, const char *const msg) { + UserData& data = static_cast*>(userdata)->m_data; + logger::emit_weechat(data, level, area, msg); + }; + this->userdata = this; + } +} + +namespace xml { + extern "C" { +#include + } + + template + void set_error_context(T *context) { + xmlGenericErrorContext = context; + } +} diff --git a/stubs.cpp b/stubs.cpp new file mode 100644 index 0000000..c7bdcb3 --- /dev/null +++ b/stubs.cpp @@ -0,0 +1,31 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, version 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +namespace c { + extern "C" { +#include "plugin.h" +// #include "connection.h" + void connection__init() { return; } + bool connection__connect(weechat::xmpp::account&, xmpp::connection&, + std::string, std::string, std::string) { return true; } + void connection__process(xmpp::context&, xmpp::connection&, int) { return; } +// #include "command.h" + void command__init() { return; } +// #include "input.h" + int input__text_changed_cb(const void*, void*, const char*, const char*, void*) { return 0; } +// #include "buffer.h" + std::string buffer__typing_bar_cb(weechat::gui_bar_item&, weechat::gui_window&, + weechat::gui_buffer&, weechat::hashtable&) { return ""; } + int buffer__close_cb(const void*, void*, struct t_gui_buffer*) { return 0; } + int buffer__nickcmp_cb(struct t_gui_buffer*, const char*, const char*) { return 0; } +// #include "completion.h" + void completion__init() { return; } +// #include "user.h" + void user__free_all(weechat::xmpp::account&) { return; } + } +} + +struct weechat::xmpp::t_channel { + weechat::gui_buffer buffer; +}; diff --git a/tests/account.inl b/tests/account.inl new file mode 100644 index 0000000..e80327a --- /dev/null +++ b/tests/account.inl @@ -0,0 +1,10 @@ +#include + +#include "../account.hh" + +TEST_CASE("create account") +{ + weechat::xmpp::account acc("demo"); + + CHECK(acc.name == "demo"); +} diff --git a/tests/config.inl b/tests/config.inl new file mode 100644 index 0000000..52a7368 --- /dev/null +++ b/tests/config.inl @@ -0,0 +1,10 @@ +#include + +#include "../config.hh" + +TEST_CASE("create config") +{ + weechat::xmpp::config cfg; + + CHECK(cfg.name() == weechat::xmpp::config::default_name); +} diff --git a/tests/main.cc b/tests/main.cpp similarity index 100% rename from tests/main.cc rename to tests/main.cpp diff --git a/tests/plugin.cc b/tests/plugin.inl similarity index 93% rename from tests/plugin.cc rename to tests/plugin.inl index 9485760..84778f2 100644 --- a/tests/plugin.cc +++ b/tests/plugin.inl @@ -1,4 +1,3 @@ -#include #include #include "../plugin.hh" @@ -13,6 +12,7 @@ TEST_CASE("placeholder") CHECK(argc != 1); } + (void) argv; //weechat::plugin c; //CHECK(&c.name() == NULL); } diff --git a/tests/run b/tests/run new file mode 100755 index 0000000..ec5d7c4 Binary files /dev/null and b/tests/run differ diff --git a/tests/strophe.cc b/tests/strophe.inl similarity index 72% rename from tests/strophe.cc rename to tests/strophe.inl index fe40fed..fc01590 100644 --- a/tests/strophe.cc +++ b/tests/strophe.inl @@ -1,11 +1,10 @@ -#include #include #include "../strophe.hh" TEST_CASE("create context") { - xmpp::context ctx; + xmpp::context ctx(0); CHECK(ctx.get()); } diff --git a/tests/weechat.inl b/tests/weechat.inl new file mode 100644 index 0000000..06f1ad6 --- /dev/null +++ b/tests/weechat.inl @@ -0,0 +1,10 @@ +#include + +#include "../weechat.hh" + +TEST_CASE("create error") +{ + weechat::error err("content"); + + CHECK(err.what() == "content"); +} diff --git a/weechat.cpp b/weechat.cpp new file mode 100644 index 0000000..b665f06 --- /dev/null +++ b/weechat.cpp @@ -0,0 +1,137 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, version 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "weechat.hh" +#include "plugin.hh" + +using namespace std::placeholders; + +namespace weechat { + config_option::config_option( + config_file& config_file, config_section& section, std::string name, + std::string type, std::string description, std::string string_values, + int min, int max, std::string default_value, std::string value, bool null_value_allowed, + check_callback check_value_cb, change_callback change_cb, delete_callback delete_cb) + : config_option(weechat::config_new_option( + config_file, section, name.data(), type.data(), + description.data(), string_values.data(), min, max, + default_value.data(), value.data(), null_value_allowed, + this->m_check_cb, this->m_change_cb, this->m_delete_cb)) { + this->m_check_cb = check_value_cb; + this->m_change_cb = change_cb; + this->m_delete_cb = delete_cb; + this->m_name = name; + } + + config_option::config_option(struct t_config_option* option) + : std::reference_wrapper(*option) { + if (!option) + throw weechat::error("failed to create config option"); + } + + config_option::operator int () const { + return weechat::config_integer(*this); + } + config_option::operator bool () const { + return weechat::config_boolean(*this); + } + config_option::operator std::string () const { + return weechat::config_string(*this); + } + std::string config_option::string(std::string property) const { + return weechat::config_option_get_string(*this, property.data()); + } + config_option& config_option::operator= (std::string_view value) { + weechat::config_option_set(*this, std::string(value).data(), 1); + return *this; + } + + config_section::config_section(config_file& config_file, std::string name, + bool user_can_add_options, bool user_can_delete_options, + read_callback read_cb, write_callback write_cb, + write_default_callback write_default_cb, + create_option_callback create_option_cb, + delete_option_callback delete_option_cb) + : config_section(weechat::config_new_section(config_file, name.data(), + user_can_add_options, user_can_delete_options, + this->m_read_cb, this->m_write_cb, + this->m_write_default_cb, + this->m_create_option_cb, + this->m_delete_option_cb)) { + this->m_read_cb = read_cb; + this->m_write_cb = write_cb; + this->m_write_default_cb = write_default_cb; + this->m_create_option_cb = create_option_cb; + this->m_delete_option_cb = delete_option_cb; + this->m_name = name; + } + + config_section::config_section(struct t_config_section* section) + : std::reference_wrapper(*section) { + if (!section) + throw weechat::error("failed to create config section"); + } + + config_file::config_file(std::string name, reload_callback reload_cb) + : config_file(weechat::config_new(name.data(), this->m_reload_cb)) { + this->m_reload_cb = reload_cb; + this->m_name = name; + } + + config_file::config_file(struct t_config_file* file) + : std::reference_wrapper(*file) { + if (!file) + throw weechat::error("failed to create config file"); + } + + gui_bar_item::gui_bar_item(std::string_view name, gui_bar_item::build_callback callback) + : gui_bar_item(weechat::bar_item_new(name.data(), this->m_cb)) { + this->m_cb = callback; + } + + gui_buffer::gui_buffer(std::string name, gui_buffer::input_callback input_cb, + gui_buffer::close_callback close_cb) + : gui_buffer(weechat::buffer_new(name.data(), this->m_input_cb, this->m_close_cb)) { + this->m_input_cb = input_cb; + this->m_close_cb = close_cb; + this->name = name; + } + + gui_buffer::gui_buffer(struct t_gui_buffer* buffer) + : std::reference_wrapper(*buffer) { + if (!buffer) + throw weechat::error("failed to create buffer"); + } + + gui_buffer::~gui_buffer() { + weechat::buffer_close(*this); + } + + gui_bar_item::gui_bar_item(struct t_gui_bar_item* item) + : std::reference_wrapper(*item) { + if (!item) + throw weechat::error("failed to create bar item"); + } + + gui_bar_item::~gui_bar_item() { + weechat::bar_item_remove(*this); + } + + hook::hook(struct t_hook* hook) + : std::reference_wrapper(*hook) { + if (!hook) + throw weechat::error("failed to create hook timer"); + } + + hook::hook(long interval, int align_second, int max_calls, + hook::timer_callback callback) + : hook(weechat::hook_timer(interval, align_second, max_calls, + callback ? &this->m_timer_cb : nullptr)) { + this->m_timer_cb = callback; + } + + hook::~hook() { + weechat::unhook(*this); + } +} diff --git a/weechat.hh b/weechat.hh new file mode 100644 index 0000000..7499666 --- /dev/null +++ b/weechat.hh @@ -0,0 +1,238 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, version 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace weechat { + extern "C" { +#include + + class config_option; //typedef struct t_config_option config_option; + class config_section; //typedef struct t_config_section config_section; + class config_file; //typedef struct t_config_file config_file; + typedef struct t_gui_window gui_window; + class gui_buffer; //typedef struct t_gui_buffer gui_buffer; + typedef struct t_gui_bar gui_bar; + class gui_bar_item; //typedef struct t_gui_bar_item gui_bar_item; + typedef struct t_gui_bar_window gui_bar_window; + typedef struct t_gui_completion gui_completion; + typedef struct t_gui_nick gui_nick; + typedef struct t_gui_nick_group gui_nick_group; + typedef struct t_infolist infolist; + typedef struct t_infolist_item infolist_item; + typedef struct t_upgrade_file upgrade_file; + typedef struct t_weelist weelist; + typedef struct t_weelist_item weelist_item; + typedef struct t_arraylist arraylist; + typedef struct t_hashtable hashtable; + typedef struct t_hdata hdata; + class hook; //typedef struct t_hook hook; + class plugin; //typedef struct t_weechat_plugin weechat_plugin; + } + + enum errc : int { + ok = WEECHAT_RC_OK, + eat = WEECHAT_RC_OK_EAT, + err = WEECHAT_RC_ERROR, + }; + + class error : virtual public std::runtime_error { + public: + explicit inline error(const std::string_view subject) + : std::runtime_error(std::string(subject)) { + } + virtual ~error() throw () {} + }; + + class config_option : public std::reference_wrapper { + public: + typedef int (*check_fn)(const void *, void *, struct t_config_option *, const char *); + typedef std::function check_callback; + typedef void (*change_fn)(const void *, void *, struct t_config_option *); + typedef std::function change_callback; + typedef void (*delete_fn)(const void *, void *, struct t_config_option *); + typedef std::function delete_callback; + + config_option( + config_file& config_file, config_section& section, std::string name, + std::string type, std::string description, std::string string_values, + int min, int max, std::string default_value, std::string value, bool null_value_allowed, + check_callback check_value_cb, change_callback change_cb, delete_callback delete_cb); + explicit config_option(struct t_config_option* config_option); + inline ~config_option() {} + + inline operator struct t_config_option* () const { return &this->get(); } + operator int () const; + operator bool () const; + operator std::string () const; + std::string string(std::string property) const; + template T *pointer(std::string property) const; + config_option& operator= (std::string_view value); + inline config_option& operator= (struct t_config_option* config_option_ptr) { + *this = config_option_ptr; + return *this; + } + + inline std::string name() const { return this->m_name; } + + private: + check_callback m_check_cb; + change_callback m_change_cb; + delete_callback m_delete_cb; + std::string m_name; + }; + + class config_section : public std::reference_wrapper { + public: + typedef int (*read_fn)(const void *, void *, struct t_config_file *, struct t_config_section *, + const char *, const char *); + typedef std::function read_callback; + typedef int (*write_fn)(const void *, void *, struct t_config_file *, const char *); + typedef std::function write_callback; + typedef int (*write_default_fn)(const void *, void *, struct t_config_file *, const char *); + typedef std::function write_default_callback; + typedef int (*create_option_fn)(const void *, void *, struct t_config_file *, struct t_config_section *, + const char *, const char *); + typedef std::function create_option_callback; + typedef int (*delete_option_fn)(const void *, void *, struct t_config_file *, struct t_config_section *, + struct t_config_option *); + typedef std::function delete_option_callback; + + config_section( + config_file& config_file, std::string name, + bool user_can_add_options, bool user_can_delete_options, + read_callback read_cb, write_callback write_cb, + write_default_callback write_default_cb, + create_option_callback create_option_cb, + delete_option_callback delete_option_cb); + explicit config_section(struct t_config_section* config_section); + inline ~config_section() {} + + inline operator struct t_config_section* () const { return &this->get(); } + inline config_section& operator= (struct t_config_section* config_section_ptr) { + *this = config_section_ptr; + return *this; + } + + inline std::string name() const { return this->m_name; } + + private: + std::string m_name; + + read_callback m_read_cb; + write_callback m_write_cb; + write_default_callback m_write_default_cb; + create_option_callback m_create_option_cb; + delete_option_callback m_delete_option_cb; + }; + + class config_file : public std::reference_wrapper { + public: + typedef int (*reload_fn)(const void *, void *, struct t_config_file *); + typedef std::function reload_callback; + + config_file(std::string name, reload_callback reload_cb); + explicit config_file(struct t_config_file* config_file); + inline ~config_file() {} + + inline operator struct t_config_file* () const { return &this->get(); } + inline config_file& operator= (struct t_config_file* config_file_ptr) { + *this = config_file_ptr; + return *this; + } + + inline std::string name() const { return this->m_name; } + + private: + std::string m_name; + + reload_callback m_reload_cb; + }; + + class gui_buffer : std::reference_wrapper { + public: + typedef int (*input_fn)(const void *, void *, struct t_gui_buffer *, const char *); + typedef std::function input_callback; + typedef int (*close_fn)(const void *, void *, struct t_gui_buffer *); + typedef std::function close_callback; + + gui_buffer(std::string name, input_callback input_cb, close_callback close_cb); + explicit gui_buffer(struct t_gui_buffer* gui_buffer); + ~gui_buffer(); + + inline operator struct t_gui_buffer* () const { return &this->get(); } + inline gui_buffer& operator= (struct t_gui_buffer* gui_buffer_ptr) { + *this = gui_buffer_ptr; + return *this; + } + + std::string name; + + private: + input_callback m_input_cb; + close_callback m_close_cb; + }; + + class gui_bar_item : public std::reference_wrapper { + public: + typedef char* (*build_fn)(const void *, void *, struct t_gui_bar_item *, struct t_gui_window *, + struct t_gui_buffer *, struct t_hashtable *); + typedef std::function build_callback; + + gui_bar_item(std::string_view name, build_callback callback); + explicit gui_bar_item(struct t_gui_bar_item* item); + ~gui_bar_item(); + + void update(std::string_view name); + + inline operator struct t_gui_bar_item* () const { return &this->get(); } + inline gui_bar_item& operator= (struct t_gui_bar_item* item_ptr) { + *this = item_ptr; + return *this; + } + + static gui_bar_item search(std::string_view name); + + private: + build_callback m_cb; + }; + + class hook : public std::reference_wrapper { + public: + typedef int (*timer_fn)(const void *, void *, int remaining_calls); + typedef std::function timer_callback; + + hook(long interval, int align_second, int max_calls, timer_callback callback); + explicit hook(struct t_hook* hook); + ~hook(); + + inline operator struct t_hook* () const { return &this->get(); } + inline hook& operator= (struct t_hook* hook_ptr) { + *this = hook_ptr; + return *this; + } + + private: + union { + timer_callback m_timer_cb; + }; + }; +} diff --git a/weechat.ipp b/weechat.ipp new file mode 100644 index 0000000..36ae949 --- /dev/null +++ b/weechat.ipp @@ -0,0 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, version 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "weechat.hh" +#include "plugin.hh" + +namespace weechat { + template + T *config_option::pointer(std::string property) const { + return weechat::config_option_get_pointer(*this, property.data()); + } +}