diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..3b8086c --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,15 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((c-mode + (eval . (setq-local gud-gdb-command-name + (string-join `("gdb" + "-ex 'handle SIGPIPE nostop noprint pass'" + ,(concat "--args weechat -a -P 'alias,buflist,exec,irc' -r '/plugin load " + (expand-file-name "xmpp.so" (projectile-project-root)) + "'")) + " "))) + (flycheck-clang-warnings . ("all" "extra" "error-implicit-function-declaration" "no-missing-field-initializers")) + (flycheck-clang-language-standard . "c++17") + (flycheck-checker . c/c++-clang) + (projectile-project-compilation-cmd . "make && (make test || true)"))) diff --git a/.envrc b/.envrc index aeb2bc0..f5f9990 100644 --- a/.envrc +++ b/.envrc @@ -1,66 +1,37 @@ # -*- mode: sh; -*- -# Thanks -export_function() -{ - local name=$1 - local alias_dir=$PWD/.direnv/aliases - mkdir -p "$alias_dir" - PATH_add "$alias_dir" - local target="$alias_dir/$name" - if declare -f "$name" >/dev/null; then - echo "#!$SHELL" > "$target" - declare -f "$name" >> "$target" 2>/dev/null - # Notice that we add shell variables to the function trigger. - echo "$name \$*" >> "$target" - chmod +x "$target" - fi -} -use_guix() -{ - # Recreate a garbage collector root. - gcroots="$HOME/.config/guix/gcroots" - mkdir -p "$gcroots" - gcroot="$gcroots/guix" - if [ -L "$gcroot" ] - then - rm -v "$gcroot" - fi +export CC=gcc CXX=g++ - # Miscellaneous packages. - ENVIRONMENTS=( - weechat # Debug runs - ) +# Miscellaneous packages. +ENVIRONMENTS=( + weechat # Debug runs +) - # Environment packages. - PACKAGES=( - autoconf # Deps with autoreconf - autoconf-archive # Deps with m4 tooling - automake # Deps with automake - libtool # Deps with libtool - make # Makefile and deps with makefiles - cmake # Deps with cmake - gcc-toolchain@11 # Compilation - pkg-config # Deps configuration and configuration of deps deps - patchelf # Fix linkage (guix) - bear # Generate compile_commands.json for language servers - universal-ctags # Generate tags (make tags) - weechat # Weechat includes - libxml2 # Dep (libxml2) - libstrophe # Dep (strophe) - libgcrypt # Dep (gcrypt) - libsignal-protocol-c # Dep (libsignal) - lmdb # Dep (lmdb) - rnp # Dep (rnpgp) - ) - - # Thanks - eval "$(guix environment --search-paths --root="$gcroot" ${ENVIRONMENTS[@]} --ad-hoc ${PACKAGES[@]} "$@")" - - export CC=gcc -} +# Environment packages. +PACKAGES=( + autoconf # Deps with autoreconf + autoconf-archive # Deps with m4 tooling + automake # Deps with automake + libtool # Deps with libtool + make # Deps with makefiles + cmake # Deps with cmake + doctest # Testing + gcc-toolchain@11 # Compilation + pkg-config # Deps configuration and configuration of deps deps + patchelf # Fix linkage (guix) + bear # Generate compile_commands.json for language servers + universal-ctags # Generate tags (make tags) + weechat # Weechat includes + libxml2 # Dep (libxml2) + libstrophe # Dep (strophe) + libgcrypt # Dep (gcrypt) + libsignal-protocol-c # Dep (libsignal) + lmdb lmdbxx # Dep (lmdb) + rnp # Dep (rnpgp) +) use guix \ + ${ENVIRONMENTS[@]} --ad-hoc ${PACKAGES[@]} \ --with-debug-info=weechat\ --with-debug-info=libstrophe\ --with-debug-info=libsignal-protocol-c\ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a495ab2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/diff"] + path = deps/diff + url = https://github.com/kristapsdz/libdiff diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..59e6ea0 --- /dev/null +++ b/Makefile @@ -0,0 +1,116 @@ +ifdef DEBUG + DBGCFLAGS=-fsanitize=address -fsanitize=undefined -fsanitize=leak + DBGLDFLAGS=-lasan -lubsan -llsan +endif + +RM=rm -f +FIND=find + +INCLUDES=-Ilibstrophe \ + $(shell xml2-config --cflags) \ + $(shell pkg-config --cflags librnp-0) \ + $(shell pkg-config --cflags libsignal-protocol-c) +CFLAGS+=$(DBGCFLAGS) \ + -fno-omit-frame-pointer -fPIC \ + -std=gnu99 -gdwarf-4 \ + -Wall -Wextra \ + -Werror-implicit-function-declaration \ + -Wno-missing-field-initializers \ + -D_XOPEN_SOURCE=700 \ + $(INCLUDES) +CPPFLAGS+=$(DBGCFLAGS) \ + -fno-omit-frame-pointer -fPIC \ + -std=c++17 -gdwarf-4 \ + -Wall -Wextra \ + -Wno-missing-field-initializers \ + $(INCLUDES) +LDFLAGS+=$(DBGLDFLAGS) \ + -shared -gdwarf-4 \ + $(DBGCFLAGS) +LDLIBS=-lstrophe \ + -lpthread \ + $(shell xml2-config --libs) \ + $(shell pkg-config --libs librnp-0) \ + $(shell pkg-config --libs libsignal-protocol-c) \ + -lgcrypt \ + -llmdb + +PREFIX ?= /usr/local +LIBDIR ?= $(PREFIX)/lib + +HDRS=plugin.hh +SRCS=plugin.cpp +DEPS=deps/diff/libdiff.a +TSTS=$(patsubst %.cpp,tests/%.cc,$(SRCS)) tests/main.cc +OBJS=$(patsubst %.cpp,.%.o,$(SRCS)) +JOBS=$(patsubst tests/%.cc,tests/.%.o,$(TSTS)) + +all: weechat-xmpp +weechat-xmpp: $(DEPS) xmpp.so + +xmpp.so: $(OBJS) $(DEPS) $(HDRS) + $(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 + +.%.o: %.cpp + @$(CXX) $(CPPFLAGS) -c $< -o $@ + +tests/.%.o: tests/%.cc + @$(CXX) $(CPPFLAGS) -c $< -o $@ + +deps/diff/libdiff.a: + git submodule update --init --recursive + cd deps/diff && ./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) + 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 + +test: tests/run + tests/run + +debug: xmpp.so + env LD_PRELOAD=$(DEBUG) gdb -ex "handle SIGPIPE nostop noprint pass" --args \ + weechat -a -P 'alias,buflist,exec,irc' -r '/plugin load ./xmpp.so' + +depend: .depend + +.depend: $(SRCS) + $(RM) ./.depend + $(CXX) $(CPPFLAGS) -MM $^>>./.depend + +tidy: + $(FIND) . -name "*.o" -delete + +clean: + $(RM) -f $(OBJS) + $(MAKE) -C deps/diff clean || true + git submodule foreach --recursive git clean -xfd || true + git submodule foreach --recursive git reset --hard || true + +distclean: clean + $(RM) *~ .depend + +install: xmpp.so +ifeq ($(shell id -u),0) + mkdir -p $(DESTDIR)$(LIBDIR)/weechat/plugins + cp xmpp.so $(DESTDIR)$(LIBDIR)/weechat/plugins/xmpp.so + chmod 644 $(DESTDIR)$(LIBDIR)/weechat/plugins/xmpp.so +else + mkdir -p ~/.weechat/plugins + cp xmpp.so ~/.weechat/plugins/xmpp.so + chmod 755 ~/.weechat/plugins/xmpp.so +endif + +.PHONY: check + +check: + clang-check --analyze *.c *.cc *.cpp + +include .depend diff --git a/deps/diff b/deps/diff new file mode 160000 index 0000000..aadb3d7 --- /dev/null +++ b/deps/diff @@ -0,0 +1 @@ +Subproject commit aadb3d7fe4dcb4b212c77e4fc6c2599826aeb50a diff --git a/plugin.cpp b/plugin.cpp new file mode 100644 index 0000000..c1b70c7 --- /dev/null +++ b/plugin.cpp @@ -0,0 +1,132 @@ +// 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 "plugin.hh" + +#define WEECHAT_XMPP_PLUGIN_NAME "xmpp" +#define WEECHAT_XMPP_PLUGIN_VERSION "0.2.0" + +namespace c { + extern "C" { +#include +#include +#include +#include + +#include "config.h" +#include "account.h" +#include "connection.h" +#include "command.h" +#include "input.h" +#include "buffer.h" +#include "completion.h" + } + +#define TIMER_INTERVAL_SEC 0.01 + + struct t_hook *weechat_xmpp_process_timer = NULL; + + struct t_gui_bar_item *weechat_xmpp_typing_bar_item = NULL; + + bool weechat_plugin_init() + { + if (!config__init()) + return false; + + config__read(); + + connection__init(); + + command__init(); + + completion__init(); + + weechat_xmpp_process_timer = weechat_hook_timer(TIMER_INTERVAL_SEC * 1000, 0, 0, + &account__timer_cb, + NULL, NULL); + + if (!weechat_bar_search("typing")) + { + weechat_bar_new("typing", "off", "400", "window", "${typing}", + "bottom", "horizontal", "vertical", + "1", "1", "default", "default", "default", "default", + "off", "xmpp_typing"); + } + + weechat_xmpp_typing_bar_item = weechat_bar_item_new("xmpp_typing", + &buffer__typing_bar_cb, + NULL, NULL); + + weechat_hook_signal("input_text_changed", &input__text_changed_cb, NULL, NULL); + + return true; + } + + void weechat_plugin_end() + { + if (weechat_xmpp_typing_bar_item) + weechat_bar_item_remove(weechat_xmpp_typing_bar_item); + + if (weechat_xmpp_process_timer) + weechat_unhook(weechat_xmpp_process_timer); + + config__write(); + + account__disconnect_all(); + + account__free_all(); + + xmpp_shutdown(); + } +} + +namespace weechat { + plugin::plugin() { + } + + plugin::plugin(plugin_ptr ptr) + : plugin_ptr(std::move(ptr)) { + this->m_name = WEECHAT_XMPP_PLUGIN_NAME; + this->m_version = WEECHAT_XMPP_PLUGIN_VERSION; + } + + plugin::plugin(struct t_weechat_plugin *ptr) + : plugin(std::move(weechat::plugin_ptr( + ptr, [this] (struct t_weechat_plugin *) { } + ))) { + } + + bool plugin::init(std::vector) { + weechat_printf(nullptr, "%s: It works!", this->name().data()); + return weechat_plugin_init(); + } + + bool plugin::end() { + weechat_plugin_end(); + return true; + } + + plugin plugin::instance; +} + +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); + + int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[]) + { + weechat::plugin::instance = std::move(weechat::plugin(plugin)); + std::vector args(argv, argv+argc); + return weechat::plugin::instance.init(args) ? WEECHAT_RC_OK : WEECHAT_RC_ERROR; + } + + int weechat_plugin_end(struct t_weechat_plugin *) + { + return weechat::plugin::instance.end() ? WEECHAT_RC_OK : WEECHAT_RC_ERROR; + } +} diff --git a/plugin.hh b/plugin.hh new file mode 100644 index 0000000..5f86adb --- /dev/null +++ b/plugin.hh @@ -0,0 +1,44 @@ +// 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 + +#define weechat_plugin ::weechat::plugin::instance.get() + +namespace weechat { + extern "C" { + //__attribute__((visibility("default"))) + int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[]); + int weechat_plugin_end(struct t_weechat_plugin *plugin); + } + + typedef std::unique_ptr< + struct t_weechat_plugin, + std::function> plugin_ptr; + + class plugin : public plugin_ptr { + public: + plugin(); + plugin(plugin_ptr ptr); + plugin(struct t_weechat_plugin *ptr); + + inline std::string const& name() const { return this->m_name; } + inline std::string const& version() const { return this->m_version; } + + bool init(std::vector args); + bool end(); + + static plugin instance; + + private: + std::string m_name; + std::string m_version; + }; +} diff --git a/tests/main.cc b/tests/main.cc new file mode 100644 index 0000000..af24eeb --- /dev/null +++ b/tests/main.cc @@ -0,0 +1,3 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#include diff --git a/tests/plugin.cc b/tests/plugin.cc new file mode 100644 index 0000000..45e9a1a --- /dev/null +++ b/tests/plugin.cc @@ -0,0 +1,18 @@ +#include +#include + +#include "../plugin.hh" + +TEST_CASE("placeholder") +{ + int argc = 2; + const char *argv[2] = {"a", "b"}; + + SUBCASE("takes no arguments") + { + CHECK(argc != 1); + } + + weechat::plugin c; + CHECK(&c.name() == NULL); +}