obs/Source/OBS.cpp

2191 lines
78 KiB
C++

/********************************************************************************
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
********************************************************************************/
#include "Main.h"
#include <intrin.h>
void SetupSceneCollection(CTSTR scenecollection);
//primarily main window stuff an initialization/destruction code
typedef bool (*LOADPLUGINPROC)();
typedef bool (*LOADPLUGINEXPROC)(UINT);
typedef void (*UNLOADPLUGINPROC)();
typedef CTSTR (*GETPLUGINNAMEPROC)();
ImageSource* STDCALL CreateDesktopSource(XElement *data);
bool STDCALL ConfigureDesktopSource(XElement *data, bool bCreating);
bool STDCALL ConfigureWindowCaptureSource(XElement *data, bool bCreating);
bool STDCALL ConfigureMonitorCaptureSource(XElement *data, bool bCreating);
ImageSource* STDCALL CreateBitmapSource(XElement *data);
bool STDCALL ConfigureBitmapSource(XElement *element, bool bCreating);
ImageSource* STDCALL CreateBitmapTransitionSource(XElement *data);
bool STDCALL ConfigureBitmapTransitionSource(XElement *element, bool bCreating);
ImageSource* STDCALL CreateTextSource(XElement *data);
bool STDCALL ConfigureTextSource(XElement *element, bool bCreating);
ImageSource* STDCALL CreateGlobalSource(XElement *data);
void STDCALL SceneHotkey(DWORD hotkey, UPARAM param, bool bDown);
APIInterface* CreateOBSApiInterface();
#define QuickClearHotkey(hotkeyID) \
if(hotkeyID) \
{ \
API->DeleteHotkey(hotkeyID); \
hotkeyID = NULL; \
}
//----------------------------
WNDPROC listboxProc = NULL;
WNDPROC listviewProc = NULL;
//----------------------------
const float defaultBlendFactor[4] = {1.0f, 1.0f, 1.0f, 1.0f};
//----------------------------
BOOL CALLBACK MonitorInfoEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, List<MonitorInfo> &monitors)
{
monitors << MonitorInfo(hMonitor, lprcMonitor);
return TRUE;
}
const int controlPadding = 3;
const int totalControlAreaWidth = minClientWidth - 10;
const int miscAreaWidth = 290;
const int totalControlAreaHeight = 171;//170;//
const int listAreaWidth = totalControlAreaWidth-miscAreaWidth;
const int controlWidth = miscAreaWidth/2;
const int controlHeight = 22;
const int volControlHeight = 32;
const int volMeterHeight = 10;
const int textControlHeight = 16;
const int listControlWidth = listAreaWidth/2;
Scene* STDCALL CreateNormalScene(XElement *data)
{
return new Scene;
}
BOOL IsWebrootLoaded()
{
BOOL ret = FALSE;
StringList moduleList;
OSGetLoadedModuleList (GetCurrentProcess(), moduleList);
HMODULE msIMG = GetModuleHandle(TEXT("MSIMG32"));
if (msIMG)
{
FARPROC alphaBlend = GetProcAddress(msIMG, "AlphaBlend");
if (alphaBlend)
{
if (!IsBadReadPtr(alphaBlend, 5))
{
BYTE opCode = *(BYTE *)alphaBlend;
if (opCode == 0xE9)
{
if (moduleList.HasValue(TEXT("wrusr.dll")))
ret = TRUE;
}
}
}
}
return ret;
}
// Checks the AppData path is writable and the disk has enough space
VOID CheckPermissionsAndDiskSpace()
{
ULARGE_INTEGER freeSpace;
if (GetDiskFreeSpaceEx (lpAppDataPath, &freeSpace, NULL, NULL))
{
// 1MB ought to be enough for anybody...
if (freeSpace.QuadPart < 1048576)
{
OBSMessageBox(OBSGetMainWindow(), Str("DiskFull"), NULL, MB_ICONERROR);
}
}
HANDLE tempFile;
String testPath;
testPath = lpAppDataPath;
testPath += TEXT("\\.test");
tempFile = CreateFile(testPath.Array(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
if (tempFile == INVALID_HANDLE_VALUE)
{
DWORD err = GetLastError();
if (err == ERROR_ACCESS_DENIED || err == ERROR_FILE_READ_ONLY)
OBSMessageBox(OBSGetMainWindow(), Str("BadAppDataPermissions"), NULL, MB_ICONERROR);
// TODO: extra handling for unknown errors (maybe some av returns weird codes?)
}
else
{
CloseHandle(tempFile);
}
}
//---------------------------------------------------------------------------
VOID OBS::LoadAllPlugins()
{
//-----------------------------------------------------
// load plugins
OSFindData ofd;
HANDLE hFind = OSFindFirstFile(TEXT("plugins/*.dll"), ofd);
if (hFind)
{
do
{
if (!ofd.bDirectory) //why would someone give a directory a .dll extension in the first place? pranksters.
{
String strLocation;
strLocation << TEXT("plugins/") << ofd.fileName;
HMODULE hPlugin = LoadLibrary(strLocation);
if (hPlugin)
{
bool bLoaded = false;
//slightly redundant I suppose seeing as both these things are being added at the same time
LOADPLUGINEXPROC loadPluginEx = (LOADPLUGINEXPROC)GetProcAddress(hPlugin, "LoadPluginEx");
if (loadPluginEx) {
bLoaded = loadPluginEx(OBSGetAPIVersion());
}
else {
LOADPLUGINPROC loadPlugin = (LOADPLUGINPROC)GetProcAddress(hPlugin, "LoadPlugin");
bLoaded = loadPlugin && loadPlugin();
}
if (bLoaded) {
PluginInfo *pluginInfo = plugins.CreateNew();
pluginInfo->hModule = hPlugin;
pluginInfo->strFile = ofd.fileName;
/* get event callbacks for the plugin */
pluginInfo->startStreamCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnStartStream");
pluginInfo->stopStreamCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnStopStream");
pluginInfo->startStreamingCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnStartStreaming");
pluginInfo->stopStreamingCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnStopStreaming");
pluginInfo->startRecordingCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnStartRecording");
pluginInfo->stopRecordingCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnStopRecording");
pluginInfo->statusCallback = (OBS_STATUS_CALLBACK)GetProcAddress(hPlugin, "OnOBSStatus");
pluginInfo->streamStatusCallback = (OBS_STREAM_STATUS_CALLBACK)GetProcAddress(hPlugin, "OnStreamStatus");
pluginInfo->sceneSwitchCallback = (OBS_SCENE_SWITCH_CALLBACK)GetProcAddress(hPlugin, "OnSceneSwitch");
pluginInfo->sceneCollectionSwitchCallback = (OBS_SCENE_SWITCH_CALLBACK)GetProcAddress(hPlugin, "OnSceneCollectionSwitch");
pluginInfo->scenesChangedCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnScenesChanged");
pluginInfo->sceneCollectionsChangedCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnSceneCollectionsChanged");
pluginInfo->sourceOrderChangedCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnSourceOrderChanged");
pluginInfo->sourceChangedCallback = (OBS_SOURCE_CHANGED_CALLBACK)GetProcAddress(hPlugin, "OnSourceChanged");
pluginInfo->sourcesAddedOrRemovedCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnSourcesAddedOrRemoved");
pluginInfo->micVolumeChangeCallback = (OBS_VOLUME_CHANGED_CALLBACK)GetProcAddress(hPlugin, "OnMicVolumeChanged");
pluginInfo->desktopVolumeChangeCallback = (OBS_VOLUME_CHANGED_CALLBACK)GetProcAddress(hPlugin, "OnDesktopVolumeChanged");
pluginInfo->logUpdateCallback = (OBS_LOG_UPDATE_CALLBACK)GetProcAddress(hPlugin, "OnLogUpdate");
pluginInfo->startRecordingReplayBufferCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnStartRecordingReplayBuffer");
pluginInfo->stopRecordingReplayBufferCallback = (OBS_CALLBACK)GetProcAddress(hPlugin, "OnStopRecordingReplayBuffer");
pluginInfo->replayBufferSavedCallback = (OBS_REPLAY_BUFFER_SAVED_CALLBACK)GetProcAddress(hPlugin, "OnReplayBufferSaved");
//GETPLUGINNAMEPROC getName = (GETPLUGINNAMEPROC)GetProcAddress(hPlugin, "GetPluginName");
//CTSTR lpName = (getName) ? getName() : TEXT("<unknown>");
//FIXME: TODO: log this somewhere else, it comes before the OBS version info and looks weird.
//Log(TEXT("Loaded plugin '%s', %s"), lpName, strLocation);
}
else
{
Log(TEXT("Failed to initialize plugin %s"), strLocation.Array());
FreeLibrary(hPlugin);
}
}
else
{
DWORD err = GetLastError();
Log(TEXT("Failed to load plugin %s, %d"), strLocation.Array(), err);
#ifndef _DEBUG
if (err == 193)
{
#ifdef _M_X64
String message = FormattedString(Str("Plugins.InvalidVersion"), ofd.fileName, 32);
#else
String message = FormattedString(Str("Plugins.InvalidVersion"), ofd.fileName, 64);
#endif
OBSMessageBox(hwndMain, message.Array(), NULL, MB_ICONEXCLAMATION);
}
#endif
}
}
} while (OSFindNextFile(hFind, ofd));
OSFindClose(hFind);
}
}
OBS::OBS()
{
App = this;
performTransition = true; //Default to true and don't set the conf.
//We don't want to let plugins disable standard behavior permanently.
hSceneMutex = OSCreateMutex();
hAuxAudioMutex = OSCreateMutex();
hVideoEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
monitors.Clear();
EnumDisplayMonitors(NULL, NULL, (MONITORENUMPROC)MonitorInfoEnumProc, (LPARAM)&monitors);
INITCOMMONCONTROLSEX ecce;
ecce.dwSize = sizeof(ecce);
ecce.dwICC = ICC_STANDARD_CLASSES;
if(!InitCommonControlsEx(&ecce))
CrashError(TEXT("Could not initalize common shell controls"));
InitHotkeyExControl(hinstMain);
InitColorControl(hinstMain);
InitVolumeControl(hinstMain);
InitVolumeMeter(hinstMain);
// still need this here for API
strLanguage = GlobalConfig->GetString(TEXT("General"), TEXT("Language"), TEXT("en"));
//-----------------------------------------------------
// load classes
RegisterSceneClass(TEXT("Scene"), Str("Scene"), (OBSCREATEPROC)CreateNormalScene, NULL, false);
RegisterImageSourceClass(TEXT("DesktopImageSource"), Str("Sources.SoftwareCaptureSource"), (OBSCREATEPROC)CreateDesktopSource, (OBSCONFIGPROC)ConfigureDesktopSource, true);
RegisterImageSourceClass(TEXT("WindowCaptureSource"), Str("Sources.SoftwareCaptureSource.WindowCapture"), (OBSCREATEPROC)CreateDesktopSource, (OBSCONFIGPROC)ConfigureWindowCaptureSource, false);
RegisterImageSourceClass(TEXT("MonitorCaptureSource"), Str("Sources.SoftwareCaptureSource.MonitorCapture"), (OBSCREATEPROC)CreateDesktopSource, (OBSCONFIGPROC)ConfigureMonitorCaptureSource, false);
RegisterImageSourceClass(TEXT("BitmapImageSource"), Str("Sources.BitmapSource"), (OBSCREATEPROC)CreateBitmapSource, (OBSCONFIGPROC)ConfigureBitmapSource, false);
RegisterImageSourceClass(TEXT("BitmapTransitionSource"), Str("Sources.TransitionSource"), (OBSCREATEPROC)CreateBitmapTransitionSource, (OBSCONFIGPROC)ConfigureBitmapTransitionSource, false);
RegisterImageSourceClass(TEXT("GlobalSource"), Str("Sources.GlobalSource"), (OBSCREATEPROC)CreateGlobalSource, (OBSCONFIGPROC)OBS::ConfigGlobalSource, false);
RegisterImageSourceClass(TEXT("TextSource"), Str("Sources.TextSource"), (OBSCREATEPROC)CreateTextSource, (OBSCONFIGPROC)ConfigureTextSource, false);
//-----------------------------------------------------
// render frame class
WNDCLASS wc;
zero(&wc, sizeof(wc));
wc.hInstance = hinstMain;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = OBS_RENDERFRAME_CLASS;
wc.lpfnWndProc = (WNDPROC)OBS::RenderFrameProc;
wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
if(!RegisterClass(&wc))
CrashError(TEXT("Could not register render frame class"));
//-----------------------------------------------------
// projector frame class
wc.lpszClassName = OBS_PROJECTORFRAME_CLASS;
wc.lpfnWndProc = (WNDPROC)OBS::ProjectorFrameProc;
wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
if(!RegisterClass(&wc))
CrashError(TEXT("Could not register projector frame class"));
//-----------------------------------------------------
// log window class
wc.lpszClassName = OBS_LOGWINDOW_CLASS;
wc.lpfnWndProc = (WNDPROC)OBS::LogWindowProc;
wc.hIcon = LoadIcon(hinstMain, MAKEINTRESOURCE(IDI_ICON1));
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
if(!RegisterClass(&wc))
CrashError(TEXT("Could not register main window class"));
//-----------------------------------------------------
// main window class
wc.lpszClassName = OBS_WINDOW_CLASS;
wc.lpfnWndProc = (WNDPROC)OBSProc;
wc.hIcon = LoadIcon(hinstMain, MAKEINTRESOURCE(IDI_ICON1));
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAINMENU);
if(!RegisterClass(&wc))
CrashError(TEXT("Could not register main window class"));
//-----------------------------------------------------
// create main window
int fullscreenX = GetSystemMetrics(SM_CXFULLSCREEN);
int fullscreenY = GetSystemMetrics(SM_CYFULLSCREEN);
borderXSize = borderYSize = 0;
borderXSize += GetSystemMetrics(SM_CXSIZEFRAME)*2;
borderYSize += GetSystemMetrics(SM_CYSIZEFRAME)*2;
borderYSize += GetSystemMetrics(SM_CYMENU);
borderYSize += GetSystemMetrics(SM_CYCAPTION);
clientWidth = GlobalConfig->GetInt(TEXT("General"), TEXT("Width"), defaultClientWidth);
clientHeight = GlobalConfig->GetInt(TEXT("General"), TEXT("Height"), defaultClientHeight);
if(clientWidth < minClientWidth)
clientWidth = minClientWidth;
if(clientHeight < minClientHeight)
clientHeight = minClientHeight;
int maxCX = fullscreenX-borderXSize;
int maxCY = fullscreenY-borderYSize;
if(clientWidth > maxCX)
clientWidth = maxCX;
if(clientHeight > maxCY)
clientHeight = maxCY;
int cx = clientWidth + borderXSize;
int cy = clientHeight + borderYSize;
int x = (fullscreenX/2)-(cx/2);
int y = (fullscreenY/2)-(cy/2);
int posX = GlobalConfig->GetInt(TEXT("General"), TEXT("PosX"), -9999);
int posY = GlobalConfig->GetInt(TEXT("General"), TEXT("PosY"), -9999);
bool bInsideMonitors = false;
for(UINT i=0; i<monitors.Num(); i++)
{
if( posX >= monitors[i].rect.left && posX < monitors[i].rect.right &&
posY >= monitors[i].rect.top && posY < monitors[i].rect.bottom )
{
bInsideMonitors = true;
break;
}
}
if(bInsideMonitors)
{
x = posX;
y = posY;
}
bPanelVisibleWindowed = GlobalConfig->GetInt(TEXT("General"), TEXT("PanelVisibleWindowed"), 1) != 0;
bPanelVisibleFullscreen = GlobalConfig->GetInt(TEXT("General"), TEXT("PanelVisibleFullscreen"), 0) != 0;
bPanelVisible = bPanelVisibleWindowed; // Assuming OBS always starts windowed
bPanelVisibleProcessed = false; // Force immediate process
bFullscreenMode = false;
hwndMain = CreateWindowEx(WS_EX_CONTROLPARENT|WS_EX_WINDOWEDGE|(LocaleIsRTL() ? WS_EX_LAYOUTRTL : 0), OBS_WINDOW_CLASS, GetApplicationName(),
WS_OVERLAPPED | WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU | WS_CLIPCHILDREN,
x, y, cx, cy, NULL, NULL, hinstMain, NULL);
if(!hwndMain)
CrashError(TEXT("Could not create main window"));
hmenuMain = GetMenu(hwndMain);
LocalizeMenu(hmenuMain);
//-----------------------------------------------------
// render frame
hwndRenderFrame = CreateWindow(OBS_RENDERFRAME_CLASS, NULL,
WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
0, 0, 0, 0,
hwndMain, NULL, hinstMain, NULL);
if(!hwndRenderFrame)
CrashError(TEXT("Could not create render frame"));
//-----------------------------------------------------
// projector window
hwndProjector = CreateWindow(OBS_PROJECTORFRAME_CLASS,
L"OBS Projector Window",
WS_POPUP, 0, 0, 0, 0,
NULL, NULL, hinstMain, NULL);
//-----------------------------------------------------
// log window
x = (fullscreenX/2)-(600/2);
y = (fullscreenY/2)-(500/2);
int logPosX = GlobalConfig->GetInt(TEXT("General"), TEXT("LogPosX"), -9999);
int logPosY = GlobalConfig->GetInt(TEXT("General"), TEXT("LogPosY"), -9999);
int logSizeX = GlobalConfig->GetInt(TEXT("General"), TEXT("LogSizeX"), 600);
int logSizeY = GlobalConfig->GetInt(TEXT("General"), TEXT("LogSizeY"), 500);
bInsideMonitors = false;
for(UINT i=0; i<monitors.Num(); i++)
{
if( logPosX >= monitors[i].rect.left && logPosX < monitors[i].rect.right &&
logPosY >= monitors[i].rect.top && logPosY < monitors[i].rect.bottom )
{
bInsideMonitors = true;
break;
}
}
if(bInsideMonitors)
{
x = logPosX;
y = logPosY;
}
else
{
logSizeX = 600;
logSizeY = 500;
}
hwndLogWindow = CreateWindowEx(LocaleIsRTL() ? WS_EX_LAYOUTRTL | WS_EX_NOINHERITLAYOUT : 0, OBS_LOGWINDOW_CLASS, L"LogWindow", WS_OVERLAPPEDWINDOW, x, y, logSizeX, logSizeY, NULL, NULL, hinstMain, NULL);
LocalizeWindow(hwndLogWindow);
RECT client;
GetClientRect(hwndLogWindow, &client);
hwndLog = CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"",
ES_MULTILINE | WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_VSCROLL | WS_HSCROLL | ES_READONLY | ES_NOHIDESEL,
client.left, client.top, client.right, client.bottom, hwndLogWindow, (HMENU)ID_LOG_WINDOW, 0, 0);
SendMessage(hwndLog, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
SendMessage(hwndLog, EM_SETLIMITTEXT, 0, 0);
ShowWindow(hwndLog, SW_SHOW);
ResetLogUpdateCallback([] { PostMessage(hwndLogWindow, WM_COMMAND, MAKEWPARAM(ID_LOG_WINDOW, 0), 0); });
//-----------------------------------------------------
// render frame text
hwndRenderMessage = CreateWindow(TEXT("STATIC"), Str("MainWindow.BeginMessage"),
WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS|SS_CENTER,
0, 0, 0, 0, hwndRenderFrame, NULL, hinstMain, NULL);
SendMessage(hwndRenderMessage, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// scenes listbox
HWND hwndTemp;
hwndTemp = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("LISTBOX"), NULL,
WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|LBS_HASSTRINGS|WS_VSCROLL|LBS_NOTIFY|LBS_NOINTEGRALHEIGHT|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_SCENES, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
listboxProc = (WNDPROC)GetWindowLongPtr(hwndTemp, GWLP_WNDPROC);
SetWindowLongPtr(hwndTemp, GWLP_WNDPROC, (LONG_PTR)OBS::ListboxHook);
//-----------------------------------------------------
// elements listview
hwndTemp = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL,
WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|WS_VSCROLL|WS_CLIPSIBLINGS|LVS_REPORT|LVS_NOCOLUMNHEADER|
LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_NOLABELWRAP,
0, 0, 0, 0, hwndMain, (HMENU)ID_SOURCES, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
ListView_SetExtendedListViewStyle(hwndTemp, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
//add single column needed for report style
LVCOLUMN column;
column.mask = LVCF_TEXT;
column.fmt = LVCFMT_FIXED_WIDTH;
column.cx = 0;
column.pszText = TEXT("");
ListView_InsertColumn(hwndTemp, 0, &column);
ListView_InsertColumn(hwndTemp, 1, &column);
listviewProc = (WNDPROC)GetWindowLongPtr(hwndTemp, GWLP_WNDPROC);
SetWindowLongPtr(hwndTemp, GWLP_WNDPROC, (LONG_PTR)OBS::ListboxHook);
HWND hwndSources = hwndTemp;
//-----------------------------------------------------
// status control
hwndTemp = CreateWindowEx(0, STATUSCLASSNAME, NULL,
WS_CHILD|WS_VISIBLE|SBARS_SIZEGRIP|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_STATUS, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// mic volume control
hwndTemp = CreateWindow(VOLUME_CONTROL_CLASS, NULL,
WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_MICVOLUME, 0, 0);
SetVolumeControlIcons(hwndTemp, GetIcon(hinstMain, IDI_SOUND_MIC), GetIcon(hinstMain, IDI_SOUND_MIC_MUTED));
//-----------------------------------------------------
// mic volume meter
hwndTemp = CreateWindow(VOLUME_METER_CLASS, NULL,
WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_MICVOLUMEMETER, 0, 0);
//-----------------------------------------------------
// desktop volume meter
hwndTemp = CreateWindow(VOLUME_METER_CLASS, NULL,
WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_DESKTOPVOLUMEMETER, 0, 0);
//-----------------------------------------------------
// desktop volume control
hwndTemp = CreateWindow(VOLUME_CONTROL_CLASS, NULL,
WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_DESKTOPVOLUME, 0, 0);
SetVolumeControlIcons(hwndTemp, GetIcon(hinstMain, IDI_SOUND_DESKTOP), GetIcon(hinstMain, IDI_SOUND_DESKTOP_MUTED));
//-----------------------------------------------------
// start/stop recording button
hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.StartRecording"),
WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_SPLITBUTTON|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_TOGGLERECORDING, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// settings button
hwndTemp = CreateWindow(TEXT("BUTTON"), Str("Settings"),
WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_SETTINGS, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// start/stop stream button
hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.StartStream"),
WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_STARTSTOP, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// edit scene button
hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.SceneEditor"),
WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_AUTOCHECKBOX|BS_PUSHLIKE|WS_DISABLED|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_SCENEEDITOR, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// global sources button
hwndTemp = CreateWindow(TEXT("BUTTON"), Str("GlobalSources"),
WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_GLOBALSOURCES, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// test stream button
hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.TestStream"),
WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_TESTSTREAM, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// plugins button
hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.Plugins"),
WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_PLUGINS, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// dashboard button
hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.Dashboard"),
WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_DASHBOARD, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// exit button
hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.Exit"),
WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_EXIT, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// scenes text
hwndTemp = CreateWindow(TEXT("STATIC"), Str("MainWindow.Scenes"),
WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_SCENES_TEXT, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// scenes text
hwndTemp = CreateWindow(TEXT("STATIC"), Str("MainWindow.Sources"),
WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS,
0, 0, 0, 0, hwndMain, (HMENU)ID_SOURCES_TEXT, 0, 0);
SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
//-----------------------------------------------------
// notification area
bNotificationAreaIcon = false;
wmExplorerRestarted = RegisterWindowMessage(TEXT("TaskbarCreated"));
if (AppConfig->GetInt(TEXT("General"), TEXT("ShowNotificationAreaIcon"), 0) != 0)
{
ShowNotificationAreaIcon();
}
//-----------------------------------------------------
// populate scenes
hwndTemp = GetDlgItem(hwndMain, ID_SCENES);
String collection = GetCurrentSceneCollection();
if (!OSFileExists(String() << lpAppDataPath << L"\\sceneCollection\\" << collection << L".xconfig"))
collection.Clear();
if (collection.IsEmpty())
{
OSFindData ofd;
HANDLE hFind = OSFindFirstFile(String() << lpAppDataPath << L"\\sceneCollection\\*.xconfig", ofd);
if (hFind)
{
do
{
if (!ofd.bDirectory)
{
collection = GetPathWithoutExtension(ofd.fileName);
break;
}
} while (OSFindNextFile(hFind, ofd));
OSFindClose(hFind);
}
if (collection.IsEmpty())
{
CopyFile(String() << lpAppDataPath << L"\\scenes.xconfig", String() << lpAppDataPath << L"\\sceneCollection\\scenes.xconfig", true);
collection = L"scenes";
GlobalConfig->SetString(L"General", L"SceneCollection", collection);
}
}
String strScenesConfig;
strScenesConfig = FormattedString(L"%s\\sceneCollection\\%s.xconfig", lpAppDataPath, collection.Array());
if(!scenesConfig.Open(strScenesConfig))
CrashError(TEXT("Could not open '%s'"), strScenesConfig.Array());
XElement *scenes = scenesConfig.GetElement(TEXT("scenes"));
if(!scenes)
scenes = scenesConfig.CreateElement(TEXT("scenes"));
UINT numScenes = scenes->NumElements();
if(!numScenes)
{
XElement *scene = scenes->CreateElement(Str("Scene"));
scene->SetString(TEXT("class"), TEXT("Scene"));
numScenes++;
}
for(UINT i=0; i<numScenes; i++)
{
XElement *scene = scenes->GetElementByID(i);
//scene->SetString(TEXT("class"), TEXT("Scene"));
SendMessage(hwndTemp, LB_ADDSTRING, 0, (LPARAM)scene->GetName());
}
//-----------------------------------------------------
// populate sources
if(numScenes)
{
String strScene = AppConfig->GetString(TEXT("General"), TEXT("CurrentScene"));
int id = (int)SendMessage(hwndTemp, LB_FINDSTRINGEXACT, -1, (LPARAM)strScene.Array());
if(id == LB_ERR)
id = 0;
SendMessage(hwndTemp, LB_SETCURSEL, (WPARAM)id, 0);
SendMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_SCENES, LBN_SELCHANGE), (LPARAM)GetDlgItem(hwndMain, ID_SCENES));
}
//-----------------------------------------------------
hHotkeyMutex = OSCreateMutex();
hInfoMutex = OSCreateMutex();
hStartupShutdownMutex = OSCreateMutex();
//-----------------------------------------------------
API = CreateOBSApiInterface();
bDragResize = false;
if(GlobalConfig->GetInt(TEXT("General"), TEXT("Maximized")))
{ // Window was maximized last session
SendMessage(hwndMain, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
}
//-----------------------------------------------------
for(UINT i=0; i<numScenes; i++)
{
XElement *scene = scenes->GetElementByID(i);
DWORD hotkey = scene->GetInt(TEXT("hotkey"));
if(hotkey)
{
SceneHotkeyInfo hotkeyInfo;
hotkeyInfo.hotkey = hotkey;
hotkeyInfo.scene = scene;
hotkeyInfo.hotkeyID = API->CreateHotkey(hotkey, SceneHotkey, 0);
if(hotkeyInfo.hotkeyID)
sceneHotkeys << hotkeyInfo;
}
}
//-----------------------------------------------------
// Add built-in settings panes
currentSettingsPane = NULL;
AddBuiltInSettingsPanes();
AddEncoderSettingsPanes();
//-----------------------------------------------------
ReloadIniSettings();
ResetProfileMenu();
ResetSceneCollectionMenu();
ResetLogUploadMenu();
//-----------------------------------------------------
bAutoReconnect = AppConfig->GetInt(TEXT("Publish"), TEXT("AutoReconnect"), 1) != 0;
reconnectTimeout = AppConfig->GetInt(TEXT("Publish"), TEXT("AutoReconnectTimeout"), 10);
hHotkeyThread = OSCreateThread((XTHREAD)HotkeyThread, NULL);
#ifndef OBS_DISABLE_AUTOUPDATE
ULARGE_INTEGER lastUpdateTime;
ULARGE_INTEGER currentTime;
FILETIME systemTime;
lastUpdateTime.QuadPart = GlobalConfig->GetInt(TEXT("General"), OBS_CONFIG_UPDATE_KEY, 0);
GetSystemTimeAsFileTime(&systemTime);
currentTime.LowPart = systemTime.dwLowDateTime;
currentTime.HighPart = systemTime.dwHighDateTime;
//OBS doesn't support 64 bit ints in the config file, so we have to normalize it to a 32 bit int
currentTime.QuadPart /= 10000000;
currentTime.QuadPart -= 13000000000;
if (currentTime.QuadPart - lastUpdateTime.QuadPart >= 3600)
{
GlobalConfig->SetInt(TEXT("General"), OBS_CONFIG_UPDATE_KEY, (int)currentTime.QuadPart);
OSCloseThread(OSCreateThread((XTHREAD)CheckUpdateThread, (LPVOID)0));
}
#endif
// TODO: Should these be stored in the config file?
bRenderViewEnabled = GlobalConfig->GetInt(TEXT("General"), TEXT("PreviewEnabled"), 1) != 0;
bForceRenderViewErase = false;
renderFrameIn1To1Mode = false;
if(GlobalConfig->GetInt(TEXT("General"), TEXT("ShowWebrootWarning"), TRUE) && IsWebrootLoaded())
OBSMessageBox(hwndMain, TEXT("Webroot Secureanywhere appears to be active. This product will cause problems with OBS as the security features block OBS from accessing Windows GDI functions. It is highly recommended that you disable Secureanywhere and restart OBS.\r\n\r\nOf course you can always just ignore this message if you want, but it may prevent you from being able to stream certain things. Please do not report any bugs you may encounter if you leave Secureanywhere enabled."), TEXT("Just a slight issue you might want to be aware of"), MB_OK);
CheckPermissionsAndDiskSpace();
ConfigureStreamButtons();
ResizeWindow(false);
ShowWindow(hwndMain, SW_SHOW);
renderFrameIn1To1Mode = !!GlobalConfig->GetInt(L"General", L"1to1Preview", false);
App->bAlwaysOnTop = !!GlobalConfig->GetInt(L"General", L"AlwaysOnTop");
CheckMenuItem(GetMenu(hwndMain), ID_ALWAYSONTOP, (App->bAlwaysOnTop) ? MF_CHECKED : MF_UNCHECKED);
SetWindowPos(hwndMain, (App->bAlwaysOnTop) ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
// make sure sources listview column widths are as expected after obs window is shown
ListView_SetColumnWidth(hwndSources,0,LVSCW_AUTOSIZE_USEHEADER);
ListView_SetColumnWidth(hwndSources,1,LVSCW_AUTOSIZE_USEHEADER);
if (GlobalConfig->GetInt(L"General", L"ShowLogWindowOnLaunch") != 0)
PostMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_SHOWLOG, 0), 0);
if (bStreamOnStart)
PostMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_STARTSTOP, 0), NULL);
}
OBS::~OBS()
{
Stop(true, true);
bShuttingDown = true;
OSTerminateThread(hHotkeyThread, 2500);
ClosePendingStreams();
for(UINT i=0; i<plugins.Num(); i++)
{
PluginInfo &pluginInfo = plugins[i];
UNLOADPLUGINPROC unloadPlugin = (UNLOADPLUGINPROC)GetProcAddress(pluginInfo.hModule, "UnloadPlugin");
if(unloadPlugin)
unloadPlugin();
FreeLibrary(pluginInfo.hModule);
pluginInfo.strFile.Clear();
ZeroMemory(&pluginInfo, sizeof(pluginInfo));
}
if (AppConfig->GetInt(TEXT("General"), TEXT("ShowNotificationAreaIcon"), 0) != 0)
{
App->HideNotificationAreaIcon();
}
if (logDirectoryMonitor)
OSMonitorDirectoryCallbackStop(logDirectoryMonitor);
//DestroyWindow(hwndMain);
// Remember window state for next launch
WINDOWPLACEMENT placement;
placement.length = sizeof(placement);
GetWindowPlacement(hwndMain, &placement);
RECT rect = { 0 };
GetWindowRect(hwndMain, &rect);
GlobalConfig->SetInt(TEXT("General"), TEXT("PosX"), rect.left);
GlobalConfig->SetInt(TEXT("General"), TEXT("PosY"), rect.top);
GlobalConfig->SetInt(TEXT("General"), TEXT("Width"),
rect.right - rect.left -
GetSystemMetrics(SM_CXSIZEFRAME) * 2);
GlobalConfig->SetInt(TEXT("General"), TEXT("Height"),
rect.bottom - rect.top -
GetSystemMetrics(SM_CYSIZEFRAME) * 2 - GetSystemMetrics(SM_CYCAPTION) - GetSystemMetrics(SM_CYMENU));
GlobalConfig->SetInt(TEXT("General"), TEXT("Maximized"), placement.showCmd == SW_SHOWMAXIMIZED ? 1 : 0);
GetWindowRect(hwndLogWindow, &rect);
GlobalConfig->SetInt(TEXT("General"), TEXT("LogPosX"), rect.left);
GlobalConfig->SetInt(TEXT("General"), TEXT("LogPosY"), rect.top);
GlobalConfig->SetInt(TEXT("General"), TEXT("LogSizeX"), rect.right - rect.left);
GlobalConfig->SetInt(TEXT("General"), TEXT("LogSizeY"), rect.bottom - rect.top);
GlobalConfig->SetInt(L"General", L"1to1Preview", renderFrameIn1To1Mode);
GlobalConfig->SetInt(L"General", L"AlwaysOnTop", App->bAlwaysOnTop);
// Save control panel visibility
GlobalConfig->SetInt(TEXT("General"), TEXT("PanelVisibleWindowed"), bPanelVisibleWindowed ? 1 : 0);
GlobalConfig->SetInt(TEXT("General"), TEXT("PanelVisibleFullscreen"), bPanelVisibleFullscreen ? 1 : 0);
// Save preview enabled/disabled state
GlobalConfig->SetInt(TEXT("General"), TEXT("PreviewEnabled"), bRenderViewEnabled ? 1 : 0);
scenesConfig.SaveTo(String() << lpAppDataPath << "\\scenes.xconfig");
scenesConfig.Close(true);
for(UINT i=0; i<Icons.Num(); i++)
DeleteObject(Icons[i].hIcon);
Icons.Clear();
for(UINT i=0; i<Fonts.Num(); i++)
{
DeleteObject(Fonts[i].hFont);
Fonts[i].strFontFace.Clear();
}
Fonts.Clear();
for(UINT i=0; i<sceneClasses.Num(); i++)
sceneClasses[i].FreeData();
for(UINT i=0; i<imageSourceClasses.Num(); i++)
imageSourceClasses[i].FreeData();
if (hVideoEvent)
CloseHandle(hVideoEvent);
if(hSceneMutex)
OSCloseMutex(hSceneMutex);
if(hAuxAudioMutex)
OSCloseMutex(hAuxAudioMutex);
delete API;
API = NULL;
for (UINT i=0; i<settingsPanes.Num(); i++)
delete settingsPanes[i];
if(hInfoMutex)
OSCloseMutex(hInfoMutex);
if(hHotkeyMutex)
OSCloseMutex(hHotkeyMutex);
App = NULL;
}
/**
* Controls which message should be displayed in the middle of the main window.
*/
void OBS::UpdateRenderViewMessage()
{
if(bRunning)
{
if(bRenderViewEnabled)
{
// Message should be invisible
ShowWindow(hwndRenderMessage, SW_HIDE);
}
else
{
ShowWindow(hwndRenderMessage, SW_SHOW);
SetWindowText(hwndRenderMessage, Str("MainWindow.PreviewDisabled"));
}
}
else
{
ShowWindow(hwndRenderMessage, SW_SHOW);
SetWindowText(hwndRenderMessage, Str("MainWindow.BeginMessage"));
}
}
void OBS::ResizeRenderFrame(bool bRedrawRenderFrame)
{
// Get output steam size and aspect ratio
int curCX, curCY;
float mainAspect;
if(bRunning)
{
curCX = outputCX;
curCY = outputCY;
mainAspect = float(curCX)/float(curCY);
}
else
{
// Default to the monitor's resolution if the base size is undefined
int monitorID = AppConfig->GetInt(TEXT("Video"), TEXT("Monitor"));
if(monitorID >= (int)monitors.Num())
monitorID = 0;
RECT &screenRect = monitors[monitorID].rect;
int defCX = screenRect.right - screenRect.left;
int defCY = screenRect.bottom - screenRect.top;
// Calculate output size using the same algorithm that's in OBS::Start()
float scale = AppConfig->GetFloat(TEXT("Video"), TEXT("Downscale"), 1.0f);
curCX = AppConfig->GetInt(TEXT("Video"), TEXT("BaseWidth"), defCX);
curCY = AppConfig->GetInt(TEXT("Video"), TEXT("BaseHeight"), defCY);
curCX = MIN(MAX(curCX, 128), 4096);
curCY = MIN(MAX(curCY, 128), 4096);
curCX = UINT(double(curCX) / double(scale));
curCY = UINT(double(curCY) / double(scale));
curCX = curCX & 0xFFFFFFFC; // Align width to 128bit for fast SSE YUV4:2:0 conversion
curCY = curCY & 0xFFFFFFFE;
mainAspect = float(curCX)/float(curCY);
}
// Get area to render in
int x, y;
UINT controlWidth = clientWidth;
UINT controlHeight = clientHeight;
if(bPanelVisible)
controlHeight -= totalControlAreaHeight + controlPadding;
UINT newRenderFrameWidth, newRenderFrameHeight;
if(renderFrameIn1To1Mode)
{
newRenderFrameWidth = (UINT)curCX;
newRenderFrameHeight = (UINT)curCY;
x = (int)controlWidth / 2 - curCX / 2;
y = (int)controlHeight / 2 - curCY / 2;
}
else
{ // Scale to fit
Vect2 renderSize = Vect2(float(controlWidth), float(controlHeight));
float renderAspect = renderSize.x/renderSize.y;
if(renderAspect > mainAspect)
{
renderSize.x = renderSize.y*mainAspect;
x = int((float(controlWidth)-renderSize.x)*0.5f);
y = 0;
}
else
{
renderSize.y = renderSize.x/mainAspect;
x = 0;
y = int((float(controlHeight)-renderSize.y)*0.5f);
}
// Round and ensure even size
newRenderFrameWidth = int(renderSize.x+0.5f)&0xFFFFFFFE;
newRenderFrameHeight = int(renderSize.y+0.5f)&0xFFFFFFFE;
}
// Fill the majority of the window with the 3D scene. We'll render everything in DirectX
SetWindowPos(hwndRenderFrame, NULL, 0, 0, controlWidth, controlHeight, SWP_NOOWNERZORDER);
//----------------------------------------------
renderFrameX = x;
renderFrameY = y;
renderFrameWidth = newRenderFrameWidth;
renderFrameHeight = newRenderFrameHeight;
renderFrameCtrlWidth = controlWidth;
renderFrameCtrlHeight = controlHeight;
if(!bRunning)
{
oldRenderFrameCtrlWidth = renderFrameCtrlWidth;
oldRenderFrameCtrlHeight = renderFrameCtrlHeight;
InvalidateRect(hwndRenderMessage, NULL, true); // Repaint text
}
else if(bRunning && bRedrawRenderFrame)
{
oldRenderFrameCtrlWidth = renderFrameCtrlWidth;
oldRenderFrameCtrlHeight = renderFrameCtrlHeight;
bResizeRenderView = true;
}
}
void OBS::SetFullscreenMode(bool fullscreen)
{
if(App->bFullscreenMode == fullscreen)
return; // Nothing to do
App->bFullscreenMode = fullscreen;
if(fullscreen)
{
// Remember current window placement
fullscreenPrevPlacement.length = sizeof(fullscreenPrevPlacement);
GetWindowPlacement(hwndMain, &fullscreenPrevPlacement);
// Update panel visibility if required
if(bPanelVisible != bPanelVisibleFullscreen) {
bPanelVisible = bPanelVisibleFullscreen;
bPanelVisibleProcessed = false;
}
// Hide borders
LONG style = GetWindowLong(hwndMain, GWL_STYLE);
SetWindowLong(hwndMain, GWL_STYLE, style & ~(WS_CAPTION | WS_THICKFRAME));
// Hide menu and status bar
SetMenu(hwndMain, NULL);
// Fill entire screen
HMONITOR monitorForWidow = MonitorFromWindow(hwndMain, MONITOR_DEFAULTTONEAREST);
MONITORINFO monitorInfo;
monitorInfo.cbSize = sizeof(monitorInfo);
GetMonitorInfo(monitorForWidow, &monitorInfo);
int x = monitorInfo.rcMonitor.left;
int y = monitorInfo.rcMonitor.top;
int cx = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left;
int cy = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top;
SetWindowPos(hwndMain, HWND_TOPMOST, x, y, cx, cy, SWP_FRAMECHANGED);
// Update menu checkboxes
CheckMenuItem(hmenuMain, ID_FULLSCREENMODE, MF_CHECKED);
}
else
{
// Show borders
LONG style = GetWindowLong(hwndMain, GWL_STYLE);
SetWindowLong(hwndMain, GWL_STYLE, style | WS_CAPTION | WS_THICKFRAME);
// Show menu and status bar
SetMenu(hwndMain, hmenuMain);
// Restore control panel visible state if required
if(bPanelVisible != bPanelVisibleWindowed) {
bPanelVisible = bPanelVisibleWindowed;
bPanelVisibleProcessed = false;
}
// Restore original window size
SetWindowPlacement(hwndMain, &fullscreenPrevPlacement);
// Update menu checkboxes
CheckMenuItem(hmenuMain, ID_FULLSCREENMODE, MF_UNCHECKED);
// Disable always-on-top if needed
SetWindowPos(hwndMain, (App->bAlwaysOnTop)?HWND_TOPMOST:HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}
// Workaround: If the window is maximized, resize isn't called, so do it manually
// Also, when going into fullscreen, this can prevent pixelation
ResizeWindow(true);
}
/**
* Show or hide the control panel.
*/
void OBS::ProcessPanelVisible(bool fromResizeWindow)
{
if(bPanelVisibleProcessed)
return; // Already done
const int visible = bPanelVisible ? SW_SHOW : SW_HIDE;
ShowWindow(GetDlgItem(hwndMain, ID_MICVOLUME), visible);
ShowWindow(GetDlgItem(hwndMain, ID_DESKTOPVOLUME), visible);
ShowWindow(GetDlgItem(hwndMain, ID_MICVOLUMEMETER), visible);
ShowWindow(GetDlgItem(hwndMain, ID_DESKTOPVOLUMEMETER), visible);
ShowWindow(GetDlgItem(hwndMain, ID_SETTINGS), visible);
ShowWindow(GetDlgItem(hwndMain, ID_TOGGLERECORDING), visible);
ShowWindow(GetDlgItem(hwndMain, ID_STARTSTOP), visible);
ShowWindow(GetDlgItem(hwndMain, ID_SCENEEDITOR), visible);
ShowWindow(GetDlgItem(hwndMain, ID_TESTSTREAM), visible);
ShowWindow(GetDlgItem(hwndMain, ID_GLOBALSOURCES), visible);
ShowWindow(GetDlgItem(hwndMain, ID_PLUGINS), visible);
if(!bPanelVisible) ShowWindow(GetDlgItem(hwndMain, ID_DASHBOARD), SW_HIDE);
ShowWindow(GetDlgItem(hwndMain, ID_EXIT), visible);
ShowWindow(GetDlgItem(hwndMain, ID_SCENES_TEXT), visible);
ShowWindow(GetDlgItem(hwndMain, ID_SOURCES_TEXT), visible);
ShowWindow(GetDlgItem(hwndMain, ID_SCENES), visible);
ShowWindow(GetDlgItem(hwndMain, ID_SOURCES), visible);
bPanelVisibleProcessed = true;
// HACK: Force resize to fix dashboard button. The setting should not be calculated every resize
if(bPanelVisible && !fromResizeWindow)
ResizeWindow(false);
}
void OBS::GetBaseSize(UINT &width, UINT &height) const
{
if(bRunning)
{
width = baseCX;
height = baseCY;
}
else
{
int monitorID = AppConfig->GetInt(TEXT("Video"), TEXT("Monitor"));
if(monitorID >= (int)monitors.Num())
monitorID = 0;
RECT &screenRect = monitors[monitorID].rect;
int defCX = screenRect.right - screenRect.left;
int defCY = screenRect.bottom - screenRect.top;
width = AppConfig->GetInt(TEXT("Video"), TEXT("BaseWidth"), defCX);
height = AppConfig->GetInt(TEXT("Video"), TEXT("BaseHeight"), defCY);
}
}
void OBS::ResizeWindow(bool bRedrawRenderFrame)
{
//const int listControlHeight = totalControlAreaHeight - textControlHeight - controlHeight - controlPadding;
ResizeRenderFrame(bRedrawRenderFrame);
//-----------------------------------------------------
DWORD flags = SWP_NOOWNERZORDER|SWP_SHOWWINDOW;
int xStart = clientWidth/2 - totalControlAreaWidth/2 + (controlPadding/2 + 1);
int yStart = clientHeight - totalControlAreaHeight;
int xPos = xStart;
int yPos = yStart;
//-----------------------------------------------------
HWND hwndTemp = GetDlgItem(hwndMain, ID_STATUS);
//SetWindowPos(GetDlgItem(hwndMain, ID_STATUS), NULL, xPos, yPos+listControlHeight, totalWidth-controlPadding, statusHeight, 0);
SendMessage(hwndTemp, WM_SIZE, SIZE_RESTORED, 0);
int parts[5];
parts[4] = -1;
parts[3] = clientWidth-100;
parts[2] = parts[3]-60;
parts[1] = parts[2]-170;
parts[0] = parts[1]-170;
SendMessage(hwndTemp, SB_SETPARTS, 5, (LPARAM)parts);
int resetXPos = xStart+listControlWidth*2;
//-----------------------------------------------------
UpdateRenderViewMessage();
SetWindowPos(hwndRenderMessage, NULL, 0, renderFrameCtrlHeight / 2 - 10, renderFrameCtrlWidth, 50, flags & ~SWP_SHOWWINDOW);
//-----------------------------------------------------
// Don't waste time resizing invisible controls
if(!bPanelVisibleProcessed)
ProcessPanelVisible(true);
if(!bPanelVisible)
return;
xPos = resetXPos;
yPos = yStart;
SetWindowPos(GetDlgItem(hwndMain, ID_MICVOLUME), NULL, xPos, yPos, controlWidth-controlPadding, volControlHeight, flags);
xPos += controlWidth;
SetWindowPos(GetDlgItem(hwndMain, ID_DESKTOPVOLUME), NULL, xPos, yPos, controlWidth-controlPadding, volControlHeight, flags);
xPos += controlWidth;
yPos += volControlHeight+controlPadding;
//-----------------------------------------------------
xPos = resetXPos;
SetWindowPos(GetDlgItem(hwndMain, ID_MICVOLUMEMETER), NULL, xPos, yPos, controlWidth-controlPadding, volMeterHeight, flags);
xPos += controlWidth;
SetWindowPos(GetDlgItem(hwndMain, ID_DESKTOPVOLUMEMETER), NULL, xPos, yPos, controlWidth-controlPadding, volMeterHeight, flags);
xPos += controlWidth;
yPos += volMeterHeight+controlPadding;
//-----------------------------------------------------
xPos = resetXPos;
SetWindowPos(GetDlgItem(hwndMain, ID_SETTINGS), NULL, xPos, yPos, controlWidth-controlPadding, controlHeight, flags);
xPos += controlWidth;
SetWindowPos(GetDlgItem(hwndMain, ID_STARTSTOP), NULL, xPos, yPos, controlWidth-controlPadding, controlHeight, flags);
xPos += controlWidth;
yPos += controlHeight+controlPadding;
//-----------------------------------------------------
xPos = resetXPos;
SetWindowPos(GetDlgItem(hwndMain, ID_SCENEEDITOR), NULL, xPos, yPos, controlWidth-controlPadding, controlHeight, flags);
xPos += controlWidth;
SetWindowPos(GetDlgItem(hwndMain, ID_TOGGLERECORDING), NULL, xPos, yPos, controlWidth-controlPadding, controlHeight, flags);
xPos += controlWidth;
yPos += controlHeight+controlPadding;
//-----------------------------------------------------
xPos = resetXPos;
SetWindowPos(GetDlgItem(hwndMain, ID_GLOBALSOURCES), NULL, xPos, yPos, controlWidth-controlPadding, controlHeight, flags);
xPos += controlWidth;
SetWindowPos(GetDlgItem(hwndMain, ID_TESTSTREAM), NULL, xPos, yPos, controlWidth-controlPadding, controlHeight, flags);
xPos += controlWidth;
yPos += controlHeight+controlPadding;
//-----------------------------------------------------
xPos = resetXPos;
SetWindowPos(GetDlgItem(hwndMain, ID_PLUGINS), NULL, xPos, yPos, controlWidth-controlPadding, controlHeight, flags);
xPos += controlWidth;
UpdateDashboardButton();
SetWindowPos(GetDlgItem(hwndMain, ID_EXIT), NULL, xPos, yPos, controlWidth-controlPadding, controlHeight, flags);
xPos += controlWidth;
yPos += controlHeight;
//-----------------------------------------------------
int listControlHeight = yPos-yStart-textControlHeight;
xPos = xStart;
yPos = yStart;
SetWindowPos(GetDlgItem(hwndMain, ID_SCENES_TEXT), NULL, xPos+2, yPos, listControlWidth-controlPadding-2, textControlHeight, flags);
xPos += listControlWidth;
SetWindowPos(GetDlgItem(hwndMain, ID_SOURCES_TEXT), NULL, xPos+2, yPos, listControlWidth-controlPadding-2, textControlHeight, flags);
xPos += listControlWidth;
yPos += textControlHeight;
xPos = xStart;
//-----------------------------------------------------
SetWindowPos(GetDlgItem(hwndMain, ID_SCENES), NULL, xPos, yPos, listControlWidth-controlPadding, listControlHeight, flags);
xPos += listControlWidth;
SetWindowPos(GetDlgItem(hwndMain, ID_SOURCES), NULL, xPos, yPos, listControlWidth-controlPadding, listControlHeight, flags);
xPos += listControlWidth;
}
void OBS::GetProfiles(StringList &profileList)
{
String strProfilesWildcard;
OSFindData ofd;
HANDLE hFind;
profileList.Clear();
String profileDir(FormattedString(L"%s/profiles/", OBSGetAppDataPath()));
strProfilesWildcard << profileDir << "*.ini";
if(hFind = OSFindFirstFile(strProfilesWildcard, ofd))
{
do
{
String profile(GetPathWithoutExtension(ofd.fileName));
String profilePath(FormattedString(L"%s%s.ini", profileDir.Array(), profile.Array()));
if(ofd.bDirectory || !OSFileExists(profilePath) || profileList.HasValue(profile)) continue;
profileList << profile;
} while(OSFindNextFile(hFind, ofd));
OSFindClose(hFind);
}
}
void OBS::GetSceneCollection(StringList &sceneCollectionList)
{
String strSceneCollectionWildcard;
OSFindData ofd;
HANDLE hFind;
sceneCollectionList.Clear();
String sceneCollectionDir(FormattedString(L"%s/sceneCollection/", OBSGetAppDataPath()));
strSceneCollectionWildcard << sceneCollectionDir << "*.xconfig";
if (hFind = OSFindFirstFile(strSceneCollectionWildcard, ofd))
{
do
{
String sceneCollection(GetPathWithoutExtension(ofd.fileName));
String sceneCollectionPath(FormattedString(L"%s%s.xconfig", sceneCollectionDir.Array(), sceneCollection.Array()));
if (ofd.bDirectory || !OSFileExists(sceneCollectionPath) || sceneCollectionList.HasValue(sceneCollection)) continue;
sceneCollectionList << sceneCollection;
} while (OSFindNextFile(hFind, ofd));
OSFindClose(hFind);
}
}
void OBS::RefreshStreamButtons(bool disable)
{
if (bShuttingDown) return;
int networkMode = AppConfig->GetInt(TEXT("Publish"), TEXT("Mode"), 2);
bRecordingOnly = (networkMode == 1);
bool canStream = networkMode == 0 && !bTestStream && bStreamFlushed;
canRecord = !bTestStream;
bool canTest = !bRecordingReplayBuffer && !bRecording && (!bStreaming || bTestStream);
EnableWindow(GetDlgItem(hwndMain, ID_STARTSTOP), !disable && canStream);
EnableWindow(GetDlgItem(hwndMain, ID_TOGGLERECORDING), !disable && canRecord);
EnableWindow(GetDlgItem(hwndMain, ID_TESTSTREAM), !disable && canTest);
HMENU fileMenu = GetSubMenu(GetMenu(hwndMain), 0);
EnableMenuItem(fileMenu, 2, (canRecord ? MF_ENABLED : MF_DISABLED) | MF_BYPOSITION);
}
void OBS::ConfigureStreamButtons()
{
if (bShuttingDown) return;
if (GetWindowThreadProcessId(hwndMain, nullptr) != GetCurrentThreadId())
return PostConfigureStreamButtons();
RefreshStreamButtons();
SetWindowText(GetDlgItem(hwndMain, ID_STARTSTOP), (bStreaming && !bTestStream) ? Str("MainWindow.StopStream") : Str("MainWindow.StartStream"));
SetWindowText(GetDlgItem(hwndMain, ID_TOGGLERECORDING), bRecording ? Str("MainWindow.StopRecording") : Str("MainWindow.StartRecording"));
SetWindowText(GetDlgItem(hwndMain, ID_TESTSTREAM), bTestStream ? Str("MainWindow.StopTest") : Str("MainWindow.TestStream"));
}
void OBS::PostConfigureStreamButtons()
{
if (hwndMain) PostMessage(hwndMain, OBS_CONFIGURE_STREAM_BUTTONS, 0, 0);
}
void OBS::ReloadSceneCollection()
{
HWND hwndTemp;
hwndTemp = GetDlgItem(hwndMain, ID_SCENES);
CTSTR collection = GetCurrentSceneCollection();
String strScenesConfig = FormattedString(L"%s\\sceneCollection\\%s.xconfig", lpAppDataPath, collection);
if (!scenesConfig.Open(strScenesConfig))
CrashError(TEXT("Could not open '%s'"), strScenesConfig.Array());
XElement *scenes = scenesConfig.GetElement(TEXT("scenes"));
if (!scenes)
scenes = scenesConfig.CreateElement(TEXT("scenes"));
SendMessage(hwndTemp, LB_RESETCONTENT, 0, 0);
App->sceneElement = NULL;
UINT numScenes = scenes->NumElements();
if (!numScenes)
{
XElement *scene = scenes->CreateElement(Str("Scene"));
scene->SetString(TEXT("class"), TEXT("Scene"));
numScenes++;
}
for (UINT i = 0; i<numScenes; i++)
{
XElement *scene = scenes->GetElementByID(i);
SendMessage(hwndTemp, LB_ADDSTRING, 0, (LPARAM)scene->GetName());
}
//-----------------------------------------------------
// populate sources
if (numScenes)
{
String strScene = AppConfig->GetString(TEXT("General"), TEXT("CurrentScene"));
int id = (int)SendMessage(hwndTemp, LB_FINDSTRINGEXACT, -1, (LPARAM)strScene.Array());
if (id == LB_ERR)
id = 0;
SendMessage(hwndTemp, LB_SETCURSEL, (WPARAM)id, 0);
SendMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_SCENES, LBN_SELCHANGE), (LPARAM)GetDlgItem(hwndMain, ID_SCENES));
}
//-----------------------------------------------------
for (UINT i = 0; i<numScenes; i++)
{
XElement *scene = scenes->GetElementByID(i);
DWORD hotkey = scene->GetInt(TEXT("hotkey"));
if (hotkey)
{
SceneHotkeyInfo hotkeyInfo;
hotkeyInfo.hotkey = hotkey;
hotkeyInfo.scene = scene;
hotkeyInfo.hotkeyID = API->CreateHotkey(hotkey, SceneHotkey, 0);
if (hotkeyInfo.hotkeyID)
sceneHotkeys << hotkeyInfo;
}
}
}
void OBS::ReloadIniSettings()
{
HWND hwndTemp;
//-------------------------------------------
// mic volume data
hwndTemp = GetDlgItem(hwndMain, ID_MICVOLUME);
if(!AppConfig->HasKey(TEXT("Audio"), TEXT("MicVolume")))
AppConfig->SetFloat(TEXT("Audio"), TEXT("MicVolume"), 1.0f);
SetVolumeControlValue(hwndTemp, AppConfig->GetFloat(TEXT("Audio"), TEXT("MicVolume"), 1.0f));
SetVolumeControlMutedVal(hwndTemp, AppConfig->GetFloat(TEXT("Audio"), TEXT("MicMutedVolume"), 1.0f));
AudioDeviceList audioDevices;
GetAudioDevices(audioDevices, ADT_RECORDING, false, true);
String strDevice = AppConfig->GetString(TEXT("Audio"), TEXT("Device"), NULL);
if(strDevice.IsEmpty() || !audioDevices.HasID(strDevice))
{
AppConfig->SetString(TEXT("Audio"), TEXT("Device"), TEXT("Disable"));
strDevice = TEXT("Disable");
}
audioDevices.FreeData();
EnableWindow(hwndTemp, !strDevice.CompareI(TEXT("Disable")));
//-------------------------------------------
// desktop volume
hwndTemp = GetDlgItem(hwndMain, ID_DESKTOPVOLUME);
if(!AppConfig->HasKey(TEXT("Audio"), TEXT("DesktopVolume")))
AppConfig->SetFloat(TEXT("Audio"), TEXT("DesktopVolume"), 1.0f);
SetVolumeControlValue(hwndTemp, AppConfig->GetFloat(TEXT("Audio"), TEXT("DesktopVolume"), 0.0f));
SetVolumeControlMutedVal(hwndTemp, AppConfig->GetFloat(TEXT("Audio"), TEXT("DesktopMutedVolume"), 1.0f));
//-------------------------------------------
// desktop boost
DWORD desktopBoostMultiple = GlobalConfig->GetInt(TEXT("Audio"), TEXT("DesktopBoostMultiple"), 1);
if(desktopBoostMultiple < 1)
desktopBoostMultiple = 1;
else if(desktopBoostMultiple > 20)
desktopBoostMultiple = 20;
desktopBoost = float(desktopBoostMultiple);
//-------------------------------------------
// mic boost
DWORD micBoostMultiple = AppConfig->GetInt(TEXT("Audio"), TEXT("MicBoostMultiple"), 1);
if(micBoostMultiple < 1)
micBoostMultiple = 1;
else if(micBoostMultiple > 20)
micBoostMultiple = 20;
micBoost = float(micBoostMultiple);
//-------------------------------------------
// dashboard
/*
strDashboard = AppConfig->GetString(TEXT("Publish"), TEXT("Dashboard"));
strDashboard.KillSpaces();
UpdateDashboardButton();
*/
//-------------------------------------------
// hotkeys
QuickClearHotkey(pushToTalkHotkeyID);
QuickClearHotkey(pushToTalkHotkey2ID);
QuickClearHotkey(muteMicHotkeyID);
QuickClearHotkey(muteDesktopHotkeyID);
QuickClearHotkey(stopStreamHotkeyID);
QuickClearHotkey(startStreamHotkeyID);
QuickClearHotkey(stopRecordingHotkeyID);
QuickClearHotkey(startRecordingHotkeyID);
QuickClearHotkey(stopReplayBufferHotkeyID);
QuickClearHotkey(startReplayBufferHotkeyID);
QuickClearHotkey(saveReplayBufferHotkeyID);
QuickClearHotkey(recordFromReplayBufferHotkeyID);
bUsingPushToTalk = !!AppConfig->GetInt(L"Audio", L"UsePushToTalk") != 0;
DWORD hotkey = AppConfig->GetInt(TEXT("Audio"), TEXT("PushToTalkHotkey"));
DWORD hotkey2 = AppConfig->GetInt(TEXT("Audio"), TEXT("PushToTalkHotkey2"));
pushToTalkDelay = AppConfig->GetInt(TEXT("Audio"), TEXT("PushToTalkDelay"), 200);
if(bUsingPushToTalk && hotkey)
pushToTalkHotkeyID = API->CreateHotkey(hotkey, OBS::PushToTalkHotkey, NULL);
if(bUsingPushToTalk && hotkey2)
pushToTalkHotkey2ID = API->CreateHotkey(hotkey2, OBS::PushToTalkHotkey, NULL);
hotkey = AppConfig->GetInt(TEXT("Audio"), TEXT("MuteMicHotkey"));
if(hotkey)
muteMicHotkeyID = API->CreateHotkey(hotkey, OBS::MuteMicHotkey, NULL);
hotkey = AppConfig->GetInt(TEXT("Audio"), TEXT("MuteDesktopHotkey"));
if(hotkey)
muteDesktopHotkeyID = API->CreateHotkey(hotkey, OBS::MuteDesktopHotkey, NULL);
hotkey = AppConfig->GetInt(TEXT("Publish"), TEXT("StopStreamHotkey"));
if (hotkey)
stopStreamHotkeyID = API->CreateHotkey(hotkey, OBS::StopStreamHotkey, NULL);
hotkey = AppConfig->GetInt(TEXT("Publish"), TEXT("StartStreamHotkey"));
if (hotkey)
startStreamHotkeyID = API->CreateHotkey(hotkey, OBS::StartStreamHotkey, NULL);
hotkey = AppConfig->GetInt(TEXT("Publish"), TEXT("StopRecordingHotkey"));
if (hotkey)
stopRecordingHotkeyID = API->CreateHotkey(hotkey, OBS::StopRecordingHotkey, NULL);
hotkey = AppConfig->GetInt(TEXT("Publish"), TEXT("StartRecordingHotkey"));
if (hotkey)
startRecordingHotkeyID = API->CreateHotkey(hotkey, OBS::StartRecordingHotkey, NULL);
hotkey = AppConfig->GetInt(L"Publish", L"StopReplayBufferHotkey");
if (hotkey)
stopReplayBufferHotkeyID = API->CreateHotkey(hotkey, OBS::StopReplayBufferHotkey, NULL);
hotkey = AppConfig->GetInt(L"Publish", L"StartReplayBufferHotkey");
if (hotkey)
startReplayBufferHotkeyID = API->CreateHotkey(hotkey, OBS::StartReplayBufferHotkey, NULL);
hotkey = AppConfig->GetInt(L"Publish", L"SaveReplayBufferHotkey");
if (hotkey)
saveReplayBufferHotkeyID = API->CreateHotkey(hotkey, OBS::SaveReplayBufferHotkey, NULL);
hotkey = AppConfig->GetInt(L"Publish", L"RecordFromReplayBufferHotkey");
if (hotkey)
recordFromReplayBufferHotkeyID = API->CreateHotkey(hotkey, OBS::RecordFromReplayBufferHotkey, NULL);
//-------------------------------------------
// Notification Area icon
bool showIcon = AppConfig->GetInt(TEXT("General"), TEXT("ShowNotificationAreaIcon"), 0) != 0;
bool minimizeToIcon = AppConfig->GetInt(TEXT("General"), TEXT("MinimizeToNotificationArea"), 0) != 0;
if (showIcon)
{
ShowNotificationAreaIcon();
if (minimizeToIcon && IsIconic(hwndMain))
ShowWindow(hwndMain, SW_HIDE);
}
else
HideNotificationAreaIcon();
bKeepRecording = AppConfig->GetInt(TEXT("Publish"), TEXT("KeepRecording")) != 0;
if (!minimizeToIcon && !IsWindowVisible(hwndMain))
ShowWindow(hwndMain, SW_SHOW);
ConfigureStreamButtons();
//--------------------------------------------
// Update old config, transition old encoder selection
int qsv = AppConfig->GetInt(L"Video Encoding", L"UseQSV", -1);
int nvenc = AppConfig->GetInt(L"Video Encoding", L"UseNVENC", -1);
if (qsv != -1 || nvenc != -1)
{
AppConfig->SetString(L"Video Encoding", L"Encoder", (qsv > 0) ? L"QSV" : (nvenc > 0) ? L"NVENC" : L"x264");
AppConfig->Remove(L"Video Encoding", L"UseQSV");
AppConfig->Remove(L"Video Encoding", L"UseNVENC");
int custom = AppConfig->GetInt(L"Video Encoding", L"UseCustomSettings", -1);
int customQSV = AppConfig->GetInt(L"Video Encoding", L"QSVUseVideoEncoderSettings", -1);
if (custom > 0 && customQSV > 0)
AppConfig->SetString(L"Video Encoding", L"CustomQSVSettings", AppConfig->GetString(L"Video Encoding", L"CustomSettings"));
}
}
void OBS::UpdateAudioMeters()
{
SetVolumeMeterValue(GetDlgItem(hwndMain, ID_DESKTOPVOLUMEMETER), desktopMag, desktopMax, desktopPeak);
SetVolumeMeterValue(GetDlgItem(hwndMain, ID_MICVOLUMEMETER), micMag, micMax, micPeak);
}
HICON OBS::GetIcon(HINSTANCE hInst, int resource)
{
for(UINT i=0; i<Icons.Num(); i++)
{
if(Icons[i].resource == resource && Icons[i].hInst == hInst)
return Icons[i].hIcon;
}
//---------------------
IconInfo ii;
ii.hInst = hInst;
ii.resource = resource;
ii.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(resource));
Icons << ii;
return ii.hIcon;
}
HFONT OBS::GetFont(CTSTR lpFontFace, int fontSize, int fontWeight)
{
for(UINT i=0; i<Fonts.Num(); i++)
{
if(Fonts[i].strFontFace.CompareI(lpFontFace) && Fonts[i].fontSize == fontSize && Fonts[i].fontWeight == fontWeight)
return Fonts[i].hFont;
}
//---------------------
HFONT hFont = NULL;
LOGFONT lf;
zero(&lf, sizeof(lf));
scpy_n(lf.lfFaceName, lpFontFace, 31);
lf.lfHeight = fontSize;
lf.lfWeight = fontWeight;
lf.lfQuality = ANTIALIASED_QUALITY;
if(hFont = CreateFontIndirect(&lf))
{
FontInfo &fi = *Fonts.CreateNew();
fi.hFont = hFont;
fi.fontSize = fontSize;
fi.fontWeight = fontWeight;
fi.strFontFace = lpFontFace;
}
return hFont;
}
StatusBarDrawData statusBarData;
void OBS::ClearStatusBar()
{
HWND hwndStatusBar = GetDlgItem(hwndMain, ID_STATUS);
PostMessage(hwndStatusBar, SB_SETTEXT, 0, NULL);
PostMessage(hwndStatusBar, SB_SETTEXT, 1, NULL);
PostMessage(hwndStatusBar, SB_SETTEXT, 2, NULL);
PostMessage(hwndStatusBar, SB_SETTEXT, 3, NULL);
PostMessage(hwndStatusBar, SB_SETTEXT, 4, NULL);
}
void OBS::SetStatusBarData()
{
if (bRunning && OSTryEnterMutex(hStartupShutdownMutex))
{
HWND hwndStatusBar = GetDlgItem(hwndMain, ID_STATUS);
SendMessage(hwndStatusBar, WM_SETREDRAW, 0, 0);
SendMessage(hwndStatusBar, SB_SETTEXT, 0 | SBT_OWNERDRAW, NULL);
SendMessage(hwndStatusBar, SB_SETTEXT, 1 | SBT_OWNERDRAW, NULL);
SendMessage(hwndStatusBar, SB_SETTEXT, 2 | SBT_OWNERDRAW, NULL);
SendMessage(hwndStatusBar, SB_SETTEXT, 3 | SBT_OWNERDRAW, NULL);
SendMessage(hwndStatusBar, SB_SETTEXT, 4 | SBT_OWNERDRAW, NULL);
SendMessage(hwndStatusBar, WM_SETREDRAW, 1, 0);
InvalidateRect(hwndStatusBar, NULL, FALSE);
if (bRunning && network)
{
ReportStreamStatus(bRunning, bTestStream,
(UINT) App->bytesPerSec, App->curStrain,
(UINT)this->totalStreamTime, (UINT)network->NumTotalVideoFrames(),
(UINT)App->curFramesDropped, (UINT) App->captureFPS);
}
ReportOBSStatus(bRunning, bStreaming, bRecording, bTestStream, bReconnecting);
OSLeaveMutex(hStartupShutdownMutex);
}
}
void OBS::DrawStatusBar(DRAWITEMSTRUCT &dis)
{
if(!App->bRunning && !App->bStreaming && !App->bRecording)
return;
HDC hdcTemp = CreateCompatibleDC(dis.hDC);
HBITMAP hbmpTemp = CreateCompatibleBitmap(dis.hDC, dis.rcItem.right-dis.rcItem.left, dis.rcItem.bottom-dis.rcItem.top);
SelectObject(hdcTemp, hbmpTemp);
SelectObject(hdcTemp, GetCurrentObject(dis.hDC, OBJ_FONT));
SetTextColor(hdcTemp, GetTextColor(dis.hDC));
//HBRUSH hColorBrush = CreateSolidBrush((green<<8)|red);
RECT rc;
mcpy(&rc, &dis.rcItem, sizeof(rc));
rc.left -= dis.rcItem.left;
rc.right -= dis.rcItem.left;
rc.top -= dis.rcItem.top;
rc.bottom -= dis.rcItem.top;
FillRect(hdcTemp, &rc, (HBRUSH)(COLOR_BTNFACE+1));
//DeleteObject(hColorBrush);
//--------------------------------
if(dis.itemID == 4)
{
HBRUSH hColorBrush;
DWORD green = 0xFF, red;
statusBarData.bytesPerSec = App->bytesPerSec;
statusBarData.strain = App->curStrain;
//statusBarData.strain = rand()%101;
//show grey rather than green when not connected
if (App->network && App->network->NumTotalVideoFrames() == 0)
{
hColorBrush = CreateSolidBrush(RGB(100,100,100));
}
else
{
if(statusBarData.strain > 50.0)
green = DWORD(((50.0-(statusBarData.strain-50.0))/50.0)*255.0);
double redStrain = statusBarData.strain/50.0;
if(redStrain > 1.0)
redStrain = 1.0;
red = DWORD(redStrain*255.0);
//--------------------------------
hColorBrush = CreateSolidBrush((green<<8)|red);
}
RECT rcBox = {0, 0, 20, 20};
/*rc.left += dis.rcItem.left;
rc.right += dis.rcItem.left;
rc.top += dis.rcItem.top;
rc.bottom += dis.rcItem.top;*/
FillRect(hdcTemp, &rcBox, hColorBrush);
DeleteObject(hColorBrush);
//--------------------------------
SetBkMode(hdcTemp, TRANSPARENT);
rc.left += 22;
String strKBPS;
strKBPS << IntString((statusBarData.bytesPerSec*8) / 1000) << TEXT("kb/s");
//strKBPS << IntString(rand()) << TEXT("kb/s");
DrawText(hdcTemp, strKBPS, strKBPS.Length(), &rc, DT_VCENTER|DT_SINGLELINE|DT_LEFT);
}
else
{
String strOutString;
DWORD color = 0x000000;
switch(dis.itemID)
{
case 0:
{
StreamInfoPriority priority;
strOutString << App->GetMostImportantInfo(priority);
if (priority == StreamInfoPriority_Critical)
color = 0x0000FF;
break;
}
case 1:
{
DWORD streamTimeSecondsTotal = App->totalStreamTime/1000;
DWORD streamTimeMinutesTotal = streamTimeSecondsTotal/60;
DWORD streamTimeSeconds = streamTimeSecondsTotal%60;
DWORD streamTimeHours = streamTimeMinutesTotal/60;
DWORD streamTimeMinutes = streamTimeMinutesTotal%60;
int networkMode = AppConfig->GetInt(TEXT("Publish"), TEXT("Mode"), 2);
strOutString = FormattedString(TEXT("%u:%02u:%02u"), streamTimeHours, streamTimeMinutes, streamTimeSeconds);
StringList mods;
if (App->bStreaming && !App->bTestStream)
mods << L"LIVE";
if (App->bRecording)
mods << L"REC";
if (App->bRecordingReplayBuffer)
mods << L"BUF";
if (App->bTestStream)
mods << L"Preview";
strOutString << FormattedString(L" (%s)", mods.Join(L" + ").Array());
}
break;
case 2:
{
double percentageDropped = 0.0;
if (OSTryEnterMutex(App->hStartupShutdownMutex))
{
if(App->network)
{
UINT numTotalFrames = App->network->NumTotalVideoFrames();
if(numTotalFrames)
percentageDropped = (double(App->network->NumDroppedFrames())/double(numTotalFrames))*100.0;
}
OSLeaveMutex(App->hStartupShutdownMutex);
}
strOutString << Str("MainWindow.DroppedFrames") << FormattedString(TEXT(" %u (%0.2f%%)"), App->curFramesDropped, percentageDropped);
}
break;
case 3: strOutString << TEXT("FPS: ") << IntString(App->captureFPS); break;
}
if(strOutString.IsValid())
{
SetTextColor(hdcTemp, color);
SetBkMode(hdcTemp, TRANSPARENT);
DrawText(hdcTemp, strOutString, strOutString.Length(), &rc, DT_VCENTER|DT_SINGLELINE|DT_LEFT);
}
}
//--------------------------------
BitBlt(dis.hDC, dis.rcItem.left, dis.rcItem.top, dis.rcItem.right-dis.rcItem.left, dis.rcItem.bottom-dis.rcItem.top, hdcTemp, 0, 0, SRCCOPY);
DeleteObject(hdcTemp);
DeleteObject(hbmpTemp);
}
void OBS::SelectSources()
{
if(scene)
scene->DeselectAll();
HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES);
UINT numSelected = ListView_GetSelectedCount(hwndSources);
if(numSelected)
{
List<UINT> selectedItems;
selectedItems.SetSize(numSelected);
//SendMessage(hwndSources, LB_GETSELITEMS, numSelected, (LPARAM)selectedItems.Array());
if(scene)
{
int iPos = ListView_GetNextItem(hwndSources, -1, LVNI_SELECTED);
while (iPos != -1)
{
SceneItem *sceneItem = scene->GetSceneItem(iPos);
sceneItem->bSelected = true;
iPos = ListView_GetNextItem(hwndSources, iPos, LVNI_SELECTED);
}
}
}
}
void OBS::CheckSources()
{
XElement *curSceneElement = App->sceneElement;
XElement *sources = curSceneElement->GetElement(TEXT("sources"));
if(!sources)
return;
HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES);
UINT numSources = ListView_GetItemCount(hwndSources);
for(UINT i = 0; i < numSources; i++)
{
bool checked = ListView_GetCheckState(hwndSources, i) > 0;
XElement *source =sources->GetElementByID(i);
bool curRender = source->GetInt(TEXT("render"), 0) > 0;
if(curRender != checked)
{
source->SetInt(TEXT("render"), (checked)?1:0);
if(scene && i < scene->NumSceneItems())
{
SceneItem *sceneItem = scene->GetSceneItem(i);
sceneItem->bRender = checked;
sceneItem->SetRender(checked);
}
ReportSourceChanged(source->GetName(), source);
}
}
}
void OBS::SetSourceRender(CTSTR sourceName, bool render)
{
XElement *curSceneElement = App->sceneElement;
XElement *sources = curSceneElement->GetElement(TEXT("sources"));
if(!sources)
return;
HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES);
UINT numSources = ListView_GetItemCount(hwndSources);
for(UINT i = 0; i < numSources; i++)
{
bool checked = ListView_GetCheckState(hwndSources, i) > 0;
XElement *source =sources->GetElementByID(i);
if(scmp(source->GetName(), sourceName) == 0 && checked != render)
{
if(scene && i < scene->NumSceneItems())
{
SceneItem *sceneItem = scene->GetSceneItem(i);
sceneItem->SetRender(render);
}
else
{
source->SetInt(TEXT("render"), (render)?1:0);
}
App->bChangingSources = true;
ListView_SetCheckState(hwndSources, i, render);
App->bChangingSources = false;
ReportSourceChanged(sourceName, source);
break;
}
}
}
BOOL OBS::SetNotificationAreaIcon(DWORD dwMessage, int idIcon, const String &tooltip)
{
NOTIFYICONDATA niData;
BOOL result;
ZeroMemory(&niData, sizeof(NOTIFYICONDATA));
niData.cbSize = sizeof(niData);
niData.hWnd = hwndMain;
niData.uID = 0;
if (NIM_DELETE != dwMessage)
{
niData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
niData.uCallbackMessage = OBS_NOTIFICATIONAREA;
niData.hIcon = LoadIcon(hinstMain, MAKEINTRESOURCE(idIcon));
scpy_n (niData.szTip, tooltip, _countof(niData.szTip)-1);
}
result = Shell_NotifyIcon(dwMessage, &niData);
if(niData.hIcon)
DestroyIcon(niData.hIcon);
return result;
}
BOOL OBS::ShowNotificationAreaIcon()
{
BOOL result = FALSE;
int idIcon = (bRunning && !bTestStream) ? IDI_ICON2 : IDI_ICON1;
if (!bNotificationAreaIcon)
{
bNotificationAreaIcon = true;
result = SetNotificationAreaIcon(NIM_ADD, idIcon, GetApplicationName());
}
else
{
result = SetNotificationAreaIcon(NIM_MODIFY, idIcon, GetApplicationName());
}
return result;
}
BOOL OBS::UpdateNotificationAreaIcon()
{
if (bNotificationAreaIcon)
return ShowNotificationAreaIcon();
return TRUE;
}
BOOL OBS::HideNotificationAreaIcon()
{
bNotificationAreaIcon = false;
return SetNotificationAreaIcon(NIM_DELETE, 0, TEXT(""));
}
BOOL OBS::UpdateDashboardButton()
{
//Are we in live stream mode, and do we have a non-empty dashboard string?
/*
BOOL bStreamOutput = AppConfig->GetInt(TEXT("Publish"), TEXT("Mode")) == 0;
BOOL bDashboardValid = strDashboard.IsValid();
BOOL bShowDashboardButton = bPanelVisible && bStreamOutput && bDashboardValid;
return ShowWindow(GetDlgItem(hwndMain, ID_DASHBOARD), bShowDashboardButton ? SW_SHOW : SW_HIDE);
*/
return false;
}
void OBS::ActuallyEnableProjector()
{
DisableProjector();
D3D10System *sys = static_cast<D3D10System*>(GS);
DXGI_SWAP_CHAIN_DESC swapDesc;
zero(&swapDesc, sizeof(swapDesc));
swapDesc.BufferCount = 2;
swapDesc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapDesc.BufferDesc.Width = projectorWidth;
swapDesc.BufferDesc.Height = projectorHeight;
swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapDesc.OutputWindow = hwndProjector;
swapDesc.SampleDesc.Count = 1;
swapDesc.Windowed = TRUE;
if (!bShutdownEncodeThread)
SetWindowPos(hwndProjector, NULL, projectorX, projectorY, projectorWidth, projectorHeight, SWP_SHOWWINDOW);
ID3D10Texture2D *backBuffer = NULL;
ID3D10RenderTargetView *target = NULL;
if (FAILED(sys->factory->CreateSwapChain(sys->d3d, &swapDesc, &projectorSwap))) {
AppWarning(L"Could not create projector swap chain");
goto exit;
}
if (FAILED(projectorSwap->GetBuffer(0, IID_ID3D10Texture2D, (void**)&backBuffer))) {
AppWarning(TEXT("Unable to get projector back buffer"));
goto exit;
}
if(FAILED(sys->d3d->CreateRenderTargetView(backBuffer, NULL, &target))) {
AppWarning(TEXT("Unable to get render view from projector back buffer"));
goto exit;
}
D3D10Texture *tex = new D3D10Texture();
tex->width = projectorWidth;
tex->height = projectorHeight;
tex->format = GS_BGRA;
tex->texture = backBuffer;
tex->renderTarget = target;
projectorTexture = tex;
bProjector = true;
exit:
if (!bProjector) {
SafeRelease(projectorSwap);
SafeRelease(backBuffer);
}
bPleaseEnableProjector = false;
}
void OBS::EnableProjector(UINT monitorID)
{
const MonitorInfo &mi = GetMonitor(monitorID);
projectorMonitorID = monitorID;
projectorWidth = mi.rect.right-mi.rect.left;
projectorHeight = mi.rect.bottom-mi.rect.top;
projectorX = mi.rect.left;
projectorY = mi.rect.top;
bPleaseEnableProjector = true;
}
void OBS::DisableProjector()
{
SafeRelease(projectorSwap);
delete projectorTexture;
projectorTexture = NULL;
bProjector = false;
bPleaseDisableProjector = false;
if (!bShutdownEncodeThread)
ShowWindow(hwndProjector, SW_HIDE);
}
void OBS::GetThreadHandles (HANDLE *videoThread, HANDLE *encodeThread)
{
if (hVideoThread)
*videoThread = hVideoThread;
if (hEncodeThread)
*encodeThread = hEncodeThread;
}
NetworkStream* CreateRTMPPublisher();
NetworkStream* CreateDelayedPublisher(DWORD delayTime);
NetworkStream* CreateNullNetwork();
void OBS::RestartNetwork()
{
OSEnterMutex(App->hStartupShutdownMutex);
//delete the old one
App->network.reset();
//start up a new one
App->bSentHeaders = false;
App->network.reset(CreateRTMPPublisher());
OSLeaveMutex(App->hStartupShutdownMutex);
}