Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ext_components/cp0_lvgl/include/cp0_lvgl_app.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ int cp0_sudo_run_argv_async_ex(const char *const *argv,
/* Cancellation is idempotent while a request ID remains in the bounded recent
* completion history. Returns -ENOENT only for an unknown or expired ID. */
int cp0_sudo_cancel(uint64_t request_id);
int cp0_sudo_queue_password(const char *password);
int cp0_file_read_first_line(const char *path, char *out, int out_size);
int cp0_desktop_exec_is_safe(const char *exec, char *reason, int reason_size);
int cp0_network_default_info_read(cp0_eth_info_t *info);
Expand Down
1 change: 1 addition & 0 deletions ext_components/cp0_lvgl/include/keyboard_input.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ extern volatile uint32_t LV_EVENT_KEYBOARD;

void *keyboard_read_thread(void *argv);
int cp0_keyboard_inject(uint32_t key_code, int key_state, uint32_t mods);
int cp0_keyboard_inject_text(const char *utf8);
const char *kbd_state_name(int state);
void kbd_dump_keymap_table(void);
#ifdef __cplusplus
Expand Down
26 changes: 26 additions & 0 deletions ext_components/cp0_lvgl/src/cp0/cp0_lvgl_keyboard.c
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,32 @@ int cp0_keyboard_inject(uint32_t key_code, int key_state, uint32_t mods)
return 0;
}

int cp0_keyboard_inject_text(const char *utf8)
{
if (!utf8)
return -1;

const unsigned char *cursor = (const unsigned char *)utf8;
while (*cursor) {
size_t length = 1;
if ((*cursor & 0xe0) == 0xc0) length = 2;
else if ((*cursor & 0xf0) == 0xe0) length = 3;
else if ((*cursor & 0xf8) == 0xf0) length = 4;

for (size_t i = 1; i < length; ++i)
if (!cursor[i] || (cursor[i] & 0xc0) != 0x80)
return -1;

struct key_item item = {0};
item.key_state = KBD_KEY_RELEASED;
memcpy(item.utf8, cursor, length);
snprintf(item.sym_name, sizeof(item.sym_name), "RPC_TEXT");
enqueue_key(&item);
cursor += length;
}
return 0;
}

/* ============================================================
* Key repeat control
* ============================================================ */
Expand Down
40 changes: 40 additions & 0 deletions ext_components/cp0_lvgl/src/cp0/cp0_lvgl_rpc.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "cp0_lvgl.h"
#include "cp0_lvgl_app.h"

#ifndef CP0_LVGL_USE_ZMQ_RPC
#define CP0_LVGL_USE_ZMQ_RPC 0
Expand Down Expand Up @@ -47,6 +48,13 @@ const char *env_or_default(const char *name, const char *fallback)
return value && value[0] ? value : fallback;
}

void secure_clear(std::string &value)
{
volatile char *data = value.empty() ? nullptr : &value[0];
for (size_t i = 0; data && i < value.size(); ++i) data[i] = 0;
value.clear();
}

bool capture_ppm(std::vector<uint8_t> &out, std::string &error)
{
const char *device = env_or_default("APPLAUNCH_LINUX_FBDEV_DEVICE", "/dev/fb0");
Expand Down Expand Up @@ -186,6 +194,38 @@ void rpc_broker(std::shared_ptr<zmq::context_t> context)
send_text(rep, "OK key");
continue;
}
if (command == "text") {
std::string value;
std::getline(input, value);
if (!value.empty() && value.front() == ' ') value.erase(0, 1);
if (value.empty()) {
send_text(rep, "ERR usage: text <utf8>");
continue;
}
if (cp0_keyboard_inject_text(value.c_str()) != 0) {
send_text(rep, "ERR invalid utf8 text");
continue;
}
send_text(rep, "OK text");
continue;
}
if (command == "password") {
std::string value;
std::getline(input, value);
if (!value.empty() && value.front() == ' ') value.erase(0, 1);
if (value.empty()) {
send_text(rep, "ERR usage: password <value>");
continue;
}
const int result = cp0_sudo_queue_password(value.c_str());
secure_clear(value);
if (result != 0) {
send_text(rep, "ERR unable to queue password " + std::to_string(result));
continue;
}
send_text(rep, "OK password");
continue;
}
if (command == "screenshot") {
std::vector<uint8_t> ppm;
std::string error;
Expand Down
96 changes: 73 additions & 23 deletions ext_components/cp0_lvgl/src/cp0_sudo_async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <cstring>
#include <iterator>
#include <memory>
#include <mutex>
#include <new>
#include <string>
#include <thread>
Expand Down Expand Up @@ -60,6 +61,9 @@ lv_group_t *g_prompt_group = nullptr;
lv_timer_t *g_timer = nullptr;
std::shared_ptr<Request> g_prompt_request;
std::string g_password;
std::mutex g_queued_password_mutex;
std::string g_queued_password;
int64_t g_queued_password_deadline_ms = 0;

int64_t now_ms()
{
Expand Down Expand Up @@ -142,6 +146,30 @@ bool authentication_error(const std::string &output)

void key_event_cb(lv_event_t *event);
void execute_actions(std::vector<Action> actions);
void submit_password();
void apply_queued_password(void *);

void clear_queued_password()
{
std::lock_guard<std::mutex> lock(g_queued_password_mutex);
secure_clear(g_queued_password);
g_queued_password_deadline_ms = 0;
}

bool take_queued_password(std::string &password)
{
std::lock_guard<std::mutex> lock(g_queued_password_mutex);
if (g_queued_password.empty()) return false;
if (g_queued_password_deadline_ms > 0 && now_ms() > g_queued_password_deadline_ms) {
secure_clear(g_queued_password);
g_queued_password_deadline_ms = 0;
return false;
}
password = std::move(g_queued_password);
g_queued_password.clear();
g_queued_password_deadline_ms = 0;
return true;
}

void destroy_prompt()
{
Expand All @@ -158,6 +186,7 @@ void destroy_prompt()
g_hint_label = nullptr;
g_prompt_request.reset();
secure_clear(g_password);
clear_queued_password();
}

void update_password_label()
Expand Down Expand Up @@ -217,6 +246,7 @@ void create_prompt(const std::shared_ptr<Request> &request)
lv_group_add_obj(g_prompt_group, g_overlay);
lv_group_set_default(g_prompt_group);
if (lv_indev_t *indev = lv_indev_get_next(nullptr)) lv_indev_set_group(indev, g_prompt_group);
lv_async_call(apply_queued_password, nullptr);
}

void show_auth_error(const std::shared_ptr<Request> &request)
Expand Down Expand Up @@ -301,41 +331,31 @@ void run_worker(std::shared_ptr<Request> request, std::string password)
if (!resolve_run_user(identity)) exit_code = -EPERM;
else {
std::string password_line = password + "\n";
int auth_timeout = request->auth_timeout_ms;
if (auth_timeout > 0)
auth_timeout = static_cast<int>(std::max<int64_t>(1, request->deadline_ms - now_ms()));
auto auth = cp0_runner::run({"sudo", "-k", "-S", "-p", "", "-v"},
&password_line, {}, &request->cancel_requested, auth_timeout,
kMaxCapturedOutputBytes, identity.uid, identity.gid, identity.name,
identity.home, identity.shell);
secure_clear(password_line);
if (auth.exit_code != 0) {
exit_code = auth.exit_code;
result = auth.exit_code == -ECANCELED ? CP0_SUDO_RESULT_CANCELLED :
auth.exit_code == -ETIMEDOUT ? CP0_SUDO_RESULT_TIMED_OUT :
authentication_error(auth.output) ? CP0_SUDO_RESULT_AUTH_FAILED :
CP0_SUDO_RESULT_EXEC_FAILED;
auto actions = g_coordinator.worker_auth_result(request->id, result, exit_code, now_ms());
secure_clear(password);
if (!actions.empty() && !post_actions(actions))
g_coordinator.requeue_actions(std::move(actions));
return;
}
std::vector<std::string> command;
if (request->use_login_shell)
command = {"sudo", "-n", "--", identity.shell, "-c", request->argv.front()};
command = {"sudo", "-k", "-S", "-p", "", "--", identity.shell, "-c",
request->argv.front()};
else {
command = {"sudo", "-n", "--"};
command = {"sudo", "-k", "-S", "-p", "", "--"};
command.insert(command.end(), request->argv.begin(), request->argv.end());
}
auto execution = cp0_runner::run(std::move(command), nullptr,
auto execution = cp0_runner::run(std::move(command), &password_line,
[request](const char *data, size_t size) { stream_output(request, data, size); },
&request->cancel_requested, request->exec_timeout_ms, kMaxCapturedOutputBytes,
identity.uid, identity.gid, identity.name, identity.home, identity.shell);
secure_clear(password_line);
exit_code = execution.exit_code;
result = exit_code == -ECANCELED ? CP0_SUDO_RESULT_CANCELLED :
exit_code == -ETIMEDOUT ? CP0_SUDO_RESULT_TIMED_OUT :
authentication_error(execution.output) ? CP0_SUDO_RESULT_AUTH_FAILED :
exit_code == 0 ? CP0_SUDO_RESULT_SUCCESS : CP0_SUDO_RESULT_EXEC_FAILED;
if (result == CP0_SUDO_RESULT_AUTH_FAILED) {
auto actions = g_coordinator.worker_auth_result(request->id, result, exit_code, now_ms());
secure_clear(password);
if (!actions.empty() && !post_actions(actions))
g_coordinator.requeue_actions(std::move(actions));
return;
}
}
#endif
secure_clear(password);
Expand Down Expand Up @@ -398,6 +418,18 @@ void submit_password()
execute_actions(g_coordinator.submit_password(g_prompt_request->id));
}

void apply_queued_password(void *)
{
if (!g_prompt_request || g_coordinator.state(g_prompt_request->id) != cp0_sudo::State::PROMPT)
return;
std::string password;
if (!take_queued_password(password)) return;
secure_clear(g_password);
g_password = std::move(password);
update_password_label();
submit_password();
}

void key_event_cb(lv_event_t *event)
{
auto *key = static_cast<key_item *>(lv_event_get_param(event));
Expand Down Expand Up @@ -516,6 +548,24 @@ extern "C" int cp0_sudo_cancel(uint64_t request_id)
return 0;
}

extern "C" int cp0_sudo_queue_password(const char *password)
{
if (!password || !password[0]) return -EINVAL;
const size_t length = std::strlen(password);
if (length > kMaxPasswordBytes) return -E2BIG;
{
std::lock_guard<std::mutex> lock(g_queued_password_mutex);
secure_clear(g_queued_password);
g_queued_password.assign(password, length);
g_queued_password_deadline_ms = now_ms() + 60000;
}
if (lv_async_call(apply_queued_password, nullptr) != LV_RESULT_OK) {
clear_queued_password();
return -EIO;
}
return 0;
}

extern "C" void init_sudo_signals(void)
{
cp0_signal_sudo_argv_async.append([](std::list<std::string> args, int auth_timeout_ms,
Expand Down
34 changes: 28 additions & 6 deletions projects/APPLaunch/main/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
Expand All @@ -7,6 +11,7 @@
#include "lvgl/lvgl.h"
#include "lvgl/demos/lv_demos.h"
#include <stdio.h>
#include <errno.h>
#include <chrono>
#include <string>
#include <semaphore.h>
Expand All @@ -26,6 +31,28 @@

static sem_t lvgl_sem;

static void lvgl_wait(uint32_t milliseconds)
{
struct timespec deadline;
#if defined(__linux__)
clock_gettime(CLOCK_MONOTONIC, &deadline);
#else
clock_gettime(CLOCK_REALTIME, &deadline);
#endif
deadline.tv_nsec += (milliseconds % 1000) * 1000000;
deadline.tv_sec += milliseconds / 1000 + deadline.tv_nsec / 1000000000;
deadline.tv_nsec %= 1000000000;

int result;
do {
#if defined(__linux__)
result = sem_clockwait(&lvgl_sem, CLOCK_MONOTONIC, &deadline);
#else
result = sem_timedwait(&lvgl_sem, &deadline);
#endif
} while (result != 0 && errno == EINTR);
}

static void lvgl_resume_cb(void *data) {
sem_post(&lvgl_sem);
}
Expand Down Expand Up @@ -60,12 +87,7 @@ int main(void)
if (ms == LV_NO_TIMER_READY) {
sem_wait(&lvgl_sem); // 无定时器,阻塞等事件
} else {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_nsec += (ms % 1000) * 1000000;
ts.tv_sec += ms / 1000 + ts.tv_nsec / 1000000000;
ts.tv_nsec %= 1000000000;
sem_timedwait(&lvgl_sem, &ts); // 定时唤醒 或 被事件提前唤醒
lvgl_wait(ms); // 定时唤醒或被事件提前唤醒,不受系统时间调整影响
}
}

Expand Down
10 changes: 10 additions & 0 deletions scripts/cp0_rpc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import argparse
import getpass
import pathlib
import sys
import time
Expand Down Expand Up @@ -52,6 +53,10 @@ def main() -> int:
tap = sub.add_parser("tap")
tap.add_argument("key", type=key_code)
tap.add_argument("--duration", type=float, default=0.08)
text = sub.add_parser("text")
text.add_argument("value")
password = sub.add_parser("password")
password.add_argument("value", nargs="?")
hold = sub.add_parser("hold")
hold.add_argument("key", type=key_code)
hold.add_argument("seconds", type=float)
Expand Down Expand Up @@ -80,6 +85,11 @@ def main() -> int:
send_key(socket, args.key, 1)
time.sleep(max(0.0, args.duration))
send_key(socket, args.key, 0)
elif args.command == "text":
request(socket, f"text {args.value}")
elif args.command == "password":
value = args.value if args.value is not None else getpass.getpass("Password: ")
request(socket, f"password {value}")
elif args.command == "hold":
send_key(socket, args.key, 1)
try:
Expand Down
Loading