Merge pull request #15961 from keymanapp/fix/windows/clone-state-actions-deep-copy

fix(core): clone state actions deep copy
This commit is contained in:
rc-swag 2026-07-30 10:24:42 +10:00 committed by GitHub
commit 5cc2444cd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 96 additions and 3 deletions

View file

@ -125,8 +125,16 @@ namespace km {
{
assert(state);
assert(action_item);
if ((!state) || (!action_item))
if ((!state) || (!action_item)) {
return false;
}
// For this mock processor we only support queuing PERSIST_OPT action items.
if (action_item->type == KM_CORE_IT_PERSIST_OPT && action_item->option) {
state->actions().push_persist(update_option(static_cast<km_core_option_scope>(action_item->option->scope),
action_item->option->key,
action_item->option->value));
return true;
}
return false;
}
@ -166,6 +174,18 @@ namespace km {
u"F2 pressed test save."));
break;
}
case KM_CORE_VKEY_F3:
{
state->actions().push_persist(
update_option(KM_CORE_OPT_KEYBOARD,
u"__test_point_3",
u"F3 pressed test save 1."));
state->actions().push_persist(
update_option(KM_CORE_OPT_KEYBOARD,
u"__test_point_4",
u"F3 pressed test save 2."));
break;
}
case KM_CORE_VKEY_F4:
state->context().push_marker(KM_CORE_VKEY_QUOTE);

View file

@ -36,6 +36,23 @@ void actions::push_capslock(bool turnOn) {
emplace_back(std::move(ai));
}
actions::actions(actions const &other)
: std::vector<action>(other)
, _option_items_stack(other._option_items_stack)
{
// Update all option pointers to point to the new stack.
size_t opt_index = 0;
for (auto &item : *this) {
if (item.type == KM_CORE_IT_PERSIST_OPT) {
if (opt_index < _option_items_stack.size()) {
item.option = &_option_items_stack[opt_index++];
} else {
// no matching item in the stack; clear pointer.
item.option = nullptr;
}
}
}
}
state::state(km::core::abstract_processor & ap, km_core_option_item const *env)
: _processor(ap)

View file

@ -10,6 +10,7 @@
#include <cassert>
#include <vector>
#include <deque>
#include "keyman_core.h"
@ -27,7 +28,7 @@ using action = km_core_action_item;
class actions : public std::vector<action>
{
std::vector<option> _option_items_stack;
std::deque<option> _option_items_stack;
template<km_core_action_type V>
void _push_vkey(km_core_virtual_key);
@ -35,6 +36,10 @@ class actions : public std::vector<action>
public:
template<typename... Args>
actions(Args&&... args);
actions(actions const &other);
// If the operator is needed in the future, it shall be implemented.
// Currently blocking accidental use of it for now.
actions &operator=(actions const &) = delete;
void push_character(km_core_usv usv);
void push_marker(uint32_t marker);

View file

@ -251,6 +251,24 @@ constexpr km_core_option_item const expected_persist_opt = {
KM_CORE_OPT_KEYBOARD
};
constexpr km_core_option_item const clone_persist_opt = {
u"__test_clone",
u"Not in original",
KM_CORE_OPT_KEYBOARD
};
constexpr km_core_option_item const test_point_3_opt = {
u"__test_point_3",
u"F3 pressed test save 1.",
KM_CORE_OPT_KEYBOARD
};
constexpr km_core_option_item const test_point_4_opt = {
u"__test_point_4",
u"F3 pressed test save 2.",
KM_CORE_OPT_KEYBOARD
};
extern "C"
{
uint8_t test_imx_callback(km_core_state *state, uint32_t imx_id, void *callback_object){
@ -269,7 +287,8 @@ int main(int argc, char * argv[])
km_core_keyboard * test_kb = nullptr;
km_core_state * test_state = nullptr,
* test_clone = nullptr;
* test_clone = nullptr,
* test_clone_2 = nullptr;
test_kb = (km_core_keyboard *)new km::core::mock_processor(km::core::path("dummy.mock"));
// Simple sanity tests.
@ -394,9 +413,41 @@ int main(int argc, char * argv[])
clone_state_deleted_text
));
// Add two actions before cloning the state again
try_status(km_core_process_event(test_state, KM_CORE_VKEY_F3, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
try_status(km_core_state_clone(test_state, &test_clone_2));
// Now put an option in the test_clone_2 state only
km_core_action_item action_clone = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action_clone.option = &clone_persist_opt;
if (test_clone_2->actions().back().type == KM_CORE_IT_END) {
test_clone_2->actions().pop_back();
}
km_core_state_queue_action_items(test_clone_2, &action_clone);
test_clone_2->actions().commit();
// Test debug dump
auto doc3 = get_json_doc(*test_state), doc4 = get_json_doc(*test_clone_2);
std::cout << "doc3:" << std::endl;
std::cout << doc3 << std::endl;
std::cout << "doc4:" << std::endl;
std::cout << doc4 << std::endl;
if (doc3 == doc4) return __LINE__;
km_core_action_item action_tp3 = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action_tp3.option = &test_point_3_opt;
km_core_action_item action_tp4 = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action_tp4.option = &test_point_4_opt;
test_assert(action_items(test_state, {action_tp3, action_tp4, {KM_CORE_IT_END}}));
// Check that test_clone_2 has the same persisted options plus the extra queued option.
test_assert(action_items(test_clone_2, {action_tp3, action_tp4, action_clone, {KM_CORE_IT_END}}));
// Destroy them
km_core_state_dispose(test_state);
km_core_state_dispose(test_clone);
km_core_state_dispose(test_clone_2);
km_core_keyboard_dispose(test_kb);
return 0;