1
0

Renamed to NickelMenu

This commit is contained in:
Patrick Gaskin
2020-04-22 21:27:00 -04:00
parent a19ece7cd8
commit a293143fd0
18 changed files with 283 additions and 283 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
# make gitignore
/KoboRoot.tgz
/src/libnmi.so
/src/libnm.so
/src/qtplugin.moc
/src/qtplugin.o

View File

@@ -47,7 +47,7 @@ override CXXFLAGS += -march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard
override LDFLAGS += -Wl,-rpath,/usr/local/Kobo -Wl,-rpath,/usr/local/Qt-5.2.1-arm/lib
endif
all: src/libnmi.so
all: src/libnm.so
clean:
rm -f $(GENERATED)
@@ -59,21 +59,21 @@ gitignore:
sed 's/^./\/&/' >> .gitignore
install:
install -Dm644 src/libnmi.so $(DESTDIR)/usr/local/Kobo/imageformats/libnmi.so
install -Dm644 res/doc $(DESTDIR)/mnt/onboard/.adds/nmi/doc
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/libnmi.so,./usr/local/Kobo/imageformats/libnmi.so," --transform="s,res/doc,./mnt/onboard/.adds/nmi/doc," src/libnmi.so res/doc
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 clean gitignore install koboroot
override GENERATED += KoboRoot.tgz
src/libnmi.so: override CFLAGS += $(PTHREAD_CFLAGS) -fPIC
src/libnmi.so: override CXXFLAGS += $(PTHREAD_CFLAGS) $(QT5CORE_CFLAGS) $(QT5WIDGETS_CFLAGS) -fPIC
src/libnmi.so: override LDFLAGS += $(PTHREAD_LIBS) $(QT5CORE_LIBS) $(QT5WIDGETS_LIBS) -ldl -Wl,-soname,libnmi.so
src/libnmi.so: src/qtplugin.o src/init.o src/config.o src/dlhook.o src/failsafe.o src/menu.o src/action_c.o src/action_cc.o
src/libnm.so: override CFLAGS += $(PTHREAD_CFLAGS) -fPIC
src/libnm.so: override CXXFLAGS += $(PTHREAD_CFLAGS) $(QT5CORE_CFLAGS) $(QT5WIDGETS_CFLAGS) -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/action_c.o src/action_cc.o
override LIBRARIES += src/libnmi.so
override LIBRARIES += src/libnm.so
override MOCS += src/qtplugin.moc
define patw =

10
res/doc
View File

@@ -1,4 +1,4 @@
# nickel-menu-inject (libnmi.so)
# NickelMenu (libnm.so)
#
# This tool injects menu items into Nickel.
#
@@ -7,7 +7,7 @@
# error checking, and a failsafe mechanism which automatically uninstalls it as
# a last resort.
#
# Place your configuration files in this folder (/mnt/onboard/.kobo/.adds/nmi).
# Place your configuration files in this folder (/mnt/onboard/.kobo/.adds/nm).
# They can be named anything, and should consist of multiple lines either
# starting with # for a comment, or in the the following format:
#
@@ -43,7 +43,7 @@
# force_usb_connection - forces a usb connection dialog to be shown
# rescan_books - forces nickel to rescan books (TODO)
#
# For example, you might have a configuration file in KOBOeReader/.adds/nmi/mystuff like:
# For example, you might have a configuration file in KOBOeReader/.adds/nm/mystuff like:
#
# menu_item :main :Show an error :dbg_error :This is an error message!
# menu_item :reader :Invert Screen :nickel_setting :invert
@@ -55,7 +55,7 @@
# which can be viewed over telnet or SSH (the username is root) with the command
# logread.
#
# To uninstall nickel-menu-inject, create a file named KOBOeReader/.adds/nmi/uninstall,
# or manually uninstall it by deleting libnmi.so. You can also uninstall it by
# To uninstall NickelMenu, create a file named KOBOeReader/.adds/nm/uninstall,
# or manually uninstall it by deleting libnm.so. You can also uninstall it by
# triggering the failsafe mechanism by turning your Kobo off within 20 seconds
# of turning it on.

View File

@@ -3,21 +3,21 @@
#include "action_c.h"
#include "util.h"
int nmi_action_dbgsyslog(const char *arg, char **err_out) {
#define NMI_ERR_RET 1
NMI_LOG("dbgsyslog: %s", arg);
NMI_RETURN_OK(0);
#undef NMI_ERR_RET
int nm_action_dbgsyslog(const char *arg, char **err_out) {
#define NM_ERR_RET 1
NM_LOG("dbgsyslog: %s", arg);
NM_RETURN_OK(0);
#undef NM_ERR_RET
}
int nmi_action_dbgerror(const char *arg, char **err_out) {
#define NMI_ERR_RET 1
NMI_RETURN_ERR("%s", arg);
#undef NMI_ERR_RET
int nm_action_dbgerror(const char *arg, char **err_out) {
#define NM_ERR_RET 1
NM_RETURN_ERR("%s", arg);
#undef NM_ERR_RET
}
int nmi_action_kfmon(const char *arg, char **err_out) {
#define NMI_ERR_RET 1
NMI_RETURN_ERR("not implemented yet (arg=%s)", arg); // TODO
#undef NMI_ERR_RET
int nm_action_kfmon(const char *arg, char **err_out) {
#define NM_ERR_RET 1
NM_RETURN_ERR("not implemented yet (arg=%s)", arg); // TODO
#undef NM_ERR_RET
}

View File

@@ -1,12 +1,12 @@
#ifndef NMI_ACTION_C_H
#define NMI_ACTION_C_H
#ifndef NM_ACTION_C_H
#define NM_ACTION_C_H
#ifdef __cplusplus
extern "C" {
#endif
int nmi_action_dbgsyslog(const char *arg, char **err_out);
int nmi_action_dbgerror(const char *arg, char **err_out);
int nmi_action_kfmon(const char *arg, char **err_out);
int nm_action_dbgsyslog(const char *arg, char **err_out);
int nm_action_dbgerror(const char *arg, char **err_out);
int nm_action_kfmon(const char *arg, char **err_out);
#ifdef __cplusplus
}

View File

@@ -13,23 +13,23 @@ typedef void Device;
typedef void Settings;
typedef void PlugWorkflowManager;
extern "C" int nmi_action_nickelsetting(const char *arg, char **err_out) {
#define NMI_ERR_RET 1
extern "C" int nm_action_nickelsetting(const char *arg, char **err_out) {
#define NM_ERR_RET 1
Device *(*Device_getCurrentDevice)();
reinterpret_cast<void*&>(Device_getCurrentDevice) = dlsym(RTLD_DEFAULT, "_ZN6Device16getCurrentDeviceEv");
NMI_ASSERT(Device_getCurrentDevice, "could not dlsym Device::getCurrentDevice");
NM_ASSERT(Device_getCurrentDevice, "could not dlsym Device::getCurrentDevice");
void *(*Settings_Settings)(Settings*, Device*, bool);
reinterpret_cast<void*&>(Settings_Settings) = dlsym(RTLD_DEFAULT, "_ZN8SettingsC2ERK6Deviceb");
NMI_ASSERT(Device_getCurrentDevice, "could not dlsym Settings constructor");
NM_ASSERT(Device_getCurrentDevice, "could not dlsym Settings constructor");
void *(*Settings_SettingsD)(Settings*);
reinterpret_cast<void*&>(Settings_SettingsD) = dlsym(RTLD_DEFAULT, "_ZN8SettingsD2Ev");
NMI_ASSERT(Settings_SettingsD, "could not dlsym Settings destructor");
NM_ASSERT(Settings_SettingsD, "could not dlsym Settings destructor");
Device *dev = Device_getCurrentDevice();
NMI_ASSERT(dev, "could not get shared nickel device pointer");
NM_ASSERT(dev, "could not get shared nickel device pointer");
Settings *settings = alloca(128); // way larger than it is, but better to be safe
Settings_Settings(settings, dev, false);
@@ -54,35 +54,35 @@ extern "C" int nmi_action_nickelsetting(const char *arg, char **err_out) {
#define vtable_target(x) reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(x)+8)
void *Settings_vtable = dlsym(RTLD_DEFAULT, "_ZTV8Settings");
NMI_ASSERT(Settings_vtable, "could not dlsym the vtable for Settings");
NMI_ASSERT(vtable_ptr(settings) == vtable_target(Settings_vtable), "unexpected vtable layout (expected class to start with a pointer to 8 bytes into the vtable)");
NM_ASSERT(Settings_vtable, "could not dlsym the vtable for Settings");
NM_ASSERT(vtable_ptr(settings) == vtable_target(Settings_vtable), "unexpected vtable layout (expected class to start with a pointer to 8 bytes into the vtable)");
if (!strcmp(arg, "invert")) {
void *FeatureSettings_vtable = dlsym(RTLD_DEFAULT, "_ZTV15FeatureSettings");
NMI_ASSERT(FeatureSettings_vtable, "could not dlsym the vtable for FeatureSettings");
NM_ASSERT(FeatureSettings_vtable, "could not dlsym the vtable for FeatureSettings");
vtable_ptr(settings) = vtable_target(FeatureSettings_vtable);
bool (*FeatureSettings_invertScreen)(Settings*);
reinterpret_cast<void*&>(FeatureSettings_invertScreen) = dlsym(RTLD_DEFAULT, "_ZN15FeatureSettings12invertScreenEv");
NMI_ASSERT(FeatureSettings_invertScreen, "could not dlsym FeatureSettings::invertScreen");
NM_ASSERT(FeatureSettings_invertScreen, "could not dlsym FeatureSettings::invertScreen");
bool (*FeatureSettings_setInvertScreen)(Settings*, bool);
reinterpret_cast<void*&>(FeatureSettings_setInvertScreen) = dlsym(RTLD_DEFAULT, "_ZN15FeatureSettings15setInvertScreenEb");
NMI_ASSERT(FeatureSettings_setInvertScreen, "could not dlsym FeatureSettings::setInvertScreen");
NM_ASSERT(FeatureSettings_setInvertScreen, "could not dlsym FeatureSettings::setInvertScreen");
bool v = FeatureSettings_invertScreen(settings);
NMI_LOG("invertScreen = %d", v);
NM_LOG("invertScreen = %d", v);
vtable_ptr(settings) = vtable_target(FeatureSettings_vtable);
FeatureSettings_setInvertScreen(settings, !v);
vtable_ptr(settings) = vtable_target(FeatureSettings_vtable);
NMI_ASSERT(FeatureSettings_invertScreen(settings) == !v, "failed to set setting");
NM_ASSERT(FeatureSettings_invertScreen(settings) == !v, "failed to set setting");
vtable_ptr(settings) = vtable_target(FeatureSettings_vtable);
} else {
// TODO: more settings
Settings_SettingsD(settings);
NMI_RETURN_ERR("unknown setting name '%s'", arg);
NM_RETURN_ERR("unknown setting name '%s'", arg);
}
#undef vtable_ptr
@@ -90,15 +90,15 @@ extern "C" int nmi_action_nickelsetting(const char *arg, char **err_out) {
Settings_SettingsD(settings);
NMI_RETURN_OK(0);
#undef NMI_ERR_RET
NM_RETURN_OK(0);
#undef NM_ERR_RET
}
extern "C" int nmi_action_nickelextras(const char *arg, char **err_out) {
#define NMI_ERR_RET 1
extern "C" int nm_action_nickelextras(const char *arg, char **err_out) {
#define NM_ERR_RET 1
if (!strcmp(arg, "web_browser")) {
NMI_RETURN_ERR("not implemented yet"); // TODO
NM_RETURN_ERR("not implemented yet"); // TODO
}
const char* mimetype;
@@ -108,45 +108,45 @@ extern "C" int nmi_action_nickelextras(const char *arg, char **err_out) {
else if (!strcmp(arg, "solitaire")) mimetype = "application/x-games-Solitaire";
else if (!strcmp(arg, "sudoku")) mimetype = "application/x-games-Sudoku";
else if (!strcmp(arg, "word_scramble")) mimetype = "application/x-games-Boggle";
else NMI_RETURN_ERR("unknown beta feature name or plugin mimetype '%s'", arg);
else NM_RETURN_ERR("unknown beta feature name or plugin mimetype '%s'", arg);
void (*ExtrasPluginLoader_loadPlugin)(const char*);
reinterpret_cast<void*&>(ExtrasPluginLoader_loadPlugin) = dlsym(RTLD_DEFAULT, "_ZN18ExtrasPluginLoader10loadPluginEPKc");
NMI_ASSERT(ExtrasPluginLoader_loadPlugin, "could not dlsym ExtrasPluginLoader::loadPlugin");
NM_ASSERT(ExtrasPluginLoader_loadPlugin, "could not dlsym ExtrasPluginLoader::loadPlugin");
ExtrasPluginLoader_loadPlugin(mimetype);
NMI_RETURN_OK(0);
#undef NMI_ERR_RET
NM_RETURN_OK(0);
#undef NM_ERR_RET
}
extern "C" int nmi_action_nickelmisc(const char *arg, char **err_out) {
#define NMI_ERR_RET 1
extern "C" int nm_action_nickelmisc(const char *arg, char **err_out) {
#define NM_ERR_RET 1
if (!strcmp(arg, "rescan_books")) {
PlugWorkflowManager *(*PlugWorkflowManager_sharedInstance)();
reinterpret_cast<void*&>(PlugWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager14sharedInstanceEv");
NMI_ASSERT(PlugWorkflowManager_sharedInstance, "could not dlsym PlugWorkflowManager::sharedInstance");
NM_ASSERT(PlugWorkflowManager_sharedInstance, "could not dlsym PlugWorkflowManager::sharedInstance");
void (*PlugWorkflowManager_unplugged)(PlugWorkflowManager*);
reinterpret_cast<void*&>(PlugWorkflowManager_unplugged) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager9unpluggedEv");
NMI_ASSERT(PlugWorkflowManager_unplugged, "could not dlsym PlugWorkflowManager::unplugged");
NM_ASSERT(PlugWorkflowManager_unplugged, "could not dlsym PlugWorkflowManager::unplugged");
PlugWorkflowManager *wf = PlugWorkflowManager_sharedInstance();
NMI_ASSERT(wf, "could not get shared PlugWorkflowManager pointer");
NM_ASSERT(wf, "could not get shared PlugWorkflowManager pointer");
PlugWorkflowManager_unplugged(wf);
// TODO: finish this up
NMI_RETURN_ERR("not completely implemented yet");
NM_RETURN_ERR("not completely implemented yet");
} else if (!strcmp(arg, "force_usb_connection")) {
FILE *nhs;
NMI_ASSERT((nhs = fopen("/tmp/nickel-hardware-status", "w")), "could not open nickel hardware status pipe: %s", strerror(errno));
NM_ASSERT((nhs = fopen("/tmp/nickel-hardware-status", "w")), "could not open nickel hardware status pipe: %s", strerror(errno));
const char *msg = "usb plug add";
NMI_ASSERT(fputs(msg, nhs) >= 0, "could not write message '%s' to pipe: %s", msg, strerror(errno));
NM_ASSERT(fputs(msg, nhs) >= 0, "could not write message '%s' to pipe: %s", msg, strerror(errno));
fclose(nhs);
} else {
NMI_RETURN_ERR("unknown action '%s'", arg);
NM_RETURN_ERR("unknown action '%s'", arg);
}
NMI_RETURN_OK(0);
#undef NMI_ERR_RET
NM_RETURN_OK(0);
#undef NM_ERR_RET
}

View File

@@ -1,12 +1,12 @@
#ifndef NMI_ACTION_CC_H
#define NMI_ACTION_CC_H
#ifndef NM_ACTION_CC_H
#define NM_ACTION_CC_H
#ifdef __cplusplus
extern "C" {
#endif
int nmi_action_nickelsetting(const char *arg, char **err_out);
int nmi_action_nickelextras(const char *arg, char **err_out);
int nmi_action_nickelmisc(const char *arg, char **err_out);
int nm_action_nickelsetting(const char *arg, char **err_out);
int nm_action_nickelextras(const char *arg, char **err_out);
int nm_action_nickelmisc(const char *arg, char **err_out);
#ifdef __cplusplus
}

View File

@@ -16,20 +16,20 @@
#include "menu.h"
#include "util.h"
#ifndef NMI_CONFIG_DIR
#define NMI_CONFIG_DIR "/mnt/onboard/.adds/nmi"
#ifndef NM_CONFIG_DIR
#define NM_CONFIG_DIR "/mnt/onboard/.adds/nm"
#endif
typedef enum {
NMI_CONFIG_TYPE_MENU_ITEM = 1,
} nmi_config_type_t;
NM_CONFIG_TYPE_MENU_ITEM = 1,
} nm_config_type_t;
struct nmi_config_t {
nmi_config_type_t type;
struct nm_config_t {
nm_config_type_t type;
union {
nmi_menu_item_t *menu_item;
nm_menu_item_t *menu_item;
} value;
nmi_config_t *next;
nm_config_t *next;
};
// strtrim trims ASCII whitespace in-place (i.e. don't give it a string literal)
@@ -43,53 +43,53 @@ static char *strtrim(char *s){
return a;
}
static void nmi_config_push_menu_item(nmi_config_t **cfg, nmi_menu_item_t *it) {
nmi_config_t *tmp = calloc(1, sizeof(nmi_config_t));
tmp->type = NMI_CONFIG_TYPE_MENU_ITEM;
static void nm_config_push_menu_item(nm_config_t **cfg, nm_menu_item_t *it) {
nm_config_t *tmp = calloc(1, sizeof(nm_config_t));
tmp->type = NM_CONFIG_TYPE_MENU_ITEM;
tmp->value.menu_item = it;
tmp->next = *cfg;
*cfg = tmp;
}
nmi_config_t *nmi_config_parse(char **err_out) {
#define NMI_ERR_RET NULL
NMI_LOG("config: reading config dir %s", NMI_CONFIG_DIR);
nm_config_t *nm_config_parse(char **err_out) {
#define NM_ERR_RET NULL
NM_LOG("config: reading config dir %s", NM_CONFIG_DIR);
// set up the linked list
nmi_config_t *cfg = NULL;
nm_config_t *cfg = NULL;
// open the config dir
DIR *cfgdir;
NMI_ASSERT((cfgdir = opendir(NMI_CONFIG_DIR)), "could not open config dir: %s", strerror(errno));
NM_ASSERT((cfgdir = opendir(NM_CONFIG_DIR)), "could not open config dir: %s", strerror(errno));
// loop over the dirents of the config dir
struct dirent *dirent; errno = 0;
while ((dirent = readdir(cfgdir))) {
char *fn;
NMI_ASSERT(asprintf(&fn, "%s/%s", NMI_CONFIG_DIR, dirent->d_name) != -1, "could not build full path for config file");
NM_ASSERT(asprintf(&fn, "%s/%s", NM_CONFIG_DIR, dirent->d_name) != -1, "could not build full path for config file");
// skip it if it isn't a file
bool reg = dirent->d_type == DT_REG;
if (dirent->d_type == DT_UNKNOWN) {
struct stat statbuf;
NMI_ASSERT(!stat(fn, &statbuf), "could not stat %s", fn);
NM_ASSERT(!stat(fn, &statbuf), "could not stat %s", fn);
reg = S_ISREG(statbuf.st_mode);
}
if (!reg) {
NMI_LOG("config: skipping %s because not a regular file", fn);
NM_LOG("config: skipping %s because not a regular file", fn);
continue;
}
// open the config file
NMI_LOG("config: reading config file %s", fn);
NM_LOG("config: reading config file %s", fn);
FILE *cfgfile;
NMI_ASSERT((cfgfile = fopen(fn, "r")), "could not open file: %s", strerror(errno));
NM_ASSERT((cfgfile = fopen(fn, "r")), "could not open file: %s", strerror(errno));
#define RETERR(fmt, ...) do { \
fclose(cfgfile); \
free(line); \
closedir(cfgdir); \
NMI_RETURN_ERR(fmt, ##__VA_ARGS__); \
NM_RETURN_ERR(fmt, ##__VA_ARGS__); \
} while (0)
// parse each line
@@ -109,13 +109,13 @@ nmi_config_t *nmi_config_parse(char **err_out) {
char *c_typ = strtrim(strsep(&cur, ":"));
if (!strcmp(c_typ, "menu_item")) {
// type: menu_item
nmi_menu_item_t *it = calloc(1, sizeof(nmi_menu_item_t));
nm_menu_item_t *it = calloc(1, sizeof(nm_menu_item_t));
// type: menu_item - field 2: location
char *c_loc = strtrim(strsep(&cur, ":"));
if (!c_loc) RETERR("file %s: line %d: field 2: expected location, got end of line", fn, line_n);
else if (!strcmp(c_loc, "main")) it->loc = NMI_MENU_LOCATION_MAIN_MENU;
else if (!strcmp(c_loc, "reader")) it->loc = NMI_MENU_LOCATION_READER_MENU;
else if (!strcmp(c_loc, "main")) it->loc = NM_MENU_LOCATION_MAIN_MENU;
else if (!strcmp(c_loc, "reader")) it->loc = NM_MENU_LOCATION_READER_MENU;
else RETERR("file %s: line %d: field 2: unknown location '%s'", fn, line_n, c_loc);
// type: menu_item - field 3: label
@@ -126,12 +126,12 @@ nmi_config_t *nmi_config_parse(char **err_out) {
// type: menu_item - field 4: action
char *c_act = strtrim(strsep(&cur, ":"));
if (!c_act) RETERR("file %s: line %d: field 4: expected action, got end of line", fn, line_n);
else if (!strcmp(c_act, "dbg_syslog")) it->act = nmi_action_dbgsyslog;
else if (!strcmp(c_act, "dbg_error")) it->act = nmi_action_dbgerror;
else if (!strcmp(c_act, "kfmon")) it->act = nmi_action_kfmon;
else if (!strcmp(c_act, "nickel_setting")) it->act = nmi_action_nickelsetting;
else if (!strcmp(c_act, "nickel_extras")) it->act = nmi_action_nickelextras;
else if (!strcmp(c_act, "nickel_misc")) it->act = nmi_action_nickelmisc;
else if (!strcmp(c_act, "dbg_syslog")) it->act = nm_action_dbgsyslog;
else if (!strcmp(c_act, "dbg_error")) it->act = nm_action_dbgerror;
else if (!strcmp(c_act, "kfmon")) it->act = nm_action_kfmon;
else if (!strcmp(c_act, "nickel_setting")) it->act = nm_action_nickelsetting;
else if (!strcmp(c_act, "nickel_extras")) it->act = nm_action_nickelextras;
else if (!strcmp(c_act, "nickel_misc")) it->act = nm_action_nickelmisc;
else RETERR("file %s: line %d: field 4: unknown action '%s'", fn, line_n, c_act);
// type: menu_item - field 5: argument
@@ -139,7 +139,7 @@ nmi_config_t *nmi_config_parse(char **err_out) {
if (!c_arg) RETERR("file %s: line %d: field 5: expected argument, got end of line\n", fn, line_n);
else it->arg = strdup(c_arg);
nmi_config_push_menu_item(&cfg, it);
nm_config_push_menu_item(&cfg, it);
} else RETERR("file %s: line %d: field 1: unknown type '%s'", fn, line_n, c_typ);
}
@@ -148,53 +148,53 @@ nmi_config_t *nmi_config_parse(char **err_out) {
fclose(cfgfile);
free(line);
}
NMI_ASSERT(!errno, "could not read config dir: %s", strerror(errno));
NM_ASSERT(!errno, "could not read config dir: %s", strerror(errno));
// close the config dir
closedir(cfgdir);
// add a default entry if none were found
if (!cfg) {
nmi_menu_item_t *it = calloc(1, sizeof(nmi_menu_item_t));
it->loc = NMI_MENU_LOCATION_MAIN_MENU;
it->lbl = strdup("nickel-menu-inject");
it->arg = strdup("See KOBOeReader/.add/nmi/doc for instructions on how to customize this menu.");
it->act = nmi_action_dbgerror;
nmi_config_push_menu_item(&cfg, it);
nm_menu_item_t *it = calloc(1, sizeof(nm_menu_item_t));
it->loc = NM_MENU_LOCATION_MAIN_MENU;
it->lbl = strdup("NickelMenu");
it->arg = strdup("See KOBOeReader/.add/nm/doc for instructions on how to customize this menu.");
it->act = nm_action_dbgerror;
nm_config_push_menu_item(&cfg, it);
}
for (nmi_config_t *cur = cfg; cur; cur = cur->next)
if (cur->type == NMI_CONFIG_TYPE_MENU_ITEM)
NMI_LOG("cfg(NMI_CONFIG_TYPE_MENU_ITEM) : %d:%s:%p:%s", cur->value.menu_item->loc, cur->value.menu_item->lbl, cur->value.menu_item->act, cur->value.menu_item->arg);
for (nm_config_t *cur = cfg; cur; cur = cur->next)
if (cur->type == NM_CONFIG_TYPE_MENU_ITEM)
NM_LOG("cfg(NM_CONFIG_TYPE_MENU_ITEM) : %d:%s:%p:%s", cur->value.menu_item->loc, cur->value.menu_item->lbl, cur->value.menu_item->act, cur->value.menu_item->arg);
// return the head of the list
NMI_RETURN_OK(cfg);
#undef NMI_ERR_RET
NM_RETURN_OK(cfg);
#undef NM_ERR_RET
}
nmi_menu_item_t **nmi_config_get_menu(nmi_config_t *cfg, size_t *n_out) {
nm_menu_item_t **nm_config_get_menu(nm_config_t *cfg, size_t *n_out) {
*n_out = 0;
for (nmi_config_t *cur = cfg; cur; cur = cur->next)
if (cur->type == NMI_CONFIG_TYPE_MENU_ITEM)
for (nm_config_t *cur = cfg; cur; cur = cur->next)
if (cur->type == NM_CONFIG_TYPE_MENU_ITEM)
(*n_out)++;
nmi_menu_item_t **it = calloc(*n_out, sizeof(nmi_menu_item_t*));
nm_menu_item_t **it = calloc(*n_out, sizeof(nm_menu_item_t*));
if (!it)
return NULL;
nmi_menu_item_t **tmp = it;
for (nmi_config_t *cur = cfg; cur; cur = cur->next)
if (cur->type == NMI_CONFIG_TYPE_MENU_ITEM)
nm_menu_item_t **tmp = it;
for (nm_config_t *cur = cfg; cur; cur = cur->next)
if (cur->type == NM_CONFIG_TYPE_MENU_ITEM)
*(tmp++) = cur->value.menu_item;
return it;
}
void nmi_config_free(nmi_config_t *cfg) {
void nm_config_free(nm_config_t *cfg) {
while (cfg) {
nmi_config_t *n = cfg->next;
nm_config_t *n = cfg->next;
if (cfg->type == NMI_CONFIG_TYPE_MENU_ITEM) {
if (cfg->type == NM_CONFIG_TYPE_MENU_ITEM) {
free(cfg->value.menu_item->lbl);
free(cfg->value.menu_item->arg);
free(cfg->value.menu_item);

View File

@@ -1,5 +1,5 @@
#ifndef NMI_CONFIG_H
#define NMI_CONFIG_H
#ifndef NM_CONFIG_H
#define NM_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
@@ -7,20 +7,20 @@ extern "C" {
#include <stdbool.h>
#include "menu.h"
typedef struct nmi_config_t nmi_config_t;
typedef struct nm_config_t nm_config_t;
// nmi_config_parse parses the configuration files in /mnt/onboard/.adds/nmi.
// nm_config_parse parses the configuration files in /mnt/onboard/.adds/nm.
// An error is returned if there are syntax errors, file access errors, or
// invalid action names for menu_item.
nmi_config_t *nmi_config_parse(char **err_out);
nm_config_t *nm_config_parse(char **err_out);
// nmi_config_get_menu gets a malloc'd array of pointers to the menu items
// defined in the config. These pointers will be valid until nmi_config_free is
// nm_config_get_menu gets a malloc'd array of pointers to the menu items
// defined in the config. These pointers will be valid until nm_config_free is
// called.
nmi_menu_item_t **nmi_config_get_menu(nmi_config_t *cfg, size_t *n_out);
nm_menu_item_t **nm_config_get_menu(nm_config_t *cfg, size_t *n_out);
// nmi_config_free frees all allocated memory.
void nmi_config_free(nmi_config_t *cfg);
// nm_config_free frees all allocated memory.
void nm_config_free(nm_config_t *cfg);
#ifdef __cplusplus
}

View File

@@ -28,15 +28,15 @@
// prevent GCC from giving us warnings everywhere about the format specifiers for the ELF typedefs
#pragma GCC diagnostic ignored "-Wformat"
void *nmi_dlhook(void *handle, const char *symname, void *target, char **err_out) {
#define NMI_ERR_RET NULL
void *nm_dlhook(void *handle, const char *symname, void *target, char **err_out) {
#define NM_ERR_RET NULL
NMI_ASSERT(handle && symname && target, "BUG: required arguments are null");
NM_ASSERT(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;
NMI_ASSERT(!dlinfo(handle, RTLD_DI_LINKMAP, &lm), "could not get link_map for lib");
NMI_LOG("lib %s is mapped at %lx", lm->l_name, lm->l_addr);
NM_ASSERT(!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 {
@@ -64,10 +64,10 @@ void *nmi_dlhook(void *handle, const char *symname, void *target, char **err_out
case DT_STRTAB: dyn.str = (const char*)(lm->l_ld[i].d_un.d_val); break; // .dynstr - offset
}
}
NMI_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);
NMI_ASSERT(dyn.plt_ent_sz, "plt_ent_sz is zero");
NMI_ASSERT(dyn.plt_sz%dyn.plt_ent_sz == 0, ".rel.plt length is not a multiple of plt_ent_sz");
NMI_ASSERT((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);
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_ASSERT(dyn.plt_ent_sz, "plt_ent_sz is zero");
NM_ASSERT(dyn.plt_sz%dyn.plt_ent_sz == 0, ".rel.plt length is not a multiple of plt_ent_sz");
NM_ASSERT((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++) {
@@ -75,7 +75,7 @@ void *nmi_dlhook(void *handle, const char *symname, void *target, char **err_out
ElfW(Rel) *rel = dyn.plt_is_rela
? (ElfW(Rel)*)(&dyn.plt.rela[i])
: &dyn.plt.rel[i];
NMI_ASSERT(ELFW(R_TYPE)(rel->r_info) == R_JUMP_SLOT, "not a jump slot relocation (R_TYPE=%lu)", ELFW(R_TYPE)(rel->r_info));
NM_ASSERT(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];
@@ -83,35 +83,35 @@ void *nmi_dlhook(void *handle, const char *symname, void *target, char **err_out
continue;
void **gotoff = (void**)(lm->l_addr + rel->r_offset);
NMI_LOG("found symbol %s (gotoff=%p [mapped=%p])", str, (void*)(rel->r_offset), gotoff);
NM_LOG("found symbol %s (gotoff=%p [mapped=%p])", str, (void*)(rel->r_offset), gotoff);
NMI_ASSERT(ELFW(ST_TYPE)(sym->st_info) != STT_GNU_IFUNC, "STT_GNU_IFUNC not implemented (gotoff=%p)", (void*)(rel->r_offset));
NMI_ASSERT(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));
NMI_ASSERT(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_ASSERT(ELFW(ST_TYPE)(sym->st_info) != STT_GNU_IFUNC, "STT_GNU_IFUNC not implemented (gotoff=%p)", (void*)(rel->r_offset));
NM_ASSERT(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_ASSERT(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));
// TODO: figure out why directly getting the offset from the GOT was broken on ARM, but not x86
NMI_LOG("ensuring the symbol is loaded");
NM_LOG("ensuring the symbol is loaded");
void *orig = dlsym(handle, symname);
NMI_ASSERT(orig, "could not dlsym symbol");
NM_ASSERT(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
NMI_LOG("removing memory protection");
NM_LOG("removing memory protection");
long pagesize = sysconf(_SC_PAGESIZE);
NMI_ASSERT(pagesize != -1, "could not get memory page size");
NM_ASSERT(pagesize != -1, "could not get memory page size");
void *gotpage = (void*)((size_t)(gotoff) & ~(pagesize-1));
NMI_ASSERT(!mprotect(gotpage, pagesize, PROT_READ|PROT_WRITE), "could not set memory protection of page %p containing %p to PROT_READ|PROT_WRITE", gotpage, gotoff);
NM_ASSERT(!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
NMI_LOG("patching symbol");
NM_LOG("patching symbol");
//void *orig = *gotoff;
*gotoff = target;
NMI_LOG("successfully patched symbol %s (orig=%p, new=%p)", str, orig, target);
NMI_RETURN_OK(orig);
NM_LOG("successfully patched symbol %s (orig=%p, new=%p)", str, orig, target);
NM_RETURN_OK(orig);
}
NMI_RETURN_ERR("could not find symbol");
#undef NMI_err_ret
NM_RETURN_ERR("could not find symbol");
#undef NM_err_ret
}

View File

@@ -1,16 +1,16 @@
#ifndef NMI_DLHOOK_H
#define NMI_DLHOOK_H
#ifndef NM_DLHOOK_H
#define NM_DLHOOK_H
#ifdef __cplusplus
extern "C" {
#endif
// nmi_dlhook takes a lib handle from dlopen and redirects the specified symbol
// 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). If
// an error occurs, NULL is returned and if err is a valid pointer, it is set to
// a malloc'd string describing it. This function requires glibc and Linux. It
// should work on any architecture, and it should be resilient to most errors.
void *nmi_dlhook(void *handle, const char *symname, void *target, char **err_out);
void *nm_dlhook(void *handle, const char *symname, void *target, char **err_out);
#ifdef __cplusplus
}

View File

@@ -10,49 +10,49 @@
#include "failsafe.h"
#include "util.h"
struct nmi_failsafe_t {
struct nm_failsafe_t {
char *orig;
char *tmp;
int delay;
};
nmi_failsafe_t *nmi_failsafe_create(char **err_out) {
#define NMI_ERR_RET NULL
nm_failsafe_t *nm_failsafe_create(char **err_out) {
#define NM_ERR_RET NULL
NMI_LOG("failsafe: allocating memory");
nmi_failsafe_t *fs;
NMI_ASSERT((fs = calloc(1, sizeof(nmi_failsafe_t))), "could not allocate memory");
NM_LOG("failsafe: allocating memory");
nm_failsafe_t *fs;
NM_ASSERT((fs = calloc(1, sizeof(nm_failsafe_t))), "could not allocate memory");
NMI_LOG("failsafe: finding filenames");
NM_LOG("failsafe: finding filenames");
Dl_info info;
NMI_ASSERT(dladdr(nmi_failsafe_create, &info), "could not find own path");
NMI_ASSERT(info.dli_fname, "dladdr did not return a filename");
NM_ASSERT(dladdr(nm_failsafe_create, &info), "could not find own path");
NM_ASSERT(info.dli_fname, "dladdr did not return a filename");
char *d = strrchr(info.dli_fname, '.');
NMI_ASSERT(!(d && !strcmp(d, ".failsafe")), "lib was loaded from the failsafe for some reason");
NMI_ASSERT((fs->orig = realpath(info.dli_fname, NULL)), "could not resolve %s", info.dli_fname);
NMI_ASSERT(asprintf(&fs->tmp, "%s.failsafe", fs->orig) != -1, "could not generate temp filename");
NM_ASSERT(!(d && !strcmp(d, ".failsafe")), "lib was loaded from the failsafe for some reason");
NM_ASSERT((fs->orig = realpath(info.dli_fname, NULL)), "could not resolve %s", info.dli_fname);
NM_ASSERT(asprintf(&fs->tmp, "%s.failsafe", fs->orig) != -1, "could not generate temp filename");
NMI_LOG("failsafe: ensuring own lib remains in memory even if it is dlclosed after being loaded with a dlopen");
NMI_ASSERT(dlopen(fs->orig, RTLD_LAZY|RTLD_NODELETE), "could not dlopen self");
NM_LOG("failsafe: ensuring own lib remains in memory even if it is dlclosed after being loaded with a dlopen");
NM_ASSERT(dlopen(fs->orig, RTLD_LAZY|RTLD_NODELETE), "could not dlopen self");
NMI_LOG("failsafe: renaming %s to %s", fs->orig, fs->tmp);
NMI_ASSERT(!rename(fs->orig, fs->tmp), "could not rename lib");
NM_LOG("failsafe: renaming %s to %s", fs->orig, fs->tmp);
NM_ASSERT(!rename(fs->orig, fs->tmp), "could not rename lib");
NMI_RETURN_OK(fs);
#undef NMI_ERR_RET
NM_RETURN_OK(fs);
#undef NM_ERR_RET
}
static void *_nmi_failsafe_destroy(void* _fs) {
nmi_failsafe_t *fs = (nmi_failsafe_t*)(_fs);
static void *_nm_failsafe_destroy(void* _fs) {
nm_failsafe_t *fs = (nm_failsafe_t*)(_fs);
NMI_LOG("failsafe: restoring after %d seconds", fs->delay);
NM_LOG("failsafe: restoring after %d seconds", fs->delay);
sleep(fs->delay);
NMI_LOG("failsafe: renaming %s to %s", fs->tmp, fs->orig);
NM_LOG("failsafe: renaming %s to %s", fs->tmp, fs->orig);
if (rename(fs->tmp, fs->orig))
NMI_LOG("error: could not rename lib");
NM_LOG("error: could not rename lib");
NMI_LOG("failsafe: freeing memory");
NM_LOG("failsafe: freeing memory");
free(fs->orig);
free(fs->tmp);
free(fs);
@@ -60,15 +60,15 @@ static void *_nmi_failsafe_destroy(void* _fs) {
return NULL;
}
void nmi_failsafe_destroy(nmi_failsafe_t *fs, int delay) {
void nm_failsafe_destroy(nm_failsafe_t *fs, int delay) {
fs->delay = delay;
NMI_LOG("failsafe: scheduling restore");
NM_LOG("failsafe: scheduling restore");
pthread_t t;
pthread_create(&t, NULL, _nmi_failsafe_destroy, fs);
pthread_create(&t, NULL, _nm_failsafe_destroy, fs);
}
void nmi_failsafe_uninstall(nmi_failsafe_t *fs) {
NMI_LOG("failsafe: deleting %s", fs->tmp);
void nm_failsafe_uninstall(nm_failsafe_t *fs) {
NM_LOG("failsafe: deleting %s", fs->tmp);
unlink(fs->tmp);
}

View File

@@ -1,27 +1,27 @@
#ifndef NMI_FAILSAFE_H
#define NMI_FAILSAFE_H
#ifndef NM_FAILSAFE_H
#define NM_FAILSAFE_H
#ifdef __cplusplus
extern "C" {
#endif
// nmi_failsafe_t is a failsafe mechanism for injected shared libraries. It
// 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 nmi_failsafe_t nmi_failsafe_t;
typedef struct nm_failsafe_t nm_failsafe_t;
// nmi_failsafe_create allocates and arms a failsafe mechanism for the currently
// nm_failsafe_create allocates and arms a failsafe mechanism for the currently
// dlopen'd or LD_PRELOAD'd library.
nmi_failsafe_t *nmi_failsafe_create(char **err_out);
nm_failsafe_t *nm_failsafe_create(char **err_out);
// nmi_failsafe_destroy starts a pthread which disarms and frees the failsafe
// after a delay. The nmi_failsafe_t must not be used afterwards.
void nmi_failsafe_destroy(nmi_failsafe_t *fs, int delay);
// nm_failsafe_destroy starts a pthread which disarms and frees the failsafe
// after a delay. The nm_failsafe_t must not be used afterwards.
void nm_failsafe_destroy(nm_failsafe_t *fs, int delay);
// nmi_failsafe_uninstall uninstalls the lib. The nmi_failsafe_t must not be
// nm_failsafe_uninstall uninstalls the lib. The nm_failsafe_t must not be
// used afterwards.
void nmi_failsafe_uninstall(nmi_failsafe_t *fs);
void nm_failsafe_uninstall(nm_failsafe_t *fs);
#ifdef __cplusplus
}

View File

@@ -14,71 +14,71 @@
#include "menu.h"
#include "util.h"
__attribute__((constructor)) void nmi_init() {
__attribute__((constructor)) void nm_init() {
// for if it's been loaded with LD_PRELOAD rather than as a Qt plugin
if (strcmp(program_invocation_short_name, "nickel"))
if (!(getenv("LIBNMI_FORCE") && !strcmp(getenv("LIBNMI_FORCE"), "true")))
if (!(getenv("LIBNM_FORCE") && !strcmp(getenv("LIBNM_FORCE"), "true")))
return;
char *err;
NMI_LOG("init: creating failsafe");
nmi_failsafe_t *fs;
if (!(fs = nmi_failsafe_create(&err)) && err) {
NMI_LOG("error: could not create failsafe: %s, stopping", err);
NM_LOG("init: creating failsafe");
nm_failsafe_t *fs;
if (!(fs = nm_failsafe_create(&err)) && err) {
NM_LOG("error: could not create failsafe: %s, stopping", err);
free(err);
goto stop;
}
NMI_LOG("init: checking for uninstall flag");
if (!access("/mnt/onboard/.adds/nmi/uninstall", F_OK)) {
NMI_LOG("init: flag found, uninstalling");
nmi_failsafe_uninstall(fs);
unlink("/mnt/onboard/.adds/nmi/uninstall");
NM_LOG("init: checking for uninstall flag");
if (!access("/mnt/onboard/.adds/nm/uninstall", F_OK)) {
NM_LOG("init: flag found, uninstalling");
nm_failsafe_uninstall(fs);
unlink("/mnt/onboard/.adds/nm/uninstall");
goto stop;
}
NMI_LOG("init: parsing config");
NM_LOG("init: parsing config");
size_t items_n;
nmi_menu_item_t **items;
nmi_config_t *cfg;
if (!(cfg = nmi_config_parse(&err)) && err) {
NMI_LOG("error: could not parse config: %s, creating error item in main menu instead", err);
nm_menu_item_t **items;
nm_config_t *cfg;
if (!(cfg = nm_config_parse(&err)) && err) {
NM_LOG("error: could not parse config: %s, creating error item in main menu instead", err);
items_n = 1;
items = calloc(items_n, sizeof(nmi_menu_item_t*));
items[0] = calloc(1, sizeof(nmi_menu_item_t));
items = calloc(items_n, sizeof(nm_menu_item_t*));
items[0] = calloc(1, sizeof(nm_menu_item_t));
items[0]->loc = NMI_MENU_LOCATION_MAIN_MENU;
items[0]->loc = NM_MENU_LOCATION_MAIN_MENU;
items[0]->lbl = strdup("Config Error");
items[0]->arg = strdup(err);
items[0]->act = nmi_action_dbgerror;
items[0]->act = nm_action_dbgerror;
free(err);
} else if (!(items = nmi_config_get_menu(cfg, &items_n))) {
NMI_LOG("error: could not allocate memory, stopping");
} else if (!(items = nm_config_get_menu(cfg, &items_n))) {
NM_LOG("error: could not allocate memory, stopping");
goto stop_fs;
}
NMI_LOG("init: opening libnickel");
NM_LOG("init: opening libnickel");
void *libnickel = dlopen("libnickel.so.1.0.0", RTLD_LAZY|RTLD_NODELETE);
if (!libnickel) {
NMI_LOG("error: could not dlopen libnickel, stopping");
NM_LOG("error: could not dlopen libnickel, stopping");
goto stop_fs;
}
NMI_LOG("init: hooking libnickel");
if (nmi_menu_hook(libnickel, items, items_n, &err) && err) {
NMI_LOG("error: could not hook libnickel: %s, stopping", err);
NM_LOG("init: hooking libnickel");
if (nm_menu_hook(libnickel, items, items_n, &err) && err) {
NM_LOG("error: could not hook libnickel: %s, stopping", err);
free(err);
goto stop_fs;
}
stop_fs:
NMI_LOG("init: destroying failsafe");
nmi_failsafe_destroy(fs, 20);
NM_LOG("init: destroying failsafe");
nm_failsafe_destroy(fs, 20);
stop:
NMI_LOG("init: done");
NM_LOG("init: done");
return;
}

View File

@@ -33,69 +33,69 @@ static QAction* (*AbstractNickelMenuController_createAction)(void*, QMenu*, QWid
// a signal handler).
static void (*ConfirmationDialogFactory_showOKDialog)(QString const&, QString const&);
static nmi_menu_item_t **_items;
static nm_menu_item_t **_items;
static size_t _items_n;
extern "C" int nmi_menu_hook(void *libnickel, nmi_menu_item_t **items, size_t items_n, char **err_out) {
#define NMI_ERR_RET 1
extern "C" int nm_menu_hook(void *libnickel, nm_menu_item_t **items, size_t items_n, char **err_out) {
#define NM_ERR_RET 1
reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem) = dlsym(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_");
reinterpret_cast<void*&>(AbstractNickelMenuController_createAction) = dlsym(libnickel, "_ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb");
reinterpret_cast<void*&>(ConfirmationDialogFactory_showOKDialog) = dlsym(libnickel, "_ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_");
NMI_ASSERT(AbstractNickelMenuController_createMenuTextItem, "unsupported firmware: could not find AbstractNickelMenuController::createMenuTextItem(void* _this, QMenu*, QString, bool, bool, QString const&)");
NMI_ASSERT(AbstractNickelMenuController_createAction, "unsupported firmware: could not find AbstractNickelMenuController::createAction(void* _this, QMenu*, QWidget*, bool, bool, bool)");
NMI_ASSERT(AbstractNickelMenuController_createAction, "unsupported firmware: could not find ConfirmationDialogFactory::showOKDialog(QString const&, QString const&)");
NM_ASSERT(AbstractNickelMenuController_createMenuTextItem, "unsupported firmware: could not find AbstractNickelMenuController::createMenuTextItem(void* _this, QMenu*, QString, bool, bool, QString const&)");
NM_ASSERT(AbstractNickelMenuController_createAction, "unsupported firmware: could not find AbstractNickelMenuController::createAction(void* _this, QMenu*, QWidget*, bool, bool, bool)");
NM_ASSERT(AbstractNickelMenuController_createAction, "unsupported firmware: could not find ConfirmationDialogFactory::showOKDialog(QString const&, QString const&)");
void* nmh = dlsym(RTLD_DEFAULT, "_nmi_menu_hook");
NMI_ASSERT(nmh, "internal error: could not dlsym _nmi_menu_hook");
void* nmh = dlsym(RTLD_DEFAULT, "_nm_menu_hook");
NM_ASSERT(nmh, "internal error: could not dlsym _nm_menu_hook");
char *err;
reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem_orig) = nmi_dlhook(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", nmh, &err);
NMI_ASSERT(AbstractNickelMenuController_createMenuTextItem_orig, "failed to hook _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_: %s", err);
reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem_orig) = nm_dlhook(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", nmh, &err);
NM_ASSERT(AbstractNickelMenuController_createMenuTextItem_orig, "failed to hook _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_: %s", err);
_items = items;
_items_n = items_n;
NMI_RETURN_OK(0);
#undef NMI_ERR_RET
NM_RETURN_OK(0);
#undef NM_ERR_RET
}
extern "C" MenuTextItem* _nmi_menu_hook(void* _this, QMenu* menu, QString const& label, bool checkable, bool checked, QString const& thingy) {
NMI_LOG("AbstractNickelMenuController::createMenuTextItem(%p, `%s`, %d, %d, `%s`)", menu, qPrintable(label), checkable, checked, qPrintable(thingy));
extern "C" 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", "Help");
QString trrm = QCoreApplication::translate("DictionaryActionProxy", "Dictionary");
NMI_LOG("Comparing against '%s', '%s'", qPrintable(trmm), qPrintable(trrm));
NM_LOG("Comparing against '%s', '%s'", qPrintable(trmm), qPrintable(trrm));
bool ismm, isrm;
if ((ismm = (label == trmm) && !checkable))
NMI_LOG("Intercepting main menu (label=Help, checkable=false)...");
NM_LOG("Intercepting main menu (label=Help, checkable=false)...");
if ((isrm = (label == trrm) && !checkable))
NMI_LOG("Intercepting reader menu (label=Dictionary, checkable=false)...");
NM_LOG("Intercepting reader menu (label=Dictionary, checkable=false)...");
for (size_t i = 0; i < _items_n; i++) {
nmi_menu_item_t *it = _items[i];
if (it->loc == NMI_MENU_LOCATION_MAIN_MENU && !ismm)
nm_menu_item_t *it = _items[i];
if (it->loc == NM_MENU_LOCATION_MAIN_MENU && !ismm)
continue;
if (it->loc == NMI_MENU_LOCATION_READER_MENU && !isrm)
if (it->loc == NM_MENU_LOCATION_READER_MENU && !isrm)
continue;
NMI_LOG("Adding item '%s'...", it->lbl);
NM_LOG("Adding item '%s'...", it->lbl);
MenuTextItem* item = AbstractNickelMenuController_createMenuTextItem_orig(_this, menu, QString::fromUtf8(it->lbl), false, false, "");
QAction* action = AbstractNickelMenuController_createAction(_this, menu, item, true, true, true);
// note: we're capturing by value, i.e. the pointer to the global variable, rather then the stack variable, so this is safe
QObject::connect(action, &QAction::triggered, std::function<void(bool)>([it](bool){
NMI_LOG("Item '%s' pressed...", it->lbl);
NM_LOG("Item '%s' pressed...", it->lbl);
char *err;
if (it->act(it->arg, &err) && err) {
NMI_LOG("Got error: '%s', displaying...", err);
NM_LOG("Got error: '%s', displaying...", err);
ConfirmationDialogFactory_showOKDialog(QString::fromUtf8(it->lbl), QString::fromUtf8(err));
free(err);
return;
}
NMI_LOG("Success!");
NM_LOG("Success!");
}));
}

View File

@@ -1,5 +1,5 @@
#ifndef NMI_MENU_H
#define NMI_MENU_H
#ifndef NM_MENU_H
#define NM_MENU_H
#ifdef __cplusplus
extern "C" {
#endif
@@ -7,23 +7,23 @@ extern "C" {
#include <stddef.h>
typedef enum {
NMI_MENU_LOCATION_MAIN_MENU = 1,
NMI_MENU_LOCATION_READER_MENU = 2,
} nmi_menu_location_t;
NM_MENU_LOCATION_MAIN_MENU = 1,
NM_MENU_LOCATION_READER_MENU = 2,
} nm_menu_location_t;
typedef struct {
nmi_menu_location_t loc;
nm_menu_location_t loc;
char *lbl;
char *arg;
int (*act)(const char *arg, char **out_err); // can block, must return 0 on success, nonzero with out_err set to the malloc'd error message on error
} nmi_menu_item_t;
} nm_menu_item_t;
// nmi_menu_hook hooks a dlopen'd libnickel handle to add the specified menus,
// nm_menu_hook hooks a dlopen'd libnickel handle to add the specified menus,
// and returns 0 on success, or 1 with err_out (if provided) set to the malloc'd
// error message on error. The provided configuration and all pointers it
// references must remain valid for the lifetime of the program (i.e. not stack
// allocated). It MUST NOT be called more than once.
int nmi_menu_hook(void *libnickel, nmi_menu_item_t **items, size_t items_n, char **err_out);
int nm_menu_hook(void *libnickel, nm_menu_item_t **items, size_t items_n, char **err_out);
#ifdef __cplusplus
}

View File

@@ -6,7 +6,7 @@
// 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 NMIPlugin : public QImageIOPlugin {
class NMPlugin : public QImageIOPlugin {
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface")
public:

View File

@@ -1,5 +1,5 @@
#ifndef NMI_UTIL_H
#define NMI_UTIL_H
#ifndef NM_UTIL_H
#define NM_UTIL_H
#ifdef __cplusplus
extern "C" {
#endif
@@ -13,34 +13,34 @@ extern "C" {
// A bunch of useful macros to simplify error handling and logging.
// NMI_LOG writes a log message.
#define NMI_LOG(fmt, ...) syslog(LOG_DEBUG, "(nickel-menu-inject) " fmt " (%s:%d)", ##__VA_ARGS__, __FILE__, __LINE__)
// NM_LOG writes a log message.
#define NM_LOG(fmt, ...) syslog(LOG_DEBUG, "(NickelMenu) " fmt " (%s:%d)", ##__VA_ARGS__, __FILE__, __LINE__)
// NMI_RETURN returns ret, and if ret is NMI_ERR_RET and err_out is not NULL, it
// NM_RETURN returns ret, and if ret is NM_ERR_RET and err_out is not NULL, it
// writes the formatted error message to *err_out as a malloc'd string. The
// arguments may or may not be evaluated more than once.
#define NMI_RETURN(ret, fmt, ...) do { \
#define NM_RETURN(ret, fmt, ...) do { \
typeof(ret) _ret = (ret); \
if (err_out) { \
if (_ret == NMI_ERR_RET) asprintf(err_out, fmt " (%s:%d)", ##__VA_ARGS__, __FILE__, __LINE__); \
if (_ret == NM_ERR_RET) asprintf(err_out, fmt " (%s:%d)", ##__VA_ARGS__, __FILE__, __LINE__); \
else *err_out = NULL; \
} \
return _ret; \
} while (0)
// NMI_ASSERT is like assert, but it writes the formatted error message to
// err_out as a malloc'd string, and returns NMI_ERR_RET. Cond will always be
// NM_ASSERT is like assert, but it writes the formatted error message to
// err_out as a malloc'd string, and returns NM_ERR_RET. Cond will always be
// evaluated exactly once. The other arguments may or may not be evaluated one
// or more times.
#define NMI_ASSERT(cond, fmt, ...) do { \
if (!(cond)) NMI_RETURN(NMI_ERR_RET, fmt " (assertion failed: %s)", ##__VA_ARGS__, #cond); \
#define NM_ASSERT(cond, fmt, ...) do { \
if (!(cond)) NM_RETURN(NM_ERR_RET, fmt " (assertion failed: %s)", ##__VA_ARGS__, #cond); \
} while (0)
// NMI_RETURN_ERR is the same as NMI_RETURN(NMI_ERR_RET, fmt, ...).
#define NMI_RETURN_ERR(fmt, ...) NMI_RETURN(NMI_ERR_RET, fmt, ##__VA_ARGS__)
// NM_RETURN_ERR is the same as NM_RETURN(NM_ERR_RET, fmt, ...).
#define NM_RETURN_ERR(fmt, ...) NM_RETURN(NM_ERR_RET, fmt, ##__VA_ARGS__)
// NMI_RETURN_OK is the same as NMI_RETURN(ret, "").
#define NMI_RETURN_OK(ret) NMI_RETURN(ret, "")
// NM_RETURN_OK is the same as NM_RETURN(ret, "").
#define NM_RETURN_OK(ret) NM_RETURN(ret, "")
#ifdef __cplusplus
}