From 56a697147c84127260788b16292124197be4c0f4 Mon Sep 17 00:00:00 2001 From: Dmitry Sapozhnikov <11535558+o-sdn-o@users.noreply.github.com> Date: Mon, 5 Dec 2022 17:19:45 +0500 Subject: [PATCH] #266 #273 WIP: Bidirectional clipboard sync (win32, preview hide timeout + highlighting) --- src/netxs/apps.hpp | 15 +- src/netxs/apps/term.hpp | 2 +- src/netxs/console/ansi.hpp | 6 +- src/netxs/console/console.hpp | 262 +++++++++++++++++---------------- src/netxs/console/terminal.hpp | 14 +- src/netxs/datetime/quartz.hpp | 10 ++ src/netxs/os/system.hpp | 6 +- 7 files changed, 170 insertions(+), 145 deletions(-) diff --git a/src/netxs/apps.hpp b/src/netxs/apps.hpp index b3839f081..ff10c403a 100644 --- a/src/netxs/apps.hpp +++ b/src/netxs/apps.hpp @@ -88,7 +88,7 @@ R"==( - + @@ -111,7 +111,7 @@ R"==( - + @@ -227,11 +227,14 @@ R"==( - + + + + - + @@ -264,7 +267,7 @@ R"==( - + @@ -287,7 +290,7 @@ R"==( - + diff --git a/src/netxs/apps/term.hpp b/src/netxs/apps/term.hpp index f8e8b63d8..530c5dadb 100644 --- a/src/netxs/apps/term.hpp +++ b/src/netxs/apps/term.hpp @@ -166,7 +166,7 @@ namespace netxs::app::term if (boss.client) boss.client->SIGNAL(tier::release, e2::data::text, "HTML-code"); boss.color(0xFFffff00, x3.bgc()); break; - case clip::password: + case clip::safetext: if (boss.client) boss.client->SIGNAL(tier::release, e2::data::text, "Protected"); boss.color(0xFFffff00, x3.bgc()); break; diff --git a/src/netxs/console/ansi.hpp b/src/netxs/console/ansi.hpp index 85d67ca03..640d4ce6d 100644 --- a/src/netxs/console/ansi.hpp +++ b/src/netxs/console/ansi.hpp @@ -270,7 +270,7 @@ namespace netxs::ansi static const auto mimeansi = "text/xterm"sv; static const auto mimehtml = "text/html"sv; static const auto mimerich = "text/rtf"sv; - static const auto mimehide = "hidden/plain"sv; + static const auto mimesafe = "text/protected"sv; struct clip { @@ -281,7 +281,7 @@ namespace netxs::ansi ansitext, richtext, htmltext, - password, // mime: Sensitive textonly data. + safetext, // mime: Sensitive textonly data. count, }; @@ -643,7 +643,7 @@ namespace netxs::ansi return add("\033]52;", kind == clip::htmltext ? mimehtml : kind == clip::richtext ? mimerich : kind == clip::ansitext ? mimeansi - : kind == clip::password ? mimehide + : kind == clip::safetext ? mimesafe : mimetext, ";", utf::base64(utf8), C0_BEL); } auto& old_palette(si32 i, rgba const& c) // esc: Set color palette (Linux console). diff --git a/src/netxs/console/console.hpp b/src/netxs/console/console.hpp index 4d1912176..bdc610e23 100644 --- a/src/netxs/console/console.hpp +++ b/src/netxs/console/console.hpp @@ -1384,6 +1384,112 @@ namespace netxs::console } }; + // console: Client properties. + class conf + { + using time = period; + + public: + text ip; + text port; + text fullname; + text region; + text name; + text os_user_id; + text title; + text selected; + twod coor; + time clip_preview_time; + cell clip_preview_clrs; + byte clip_preview_alfa; + bool clip_preview_show; + twod clip_preview_size; + cell background_color; + si32 legacy_mode; + si32 session_id; + time dblclick_timeout; // conf: Double click timeout. + time tooltip_timeout; // conf: Timeout for tooltip. + cell tooltip_colors; // conf: Tooltip rendering colors. + bool tooltip_enabled; // conf: Enable tooltips. + bool glow_fx; // conf: Enable glow effect in main menu. + bool debug_overlay; // conf: Enable to show debug overlay. + text debug_toggle; // conf: Debug toggle shortcut. + bool show_regions; // conf: Highlight region ownership. + bool simple; // conf: Isn't it a directvt app. + bool is_standalone_app; // conf: . + + template + void read(T&& config) + { + config.cd("/config/client/"); + clip_preview_clrs = config.take("clipboard/preview", cell{}.bgc(bluedk).fgc(whitelt)); + clip_preview_time = config.take("clipboard/preview/timeout", time{ 3s }); + clip_preview_alfa = config.take("clipboard/preview/alpha", 0x1f); + clip_preview_show = config.take("clipboard/preview/enabled", true); + clip_preview_size = config.take("clipboard/preview/size", twod{ 80,25 }); + coor = config.take("viewport/coor", dot_00); //todo Move user's viewport to the last saved position + dblclick_timeout = config.take("mouse/dblclick", time{ 500ms }); + tooltip_colors = config.take("tooltip", cell{}.bgc(0xFFffffff).fgc(0xFF000000)); + tooltip_timeout = config.take("tooltip/timeout", time{ 500ms }); + tooltip_enabled = config.take("tooltip/enabled", true); + debug_overlay = config.take("debug/overlay", faux); + debug_toggle = config.take("debug/toggle", "🐞"s); + show_regions = config.take("regions/enabled", faux); + } + + conf() = default; + conf(conf const&) = default; + conf(conf&&) = default; + conf& operator = (conf const&) = default; + conf(si32 mode, xml::settings& config) + : session_id{ 0 }, + legacy_mode{ mode } + { + read(config); + simple = !(legacy_mode & os::legacy::direct); + glow_fx = faux; + is_standalone_app = true; + title = ""; + } + conf(xipc peer, si32 session_id, xml::settings& config) + : session_id{ session_id } + { + auto _ip = peer->line(';'); + auto _name = peer->line(';'); + auto _user = peer->line(';'); + auto _mode = peer->line(';'); + auto _runcfg = peer->line(';'); + auto xmlconfig = utf::unbase64(_runcfg); + config.merge(xmlconfig); + + _user = "[" + _user + ":" + std::to_string(session_id) + "]"; + auto c_info = utf::divide(_ip, " "); + ip = c_info.size() > 0 ? c_info[0] : text{}; + port = c_info.size() > 1 ? c_info[1] : text{}; + legacy_mode = utf::to_int(_mode, os::legacy::clean); + os_user_id = _user; + fullname = _name; + name = _user; + title = _user; + selected = config.take("/config/menu/selected", ""s); + read(config); + background_color = cell{}.fgc(config.take("background/fgc", rgba{ whitedk })) + .bgc(config.take("background/bgc", rgba{ 0xFF000000 })); + glow_fx = config.take("glowfx", true); + simple = faux; + is_standalone_app = faux; + } + + friend auto& operator << (std::ostream& s, conf const& c) + { + return s << "\n\t ip: " <<(c.ip.empty() ? text{} : (c.ip + ":" + c.port)) + << "\n\tregion: " << c.region + << "\n\t name: " << c.fullname + << "\n\t user: " << c.os_user_id + << "\n\t mode: " << os::legacy::str(c.legacy_mode); + } + }; + // console: Template modules for the base class behavior extension. namespace pro { @@ -3471,14 +3577,16 @@ namespace netxs::console struct topgear : public hids { + conf& props; clip clip_rawdata{}; // topgear: Clipboard data. face clip_preview{}; // topgear: Clipboard preview render. twod preview_size{}; // topgear: Clipboard preview render size. bool not_directvt{}; // topgear: Is it the top level gear (not directvt). template - topgear(bool not_directvt, Args&&... args) + topgear(conf& props, bool not_directvt, Args&&... args) : hids{ std::forward(args)... }, + props{ props }, not_directvt{ not_directvt } { } @@ -3506,7 +3614,7 @@ namespace netxs::console clip_rawdata = data; if (not_directvt) { - if (data.kind == clip::password) + if (data.kind == clip::safetext) { auto block = page{ " Protected Data " }; clip_preview.mark(cell{}.bgc(0x7Fffffff).fgc(0xFF000000)); @@ -3514,16 +3622,25 @@ namespace netxs::console clip_preview.wipe(); clip_preview.output(block); } + else if (data.kind == clip::textonly) + { + auto block = page{ data.utf8 }; + clip_preview.mark(cell{}); + clip_preview.size(preview_size); + clip_preview.wipe(); + clip_preview.output(block, cell::shaders::selection(props.clip_preview_clrs)); //todo make transparency configurable + } else { auto block = page{ data.utf8 }; clip_preview.mark(cell{}); clip_preview.size(preview_size); clip_preview.wipe(); - clip_preview.output(block, cell::shaders::xlucent(0x1f)); //todo make transparency configurable + clip_preview.output(block, cell::shaders::xlucent(props.clip_preview_alfa)); } } if (forward) owner.SIGNAL(tier::release, hids::events::clipbrd::set, *this); + mouse::delta.set(); // Update time stamp. } clip get_clip_data() override { @@ -3541,10 +3658,7 @@ namespace netxs::console using skill::boss, skill::memo; - period& dblclick_timeout; - period& tooltip_timeout; - bool& simple_instance; - bool& standalone_instance; + conf& props; template void forward(T& device) @@ -3552,7 +3666,7 @@ namespace netxs::console auto gear_it = gears.find(device.gear_id); if (gear_it == gears.end()) { - gear_it = gears.emplace(device.gear_id, bell::create(device.gear_id == 0, boss, xmap, dblclick_timeout, tooltip_timeout, simple_instance)).first; + gear_it = gears.emplace(device.gear_id, bell::create(props, device.gear_id == 0, boss, xmap, props.dblclick_timeout, props.tooltip_timeout, props.simple)).first; } auto& [_id, gear_ptr] = *gear_it; gear_ptr->hids::take(device); @@ -3567,11 +3681,8 @@ namespace netxs::console input(base&&) = delete; template input(T& boss) - : skill{ boss }, - dblclick_timeout{ boss.props.dblclick_timeout }, - tooltip_timeout{ boss.props.tooltip_timeout }, - simple_instance{ boss.props.simple }, - standalone_instance{ boss.props.is_standalone_app } + : skill{ boss }, + props{ boss.props } { xmap.link(boss.bell::id); xmap.move(boss.base::coor()); @@ -3629,7 +3740,7 @@ namespace netxs::console } void check_focus() { - if (simple_instance) + if (props.simple) { auto f = sysfocus{}; f.gear_id = 0; @@ -3637,10 +3748,6 @@ namespace netxs::console boss.SIGNAL(tier::release, e2::conio::focus, f); } } - auto is_not_standalone_instance() - { - return !standalone_instance; - } void fire(events::id_t event_id) { for (auto& [id, gear_ptr] : gears) @@ -3662,7 +3769,7 @@ namespace netxs::console { if (gears.empty()) { - gears.emplace(0, bell::create(true, boss, xmap, dblclick_timeout, tooltip_timeout, simple_instance)); + gears.emplace(0, bell::create(props, true, boss, xmap, props.dblclick_timeout, props.tooltip_timeout, props.simple)); } for (auto& [id, gear_ptr] : gears) { @@ -5279,104 +5386,6 @@ namespace netxs::console } }; - // console: Client properties. - class conf - { - using time = period; - - public: - text ip; - text port; - text fullname; - text region; - text name; - text os_user_id; - text title; - text selected; - twod coor; - bool clip_preview_show; - twod clip_preview_size; - cell background_color; - si32 legacy_mode; - si32 session_id; - time dblclick_timeout; // conf: Double click timeout. - time tooltip_timeout; // conf: Timeout for tooltip. - bool tooltip_enabled; // conf: Enable tooltips. - bool glow_fx; // conf: Enable glow effect in main menu. - bool debug_overlay; // conf: Enable to show debug overlay. - text debug_toggle; // conf: Debug toggle shortcut. - bool show_regions; // conf: Highlight region ownership. - bool simple; // conf: Isn't it a directvt app. - bool is_standalone_app; // conf: . - - template - void read(T&& config) - { - config.cd("/config/client/"); - clip_preview_show = config.take("clipboard/preview/enabled", true); - clip_preview_size = config.take("clipboard/preview/size", twod{ 80,25 }); - coor = config.take("viewport/coor", dot_00); //todo Move user's viewport to the last saved position - dblclick_timeout = config.take("mouse/dblclick", time{ 500ms }); - tooltip_timeout = config.take("tooltip/timeout", time{ 500ms }); - tooltip_enabled = config.take("tooltip/enabled", true); - debug_overlay = config.take("debug/overlay", faux); - debug_toggle = config.take("debug/toggle", "🐞"s); - show_regions = config.take("regions/enabled", faux); - } - - conf() = default; - conf(conf const&) = default; - conf(conf&&) = default; - conf& operator = (conf const&) = default; - conf(si32 mode, xml::settings& config) - : session_id{ 0 }, - legacy_mode{ mode } - { - read(config); - simple = !(legacy_mode & os::legacy::direct); - glow_fx = faux; - is_standalone_app = true; - title = ""; - } - conf(xipc peer, si32 session_id, xml::settings& config) - : session_id{ session_id } - { - auto _ip = peer->line(';'); - auto _name = peer->line(';'); - auto _user = peer->line(';'); - auto _mode = peer->line(';'); - auto _runcfg = peer->line(';'); - auto xmlconfig = utf::unbase64(_runcfg); - config.merge(xmlconfig); - - _user = "[" + _user + ":" + std::to_string(session_id) + "]"; - auto c_info = utf::divide(_ip, " "); - ip = c_info.size() > 0 ? c_info[0] : text{}; - port = c_info.size() > 1 ? c_info[1] : text{}; - legacy_mode = utf::to_int(_mode, os::legacy::clean); - os_user_id = _user; - fullname = _name; - name = _user; - title = _user; - selected = config.take("/config/menu/selected", ""s); - read(config); - background_color = cell{}.fgc(config.take("background/fgc", rgba{ whitedk })) - .bgc(config.take("background/bgc", rgba{ 0xFF000000 })); - glow_fx = config.take("glowfx", true); - simple = faux; - is_standalone_app = faux; - } - - friend auto& operator << (std::ostream& s, conf const& c) - { - return s << "\n\t ip: " <<(c.ip.empty() ? text{} : (c.ip + ":" + c.port)) - << "\n\tregion: " << c.region - << "\n\t name: " << c.fullname - << "\n\t user: " << c.os_user_id - << "\n\t mode: " << os::legacy::str(c.legacy_mode); - } - }; - // console: Client's gate. class gate : public base @@ -5436,19 +5445,22 @@ namespace netxs::console canvas.fill(area, cell::shaders::fuse(brush)); } } - void draw_clip_preview(face& canvas) + void draw_clip_preview(face& canvas, moment const& stamp) { for (auto& [id, gear_ptr] : input.gears) { auto& gear = *gear_ptr; - auto coor = gear.coord + dot_21 * 2; - gear.clip_preview.move(coor); - canvas.plot(gear.clip_preview, cell::shaders::lite); + if (props.clip_preview_time == period::zero() + || props.clip_preview_time > stamp - gear.delta.stamp()) + { + auto coor = gear.coord + dot_21 * 2; + gear.clip_preview.move(coor); + canvas.plot(gear.clip_preview, cell::shaders::lite); + } } } void draw_tooltips(face& canvas) { - static constexpr auto def_tooltip = { rgba{ 0xFFffffff }, rgba{ 0xFF000000 } }; //todo unify auto full = canvas.full(); for (auto& [id, gear_ptr] : input.gears) { @@ -5464,7 +5476,7 @@ namespace netxs::console area.coor = std::max(dot_00, gear.coord - twod{ 4, tooltip_page.size() + 1 }); canvas.full(area); canvas.cup(dot_00); - canvas.output(tooltip_page, cell::shaders::selection(def_tooltip)); + canvas.output(tooltip_page, cell::shaders::selection(props.tooltip_colors)); canvas.full(full); } } @@ -5554,7 +5566,7 @@ namespace netxs::console if (damaged) { canvas.wipe(world.bell::id); - if (input.is_not_standalone_instance()) + if (!props.is_standalone_app) { if (background) // Render active wallpaper. { @@ -5576,7 +5588,7 @@ namespace netxs::console if (!direct && props.clip_preview_show) { - draw_clip_preview(canvas); + draw_clip_preview(canvas, stamp); } if (props.tooltip_enabled) diff --git a/src/netxs/console/terminal.hpp b/src/netxs/console/terminal.hpp index c828cc155..1891d3fbf 100644 --- a/src/netxs/console/terminal.hpp +++ b/src/netxs/console/terminal.hpp @@ -136,7 +136,7 @@ namespace netxs::ui { "ansi", clip::ansitext }, { "rich", clip::richtext }, { "html", clip::htmltext }, - { "pass", clip::password }}; + { "protected", clip::safetext }}; static auto cursor_options = std::unordered_map {{ "underline", faux }, { "block" , true }}; @@ -1838,7 +1838,7 @@ namespace netxs::ui case clip::richtext: work(cell::shaders::xlight); break; case clip::htmltext: work(cell::shaders::xlight); break; case clip::textonly: work(cell::shaders::selection(owner.config.def_selclr)); break; - case clip::password: work(cell::shaders::selection(owner.config.def_selclr)); break; + case clip::safetext: work(cell::shaders::selection(owner.config.def_selclr)); break; default: work(cell::shaders::selection(owner.config.def_offclr)); break; } } @@ -1900,7 +1900,7 @@ namespace netxs::ui if (selbox || grip_1.coor.y == grip_2.coor.y) { selmod == clip::textonly || - selmod == clip::password ? buffer.s11n(canvas, square) + selmod == clip::safetext ? buffer.s11n(canvas, square) : buffer.s11n(canvas, square); } else @@ -1910,7 +1910,7 @@ namespace netxs::ui auto part_2 = rect{ {0, grip_1.coor.y + 1 }, { panel.x, std::max(0, square.size.y - 2) } }; auto part_3 = rect{ {0, grip_2.coor.y }, { grip_2.coor.x + 1, 1 } }; if (selmod == clip::textonly - || selmod == clip::password) + || selmod == clip::safetext) { buffer.s11n(canvas, part_1); buffer.s11n(canvas, part_2); @@ -5408,7 +5408,7 @@ namespace netxs::ui } while (head++ != tail); selmod == clip::textonly || - selmod == clip::password ? yield.s11n(dest, mark) + selmod == clip::safetext ? yield.s11n(dest, mark) : yield.s11n(dest, mark); } else @@ -5441,7 +5441,7 @@ namespace netxs::ui if (yield.length()) yield.pop_back(); // Pop last eol. }; if (selmod == clip::textonly - || selmod == clip::password) + || selmod == clip::safetext) { build([&](auto& curln) { @@ -5478,7 +5478,7 @@ namespace netxs::ui auto selbox = selection_selbox(); if (!selection_active()) return std::move(yield); if (selmod != clip::textonly - && selmod != clip::password) yield.nil(); + && selmod != clip::safetext) yield.nil(); len = yield.size(); if (uptop.role != grip::idle) { diff --git a/src/netxs/datetime/quartz.hpp b/src/netxs/datetime/quartz.hpp index af2f8de55..421f458b0 100644 --- a/src/netxs/datetime/quartz.hpp +++ b/src/netxs/datetime/quartz.hpp @@ -213,11 +213,21 @@ namespace netxs::datetime rec.item = item; } } + // tail: Update last time stamp. + void set() + { + set(hist[iter].item); + } // tail: Return last value. auto& get() const { return hist[iter].item; } + // tail: Return last time stamp. + auto& stamp() const + { + return hist[iter].time; + } // tail: Shrink tail size to 1 and free memory. void dry() { diff --git a/src/netxs/os/system.hpp b/src/netxs/os/system.hpp index fde80dc8b..70aeb658d 100644 --- a/src/netxs/os/system.hpp +++ b/src/netxs/os/system.hpp @@ -1771,7 +1771,7 @@ namespace netxs::os // cf_ansi: ANSI-text UTF-8 with mime mark // cf_html: HTML-code UTF-8 // CF_UNICODETEXT: HTML-code UTF-16 - // clip::password (https://learn.microsoft.com/en-us/windows/win32/dataxchg/clipboard-formats#cloud-clipboard-and-clipboard-history-formats) + // clip::safetext (https://learn.microsoft.com/en-us/windows/win32/dataxchg/clipboard-formats#cloud-clipboard-and-clipboard-history-formats) // ExcludeClipboardContentFromMonitorProcessing: 1 // CanIncludeInClipboardHistory: 0 // CanUploadToCloudClipboard: 0 @@ -1841,7 +1841,7 @@ namespace netxs::os send(os::cf_rich, rich); send(os::cf_text, utf8); } - else if (mime.starts_with(ansi::mimehide)) + else if (mime.starts_with(ansi::mimesafe)) { send(os::cf_sec1, "1"); send(os::cf_sec2, "0"); @@ -3937,7 +3937,7 @@ namespace netxs::os { if (format == os::cf_text) { - auto mime = hidden ? ansi::clip::password : ansi::clip::textonly; + auto mime = hidden ? ansi::clip::safetext : ansi::clip::textonly; if (auto hglb = ::GetClipboardData(format)) if (auto lptr = ::GlobalLock(hglb)) {