Skip to content

Commit

Permalink
directvt#571 WIP: Try to make popup GUI windows foreground
Browse files Browse the repository at this point in the history
  • Loading branch information
o-sdn-o committed Sep 18, 2024
1 parent 476b09e commit 0546dba
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 30 deletions.
67 changes: 63 additions & 4 deletions src/netxs/desktopio/consrv.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -534,6 +535,7 @@ struct impl : consrv
closed{ faux },
leader{ },
ctrl_c{ faux },
fstate{ true },
mstate{ }
{ }

Expand Down Expand Up @@ -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<Arch>(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<Arch>(::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<Arch>(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 };
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 ()
{
Expand Down Expand Up @@ -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 ()
{
Expand Down Expand Up @@ -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{};
Expand Down
11 changes: 10 additions & 1 deletion src/netxs/desktopio/gui.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 }; }
Expand Down Expand Up @@ -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
Expand Down
69 changes: 45 additions & 24 deletions src/netxs/desktopio/system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,63 +241,62 @@ namespace netxs::os
using NtOpenFile_ptr = std::decay<decltype(::NtOpenFile)>::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<decltype(::CallMsgFilterW)>::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<NtOpenFile_ptr>( ::GetProcAddress(ntdll_dll, "NtOpenFile"));
RtlGetVersion = reinterpret_cast<RtlGetVersion_ptr>( ::GetProcAddress(ntdll_dll, "RtlGetVersion"));
CsrClientCallServer = reinterpret_cast<CsrClientCallServer_ptr>(::GetProcAddress(ntdll_dll, "CsrClientCallServer"));
ConsoleControl = reinterpret_cast<ConsoleControl_ptr>(::GetProcAddress(user32_dll, "ConsoleControl"));
//TranslateMessageEx = reinterpret_cast<TranslateMessageEx_ptr> (::GetProcAddress(user32_dll, "TranslateMessageEx"));
//ConsoleControl = reinterpret_cast<ConsoleControl_ptr>(::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)");
}
}

void operator=(refs const&) = delete;
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; }
Expand Down Expand Up @@ -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<class ...Args>
//auto ConsoleControl(Args... args)
//{
// auto& inst = get_ntdll();
// return inst ? inst.ConsoleControl(std::forward<Args>(args)...)
// : nt::status::not_found;
//}
template<class ...Args>
auto ConsoleControl(Args... args)
{
auto& inst = get_ntdll();
return inst ? inst.ConsoleControl(std::forward<Args>(args)...)
: nt::status::not_found;
}
//template<class Arch>
//auto ConsoleTask(Arch proc_pid, ui32 what)
//{
Expand Down Expand Up @@ -411,6 +410,28 @@ namespace netxs::os
(ui32)sizeof(nttask::payload)); //todo MSVC 17.7.0 requires type cast (ui32)
return stat;
}
template<class Arch = size_t>
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<class I = noop, class O = noop>
auto ioctl(DWORD dwIoControlCode, fd_t hDevice, I&& send = {}, O&& recv = {}) -> NTSTATUS
{
Expand Down Expand Up @@ -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<DWORD>::max();
static auto mutex = std::mutex();
static auto cf_text = UINT{ CF_UNICODETEXT };
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/vtm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 0546dba

Please sign in to comment.