diff --git a/res/doc b/res/doc index b17195e..dc4a626 100644 --- a/res/doc +++ b/res/doc @@ -66,6 +66,7 @@ # nickel_open - opens a view # nickel_wifi - controls wifi (note: it doesn't wait for it to connect or disconnect, neither does it check for success) # nickel_bluetooth - controls bluetooth +# nickel_screenshot - take screenshot (without having to turn on the "nickel_setting:screenshots" setting) # nickel_orientation - controls screen orientation # (devices without an orientation sensor may need to use the kobopatch patch "Allow rotation on all devices" or set [DeveloperSettings] ForceAllowLandscape=true) # (devices with an orientation sensor don't need to do anything, but can set the config setting to make this work on all views) @@ -156,6 +157,8 @@ # toggle (4.34.20097+) # check (4.34.20097+) # scan (4.34.20097+) +# nickel_screenshot - one of: +# capture - Capture screenshot directly # # chain_success:: # chain_failure:: diff --git a/src/action.h b/src/action.h index 1806586..731e3b8 100644 --- a/src/action.h +++ b/src/action.h @@ -52,6 +52,7 @@ void nm_action_result_free(nm_action_result_t *res); X(nickel_wifi) \ X(nickel_bluetooth) \ X(nickel_orientation) \ + X(nickel_screenshot) \ X(power) \ X(skip) \ X(uninstall) diff --git a/src/action_cc.cc b/src/action_cc.cc index d84c64f..78fe09e 100644 --- a/src/action_cc.cc +++ b/src/action_cc.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -65,6 +66,57 @@ typedef void BluetoothManager; NM_CHECK(nullptr, var, err); \ } while(0) + +struct SettingsSymbols { + //libnickel 4.6 * _ZN8SettingsC2ERK6Deviceb _ZN8SettingsC2ERK6Device + void *(*Settings_Settings)(Settings*, Device*, bool); + void *(*Settings_SettingsLegacy)(Settings*, Device*); + //libnickel 4.6 * _ZN8SettingsD2Ev + void *(*Settings_SettingsD)(Settings*); + //libnickel 4.6 * _ZN8Settings10getSettingERK7QStringRK8QVariant + QVariant (*Settings_getSetting)(Settings*, QString const&, QVariant const&); + //libnickel 4.6 * _ZN8Settings11saveSettingERK7QStringRK8QVariantb + void *(*Settings_saveSetting)(Settings*, QString const&, QVariant const&, bool); +}; + + +Device* get_Device_getCurrentDevice() { + //libnickel 4.6 * _ZN6Device16getCurrentDeviceEv + Device *(*Device_getCurrentDevice)(); + NM_ACT_XSYM(Device_getCurrentDevice, "_ZN6Device16getCurrentDeviceEv", "could not dlsym Device::getCurrentDevice"); + + Device *dev = Device_getCurrentDevice(); + NM_CHECK(nullptr, dev, "could not get shared nickel device pointer"); + + return dev; +} + +SettingsSymbols prepare_Settings_symbols() { + SettingsSymbols symbols{}; + + //libnickel 4.6 * _ZN8SettingsC2ERK6Deviceb _ZN8SettingsC2ERK6Device + NM_ACT_SYM(symbols.Settings_Settings, "_ZN8SettingsC2ERK6Deviceb"); + NM_ACT_SYM(symbols.Settings_SettingsLegacy, "_ZN8SettingsC2ERK6Device"); + NM_CHECK(SettingsSymbols{}, symbols.Settings_Settings || symbols.Settings_SettingsLegacy, "could not dlsym Settings constructor (new and/or old)"); + + //libnickel 4.6 * _ZN8SettingsD2Ev + NM_ACT_SYM(symbols.Settings_SettingsD, "_ZN8SettingsD2Ev"); + NM_CHECK(SettingsSymbols{}, symbols.Settings_SettingsD, "could not dlsym Settings destructor"); + + // some settings don't have symbols in a usable form, and some are inlined, so we may need to set them directly + //libnickel 4.6 * _ZN8Settings10getSettingERK7QStringRK8QVariant + NM_ACT_SYM(symbols.Settings_getSetting, "_ZN8Settings10getSettingERK7QStringRK8QVariant"); + NM_CHECK(SettingsSymbols{}, symbols.Settings_getSetting, "could not dlsym Settings::getSetting"); + + // ditto + //libnickel 4.6 * _ZN8Settings11saveSettingERK7QStringRK8QVariantb + NM_ACT_SYM(symbols.Settings_saveSetting, "_ZN8Settings11saveSettingERK7QStringRK8QVariantb"); + NM_CHECK(SettingsSymbols{}, symbols.Settings_saveSetting, "could not dlsym Settings::saveSetting"); + + return symbols; +} + + NM_ACTION_(nickel_open) { char *tmp1 = strdupa(arg); // strsep and strtrim will modify it char *arg1 = strtrim(strsep(&tmp1, ":")); @@ -214,34 +266,14 @@ NM_ACTION_(nickel_setting) { else NM_ERR_RET(nullptr, "unknown action '%s' for nickel_setting: expected 'toggle', 'enable', or 'disable'", arg1); - //libnickel 4.6 * _ZN6Device16getCurrentDeviceEv - Device *(*Device_getCurrentDevice)(); - NM_ACT_XSYM(Device_getCurrentDevice, "_ZN6Device16getCurrentDeviceEv", "could not dlsym Device::getCurrentDevice"); - - //libnickel 4.6 * _ZN8SettingsC2ERK6Deviceb _ZN8SettingsC2ERK6Device - void *(*Settings_Settings)(Settings*, Device*, bool); - void *(*Settings_SettingsLegacy)(Settings*, Device*); - NM_ACT_SYM(Settings_Settings, "_ZN8SettingsC2ERK6Deviceb"); - NM_ACT_SYM(Settings_SettingsLegacy, "_ZN8SettingsC2ERK6Device"); - NM_CHECK(nullptr, Settings_Settings || Settings_SettingsLegacy, "could not dlsym Settings constructor (new and/or old)"); - - //libnickel 4.6 * _ZN8SettingsD2Ev - void *(*Settings_SettingsD)(Settings*); - NM_ACT_XSYM(Settings_SettingsD, "_ZN8SettingsD2Ev", "could not dlsym Settings destructor"); - - // some settings don't have symbols in a usable form, and some are inlined, so we may need to set them directly - //libnickel 4.6 * _ZN8Settings10getSettingERK7QStringRK8QVariant - QVariant (*Settings_getSetting)(Settings*, QString const&, QVariant const&); // the last param is the default, also note that this requires a subclass of Settings - NM_ACT_XSYM(Settings_getSetting, "_ZN8Settings10getSettingERK7QStringRK8QVariant", "could not dlsym Settings::getSetting"); - - // ditto - //libnickel 4.6 * _ZN8Settings11saveSettingERK7QStringRK8QVariantb - void *(*Settings_saveSetting)(Settings*, QString const&, QVariant const&, bool); // the last param is whether to do a full disk sync immediately (rather than waiting for the kernel to do it) - NM_ACT_XSYM(Settings_saveSetting, "_ZN8Settings11saveSettingERK7QStringRK8QVariantb", "could not dlsym Settings::saveSetting"); - - Device *dev = Device_getCurrentDevice(); - NM_CHECK(nullptr, dev, "could not get shared nickel device pointer"); + SettingsSymbols settings_syms = prepare_Settings_symbols(); + auto Settings_Settings = settings_syms.Settings_Settings; + auto Settings_SettingsLegacy = settings_syms.Settings_SettingsLegacy; + auto Settings_SettingsD = settings_syms.Settings_SettingsD; + auto Settings_getSetting = settings_syms.Settings_getSetting; + auto Settings_saveSetting = settings_syms.Settings_saveSetting; + Device *dev = get_Device_getCurrentDevice(); Settings *settings = alloca(128); // way larger than it is, but better to be safe if (Settings_Settings) Settings_Settings(settings, dev, false); @@ -677,25 +709,10 @@ NM_ACTION_(nickel_orientation) { void (*QWindowSystemInterface_handleScreenOrientationChange)(QScreen*, Qt::ScreenOrientation); NM_ACT_XSYM(QWindowSystemInterface_handleScreenOrientationChange, "_ZN22QWindowSystemInterface29handleScreenOrientationChangeEP7QScreenN2Qt17ScreenOrientationE", "could not dlsym QWindowSystemInterface::handleScreenOrientationChange (did the way Nickel handles the screen orientation sensor change?)"); - //libnickel 4.6 * _ZN6Device16getCurrentDeviceEv - Device *(*Device_getCurrentDevice)(); - NM_ACT_XSYM(Device_getCurrentDevice, "_ZN6Device16getCurrentDeviceEv", "could not dlsym Device::getCurrentDevice"); - //libnickel 4.11.11911 * _ZNK6Device20hasOrientationSensorEv bool (*Device_hasOrientationSensor)(Device*); NM_ACT_XSYM(Device_hasOrientationSensor, "_ZNK6Device20hasOrientationSensorEv", "could not dlsym Device::hasOrientationSensor"); - //libnickel 4.6 * _ZN8SettingsC2ERK6Deviceb _ZN8SettingsC2ERK6Device - void *(*Settings_Settings)(Settings*, Device*, bool); - void *(*Settings_SettingsLegacy)(Settings*, Device*); - NM_ACT_SYM(Settings_Settings, "_ZN8SettingsC2ERK6Deviceb"); - NM_ACT_SYM(Settings_SettingsLegacy, "_ZN8SettingsC2ERK6Device"); - NM_CHECK(nullptr, Settings_Settings || Settings_SettingsLegacy, "could not dlsym Settings constructor (new and/or old)"); - - //libnickel 4.6 * _ZN8SettingsD2Ev - void *(*Settings_SettingsD)(Settings*); - NM_ACT_XSYM(Settings_SettingsD, "_ZN8SettingsD2Ev", "could not dlsym Settings destructor"); - void *ApplicationSettings_vtable = dlsym(RTLD_DEFAULT, "_ZTV19ApplicationSettings"); NM_CHECK(nullptr, ApplicationSettings_vtable, "could not dlsym the vtable for ApplicationSettings"); @@ -707,8 +724,12 @@ NM_ACTION_(nickel_orientation) { int (*ApplicationSettings_lockedOrientation)(Settings*); NM_ACT_XSYM(ApplicationSettings_lockedOrientation, "_ZN19ApplicationSettings17lockedOrientationEv", "could not dlsym ApplicationSettings::lockedOrientation"); - Device *dev = Device_getCurrentDevice(); - NM_CHECK(nullptr, dev, "could not get shared nickel device pointer"); + Device *dev = get_Device_getCurrentDevice(); + + SettingsSymbols settings_syms = prepare_Settings_symbols(); + auto Settings_Settings = settings_syms.Settings_Settings; + auto Settings_SettingsLegacy = settings_syms.Settings_SettingsLegacy; + auto Settings_SettingsD = settings_syms.Settings_SettingsD; Settings *settings = alloca(128); // way larger than it is, but better to be safe if (Settings_Settings) @@ -981,3 +1002,92 @@ NM_ACTION_(nickel_bluetooth) { break; } } + +NM_ACTION_(nickel_screenshot) { + enum SCREENSHOT_ACTION { + CAPTURE = 0b00001, + }; + + int action = 0; + if (!strcmp(arg, "capture")) action |= CAPTURE; + else + NM_ERR_RET(nullptr, "unknown nickel_screenshot action '%s'", arg); + + QObject* app = nullptr; + QKeyEvent* press = nullptr; + QKeyEvent* release = nullptr; + + #define vtable_ptr(x) *reinterpret_cast(x) + #define vtable_target(x) reinterpret_cast(reinterpret_cast(x)+8) + + // Prepare Settings symbols + SettingsSymbols settings_syms = prepare_Settings_symbols(); + auto Settings_Settings = settings_syms.Settings_Settings; + auto Settings_SettingsLegacy = settings_syms.Settings_SettingsLegacy; + auto Settings_SettingsD = settings_syms.Settings_SettingsD; + auto Settings_getSetting = settings_syms.Settings_getSetting; + auto Settings_saveSetting = settings_syms.Settings_saveSetting; + + Device *dev = get_Device_getCurrentDevice(); + Settings *settings = alloca(128); // way larger than it is, but better to be safe + if (Settings_Settings) + Settings_Settings(settings, dev, false); + else if (Settings_SettingsLegacy) + Settings_SettingsLegacy(settings, dev); + + QVariant v1; + + //libnickel 4.6 * _ZTV15FeatureSettings + void *FeatureSettings_vtable = dlsym(RTLD_DEFAULT, "_ZTV15FeatureSettings"); + NM_CHECK(nullptr, FeatureSettings_vtable, "could not dlsym the vtable for FeatureSettings"); + vtable_ptr(settings) = vtable_target(FeatureSettings_vtable); + + QString screenshot_key = QStringLiteral("Screenshots"); + bool org_screenshot_enabled = false; + + switch (action) { + case CAPTURE: + // Pressing the Power button on Kobo is the same as pressing the Escape key on keyboard + // So to take screenshot, we just need to: + // 1. Enable the "Screenshots" feature + // 2. Send an "Escape" KeyEvent to the QApplication instance + + // Get Nickel::Application->notify() + //libnickel 4.6 * _ZN18Nickel3Application6notifyEP7QObjectP6QEvent + int (*AppNotify)(void* app, void* receiver, void* event); + NM_ACT_XSYM(AppNotify, "_ZN18Nickel3Application6notifyEP7QObjectP6QEvent", "could not dlsym Nickel::Application->notify()"); + + app = QApplication::instance(); + + // Get the original state of the "Screenshots" setting + v1 = Settings_getSetting(settings, screenshot_key, QVariant(false)); + vtable_ptr(settings) = vtable_target(FeatureSettings_vtable); + org_screenshot_enabled = v1.toBool(); + + // If it's not enabled => temporary set it to True + if (!org_screenshot_enabled) { + Settings_saveSetting(settings, screenshot_key, QVariant(true), false); + vtable_ptr(settings) = vtable_target(FeatureSettings_vtable); + } + + // Send the "Escape" KeyEvent + press = new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); + release = new QKeyEvent(QEvent::KeyRelease, Qt::Key_Escape, Qt::NoModifier); + AppNotify(app, nullptr, press); + AppNotify(app, nullptr, release); + + // Restore "Screenshots" setting if it was previously disabled + if (!org_screenshot_enabled) { + Settings_saveSetting(settings, screenshot_key, QVariant(false), false); + vtable_ptr(settings) = vtable_target(FeatureSettings_vtable); + } + + // Deconstruct Settings + Settings_SettingsD(settings); + + return nm_action_result_silent(); + default: + NM_ERR_RET(nullptr, "unknown nickel_screenshot action '%s'", arg); + break; + } +}