diff --git a/src/netxs/desktopio/consrv.hpp b/src/netxs/desktopio/consrv.hpp index a2b6bd0430..2f59873b77 100644 --- a/src/netxs/desktopio/consrv.hpp +++ b/src/netxs/desktopio/consrv.hpp @@ -523,6 +523,7 @@ struct impl : consrv irec leader; // evnt: Hanging key event record (lead byte). work ostask; // evnt: Console task thread for the child process. bool ctrl_c; // evnt: Ctrl+C was pressed. + bool fstate; // evnt: Console has kb focus. cast macros; // evnt: Doskey macros storage. hist inputs; // evnt: Input history per process name storage. mbtn dclick; // evnt: Mouse double-click tracker. @@ -534,6 +535,7 @@ struct impl : consrv closed{ faux }, leader{ }, ctrl_c{ faux }, + fstate{ true }, mstate{ } { } @@ -770,6 +772,39 @@ struct impl : consrv if (io_log) log("", prompt, "\n\t-------------------------"); }}; } + void set_process_foreground(Arch procid) + { + if (!fstate) return; + //auto& cl = closed; + std::thread{ [prompt = server.prompt, procid, io_log = server.io_log]() + { + //todo should we wait until the new app has created their fake console window? ConsoleFG doesn't work if we are too fast. + //wait input + //std::this_thread::yield(); + os::sleep(1s); + auto h_process = ::OpenProcess(MAXIMUM_ALLOWED, FALSE, (ui32)procid); + auto rc = nt::ConsoleFG(h_process, 1); + if (!rc) log("%%Set process foreground: rc=%% pid=%%", prompt, utf::to_hex(rc), procid); + else log("%%Set process foreground: rc=%% pid=%%", ansi::err(prompt), utf::to_hex(rc), procid); + os::close(h_process); + }}.detach(); + } + void set_all_processes_foreground(bool fgstate) + { + fstate = fgstate; + //auto rc = + nt::ConsoleFG(::GetCurrentProcess(), fgstate); + //log("%%Set process foreground: rc=%% pid=-1 state=%%", server.prompt, utf::to_hex(rc), fgstate?"1":"0"); + for (auto& client : server.joined) + { + auto h_process = ::OpenProcess(MAXIMUM_ALLOWED, FALSE, (ui32)client.procid); + //rc = + nt::ConsoleFG(h_process, fgstate); + //if (!rc) log("%%\tSet process foreground: rc=%% pid=%% state=%%", server.prompt, utf::to_hex(rc), client.procid, fgstate?"1":"0"); + //else log("%%\tSet process foreground: rc=%% pid=%% state=%%", ansi::err(server.prompt), utf::to_hex(rc), client.procid, fgstate?"1":"0"); + os::close(h_process); + } + } void sighup() { auto lock = std::lock_guard{ locker }; @@ -917,6 +952,7 @@ struct impl : consrv void focus(bool state) { auto lock = std::lock_guard{ locker }; + set_all_processes_foreground(state); auto data = INPUT_RECORD{ .EventType = FOCUS_EVENT }; data.Event.FocusEvent.bSetFocus = state; stream.emplace_back(data); @@ -2503,6 +2539,7 @@ struct impl : consrv client.detail.header = utf::to_utf(details.header_data, details.header_size / sizeof(wchr)); client.detail.curexe = utf::to_utf(details.curexe_data, details.curexe_size / sizeof(wchr)); client.detail.curdir = utf::to_utf(details.curdir_data, details.curdir_size / sizeof(wchr)); + events.set_process_foreground(client.procid); log("\tprocid: ", client.procid, "\n\tthread: ", client.thread, "\n\tpgroup: ", client.pgroup, @@ -4500,20 +4537,41 @@ struct impl : consrv } reply; }; + //todo this approach is not crossplatform, use some kind of vt request instead + //auto brand = wide{}; + //auto& packet = payload::cast(upload); + //packet.reply.index = 0; + //if (os::dtvt::fontsz == dot_00) + //{ + // packet.reply.sizex = 10; + // packet.reply.sizey = 20; + // brand = L"Consolas"s; + //} + //else + //{ + // packet.reply.sizex = (si16)os::dtvt::fontsz.x; + // packet.reply.sizey = (si16)os::dtvt::fontsz.y; + // brand = utf::to_utf(os::dtvt::fontnm); + //} + //brand += L'\0'; + //packet.reply.pitch = TMPF_TRUETYPE; // Pwsh checks this to decide whether or not to switch to UTF-8. For raster fonts (non-Unicode), the low-order bits are set to zero. + //packet.reply.heavy = 0; + //std::copy(std::begin(brand), std::end(brand), std::begin(packet.reply.brand)); + auto& packet = payload::cast(upload); packet.reply.index = 0; packet.reply.sizex = 10; packet.reply.sizey = 20; packet.reply.pitch = TMPF_TRUETYPE; // Pwsh checks this to decide whether or not to switch to UTF-8. For raster fonts (non-Unicode), the low-order bits are set to zero. packet.reply.heavy = 0; - auto brand = L"Consolas"s + L'\0'; + auto brand = L"Courier New"s + L'\0'; std::copy(std::begin(brand), std::end(brand), std::begin(packet.reply.brand)); log("\tinput.fullscreen: ", packet.input.fullscreen ? "true" : "faux", "\n\treply.index: ", packet.reply.index, "\n\treply.size : ", packet.reply.sizex, "x", packet.reply.sizey, "\n\treply.pitch: ", packet.reply.pitch, "\n\treply.heavy: ", packet.reply.heavy, - "\n\treply.brand: ", utf::to_utf(brand)); + "\n\treply.brand: ", utf::to_utf(brand)); } auto api_window_font_set () { @@ -4613,14 +4671,14 @@ struct impl : consrv reply; }; auto& packet = payload::cast(upload); - packet.reply.handle = (Arch)winhnd; // - Fake window handle to tell powershell that everything is under console control. + packet.reply.handle = (Arch)winhnd; // - Console window handle to tell powershell that everything is under the console control. // - GH#268: "git log" launches "less.exe" which crashes if reply=NULL. // - "Far.exe" set their icon to all windows in the system if reply=-1. // - msys uses the handle to determine what processes are running in the same session. // - vim sets the icon of its hosting window. // - The handle is used to show/hide GUI console window. // - Used for SetConsoleTitle(). - log("\tfake window handle: ", utf::to_hex_0x(packet.reply.handle)); + log("\tconsole window handle: ", utf::to_hex_0x(packet.reply.handle)); } auto api_window_xkeys () { @@ -4968,6 +5026,7 @@ struct impl : consrv case WM_CREATE: break; case WM_DESTROY: ::PostQuitMessage(0); break; case WM_CLOSE: + // We do not process any of wm_title/wm_icon/wm_etc window messages bc it is not crossplatform approach. default: return DefWindowProcA(hwnd, uMsg, wParam, lParam); } return LRESULT{}; diff --git a/src/netxs/desktopio/gui.hpp b/src/netxs/desktopio/gui.hpp index e9510b1424..9cfeea78b9 100644 --- a/src/netxs/desktopio/gui.hpp +++ b/src/netxs/desktopio/gui.hpp @@ -357,6 +357,9 @@ namespace netxs::gui overline = f.overline; dashline = f.dashline; wavyline = f.wavyline; + //todo implement it via realtime request (for remotes) + //os::dtvt::fontnm = fallback.front().font_name; + //os::dtvt::fontsz = cellsize; } log("%%Set cell size: ", prompt::gui, cellsize); } @@ -3810,7 +3813,12 @@ namespace netxs::gui } void window_make_focused() { ::SetFocus((HWND)master.hWnd); } // Calls WM_KILLFOCOS(prev) + WM_ACTIVATEAPP(next) + WM_SETFOCUS(next). 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_foreground() { ::SetForegroundWindow((HWND)master.hWnd); } // Neither ::SetFocus() nor ::SetActiveWindow() can switch focus immediately. + void window_make_foreground() // Neither ::SetFocus() nor ::SetActiveWindow() can switch focus immediately. + { + ::SetForegroundWindow((HWND)master.hWnd); + ::AllowSetForegroundWindow(ASFW_ANY); + //::LockSetForegroundWindow(LSFW_UNLOCK); + } void window_shutdown() { ::SendMessageW((HWND)master.hWnd, WM_CLOSE, NULL, NULL); } void window_cleanup() { ::RemoveClipboardFormatListener((HWND)master.hWnd); ::PostQuitMessage(0); } twod mouse_get_pos() { return twod{ winmsg.pt.x, winmsg.pt.y }; } @@ -3911,6 +3919,7 @@ namespace netxs::gui for (auto p : { &master, &blinky, &footer, &header }) ::ShowWindow((HWND)p->hWnd, std::exchange(mode, SW_SHOWNA)); ::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 diff --git a/src/netxs/desktopio/system.hpp b/src/netxs/desktopio/system.hpp index 7a9c0be47c..f84f768d2d 100644 --- a/src/netxs/desktopio/system.hpp +++ b/src/netxs/desktopio/system.hpp @@ -241,37 +241,36 @@ namespace netxs::os using NtOpenFile_ptr = std::decay::type; using CsrClientCallServer_ptr = NTSTATUS(_stdcall *)(void*, void*, ui32, ui32); using RtlGetVersion_ptr = NTSTATUS(_stdcall *)(RTL_OSVERSIONINFOW*); + using ConsoleControl_ptr = NTSTATUS(_stdcall *)(ui32, void*, ui32); //using TranslateMessageEx_ptr = std::decay::type; //using TranslateMessageEx_ptr = BOOL(_stdcall *)(MSG const* pmsg, UINT flags); - //using ConsoleControl_ptr = NTSTATUS(_stdcall *)(ui32, void*, ui32); HMODULE ntdll_dll{}; + HMODULE user32_dll{}; NtOpenFile_ptr NtOpenFile{}; RtlGetVersion_ptr RtlGetVersion{}; CsrClientCallServer_ptr CsrClientCallServer{}; - - //HMODULE user32_dll{}; + ConsoleControl_ptr ConsoleControl{}; //TranslateMessageEx_ptr TranslateMessageEx{}; - //ConsoleControl_ptr ConsoleControl{}; refs() { - //user32_dll = ::LoadLibraryExA("user32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - ntdll_dll = ::LoadLibraryExA("ntdll.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - //if (!ntdll_dll || !user32_dll) os::fail("LoadLibraryEx(ntdll.dll | user32.dll)"); - if (!ntdll_dll) os::fail("LoadLibraryEx(ntdll.dll)"); + user32_dll = ::LoadLibraryExA("user32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + ntdll_dll = ::LoadLibraryExA("ntdll.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!ntdll_dll || !user32_dll) os::fail("LoadLibraryEx(ntdll.dll | user32.dll)"); + //if (!ntdll_dll) os::fail("LoadLibraryEx(ntdll.dll)"); else { NtOpenFile = reinterpret_cast( ::GetProcAddress(ntdll_dll, "NtOpenFile")); RtlGetVersion = reinterpret_cast( ::GetProcAddress(ntdll_dll, "RtlGetVersion")); CsrClientCallServer = reinterpret_cast(::GetProcAddress(ntdll_dll, "CsrClientCallServer")); + ConsoleControl = reinterpret_cast(::GetProcAddress(user32_dll, "ConsoleControl")); //TranslateMessageEx = reinterpret_cast (::GetProcAddress(user32_dll, "TranslateMessageEx")); - //ConsoleControl = reinterpret_cast(::GetProcAddress(user32_dll, "ConsoleControl")); if (!NtOpenFile) os::fail("::GetProcAddress(NtOpenFile)"); if (!RtlGetVersion) os::fail("::GetProcAddress(RtlGetVersion)"); if (!CsrClientCallServer) os::fail("::GetProcAddress(CsrClientCallServer)"); + if (!ConsoleControl) os::fail("::GetProcAddress(ConsoleControl)"); //if (!TranslateMessageEx) os::fail("::GetProcAddress(TranslateMessageEx)"); - //if (!ConsoleControl) os::fail("::GetProcAddress(ConsoleControl)"); } } @@ -279,25 +278,25 @@ namespace netxs::os refs(refs const&) = delete; refs(refs&& other) : ntdll_dll{ other.ntdll_dll }, + user32_dll{ other.user32_dll }, NtOpenFile{ other.NtOpenFile }, RtlGetVersion{ other.RtlGetVersion }, - CsrClientCallServer{ other.CsrClientCallServer } - //user32_dll{ other.user32_dll }, + CsrClientCallServer{ other.CsrClientCallServer }, + ConsoleControl{ other.ConsoleControl } //TranslateMessageEx{ other.TranslateMessageEx } - //ConsoleControl{ other.ConsoleControl } { other.ntdll_dll = {}; + other.user32_dll = {}; other.NtOpenFile = {}; other.RtlGetVersion = {}; other.CsrClientCallServer = {}; + other.ConsoleControl = {}; //other.TranslateMessageEx = {}; - //other.user32_dll = {}; - //other.ConsoleControl = {}; } ~refs() { if (ntdll_dll) ::FreeLibrary(ntdll_dll); - //if (user32_dll) ::FreeLibrary(user32_dll); + if (user32_dll) ::FreeLibrary(user32_dll); } constexpr explicit operator bool () const { return NtOpenFile != nullptr; } @@ -352,13 +351,13 @@ namespace netxs::os //todo: nt native api monobitness: // We have to make a direct call to ntdll.dll!CsrClientCallServer // due to a user32.dll!ConsoleControl does not work properly under WoW64. - //template - //auto ConsoleControl(Args... args) - //{ - // auto& inst = get_ntdll(); - // return inst ? inst.ConsoleControl(std::forward(args)...) - // : nt::status::not_found; - //} + template + auto ConsoleControl(Args... args) + { + auto& inst = get_ntdll(); + return inst ? inst.ConsoleControl(std::forward(args)...) + : nt::status::not_found; + } //template //auto ConsoleTask(Arch proc_pid, ui32 what) //{ @@ -411,6 +410,28 @@ namespace netxs::os (ui32)sizeof(nttask::payload)); //todo MSVC 17.7.0 requires type cast (ui32) return stat; } + template + auto ConsoleFG(HANDLE h_proc, bool f_stat) + { + struct fgstat + { + Arch h_proc; + ui32 f_stat; + }; + auto stat = fgstat{ .h_proc = (Arch)h_proc, .f_stat = f_stat }; + auto rc = nt::ConsoleControl((ui32)sizeof("Stat"), &stat, (ui32)sizeof(stat)); + return rc; + } + //void try_to_set_foreground()//HWND hWnd) + //{ + // //auto rc = + // nt::ConsoleFG(::GetCurrentProcess(), 1); + // //if (!rc) log("%%Set current process foreground: rc=%%", prompt::os, utf::to_hex(rc)); + // //else log("%%Set current process foreground: rc=%%", ansi::err(prompt::os), utf::to_hex(rc)); + // //::SetForegroundWindow(hWnd); + // //::LockSetForegroundWindow(LSFW_UNLOCK); + // //::AllowSetForegroundWindow(ASFW_ANY); + //} template auto ioctl(DWORD dwIoControlCode, fd_t hDevice, I&& send = {}, O&& recv = {}) -> NTSTATUS { @@ -1954,7 +1975,6 @@ namespace netxs::os { static constexpr auto ocs52head = "\033]52;"sv; #if defined(_WIN32) - static auto winhndl = HWND{}; static auto sequence = std::numeric_limits::max(); static auto mutex = std::mutex(); static auto cf_text = UINT{ CF_UNICODETEXT }; @@ -3635,6 +3655,7 @@ namespace netxs::os os::stdin_fd = fd_t{ ptr::test(::GetStdHandle(STD_INPUT_HANDLE ), os::invalid_fd) }; os::stdout_fd = fd_t{ ptr::test(::GetStdHandle(STD_OUTPUT_HANDLE), os::invalid_fd) }; os::stderr_fd = fd_t{ ptr::test(::GetStdHandle(STD_ERROR_HANDLE ), os::invalid_fd) }; + ::AllowSetForegroundWindow(ASFW_ANY); #else { auto conmode = -1; diff --git a/src/vtm.hpp b/src/vtm.hpp index 41208006b5..38e873b7fa 100644 --- a/src/vtm.hpp +++ b/src/vtm.hpp @@ -1550,7 +1550,7 @@ namespace netxs::app::vtm conf_rec.winform = item.take(attr::winform, fallback.winform, shared::win::options); conf_rec.hotkey = item.take(attr::hotkey, fallback.hotkey ); //todo register hotkey conf_rec.appcfg.cwd = item.take(attr::cwd, fallback.appcfg.cwd); - conf_rec.appcfg.cfg = item.take(attr::cfg, ""s); + conf_rec.appcfg.cfg = item.take(attr::cfg, ""s); conf_rec.appcfg.cmd = item.take(attr::cmd, fallback.appcfg.cmd); conf_rec.type = item.take(attr::type, fallback.type ); utf::to_low(conf_rec.type);