Skip to content

Commit dca9ba8

Browse files
authored
Merge pull request #785 from o-sdn-o/gui-bridge
Enable floating point (pixel-wise) mouse reporting over Win32 Console API
2 parents c68add9 + 982f93e commit dca9ba8

File tree

12 files changed

+147
-39
lines changed

12 files changed

+147
-39
lines changed

doc/apps.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
- Enforced ENABLE_PROCESSED_OUTPUT and ENABLE_VIRTUAL_TERMINAL_PROCESSING modes.
3939
- Disabled ENABLE_QUICK_EDIT_MODE mode.
4040
- Per process (not per process name) Windows Command Prompt (cmd.exe) input history, aka "Line input" or "Cooked read".
41+
- Floating point (pixel-wise) mouse reporting.
4142
- Stdin/stdout logging.
4243

4344
### Private control sequences
@@ -141,6 +142,64 @@ Example 4. Output the longest word in the Hindi language 16x1 (G1_00):
141142
Screenshot:
142143
![image](images/vtm_character_geometry_modifiers_screenshot.png)
143144
145+
### Floating point (pixel-wise) mouse reporting
146+
147+
On Windows, when using the Win32 Console API, vtm reports mouse events with fractional mouse coordinates. Fractional coordinates are 32-bit floating-point numbers that represent the position of the cursor relative to the console's grid of text cells. Screen pixel coordinates can be calculated by multiplying the fractional coordinates by the cell size.
148+
149+
Example:
150+
```c++
151+
#include <iostream>
152+
#include <vector>
153+
#include <windows.h>
154+
155+
static constexpr auto custom_type = 0b1000'0000'0000'0000;
156+
static constexpr auto fp2d_mouse = 3;
157+
struct fp2d_mouse_input : MENU_EVENT_RECORD // MENU_EVENT_RECORD structure extension.
158+
{
159+
//DWORD EventType = MENU_EVENT;
160+
//DWORD dwCommandId = custom_type | fp2d_mouse;
161+
float x; // Floating point mouse x coord.
162+
float y; // Floating point mouse y coord.
163+
};
164+
int main()
165+
{
166+
auto inp = ::GetStdHandle(STD_INPUT_HANDLE);
167+
::SetConsoleMode(inp, ENABLE_MOUSE_INPUT);
168+
auto r = INPUT_RECORD{};
169+
auto count = DWORD{};
170+
auto x = std::numeric_limits<float>::quiet_NaN();
171+
auto y = std::numeric_limits<float>::quiet_NaN();
172+
auto mouse_out = false;
173+
std::cout << "Press any mouse button to exit\n";
174+
while (true)
175+
{
176+
::ReadConsoleInputW(inp, &r, 1, &count);
177+
if (r.EventType == MENU_EVENT)
178+
{
179+
if (r.Event.MenuEvent.dwCommandId == (custom_type | fp2d_mouse)) // The floating point coordinates message always precedes the classic mouse report.
180+
{
181+
x = reinterpret_cast<fp2d_mouse_input*>(&r.Event.MenuEvent)->x;
182+
y = reinterpret_cast<fp2d_mouse_input*>(&r.Event.MenuEvent)->y;
183+
mouse_out = std::isnan(x); // NaN is a sign that the mouse has gone away or is disconnected.
184+
if (mouse_out) std::cout << "The mouse has left the window\n";
185+
}
186+
}
187+
else if (r.EventType == MOUSE_EVENT && !mouse_out)
188+
{
189+
if (r.Event.MouseEvent.dwButtonState) return 0;
190+
if (std::isnan(x)) // Classical behavior.
191+
{
192+
std::cout << "MOUSE_EVENT coord: " << r.Event.MouseEvent.dwMousePosition.X << "," << r.Event.MouseEvent.dwMousePosition.Y << "\n";
193+
}
194+
else // Floating point mouse coordinates.
195+
{
196+
std::cout << "MOUSE_EVENT coord: " << x << "," << y << "\n";
197+
}
198+
}
199+
}
200+
}
201+
```
202+
144203
### Window menu
145204

146205
It is possible to create your own terminal window menu from scratch by configuring own menu items in the `<config/terminal/menu/>` subsection of the configuration file. See (`doc/settings.md#event-scripting`)[https://github.com/directvt/vtm/blob/master/doc/settings.md#event-scripting] for details.

doc/vt-input-mode.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Note: By enabling `vt-input-mode`, all current terminal modes are automatically
7878
```
7979
- Mouse
8080
```
81-
ESC _ event=mouse ; id=0 ; kbmods=<KeyMods> ; coord=<X>,<Y> ; buttons=<ButtonState> ; wheel=<DeltaY>[,<DeltaX>] ESC \
81+
ESC _ event=mouse ; id=0 ; kbmods=<KeyMods> ; coord=<X>,<Y> ; buttons=<ButtonState> ; scroll=<DeltaX>,<DeltaY> ESC \
8282
```
8383
- Focus
8484
```
@@ -351,7 +351,7 @@ Key ID | Name | Generic Name | Scan Code | Notes
351351
### Mouse
352352

353353
```
354-
ESC _ event=mouse ; id=0 ; kbmods=<KeyMods> ; coord=<X>,<Y> ; buttons=<ButtonState> ; wheel=<DeltaY>[,<DeltaX>] ESC \
354+
ESC _ event=mouse ; id=0 ; kbmods=<KeyMods> ; coord=<X>,<Y> ; buttons=<ButtonState> ; scroll=<DeltaX>,<DeltaY> ESC \
355355
```
356356

357357
Attribute | Description
@@ -360,7 +360,7 @@ Attribute | Description
360360
`kbmods=<KeyMods>` | Keyboard modifiers (see Keyboard event).
361361
`coord=<X>,<Y>` | Pixel-wise coordinates of the mouse pointer. Each coordinate is represented in the form of a floating point value of the sum of the integer coordinate of the cell in the terminal window grid and the relative offset within the cell in the range `[0.0f, 1.0f)`.
362362
`buttons=<ButtonState>` | Mouse button state.
363-
`wheel=<DeltaY>[,<DeltaX>]` | Vertical and horizontal high-resolution wheel delta integer value.
363+
`scroll=<DeltaX>,<DeltaY>` | Integer value of high resolution horizontal and vertical scroll delta in integer 1/120 units.
364364

365365
In response to the activation of `mouse` tracking, the application receives a vt-sequence containing current mouse state:
366366
```
@@ -378,6 +378,8 @@ Bit | Active button
378378
2 | Middle
379379
3 | 4th
380380
4 | 5th
381+
... | ...
382+
N-1 | Nth
381383

382384
Note: Mouse tracking will continue outside the terminal window as long as the mouse button pressed inside the window is active. In this case, coordinates with negative values are possible.
383385

src/netxs/apps.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ namespace netxs::app::shared
703703
chord_block->attach_cells({ 5, 3 }, { {}, label(skin::globals().NsInfoGeneric), label(skin::globals().NsInfoLiteral), label(skin::globals().NsInfoSpecific), label(skin::globals().NsInfoScancodes),
704704
pressed_label, pressed[0], pressed[1], pressed[2], pressed[3],
705705
released_label, released[0], released[1], released[2], released[3] });
706-
released[0]->set("<Press any keys>")->hidden = faux;
706+
released[0]->set(skin::globals().NsInfo_pressanykeys)->hidden = faux;
707707
auto& update = window_ptr->base::field([pressed, released](auto& boss, hids& gear, bool is_key_event)
708708
{
709709
//log("vkchord=%% keyid=%% hexvkchord=%% hexscchord=%% hexchchord=%%", input::key::kmap::to_string(gear.vkchord, faux),

src/netxs/apps/test.hpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ namespace netxs::app::test
5858
{
5959
return ansi::mgl(1).wrp(wrap::off).fgc(hdrclr).unc(whitedk).cap(caption).erl().und(unln::none).eol().fgc(txtclr).mgl(3).unc(0).wrp(wrap::on);
6060
};
61-
return ansi::mgl(1).mgr(2).jet(bias::center)
61+
auto crop = ansi::mgl(1).mgr(2).jet(bias::center)
6262
.add("\n")
6363
.wrp(wrap::off).fgc(hdrclr).cap(skin::globals().NsInfoSF, 3, 3, faux).eol()
6464
.jet(bias::left)
@@ -260,8 +260,10 @@ namespace netxs::app::test
260260
.add(header(skin::globals().NsInfoCharacterHalves))
261261
.add("\n")
262262
.add("😎", vss<21,11>, " 😃", vss<21,21>, "<VS21_11/VS21_21\n")
263-
.add("\n")
264-
.add(header(skin::globals().NsInfosRGBBlending))
263+
.add("\n");
264+
if constexpr (debugmode)
265+
{
266+
crop.add(header(skin::globals().NsInfosRGBBlending))
265267
.add("\n")
266268
.add(skin::globals().NsInfoPressCtrlCaps)
267269
.add("\n")
@@ -275,6 +277,8 @@ namespace netxs::app::test
275277
.bgc(argb{})
276278
.fgc(purered).add(" test \n")
277279
.fgc(purecyan).add(" test ");
280+
}
281+
return crop;
278282
};
279283

280284
auto get_text = []

src/netxs/desktopio/ansivt.hpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -612,8 +612,9 @@ namespace netxs::ansi
612612
return *this;
613613
}
614614
template<class T>
615-
auto& mouse_sgr(T const& gear, twod coor) // escx: Mouse tracking report (SGR).
615+
auto& mouse_sgr(T const& gear, fp2d coor) // escx: Mouse tracking report (SGR).
616616
{
617+
if (std::isnan(coor.x)) return *this;
617618
using hids = T;
618619
static constexpr auto left = si32{ 0 };
619620
static constexpr auto mddl = si32{ 1 };
@@ -681,13 +682,14 @@ namespace netxs::ansi
681682
auto count = std::max(1, std::abs(gear.m_sys.wheelsi));
682683
while (count--)
683684
{
684-
add("\033[<", ctrl, ';', coor.x, ';', coor.y, pressed ? 'M' : 'm');
685+
add("\033[<", ctrl, ';', (si32)coor.x, ';', (si32)coor.y, pressed ? 'M' : 'm');
685686
}
686687
return *this;
687688
}
688689
template<class T>
689-
auto& mouse_x11(T const& gear, twod coor, bool utf8) // escx: Mouse tracking report (X11).
690+
auto& mouse_x11(T const& gear, fp2d coor, bool utf8) // escx: Mouse tracking report (X11).
690691
{
692+
if (std::isnan(coor.x)) return *this;
691693
using hids = T;
692694
static constexpr auto left = si32{ 0 };
693695
static constexpr auto mddl = si32{ 1 };
@@ -739,15 +741,15 @@ namespace netxs::ansi
739741
if (utf8)
740742
{
741743
add("\033[M");
742-
utf::to_utf_from_code(std::clamp(ctrl, 0, si16max - 32) + 32, *this);
743-
utf::to_utf_from_code(std::clamp(coor.x, 1, si16max - 32) + 32, *this);
744-
utf::to_utf_from_code(std::clamp(coor.y, 1, si16max - 32) + 32, *this);
744+
utf::to_utf_from_code(std::clamp(ctrl, 0, si16max - 32) + 32, *this);
745+
utf::to_utf_from_code(std::clamp((si32)coor.x, 1, si16max - 32) + 32, *this);
746+
utf::to_utf_from_code(std::clamp((si32)coor.y, 1, si16max - 32) + 32, *this);
745747
}
746748
else
747749
{
748-
add("\033[M", (char)(std::clamp(ctrl, 0, 127-32) + 32),
749-
(char)(std::clamp(coor.x, 1, 127-32) + 32),
750-
(char)(std::clamp(coor.y, 1, 127-32) + 32));
750+
add("\033[M", (char)(std::clamp(ctrl, 0, 127-32) + 32),
751+
(char)(std::clamp((si32)coor.x, 1, 127-32) + 32),
752+
(char)(std::clamp((si32)coor.y, 1, 127-32) + 32));
751753
}
752754
}
753755
return *this;

src/netxs/desktopio/application.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace netxs::app
2222

2323
namespace netxs::app::shared
2424
{
25-
static const auto version = "v2025.08.10";
25+
static const auto version = "v2025.08.12";
2626
static const auto repository = "https://github.com/directvt/vtm";
2727
static const auto usr_config = "~/.config/vtm/settings.xml"s;
2828
static const auto sys_config = "/etc/vtm/settings.xml"s;

src/netxs/desktopio/consrv.hpp

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ struct consrv
2323
virtual fd_t watch() = 0;
2424
virtual bool send(view utf8) = 0;
2525
virtual void keybd(input::hids& gear, bool decckm) = 0;
26-
virtual void mouse(input::hids& gear, bool moved, twod coord, input::mouse::prot encod, input::mouse::mode state) = 0;
26+
virtual void mouse(input::hids& gear, bool moved, fp2d coord, input::mouse::prot encod, input::mouse::mode state) = 0;
2727
virtual void paste(view block) = 0;
2828
virtual void focus(bool state) = 0;
2929
virtual void winsz(twod newsz) = 0;
@@ -959,16 +959,22 @@ struct impl : consrv
959959
void style(si32 format)
960960
{
961961
auto lock = std::lock_guard{ locker };
962-
auto data = INPUT_RECORD{ .EventType = MENU_EVENT };
963-
data.Event.MenuEvent.dwCommandId = nt::console::event::custom | nt::console::event::style;
964-
stream.emplace_back(data);
965-
data.Event.MenuEvent.dwCommandId = nt::console::event::custom | format;
966-
stream.emplace_back(data);
962+
auto data = nt::console::style_input{ .format = format };
963+
stream.emplace_back(*reinterpret_cast<INPUT_RECORD*>(&data));
967964
ondata.reset();
968965
signal.notify_one();
969966
}
970-
void mouse(input::hids& gear, twod coord)
967+
void mouse(input::hids& gear, fp2d coord)
971968
{
969+
if (std::isnan(coord.x)) // Forward a mouse halt event.
970+
{
971+
auto lock = std::lock_guard{ locker };
972+
auto r2 = nt::console::fp2d_mouse_input{ .coord = coord };
973+
stream.emplace_back(*reinterpret_cast<INPUT_RECORD*>(&r2));
974+
ondata.reset();
975+
signal.notify_one();
976+
return;
977+
}
972978
auto state = os::nt::ms_kbstate(gear.ctlstat);
973979
auto bttns = gear.m_sys.buttons & 0b00011111;
974980
auto moved = gear.m_sys.buttons == gear.m_sav.buttons && gear.m_sys.wheelfp == 0.f; // No events means mouse move. MSFT: "MOUSE_EVENT_RECORD::dwEventFlags: If this value is zero, it indicates a mouse button being pressed or released". Far Manager relies on this.
@@ -982,7 +988,7 @@ struct impl : consrv
982988
{
983989
auto& s = dclick[i];
984990
auto fired = gear.m_sys.timecod;
985-
if (fired - s.fired < gear.delay && s.coord == coord) // Set the double-click flag if the delay has not expired and the mouse is in the same position.
991+
if (fired - s.fired < gear.delay && s.coord == twod{ coord }) // Set the double-click flag if the delay has not expired and the mouse is in the same cell.
986992
{
987993
flags |= DOUBLE_CLICK;
988994
s.fired = {};
@@ -1003,6 +1009,8 @@ struct impl : consrv
10031009
if (gear.m_sys.hzwheel) flags |= MOUSE_HWHEELED;
10041010
}
10051011
auto lock = std::lock_guard{ locker };
1012+
auto r2 = nt::console::fp2d_mouse_input{ .coord = coord };
1013+
stream.emplace_back(*reinterpret_cast<INPUT_RECORD*>(&r2));
10061014
stream.emplace_back(INPUT_RECORD
10071015
{
10081016
.EventType = MOUSE_EVENT,
@@ -1012,8 +1020,8 @@ struct impl : consrv
10121020
{
10131021
.dwMousePosition =
10141022
{
1015-
.X = (si16)std::clamp<si32>(coord.x, si16min, si16max),
1016-
.Y = (si16)std::clamp<si32>(coord.y, si16min, si16max),
1023+
.X = (si16)std::clamp<si32>((si32)coord.x, si16min, si16max),
1024+
.Y = (si16)std::clamp<si32>((si32)coord.y, si16min, si16max),
10171025
},
10181026
.dwButtonState = (DWORD)bttns,
10191027
.dwControlKeyState = state,
@@ -4934,7 +4942,7 @@ struct impl : consrv
49344942
uiterm.mtrack.setmode(input::mouse::prot::w32);
49354943
}
49364944
}
4937-
void mouse(input::hids& gear, bool /*moved*/, twod coord, input::mouse::prot /*encod*/,
4945+
void mouse(input::hids& gear, bool /*moved*/, fp2d coord, input::mouse::prot /*encod*/,
49384946
input::mouse::mode /*state*/) { events.mouse(gear, coord); }
49394947
void keybd(input::hids& gear, bool decckm) { events.keybd(gear, decckm); }
49404948
void paste(view block) { events.paste(block); }
@@ -5153,7 +5161,7 @@ struct consrv : ipc::stdcon
51535161
{
51545162
//todo win32-input-mode
51555163
}
5156-
void mouse(input::hids& /*gear*/, bool /*moved*/, twod /*coord*/, input::mouse::prot /*encod*/, input::mouse::mode /*state*/)
5164+
void mouse(input::hids& /*gear*/, bool /*moved*/, fp2d /*coord*/, input::mouse::prot /*encod*/, input::mouse::mode /*state*/)
51575165
{
51585166
//todo win32-input-mode
51595167
}

src/netxs/desktopio/geometry.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ namespace netxs
283283
constexpr twod clamp(twod point) const
284284
{
285285
auto [min, max] = twod::sort(coor, coor + size);
286-
return std::clamp(point, min, max - dot_11);
286+
return std::clamp(point, min, std::max(min, max - dot_11)); // Use std::max for zero size cases.
287287
}
288288
// rect: Is the point inside the rect.
289289
constexpr bool hittest(twod p) const

src/netxs/desktopio/intmath.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ namespace netxs
9292
static constexpr auto fp64min = std::numeric_limits<fp64>::lowest();
9393
static constexpr auto fp32epsilon = std::numeric_limits<fp32>::min();
9494
static constexpr auto fp64epsilon = std::numeric_limits<fp64>::min();
95+
static constexpr auto fp32nan = std::numeric_limits<fp32>::quiet_NaN();
96+
static constexpr auto fp64nan = std::numeric_limits<fp64>::quiet_NaN();
9597
static constexpr auto debugmode
9698
#if defined(DEBUG)
9799
= true;

0 commit comments

Comments
 (0)