1
0

Add action to directly capture screenshot (#206)

This commit is contained in:
redphx
2025-12-07 08:01:57 +07:00
committed by GitHub
parent 2d61a77c80
commit 413eb415fd
3 changed files with 158 additions and 44 deletions

View File

@@ -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:<action>:<arg>
# chain_failure:<action>:<arg>

View File

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

View File

@@ -8,6 +8,7 @@
#include <QUrl>
#include <QVariant>
#include <QWidget>
#include <QKeyEvent>
#include <initializer_list>
@@ -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<void**&>(x)
#define vtable_target(x) reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(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;
}
}