Started implementing config, implemented uninstall flag
This commit is contained in:
16
res/doc
16
res/doc
@@ -9,7 +9,7 @@
|
||||
#
|
||||
# Place your configuration files in this folder (/mnt/onboard/.kobo/.adds/nmi).
|
||||
# They can be named anything, and should consist of multiple lines either
|
||||
# starting with # for a comment, or in the one of the following formats:
|
||||
# starting with # for a comment, or in the the following format:
|
||||
#
|
||||
# menu_item:<location>:<label>:<subsystem>:<arg>
|
||||
# Adds a menu item (spaces around fields are ignored).
|
||||
@@ -31,11 +31,17 @@
|
||||
# invert - toggles FeatureSettings.InvertScreen (all versions)
|
||||
# screenshots - toggles FeatureSettings.Screenshots (all versions)
|
||||
#
|
||||
# uninstall
|
||||
# Makes nickel-menu-inject uninstall itself.
|
||||
#
|
||||
# For example, you might have a configuration file in KOBOeReader/.adds/nmi/mystuff like:
|
||||
#
|
||||
# menu_item :main :Show an error :dbg_error :This is an error message!
|
||||
# menu_item :reader :Invert Screen :nickel_setting :invert
|
||||
#
|
||||
#
|
||||
# If there is a error in the configuration, an item which displays it will be
|
||||
# added to the main menu. If an internal error occurs, it is written to syslog,
|
||||
# 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
|
||||
# triggering the failsafe mechanism by turning your Kobo off within 20 seconds
|
||||
# of turning it on.
|
||||
78
src/config.c
78
src/config.c
@@ -1,4 +1,6 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -9,40 +11,54 @@
|
||||
#include "subsys_c.h"
|
||||
#include "subsys_cc.h"
|
||||
|
||||
// TODO: actually parse it
|
||||
typedef enum {
|
||||
NMI_CONFIG_TYPE_MENU_ITEM = 1,
|
||||
} nmi_config_type_t;
|
||||
|
||||
nmi_menu_entry_t *nmi_config_parse(size_t *n, char **err_out) {
|
||||
struct nmi_config_t {
|
||||
nmi_config_type_t type;
|
||||
union {
|
||||
nmi_menu_item_t *menu_item;
|
||||
} value;
|
||||
nmi_config_t *next;
|
||||
};
|
||||
|
||||
nmi_config_t *nmi_config_parse(char **err_out) {
|
||||
#define NMI_ERR_RET NULL
|
||||
NMI_ASSERT(n, "required argument is null");
|
||||
nmi_menu_entry_t *me = calloc(5, sizeof(nmi_menu_entry_t));
|
||||
|
||||
me[0].loc = NMI_MENU_LOCATION_MAIN_MENU;
|
||||
me[0].lbl = strdup("Test Log (main)");
|
||||
me[0].arg = strdup("It worked!");
|
||||
me[0].execute = nmi_subsys_dbgsyslog;
|
||||
|
||||
me[1].loc = NMI_MENU_LOCATION_READER_MENU;
|
||||
me[1].lbl = strdup("Test Log (reader)");
|
||||
me[1].arg = strdup("It worked!");
|
||||
me[1].execute = nmi_subsys_dbgsyslog;
|
||||
|
||||
me[2].loc = NMI_MENU_LOCATION_MAIN_MENU;
|
||||
me[2].lbl = strdup("Test Error (main)");
|
||||
me[2].arg = strdup("It's a fake error!");
|
||||
me[2].execute = nmi_subsys_dbgerror;
|
||||
|
||||
me[3].loc = NMI_MENU_LOCATION_READER_MENU;
|
||||
me[3].lbl = strdup("Test Error (reader)");
|
||||
me[3].arg = strdup("It's a fake error!");
|
||||
me[3].execute = nmi_subsys_dbgerror;
|
||||
|
||||
me[4].loc = NMI_MENU_LOCATION_READER_MENU;
|
||||
me[4].lbl = strdup("Invert Screen");
|
||||
me[4].arg = "invert";
|
||||
me[4].execute = nmi_subsys_nickelsetting;
|
||||
|
||||
*n = 5;
|
||||
NMI_RETURN_OK(me);
|
||||
NMI_RETURN_ERR("not implemented");
|
||||
#undef NMI_ERR_RET
|
||||
}
|
||||
|
||||
nmi_menu_item_t **nmi_config_get_menu(nmi_config_t *cfg, size_t *n_out) {
|
||||
*n_out = 0;
|
||||
for (nmi_config_t *cur = cfg; cur; cur = cfg->next)
|
||||
if (cur->type == NMI_CONFIG_TYPE_MENU_ITEM)
|
||||
(*n_out)++;
|
||||
|
||||
nmi_menu_item_t **it = calloc(*n_out, sizeof(nmi_menu_item_t*));
|
||||
if (!it)
|
||||
return NULL;
|
||||
|
||||
nmi_menu_item_t **tmp = it;
|
||||
for (nmi_config_t *cur = cfg; cur; cur = cfg->next)
|
||||
if (cur->type == NMI_CONFIG_TYPE_MENU_ITEM)
|
||||
*(tmp++) = cur->value.menu_item;
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
void nmi_config_free(nmi_config_t *cfg) {
|
||||
while (cfg) {
|
||||
nmi_config_t *n = cfg->next;
|
||||
|
||||
if (cfg->type == NMI_CONFIG_TYPE_MENU_ITEM) {
|
||||
free(cfg->value.menu_item->lbl);
|
||||
free(cfg->value.menu_item->arg);
|
||||
free(cfg->value.menu_item);
|
||||
}
|
||||
free(cfg);
|
||||
|
||||
cfg = n;
|
||||
}
|
||||
};
|
||||
|
||||
17
src/config.h
17
src/config.h
@@ -4,10 +4,23 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "menu.h"
|
||||
|
||||
// TODO
|
||||
nmi_menu_entry_t *nmi_config_parse(size_t *n, char **err_out);
|
||||
typedef struct nmi_config_t nmi_config_t;
|
||||
|
||||
// nmi_config_parse parses the configuration files in /mnt/onboard/.adds/nmi.
|
||||
// An error is returned if there are syntax errors, file access errors, or
|
||||
// invalid subsystem names for menu_item.
|
||||
nmi_config_t *nmi_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
|
||||
// called.
|
||||
nmi_menu_item_t **nmi_config_get_menu(nmi_config_t *cfg, size_t *n_out);
|
||||
|
||||
// nmi_config_free frees all allocated memory.
|
||||
void nmi_config_free(nmi_config_t *cfg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -60,7 +60,12 @@ static void *_nmi_failsafe_destroy(void* _fs) {
|
||||
void nmi_failsafe_destroy(nmi_failsafe_t *fs, int delay) {
|
||||
fs->delay = delay;
|
||||
|
||||
NMI_LOG("failsafe: starting restore thread");
|
||||
NMI_LOG("failsafe: scheduling restore");
|
||||
pthread_t t;
|
||||
pthread_create(&t, NULL, _nmi_failsafe_destroy, fs);
|
||||
}
|
||||
|
||||
void nmi_failsafe_uninstall(nmi_failsafe_t *fs) {
|
||||
NMI_LOG("failsafe: deleting %s", fs->tmp);
|
||||
unlink(fs->tmp);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ nmi_failsafe_t *nmi_failsafe_create(char **err_out);
|
||||
// after a delay. The nmi_failsafe_t must not be used afterwards.
|
||||
void nmi_failsafe_destroy(nmi_failsafe_t *fs, int delay);
|
||||
|
||||
// nmi_failsafe_uninstall uninstalls the lib. The nmi_failsafe_t must not be
|
||||
// used afterwards.
|
||||
void nmi_failsafe_uninstall(nmi_failsafe_t *fs);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
33
src/init.c
33
src/init.c
@@ -5,10 +5,12 @@
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "failsafe.h"
|
||||
#include "menu.h"
|
||||
#include "subsys_c.h"
|
||||
#include "util.h"
|
||||
|
||||
__attribute__((constructor)) void nmi_init() {
|
||||
@@ -27,14 +29,33 @@ __attribute__((constructor)) void nmi_init() {
|
||||
goto stop;
|
||||
}
|
||||
|
||||
// TODO: handle uninstall
|
||||
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");
|
||||
goto stop;
|
||||
}
|
||||
|
||||
NMI_LOG("init: parsing config");
|
||||
size_t entries_n;
|
||||
nmi_menu_entry_t *entries;
|
||||
if (!(entries = nmi_config_parse(&entries_n, &err)) && err) {
|
||||
NMI_LOG("error: could not parse config: %s, stopping", err);
|
||||
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);
|
||||
|
||||
items_n = 1;
|
||||
items = calloc(items_n, sizeof(nmi_menu_item_t*));
|
||||
*items = calloc(items_n, sizeof(nmi_menu_item_t));
|
||||
|
||||
items[0]->loc = NMI_MENU_LOCATION_MAIN_MENU;
|
||||
items[0]->lbl = strdup("Config Error");
|
||||
items[0]->arg = strdup(err);
|
||||
items[0]->execute = nmi_subsys_dbgerror;
|
||||
|
||||
free(err);
|
||||
} else if (!(items = nmi_config_get_menu(cfg, &items_n))) {
|
||||
NMI_LOG("error: could not allocate memory, stopping");
|
||||
goto stop_fs;
|
||||
}
|
||||
|
||||
@@ -46,7 +67,7 @@ __attribute__((constructor)) void nmi_init() {
|
||||
}
|
||||
|
||||
NMI_LOG("init: hooking libnickel");
|
||||
if (nmi_menu_hook(libnickel, entries, entries_n, &err) && err) {
|
||||
if (nmi_menu_hook(libnickel, items, items_n, &err) && err) {
|
||||
NMI_LOG("error: could not hook libnickel: %s, stopping", err);
|
||||
free(err);
|
||||
goto stop_fs;
|
||||
|
||||
30
src/menu.cc
30
src/menu.cc
@@ -33,10 +33,10 @@ static QAction* (*AbstractNickelMenuController_createAction)(void*, QMenu*, QWid
|
||||
// a signal handler).
|
||||
static void (*ConfirmationDialogFactory_showOKDialog)(QString const&, QString const&);
|
||||
|
||||
static nmi_menu_entry_t *_entries;
|
||||
static size_t _entries_n;
|
||||
static nmi_menu_item_t **_items;
|
||||
static size_t _items_n;
|
||||
|
||||
extern "C" int nmi_menu_hook(void *libnickel, nmi_menu_entry_t *entries, size_t entries_n, char **err_out) {
|
||||
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
|
||||
reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem) = dlsym(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_");
|
||||
reinterpret_cast<void*&>(AbstractNickelMenuController_createAction) = dlsym(libnickel, "_ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb");
|
||||
@@ -53,8 +53,8 @@ extern "C" int nmi_menu_hook(void *libnickel, nmi_menu_entry_t *entries, size_t
|
||||
reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem_orig) = nmi_dlhook(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", nmh, &err);
|
||||
NMI_ASSERT(AbstractNickelMenuController_createMenuTextItem_orig, "failed to hook _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_: %s", err);
|
||||
|
||||
_entries = entries;
|
||||
_entries_n = entries_n;
|
||||
_items = items;
|
||||
_items_n = items_n;
|
||||
|
||||
NMI_RETURN_OK(0);
|
||||
#undef NMI_ERR_RET
|
||||
@@ -73,25 +73,25 @@ extern "C" MenuTextItem* _nmi_menu_hook(void* _this, QMenu* menu, QString const&
|
||||
if ((isrm = (label == trrm) && !checkable))
|
||||
NMI_LOG("Intercepting reader menu (label=Dictionary, checkable=false)...");
|
||||
|
||||
for (size_t i = 0; i < _entries_n; i++) {
|
||||
nmi_menu_entry_t *ent = &_entries[i];
|
||||
if (ent->loc == NMI_MENU_LOCATION_MAIN_MENU && !ismm)
|
||||
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)
|
||||
continue;
|
||||
if (ent->loc == NMI_MENU_LOCATION_READER_MENU && !isrm)
|
||||
if (it->loc == NMI_MENU_LOCATION_READER_MENU && !isrm)
|
||||
continue;
|
||||
|
||||
NMI_LOG("Adding item '%s'...", ent->lbl);
|
||||
NMI_LOG("Adding item '%s'...", it->lbl);
|
||||
|
||||
MenuTextItem* item = AbstractNickelMenuController_createMenuTextItem_orig(_this, menu, QString::fromUtf8(ent->lbl), false, false, "");
|
||||
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)>([ent](bool checked){
|
||||
NMI_LOG("Item '%s' pressed...", ent->lbl);
|
||||
QObject::connect(action, &QAction::triggered, std::function<void(bool)>([it](bool checked){
|
||||
NMI_LOG("Item '%s' pressed...", it->lbl);
|
||||
char *err;
|
||||
if (ent->execute(ent->arg, &err) && err) {
|
||||
if (it->execute(it->arg, &err) && err) {
|
||||
NMI_LOG("Got error %s, displaying...", err);
|
||||
ConfirmationDialogFactory_showOKDialog(QString::fromUtf8(ent->lbl), QString::fromUtf8(err));
|
||||
ConfirmationDialogFactory_showOKDialog(QString::fromUtf8(it->lbl), QString::fromUtf8(err));
|
||||
free(err);
|
||||
return;
|
||||
}
|
||||
|
||||
10
src/menu.h
10
src/menu.h
@@ -7,23 +7,23 @@ extern "C" {
|
||||
#include <stddef.h>
|
||||
|
||||
typedef enum {
|
||||
NMI_MENU_LOCATION_MAIN_MENU = 1,
|
||||
NMI_MENU_LOCATION_MAIN_MENU = 1,
|
||||
NMI_MENU_LOCATION_READER_MENU = 2,
|
||||
} nmi_menu_location_t;
|
||||
|
||||
typedef struct {
|
||||
nmi_menu_location_t loc;
|
||||
const char *lbl;
|
||||
const char *arg;
|
||||
char *lbl;
|
||||
char *arg;
|
||||
int (*execute)(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_entry_t;
|
||||
} nmi_menu_item_t;
|
||||
|
||||
// nmi_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_entry_t *entries, size_t entries_n, char **err_out);
|
||||
int nmi_menu_hook(void *libnickel, nmi_menu_item_t **items, size_t items_n, char **err_out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user