Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/client/core/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ void App::OnPacketReceived(const NetworkPacket& packet)

QueueOrCreateWorld(id, url, textureName, width, height);
}
else if (event.name == CefEvent::Server::CreateWorld2DBrowser && event.args.size() >= 6) {
else if (event.name == CefEvent::Server::CreateWorld2DBrowser && event.args.size() >= 10) {
int id = event.args[0].intValue;
const std::string& url = event.args[1].stringValue;
float worldX = event.args[2].floatValue;
Expand Down Expand Up @@ -541,6 +541,12 @@ void App::OnPacketReceived(const NetworkPacket& packet)
{
browser_.ExitGame();
}
else if (event.name == CefEvent::Server::SetEscapeMenuMode && event.args.size() >= 1)
{
int mode = event.args[0].intValue;

browser_.SetEscapeMenuMode(static_cast<EscapeMenuMode>(mode));
}
break;
}
case PacketType::EmitBrowserEvent:
Expand Down
213 changes: 213 additions & 0 deletions src/client/core/browser/escape_menu.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
#include "escape_menu.hpp"

#include <game_sa/CMenuManager.h>
#include <game_sa/CPad.h>
#include <game_sa/CTimer.h>

namespace
{
bool IsPadStartPressed(CPad* pad)
{
if (!pad)
return false;

return pad->NewState.Start != 0
|| pad->OldState.Start != 0
|| pad->PCTempKeyState.Start != 0
|| pad->PCTempJoyState.Start != 0
|| pad->PCTempMouseState.Start != 0;
}

void ClearPadStartState(CPad* pad)
{
if (!pad)
return;

pad->NewState.Start = 0;
pad->OldState.Start = 0;
pad->PCTempKeyState.Start = 0;
pad->PCTempJoyState.Start = 0;
pad->PCTempMouseState.Start = 0;
}
}

const char* EscapeMenuController::GetModeName(EscapeMenuMode mode)
{
switch (mode)
{
case EscapeMenuMode::Gta:
return "gta";
case EscapeMenuMode::Disabled:
return "disabled";
case EscapeMenuMode::Custom:
return "custom";
}

return "unknown";
}

EscapeMenuMode EscapeMenuController::NormalizeMode(EscapeMenuMode mode)
{
switch (mode)
{
case EscapeMenuMode::Gta:
case EscapeMenuMode::Disabled:
case EscapeMenuMode::Custom:
return mode;
}

return EscapeMenuMode::Gta;
}

bool EscapeMenuController::ShouldSuppressNativeMenu(EscapeMenuMode mode)
{
return mode == EscapeMenuMode::Disabled || mode == EscapeMenuMode::Custom;
}

EscapeMenuMode EscapeMenuController::SetMode(EscapeMenuMode mode)
{
mode = NormalizeMode(mode);

const EscapeMenuMode previous = mode_.exchange(mode, std::memory_order_acq_rel);

if (mode != EscapeMenuMode::Custom)
SetCustomMenuOpen(false);

if (mode == EscapeMenuMode::Gta)
{
input_down_.store(false, std::memory_order_release);
}
else
{
SuppressNativeMenu();
}

return previous;
}

EscapeMenuMode EscapeMenuController::GetMode() const
{
return mode_.load(std::memory_order_acquire);
}

bool EscapeMenuController::IsNativePauseMenuVisible() const
{
return FrontEndMenuManager.m_bMenuActive && !FrontEndMenuManager.m_bDontDrawFrontEnd;
}

bool EscapeMenuController::UpdateInput()
{
const EscapeMenuMode mode = GetMode();
if (!ShouldSuppressNativeMenu(mode))
{
input_down_.store(false, std::memory_order_release);
return false;
}

const bool escape_down = (::GetAsyncKeyState(VK_ESCAPE) & 0x8000) != 0;
const bool start_down = IsPadStartPressed(CPad::GetPad(0)) || IsPadStartPressed(CPad::GetPad(1));

return ProcessInputState(escape_down || start_down);
}

bool EscapeMenuController::ConsumeWndProcMessage(UINT message, WPARAM w_param, LPARAM l_param)
{
if (!ShouldSuppressNativeMenu(GetMode()))
return false;

if ((message == WM_KEYDOWN || message == WM_KEYUP || message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
&& ((static_cast<int>(w_param) & 0xFF) == VK_ESCAPE))
{
const bool down = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN);
const bool repeat = down ? (((static_cast<std::uint32_t>(l_param) >> 30) & 1u) != 0u) : false;

if (!(down && repeat))
{
ProcessInputState(down);
}
else
{
SuppressNativeMenu();
}

return true;
}

if (message == WM_CHAR && w_param == VK_ESCAPE)
{
SuppressNativeMenu();
return true;
}

return false;
}

void EscapeMenuController::SuppressNativeMenu()
{
if (!ShouldSuppressNativeMenu(GetMode()))
return;

ClearPadStartState(CPad::GetPad(0));
ClearPadStartState(CPad::GetPad(1));

FrontEndMenuManager.m_bActivateMenuNextFrame = false;

if (FrontEndMenuManager.m_bMenuActive)
{
FrontEndMenuManager.m_bMenuActive = false;
FrontEndMenuManager.m_bActivateMenuNextFrame = false;
CTimer::EndUserPause();

if (auto* pad = CPad::GetPad(0))
{
pad->JustOutOfFrontEnd = 1;
}
}
}

bool EscapeMenuController::IsCustomMenuOpen() const
{
return custom_menu_open_.load(std::memory_order_acquire);
}

bool EscapeMenuController::HasPendingCustomMenuVisibilityChange()
{
return custom_menu_visibility_changed_.exchange(false, std::memory_order_acq_rel);
}

bool EscapeMenuController::ProcessInputState(bool is_down)
{
const EscapeMenuMode mode = GetMode();

if (mode == EscapeMenuMode::Gta)
{
input_down_.store(is_down, std::memory_order_release);
return false;
}

const bool was_down = input_down_.exchange(is_down, std::memory_order_acq_rel);
const bool just_pressed = is_down && !was_down;

SuppressNativeMenu();

if (mode == EscapeMenuMode::Custom && just_pressed)
{
ToggleCustomMenu();
}

return true;
}

void EscapeMenuController::SetCustomMenuOpen(bool open)
{
const bool previous = custom_menu_open_.exchange(open, std::memory_order_acq_rel);
if (previous != open)
{
custom_menu_visibility_changed_.store(true, std::memory_order_release);
}
}

void EscapeMenuController::ToggleCustomMenu()
{
SetCustomMenuOpen(!IsCustomMenuOpen());
}
43 changes: 43 additions & 0 deletions src/client/core/browser/escape_menu.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#pragma once

#include <atomic>
#include <cstdint>

#include <windows.h>

// Defines how the native GTA SA ESC/pause menu is handled
enum class EscapeMenuMode : std::uint8_t
{
Gta = 0,
Disabled = 1,
Custom = 2
};

class EscapeMenuController
{
public:
static const char* GetModeName(EscapeMenuMode mode);
static EscapeMenuMode NormalizeMode(EscapeMenuMode mode);
static bool ShouldSuppressNativeMenu(EscapeMenuMode mode);

EscapeMenuMode SetMode(EscapeMenuMode mode);
EscapeMenuMode GetMode() const;

bool IsNativePauseMenuVisible() const;
bool UpdateInput();
bool ConsumeWndProcMessage(UINT message, WPARAM w_param, LPARAM l_param);
void SuppressNativeMenu();

bool IsCustomMenuOpen() const;
bool HasPendingCustomMenuVisibilityChange();

private:
bool ProcessInputState(bool is_down);
void SetCustomMenuOpen(bool open);
void ToggleCustomMenu();

std::atomic<EscapeMenuMode> mode_{ EscapeMenuMode::Gta };
std::atomic<bool> input_down_{ false };
std::atomic<bool> custom_menu_open_{ false };
std::atomic<bool> custom_menu_visibility_changed_{ false };
};
70 changes: 69 additions & 1 deletion src/client/core/browser/manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,7 @@ void BrowserManager::OnPaint(int id, const void* buffer, int width, int height)

bool BrowserManager::RenderAll()
{
if (!draw_enabled_)
if (ShouldSkipBrowserRendering())
return false;

UpdateAudioSpatialization();
Expand Down Expand Up @@ -1099,6 +1099,9 @@ bool BrowserManager::RenderAll()

void BrowserManager::OnBeforeEntityRender(CEntity* entity)
{
if (ShouldSkipBrowserRendering())
return;

auto it = entityToBrowserId_.find(entity);
if (it == entityToBrowserId_.end())
return;
Expand Down Expand Up @@ -1131,6 +1134,9 @@ BrowserInstance* BrowserManager::GetBrowserInstance(int id)

bool BrowserManager::IsAnyBrowserVisible() const
{
if (ShouldSkipBrowserRendering())
return false;

for (const auto& [id, browser] : browsers_)
{
if (browser && browser->visible)
Expand Down Expand Up @@ -1286,6 +1292,62 @@ void BrowserManager::ExitGame()
ExitProcess(0);
}

void BrowserManager::SetEscapeMenuMode(EscapeMenuMode mode)
{
const EscapeMenuMode previous = escape_menu_.SetMode(mode);
const EscapeMenuMode current = escape_menu_.GetMode();

DispatchEscapeMenuEvents();

if (previous != current)
{
LOG_INFO("[CEF] Escape menu mode changed: {} -> {}",
EscapeMenuController::GetModeName(previous),
EscapeMenuController::GetModeName(current));
}
}

bool BrowserManager::ShouldSkipBrowserRendering() const
{
return !draw_enabled_ || escape_menu_.IsNativePauseMenuVisible();
}

void BrowserManager::DispatchEscapeMenuEvents()
{
if (escape_menu_.HasPendingCustomMenuVisibilityChange())
{
EmitCustomEscapeMenuVisibility();
}
}

void BrowserManager::EmitCustomEscapeMenuVisibility()
{
if (!CefCurrentlyOn(TID_UI))
{
CefPostTask(TID_UI, base::BindOnce(&BrowserManager::EmitCustomEscapeMenuVisibility, base::Unretained(this)));
return;
}

const bool visible = escape_menu_.IsCustomMenuOpen();

for (const auto& [id, instance] : browsers_)
{
if (!instance || !instance->browser || !instance->browser->IsValid())
continue;

CefRefPtr<CefFrame> frame = instance->browser->GetMainFrame();
if (!frame || !frame->IsValid())
continue;

CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("emit_event");
CefRefPtr<CefListValue> list = msg->GetArgumentList();
list->SetString(0, "cef:escape_menu");
list->SetBool(1, visible);

frame->SendProcessMessage(PID_RENDERER, msg);
}
}

void BrowserManager::OnDeviceLost()
{
// Stop CEF rendering during device reset
Expand Down Expand Up @@ -1341,6 +1403,12 @@ void BrowserManager::OnDeviceReset(IDirect3DDevice9* device)

LRESULT BrowserManager::OnWndProcMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (escape_menu_.ConsumeWndProcMessage(msg, wParam, lParam))
{
DispatchEscapeMenuEvents();
return true;
}

if (msg == WM_KEYDOWN || msg == WM_KEYUP || msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP)
{
if (key_capture_enabled_ && network_.GetState() == ConnectionState::CONNECTED && !network_.IsNonCefServer())
Expand Down
Loading
Loading