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
32 changes: 18 additions & 14 deletions doc/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,21 +525,25 @@ It is possible to emulate the tmux-like keyboard prefix approach by using a glob
```xml
<config>
<events>
<applet> <!-- Key bindings for the application window. -->
<if_mod_on="if (not kbmodifier) then return; end;"/> <!-- `if_mod_on` macro definition. Do nothing if `kbmodifier` is false. -->
<desktop>
<script="kbmodifier = not kbmodifier; log('kbmodifier=', kbmodifier);" on="Ctrl+B"/> <!-- Emulate tmux-like prefix key. The expression `log('kbmodifier=', kbmodifier);` is for debugging purposes only (the output is visible in the `Log Monitor`). -->
<script=if_mod_on | MoveAppletLeft on="preview: LeftArrow" /> <!-- The ` | ` operator concatenates script fragments/macros. -->
<script=if_mod_on | MoveAppletRight on="preview: RightArrow"/> <!-- Use "preview:..." to get the key event before the terminal/application. -->
<script=if_mod_on | MoveAppletUp on="preview: UpArrow" /> <!-- When kbmodifier is true, you can move windows using the arrow keys. -->
<script=if_mod_on | MoveAppletDown on="preview: DownArrow" /> <!-- Macros like `MoveApplet...` are defined in the default configuration. You can list them with `vtm -l`. -->
<script=if_mod_on | MoveAppletTopLeft on="preview: LeftArrow+UpArrow | UpArrow+LeftArrow" /> <!-- Simultaneous key presses should also be processed if supported. -->
<script=if_mod_on | MoveAppletBottomLeft on="preview: LeftArrow+DownArrow | DownArrow+LeftArrow" /> <!-- It is convenient to specify multiple keyboard shortcuts in one definition separated by `|`. -->
<script=if_mod_on | MoveAppletTopRight on="preview: RightArrow+UpArrow | UpArrow+RightArrow" />
<script=if_mod_on | MoveAppletBottomRight on="preview: RightArrow+DownArrow | DownArrow+RightArrow"/>
<script=if_mod_on | IncreaseAppletWidth on="preview: Ctrl+RightArrow" />
<script=if_mod_on | DecreaseAppletWidth on="preview: Ctrl+LeftArrow" />
<script=if_mod_on | IncreaseAppletHeight on="preview: Ctrl+DownArrow" />
<script=if_mod_on | DecreaseAppletHeight on="preview: Ctrl+UpArrow" />
</desktop>
<applet> <!-- Key bindings for the application window. -->
<KeyFilter="if (not kbmodifier and vtm.gear.Bypass()) then return; end; "/> <!-- `KeyFilter` macro. Do nothing if `kbmodifier` is false. Calling vtm.gear.Bypass() always returns true. -->
<script=KeyFilter | MoveAppletLeft prerun=KeyFilter on="LeftArrow" /> <!-- The ` | ` operator concatenates script fragments/macros. If for some reason the keyboard event is not processed by anyone, it will then return and fire on this object, so the KeyFilter is also reused at the beginning of the `script="..."`. -->
<script=KeyFilter | MoveAppletRight prerun=KeyFilter on="RightArrow" /> <!-- The `prerun` attribute contains a Lua script that will be executed during pre-polling to filter out key events. -->
<script=KeyFilter | MoveAppletUp prerun=KeyFilter on="UpArrow" /> <!-- When kbmodifier is true, you can move windows using the arrow keys. -->
<script=KeyFilter | MoveAppletDown prerun=KeyFilter on="DownArrow" /> <!-- Macros like `MoveApplet...` are defined in the default configuration. You can list them with `vtm -l`. -->
<script=KeyFilter | MoveAppletTopLeft prerun=KeyFilter on="LeftArrow+UpArrow | UpArrow+LeftArrow" /> <!-- Simultaneous key presses should also be processed if supported. -->
<script=KeyFilter | MoveAppletBottomLeft prerun=KeyFilter on="LeftArrow+DownArrow | DownArrow+LeftArrow" /> <!-- It is convenient to specify multiple keyboard shortcuts in one definition separated by `|`. -->
<script=KeyFilter | MoveAppletTopRight prerun=KeyFilter on="RightArrow+UpArrow | UpArrow+RightArrow" />
<script=KeyFilter | MoveAppletBottomRight prerun=KeyFilter on="RightArrow+DownArrow | DownArrow+RightArrow"/>
<script=KeyFilter | IncreaseAppletWidth prerun=KeyFilter on="Ctrl+RightArrow" />
<script=KeyFilter | DecreaseAppletWidth prerun=KeyFilter on="Ctrl+LeftArrow" />
<script=KeyFilter | IncreaseAppletHeight prerun=KeyFilter on="Ctrl+DownArrow" />
<script=KeyFilter | DecreaseAppletHeight prerun=KeyFilter on="Ctrl+UpArrow" />
<script=KeyFilter | FocusPrevWindow prerun=KeyFilter on="PageUp" />
<script=KeyFilter | FocusNextWindow prerun=KeyFilter on="PageDown" />
</applet>
</events>
</config>
Expand Down
23 changes: 13 additions & 10 deletions doc/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ The syntax for defining event bindings is:

```xml
<dom_element1>
<script="script body" on="EventID1" ... on="preview:EventId2" ...>
<script="script body" prerun="prerun script body" on="EventID1" ... on="preview:EventId2" ...>
<on="EventID"/>
<on="EventID" source="OptionalEventSourceObjectID"/>
...
Expand All @@ -416,6 +416,7 @@ Tag | Belongs to | Value | Description
`<dom_element>` | | ObjectID | Visual tree object id.
`id` | `<dom_element>` | UTF-8 string | Additional id for the visual tree object.
`script` | `<dom_element>` | UTF-8 string | A Lua script that will be executed when the events specified by the `on` tags occur.
`prerun` | `script` | UTF-8 string | A Lua script that will be executed during pre-polling prior the non-preview keyboard events specified in the `on` tags occurs. This is mostly used to indicate that the event is not expected on the source object.
`on` | `script` | EventID | Specific event id text string.
`source` | `on` | ObjectID | Visual tree object id.

Expand Down Expand Up @@ -594,6 +595,7 @@ Standard object names
| | | `vtm.gear.Interrupt()` | Interrupt the key event processing.
| | | `vtm.gear.RepeatWhilePressed(ref ObjectId)` | Capture the mouse by ObjectId and trigger the mouse button pressed event to repeat while pressed.
| | | `vtm.gear.Focus(ref ObjectId) -> bool` | Set input focus to the object. Returns true if focus is already set.
| | | `vtm.gate.Bypass() -> bool` | Indicates that the keyboard event being processed is not expected by this object. Used by the `prerun` function inside `script` to bypass tier::keybdrelease events. Always return true.
|`desktop` | Desktop environment | `vtm.desktop.Cleanup(bool b)` | Clean up temporary internal structures of the desktop environment and optionally report the state of registry objects.
| | | `vtm.desktop.EventList()` | Print all available generic event IDs.
| | | `vtm.desktop.Shutdown()` | Close all windows and shutdown the desktop.
Expand Down Expand Up @@ -666,14 +668,15 @@ Standard object names

Key bindings:

Configuration | Interpretation
---------------------------------------------------------------|-----------------
`<script=ScriptReference on="Key+Chord"/>` | Append existing bindings using an indirect reference (the `ScriptReference` variable without quotes).
`<script="text" on="Key+Chord \| Another+Chord"/>` | Append existing bindings for `Key+Chord | Another+Chord`.
`<script="text" on="Key+Chord"/>` | Append existing bindings with the directly specified Lua script body.
`<script="text"><on="Key+Chord" source="ObjectID"/></script>` | Binding to an event source using a specific ObjectID.
`<script="" on="Key+Chord"/>` | Remove all existing bindings for the specified key combination "Key+Chord".
`<script="..." on="" />` | Do nothing.
Configuration | Interpretation
-------------------------------------------------------------|-----------------
`<script=ScriptReference on="KeyChord"/>` | Append existing bindings using an indirect reference (the `ScriptReference` variable without quotes).
`<script="..." on="KeyChord \| AnotherChord"/>` | Append existing bindings for `KeyChord | AnotherChord`.
`<script="..." on="KeyChord"/>` | Append existing bindings with the directly specified Lua script body.
`<script="..."><on="KeyChord" source="ObjectID"/></script>` | Binding to an event source using a specific `ObjectID`.
`<script="" on="KeyChord"/>` | Remove all existing bindings for the specified key combination `KeyChord`.
`<script="..." on="KeyChord" prerun="if (something) vtm.gear.Bypass() end"/>` | Bypass the `KeyChord` event if something. Works only with non-preview KeyChords.
`<script="..." on="" />` | Do nothing.

EventId's:

Expand Down Expand Up @@ -1143,7 +1146,7 @@ Notes
<script="vtm.defapp.ShowClosingPreview(faux);" on="Any" /> <!-- Preview for "Any" is always triggered after all other previews. Non-preview "Any" is triggered before all other keys. -->
<script="if (not vtm.gear.IsKeyRepeated()) then vtm.defapp.ShowClosingPreview(true); end" on="Esc" /> <!-- Window pred-close action (close when releasing the Esc key). -->
<script="if (vtm.defapp.ShowClosingPreview()) then vtm.defapp.Close() end" on="preview:-Esc"/> <!-- Close the window on Esc release. -->
<script="if (vtm.defapp.ShowClosingPreview()) then local focus_count=vtm(); vtm.defapp.ShowClosingPreview(focus_count~=0) end" source="applet" on="release:e2::form::state::focus::count" /> <!-- Disable window closing preview when focus is lost. -->
<script="if (vtm.defapp.ShowClosingPreview()) then local focus_count=vtm(); vtm.defapp.ShowClosingPreview(focus_count~=0) end" on="release:e2::form::state::focus::count" /> <!-- Disable window closing preview when focus is lost. -->
<script="vtm.defapp.ScrollViewportByPage( 0, 1)" on="PageUp" />
<script="vtm.defapp.ScrollViewportByPage( 0,-1)" on="PageDown" />
<script="vtm.defapp.ScrollViewportByStep( 0, 3)" on="UpArrow" />
Expand Down
2 changes: 1 addition & 1 deletion src/netxs/desktopio/ansivt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ namespace netxs::ansi
{
auto count = 1;
auto width = 0_sz;
auto total = std::count(text::begin(), text::end(), '\n');
auto total = std::count(text::begin(), text::end(), '\n') + 1;
while (total)
{
total /= 10;
Expand Down
2 changes: 1 addition & 1 deletion src/netxs/desktopio/application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace netxs::app

namespace netxs::app::shared
{
static const auto version = "v2025.10.17";
static const auto version = "v2025.10.18";
static const auto repository = "https://github.com/directvt/vtm";
static const auto usr_config = "~/.config/vtm/settings.xml"s;
static const auto sys_config = "/etc/vtm/settings.xml"s;
Expand Down
5 changes: 5 additions & 0 deletions src/netxs/desktopio/console.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,11 @@ namespace netxs::ui
gear.indexer.expire();
luafx.set_return();
}},
{ "Bypass", [&]
{
gear.touched = {};
luafx.set_return(true);
}},
{ "SetHandled", [&]
{
auto dismiss = luafx.get_args_or(1, faux);
Expand Down
2 changes: 1 addition & 1 deletion src/netxs/desktopio/controls.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1759,7 +1759,7 @@ namespace netxs::ui
keybd(base&&) = delete;
keybd(base& boss)
: skill{ boss },
instance_id{ datetime::now().time_since_epoch().count() }
instance_id{ datetime::uniqueid() }
{
boss.LISTEN(tier::general, input::events::die, gear, memo)
{
Expand Down
3 changes: 3 additions & 0 deletions src/netxs/desktopio/events.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ namespace netxs::events
static constexpr auto mouserelease = __COUNTER__ - counter; // events: Run in subscription order for object tree.
static constexpr auto keybdpreview = __COUNTER__ - counter; // events: Run in subscription order for focused objects.
static constexpr auto keybdrelease = __COUNTER__ - counter; // events: Run in subscription order for focused objects.
static constexpr auto keybd_prerun = __COUNTER__ - counter; // events: Run in subscription order for focused objects (fires during keybdpreview stage; subscribers can reset gear.touch if their current keybdrelease subscription is not confirmed).
static constexpr auto unknown = __COUNTER__ - counter; // events: .
static constexpr auto str = std::to_array({ "release"sv,
"preview"sv,
Expand All @@ -52,6 +53,7 @@ namespace netxs::events
"mouserelease"sv,
"keybdpreview"sv,
"keybdrelease"sv,
"keybd_prerun"sv,
"unknown"sv, });
static constexpr auto order = std::to_array({ feed::fwd,
feed::rev,
Expand All @@ -61,6 +63,7 @@ namespace netxs::events
feed::none,
feed::none,
feed::none,
feed::none,
feed::none, });
};

Expand Down
14 changes: 11 additions & 3 deletions src/netxs/desktopio/input.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2125,6 +2125,7 @@ namespace netxs::input
text chord;
txts sources; // Event source list.
netxs::sptr<std::pair<ui64, text>> script_ptr;
netxs::sptr<std::pair<ui64, text>> prerun_ptr;
};
using vector = std::vector<binding_t>;

Expand Down Expand Up @@ -2202,13 +2203,14 @@ namespace netxs::input
}
}
}
auto keybind(base& boss, qiew chord_str, auto&& script_body, txts const& sources = {})
auto keybind(base& boss, qiew chord_str, auto&& script_body, netxs::sptr<std::pair<ui64, text>> prerun_body = {}, txts const& sources = {})
{
if (!chord_str) return;
auto [chords, is_preview] = input::bindings::get_chords(chord_str);
if (chords.size())
{
auto script_ptr = ptr::shared<script_ref>(boss.indexer, boss, script_body);
auto prerun_ptr = prerun_body ? ptr::shared<script_ref>(boss.indexer, boss, prerun_body) : netxs::sptr<script_ref>{};
auto reset_handler = !(script_ptr->script_body_ptr && script_ptr->script_body_ptr->second.size());
for (auto& binary_chord : chords) if (binary_chord.size()) // Scripts always store their sensors at the boss side, since the lifetime of base::scripting_context depends on the boss.
{
Expand Down Expand Up @@ -2237,6 +2239,10 @@ namespace netxs::input
auto event_id = boss.indexer.get_kbchord_hint(binary_chord);
auto tier_id = is_preview ? tier::keybdpreview : tier::keybdrelease;
set_handler(reset_handler, boss, tier_id, event_id, sources, script_ptr);
if (prerun_ptr)
{
set_handler(reset_handler, boss, tier::keybd_prerun, event_id, sources, prerun_ptr);
}
}
}
}
Expand All @@ -2245,7 +2251,7 @@ namespace netxs::input
{
for (auto& r : bindings)
{
keybind(boss, r.chord, r.script_ptr, r.sources);
keybind(boss, r.chord, r.script_ptr, r.prerun_ptr, r.sources);
}
}
void dispatch(auto& boss, auto& instance_id, hids& gear, si32 tier_id, hint event_id)
Expand All @@ -2255,6 +2261,7 @@ namespace netxs::input
&& boss.bell::has_handlers(tier::keybdrelease, event_id))
{
gear.touched = instance_id;
boss.base::signal(tier::keybd_prerun, event_id, gear);
}
}
auto load(settings& config, auto& script_list)
Expand All @@ -2265,6 +2272,7 @@ namespace netxs::input
//todo revise
//auto script_context = config.settings::push_context(script_ptr);
auto script_body_ptr = ptr::shared(std::pair<ui64, text>{ 0, config.settings::take_value(script_ptr) });
auto prerun_body_ptr = ptr::shared(std::pair<ui64, text>{ 0, config.settings::take_value_from(script_ptr, "prerun", ""s) });
auto on_ptr_list = config.settings::take_ptr_list_of(script_ptr, "on");
for (auto event_ptr : on_ptr_list)
{
Expand All @@ -2277,7 +2285,7 @@ namespace netxs::input
// log("chord='%%' \tpreview=%% source='%%' script=%%", on_rec, (si32)preview, source, ansi::hi(script_body_ptr->second));
// }
//}
bindings.push_back({ .chord = std::move(on_rec), .sources = std::move(sources), .script_ptr = script_body_ptr });
bindings.push_back({ .chord = std::move(on_rec), .sources = std::move(sources), .script_ptr = script_body_ptr, .prerun_ptr = prerun_body_ptr });
}
}
return bindings;
Expand Down
2 changes: 1 addition & 1 deletion src/vtm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ R"==(
<script="vtm.defapp.ShowClosingPreview(faux);" on="Any" /> <!-- Preview for "Any" is always triggered after all other previews. Non-preview "Any" is triggered before all other keys. -->
<script="if (not vtm.gear.IsKeyRepeated()) then vtm.defapp.ShowClosingPreview(true); end" on="Esc" /> <!-- Window pred-close action (close when releasing the Esc key). -->
<script="if (vtm.defapp.ShowClosingPreview()) then vtm.defapp.Close() end" on="preview:-Esc"/> <!-- Close the window on Esc release. -->
<script="if (vtm.defapp.ShowClosingPreview()) then local focus_count=vtm(); vtm.defapp.ShowClosingPreview(focus_count~=0) end" source="applet" on="release:e2::form::state::focus::count" /> <!-- Disable window closing preview when focus is lost. -->
<script="if (vtm.defapp.ShowClosingPreview()) then local focus_count=vtm(); vtm.defapp.ShowClosingPreview(focus_count~=0) end" on="release:e2::form::state::focus::count" /> <!-- Disable window closing preview when focus is lost. -->
<script="vtm.defapp.ScrollViewportByPage( 0, 1)" on="PageUp" />
<script="vtm.defapp.ScrollViewportByPage( 0,-1)" on="PageDown" />
<script="vtm.defapp.ScrollViewportByStep( 0, 3)" on="UpArrow" />
Expand Down