Make in-stream control panel persistent and readable
This commit is contained in:
parent
7ddadd21d4
commit
8a7be971a4
3 changed files with 138 additions and 50 deletions
|
|
@ -178,29 +178,8 @@ void Session::clConnectionStatusUpdate(int connectionStatus)
|
|||
"Connection status update: %d",
|
||||
connectionStatus);
|
||||
|
||||
if (!s_ActiveSession->m_Preferences->connectionWarnings) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_ActiveSession->m_MouseEmulationRefCount > 0) {
|
||||
// Don't display the overlay if mouse emulation is already using it
|
||||
return;
|
||||
}
|
||||
|
||||
switch (connectionStatus)
|
||||
{
|
||||
case CONN_STATUS_POOR:
|
||||
s_ActiveSession->m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed);
|
||||
s_ActiveSession->m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate,
|
||||
s_ActiveSession->m_StreamConfig.bitrate > 5000 ?
|
||||
"Slow connection to PC\nReduce your bitrate" : "Poor connection to PC");
|
||||
s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
|
||||
break;
|
||||
case CONN_STATUS_OKAY:
|
||||
s_ActiveSession->m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed);
|
||||
s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
|
||||
break;
|
||||
}
|
||||
s_ActiveSession->m_ConnectionStatus.store(connectionStatus, std::memory_order_relaxed);
|
||||
s_ActiveSession->refreshControlPanelOverlay();
|
||||
}
|
||||
|
||||
void Session::clSetHdrMode(bool enabled)
|
||||
|
|
@ -579,6 +558,8 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
|||
m_AudioVolumeScalar(1.0f),
|
||||
m_AllowGamepadInput(true),
|
||||
m_AllowKeyboardMouseInput(false),
|
||||
m_ControlPanelVisible(true),
|
||||
m_ConnectionStatus(CONN_STATUS_OKAY),
|
||||
m_StatusOverlayGeneration(0),
|
||||
m_QtWindow(nullptr),
|
||||
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
|
||||
|
|
@ -593,6 +574,7 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
|||
m_AudioSampleCount(0),
|
||||
m_DropAudioEndTime(0)
|
||||
{
|
||||
m_TemporaryStatusOverlayText[0] = '\0';
|
||||
}
|
||||
|
||||
void Session::setGamepadInputAllowed(bool allowed)
|
||||
|
|
@ -661,6 +643,13 @@ void Session::toggleKeyboardMouseInputAllowed()
|
|||
setKeyboardMouseInputAllowed(!isKeyboardMouseInputAllowed());
|
||||
}
|
||||
|
||||
void Session::toggleControlPanelVisibility()
|
||||
{
|
||||
const bool visible = !m_ControlPanelVisible.load(std::memory_order_relaxed);
|
||||
m_ControlPanelVisible.store(visible, std::memory_order_relaxed);
|
||||
showTemporaryStatusOverlay(visible ? "Control panel shown" : "Control panel hidden");
|
||||
}
|
||||
|
||||
void Session::notifyInputPermissionState()
|
||||
{
|
||||
char buffer[96];
|
||||
|
|
@ -700,14 +689,80 @@ Uint32 Session::statusOverlayTimeoutCallback(Uint32, void* param)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void Session::showTemporaryStatusOverlay(const char* text, Uint32 timeoutMs)
|
||||
void Session::refreshControlPanelOverlay()
|
||||
{
|
||||
if (m_MouseEmulationRefCount > 0) {
|
||||
if (!m_ControlPanelVisible.load(std::memory_order_relaxed)) {
|
||||
if (m_TemporaryStatusOverlayText[0] != '\0') {
|
||||
m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate, m_TemporaryStatusOverlayText);
|
||||
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
|
||||
}
|
||||
else {
|
||||
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate, text);
|
||||
char volumeState[24];
|
||||
if (m_ManualAudioMuted.load(std::memory_order_relaxed)) {
|
||||
SDL_snprintf(volumeState, sizeof(volumeState), "MUTED");
|
||||
}
|
||||
else {
|
||||
const int percent = (int)SDL_roundf(getAudioVolumeScalar() * 100.0f);
|
||||
SDL_snprintf(volumeState, sizeof(volumeState), "%d%%", percent);
|
||||
}
|
||||
|
||||
char panelText[640];
|
||||
SDL_snprintf(panelText,
|
||||
sizeof(panelText),
|
||||
"Stream Controls\n"
|
||||
"KB/M (Ctrl+Alt+Shift+K): %s\n"
|
||||
"Pad (Ctrl+Alt+Shift+G): %s\n"
|
||||
"Vol (U/J, mute N): %s\n"
|
||||
"UI (Ctrl+Alt+Shift+P / Select+L1+R1+B): ON",
|
||||
isKeyboardMouseInputAllowed() ? "ON" : "OFF",
|
||||
isGamepadInputAllowed() ? "ON" : "OFF",
|
||||
volumeState);
|
||||
|
||||
size_t used = SDL_strlen(panelText);
|
||||
if (used < sizeof(panelText) && m_MouseEmulationRefCount > 0) {
|
||||
SDL_snprintf(panelText + used,
|
||||
sizeof(panelText) - used,
|
||||
"\nMouse mode: ACTIVE (hold Start to disable)");
|
||||
used = SDL_strlen(panelText);
|
||||
}
|
||||
|
||||
if (used < sizeof(panelText) &&
|
||||
m_Preferences->connectionWarnings &&
|
||||
m_ConnectionStatus.load(std::memory_order_relaxed) == CONN_STATUS_POOR) {
|
||||
SDL_snprintf(panelText + used,
|
||||
sizeof(panelText) - used,
|
||||
"\nNetwork: %s",
|
||||
m_StreamConfig.bitrate > 5000 ? "Slow (reduce bitrate)" : "Poor");
|
||||
used = SDL_strlen(panelText);
|
||||
}
|
||||
|
||||
if (used < sizeof(panelText) && m_TemporaryStatusOverlayText[0] != '\0') {
|
||||
SDL_snprintf(panelText + used,
|
||||
sizeof(panelText) - used,
|
||||
"\nStatus: %s",
|
||||
m_TemporaryStatusOverlayText);
|
||||
}
|
||||
|
||||
m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate, panelText);
|
||||
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
|
||||
}
|
||||
|
||||
void Session::showTemporaryStatusOverlay(const char* text, Uint32 timeoutMs)
|
||||
{
|
||||
if (text == nullptr || text[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_snprintf(m_TemporaryStatusOverlayText,
|
||||
sizeof(m_TemporaryStatusOverlayText),
|
||||
"%s",
|
||||
text);
|
||||
refreshControlPanelOverlay();
|
||||
|
||||
const uint32_t generation = m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
SDL_AddTimer(timeoutMs, statusOverlayTimeoutCallback, (void*)(uintptr_t)generation);
|
||||
|
|
@ -727,6 +782,9 @@ void Session::applyHostInputPolicy(bool allowKeyboard, bool allowMouse, bool all
|
|||
if (reason != LI_SESSION_INPUT_POLICY_REASON_STREAM_START) {
|
||||
notifyInputPermissionState();
|
||||
}
|
||||
else {
|
||||
refreshControlPanelOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void Session::updateEffectiveAudioMuteState()
|
||||
|
|
@ -1709,16 +1767,8 @@ void Session::notifyMouseEmulationMode(bool enabled)
|
|||
m_MouseEmulationRefCount += enabled ? 1 : -1;
|
||||
SDL_assert(m_MouseEmulationRefCount >= 0);
|
||||
|
||||
// We re-use the status update overlay for mouse mode notification
|
||||
if (m_MouseEmulationRefCount > 0) {
|
||||
m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed);
|
||||
m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate, "Gamepad mouse mode active\nLong press Start to deactivate");
|
||||
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
|
||||
}
|
||||
else {
|
||||
m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed);
|
||||
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
|
||||
}
|
||||
showTemporaryStatusOverlay(enabled ? "Gamepad mouse mode enabled" : "Gamepad mouse mode disabled");
|
||||
refreshControlPanelOverlay();
|
||||
}
|
||||
|
||||
class AsyncConnectionStartThread : public QThread
|
||||
|
|
@ -1924,7 +1974,10 @@ void Session::start()
|
|||
m_ManualAudioMuted.store(false, std::memory_order_relaxed);
|
||||
m_AudioMuted.store(false, std::memory_order_relaxed);
|
||||
m_AudioVolumeScalar.store(1.0f, std::memory_order_relaxed);
|
||||
m_ControlPanelVisible.store(true, std::memory_order_relaxed);
|
||||
m_ConnectionStatus.store(CONN_STATUS_OKAY, std::memory_order_relaxed);
|
||||
m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed);
|
||||
m_TemporaryStatusOverlayText[0] = '\0';
|
||||
|
||||
// Initialize the gamepad code with our preferences
|
||||
// NB: m_InputHandler must be initialize before starting the connection.
|
||||
|
|
@ -2128,6 +2181,7 @@ void Session::exec()
|
|||
|
||||
// Toggle the stats overlay if requested by the user
|
||||
m_OverlayManager.setOverlayState(Overlay::OverlayDebug, m_Preferences->showPerformanceOverlay);
|
||||
refreshControlPanelOverlay();
|
||||
|
||||
// Switch to async logging mode when we enter the SDL loop
|
||||
StreamUtils::enterAsyncLoggingMode();
|
||||
|
|
@ -2223,9 +2277,9 @@ void Session::exec()
|
|||
break;
|
||||
}
|
||||
case SDL_CODE_HIDE_STATUS_OVERLAY:
|
||||
if ((uint32_t)(uintptr_t)event.user.data1 == m_StatusOverlayGeneration.load(std::memory_order_relaxed) &&
|
||||
m_MouseEmulationRefCount == 0) {
|
||||
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
|
||||
if ((uint32_t)(uintptr_t)event.user.data1 == m_StatusOverlayGeneration.load(std::memory_order_relaxed)) {
|
||||
m_TemporaryStatusOverlayText[0] = '\0';
|
||||
refreshControlPanelOverlay();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ public:
|
|||
void setKeyboardMouseInputAllowed(bool allowed);
|
||||
void toggleGamepadInputAllowed();
|
||||
void toggleKeyboardMouseInputAllowed();
|
||||
void toggleControlPanelVisibility();
|
||||
void notifyInputPermissionState();
|
||||
|
||||
float getAudioVolumeScalar() const
|
||||
|
|
@ -275,6 +276,7 @@ private:
|
|||
static
|
||||
Uint32 statusOverlayTimeoutCallback(Uint32 interval, void* param);
|
||||
|
||||
void refreshControlPanelOverlay();
|
||||
void showTemporaryStatusOverlay(const char* text, Uint32 timeoutMs = 1500);
|
||||
void applyHostInputPolicy(bool allowKeyboard, bool allowMouse, bool allowGamepad, uint8_t reason);
|
||||
void sendInputPermissionStateToHost(uint8_t reason);
|
||||
|
|
@ -297,6 +299,8 @@ private:
|
|||
std::atomic<float> m_AudioVolumeScalar;
|
||||
std::atomic<bool> m_AllowGamepadInput;
|
||||
std::atomic<bool> m_AllowKeyboardMouseInput;
|
||||
std::atomic<bool> m_ControlPanelVisible;
|
||||
std::atomic<int> m_ConnectionStatus;
|
||||
std::atomic<uint32_t> m_StatusOverlayGeneration;
|
||||
Uint32 m_FullScreenFlag;
|
||||
QQuickWindow* m_QtWindow;
|
||||
|
|
@ -321,6 +325,7 @@ private:
|
|||
OPUS_MULTISTREAM_CONFIGURATION m_OriginalAudioConfig;
|
||||
int m_AudioSampleCount;
|
||||
Uint32 m_DropAudioEndTime;
|
||||
char m_TemporaryStatusOverlayText[160];
|
||||
|
||||
Overlay::OverlayManager m_OverlayManager;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ OverlayManager::OverlayManager() :
|
|||
m_Overlays[OverlayType::OverlayDebug].color = {0xD0, 0xD0, 0x00, 0xFF};
|
||||
m_Overlays[OverlayType::OverlayDebug].fontSize = 20;
|
||||
|
||||
m_Overlays[OverlayType::OverlayStatusUpdate].color = {0xCC, 0x00, 0x00, 0xFF};
|
||||
m_Overlays[OverlayType::OverlayStatusUpdate].fontSize = 36;
|
||||
m_Overlays[OverlayType::OverlayStatusUpdate].color = {0xF0, 0xF0, 0xF0, 0xFF};
|
||||
m_Overlays[OverlayType::OverlayStatusUpdate].fontSize = 24;
|
||||
|
||||
// While TTF will usually not be initialized here, it is valid for that not to
|
||||
// be the case, since Session destruction is deferred and could overlap with
|
||||
|
|
@ -146,16 +146,45 @@ void OverlayManager::notifyOverlayUpdated(OverlayType type)
|
|||
}
|
||||
}
|
||||
|
||||
SDL_Surface* newSurface = nullptr;
|
||||
if (m_Overlays[type].enabled) {
|
||||
// The _Wrapped variant is required for line breaks to work
|
||||
SDL_Surface* textSurface = TTF_RenderText_Blended_Wrapped(m_Overlays[type].font,
|
||||
m_Overlays[type].text,
|
||||
m_Overlays[type].color,
|
||||
1024);
|
||||
if (textSurface != nullptr && type == OverlayStatusUpdate) {
|
||||
constexpr int kHorizontalPadding = 18;
|
||||
constexpr int kVerticalPadding = 12;
|
||||
|
||||
SDL_Surface* panelSurface = SDL_CreateRGBSurfaceWithFormat(0,
|
||||
textSurface->w + (kHorizontalPadding * 2),
|
||||
textSurface->h + (kVerticalPadding * 2),
|
||||
32,
|
||||
SDL_PIXELFORMAT_ARGB8888);
|
||||
if (panelSurface != nullptr) {
|
||||
SDL_FillRect(panelSurface,
|
||||
nullptr,
|
||||
SDL_MapRGBA(panelSurface->format, 0x00, 0x00, 0x00, 0xA0));
|
||||
|
||||
SDL_Rect destination = {kHorizontalPadding, kVerticalPadding, textSurface->w, textSurface->h};
|
||||
SDL_SetSurfaceBlendMode(textSurface, SDL_BLENDMODE_BLEND);
|
||||
SDL_BlitSurface(textSurface, nullptr, panelSurface, &destination);
|
||||
SDL_FreeSurface(textSurface);
|
||||
newSurface = panelSurface;
|
||||
}
|
||||
else {
|
||||
newSurface = textSurface;
|
||||
}
|
||||
}
|
||||
else {
|
||||
newSurface = textSurface;
|
||||
}
|
||||
}
|
||||
|
||||
// Exchange the old surface with the new one
|
||||
SDL_Surface* oldSurface = (SDL_Surface*)SDL_AtomicSetPtr(
|
||||
(void**)&m_Overlays[type].surface,
|
||||
m_Overlays[type].enabled ?
|
||||
// The _Wrapped variant is required for line breaks to work
|
||||
TTF_RenderText_Blended_Wrapped(m_Overlays[type].font,
|
||||
m_Overlays[type].text,
|
||||
m_Overlays[type].color,
|
||||
1024)
|
||||
: nullptr);
|
||||
SDL_Surface* oldSurface = (SDL_Surface*)SDL_AtomicSetPtr((void**)&m_Overlays[type].surface,
|
||||
newSurface);
|
||||
|
||||
// Notify the renderer
|
||||
m_Renderer->notifyOverlayUpdated(type);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue