diff --git a/doc/settings.md b/doc/settings.md index e0d7156cc7..4a6490b35a 100644 --- a/doc/settings.md +++ b/doc/settings.md @@ -848,7 +848,7 @@ Notes - + @@ -984,7 +984,7 @@ Notes - + diff --git a/src/netxs/desktopio/application.hpp b/src/netxs/desktopio/application.hpp index bc40127ba2..353bcb335c 100644 --- a/src/netxs/desktopio/application.hpp +++ b/src/netxs/desktopio/application.hpp @@ -21,7 +21,7 @@ namespace netxs::app namespace netxs::app::shared { - static const auto version = "v2026.09.20"; + static const auto version = "v2026.09.23"; 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; diff --git a/src/netxs/desktopio/canvas.hpp b/src/netxs/desktopio/canvas.hpp index 4945a9733b..6a55daeb05 100644 --- a/src/netxs/desktopio/canvas.hpp +++ b/src/netxs/desktopio/canvas.hpp @@ -1072,7 +1072,7 @@ namespace netxs a{ c.chan.a * inv_255 } { } - operator argb() const { return argb{ r, g, b, a }; } + operator argb () const { return argb{ r, g, b, a }; } bool operator > (auto n) const { return r > n || g > n || b > n || a > n; } auto operator / (auto n) const { return irgb{ r / n, g / n, b / n, a / n }; } // 10% faster than divround. diff --git a/src/netxs/desktopio/controls.hpp b/src/netxs/desktopio/controls.hpp index 36f894e6f4..d38dbd25a2 100644 --- a/src/netxs/desktopio/controls.hpp +++ b/src/netxs/desktopio/controls.hpp @@ -384,12 +384,21 @@ namespace netxs else if constexpr (std::is_same_v, twod>) return twod{ ::lua_tonumber(lua, idx), ::lua_tonumber(lua, idx + 1) }; else if constexpr (std::is_same_v, sptr>) { - if (auto ptr = (ui::base*)::lua_touserdata(lua, idx)) // Get ui::base*. - { - auto object_ptr = ptr->This(); - return object_ptr; + auto object_ptr = sptr{}; + if (type == LUA_TTABLE) + { + auto abs_idx = ::lua_absindex(lua, idx); + ::lua_pushstring(lua, "__self"); + auto raw_type = ::lua_rawget(lua, abs_idx); // Push ptr to stack. + if (raw_type == LUA_TLIGHTUSERDATA) + if (auto ptr = (ui::base*)::lua_touserdata(lua, -1)) + { + object_ptr = ptr->This(); + } + //luna::vtmlua_log(lua); + ::lua_pop(lua, 1); // Pop ptr from stack. } - return sptr{}; + return object_ptr; } } if constexpr (is_string_v || is_cstring_v) return text{ fallback }; diff --git a/src/netxs/desktopio/generics.hpp b/src/netxs/desktopio/generics.hpp index 168f745a34..4f0f7bb9af 100644 --- a/src/netxs/desktopio/generics.hpp +++ b/src/netxs/desktopio/generics.hpp @@ -447,7 +447,7 @@ namespace netxs::generics friend auto operator + (iter i, si32 n) { i += n; return i; } friend auto operator + (si32 n, iter i) { i += n; return i; } friend auto operator - (iter i, si32 n) { i -= n; return i; } - reference operator[](difference_type n) const { return *(*this + (si32)n); } + reference operator [] (difference_type n) const { return *(*this + (si32)n); } }; ring(si32 ring_size, si32 grow_by = 0, si32 grow_mx = 0) @@ -1137,9 +1137,9 @@ namespace netxs::generics handle_type h; bool done; - void operator++() { h.resume(); done = h.done(); } - auto& operator*() const { return *(h.promise().current_value); } - auto operator!=(iterator const& other) const { return done != other.done; } + void operator ++ () { h.resume(); done = h.done(); } + auto& operator * () const { return *(h.promise().current_value); } + auto operator != (iterator const& other) const { return done != other.done; } }; auto begin() diff --git a/src/netxs/desktopio/geometry.hpp b/src/netxs/desktopio/geometry.hpp index 13d2818bfc..44286f2fad 100644 --- a/src/netxs/desktopio/geometry.hpp +++ b/src/netxs/desktopio/geometry.hpp @@ -113,8 +113,8 @@ namespace netxs constexpr auto operator ~ () const { return xy2d{ y, x }; } //In C++11, signed shift left of a negative number is always undefined - //void operator>>= (T i) { x >>=i; y >>=i; } - //void operator<<= (T i) { x <<=i; y <<=i; } + //void operator >>= (T i) { x >>=i; y >>=i; } + //void operator <<= (T i) { x <<=i; y <<=i; } // //In C++11, signed shift left of a negative number is always undefined //xy2d operator << (T i) const { return { x << i, y << i }; } @@ -753,7 +753,7 @@ namespace netxs base _data; rect _area; rect _clip; - auto length() const { return _data.length(); } + auto length() const { return _data.size(); } auto begin() { return _data.begin(); } auto begin() const { return _data.begin(); } auto data() { return _data.data(); } diff --git a/src/netxs/desktopio/gui.hpp b/src/netxs/desktopio/gui.hpp index 8d67a9c7d9..3e1240a6c4 100644 --- a/src/netxs/desktopio/gui.hpp +++ b/src/netxs/desktopio/gui.hpp @@ -91,26 +91,18 @@ namespace netxs::gui arch fg_hWnd = {}; // OR=1 foreground layer. arch bg_hdc = {}; // OR=1 background layer. arch bg_hWnd = {}; // OR=1 background layer. - ui32 shm_offset = {}; - twod prev_size; + ui32 shm_offset = {}; // Offset in bytes. + ui32 shm_pixel_limit{}; // Buffer pixel limit in pixels (argb). + twod prev_size; // Layer size for allocated bitmap. bool prev_live = {}; bool windowsized = {}; - bool bank = {}; // Bank is switching offset segment on every resize iteration. - std::array seq_nums = { 0xFFFF, 0xFFFF }; + ui16 seq_num = 0xFFFF; // X11 request sequence number for layer output tracking. - auto get_offset(auto& session, bool toggle_bank = faux) + void set_seq_num(auto& session, ui16 new_seq_num) { - //if (toggle_bank) - bank ^= toggle_bank; - //todo deadlock by unknown reason - //session.sync_reply(seq_nums[bank]); // It is already synced in layers_present() - return (ui32)((session.shm_buffer_len / 2) * bank + shm_offset); - } - void set_seq_num(auto& session, ui16 seq_num) - { - seq_nums[bank] = seq_num; + seq_num = new_seq_num; session.received_replies[seq_num].store(true, std::memory_order_release); - if constexpr (debugmode) log("Frame seq=%%", seq_num); + //if constexpr (debugmode) log("Frame seq=%%", seq_num); } void swap_backing() { @@ -128,8 +120,13 @@ namespace netxs::gui { } void hide() { live = faux; } void show() { live = true; } - void wipe() { std::memset((void*)data.data(), 0, (sz_t)area.size.x * area.size.y * sizeof(argb)); } auto resized() { return area.size != prev.size; } + void wipe() + { + assert(!resized()); + //todo ?should we use prev to be safe + std::memset((void*)data.data(), 0, (sz_t)area.size.x * area.size.y * sizeof(argb)); + } template void strike(rect r) { @@ -3590,7 +3587,6 @@ namespace netxs::gui virtual void keybd_read_vkstat() = 0; virtual void keybd_wipe_vkstat() = 0; virtual void keybd_print_vkstat(text s) = 0; - virtual bool keybd_read_input() = 0; virtual void keybd_sync_shift(bool async) = 0; virtual void keybd_send_block(view block) = 0; virtual bool keybd_test_toggled(si32 virtcod) = 0; @@ -3795,13 +3791,13 @@ namespace netxs::gui netxs::set_flag(reload); } } - void set_state(si32 new_state) + void set_state(si32 new_state, bool first_run = faux) { if (fsmode == new_state && fsmode != winstate::normal) return; // Restore to normal if it was silently hidden by the system. - log("%%Set window to ", prompt::gui, new_state == winstate::maximized ? "maximized" : new_state == winstate::normal ? "normal" : "minimized", " state"); + if constexpr (debugmode) log("%%Set window to ", prompt::gui, new_state == winstate::maximized ? "maximized" : new_state == winstate::normal ? "normal" : "minimized", " state"); auto old_state = std::exchange(fsmode, winstate::undefined); if (new_state != winstate::minimized) reset_blinky(); // To avoid visual desync. - window_sync_taskbar(new_state); + if (!first_run) window_sync_taskbar(new_state); // Trigger WM_SETFOCUS on win32. fsmode = new_state; if (old_state == winstate::normal) normsz = master.area; if (fsmode == winstate::normal) @@ -4804,21 +4800,18 @@ namespace netxs::gui auto focus_bus_on = mfocus.set_owner(lParam); if (!focus_bus_on) { - //todo try to make it sync (drop enqueue) (Key press events can precede focus events because they are sent synchronously!(X11)) - base::enqueue([&](auto& /*boss*/) + // Focus event must be sent sync (without queueing). Key press events can precede focus events because they are sent synchronously. + base::signal(tier::release, input::events::focus::set::on, { .gear_id = stream.gears->id, .focus_type = solo::on }); + if (!has_layout) // The first focus event - sync keybd layout. { - base::signal(tier::release, input::events::focus::set::on, { .gear_id = stream.gears->id, .focus_type = solo::on }); - if (!has_layout) // The first focus event - sync keybd layout. - { - has_layout = true; - keybd_sync_layout(); - } - if (mfocus.wheel) - { - //todo share keybd layout between group focused windows - window_post_command(ipc::sync_state); - } - }); + has_layout = true; + keybd_sync_layout(); + } + if (mfocus.wheel) + { + //todo share keybd layout between group focused windows + window_post_command(ipc::sync_state); + } } } else if (command == ipc::drop_focus) @@ -4828,10 +4821,7 @@ namespace netxs::gui keybd_send_state(); if (focus_bus_on) { - base::enqueue([&](auto& /*boss*/) - { - auto seed = base::signal(tier::release, input::events::focus::set::off, { .gear_id = stream.gears->id }); - }); + base::signal(tier::release, input::events::focus::set::off, { .gear_id = stream.gears->id }); } } else if (command == ipc::solo_focus) @@ -5004,11 +4994,6 @@ namespace netxs::gui os::dtvt::flagsz = true; // Notify app::shared::splice. os::dtvt::flagsz.notify_all(); auto lock = bell::sync(); - normsz = master.area; - size_window(); - set_state(config.win_state); - update_gui(); - window_initialize(); //todo it doesn't work on win32 (deferred mediakey) //LISTEN(tier::release, input::events::keybd::any, gear) @@ -5079,7 +5064,13 @@ namespace netxs::gui auto window_id = id_t{}; stream.footer.send(stream.intio, window_id, utf8); }; - base::broadcast(tier::anycast, e2::form::upon::started, This()); + + normsz = master.area; + size_window(); // First resize. + set_state(config.win_state, true/*don't window_sync_taskbar*/); + update_gui(); + base::broadcast(tier::anycast, e2::form::upon::started, This()); // Subscribe on ui::title update with pro::focus. + window_initialize(); // Trigger WM_SETFOCUS/event::FocusIn and ui::header update. } auto winio = std::thread{ [&] { @@ -5435,6 +5426,7 @@ namespace netxs::gui ui32 fake_time{}; // window: Fake alt/ctrl event time stamp. si32 fake_scan{}; // window: Fake LeftCtrl scancode. b256 vkstat{}; // window: Win32 keyboard virtual keys state. + flag shutdown_called{}; // window: window_shutdow was called. window(auto&& ...Args) : winbase{ Args... } @@ -5777,7 +5769,7 @@ namespace netxs::gui } } } - bool keybd_read_input() + bool _keybd_read_input() { union key_state_t { @@ -6032,7 +6024,7 @@ namespace netxs::gui if (mfocus.wheel && (winmsg.message == WM_KEYDOWN || winmsg.message == WM_KEYUP || // Ignore all kb events in unfocused state. winmsg.message == WM_SYSKEYDOWN || winmsg.message == WM_SYSKEYUP)) { - keybd_read_input(); + _keybd_read_input(); sys_command(syscmd::update); } else @@ -6286,7 +6278,7 @@ namespace netxs::gui void window_make_exposed() { ::SetWindowPos((HWND)master.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOACTIVATE); } void window_make_topmost(bool s) { ontop_state = s; ::SetWindowPos((HWND)master.hWnd, s ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } void window_make_foreground() { ::SetForegroundWindow((HWND)master.hWnd); } //::AllowSetForegroundWindow(ASFW_ANY); } // Neither ::SetFocus() nor ::SetActiveWindow() can switch focus immediately. - void window_shutdown() { ::SendMessageW((HWND)master.hWnd, WM_CLOSE, NULL, NULL); } + void window_shutdown() { if (!shutdown_called.exchange(true)) ::PostMessageW((HWND)master.hWnd, WM_CLOSE, NULL, NULL); } // Deadlock when SendMessageW synchronously calls focus() which locks UI mutex. void window_cleanup() { ::RemoveClipboardFormatListener((HWND)master.hWnd); ::PostQuitMessage(0); } fp2d mouse_get_pos() { return fp2d{ winmsg.pt.x, winmsg.pt.y }; } void mouse_capture_impl() { ::SetCapture((HWND)master.hWnd); } @@ -6344,7 +6336,7 @@ namespace netxs::gui } else { - ::ShowWindow((HWND)master.hWnd, SW_RESTORE); + ::ShowWindow((HWND)master.hWnd, SW_RESTORE); // Trigger WM_SETFOCUS on win32. } } void sync_os_settings() @@ -6370,16 +6362,16 @@ namespace netxs::gui //todo implement ::RemoveMenu(ctxmenu, SC_MOVE, MF_BYCOMMAND); ::RemoveMenu(ctxmenu, SC_SIZE, MF_BYCOMMAND); - // The first ShowWindow() call ignores SW_SHOW. + // The first ShowWindow call ignores explicit flags (like SW_SHOW) for the main window + // and forces the visibility mode specified in STARTUPINFO by the OS (explorer.exe/lnk). auto mode = SW_SHOW; for (auto& l : layers) { auto& p = l.get(); - ::ShowWindow((HWND)p.hWnd, std::exchange(mode, SW_SHOWNA)); + ::ShowWindow((HWND)p.hWnd, std::exchange(mode, SW_SHOWNA)); // Trigger WM_SETFOCUS on win32 if explorer.exe sets it visible. } ::AddClipboardFormatListener((HWND)master.hWnd); // It posts WM_CLIPBOARDUPDATE to sync clipboard anyway. sync_clipboard(); // Clipboard should be in sync at (before) startup. - window_make_foreground(); } //todo static @@ -6670,58 +6662,77 @@ namespace netxs::gui byte layout_count; byte behavior_type; byte width; - std::array syms; + std::array syms; // 8: Level1Shift..Level8Shift }; std::array key_syms{}; si32 key_count{}; si32 latin_key_count{}; - bool is_latin() { return latin_key_count >= 26; } + bool is_latin() { return latin_key_count > 'Z' - 'A'; } }; using modifier_map_t = x11::req::xkb::get_map::reply::modifier_map; - x11::session_t& session = *x11::session_ptr; - mouse_state_t mouse_state; - text batch_buffer; - fp2d current_mouse_pos; - ui16 master_pointer_id{}; - ui16 captured_pointer_id{}; - bool is_foreground_window{}; - twod hidden_coor; - flag block_mouse_movement{}; - std::unordered_map recv_buffers; - std::atomic current_msc = 0; - - ui32 led_state{}; // window: X11 keyboard LED state (CapsLock/NumLock/ScrollLock). - std::array vkstat{}; // window: X11 keyboard virtual keys state. - std::vector key_types; - std::array layouts; // window: Keyboard layout list. - modifier_map_t modifier_map{}; // window: Dynamic modifier bit bindings for compose processing. - std::array keycode_to_vkey{}; // window: Latin keycodes to vkey lut. - std::array vkey_to_keycode{}; // window: vkey to national keycodes lut. - std::array extvkey_to_keycode{}; // window: vkey+extflag to keycodes lut. 512: 8bit + extflag. + x11::session_t& session = *x11::session_ptr; // window: Current X11 session state. + + mouse_state_t mouse_state; // window: Filter for chaotic mouse movements in xwl. + text batch_buffer; // window: X11 sending buffer. + fp2d current_mouse_pos; // window: Current mouse coor. + ui16 master_pointer_id{}; // window: Master mouse device id for mouse capturing/releasing. + ui16 captured_pointer_id{}; // window: Lasted captured master mouse device id. + bool is_foreground_window{}; // window: Foreground window flag (has input focus). + twod hidden_coor; // window: Safe coordinates where hidden windows can be moved. + flag block_mouse_movement{}; // window: Flag blocking mouse movement event processing (discarding). + std::unordered_map recv_buffers; // window: Buffers for receiving fragmented messages from neighboring windows (multifocus). + std::atomic current_msc{}; // window: Current media stream counter value (for vblank synchronization). + + ui32 led_state{}; // window: X11 keyboard LED state (CapsLock/NumLock/ScrollLock). + std::array vkstat{}; // window: X11 keyboard virtual keys state. + std::vector key_types; // window: X11 key behavior type list. + std::array layouts; // window: Keyboard layout list. + modifier_map_t modifier_map{}; // window: Dynamic modifier bit bindings for compose processing. + std::array keycode_to_vkey{}; // window: Latin keycodes to vkey lut. + std::array vkey_to_keycode{}; // window: vkey to national keycodes lut. + std::array extvkey_to_keycode{}; // window: vkey+extflag to keycodes lut. 512: 8bit + extflag. x11::compose compose{ modifier_map }; // window: POSIX Compose state machine. + std::unordered_map timer_threads; // window: Timer threads. + std::mutex timer_mutex; // window: Timer mutex. + window(auto&& ...Args) : winbase{ Args... } { - //todo it is just a test - auto align = [](ui32 offset){ return (ui32)(offset + 15) & ~15; }; - auto large_step = (session.shm_buffer_len / 2) / 3; - auto small_step = (session.shm_buffer_len / 2) / 9; - layers[0].get().shm_offset = 0; - layers[1].get().shm_offset = align(large_step); - layers[2].get().shm_offset = align(layers[1].get().shm_offset + large_step); - layers[3].get().shm_offset = align(layers[2].get().shm_offset + small_step); - layers[4].get().shm_offset = align(layers[3].get().shm_offset + small_step); - if constexpr (debugmode) log("shm_size=%% l0=%% l1=%% l2=%% l3=%% l4=%%", session.shm_buffer_len, + _recalc_layer_offsets(); + } + + auto _recalc_layer_offsets() + { + auto prev_images = std::array{}; + auto large_step = session.shm_buffer.len / 3; + auto small_step = large_step / 3; + auto current_offset = 0u; + auto set_limits =[&](si32 i, ui32 shm_limit) + { + auto align = [](ui32 offset){ return (ui32)(offset + 15) & ~15; }; // 4bit aligning. + auto& l = layers[i].get(); + prev_images[i] = l.data; + l.shm_offset = current_offset; + auto new_limit = align(shm_limit); + l.shm_pixel_limit = new_limit / sizeof(argb); + current_offset += new_limit; + }; + set_limits(0, large_step); + set_limits(1, large_step); + set_limits(2, small_step); + set_limits(3, small_step); + set_limits(4, small_step); + if constexpr (debugmode) log("shm_size=%% l0=%% l1=%% l2=%% l3=%% l4=%%", session.shm_buffer.len, layers[0].get().shm_offset, layers[1].get().shm_offset, layers[2].get().shm_offset, layers[3].get().shm_offset, layers[4].get().shm_offset); + return prev_images; } - auto _check_if_mouse_moved(fp2d coor, bool forced) //todo: Workaround: Master's root coords are broken when windows are intensively moved in the most of linux distributions (even query_pointer affected). { if (current_mouse_pos != coor && (forced || !block_mouse_movement.load(std::memory_order_acquire))) @@ -6753,7 +6764,7 @@ namespace netxs::gui .height = (ui16)target_area.size.y }); auto r = target_area; r.coor -= s.area.coor; - auto dirty_offset = s.get_offset(session) + (r.coor.y * s.area.size.x * sizeof(ui32)); + auto dirty_offset = s.shm_offset + r.coor.y * s.area.size.x * sizeof(ui32); seq_num_any = session.accumrq(batch_buffer, x11::req::shm::put_image { .major_opcode = session.shm_major_opcode, @@ -6767,7 +6778,7 @@ namespace netxs::gui .src_height = (ui16)r.size.y, // .dst_x = (si16)0,//r.coor.x, // Window dest coor. .dst_y = (si16)0,//r.coor.y, // - .shm_seg_id = session.shm_segment_xid, + .shm_seg_id = session.shm_buffer.xid, .offset = (ui32)dirty_offset, // New data start. }); } @@ -6855,19 +6866,21 @@ namespace netxs::gui x11::req::configure_window::payload{ .x = (ui16)target_area.coor.x, .y = (ui16)target_area.coor.y, .width = (ui16)target_area.size.x, - .height = (ui16)target_area.size.y }); + .height = (ui16)target_area.size.y, + .stack_mode = x11::req::configure_window::Above }); target_area.size = dot_11; session.accumrq(batch_buffer, x11::req::configure_window{ .window_id = (ui32)s.bg_hWnd }, x11::req::configure_window::payload{ .x = (ui16)target_area.coor.x, .y = (ui16)target_area.coor.y, .width = (ui16)target_area.size.x, - .height = (ui16)target_area.size.y }); + .height = (ui16)target_area.size.y, + .stack_mode = x11::req::configure_window::Above }); //session.accumrq(batch_buffer, x11::req::map_window{ .window_id = (ui32)s.bg_hWnd }); //session.accumrq(batch_buffer, x11::req::map_window{ .window_id = (ui32)s.fg_hWnd }); if (s.live) { auto r = rect{ dot_00, s.area.size }; - auto dirty_offset = s.get_offset(session) + (r.coor.y * s.area.size.x * sizeof(ui32)); + auto dirty_offset = s.shm_offset + r.coor.y * s.area.size.x * sizeof(ui32); seq_num = session.accumrq(batch_buffer, x11::req::shm::put_image { .major_opcode = session.shm_major_opcode, @@ -6881,7 +6894,7 @@ namespace netxs::gui .src_height = (ui16)r.size.y, // .dst_x = (si16)r.coor.x, // Window dest coor. .dst_y = (si16)r.coor.y, // - .shm_seg_id = session.shm_segment_xid, + .shm_seg_id = session.shm_buffer.xid, .offset = (ui32)dirty_offset, // New data start. }); } @@ -6909,11 +6922,6 @@ namespace netxs::gui session.accumrq(batch_buffer, x11::req::poly_point{ .drawable_id = (ui32)s.bg_hWnd, .gc_id = (ui32)s.bg_hdc }); } - // Make base layer foreground. - session.accumrq(batch_buffer, x11::req::configure_window{ .window_id = (ui32)master.fg_hWnd }, - x11::req::configure_window::payload{ .stack_mode = x11::req::configure_window::Above }); - session.accumrq(batch_buffer, x11::req::configure_window{ .window_id = (ui32)master.bg_hWnd }, - x11::req::configure_window::payload{ .stack_mode = x11::req::configure_window::Above }); // Configure mouse input. session.set_mouse_input(batch_buffer, master.fg_hWnd, master.live); session.set_mouse_input(batch_buffer, master.bg_hWnd, faux); @@ -7522,7 +7530,6 @@ namespace netxs::gui auto state = keybd_test_state(); return state; } - bool keybd_read_input() { return true; } void keybd_sync_shift(bool /*async*/) {} si32 keybd_conv_keyid2media(si32 /*keyid*/) { return 0; } si32 keybd_conv_media2keyid(si32 /*mediakey*/) { return input::key::undef; } @@ -7673,7 +7680,7 @@ namespace netxs::gui { if (!s.data.data() || s.area.size.x <= 0 || s.area.size.y <= 0) return; auto target_coor = s.live ? s.area.coor : hidden_coor; - auto windowmoved = s.prev.coor != target_coor; + auto windowmoved = s.prev.coor(target_coor); s.windowsized = s.live && std::exchange(s.prev_size, s.area.size) != s.area.size; if (s.windowsized) { @@ -7686,7 +7693,6 @@ namespace netxs::gui } else if (windowmoved) { - s.prev.coor = target_coor; session.accumrq(batch_buffer, x11::req::configure_window{ .window_id = (ui32)s.fg_hWnd, }, x11::req::configure_window::payload{ .x = (si16)target_coor.x, .y = (si16)target_coor.y }); @@ -7701,7 +7707,7 @@ namespace netxs::gui { continue; } - auto dirty_offset = s.get_offset(session) + (r.coor.y * s.area.size.x * sizeof(ui32)); + auto dirty_offset = s.shm_offset + r.coor.y * s.area.size.x * sizeof(ui32); seq_num = session.accumrq(batch_buffer, x11::req::shm::put_image { .major_opcode = session.shm_major_opcode, @@ -7715,10 +7721,15 @@ namespace netxs::gui .src_height = (ui16)r.size.y, // .dst_x = (si16)r.coor.x, // Window dest coor. .dst_y = (si16)r.coor.y, // - .shm_seg_id = session.shm_segment_xid, + .shm_seg_id = session.shm_buffer.xid, .offset = (ui32)dirty_offset, // New data start. }); } + if (!s.live && windowmoved) // Put transparent pixel to the upper-left corner (the one visible window dot at hidden_coor). + { + session.accumrq(batch_buffer, x11::req::poly_point{ .drawable_id = (ui32)s.fg_hWnd, + .gc_id = (ui32)s.fg_hdc }); + } if (seq_num.has_value()) { s.set_seq_num(session, seq_num.value()); @@ -7784,11 +7795,13 @@ namespace netxs::gui if (s.windowsized) { s.windowsized = faux; + // Hide the layer with prev size. session.accumrq(batch_buffer, x11::req::configure_window{ .window_id = (ui32)s.bg_hWnd, }, x11::req::configure_window::payload{ .x = (ui16)hidden_coor.x, .y = (ui16)hidden_coor.y, .width = 1, .height = 1 }); + // Reveal the layer with new size. session.accumrq(batch_buffer, x11::req::configure_window{ .window_id = (ui32)s.fg_hWnd, }, x11::req::configure_window::payload{ .x = (ui16)s.area.coor.x, .y = (ui16)s.area.coor.y }); @@ -7819,17 +7832,79 @@ namespace netxs::gui } } } - void layer_timer_start(layer& /*s*/, span /*elapse*/, ui32 /*eventid*/) {} - void layer_timer_stop(layer& /*s*/, ui32 /*eventid*/) {} + void layer_timer_start(layer& s, span elapse, ui32 eventid) + { + if (!eventid) return; + layer_timer_stop(s, eventid); + if (std::find(s.klok.begin(), s.klok.end(), eventid) == s.klok.end()) + { + s.klok.push_back(eventid); + } + timer_threads[eventid] = std::jthread([&, timeout = elapse, eventid](std::stop_token stop_token) + { + while (!stop_token.stop_requested()) + { + auto lock = std::unique_lock{ timer_mutex }; + std::condition_variable_any{}.wait_for(lock, stop_token, timeout, [&]{ return stop_token.stop_requested(); }); + if (stop_token.stop_requested()) break; + timer_event(eventid); + } + }); + } + void layer_timer_stop(layer& s, ui32 eventid) + { + auto iter = std::find(s.klok.begin(), s.klok.end(), eventid); + if (iter != s.klok.end()) + { + s.klok.erase(iter); + } + auto it = timer_threads.find(eventid); + if (it != timer_threads.end()) + { + timer_threads.erase(it); // jthread dtor auto calls request_stop() and join. + } + } bits layer_get_bits(layer& s, bool zeroize = faux) { if (s.area) { if (s.resized()) { - auto layer_shm_ptr = (argb*)(session.shm_buffer_ptr + s.get_offset(session, true)); + auto required_pixels = std::max(1u, (ui32)(s.area.size.x * s.area.size.y)); + if (required_pixels > s.shm_pixel_limit) // Recalc new shm buffer limits. + { + auto ratio = (fp32)session.shm_buffer.len / s.shm_pixel_limit; // inc div by 4 + auto new_shm_buffer_len = (ui32)std::ceil(ratio * required_pixels * 1.1f); // inc mul by 4 // 1.1f: +~10% + auto shm_buffer = x11::session_t::shm_buffer_t{}; + if (shm_buffer.allocate(new_shm_buffer_len)) + { + auto prev_buffer = session.shm_attach(shm_buffer); + auto prev_images = _recalc_layer_offsets(); + auto i = 0; + for (auto prev_data : prev_images) // Copy existing bitmaps from the prev_buffer to the new buffer. + { + auto& l = layers[i++].get(); + auto dst = shm_buffer.ptr + l.shm_offset; + std::memcpy(dst, prev_data.data(), prev_data.length() * sizeof(argb)); + // Switch to the new buffer. + auto bitmap_span = std::span{ (argb*)dst, prev_data.length() }; + l.data = bits{ bitmap_span, prev_data.area() }; + } + session.shm_detach(prev_buffer); + // Deferred deallocation in sync with X-server. + session.sendrq(x11::req::get_input_focus{}, {}, [prev_buffer](auto& /*ev*/, view /*payload*/) mutable + { + prev_buffer.deallocate(); + if constexpr (debugmode) log("prev shm_buffer deallocated"); + }); + } + else + { + log(ansi::err("%%Failed to allocate MIT-SHM buffer of %% bytes", prompt::x11, new_shm_buffer_len)); + } + } s.prev.size = s.area.size; - auto bitmap_span = std::span{ layer_shm_ptr, (size_t)s.area.size.x * s.area.size.y }; + auto bitmap_span = std::span{ (argb*)(session.shm_buffer.ptr + s.shm_offset), required_pixels }; s.data = bits{ bitmap_span, s.area }; zeroize = true; } @@ -7895,7 +7970,7 @@ namespace netxs::gui // session.accumrq(batch_buffer, x11::req::unmap_window{ .window_id = (ui32)s.fg_hWnd }); // session.accumrq(batch_buffer, x11::req::map_window{ .window_id = (ui32)s.fg_hWnd }); // auto r = rect{ dot_00, s.area.size }; - // auto dirty_offset = s.get_offset(session); + // auto dirty_offset = s.shm_offset; // auto seq_num = session.accumrq(batch_buffer, x11::req::shm::put_image // { // .major_opcode = session.shm_major_opcode, @@ -7909,7 +7984,7 @@ namespace netxs::gui // .src_height = (ui16)r.size.y, // // .dst_x = (si16)r.coor.x, // Window dest coor. // .dst_y = (si16)r.coor.y, // - // .shm_seg_id = session.shm_segment_xid, + // .shm_seg_id = session.shm_buffer.xid, // .offset = (ui32)dirty_offset, // New data start. // }); // s.set_seq_num(session, seq_num); @@ -8729,7 +8804,6 @@ namespace netxs::gui bool keybd_test_pressed_ex(si32 /*virtcod*/, si32 /*keycode*/ = 0) { return true; /*!!(vkstat[virtcod] & 0x80);*/ } bool keybd_test_toggled(si32 /*virtcod*/) { return true; /*!!(vkstat[virtcod] & 0x01);*/ } bool keybd_read_pressed(si32 /*virtcod*/) { return true; /*!!(::GetAsyncKeyState(virtcod) & 0x8000);*/ } - bool keybd_read_input() { return true; } void keybd_sync_shift(bool /*async*/) {} si32 keybd_conv_keyid2media(si32 /*keyid*/) { return 0; } si32 keybd_conv_media2keyid(si32 /*mediakey*/) { return input::key::undef; } diff --git a/src/netxs/desktopio/input.hpp b/src/netxs/desktopio/input.hpp index 9ffc07cd9b..7ec9348651 100644 --- a/src/netxs/desktopio/input.hpp +++ b/src/netxs/desktopio/input.hpp @@ -553,8 +553,8 @@ namespace netxs::input si16 vkey; struct cmp { - auto operator()(keyrec const& r, si32 vkey) const { return r.vkey < vkey; } - auto operator()(si32 vkey, keyrec const& r) const { return vkey < r.vkey; } + auto operator () (keyrec const& r, si32 vkey) const { return r.vkey < vkey; } + auto operator () (si32 vkey, keyrec const& r) const { return vkey < r.vkey; } }; }; constexpr auto total_hash_count = [] @@ -681,7 +681,7 @@ namespace netxs::input bool operator == (map const& m) const = default; struct hashproc { - auto operator()(map const& m) const + auto operator () (map const& m) const { return m.hash; } @@ -1435,9 +1435,12 @@ namespace netxs::input } void set(view utf8) { - digest++; - string = utf8; - page_sptr.reset(); + if (string != utf8) + { + digest++; + string = utf8; + page_sptr.reset(); + } } auto get_render_sptr(cell const& tooltip_colors) { @@ -1504,9 +1507,8 @@ namespace netxs::input if (!canceled) { time_to_run = datetime::now() + timeout; - digest = current_sptr->digest; } - if (current_sptr) + if (current_sptr) // Sync empty tooltips too. Show it if updated later. { digest = current_sptr->digest; } @@ -1552,7 +1554,7 @@ namespace netxs::input if (canceled) return; if (deed == input::key::MouseMove) { - if (coor(gear.mouse::coord)) // Hide tooltip on mouse move. + if (coor(gear.mouse::coord)) // Hide tooltip on intercell mouse move. { if (visible) { @@ -1571,7 +1573,7 @@ namespace netxs::input hide(); } } - auto check(time now) // Called every timer tick. + auto check(time now) // Called every timer tick. Return true if tooltip changed (damaged). { if (changed_visibility) { @@ -1582,8 +1584,8 @@ namespace netxs::input { digest = current_sptr->digest; fresh = true; - visible = true; canceled = current_sptr->get().empty(); + visible = !canceled; coor(gear.mouse::coord); return true; } diff --git a/src/netxs/desktopio/system.hpp b/src/netxs/desktopio/system.hpp index 2a2c24819c..4a10814b4d 100644 --- a/src/netxs/desktopio/system.hpp +++ b/src/netxs/desktopio/system.hpp @@ -4689,6 +4689,7 @@ namespace netxs::os std::mutex writemtx{}; std::condition_variable writesyn{}; sptr termlink{}; + bool last_written_esc{}; // Should we wake up bash readline if it blocks after a single "\x1b". Even SIGHUP is ignored. operator bool () { return attached; } @@ -4750,6 +4751,10 @@ namespace netxs::os { std::swap(cache, writebuf); guard.unlock(); + if (cache.size()) + { + last_written_esc = cache.back() == '\x1b'; + } if (terminal.io_log) log(prompt::cin, "\n\t", utf::replace_all(ansi::hi(utf::debase(cache)), "\n", ansi::pushsgr().nil().add("\n\t").popsgr())); if (termlink->send(cache)) { @@ -4778,6 +4783,12 @@ namespace netxs::os } auto sighup(bool state = true) { + if (last_written_esc) // Wake up bash readline if it blocks after a single "\x1b". Even SIGHUP is ignored. // "\x07\x03" + { + write("\x07"); //termlink->send("\x07"); + std::this_thread::sleep_for(10ms); + //termlink->handle.close(); // This wakes up bash but breaks all app logs while its closing. + } if (attached && !signaled.exchange(state)) { termlink->sighup(); diff --git a/src/netxs/desktopio/x11.hpp b/src/netxs/desktopio/x11.hpp index 8767a9035a..b599ae3a92 100644 --- a/src/netxs/desktopio/x11.hpp +++ b/src/netxs/desktopio/x11.hpp @@ -1840,6 +1840,70 @@ namespace netxs::x11 bool is_master{}; bool enabled{}; }; + struct shm_buffer_t + { + fd_t fd{ os::invalid_fd }; + byte* ptr{}; + ui32 len{}; + ui32 xid{}; + + bool allocate(size_t size) + { + assert(fd == os::invalid_fd); + fd = + #if defined(__ANDROID__) + os::shm_open("vtm", O_RDWR | O_CREAT | O_EXCL, 0600); + #elif defined(__linux__) + ::memfd_create("x11_shm_buffer", MFD_CLOEXEC); + #else + ::shm_open(SHM_ANON, O_RDWR | O_CREAT | O_EXCL, 0600); // SHM_ANON - native anonymous descriptor in BSD. + #endif + if (fd == os::invalid_fd) + { + log("%%Failed to create anonymous shared memory fd (errno=%%)", prompt::gui, os::error()); + } + else + { + if (::ftruncate(fd, size) == -1) // Set shm size. + { + ::close(fd); + fd = os::invalid_fd; + log("%%Failed to truncate shared memory file to required size (errno=%%)", prompt::gui, os::error()); + } + else + { + auto mapped_ptr = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (mapped_ptr == MAP_FAILED) + { + ::close(fd); + fd = os::invalid_fd; + log("%%Failed to mmap shared memory descriptor (errno=%%)", prompt::gui, os::error()); + } + else + { + ptr = (byte*)mapped_ptr; + len = size; + if constexpr (debugmode) log("%%Shared buffer successfuly created at 0x%%, %% bytes", prompt::x11, utf::to_hex(ptr), len); + } + } + } + return len > 0; + } + void deallocate() + { + if (ptr && ptr != MAP_FAILED) + { + ::munmap(ptr, len); + len = {}; + ptr = {}; + } + if (fd != os::invalid_fd) + { + ::close(fd); + fd = os::invalid_fd; + } + } + }; text vendor_str; // buffer[32..32+vendor_length] = vendor_str std::vector pixmap_formats; // format * number_of_formats = pixmap_formats @@ -1892,10 +1956,7 @@ namespace netxs::x11 byte shm_major_opcode = 0; byte shm_completion_event = 0; - fd_t shm_buffer_fd = os::invalid_fd; - byte* shm_buffer_ptr = {}; - ui32 shm_buffer_len = {}; - ui32 shm_segment_xid = {}; + shm_buffer_t shm_buffer; byte xi2_major_opcode = 0; @@ -1953,7 +2014,7 @@ namespace netxs::x11 session_t() = default; ~session_t() { - reset_shared_buffer(); + shm_buffer.deallocate(); } void sync_reply(ui16 sequence_number) const @@ -2308,10 +2369,11 @@ namespace netxs::x11 log(errmsg + errdetails); return faux; } - void send_shm_attach_fd(ui32 client_shmseg_xid) + auto shm_attach(shm_buffer_t& new_shm_buffer) { + new_shm_buffer.xid = new_resource_id(); auto request = x11::req::shm::attach_fd{ .major_opcode = shm_major_opcode, - .shm_seg_id = client_shmseg_xid }; + .shm_seg_id = new_shm_buffer.xid }; auto iov = ::iovec{ .iov_base = &request, .iov_len = sizeof(request) }; union // Ancillary Data @@ -2330,20 +2392,23 @@ namespace netxs::x11 cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; - std::memcpy(&(CMSG_DATA(cmsg)), &shm_buffer_fd, sizeof(shm_buffer_fd)); + std::memcpy(&(CMSG_DATA(cmsg)), &new_shm_buffer.fd, sizeof(new_shm_buffer.fd)); auto lock = std::lock_guard{ mutex }; sequence_counter++; auto bytes_sent = ::sendmsg(x11connection->handle.w, &msg, 0); if (bytes_sent == -1) { - log("%%sendmsg failed during shm_attach_fd invocation", prompt::x11); + log(ansi::err("%%Failed to send shm_attach_fd invocation: '%%'"), prompt::x11, os::error()); } + if constexpr (debugmode) log("%%New shared buffer segment XID 0x%% is attached", prompt::x11, utf::to_hex(new_shm_buffer.xid)); + return std::exchange(shm_buffer, new_shm_buffer); } - void send_shm_detach_fd(ui32 client_shmseg_xid) + void shm_detach(shm_buffer_t& old_shm_buffer) { sendrq({ .major_opcode = shm_major_opcode, - .shm_seg_id = client_shmseg_xid }); - if constexpr (debugmode) log("%%Shared buffer segment XID %% is detached", prompt::x11, client_shmseg_xid); + .shm_seg_id = old_shm_buffer.xid }); + if constexpr (debugmode) log("%%Shared buffer segment XID 0x%% is detached", prompt::x11, utf::to_hex(old_shm_buffer.xid)); + free_resource_id(old_shm_buffer.xid); } void set_mouse_input(text& batch_buffer, arch window_id, bool state) // Make sub-layer transparent for mouse. { @@ -2478,70 +2543,6 @@ namespace netxs::x11 .format = sizeof(byte) * 8 }, // Format (8: 8-bit chars (string)). title); } - bool resize_shared_buffer(size_t size) - { - if (shm_buffer_len) - { - //todo implement delayed detach+copy - send_shm_detach_fd(shm_segment_xid); - reset_shared_buffer(); - free_resource_id(shm_segment_xid); - } - shm_buffer_fd = - #if defined(__ANDROID__) - os::shm_open("vtm", O_RDWR | O_CREAT | O_EXCL, 0600); - #elif defined(__linux__) - ::memfd_create("x11_shm_buffer", MFD_CLOEXEC); - #else - ::shm_open(SHM_ANON, O_RDWR | O_CREAT | O_EXCL, 0600); // SHM_ANON - native anonymous descriptor in BSD. - #endif - if (shm_buffer_fd == os::invalid_fd) - { - log("%%Failed to create anonymous shared memory fd (errno=%%)", prompt::gui, os::error()); - } - else - { - if (::ftruncate(shm_buffer_fd, size) == -1) // Set shm size. - { - ::close(shm_buffer_fd); - shm_buffer_fd = os::invalid_fd; - log("%%Failed to truncate shared memory file to required size (errno=%%)", prompt::gui, os::error()); - } - else - { - auto mapped_ptr = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_buffer_fd, 0); - if (mapped_ptr == MAP_FAILED) - { - ::close(shm_buffer_fd); - shm_buffer_fd = os::invalid_fd; - log("%%Failed to mmap shared memory descriptor (errno=%%)", prompt::gui, os::error()); - } - else - { - shm_buffer_ptr = (byte*)mapped_ptr; - shm_buffer_len = size; - shm_segment_xid = new_resource_id(); - send_shm_attach_fd(shm_segment_xid); - if constexpr (debugmode) log("%%Shared buffer successfuly created at 0x%%, %% bytes", prompt::x11, utf::to_hex(shm_buffer_ptr), shm_buffer_len); - } - } - } - return shm_buffer_len > 0; - } - void reset_shared_buffer() - { - if (shm_buffer_ptr && shm_buffer_ptr != MAP_FAILED) - { - ::munmap(shm_buffer_ptr, shm_buffer_len); - shm_buffer_len = {}; - shm_buffer_ptr = {}; - } - if (shm_buffer_fd != os::invalid_fd) - { - ::close(shm_buffer_fd); - shm_buffer_fd = os::invalid_fd; - } - } auto get_atoms() { auto get_atom_id = [&](qiew name, bool create) @@ -3163,9 +3164,11 @@ namespace netxs::x11 auto& x11screen = session.roots.front().s; session.set_x11_display_size(twod{ x11screen.width_in_pixels, x11screen.height_in_pixels }); auto max_grid_size = x11screen.width_in_pixels * x11screen.height_in_pixels; - auto required_buffer_size = 2 * 3 * max_grid_size * sizeof(argb); // 2: Double buffer, 3: master+blinks+header/footer/tooltip. - if (session.resize_shared_buffer(required_buffer_size)) + auto required_buffer_size = 3 * max_grid_size * sizeof(argb); // 3: master=1/3 + blinks=1/3 + header=1/9+footer=1/9+tooltip=1/9. + auto shm_buffer = session_t::shm_buffer_t{}; + if (shm_buffer.allocate(required_buffer_size)) { + session.shm_attach(shm_buffer); x11::session_ptr = session_ptr; } } @@ -3478,7 +3481,7 @@ namespace netxs::x11 }; struct node_comparator { - auto operator()(auto& a, auto& b) const + auto operator () (auto& a, auto& b) const { auto get_keysym = [](auto& x) -> ui32 { diff --git a/src/vtm.xml b/src/vtm.xml index d9ecff43bd..6c825db451 100644 --- a/src/vtm.xml +++ b/src/vtm.xml @@ -10,7 +10,7 @@ R"==( - + @@ -148,7 +148,7 @@ R"==( - +