diff --git a/.drone.yml b/.drone.yml index 09288b1..0dc7080 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,6 +6,11 @@ trigger: event: [push, pull_request, tag] steps: +- name: submodules + image: docker.io/geek1011/nickeltc:1.0 + when: + event: [push, pull_request, tag] + command: ["git", "submodule", "update", "--init", "--recursive"] - name: build image: docker.io/geek1011/nickeltc:1.0 when: @@ -14,6 +19,7 @@ steps: - make clean - make all koboroot - mkdir out && mv KoboRoot.tgz src/libnm.so out/ + depends_on: [submodules] - name: build-NM_UNINSTALL_CONFIGDIR image: docker.io/geek1011/nickeltc:1.0 when: diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3478888..61c7a2c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,6 +9,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v1 + with: + submodules: true - name: Build run: make all koboroot - name: Upload diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1b8ac1a..f56f1e7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -9,6 +9,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v1 + with: + submodules: true - name: Build run: make all koboroot - name: Release diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 75c966b..fbcb8f5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -9,6 +9,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v1 + with: + submodules: true - name: Build run: cd test/syms && go build -o ../../test.syms . - name: Run diff --git a/.gitignore b/.gitignore index f491408..ee450a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,20 @@ # make gitignore - -# KDevelop .kdev4/ -NickelMenu.kdev4 +*.kdev4 .kateconfig - -# VSCode .vscode/ - -# clangd -compile_flags.txt - -# Build artifacts +.idea/ +.clangd/ +.cache/ +compile_commands.json /KoboRoot.tgz /src/libnm.so - -/src/qtplugin.moc -/src/qtplugin.o - -/src/failsafe.o -/src/init.o -/src/config.o -/src/kfmon.o /src/action.o -/src/util.o -/src/dlhook.o -/src/generator.o /src/action_c.o +/src/config.o +/src/generator.o /src/generator_c.o - -/src/menu.o +/src/kfmon.o +/src/util.o /src/action_cc.o +/src/nickelmenu.o diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e9acc5d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "NickelHook"] + path = NickelHook + url = https://github.com/pgaskin/NickelHook.git + branch = master diff --git a/Makefile b/Makefile index 24fa8ca..557dad5 100644 --- a/Makefile +++ b/Makefile @@ -1,126 +1,19 @@ -CROSS_COMPILE = arm-nickel-linux-gnueabihf- -MOC = moc -CC = $(CROSS_COMPILE)gcc -CXX = $(CROSS_COMPILE)g++ -PKG_CONFIG = $(CROSS_COMPILE)pkg-config -STRIP = $(CROSS_COMPILE)strip +include NickelHook/NickelHook.mk -DESTDIR = +override PKGCONF += Qt5Widgets +override LIBRARY := src/libnm.so +override SOURCES += src/action.c src/action_c.c src/action_cc.cc src/config.c src/generator.c src/generator_c.c src/kfmon.c src/nickelmenu.cc src/util.c +override CFLAGS += -Wall -Wextra -Werror -fvisibility=hidden +override CXXFLAGS += -Wall -Wextra -Werror -Wno-missing-field-initializers -isystemlib -fvisibility=hidden -fvisibility-inlines-hidden +override KOBOROOT += res/doc:/mnt/onboard/.adds/nm/doc -ifneq ($(if $(MAKECMDGOALS),$(if $(filter-out clean gitignore install koboroot,$(MAKECMDGOALS)),YES,NO),YES),YES) - $(info -- Skipping configure) -else -PTHREAD_CFLAGS := -pthread -PTHREAD_LIBS := -pthread - -define pkgconf = - $(if $(filter-out undefined,$(origin $(1)_CFLAGS) $(origin $(1)_LIBS)) \ - ,$(info -- Using provided CFLAGS and LIBS for $(2)) \ - ,$(if $(shell $(PKG_CONFIG) --exists $(2) >/dev/null 2>/dev/null && echo y) \ - ,$(info -- Found $(2) ($(shell $(PKG_CONFIG) --modversion $(2))) with pkg-config) \ - $(eval $(1)_CFLAGS := $(shell $(PKG_CONFIG) --silence-errors --cflags $(2))) \ - $(eval $(1)_LIBS := $(shell $(PKG_CONFIG) --silence-errors --libs $(2))) \ - $(if $(3) \ - ,$(if $(shell $(PKG_CONFIG) $(3) $(2) >/dev/null 2>/dev/null && echo y) \ - ,$(info .. Satisfies constraint $(3)) \ - ,$(info .. Does not satisfy constraint $(3)) \ - $(error Dependencies do not satisfy constraints)) \ - ,) \ - ,$(info -- Could not automatically detect $(2) with pkg-config. Please specify $(1)_CFLAGS and/or $(1)_LIBS manually) \ - $(error Missing dependencies))) -endef - -$(call pkgconf,QT5CORE,Qt5Core) -$(call pkgconf,QT5WIDGETS,Qt5Widgets) - -CFLAGS ?= -O2 -march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard -mthumb -CXXFLAGS ?= -O2 -march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard -mthumb -LDFLAGS ?= -Wl,--as-needed - -override CFLAGS += -std=gnu11 -Wall -Wextra -Werror -override CXXFLAGS += -std=gnu++11 -Wall -Wextra -Werror -override LDFLAGS += -Wl,--no-undefined -Wl,-rpath,/usr/local/Kobo -Wl,-rpath,/usr/local/Qt-5.2.1-arm/lib - -NM_VERSION := $(shell git describe --tags --always --dirty) -# Only use it if we got something useful out of git describe... -ifdef NM_VERSION - override CPPFLAGS += -DNM_VERSION='"$(NM_VERSION)"' -endif +override SKIPCONFIGURE += strip +strip: + $(STRIP) --strip-unneeded src/libnm.so +.PHONY: strip ifeq ($(NM_UNINSTALL_CONFIGDIR),1) - $(info -- NM_UNINSTALL_CONFIGDIR... enabled) - override CPPFLAGS += -DNM_UNINSTALL_CONFIGDIR -else - $(info -- NM_UNINSTALL_CONFIGDIR... disabled) -endif +override CPPFLAGS += -DNM_UNINSTALL_CONFIGDIR endif -define GITIGNORE_HEAD -# make gitignore - -# KDevelop -.kdev4/ -NickelMenu.kdev4 -.kateconfig - -# VSCode -.vscode/ - -# clangd -compile_flags.txt - -# Build artifacts -endef -export GITIGNORE_HEAD - -all: src/libnm.so - -strip: src/libnm.so - $(STRIP) --strip-unneeded $(CURDIR)/src/libnm.so - -clean: - rm -f $(GENERATED) - -gitignore: - echo "$${GITIGNORE_HEAD}" > .gitignore - echo '$(GENERATED)' | \ - sed 's/ /\n/g' | \ - sed 's/^./\/&/' >> .gitignore - -install: - install -Dm644 src/libnm.so $(DESTDIR)/usr/local/Kobo/imageformats/libnm.so - install -Dm644 res/doc $(DESTDIR)/mnt/onboard/.adds/nm/doc - -koboroot: - tar cvzf KoboRoot.tgz --show-transformed --owner=root --group=root --mode="u=rwX,go=rX" --transform="s,src/libnm.so,./usr/local/Kobo/imageformats/libnm.so," --transform="s,res/doc,./mnt/onboard/.adds/nm/doc," src/libnm.so res/doc - -.PHONY: all strip clean gitignore install koboroot -override GENERATED += KoboRoot.tgz - -src/libnm.so: override CFLAGS += $(PTHREAD_CFLAGS) -fvisibility=hidden -fPIC -src/libnm.so: override CXXFLAGS += $(PTHREAD_CFLAGS) $(QT5CORE_CFLAGS) $(QT5WIDGETS_CFLAGS) -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -src/libnm.so: override LDFLAGS += $(PTHREAD_LIBS) $(QT5CORE_LIBS) $(QT5WIDGETS_LIBS) -ldl -Wl,-soname,libnm.so -src/libnm.so: src/qtplugin.o src/init.o src/config.o src/dlhook.o src/failsafe.o src/menu.o src/kfmon.o src/action.o src/action_c.o src/action_cc.o src/generator.o src/generator_c.o src/util.o - -override LIBRARIES += src/libnm.so -override MOCS += src/qtplugin.moc - -define patw = - $(foreach dir,src res,$(dir)/*$(1)) -endef -define rpatw = - $(patsubst %$(1),%$(2),$(foreach w,$(call patw,$(1)),$(wildcard $(w)))) -endef - -$(LIBRARIES): src/%.so: - $(CXX) $(CPPFLAGS) $(CXXFLAGS) -shared -o $@ $^ $(LDFLAGS) -$(MOCS): %.moc: %.h - $(MOC) $< -o $@ -$(patsubst %.moc,%.o,$(MOCS)): %.o: %.moc - $(CXX) -xc++ $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ -$(call rpatw,.c,.o): %.o: %.c - $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ -$(call rpatw,.cc,.o): %.o: %.cc - $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ - -override GENERATED += $(LIBRARIES) $(MOCS) $(patsubst %.moc,%.o,$(MOCS)) $(call rpatw,.c,.o) $(call rpatw,.cc,.o) +include NickelHook/NickelHook.mk diff --git a/NickelHook b/NickelHook new file mode 160000 index 0000000..98931ee --- /dev/null +++ b/NickelHook @@ -0,0 +1 @@ +Subproject commit 98931eee9ce1624286b8eecca9411ef3c1875ca6 diff --git a/src/action_cc.cc b/src/action_cc.cc index c84de43..2344bbc 100644 --- a/src/action_cc.cc +++ b/src/action_cc.cc @@ -310,7 +310,6 @@ NM_ACTION_(nickel_setting) { } NM_ACTION_(nickel_extras) { - const char* mimetype; if (strchr(arg, '/')) mimetype = arg; else if (!strcmp(arg, "unblock_it")) mimetype = "application/x-games-RushHour"; @@ -330,7 +329,6 @@ NM_ACTION_(nickel_extras) { } NM_ACTION_(nickel_browser) { - bool modal; QUrl *url; QString *css; @@ -494,12 +492,11 @@ NM_ACTION_(power) { } NM_ACTION_(nickel_wifi) { - //libnickel 4.6 * _ZN23WirelessWorkflowManager14sharedInstanceEv WirelessWorkflowManager *(*WirelessWorkflowManager_sharedInstance)(); reinterpret_cast(WirelessWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN23WirelessWorkflowManager14sharedInstanceEv"); NM_CHECK(nullptr, WirelessWorkflowManager_sharedInstance, "could not dlsym WirelessWorkflowManager::sharedInstance"); - + WirelessWorkflowManager *wfm = WirelessWorkflowManager_sharedInstance(); NM_CHECK(nullptr, wfm, "could not get shared wireless manager pointer"); @@ -578,7 +575,6 @@ NM_ACTION_(cmd_spawn) { } NM_ACTION_(cmd_output) { - // split the timeout into timeout, put the command into cmd char *tmp = strdup(arg); char *cmd = tmp; diff --git a/src/config.c b/src/config.c index 616d6d8..b40648c 100644 --- a/src/config.c +++ b/src/config.c @@ -13,7 +13,6 @@ #include "action.h" #include "config.h" #include "generator.h" -#include "menu.h" #include "util.h" struct nm_config_file_t { @@ -22,7 +21,6 @@ struct nm_config_file_t { nm_config_file_t *next; }; - // nm_config_files_filter skips special files, including: // - dotfiles // - vim: .*~, .*.s?? (unix only, usually swp or swo), *.swp, *.swo @@ -671,3 +669,118 @@ void nm_config_free(nm_config_t *cfg) { cfg = n; } } + +// note: not thread safe +static nm_config_file_t *nm_global_menu_config_files = NULL; // updated in-place by nm_global_config_update +static nm_config_t *nm_global_menu_config = NULL; // updated by nm_global_config_update, replaced by nm_global_config_replace, NULL on error +static nm_menu_item_t **nm_global_menu_config_items = NULL; // updated by nm_global_config_replace to an error message or the items from nm_global_menu_config +static size_t nm_global_menu_config_n = 0; // ^ +static int nm_global_menu_config_rev = -1; // incremented by nm_global_config_update whenever the config items change for any reason + +nm_menu_item_t **nm_global_config_items(size_t *n_out) { + if (n_out) + *n_out = nm_global_menu_config_n; + return nm_global_menu_config_items; +} + +static void nm_global_config_replace(nm_config_t *cfg, const char *err) { + if (nm_global_menu_config_n) + nm_global_menu_config_n = 0; + + if (nm_global_menu_config_items) { + free(nm_global_menu_config_items); + nm_global_menu_config_items = NULL; + } + + if (nm_global_menu_config) { + nm_config_free(nm_global_menu_config); + nm_global_menu_config = NULL; + } + + if (err && cfg) + nm_config_free(cfg); + + // this isn't strictly necessary, but we should always try to reparse it + // every time just in case the error was temporary + if (err && nm_global_menu_config_files) { + nm_config_files_free(nm_global_menu_config_files); + nm_global_menu_config_files = NULL; + } + + if (err) { + nm_global_menu_config_n = 1; + nm_global_menu_config_items = calloc(nm_global_menu_config_n, sizeof(nm_menu_item_t*)); + nm_global_menu_config_items[0] = calloc(1, sizeof(nm_menu_item_t)); + nm_global_menu_config_items[0]->loc = NM_MENU_LOCATION_MAIN_MENU; + nm_global_menu_config_items[0]->lbl = strdup("Config Error"); + nm_global_menu_config_items[0]->action = calloc(1, sizeof(nm_menu_action_t)); + nm_global_menu_config_items[0]->action->arg = strdup(err); + nm_global_menu_config_items[0]->action->act = NM_ACTION(dbg_msg); + nm_global_menu_config_items[0]->action->on_failure = true; + nm_global_menu_config_items[0]->action->on_success = true; + return; + } + + nm_global_menu_config = cfg; + nm_global_menu_config_items = nm_config_get_menu(cfg, &nm_global_menu_config_n); + if (!nm_global_menu_config_items) + NM_LOG("could not allocate memory"); +} + +int nm_global_config_update() { + NM_LOG("global: scanning for config files"); + int state = nm_config_files_update(&nm_global_menu_config_files); + if (state == -1) { + const char *err = nm_err(); + NM_LOG("... error: %s", err); + NM_LOG("global: freeing old config and replacing with error item"); + nm_global_config_replace(NULL, err); + nm_global_menu_config_rev++; + NM_ERR_RET(nm_global_menu_config_rev, "scan for config files: %s", err); + } + NM_LOG("global:%s changes detected", state == 0 ? "" : " no"); + + if (state == 0) { + NM_LOG("global: parsing new config"); + nm_config_t *cfg = nm_config_parse(nm_global_menu_config_files); + if (!cfg) { + const char *err = nm_err(); + NM_LOG("... error: %s", err); + NM_LOG("global: freeing old config and replacing with error item"); + nm_global_config_replace(NULL, err); + nm_global_menu_config_rev++; + NM_ERR_RET(nm_global_menu_config_rev, "parse config files: %s", err); + } + + NM_LOG("global: config updated, freeing old config and replacing with new one"); + nm_global_config_replace(cfg, NULL); + nm_global_menu_config_rev++; + NM_LOG("global: done swapping config"); + } + + NM_LOG("global: running generators"); + bool g_updated = nm_config_generate(nm_global_menu_config, false); + NM_LOG("global:%s generators updated", g_updated ? "" : " no"); + + if (g_updated) { + NM_LOG("global: generators updated, freeing old items and replacing with new ones"); + + if (nm_global_menu_config_n) + nm_global_menu_config_n = 0; + + if (nm_global_menu_config_items) { + free(nm_global_menu_config_items); + nm_global_menu_config_items = NULL; + } + + nm_global_menu_config_items = nm_config_get_menu(nm_global_menu_config, &nm_global_menu_config_n); + if (!nm_global_menu_config_items) + NM_LOG("could not allocate memory"); + + nm_global_menu_config_rev++; + NM_LOG("done replacing items"); + } + + nm_err_set(NULL); + return nm_global_menu_config_rev; +} diff --git a/src/config.h b/src/config.h index 5e3b5a8..1a38990 100644 --- a/src/config.h +++ b/src/config.h @@ -5,7 +5,28 @@ extern "C" { #endif #include -#include "menu.h" +#include + +#include "action.h" + +typedef enum { + NM_MENU_LOCATION_MAIN_MENU = 1, + NM_MENU_LOCATION_READER_MENU = 2, +} nm_menu_location_t; + +typedef struct nm_menu_action_t { + char *arg; + bool on_success; + bool on_failure; + nm_action_fn_t act; // can block, must return zero on success, nonzero with nm_err set on error + struct nm_menu_action_t *next; +} nm_menu_action_t; + +typedef struct { + nm_menu_location_t loc; + char *lbl; + nm_menu_action_t *action; +} nm_menu_item_t; #ifndef NM_CONFIG_DIR #define NM_CONFIG_DIR "/mnt/onboard/.adds/nm" @@ -52,6 +73,21 @@ nm_menu_item_t **nm_config_get_menu(nm_config_t *cfg, size_t *n_out); // nm_config_free frees all allocated memory. void nm_config_free(nm_config_t *cfg); +// nm_global_config_update updates and regenerates the config if needed. If the +// menu items changed (i.e. the old items aren't valid anymore), the revision +// will be incremented and returned (even if there was an error). On error, +// nm_err is set, and otherwise, it is cleared. +int nm_global_config_update(); + +// nm_global_config_items returns an array of pointers with the current menu +// items (the pointer and the items it points to will remain valid until the +// next time nm_global_config_update is called). The number of items is stored +// in the variable pointed to by n_out. If an error ocurred during the last time +// nm_global_config_update was called, it is returned as a "Config Error" menu +// item. If nm_global_config_update has never been called successfully before, +// NULL is returned and n_out is set to 0. +nm_menu_item_t **nm_global_config_items(size_t *n_out); + #ifdef __cplusplus } #endif diff --git a/src/dlhook.c b/src/dlhook.c deleted file mode 100644 index 7f8bf5d..0000000 --- a/src/dlhook.c +++ /dev/null @@ -1,114 +0,0 @@ -#define _GNU_SOURCE // dladdr, dladdr1, dlinfo, Dl_info -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dlhook.h" -#include "util.h" - -#ifndef ELFW -#define ELFW(type) _ElfW(ELF, __ELF_NATIVE_CLASS, type) -#endif - -#if __x86_64__ -#define R_JUMP_SLOT R_X86_64_JUMP_SLOT -#else -#if __arm__ -#define R_JUMP_SLOT R_ARM_JUMP_SLOT -#else -#error Please define the architecture-specific R_*_JUMP_SLOT type. -#endif -#endif - -// prevent GCC from giving us warnings everywhere about the format specifiers for the ELF typedefs -#pragma GCC diagnostic ignored "-Wformat" - -void *nm_dlhook(void *handle, const char *symname, void *target) { - NM_CHECK(NULL, handle && symname && target, "BUG: required arguments are null"); - - // the link_map conveniently gives use the base address without /proc/maps, and it gives us a pointer to dyn - struct link_map *lm; - NM_CHECK(NULL, !dlinfo(handle, RTLD_DI_LINKMAP, &lm), "could not get link_map for lib"); - NM_LOG("lib %s is mapped at %lx", lm->l_name, lm->l_addr); - - // stuff extracted from DT_DYNAMIC - struct { - bool plt_is_rela; - union { - ElfW(Addr) _; - ElfW(Rel) *rel; - ElfW(Rela) *rela; - } plt; - ElfW(Xword) plt_sz; - ElfW(Xword) plt_ent_sz; - ElfW(Sym) *sym; - const char *str; - } dyn = {0}; - - // parse DT_DYNAMIC - for (size_t i = 0; lm->l_ld[i].d_tag != DT_NULL; i++) { - switch (lm->l_ld[i].d_tag) { - case DT_PLTREL: dyn.plt_is_rela = lm->l_ld[i].d_un.d_val == DT_RELA; break; // .rel.plt - is Rel or Rela - case DT_JMPREL: dyn.plt._ = lm->l_ld[i].d_un.d_ptr; break; // .rel.plt - offset from base - case DT_PLTRELSZ: dyn.plt_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - size in bytes - case DT_RELENT: if (!dyn.plt_ent_sz) dyn.plt_ent_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - entry size if Rel - case DT_RELAENT: if (!dyn.plt_ent_sz) dyn.plt_ent_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - entry size if Rela - case DT_SYMTAB: dyn.sym = (ElfW(Sym)*)(lm->l_ld[i].d_un.d_val); break; // .dynsym - offset - case DT_STRTAB: dyn.str = (const char*)(lm->l_ld[i].d_un.d_val); break; // .dynstr - offset - } - } - NM_LOG("DT_DYNAMIC: plt_is_rela=%d plt=%p plt_sz=%lu plt_ent_sz=%lu sym=%p str=%p", dyn.plt_is_rela, (void*)(dyn.plt)._, dyn.plt_sz, dyn.plt_ent_sz, dyn.sym, dyn.str); - NM_CHECK(NULL, dyn.plt_ent_sz, "plt_ent_sz is zero"); - NM_CHECK(NULL, dyn.plt_sz%dyn.plt_ent_sz == 0, ".rel.plt length is not a multiple of plt_ent_sz"); - NM_CHECK(NULL, (dyn.plt_is_rela ? sizeof(*dyn.plt.rela) : sizeof(*dyn.plt.rel)) == dyn.plt_ent_sz, "size mismatch (%lu != %lu)", dyn.plt_is_rela ? sizeof(*dyn.plt.rela) : sizeof(*dyn.plt.rel), dyn.plt_ent_sz); - - // parse the dynamic symbol table, resolve symbols to relocations, then GOT entries - for (size_t i = 0; i < dyn.plt_sz/dyn.plt_ent_sz; i++) { - // note: Rela is a superset of Rel - ElfW(Rel) *rel = dyn.plt_is_rela - ? (ElfW(Rel)*)(&dyn.plt.rela[i]) - : &dyn.plt.rel[i]; - NM_CHECK(NULL, ELFW(R_TYPE)(rel->r_info) == R_JUMP_SLOT, "not a jump slot relocation (R_TYPE=%lu)", ELFW(R_TYPE)(rel->r_info)); - - ElfW(Sym) *sym = &dyn.sym[ELFW(R_SYM)(rel->r_info)]; - const char *str = &dyn.str[sym->st_name]; - if (strcmp(str, symname)) - continue; - - void **gotoff = (void**)(lm->l_addr + rel->r_offset); - NM_LOG("found symbol %s (gotoff=%p [mapped=%p])", str, (void*)(rel->r_offset), gotoff); - - NM_CHECK(NULL, ELFW(ST_TYPE)(sym->st_info) != STT_GNU_IFUNC, "STT_GNU_IFUNC not implemented (gotoff=%p)", (void*)(rel->r_offset)); - NM_CHECK(NULL, ELFW(ST_TYPE)(sym->st_info) == STT_FUNC, "not a function symbol (ST_TYPE=%d) (gotoff=%p)", ELFW(ST_TYPE)(sym->st_info), (void*)(rel->r_offset)); - NM_CHECK(NULL, ELFW(ST_BIND)(sym->st_info) == STB_GLOBAL, "not a globally bound symbol (ST_BIND=%d) (gotoff=%p)", ELFW(ST_BIND)(sym->st_info), (void*)(rel->r_offset)); - - NM_LOG("ensuring the symbol is loaded (to get the original address)"); - void *orig = dlsym(handle, symname); - NM_CHECK(NULL, orig, "could not dlsym symbol"); - - // remove memory protection (to bypass RELRO if it is enabled) - // note: this doesn't seem to be used on the Kobo, but we might as well stay on the safe side (plus, I test this on my local machine too) - // note: the only way to read the current memory protection is to parse /proc/maps, but there's no harm in unprotecting it again if it's not protected - // note: we won't put it back afterwards, as if full RELRO (i.e. RTLD_NOW) wasn't enabled, it would cause segfaults when resolving symbols later on - NM_LOG("removing memory protection"); - long pagesize = sysconf(_SC_PAGESIZE); - NM_CHECK(NULL, pagesize != -1, "could not get memory page size"); - void *gotpage = (void*)((size_t)(gotoff) & ~(pagesize-1)); - NM_CHECK(NULL, !mprotect(gotpage, pagesize, PROT_READ|PROT_WRITE), "could not set memory protection of page %p containing %p to PROT_READ|PROT_WRITE", gotpage, gotoff); - - // replace the target offset - NM_LOG("patching symbol"); - //void *orig = *gotoff; - *gotoff = target; - NM_LOG("successfully patched symbol %s (orig=%p, new=%p)", str, orig, target); - return orig; - } - - NM_ERR_SET("could not find symbol"); - return 0; -} diff --git a/src/dlhook.h b/src/dlhook.h deleted file mode 100644 index d166f78..0000000 --- a/src/dlhook.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef NM_DLHOOK_H -#define NM_DLHOOK_H -#ifdef __cplusplus -extern "C" { -#endif - -#include "util.h" - -// nm_dlhook takes a lib handle from dlopen and redirects the specified symbol -// to another, returning a pointer to the original one. Only calls from within -// that library itself are affected (because it replaces that library's GOT). -// This function requires glibc and Linux. It should work on any architecture, -// and it should be resilient to most errors. If it fails, NULL is returned and -// nm_err is set. -NM_PRIVATE void *nm_dlhook(void *handle, const char *symname, void *target); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/failsafe.c b/src/failsafe.c deleted file mode 100644 index 1bf8669..0000000 --- a/src/failsafe.c +++ /dev/null @@ -1,78 +0,0 @@ -#define _GNU_SOURCE // asprintf, Dl_info, dladdr -#include -#include -#include -#include -#include -#include -#include - -#include "failsafe.h" -#include "util.h" - -struct nm_failsafe_t { - char *orig; - char *tmp; - int delay; -}; - -nm_failsafe_t *nm_failsafe_create() { - NM_LOG("failsafe: allocating memory"); - nm_failsafe_t *fs; - NM_CHECK(NULL, (fs = calloc(1, sizeof(nm_failsafe_t))), "could not allocate memory"); - - NM_LOG("failsafe: finding filenames"); - Dl_info info; - NM_CHECK(NULL, dladdr(nm_failsafe_create, &info), "could not find own path"); - NM_CHECK(NULL, info.dli_fname, "dladdr did not return a filename"); - char *d = strrchr(info.dli_fname, '.'); - NM_CHECK(NULL, !(d && !strcmp(d, ".failsafe")), "lib was loaded from the failsafe for some reason"); - NM_CHECK(NULL, (fs->orig = realpath(info.dli_fname, NULL)), "could not resolve %s", info.dli_fname); - NM_CHECK(NULL, asprintf(&fs->tmp, "%s.failsafe", fs->orig) != -1, "could not generate temp filename"); - - NM_LOG("failsafe: ensuring own lib remains in memory even if it is dlclosed after being loaded with a dlopen"); - NM_CHECK(NULL, dlopen(fs->orig, RTLD_LAZY|RTLD_NODELETE), "could not dlopen self"); - - NM_LOG("failsafe: renaming %s to %s", fs->orig, fs->tmp); - NM_CHECK(NULL, !rename(fs->orig, fs->tmp), "could not rename lib"); - - return fs; -} - -static void *_nm_failsafe_destroy(void* _fs) { - nm_failsafe_t *fs = (nm_failsafe_t*)(_fs); - - NM_LOG("failsafe: restoring after %d seconds", fs->delay); - sleep(fs->delay); - - NM_LOG("failsafe: renaming %s to %s", fs->tmp, fs->orig); - if (rename(fs->tmp, fs->orig)) - NM_LOG("error: could not rename lib"); - - NM_LOG("failsafe: freeing memory"); - free(fs->orig); - free(fs->tmp); - free(fs); - - return NULL; -} - -void nm_failsafe_destroy(nm_failsafe_t *fs, int delay) { - fs->delay = delay; - - NM_LOG("failsafe: scheduling restore"); - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_t t; - pthread_create(&t, &attr, _nm_failsafe_destroy, fs); - const char name[16] = "nm_failsafe"; - pthread_setname_np(t, name); - pthread_attr_destroy(&attr); -} - -void nm_failsafe_uninstall(nm_failsafe_t *fs) { - NM_LOG("failsafe: deleting %s", fs->tmp); - unlink(fs->tmp); -} diff --git a/src/failsafe.h b/src/failsafe.h deleted file mode 100644 index d5e7040..0000000 --- a/src/failsafe.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef NM_FAILSAFE_H -#define NM_FAILSAFE_H -#ifdef __cplusplus -extern "C" { -#endif - -#include "util.h" - -// nm_failsafe_t is a failsafe mechanism for injected shared libraries. It -// works by moving it to a temporary file (so it won't get loaded the next time) -// and dlopening itself (to prevent it from being unloaded if it is dlclose'd by -// whatever dlopen'd it). When it is disarmed, the library is moved back to its -// original location. -typedef struct nm_failsafe_t nm_failsafe_t; - -// nm_failsafe_create allocates and arms a failsafe mechanism for the currently -// dlopen'd or LD_PRELOAD'd library. On error, NULL is returned and nm_err is -// set. -NM_PRIVATE nm_failsafe_t *nm_failsafe_create(); - -// nm_failsafe_destroy starts a pthread which disarms and frees the failsafe -// after a delay. The nm_failsafe_t must not be used afterwards. -NM_PRIVATE void nm_failsafe_destroy(nm_failsafe_t *fs, int delay); - -// nm_failsafe_uninstall uninstalls the lib. The nm_failsafe_t must not be -// used afterwards. -NM_PRIVATE void nm_failsafe_uninstall(nm_failsafe_t *fs); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/generator.c b/src/generator.c index d4128fa..7197106 100644 --- a/src/generator.c +++ b/src/generator.c @@ -6,8 +6,8 @@ #include #include "action.h" +#include "config.h" #include "generator.h" -#include "menu.h" #include "util.h" nm_menu_item_t **nm_generator_do(nm_generator_t *gen, size_t *sz_out) { diff --git a/src/generator.h b/src/generator.h index a4120f4..ce29e0d 100644 --- a/src/generator.h +++ b/src/generator.h @@ -6,7 +6,7 @@ extern "C" { #include #include -#include "menu.h" +#include "config.h" // nm_generator_fn_t generates menu items. It must return a malloc'd array of // pointers to malloc'd nm_menu_item_t's, and write the number of items to diff --git a/src/generator_c.c b/src/generator_c.c index 0aaee76..6cf6911 100644 --- a/src/generator_c.c +++ b/src/generator_c.c @@ -9,8 +9,8 @@ #include #include +#include "config.h" #include "generator.h" -#include "menu.h" #include "kfmon.h" #include "util.h" diff --git a/src/init.c b/src/init.c deleted file mode 100644 index 4633401..0000000 --- a/src/init.c +++ /dev/null @@ -1,208 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include - -#include "action.h" -#include "config.h" -#include "failsafe.h" -#include "init.h" -#include "menu.h" -#include "util.h" - -__attribute__((constructor)) void nm_init() { - NM_LOG("version: " NM_VERSION); - #ifdef NM_UNINSTALL_CONFIGDIR - NM_LOG("feature: NM_UNINSTALL_CONFIGDIR: true"); - #else - NM_LOG("feature: NM_UNINSTALL_CONFIGDIR: false"); - #endif - NM_LOG("config dir: %s", NM_CONFIG_DIR); - - NM_LOG("init: creating failsafe"); - - nm_failsafe_t *fs; - if (!(fs = nm_failsafe_create())) { - NM_LOG("error: could not create failsafe: %s, stopping", nm_err()); - goto stop; - } - - NM_LOG("init: checking for uninstall flag"); - - if (!access(NM_CONFIG_DIR "/uninstall", F_OK)) { - NM_LOG("init: flag found, uninstalling"); - nm_failsafe_uninstall(fs); - unlink(NM_CONFIG_DIR "/uninstall"); - goto stop; - } - - #ifdef NM_UNINSTALL_CONFIGDIR - NM_LOG("init: NM_UNINSTALL_CONFIGDIR: checking if config dir exists"); - - if (access(NM_CONFIG_DIR, F_OK) && errno == ENOENT) { - NM_LOG("init: config dir does not exist, uninstalling"); - nm_failsafe_uninstall(fs); - goto stop; - } - #endif - - NM_LOG("init: updating config"); - - int rev = nm_global_config_update(); - if (nm_err_peek()) - NM_LOG("init: error parsing config, will show a menu item with the error: %s", nm_err()); - - size_t ntmp = SIZE_MAX; - if (rev == -1) { - NM_LOG("init: no config file changes detected for initial config update (it should always return an error or update), stopping (this is a bug; err should have been returned instead)"); - return; - } else if (!nm_global_config_items(&ntmp)) { - NM_LOG("init: warning: no menu items returned by nm_global_config_items, ignoring for now (this is a bug; it should always have a menu item whether the default, an error, or the actual config)"); - } else if (ntmp == SIZE_MAX) { - NM_LOG("init: warning: no size returned by nm_global_config_items, ignoring for now (this is a bug)"); - } else if (!ntmp) { - NM_LOG("init: warning: size returned by nm_global_config_items is 0, ignoring for now (this is a bug; it should always have a menu item whether the default, an error, or the actual config)"); - } - - NM_LOG("init: opening libnickel"); - - void *libnickel = dlopen("libnickel.so.1.0.0", RTLD_LAZY|RTLD_NODELETE); - if (!libnickel) { - NM_LOG("error: could not dlopen libnickel, stopping"); - goto stop_fs; - } - - NM_LOG("init: hooking libnickel"); - - if (nm_menu_hook(libnickel)) { - NM_LOG("error: could not hook libnickel: %s, stopping", nm_err()); - goto stop_fs; - } - -stop_fs: - NM_LOG("init: destroying failsafe"); - nm_failsafe_destroy(fs, 5); - -stop: - NM_LOG("init: done"); - return; -} - -// note: not thread safe -static nm_config_file_t *nm_global_menu_config_files = NULL; // updated in-place by nm_global_config_update -static nm_config_t *nm_global_menu_config = NULL; // updated by nm_global_config_update, replaced by nm_global_config_replace, NULL on error -static nm_menu_item_t **nm_global_menu_config_items = NULL; // updated by nm_global_config_replace to an error message or the items from nm_global_menu_config -static size_t nm_global_menu_config_n = 0; // ^ -static int nm_global_menu_config_rev = -1; // incremented by nm_global_config_update whenever the config items change for any reason - -nm_menu_item_t **nm_global_config_items(size_t *n_out) { - if (n_out) - *n_out = nm_global_menu_config_n; - return nm_global_menu_config_items; -} - -static void nm_global_config_replace(nm_config_t *cfg, const char *err) { - if (nm_global_menu_config_n) - nm_global_menu_config_n = 0; - - if (nm_global_menu_config_items) { - free(nm_global_menu_config_items); - nm_global_menu_config_items = NULL; - } - - if (nm_global_menu_config) { - nm_config_free(nm_global_menu_config); - nm_global_menu_config = NULL; - } - - if (err && cfg) - nm_config_free(cfg); - - // this isn't strictly necessary, but we should always try to reparse it - // every time just in case the error was temporary - if (err && nm_global_menu_config_files) { - nm_config_files_free(nm_global_menu_config_files); - nm_global_menu_config_files = NULL; - } - - if (err) { - nm_global_menu_config_n = 1; - nm_global_menu_config_items = calloc(nm_global_menu_config_n, sizeof(nm_menu_item_t*)); - nm_global_menu_config_items[0] = calloc(1, sizeof(nm_menu_item_t)); - nm_global_menu_config_items[0]->loc = NM_MENU_LOCATION_MAIN_MENU; - nm_global_menu_config_items[0]->lbl = strdup("Config Error"); - nm_global_menu_config_items[0]->action = calloc(1, sizeof(nm_menu_action_t)); - nm_global_menu_config_items[0]->action->arg = strdup(err); - nm_global_menu_config_items[0]->action->act = NM_ACTION(dbg_msg); - nm_global_menu_config_items[0]->action->on_failure = true; - nm_global_menu_config_items[0]->action->on_success = true; - return; - } - - nm_global_menu_config = cfg; - nm_global_menu_config_items = nm_config_get_menu(cfg, &nm_global_menu_config_n); - if (!nm_global_menu_config_items) - NM_LOG("could not allocate memory"); -} - -int nm_global_config_update() { - NM_LOG("global: scanning for config files"); - int state = nm_config_files_update(&nm_global_menu_config_files); - if (state == -1) { - const char *err = nm_err(); - NM_LOG("... error: %s", err); - NM_LOG("global: freeing old config and replacing with error item"); - nm_global_config_replace(NULL, err); - nm_global_menu_config_rev++; - NM_ERR_RET(nm_global_menu_config_rev, "scan for config files: %s", err); - } - NM_LOG("global:%s changes detected", state == 0 ? "" : " no"); - - if (state == 0) { - NM_LOG("global: parsing new config"); - nm_config_t *cfg = nm_config_parse(nm_global_menu_config_files); - if (!cfg) { - const char *err = nm_err(); - NM_LOG("... error: %s", err); - NM_LOG("global: freeing old config and replacing with error item"); - nm_global_config_replace(NULL, err); - nm_global_menu_config_rev++; - NM_ERR_RET(nm_global_menu_config_rev, "parse config files: %s", err); - } - - NM_LOG("global: config updated, freeing old config and replacing with new one"); - nm_global_config_replace(cfg, NULL); - nm_global_menu_config_rev++; - NM_LOG("global: done swapping config"); - } - - NM_LOG("global: running generators"); - bool g_updated = nm_config_generate(nm_global_menu_config, false); - NM_LOG("global:%s generators updated", g_updated ? "" : " no"); - - if (g_updated) { - NM_LOG("global: generators updated, freeing old items and replacing with new ones"); - - if (nm_global_menu_config_n) - nm_global_menu_config_n = 0; - - if (nm_global_menu_config_items) { - free(nm_global_menu_config_items); - nm_global_menu_config_items = NULL; - } - - nm_global_menu_config_items = nm_config_get_menu(nm_global_menu_config, &nm_global_menu_config_n); - if (!nm_global_menu_config_items) - NM_LOG("could not allocate memory"); - - nm_global_menu_config_rev++; - NM_LOG("done replacing items"); - } - - nm_err_set(NULL); - return nm_global_menu_config_rev; -} diff --git a/src/init.h b/src/init.h deleted file mode 100644 index fd923e7..0000000 --- a/src/init.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef NM_INIT_H -#define NM_INIT_H -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include "menu.h" - -// nm_global_config_update updates and regenerates the config if needed. If the -// menu items changed (i.e. the old items aren't valid anymore), the revision -// will be incremented and returned (even if there was an error). On error, -// nm_err is set, and otherwise, it is cleared. -int nm_global_config_update(); - -// nm_global_config_items returns an array of pointers with the current menu -// items (the pointer and the items it points to will remain valid until the -// next time nm_global_config_update is called). The number of items is stored -// in the variable pointed to by n_out. If an error ocurred during the last time -// nm_global_config_update was called, it is returned as a "Config Error" menu -// item. If nm_global_config_update has never been called successfully before, -// NULL is returned and n_out is set to 0. -nm_menu_item_t **nm_global_config_items(size_t *n_out); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/menu.h b/src/menu.h deleted file mode 100644 index cece8a7..0000000 --- a/src/menu.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef NM_MENU_H -#define NM_MENU_H -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#include "action.h" - -typedef enum { - NM_MENU_LOCATION_MAIN_MENU = 1, - NM_MENU_LOCATION_READER_MENU = 2, -} nm_menu_location_t; - -typedef struct nm_menu_action_t { - char *arg; - bool on_success; - bool on_failure; - nm_action_fn_t act; // can block, must return zero on success, nonzero with nm_err set on error - struct nm_menu_action_t *next; -} nm_menu_action_t; - -typedef struct { - nm_menu_location_t loc; - char *lbl; - nm_menu_action_t *action; -} nm_menu_item_t; - -// nm_menu_hook hooks a dlopen'd libnickel handle. It MUST NOT be called more -// than once. On success, zero is returned. Otherwise, a nonzero value is -// returned and nm_err is set. -int nm_menu_hook(void *libnickel); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/menu.cc b/src/nickelmenu.cc similarity index 72% rename from src/menu.cc rename to src/nickelmenu.cc index 8d38717..2cd8362 100644 --- a/src/menu.cc +++ b/src/nickelmenu.cc @@ -5,11 +5,10 @@ #include #include -#include -#include "dlhook.h" -#include "init.h" -#include "menu.h" +#include + +#include "config.h" #include "util.h" typedef QWidget MenuTextItem; // it's actually a subclass, but we don't need its functionality directly, so we'll stay on the safe side @@ -21,7 +20,6 @@ typedef void MainWindowController; // search menu for an example of this being used). IDK what the last parameter // is for, but I suspect it might be passed to QObject::setObjectName (I haven't // tested it directly, and it's been empty in the menus I've tried so far). -static MenuTextItem* (*AbstractNickelMenuController_createMenuTextItem_orig)(void*, QMenu*, QString const&, bool, bool, QString const&) = NULL; static MenuTextItem* (*AbstractNickelMenuController_createMenuTextItem)(void*, QMenu*, QString const&, bool, bool, QString const&); // AbstractNickelMenuController::createAction finishes adding the action to the @@ -60,42 +58,42 @@ void (*MainWindowController_toast)(MainWindowController*, QString const&, QStrin static void (*LightMenuSeparator_LightMenuSeparator)(void*, QWidget*); static void (*BoldMenuSeparator_BoldMenuSeparator)(void*, QWidget*); -extern "C" int nm_menu_hook(void *libnickel) { - //libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_ - reinterpret_cast(AbstractNickelMenuController_createMenuTextItem) = dlsym(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_"); - //libnickel 4.6 * _ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb - reinterpret_cast(AbstractNickelMenuController_createAction) = dlsym(libnickel, "_ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb"); - //libnickel 4.6 * _ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_ - reinterpret_cast(ConfirmationDialogFactory_showOKDialog) = dlsym(libnickel, "_ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_"); - //libnickel 4.6 * _ZN20MainWindowController14sharedInstanceEv - reinterpret_cast(MainWindowController_sharedInstance) = dlsym(libnickel, "_ZN20MainWindowController14sharedInstanceEv"); - //libnickel 4.6 * _ZN20MainWindowController5toastERK7QStringS2_i - reinterpret_cast(MainWindowController_toast) = dlsym(libnickel, "_ZN20MainWindowController5toastERK7QStringS2_i"); - //libnickel 4.6 * _ZN18LightMenuSeparatorC2EP7QWidget - reinterpret_cast(LightMenuSeparator_LightMenuSeparator) = dlsym(libnickel, "_ZN18LightMenuSeparatorC2EP7QWidget"); - //libnickel 4.6 * _ZN17BoldMenuSeparatorC1EP7QWidget - reinterpret_cast(BoldMenuSeparator_BoldMenuSeparator) = dlsym(libnickel, "_ZN17BoldMenuSeparatorC1EP7QWidget"); +static struct nh_info NickelMenu = (struct nh_info){ + .name = "NickelMenu", + .desc = "Integrated launcher for Nickel.", + .uninstall_flag = NM_CONFIG_DIR "/uninstall", +#ifdef NM_UNINSTALL_CONFIGDIR + .uninstall_xflag = NM_CONFIG_DIR, +#else + .uninstall_xflag = NULL, +#endif + .failsafe_delay = 2, +}; - NM_CHECK(1, AbstractNickelMenuController_createMenuTextItem, "unsupported firmware: could not find AbstractNickelMenuController::createMenuTextItem(void* _this, QMenu*, QString, bool, bool, QString const&)"); - NM_CHECK(1, AbstractNickelMenuController_createAction, "unsupported firmware: could not find AbstractNickelMenuController::createAction(void* _this, QMenu*, QWidget*, bool, bool, bool)"); - NM_CHECK(1, ConfirmationDialogFactory_showOKDialog, "unsupported firmware: could not find ConfirmationDialogFactory::showOKDialog(String const&, QString const&)"); - NM_CHECK(1, MainWindowController_sharedInstance, "unsupported firmware: could not find MainWindowController::sharedInstance()"); - NM_CHECK(1, MainWindowController_toast, "unsupported firmware: could not find MainWindowController::toast(QString const&, QString const&, int)"); +static struct nh_hook NickelMenuHook[] = { + {.sym = "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", .sym_new = "_nm_menu_hook", .lib = "libnickel.so.1.0.0", .out = nh_symoutptr(AbstractNickelMenuController_createMenuTextItem)}, //libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_ + {0}, +}; - if (!LightMenuSeparator_LightMenuSeparator) - NM_LOG("warning: could not find LightMenuSeparator constructor, falling back to generic separators"); - if (!BoldMenuSeparator_BoldMenuSeparator) - NM_LOG("warning: could not find BoldMenuSeparator constructor, falling back to generic separators"); +static struct nh_dlsym NickelMenuDlsym[] = { + {.name = "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", .out = nh_symoutptr(AbstractNickelMenuController_createMenuTextItem)}, //libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_ + {.name = "_ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb", .out = nh_symoutptr(AbstractNickelMenuController_createAction)}, //libnickel 4.6 * _ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb + {.name = "_ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_", .out = nh_symoutptr(ConfirmationDialogFactory_showOKDialog)}, //libnickel 4.6 * _ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_ + {.name = "_ZN20MainWindowController14sharedInstanceEv", .out = nh_symoutptr(MainWindowController_sharedInstance)}, //libnickel 4.6 * _ZN20MainWindowController14sharedInstanceEv + {.name = "_ZN20MainWindowController5toastERK7QStringS2_i", .out = nh_symoutptr(MainWindowController_toast)}, //libnickel 4.6 * _ZN20MainWindowController5toastERK7QStringS2_i + {.name = "_ZN18LightMenuSeparatorC2EP7QWidget", .out = nh_symoutptr(LightMenuSeparator_LightMenuSeparator)}, //libnickel 4.6 * _ZN18LightMenuSeparatorC2EP7QWidget + {.name = "_ZN17BoldMenuSeparatorC1EP7QWidget", .out = nh_symoutptr(BoldMenuSeparator_BoldMenuSeparator)}, //libnickel 4.6 * _ZN17BoldMenuSeparatorC1EP7QWidget + {0}, +}; - void* nmh = dlsym(RTLD_DEFAULT, "_nm_menu_hook"); - NM_CHECK(1, nmh, "internal error: could not dlsym _nm_menu_hook"); +static int nm_init(); - //libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_ - reinterpret_cast(AbstractNickelMenuController_createMenuTextItem_orig) = nm_dlhook(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", nmh); - NM_CHECK(1, AbstractNickelMenuController_createMenuTextItem_orig, "failed to hook _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_: %s", nm_err()); - - return 0; -} +NickelHook( + .init = &nm_init, + .info = &NickelMenu, + .hook = NickelMenuHook, + .dlsym = NickelMenuDlsym, +) // AbstractNickelMenuController_createAction_before wraps // AbstractNickelMenuController::createAction to use the correct separator for @@ -106,12 +104,39 @@ QAction *AbstractNickelMenuController_createAction_before(QAction *before, nm_me // nm_menu_item_do runs a nm_menu_item_t and must be called from the thread of a // signal handler. -void nm_menu_item_do(nm_menu_item_t *it); +static void nm_menu_item_do(nm_menu_item_t *it); // _nm_menu_inject handles the QMenu::aboutToShow signal and injects menu items. -void _nm_menu_inject(void *nmc, QMenu *menu, nm_menu_location_t loc, int at); +static void _nm_menu_inject(void *nmc, QMenu *menu, nm_menu_location_t loc, int at); -extern "C" NM_PUBLIC MenuTextItem* _nm_menu_hook(void* _this, QMenu* menu, QString const& label, bool checkable, bool checked, QString const& thingy) { +static int nm_init() { + #ifdef NM_UNINSTALL_CONFIGDIR + NM_LOG("feature: NM_UNINSTALL_CONFIGDIR: true"); + #else + NM_LOG("feature: NM_UNINSTALL_CONFIGDIR: false"); + #endif + + NM_LOG("updating config"); + + int rev = nm_global_config_update(); + if (nm_err_peek()) + NM_LOG("... warning: error parsing config, will show a menu item with the error: %s", nm_err()); + + size_t ntmp = SIZE_MAX; + if (rev == -1) { + NM_LOG("... info: no config file changes detected for initial config update (it should always return an error or update), stopping (this is a bug; err should have been returned instead)"); + } else if (!nm_global_config_items(&ntmp)) { + NM_LOG("... warning: no menu items returned by nm_global_config_items, ignoring for now (this is a bug; it should always have a menu item whether the default, an error, or the actual config)"); + } else if (ntmp == SIZE_MAX) { + NM_LOG("... warning: no size returned by nm_global_config_items, ignoring for now (this is a bug)"); + } else if (!ntmp) { + NM_LOG("... warning: size returned by nm_global_config_items is 0, ignoring for now (this is a bug; it should always have a menu item whether the default, an error, or the actual config)"); + } + + return 0; +} + +extern "C" __attribute__((visibility("default"))) MenuTextItem* _nm_menu_hook(void* _this, QMenu* menu, QString const& label, bool checkable, bool checked, QString const& thingy) { NM_LOG("AbstractNickelMenuController::createMenuTextItem(%p, `%s`, %d, %d, `%s`)", menu, qPrintable(label), checkable, checked, qPrintable(thingy)); QString trmm = QCoreApplication::translate("StatusBarMenuController", "Settings"); @@ -130,7 +155,7 @@ extern "C" NM_PUBLIC MenuTextItem* _nm_menu_hook(void* _this, QMenu* menu, QStri if (loc) QObject::connect(menu, &QMenu::aboutToShow, std::bind(_nm_menu_inject, _this, menu, loc, menu->actions().count())); - return AbstractNickelMenuController_createMenuTextItem_orig(_this, menu, label, checkable, checked, thingy); + return AbstractNickelMenuController_createMenuTextItem(_this, menu, label, checkable, checked, thingy); } void _nm_menu_inject(void *nmc, QMenu *menu, nm_menu_location_t loc, int at) { @@ -185,7 +210,7 @@ void _nm_menu_inject(void *nmc, QMenu *menu, nm_menu_location_t loc, int at) { NM_LOG("adding item '%s'...", it->lbl); - MenuTextItem* item = AbstractNickelMenuController_createMenuTextItem_orig(nmc, menu, QString::fromUtf8(it->lbl), false, false, ""); + MenuTextItem* item = AbstractNickelMenuController_createMenuTextItem(nmc, menu, QString::fromUtf8(it->lbl), false, false, ""); QAction* action = AbstractNickelMenuController_createAction_before(before, loc, i == items_n-1, nmc, menu, item, true, true, true); QObject::connect(action, &QAction::triggered, [it](bool){ @@ -306,3 +331,4 @@ QAction *AbstractNickelMenuController_createAction_before(QAction *before, nm_me return action; } + diff --git a/src/qtplugin.h b/src/qtplugin.h deleted file mode 100644 index 113a7aa..0000000 --- a/src/qtplugin.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifdef __cplusplus -#include -#include -#include - -// we make it a fake image plugin so we can have Qt automatically load it -// without needing extra configuration (e.g. LD_PRELOAD, -plugin arg, etc). - -class NMPlugin : public QImageIOPlugin { - Q_OBJECT - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface") -public: - Capabilities capabilities(QIODevice*, QByteArray const&) const { return 0; }; - QImageIOHandler *create(QIODevice*, QByteArray const& = QByteArray()) const { return 0; }; -}; - -#endif diff --git a/src/util.h b/src/util.h index 92d6b24..9d40e31 100644 --- a/src/util.h +++ b/src/util.h @@ -9,18 +9,7 @@ extern "C" { #include #include -// Fallback version tag -#ifndef NM_VERSION -#define NM_VERSION "dev" -#endif - -#ifndef NM_LOG_NAME -#define NM_LOG_NAME "NickelMenu" -#endif - -// Symbol visibility (to prevent conflicts when reusing parts of NM) -#define NM_PUBLIC __attribute__((visibility("default"))) -#define NM_PRIVATE __attribute__((visibility("hidden"))) +#include // strtrim trims ASCII whitespace in-place (i.e. don't give it a string literal) // from the left/right of the string. @@ -34,22 +23,22 @@ inline char *strtrim(char *s) { } // NM_LOG writes a log message. -#define NM_LOG(fmt, ...) syslog(LOG_DEBUG, ("(" NM_LOG_NAME ") " fmt " (%s:%d)"), ##__VA_ARGS__, __FILE__, __LINE__) +#define NM_LOG(fmt, ...) nh_log(fmt " (%s:%d)", ##__VA_ARGS__, __FILE__, __LINE__) // Error handling (thread-safe): // nm_err returns the current error message and clears the error state. If there // isn't any error set, NULL is returned. The returned string is only valid on // the current thread until nm_err_set is called. -NM_PRIVATE const char *nm_err(); +const char *nm_err(); // nm_err_peek is like nm_err, but doesn't clear the error state. -NM_PRIVATE const char *nm_err_peek(); +const char *nm_err_peek(); // nm_err_set sets the current error message to the specified format string. If // fmt is NULL, the error is cleared. It is safe to use the return value of // nm_err as an argument. If fmt was not NULL, true is returned. -NM_PRIVATE bool nm_err_set(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +bool nm_err_set(const char *fmt, ...) __attribute__((format(printf, 1, 2))); // NM_ERR_SET set is like nm_err_set, but also includes information about the // current file/line. To set it to NULL, use nm_err_set directly.