From 80a090686740078339da9f790e1856ce1858205d Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 16 Oct 2026 00:54:13 +0200 Subject: [PATCH] Remote screenshot support --- Buildscripts/TactilitySDK/Tools/screenshot.py | 41 +++ Buildscripts/TactilitySDK/Tools/shell.py | 15 + Modules/coreutils-module/source/apps/rm.cpp | 2 +- Modules/lvgl-module/generate-icons.py | 2 +- .../Tactility/service/screenshot/Screenshot.h | 55 --- .../service/screenshot/ScreenshotTask.h | 62 ---- Tactility/Source/app/launcher/AppsMode.cpp | 1 - .../Source/app/screenshot/Screenshot.cpp | 326 +++--------------- Tactility/Source/lvgl/Lvgl.cpp | 9 - .../Source/service/screenshot/Screenshot.cpp | 106 ------ .../service/screenshot/ScreenshotTask.cpp | 182 ---------- 11 files changed, 108 insertions(+), 693 deletions(-) create mode 100644 Buildscripts/TactilitySDK/Tools/screenshot.py delete mode 100644 Tactility/Private/Tactility/service/screenshot/Screenshot.h delete mode 100644 Tactility/Private/Tactility/service/screenshot/ScreenshotTask.h delete mode 100644 Tactility/Source/service/screenshot/Screenshot.cpp delete mode 100644 Tactility/Source/service/screenshot/ScreenshotTask.cpp diff --git a/Buildscripts/TactilitySDK/Tools/screenshot.py b/Buildscripts/TactilitySDK/Tools/screenshot.py new file mode 100644 index 000000000..9c03cd0d7 --- /dev/null +++ b/Buildscripts/TactilitySDK/Tools/screenshot.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# License: Apache License v2 +"""Takes a screenshot of the device through its development server. Run it as "tt screenshot".""" + +import argparse +import datetime +import os +import sys + +import shell + +# Encoding the PNG can take a while on a large display +SCREENSHOT_TIMEOUT_SECONDS = 30 + + +def main(): + shell.PROGRAM_NAME = "tt screenshot" if "TACTILITY_SDK_VERSION" in os.environ else "screenshot.py" + parser = argparse.ArgumentParser(prog=shell.PROGRAM_NAME, description="take a screenshot of the device", add_help=False) + shell.add_connection_arguments(parser) + parser.add_argument("local_path", nargs="?", help="the .png file on this computer (default: screenshot-.png)") + args = shell.parse_arguments(parser) + if args.local_path is None: + args.local_path = f"screenshot-{datetime.date.today().isoformat()}.png" + + # The device saves the screenshot in its temp folder and prints the path + exit_code, output = shell.run_command(args.host, args.port, "screenshot", SCREENSHOT_TIMEOUT_SECONDS) + if exit_code != 0: + shell.fail(output.strip() or "the device failed to take a screenshot") + remote_path = output.strip().splitlines()[-1] + + try: + shell.run_pull(argparse.Namespace(host=args.host, port=args.port, remote_path=remote_path, local_path=args.local_path)) + finally: + shell.run_command(args.host, args.port, f"rm {remote_path}") + sys.exit(0) + + +if __name__ == "__main__": + import _bootstrap + _bootstrap.run() + main() diff --git a/Buildscripts/TactilitySDK/Tools/shell.py b/Buildscripts/TactilitySDK/Tools/shell.py index a53febc81..b66b92de6 100755 --- a/Buildscripts/TactilitySDK/Tools/shell.py +++ b/Buildscripts/TactilitySDK/Tools/shell.py @@ -495,6 +495,21 @@ def run_shell(args): return 1 return exit_code + +def run_command(host, port, command, timeout=REQUEST_TIMEOUT_SECONDS): + """Runs a command without a terminal. @return (exit code, output text)""" + connection = open_session(host, port, "shell", command=command) + output = bytearray() + try: + while True: + frame_type, payload = connection.read_frame(timeout) + if frame_type == FRAME_DATA: + output += payload + elif frame_type == FRAME_EXIT and len(payload) == 4: + return struct.unpack(">i", payload)[0], output.decode("utf-8", errors="replace").replace("\r", "") + finally: + connection.close() + # endregion diff --git a/Modules/coreutils-module/source/apps/rm.cpp b/Modules/coreutils-module/source/apps/rm.cpp index b37cf63a3..828773800 100644 --- a/Modules/coreutils-module/source/apps/rm.cpp +++ b/Modules/coreutils-module/source/apps/rm.cpp @@ -47,7 +47,7 @@ extern const ::AppManifest manifest = { .category = APP_CATEGORY_SYSTEM, .location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast(main) }, .flags = APP_MANIFEST_FLAG_HEADLESS, - .stack = { .depth = 3072, .desired_memory_capability = 0 }, + .stack = { .depth = 4096, .desired_memory_capability = 0 }, }; } // namespace coreutils::rm diff --git a/Modules/lvgl-module/generate-icons.py b/Modules/lvgl-module/generate-icons.py index ad87cd2b0..3c5c7d517 100755 --- a/Modules/lvgl-module/generate-icons.py +++ b/Modules/lvgl-module/generate-icons.py @@ -153,7 +153,7 @@ shared_symbol_code_point_names = [ "help", # Diceware help "hub", # App Hub "info", # System Info - "image", # Screenshot app + "image", # Files app "keyboard_arrow_up", "lightbulb", "language", # Globe diff --git a/Tactility/Private/Tactility/service/screenshot/Screenshot.h b/Tactility/Private/Tactility/service/screenshot/Screenshot.h deleted file mode 100644 index 2fe89576a..000000000 --- a/Tactility/Private/Tactility/service/screenshot/Screenshot.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include "Tactility/TactilityConfig.h" - -#if TT_FEATURE_SCREENSHOT_ENABLED - -#include "ScreenshotTask.h" - -#include -#include - -namespace tt::service::screenshot { - -enum class Mode { - None, - Timed, - Apps -}; - -class ScreenshotService final : public Service { - - Mutex mutex; - std::unique_ptr task; - Mode mode = Mode::None; - -public: - - bool onStart(ServiceContext& serviceContext) override; - - bool isTaskStarted(); - - /** The state of the service. */ - Mode getMode() const; - - /** @brief Start taking screenshot whenever an app is started - * @param[in] path the path to store the screenshots at - */ - void startApps(const std::string& path); - - /** @brief Start taking screenshots after a certain delay - * @param[in] path the path to store the screenshots at - * @param[in] delayInSeconds the delay before starting (and between successive screenshots) - * @param[in] amount 0 = indefinite, >0 for a specific - */ - void startTimed(const std::string& path, uint8_t delayInSeconds, uint8_t amount); - - /** @brief Stop taking screenshots */ - void stop(); -}; - -std::shared_ptr optScreenshotService(); - -} // namespace - -#endif diff --git a/Tactility/Private/Tactility/service/screenshot/ScreenshotTask.h b/Tactility/Private/Tactility/service/screenshot/ScreenshotTask.h deleted file mode 100644 index 49a44e720..000000000 --- a/Tactility/Private/Tactility/service/screenshot/ScreenshotTask.h +++ /dev/null @@ -1,62 +0,0 @@ -#include - -#if TT_FEATURE_SCREENSHOT_ENABLED - -#pragma once - -#include -#include - -namespace tt::service::screenshot { - -#define TASK_WORK_TYPE_DELAY 1 -#define TASK_WORK_TYPE_APPS 2 - -class ScreenshotTask { - - struct ScreenshotTaskWork { - int type = TASK_WORK_TYPE_DELAY ; - uint8_t delay_in_seconds = 0; - uint8_t amount = 0; - std::string path; - }; - - Thread* thread = nullptr; - RecursiveMutex mutex; - bool interrupted = false; - bool finished = false; - ScreenshotTaskWork work; - -public: - ScreenshotTask() = default; - ~ScreenshotTask(); - - /** @brief Start taking screenshots after a certain delay - * @param[in] path the path to store the screenshots at - * @param[in] delayInSeconds the delay before starting (and between successive screenshots) - * @param[in] amount 0 = indefinite, >0 for a specific - */ - void startTimed(const std::string& path, uint8_t delayInSeconds, uint8_t amount); - - /** @brief Start taking screenshot whenever an app is started - * @param[in] path the path to store the screenshots at - */ - void startApps(const std::string& path); - - /** @brief Stop taking screenshots */ - void stop(); - - void taskMain(); - - bool isFinished(); - -private: - - bool isInterrupted(); - void setFinished(); - void taskStart(); -}; - -} - -#endif diff --git a/Tactility/Source/app/launcher/AppsMode.cpp b/Tactility/Source/app/launcher/AppsMode.cpp index 514d2bd6e..c38bba5cf 100644 --- a/Tactility/Source/app/launcher/AppsMode.cpp +++ b/Tactility/Source/app/launcher/AppsMode.cpp @@ -33,7 +33,6 @@ constexpr IconEntry ICONS[] = { {"tactility.i2cscanner", LVGL_ICON_SHARED_CABLE}, {"tactility.meshinspect", LVGL_ICON_SHARED_LISTS}, {"tactility.notes", LVGL_ICON_SHARED_EDIT_NOTE}, - {"tactility.screenshot", LVGL_ICON_SHARED_IMAGE}, {"tactility.systeminfo", LVGL_ICON_SHARED_DEVICES}, {"tactility.terminal", LVGL_ICON_SHARED_TERMINAL}, {"tactility.webserversettings", LVGL_ICON_SHARED_CLOUD}, diff --git a/Tactility/Source/app/screenshot/Screenshot.cpp b/Tactility/Source/app/screenshot/Screenshot.cpp index 16d62b7f5..b809d0d60 100644 --- a/Tactility/Source/app/screenshot/Screenshot.cpp +++ b/Tactility/Source/app/screenshot/Screenshot.cpp @@ -1,312 +1,86 @@ -#include #include #if TT_FEATURE_SCREENSHOT_ENABLED #include -#include -#include -#include -#include #include -#include -#include - -#include #include -#include +#include #include -#include #include -#include -#include -#include -#include + +#include + +#include +#include namespace tt::app::screenshot { -constexpr auto* TAG = "Screenshot"; - -extern const ::AppManifest manifest; - namespace { -enum class CaptureMode { - Timer, - AppStart -}; +constexpr TickType_t LVGL_LOCK_TIMEOUT = pdMS_TO_TICKS(1000); -struct ModeOption { - CaptureMode mode; - const char* name; -}; - -constexpr ModeOption MODE_OPTIONS[] = { - { CaptureMode::Timer, "Timer" }, - { CaptureMode::AppStart, "App start" }, -}; - -struct Context { - uint32_t appInstanceId; - CaptureMode mode = CaptureMode::Timer; - lv_obj_t* modeChips = nullptr; - lv_obj_t* pathTextArea = nullptr; - lv_obj_t* startStopButtonLabel = nullptr; - // Shown in timer mode - lv_obj_t* delayRow = nullptr; - lv_obj_t* delayTextArea = nullptr; - std::unique_ptr updateTimer; -}; - - -void updateScreenshotMode(Context* ctx) { - auto service = service::screenshot::optScreenshotService(); - if (service == nullptr) { - LOG_E(TAG, "Service not found/running"); - return; +/** The default path is in the temp folder, so "tt screenshot" can pull and remove it */ +bool getDefaultPath(char* out_path, size_t out_size) { + char temp_path[FILE_MAX_PATH_STRING_LENGTH]; + if (paths_get_temp_path(temp_path, sizeof(temp_path)) != ERROR_NONE) { + return false; } - - lv_obj_t* label = ctx->startStopButtonLabel; - if (service->isTaskStarted()) { - lv_label_set_text(label, "Stop"); - } else { - lv_label_set_text(label, "Start"); - } - - const uint32_t chip_count = lv_obj_get_child_count(ctx->modeChips); - for (uint32_t i = 0; i < chip_count; i++) { - lv_obj_set_state(lv_obj_get_child(ctx->modeChips, static_cast(i)), LV_STATE_CHECKED, MODE_OPTIONS[i].mode == ctx->mode); - } - lv_obj_set_hidden(ctx->delayRow, ctx->mode != CaptureMode::Timer); -} - -void onBackPressed(lv_event_t* event) { - auto* ctx = static_cast(lv_event_get_user_data(event)); - app_event_emit_close(ctx->appInstanceId); -} - -void onStartPressed(lv_event_t* event) { - auto* ctx = static_cast(lv_event_get_user_data(event)); - - auto service = service::screenshot::optScreenshotService(); - if (service == nullptr) { - LOG_E(TAG, "Service not found/running"); - return; - } - - if (service->isTaskStarted()) { - LOG_I(TAG, "Stop screenshot"); - service->stop(); - updateScreenshotMode(ctx); - return; - } - - const char* path = lv_textarea_get_text(ctx->pathTextArea); - - error_t result = directory_make(path, true); - if (result != ERROR_NONE && result != ERROR_ALREADY_EXISTS) { - LOG_E(TAG, "Task failed to start: couldn't create directory at %s", path); - return; - } - - if (ctx->mode == CaptureMode::Timer) { - LOG_I(TAG, "Start timed screenshots"); - const char* delay_text = lv_textarea_get_text(ctx->delayTextArea); - int delay = atoi(delay_text); - if (delay > 0) { - service->startTimed(path, delay, 1); - } else { - LOG_W(TAG, "Ignored screenshot start because delay was 0"); - } - } else { - LOG_I(TAG, "Start app screenshots"); - service->startApps(path); - } - - updateScreenshotMode(ctx); -} - -void onModeChipPressed(lv_event_t* event) { - auto* ctx = static_cast(lv_event_get_user_data(event)); - ctx->mode = MODE_OPTIONS[lv_obj_get_index(lv_event_get_target_obj(event))].mode; - updateScreenshotMode(ctx); -} - -/** A transparent row in a card: "Title [content]" */ -lv_obj_t* createRow(lv_obj_t* card, const char* title) { - auto* row = lv_obj_create(card); - lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - lv_obj_set_style_pad_all(row, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(row, 0, LV_STATE_DEFAULT); - lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, LV_STATE_DEFAULT); - lv_obj_set_scrollable(row, false); - lv_label_set_text(lv_label_create(row), title); - return row; -} - -void createModeWidgets(Context* ctx, lv_obj_t* parent) { - auto* title = lv_label_create(parent); - lv_label_set_text(title, "Mode"); - lv_obj_set_width(title, LV_PCT(100)); - - // The chips are centered in the card while they fit, and scroll when they don't - auto* card = lvgl_card_create(parent); - lv_obj_set_size(card, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(card, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - - ctx->modeChips = lv_obj_create(card); - lv_obj_set_size(ctx->modeChips, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_set_style_max_width(ctx->modeChips, LV_PCT(100), LV_STATE_DEFAULT); - lv_obj_set_flex_flow(ctx->modeChips, LV_FLEX_FLOW_ROW); - lv_obj_set_scroll_dir(ctx->modeChips, LV_DIR_HOR); - lv_obj_set_style_bg_opa(ctx->modeChips, LV_OPA_TRANSP, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(ctx->modeChips, 0, LV_STATE_DEFAULT); - // The chips' margins leave room for their focus rings and space them apart - lv_obj_set_style_pad_all(ctx->modeChips, 0, LV_STATE_DEFAULT); - lv_obj_set_style_pad_column(ctx->modeChips, 0, LV_STATE_DEFAULT); - for (const auto& option : MODE_OPTIONS) { - auto* chip = lvgl_chip_create(ctx->modeChips); - lv_label_set_text(lv_label_create(chip), option.name); - lv_obj_add_event_cb(chip, onModeChipPressed, LV_EVENT_SHORT_CLICKED, ctx); - } - lvgl_grid_navigation_add(ctx->modeChips); -} - -void createSettingsWidgets(Context* ctx, lv_obj_t* parent) { - auto* card = lvgl_card_create(parent); - lv_obj_set_size(card, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN); - - auto* path_row = createRow(card, "Path"); - ctx->pathTextArea = lv_textarea_create(path_row); - lv_textarea_set_one_line(ctx->pathTextArea, true); - lv_obj_set_flex_grow(ctx->pathTextArea, 1); - char data_path[FILE_MAX_PATH_STRING_LENGTH]; - if (paths_get_data_path(data_path, sizeof(data_path)) == ERROR_NONE) { - std::string lvgl_mount_path = std::string(data_path) + "/screenshots"; - lv_textarea_set_text(ctx->pathTextArea, lvgl_mount_path.c_str()); - } else { - lv_textarea_set_text(ctx->pathTextArea, "Error: no data path"); - } - - ctx->delayRow = createRow(card, "Delay"); - ctx->delayTextArea = lv_textarea_create(ctx->delayRow); - lv_textarea_set_one_line(ctx->delayTextArea, true); - lv_textarea_set_accepted_chars(ctx->delayTextArea, "0123456789"); - lv_textarea_set_text(ctx->delayTextArea, "10"); - lv_obj_set_flex_grow(ctx->delayTextArea, 1); - lv_label_set_text(lv_label_create(ctx->delayRow), "seconds"); -} - -void createStartStopButton(Context* ctx, lv_obj_t* parent) { - auto* button = lv_button_create(parent); - lv_obj_add_event_cb(button, onStartPressed, LV_EVENT_SHORT_CLICKED, ctx); - ctx->startStopButtonLabel = lv_label_create(button); -} - -void createWidgets(lv_obj_t* parent, void* userData) { - auto* ctx = static_cast(userData); - - if (ctx->updateTimer->isRunning()) { - ctx->updateTimer->stop(); - } - - lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); - - auto* toolbar = lvgl_toolbar_create(parent, "Screenshot"); - // The global toolbar nav callback only knows how to stop old-model apps. - lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx); - lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); - - auto* wrapper = lv_obj_create(parent); - lv_obj_set_width(wrapper, LV_PCT(100)); - lv_obj_set_flex_grow(wrapper, 1); - lv_obj_set_style_border_width(wrapper, 0, 0); - lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); - // The cards stretch, the start/stop button is centered - lv_obj_set_flex_align(wrapper, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START); - - auto service = service::screenshot::optScreenshotService(); - if (service != nullptr && service->getMode() == service::screenshot::Mode::Apps) { - ctx->mode = CaptureMode::AppStart; - } - - createModeWidgets(ctx, wrapper); - createSettingsWidgets(ctx, wrapper); - createStartStopButton(ctx, wrapper); - - updateScreenshotMode(ctx); - - if (!ctx->updateTimer->isRunning()) { - ctx->updateTimer->start(); - } -} - -int32_t appMain(int argc, char* argv[]) { - uint32_t appInstanceId = app_scheduler_current_app_id(); - Context ctx {}; - ctx.appInstanceId = appInstanceId; - ctx.updateTimer = std::make_unique(Timer::Type::Periodic, 500 / portTICK_PERIOD_MS, [&ctx] { - if (lvgl_try_lock(500 / portTICK_PERIOD_MS)) { - updateScreenshotMode(&ctx); - lvgl_unlock(); - } - }); - - TaskEventGroup event_group {}; - task_event_group_construct(&event_group); - - AppEventSubscription sub {}; - check(app_event_subscribe(&sub, &event_group) == ERROR_NONE); - - WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); - - bool shouldClose = false; - while (!shouldClose) { - task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY); - - AppEvent event {}; - while (app_event_poll(&sub, &event) == ERROR_NONE) { - switch (event.type) { - case APP_EVENT_CLOSE: - shouldClose = true; - break; - default: - break; - } - if (shouldClose) break; + if (!directory_exists(temp_path)) { + const error_t error = directory_make(temp_path, true); + if (error != ERROR_NONE && error != ERROR_ALREADY_EXISTS) { + return false; } } + const int written = snprintf(out_path, out_size, "%s/screenshot.png", temp_path); + return written > 0 && static_cast(written) < out_size; +} - if (ctx.updateTimer->isRunning()) { - ctx.updateTimer->stop(); +int32_t main(int argc, char* argv[]) { + if (argc > 2 || (argc == 2 && argv[1][0] != '/')) { + printf("usage: screenshot [absolute path of the .png file]\n"); + return 1; } - window_manager_remove(window); - check(app_event_unsubscribe(&sub) == ERROR_NONE); - task_event_group_destruct(&event_group); + char path[FILE_MAX_PATH_STRING_LENGTH]; + if (argc == 2) { + snprintf(path, sizeof(path), "%s", argv[1]); + } else if (!getDefaultPath(path, sizeof(path))) { + printf("screenshot: no storage available\n"); + return 1; + } + if (!lvgl_try_lock(LVGL_LOCK_TIMEOUT)) { + printf("screenshot: the display is busy\n"); + return 1; + } + // lv_screenshot writes through lv_fs, which requires the drive prefix + const std::string lvgl_path = std::string(lvgl::PATH_PREFIX) + path; + const bool success = lv_screenshot_create(lv_screen_active(), LV_100ASK_SCREENSHOT_SV_PNG, lvgl_path.c_str()); + lvgl_unlock(); + + if (!success) { + printf("screenshot: failed to save %s\n", path); + return 1; + } + printf("%s\n", path); return 0; } } // namespace extern const ::AppManifest manifest = { - .id = "tactility.screenshot", - .name = "Screenshot", + .id = "screenshot", + .name = "screenshot", .category = APP_CATEGORY_SYSTEM, - .location = { APP_LOCATION_MEMORY, reinterpret_cast(appMain) }, - .flags = 0, - .stack = {} + .location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast(main) }, + .flags = APP_MANIFEST_FLAG_HEADLESS, + .stack = { .depth = 8192, .desired_memory_capability = 0 }, }; -} // namespace +} // namespace tt::app::screenshot #endif diff --git a/Tactility/Source/lvgl/Lvgl.cpp b/Tactility/Source/lvgl/Lvgl.cpp index 8533504c7..c0cb1a1d5 100644 --- a/Tactility/Source/lvgl/Lvgl.cpp +++ b/Tactility/Source/lvgl/Lvgl.cpp @@ -54,9 +54,6 @@ namespace service { namespace displayidle { extern const ServiceManifest manifest; } namespace keyboardidle { extern const ServiceManifest manifest; } #endif -#if TT_FEATURE_SCREENSHOT_ENABLED - namespace screenshot { extern const ServiceManifest manifest; } -#endif } namespace lvgl { @@ -185,9 +182,6 @@ static void onLvglStarted() { #if defined(CONFIG_TT_TDECK_WORKAROUND) addService(service::keyboardidle::manifest); #endif -#if TT_FEATURE_SCREENSHOT_ENABLED - addService(service::screenshot::manifest); -#endif lvgl::startUsbHidInput(); lvgl::startKeyboardDeviceListener(); @@ -207,9 +201,6 @@ static void onLvglStopped() { if (device_exists_of_type(&IMU_TYPE)) { check(service::removeService(service::autorotate::manifest.id)); } -#if TT_FEATURE_SCREENSHOT_ENABLED - check(service::removeService(service::screenshot::manifest.id)); -#endif #if defined(CONFIG_TT_TDECK_WORKAROUND) check(service::removeService(service::keyboardidle::manifest.id)); #endif diff --git a/Tactility/Source/service/screenshot/Screenshot.cpp b/Tactility/Source/service/screenshot/Screenshot.cpp deleted file mode 100644 index af79f8064..000000000 --- a/Tactility/Source/service/screenshot/Screenshot.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include - -#if TT_FEATURE_SCREENSHOT_ENABLED - -#include -#include -#include - -#include -#include - -#include - -namespace tt::service::screenshot { - -constexpr auto* TAG = "ScreenshotService"; - -extern const ServiceManifest manifest; - -std::shared_ptr optScreenshotService() { - return service::findServiceById(manifest.id); -} - -void ScreenshotService::startApps(const std::string& path) { - auto lock = mutex.asScopedLock(); - if (!lock.lock(50 / portTICK_PERIOD_MS)) { - LOG_W(TAG,LOG_MESSAGE_MUTEX_LOCK_FAILED); - return; - } - - if (task == nullptr || task->isFinished()) { - task = std::make_unique(); - mode = Mode::Apps; - task->startApps(path); - } else { - LOG_W(TAG,"Screenshot task already running"); - } -} - -void ScreenshotService::startTimed(const std::string& path, uint8_t delayInSeconds, uint8_t amount) { - auto lock = mutex.asScopedLock(); - if (!lock.lock(50 / portTICK_PERIOD_MS)) { - LOG_W(TAG,LOG_MESSAGE_MUTEX_LOCK_FAILED); - return; - } - - if (task == nullptr || task->isFinished()) { - task = std::make_unique(); - mode = Mode::Timed; - task->startTimed(path, delayInSeconds, amount); - } else { - LOG_W(TAG,"Screenshot task already running"); - } -} - -bool ScreenshotService::onStart(ServiceContext& serviceContext) { - if (lv_screen_active() == nullptr) { - LOG_E(TAG, "No display found"); - return false; - } - - return true; -} - -void ScreenshotService::stop() { - auto lock = mutex.asScopedLock(); - if (!lock.lock(50 / portTICK_PERIOD_MS)) { - LOG_W(TAG,LOG_MESSAGE_MUTEX_LOCK_FAILED); - return; - } - - if (task != nullptr) { - task = nullptr; - mode = Mode::None; - } else { - LOG_W(TAG,"Screenshot task not running"); - } -} - -Mode ScreenshotService::getMode() const { - auto lock = mutex.asScopedLock(); - if (!lock.lock(50 / portTICK_PERIOD_MS)) { - LOG_W(TAG,LOG_MESSAGE_MUTEX_LOCK_FAILED); - return Mode::None; - } - - return mode; -} - -bool ScreenshotService::isTaskStarted() { - auto* current_task = task.get(); - if (current_task == nullptr) { - return false; - } else { - return !current_task->isFinished(); - } -} - -extern const ServiceManifest manifest = { - .id = "tactility.screenshot", - .createService = create -}; - -} // namespace - -#endif \ No newline at end of file diff --git a/Tactility/Source/service/screenshot/ScreenshotTask.cpp b/Tactility/Source/service/screenshot/ScreenshotTask.cpp deleted file mode 100644 index ca3e8a494..000000000 --- a/Tactility/Source/service/screenshot/ScreenshotTask.cpp +++ /dev/null @@ -1,182 +0,0 @@ -#include - -#if TT_FEATURE_SCREENSHOT_ENABLED - -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include - -#include - -#include - -namespace tt::service::screenshot { - -constexpr auto* TAG = "ScreenshotTask"; - -ScreenshotTask::~ScreenshotTask() { - if (thread) { - stop(); - } -} - -bool ScreenshotTask::isInterrupted() { - auto lock = mutex.asScopedLock(); - if (!lock.lock(50 / portTICK_PERIOD_MS)) { - LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); - return true; - } - return interrupted; -} - -bool ScreenshotTask::isFinished() { - auto lock = mutex.asScopedLock(); - if (!lock.lock(50 / portTICK_PERIOD_MS)) { - LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); - return false; - } - return finished; -} - -void ScreenshotTask::setFinished() { - auto lock = mutex.asScopedLock(); - lock.lock(); - finished = true; -} - -static void makeScreenshot(const std::string& path) { - if (lvgl_try_lock(50 / portTICK_PERIOD_MS)) { - std::string lvgl_path = std::string(lvgl::PATH_PREFIX) + path; - if (lv_screenshot_create(lv_scr_act(), LV_100ASK_SCREENSHOT_SV_PNG, lvgl_path.c_str())) { - LOG_I(TAG, "Screenshot saved to %s", path.c_str()); - } else { - LOG_E(TAG, "Screenshot not saved to %s", path.c_str()); - } - lvgl_unlock(); - } else { - LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL"); - } -} - -void ScreenshotTask::taskMain() { - uint8_t screenshots_taken = 0; - uint32_t last_app_instance_id = 0; - - while (!isInterrupted()) { - if (work.type == TASK_WORK_TYPE_DELAY) { - // Splitting up the delays makes it easier to stop the service - for (int i = 0; i < (work.delay_in_seconds * 10) && !isInterrupted(); ++i){ - delay_millis(100); - } - - if (!isInterrupted()) { - screenshots_taken++; - std::string filename = std::format("{}/screenshot-{}.png", work.path, screenshots_taken); - makeScreenshot(filename); - - if (work.amount > 0 && screenshots_taken >= work.amount) { - break; // Interrupted loop - } - } - } else if (work.type == TASK_WORK_TYPE_APPS) { - AppInstanceId app_instance_id = 0; - bool has_topmost = app_manager_get_topmost_instance_id(&app_instance_id) == ERROR_NONE; - if (has_topmost && app_instance_id != last_app_instance_id) { - delay_millis(100); - last_app_instance_id = app_instance_id; - auto filename = std::format("{}/screenshot-{}.png", work.path, app_instance_id); - makeScreenshot(filename); - } - // Ensure the LVGL widgets are rendered as the app just started - delay_millis(250); - } - } - - setFinished(); -} - -void ScreenshotTask::taskStart() { - auto lock = mutex.asScopedLock(); - if (!lock.lock(50 / portTICK_PERIOD_MS)) { - LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); - return; - } - - check(thread == nullptr); - thread = new Thread( - "screenshot", - 8192, - [this] { - this->taskMain(); - return 0; - }, - getCpuAffinityConfiguration().graphics - ); - thread->start(); -} - -void ScreenshotTask::startApps(const std::string& path) { - auto lock = mutex.asScopedLock(); - if (!lock.lock(50 / portTICK_PERIOD_MS)) { - LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); - return; - } - - if (thread == nullptr) { - interrupted = false; - work.type = TASK_WORK_TYPE_APPS; - work.path = path; - taskStart(); - } else { - LOG_E(TAG, "Task was already running"); - } -} - -void ScreenshotTask::startTimed(const std::string& path, uint8_t delay_in_seconds, uint8_t amount) { - auto lock = mutex.asScopedLock(); - if (!lock.lock(50 / portTICK_PERIOD_MS)) { - LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); - return; - } - - if (thread == nullptr) { - interrupted = false; - work.type = TASK_WORK_TYPE_DELAY; - work.delay_in_seconds = delay_in_seconds; - work.amount = amount; - work.path = path; - taskStart(); - } else { - LOG_E(TAG, "Task was already running"); - } -} - -void ScreenshotTask::stop() { - if (thread != nullptr) { - if (mutex.lock(50 / portTICK_PERIOD_MS)) { - interrupted = true; - mutex.unlock(); - } - - thread->join(); - - if (mutex.lock(50 / portTICK_PERIOD_MS)) { - delete thread; - thread = nullptr; - mutex.unlock(); - } - } -} - -} // namespace - -#endif