1
0

Refactored common hook code into new NickelHook library (#64)

This commit is contained in:
Patrick Gaskin
2020-07-27 11:11:58 -04:00
committed by GitHub
parent 2cfb51ac0d
commit b4b86fbb37
24 changed files with 269 additions and 748 deletions

View File

@@ -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:

View File

@@ -9,6 +9,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v1
with:
submodules: true
- name: Build
run: make all koboroot
- name: Upload

View File

@@ -9,6 +9,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v1
with:
submodules: true
- name: Build
run: make all koboroot
- name: Release

View File

@@ -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

33
.gitignore vendored
View File

@@ -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

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "NickelHook"]
path = NickelHook
url = https://github.com/pgaskin/NickelHook.git
branch = master

133
Makefile
View File

@@ -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

1
NickelHook Submodule

Submodule NickelHook added at 98931eee9c

View File

@@ -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<void*&>(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;

View File

@@ -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;
}

View File

@@ -5,7 +5,28 @@ extern "C" {
#endif
#include <stdbool.h>
#include "menu.h"
#include <stddef.h>
#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

View File

@@ -1,114 +0,0 @@
#define _GNU_SOURCE // dladdr, dladdr1, dlinfo, Dl_info
#include <dlfcn.h>
#include <elf.h>
#include <link.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#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;
}

View File

@@ -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

View File

@@ -1,78 +0,0 @@
#define _GNU_SOURCE // asprintf, Dl_info, dladdr
#include <dlfcn.h>
#include <link.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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);
}

View File

@@ -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

View File

@@ -6,8 +6,8 @@
#include <time.h>
#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) {

View File

@@ -6,7 +6,7 @@ extern "C" {
#include <stddef.h>
#include <time.h>
#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

View File

@@ -9,8 +9,8 @@
#include <sys/stat.h>
#include <sys/types.h>
#include "config.h"
#include "generator.h"
#include "menu.h"
#include "kfmon.h"
#include "util.h"

View File

@@ -1,208 +0,0 @@
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <link.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}

View File

@@ -1,28 +0,0 @@
#ifndef NM_INIT_H
#define NM_INIT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#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

View File

@@ -1,39 +0,0 @@
#ifndef NM_MENU_H
#define NM_MENU_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#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

View File

@@ -5,11 +5,10 @@
#include <QWidget>
#include <cstdlib>
#include <dlfcn.h>
#include "dlhook.h"
#include "init.h"
#include "menu.h"
#include <NickelHook.h>
#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<void*&>(AbstractNickelMenuController_createMenuTextItem) = dlsym(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_");
//libnickel 4.6 * _ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb
reinterpret_cast<void*&>(AbstractNickelMenuController_createAction) = dlsym(libnickel, "_ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb");
//libnickel 4.6 * _ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_
reinterpret_cast<void*&>(ConfirmationDialogFactory_showOKDialog) = dlsym(libnickel, "_ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_");
//libnickel 4.6 * _ZN20MainWindowController14sharedInstanceEv
reinterpret_cast<void*&>(MainWindowController_sharedInstance) = dlsym(libnickel, "_ZN20MainWindowController14sharedInstanceEv");
//libnickel 4.6 * _ZN20MainWindowController5toastERK7QStringS2_i
reinterpret_cast<void*&>(MainWindowController_toast) = dlsym(libnickel, "_ZN20MainWindowController5toastERK7QStringS2_i");
//libnickel 4.6 * _ZN18LightMenuSeparatorC2EP7QWidget
reinterpret_cast<void*&>(LightMenuSeparator_LightMenuSeparator) = dlsym(libnickel, "_ZN18LightMenuSeparatorC2EP7QWidget");
//libnickel 4.6 * _ZN17BoldMenuSeparatorC1EP7QWidget
reinterpret_cast<void*&>(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<void*&>(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;
}

View File

@@ -1,17 +0,0 @@
#ifdef __cplusplus
#include <QImageIOHandler>
#include <QObject>
#include <QtPlugin>
// 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

View File

@@ -9,18 +9,7 @@ extern "C" {
#include <string.h>
#include <syslog.h>
// 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 <NickelHook.h>
// 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.