GM 控制台跟随游戏窗口:窗口模式下按游戏客户区收小并居中
- 新增 FindGameWindow/FitInsideGame:取本进程最大可见顶层窗口的客户区作为游戏画面 - 开窗口时按 min(配置尺寸, 客户区-2倍边距) 定尺寸并居中;游戏画面小于配置时自动缩 - 新增 GmWindowFollowGame:切全屏/改分辨率/挪游戏窗口时重新摆放,玩家手动拖过的窗口不动 - 服务端指纹回绑 186A2C95… (1260032)
This commit is contained in:
1 parent
0c7ef6d645
commit
7f41f9797a
3 files changed
+162
-9
No files matched your search
@@ -92,6 +92,125 @@ void LayoutCenterOnOwner()
|
||||
SetWindowPos(g_hwnd, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 跟随游戏窗口
|
||||
//
|
||||
// 需求:控制台要“长”在游戏画面里。全屏(无边框最大化)时用配置里的目标尺寸,
|
||||
// 窗口模式下按游戏窗口的实际客户区等比收小并居中;玩家改分辨率、切全屏/窗口,
|
||||
// 都跟着重新摆一次。
|
||||
|
||||
int g_desiredW = 1040;
|
||||
int g_desiredH = 720;
|
||||
RECT g_lastGameRect = { 0, 0, 0, 0 }; // 上次布局时游戏客户区的位置
|
||||
bool g_fitValid = false;
|
||||
|
||||
struct GameSearch {
|
||||
DWORD pid;
|
||||
HWND best;
|
||||
LONG area;
|
||||
};
|
||||
|
||||
BOOL CALLBACK OnGameCandidate(HWND hwnd, LPARAM param)
|
||||
{
|
||||
GameSearch* s = reinterpret_cast<GameSearch*>(param);
|
||||
DWORD pid = 0;
|
||||
GetWindowThreadProcessId(hwnd, &pid);
|
||||
if (pid != s->pid) return TRUE;
|
||||
if (!IsWindowVisible(hwnd)) return TRUE;
|
||||
if (GetWindow(hwnd, GW_OWNER)) return TRUE;
|
||||
|
||||
wchar_t cls[128] = {};
|
||||
if (GetClassNameW(hwnd, cls, 128)) {
|
||||
if (_wcsicmp(cls, kWindowClass) == 0) return TRUE; // 我们自己
|
||||
if (_wcsicmp(cls, L"Browser Example") == 0) return TRUE; // 游戏自带面板
|
||||
if (_wcsicmp(cls, L"Internet Explorer_Server") == 0) return TRUE;
|
||||
}
|
||||
|
||||
RECT rc = {};
|
||||
if (!GetWindowRect(hwnd, &rc)) return TRUE;
|
||||
const LONG area = (rc.right - rc.left) * (rc.bottom - rc.top);
|
||||
if (area > s->area) {
|
||||
s->area = area;
|
||||
s->best = hwnd;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 游戏主窗口 —— 本进程里可见、没有 owner、面积最大的那个顶层窗口。
|
||||
HWND FindGameWindow()
|
||||
{
|
||||
GameSearch s = { GetCurrentProcessId(), nullptr, 0 };
|
||||
EnumWindows(OnGameCandidate, reinterpret_cast<LPARAM>(&s));
|
||||
return s.best;
|
||||
}
|
||||
|
||||
// 算一个「装得进游戏窗口」的矩形。gameOut 回传游戏客户区,供变化检测用。
|
||||
bool FitInsideGame(int desiredW, int desiredH, RECT* out, RECT* gameOut)
|
||||
{
|
||||
const HWND game = FindGameWindow();
|
||||
if (!game) return false;
|
||||
|
||||
RECT client = {};
|
||||
if (!GetClientRect(game, &client)) return false;
|
||||
POINT tl = { 0, 0 };
|
||||
if (!ClientToScreen(game, &tl)) return false;
|
||||
RECT g = { client.left + tl.x, client.top + tl.y, client.right + tl.x, client.bottom + tl.y };
|
||||
|
||||
const int gw = g.right - g.left;
|
||||
const int gh = g.bottom - g.top;
|
||||
if (gw < 320 || gh < 240) return false; // 还没进游戏/窗口还没建好
|
||||
|
||||
int margin = gw / 60;
|
||||
if (margin < 12) margin = 12;
|
||||
if (margin > 28) margin = 28;
|
||||
|
||||
int w = desiredW > 0 ? desiredW : gw;
|
||||
int h = desiredH > 0 ? desiredH : gh;
|
||||
if (w > gw - 2 * margin) w = gw - 2 * margin;
|
||||
if (h > gh - 2 * margin) h = gh - 2 * margin;
|
||||
// 窗口小到一定程度就别再缩了,否则控件挤得没法用;宁可稍微溢出一点。
|
||||
if (w < 720) w = 720;
|
||||
if (h < 500) h = 500;
|
||||
if (w > gw) w = gw;
|
||||
if (h > gh) h = gh;
|
||||
|
||||
out->left = g.left + (gw - w) / 2;
|
||||
out->top = g.top + (gh - h) / 2;
|
||||
out->right = out->left + w;
|
||||
out->bottom = out->top + h;
|
||||
if (gameOut) *gameOut = g;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 游戏窗口变了(切全屏/改分辨率/挪窗口)就重新摆一次。玩家自己拖过窗口、
|
||||
// 游戏没变的情况下不动它,避免跟玩家较劲。
|
||||
void FollowGameWindow()
|
||||
{
|
||||
if (!g_hwnd || !IsWindow(g_hwnd) || !IsWindowVisible(g_hwnd) || !g_fitValid) return;
|
||||
|
||||
RECT fit = {};
|
||||
RECT game = {};
|
||||
if (!FitInsideGame(g_desiredW, g_desiredH, &fit, &game)) return;
|
||||
|
||||
RECT cur = {};
|
||||
GetWindowRect(g_hwnd, &cur);
|
||||
|
||||
const bool gameChanged = !EqualRect(&game, &g_lastGameRect);
|
||||
const bool outside = cur.left < game.left - 4 || cur.top < game.top - 4 ||
|
||||
cur.right > game.right + 4 || cur.bottom > game.bottom + 4;
|
||||
if (!gameChanged && !outside) return;
|
||||
|
||||
SetWindowPos(g_hwnd, HWND_TOPMOST, fit.left, fit.top,
|
||||
fit.right - fit.left, fit.bottom - fit.top,
|
||||
SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||
ResizeWebView();
|
||||
InvalidateRect(g_hwnd, nullptr, FALSE);
|
||||
g_lastGameRect = game;
|
||||
LogLine("gm window: refit to game %ldx%ld -> %dx%d at %ld,%ld",
|
||||
game.right - game.left, game.bottom - game.top,
|
||||
static_cast<int>(fit.right - fit.left), static_cast<int>(fit.bottom - fit.top),
|
||||
static_cast<long>(fit.left), static_cast<long>(fit.top));
|
||||
}
|
||||
|
||||
void CloseButtonRect(RECT* out)
|
||||
{
|
||||
RECT rc = {};
|
||||
@@ -450,20 +569,45 @@ extern "C" bool GmWindowShow(const wchar_t* url, int width, int height)
|
||||
if (!EnsureWindow()) return false;
|
||||
|
||||
if (url && *url) g_url = url;
|
||||
if (width > 0) g_desiredW = width;
|
||||
if (height > 0) g_desiredH = height;
|
||||
|
||||
// 优先让窗口落在游戏画面里:全屏时就是配置的目标尺寸,窗口模式下自动收小。
|
||||
RECT fit = {};
|
||||
RECT game = {};
|
||||
const bool fitted = FitInsideGame(g_desiredW, g_desiredH, &fit, &game);
|
||||
|
||||
RECT rc = {};
|
||||
GetWindowRect(g_hwnd, &rc);
|
||||
const int w = width > 0 ? width : (rc.right - rc.left);
|
||||
const int h = height > 0 ? height : (rc.bottom - rc.top);
|
||||
if (!IsWindowVisible(g_hwnd)) {
|
||||
SetWindowPos(g_hwnd, HWND_TOPMOST, 0, 0, w, h, SWP_NOMOVE | SWP_SHOWWINDOW);
|
||||
if (fitted) {
|
||||
SetWindowPos(g_hwnd, HWND_TOPMOST, fit.left, fit.top,
|
||||
fit.right - fit.left, fit.bottom - fit.top,
|
||||
SWP_SHOWWINDOW | SWP_NOACTIVATE);
|
||||
g_lastGameRect = game;
|
||||
g_fitValid = true;
|
||||
LogLine("gm window: show inside game %ldx%ld -> %dx%d at %ld,%ld",
|
||||
game.right - game.left, game.bottom - game.top,
|
||||
static_cast<int>(fit.right - fit.left), static_cast<int>(fit.bottom - fit.top),
|
||||
static_cast<long>(fit.left), static_cast<long>(fit.top));
|
||||
} else {
|
||||
SetWindowPos(g_hwnd, HWND_TOPMOST, 0, 0,
|
||||
g_desiredW, g_desiredH, SWP_NOMOVE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
|
||||
g_fitValid = false;
|
||||
LogLine("gm window: game window not found, show at %dx%d", g_desiredW, g_desiredH);
|
||||
}
|
||||
} else {
|
||||
SetWindowPos(g_hwnd, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
if (fitted) {
|
||||
SetWindowPos(g_hwnd, HWND_TOPMOST, fit.left, fit.top,
|
||||
fit.right - fit.left, fit.bottom - fit.top, SWP_NOACTIVATE);
|
||||
g_lastGameRect = game;
|
||||
g_fitValid = true;
|
||||
ResizeWebView();
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_envStarted) {
|
||||
LayoutCenterOnOwner();
|
||||
if (!fitted) LayoutCenterOnOwner();
|
||||
StartWebView();
|
||||
} else if (g_web && !g_url.empty()) {
|
||||
// 页面服务可能重启过,重新打开时刷一次,避免停在错误页
|
||||
@@ -504,6 +648,13 @@ extern "C" void GmWindowAssertTop()
|
||||
}
|
||||
}
|
||||
|
||||
// 游戏窗口换了尺寸/位置(切全屏、改分辨率、拖窗口)时把控制台重新摆回游戏画面里。
|
||||
// 主循环每轮调一次,内部只在游戏矩形变化或窗口超出游戏范围时才真正动窗口。
|
||||
extern "C" void GmWindowFollowGame()
|
||||
{
|
||||
FollowGameWindow();
|
||||
}
|
||||
|
||||
extern "C" bool GmWindowFailed()
|
||||
{
|
||||
return g_failed;
|
||||
|
||||
Reference in new issue
Block a user