1
0

Started implementing config, implemented uninstall flag

This commit is contained in:
Patrick Gaskin
2020-04-22 12:50:39 -04:00
parent b5b5093f9f
commit 61ed6820c7
8 changed files with 130 additions and 65 deletions

14
res/doc
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,17 +13,17 @@ typedef enum {
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
}