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",
|
"Connection status update: %d",
|
||||||
connectionStatus);
|
connectionStatus);
|
||||||
|
|
||||||
if (!s_ActiveSession->m_Preferences->connectionWarnings) {
|
s_ActiveSession->m_ConnectionStatus.store(connectionStatus, std::memory_order_relaxed);
|
||||||
return;
|
s_ActiveSession->refreshControlPanelOverlay();
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::clSetHdrMode(bool enabled)
|
void Session::clSetHdrMode(bool enabled)
|
||||||
|
|
@ -579,6 +558,8 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
||||||
m_AudioVolumeScalar(1.0f),
|
m_AudioVolumeScalar(1.0f),
|
||||||
m_AllowGamepadInput(true),
|
m_AllowGamepadInput(true),
|
||||||
m_AllowKeyboardMouseInput(false),
|
m_AllowKeyboardMouseInput(false),
|
||||||
|
m_ControlPanelVisible(true),
|
||||||
|
m_ConnectionStatus(CONN_STATUS_OKAY),
|
||||||
m_StatusOverlayGeneration(0),
|
m_StatusOverlayGeneration(0),
|
||||||
m_QtWindow(nullptr),
|
m_QtWindow(nullptr),
|
||||||
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
|
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_AudioSampleCount(0),
|
||||||
m_DropAudioEndTime(0)
|
m_DropAudioEndTime(0)
|
||||||
{
|
{
|
||||||
|
m_TemporaryStatusOverlayText[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::setGamepadInputAllowed(bool allowed)
|
void Session::setGamepadInputAllowed(bool allowed)
|
||||||
|
|
@ -661,6 +643,13 @@ void Session::toggleKeyboardMouseInputAllowed()
|
||||||
setKeyboardMouseInputAllowed(!isKeyboardMouseInputAllowed());
|
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()
|
void Session::notifyInputPermissionState()
|
||||||
{
|
{
|
||||||
char buffer[96];
|
char buffer[96];
|
||||||
|
|
@ -700,14 +689,80 @@ Uint32 Session::statusOverlayTimeoutCallback(Uint32, void* param)
|
||||||
return 0;
|
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;
|
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);
|
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;
|
const uint32_t generation = m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||||
SDL_AddTimer(timeoutMs, statusOverlayTimeoutCallback, (void*)(uintptr_t)generation);
|
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) {
|
if (reason != LI_SESSION_INPUT_POLICY_REASON_STREAM_START) {
|
||||||
notifyInputPermissionState();
|
notifyInputPermissionState();
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
refreshControlPanelOverlay();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::updateEffectiveAudioMuteState()
|
void Session::updateEffectiveAudioMuteState()
|
||||||
|
|
@ -1709,16 +1767,8 @@ void Session::notifyMouseEmulationMode(bool enabled)
|
||||||
m_MouseEmulationRefCount += enabled ? 1 : -1;
|
m_MouseEmulationRefCount += enabled ? 1 : -1;
|
||||||
SDL_assert(m_MouseEmulationRefCount >= 0);
|
SDL_assert(m_MouseEmulationRefCount >= 0);
|
||||||
|
|
||||||
// We re-use the status update overlay for mouse mode notification
|
showTemporaryStatusOverlay(enabled ? "Gamepad mouse mode enabled" : "Gamepad mouse mode disabled");
|
||||||
if (m_MouseEmulationRefCount > 0) {
|
refreshControlPanelOverlay();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AsyncConnectionStartThread : public QThread
|
class AsyncConnectionStartThread : public QThread
|
||||||
|
|
@ -1924,7 +1974,10 @@ void Session::start()
|
||||||
m_ManualAudioMuted.store(false, std::memory_order_relaxed);
|
m_ManualAudioMuted.store(false, std::memory_order_relaxed);
|
||||||
m_AudioMuted.store(false, std::memory_order_relaxed);
|
m_AudioMuted.store(false, std::memory_order_relaxed);
|
||||||
m_AudioVolumeScalar.store(1.0f, 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_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
m_TemporaryStatusOverlayText[0] = '\0';
|
||||||
|
|
||||||
// Initialize the gamepad code with our preferences
|
// Initialize the gamepad code with our preferences
|
||||||
// NB: m_InputHandler must be initialize before starting the connection.
|
// 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
|
// Toggle the stats overlay if requested by the user
|
||||||
m_OverlayManager.setOverlayState(Overlay::OverlayDebug, m_Preferences->showPerformanceOverlay);
|
m_OverlayManager.setOverlayState(Overlay::OverlayDebug, m_Preferences->showPerformanceOverlay);
|
||||||
|
refreshControlPanelOverlay();
|
||||||
|
|
||||||
// Switch to async logging mode when we enter the SDL loop
|
// Switch to async logging mode when we enter the SDL loop
|
||||||
StreamUtils::enterAsyncLoggingMode();
|
StreamUtils::enterAsyncLoggingMode();
|
||||||
|
|
@ -2223,9 +2277,9 @@ void Session::exec()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SDL_CODE_HIDE_STATUS_OVERLAY:
|
case SDL_CODE_HIDE_STATUS_OVERLAY:
|
||||||
if ((uint32_t)(uintptr_t)event.user.data1 == m_StatusOverlayGeneration.load(std::memory_order_relaxed) &&
|
if ((uint32_t)(uintptr_t)event.user.data1 == m_StatusOverlayGeneration.load(std::memory_order_relaxed)) {
|
||||||
m_MouseEmulationRefCount == 0) {
|
m_TemporaryStatusOverlayText[0] = '\0';
|
||||||
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
|
refreshControlPanelOverlay();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,7 @@ public:
|
||||||
void setKeyboardMouseInputAllowed(bool allowed);
|
void setKeyboardMouseInputAllowed(bool allowed);
|
||||||
void toggleGamepadInputAllowed();
|
void toggleGamepadInputAllowed();
|
||||||
void toggleKeyboardMouseInputAllowed();
|
void toggleKeyboardMouseInputAllowed();
|
||||||
|
void toggleControlPanelVisibility();
|
||||||
void notifyInputPermissionState();
|
void notifyInputPermissionState();
|
||||||
|
|
||||||
float getAudioVolumeScalar() const
|
float getAudioVolumeScalar() const
|
||||||
|
|
@ -275,6 +276,7 @@ private:
|
||||||
static
|
static
|
||||||
Uint32 statusOverlayTimeoutCallback(Uint32 interval, void* param);
|
Uint32 statusOverlayTimeoutCallback(Uint32 interval, void* param);
|
||||||
|
|
||||||
|
void refreshControlPanelOverlay();
|
||||||
void showTemporaryStatusOverlay(const char* text, Uint32 timeoutMs = 1500);
|
void showTemporaryStatusOverlay(const char* text, Uint32 timeoutMs = 1500);
|
||||||
void applyHostInputPolicy(bool allowKeyboard, bool allowMouse, bool allowGamepad, uint8_t reason);
|
void applyHostInputPolicy(bool allowKeyboard, bool allowMouse, bool allowGamepad, uint8_t reason);
|
||||||
void sendInputPermissionStateToHost(uint8_t reason);
|
void sendInputPermissionStateToHost(uint8_t reason);
|
||||||
|
|
@ -297,6 +299,8 @@ private:
|
||||||
std::atomic<float> m_AudioVolumeScalar;
|
std::atomic<float> m_AudioVolumeScalar;
|
||||||
std::atomic<bool> m_AllowGamepadInput;
|
std::atomic<bool> m_AllowGamepadInput;
|
||||||
std::atomic<bool> m_AllowKeyboardMouseInput;
|
std::atomic<bool> m_AllowKeyboardMouseInput;
|
||||||
|
std::atomic<bool> m_ControlPanelVisible;
|
||||||
|
std::atomic<int> m_ConnectionStatus;
|
||||||
std::atomic<uint32_t> m_StatusOverlayGeneration;
|
std::atomic<uint32_t> m_StatusOverlayGeneration;
|
||||||
Uint32 m_FullScreenFlag;
|
Uint32 m_FullScreenFlag;
|
||||||
QQuickWindow* m_QtWindow;
|
QQuickWindow* m_QtWindow;
|
||||||
|
|
@ -321,6 +325,7 @@ private:
|
||||||
OPUS_MULTISTREAM_CONFIGURATION m_OriginalAudioConfig;
|
OPUS_MULTISTREAM_CONFIGURATION m_OriginalAudioConfig;
|
||||||
int m_AudioSampleCount;
|
int m_AudioSampleCount;
|
||||||
Uint32 m_DropAudioEndTime;
|
Uint32 m_DropAudioEndTime;
|
||||||
|
char m_TemporaryStatusOverlayText[160];
|
||||||
|
|
||||||
Overlay::OverlayManager m_OverlayManager;
|
Overlay::OverlayManager m_OverlayManager;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ OverlayManager::OverlayManager() :
|
||||||
m_Overlays[OverlayType::OverlayDebug].color = {0xD0, 0xD0, 0x00, 0xFF};
|
m_Overlays[OverlayType::OverlayDebug].color = {0xD0, 0xD0, 0x00, 0xFF};
|
||||||
m_Overlays[OverlayType::OverlayDebug].fontSize = 20;
|
m_Overlays[OverlayType::OverlayDebug].fontSize = 20;
|
||||||
|
|
||||||
m_Overlays[OverlayType::OverlayStatusUpdate].color = {0xCC, 0x00, 0x00, 0xFF};
|
m_Overlays[OverlayType::OverlayStatusUpdate].color = {0xF0, 0xF0, 0xF0, 0xFF};
|
||||||
m_Overlays[OverlayType::OverlayStatusUpdate].fontSize = 36;
|
m_Overlays[OverlayType::OverlayStatusUpdate].fontSize = 24;
|
||||||
|
|
||||||
// While TTF will usually not be initialized here, it is valid for that not to
|
// 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
|
// be the case, since Session destruction is deferred and could overlap with
|
||||||
|
|
@ -146,16 +146,45 @@ void OverlayManager::notifyOverlayUpdated(OverlayType type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange the old surface with the new one
|
SDL_Surface* newSurface = nullptr;
|
||||||
SDL_Surface* oldSurface = (SDL_Surface*)SDL_AtomicSetPtr(
|
if (m_Overlays[type].enabled) {
|
||||||
(void**)&m_Overlays[type].surface,
|
|
||||||
m_Overlays[type].enabled ?
|
|
||||||
// The _Wrapped variant is required for line breaks to work
|
// The _Wrapped variant is required for line breaks to work
|
||||||
TTF_RenderText_Blended_Wrapped(m_Overlays[type].font,
|
SDL_Surface* textSurface = TTF_RenderText_Blended_Wrapped(m_Overlays[type].font,
|
||||||
m_Overlays[type].text,
|
m_Overlays[type].text,
|
||||||
m_Overlays[type].color,
|
m_Overlays[type].color,
|
||||||
1024)
|
1024);
|
||||||
: nullptr);
|
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,
|
||||||
|
newSurface);
|
||||||
|
|
||||||
// Notify the renderer
|
// Notify the renderer
|
||||||
m_Renderer->notifyOverlayUpdated(type);
|
m_Renderer->notifyOverlayUpdated(type);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue