1089 lines
34 KiB
C++
1089 lines
34 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 <XInput.h>
|
|
#define XINPUT_GAMEPAD_LEFT_TRIGGER 0x0400
|
|
#define XINPUT_GAMEPAD_RIGHT_TRIGGER 0x0800
|
|
|
|
|
|
void OBS::RegisterSceneClass(CTSTR lpClassName, CTSTR lpDisplayName, OBSCREATEPROC createProc, OBSCONFIGPROC configProc, bool bDeprecated)
|
|
{
|
|
if(!lpClassName || !*lpClassName)
|
|
{
|
|
AppWarning(TEXT("OBS::RegisterSceneClass: No class name specified"));
|
|
return;
|
|
}
|
|
|
|
if(!createProc)
|
|
{
|
|
AppWarning(TEXT("OBS::RegisterSceneClass: No create procedure specified"));
|
|
return;
|
|
}
|
|
|
|
if(GetSceneClass(lpClassName))
|
|
{
|
|
AppWarning(TEXT("OBS::RegisterSceneClass: Tried to register '%s', but it already exists"), lpClassName);
|
|
return;
|
|
}
|
|
|
|
ClassInfo *classInfo = sceneClasses.CreateNew();
|
|
classInfo->strClass = lpClassName;
|
|
classInfo->strName = lpDisplayName;
|
|
classInfo->createProc = createProc;
|
|
classInfo->configProc = configProc;
|
|
classInfo->bDeprecated = bDeprecated;
|
|
}
|
|
|
|
void OBS::RegisterImageSourceClass(CTSTR lpClassName, CTSTR lpDisplayName, OBSCREATEPROC createProc, OBSCONFIGPROC configProc, bool bDeprecated)
|
|
{
|
|
if(!lpClassName || !*lpClassName)
|
|
{
|
|
AppWarning(TEXT("OBS::RegisterImageSourceClass: No class name specified"));
|
|
return;
|
|
}
|
|
|
|
if(!createProc)
|
|
{
|
|
AppWarning(TEXT("OBS::RegisterImageSourceClass: No create procedure specified"));
|
|
return;
|
|
}
|
|
|
|
if(GetImageSourceClass(lpClassName))
|
|
{
|
|
AppWarning(TEXT("OBS::RegisterImageSourceClass: Tried to register '%s', but it already exists"), lpClassName);
|
|
return;
|
|
}
|
|
|
|
ClassInfo *classInfo = imageSourceClasses.CreateNew();
|
|
classInfo->strClass = lpClassName;
|
|
classInfo->strName = lpDisplayName;
|
|
classInfo->createProc = createProc;
|
|
classInfo->configProc = configProc;
|
|
classInfo->bDeprecated = bDeprecated;
|
|
}
|
|
|
|
Scene* OBS::CreateScene(CTSTR lpClassName, XElement *data)
|
|
{
|
|
for(UINT i=0; i<sceneClasses.Num(); i++)
|
|
{
|
|
if(sceneClasses[i].strClass.CompareI(lpClassName))
|
|
return (Scene*)sceneClasses[i].createProc(data);
|
|
}
|
|
|
|
AppWarning(TEXT("OBS::CreateScene: Could not find scene class '%s'"), lpClassName);
|
|
return NULL;
|
|
}
|
|
|
|
ImageSource* OBS::CreateImageSource(CTSTR lpClassName, XElement *data)
|
|
{
|
|
for(UINT i=0; i<imageSourceClasses.Num(); i++)
|
|
{
|
|
if(imageSourceClasses[i].strClass.CompareI(lpClassName))
|
|
return (ImageSource*)imageSourceClasses[i].createProc(data);
|
|
}
|
|
|
|
AppWarning(TEXT("OBS::CreateImageSource: Could not find image source class '%s'"), lpClassName);
|
|
return NULL;
|
|
}
|
|
|
|
void OBS::ConfigureScene(XElement *element)
|
|
{
|
|
if(!element)
|
|
{
|
|
AppWarning(TEXT("OBS::ConfigureScene: NULL element specified"));
|
|
return;
|
|
}
|
|
|
|
CTSTR lpClassName = element->GetString(TEXT("class"));
|
|
if(!lpClassName)
|
|
{
|
|
AppWarning(TEXT("OBS::ConfigureScene: No class specified for scene '%s'"), element->GetName());
|
|
return;
|
|
}
|
|
|
|
for(UINT i=0; i<sceneClasses.Num(); i++)
|
|
{
|
|
if(sceneClasses[i].strClass.CompareI(lpClassName))
|
|
{
|
|
if(sceneClasses[i].configProc)
|
|
sceneClasses[i].configProc(element, false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
AppWarning(TEXT("OBS::ConfigureScene: Could not find scene class '%s'"), lpClassName);
|
|
}
|
|
|
|
void OBS::ConfigureImageSource(XElement *element)
|
|
{
|
|
if(!element)
|
|
{
|
|
AppWarning(TEXT("OBS::ConfigureImageSource: NULL element specified"));
|
|
return;
|
|
}
|
|
|
|
CTSTR lpClassName = element->GetString(TEXT("class"));
|
|
if(!lpClassName)
|
|
{
|
|
AppWarning(TEXT("OBS::ConfigureImageSource: No class specified for image source '%s'"), element->GetName());
|
|
return;
|
|
}
|
|
|
|
for(UINT i=0; i<imageSourceClasses.Num(); i++)
|
|
{
|
|
if(imageSourceClasses[i].strClass.CompareI(lpClassName))
|
|
{
|
|
if(imageSourceClasses[i].configProc)
|
|
imageSourceClasses[i].configProc(element, false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
AppWarning(TEXT("OBS::ConfigureImageSource: Could not find scene class '%s'"), lpClassName);
|
|
}
|
|
|
|
void OBS::InsertSourceItem(UINT index, LPWSTR name, bool checked)
|
|
{
|
|
LVITEM lvI;
|
|
// Initialize LVITEM members that are common to all items.
|
|
lvI.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
|
|
lvI.stateMask = 0;
|
|
lvI.iSubItem = 0;
|
|
lvI.state = 0;
|
|
lvI.pszText = name;
|
|
lvI.iItem = index;
|
|
|
|
|
|
HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES);
|
|
|
|
ListView_InsertItem(hwndSources, &lvI);
|
|
ListView_SetCheckState(hwndSources, index, checked);
|
|
ListView_SetColumnWidth(hwndSources, 0, LVSCW_AUTOSIZE_USEHEADER);
|
|
ListView_SetColumnWidth(hwndSources, 1, LVSCW_AUTOSIZE_USEHEADER);
|
|
}
|
|
|
|
bool OBS::SetScene(CTSTR lpScene)
|
|
{
|
|
if(bDisableSceneSwitching)
|
|
return false;
|
|
|
|
HWND hwndScenes = GetDlgItem(hwndMain, ID_SCENES);
|
|
UINT curSel = (UINT)SendMessage(hwndScenes, LB_GETCURSEL, 0, 0);
|
|
|
|
//-------------------------
|
|
|
|
if(curSel != LB_ERR)
|
|
{
|
|
UINT textLen = (UINT)SendMessage(hwndScenes, LB_GETTEXTLEN, curSel, 0);
|
|
|
|
String strLBName;
|
|
strLBName.SetLength(textLen);
|
|
|
|
SendMessage(hwndScenes, LB_GETTEXT, curSel, (LPARAM)strLBName.Array());
|
|
if(!strLBName.CompareI(lpScene))
|
|
{
|
|
UINT id = (UINT)SendMessage(hwndScenes, LB_FINDSTRINGEXACT, -1, (LPARAM)lpScene);
|
|
if(id == LB_ERR)
|
|
return false;
|
|
|
|
SendMessage(hwndScenes, LB_SETCURSEL, id, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UINT id = (UINT)SendMessage(hwndScenes, LB_FINDSTRINGEXACT, -1, (LPARAM)lpScene);
|
|
if(id == LB_ERR)
|
|
return false;
|
|
|
|
SendMessage(hwndScenes, LB_SETCURSEL, id, 0);
|
|
}
|
|
|
|
//-------------------------
|
|
|
|
XElement *scenes = scenesConfig.GetElement(TEXT("scenes"));
|
|
XElement *newSceneElement = scenes->GetElement(lpScene);
|
|
if(!newSceneElement)
|
|
return false;
|
|
|
|
if(sceneElement == newSceneElement)
|
|
return true;
|
|
|
|
sceneElement = newSceneElement;
|
|
|
|
CTSTR lpClass = sceneElement->GetString(TEXT("class"));
|
|
if(!lpClass)
|
|
{
|
|
AppWarning(TEXT("OBS::SetScene: no class found for scene '%s'"), newSceneElement->GetName());
|
|
return false;
|
|
}
|
|
|
|
DWORD sceneChangeStartTime;
|
|
|
|
if(bRunning)
|
|
{
|
|
Log(TEXT("++++++++++++++++++++++++++++++++++++++++++++++++++++++"));
|
|
Log(TEXT(" New Scene"));
|
|
|
|
sceneChangeStartTime = OSGetTime();
|
|
}
|
|
|
|
XElement *sceneData = newSceneElement->GetElement(TEXT("data"));
|
|
|
|
//-------------------------
|
|
|
|
Scene *newScene = NULL;
|
|
if(bRunning)
|
|
newScene = CreateScene(lpClass, sceneData);
|
|
|
|
//-------------------------
|
|
|
|
HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES);
|
|
|
|
SendMessage(hwndSources, WM_SETREDRAW, (WPARAM)FALSE, (LPARAM) 0);
|
|
|
|
App->scaleItem = NULL;
|
|
|
|
bChangingSources = true;
|
|
ListView_DeleteAllItems(hwndSources);
|
|
|
|
bool bSkipTransition = !performTransition;
|
|
|
|
XElement *sources = sceneElement->GetElement(TEXT("sources"));
|
|
if(sources)
|
|
{
|
|
UINT numSources = sources->NumElements();
|
|
ListView_SetItemCount(hwndSources, numSources);
|
|
|
|
for(UINT i=0; i<numSources; i++)
|
|
{
|
|
XElement *sourceElement = sources->GetElementByID(i);
|
|
String className = sourceElement->GetString(TEXT("class"));
|
|
|
|
if(className == "DeviceCapture") {
|
|
// There's a capture device in the next scene that isn't a global source,
|
|
// so let's skip the transition since it won't work anyway.
|
|
bSkipTransition = true;
|
|
}
|
|
}
|
|
|
|
for(UINT i=0; i<numSources; i++)
|
|
{
|
|
XElement *sourceElement = sources->GetElementByID(i);
|
|
bool render = sourceElement->GetInt(TEXT("render"), 1) > 0;
|
|
|
|
InsertSourceItem(i, (LPWSTR)sourceElement->GetName(), render);
|
|
|
|
// Do not add image sources yet in case we're skipping the transition.
|
|
// This fixes the issue where capture devices sources that used the
|
|
// same device as one in the previous scene would just go blank
|
|
// after switching.
|
|
if(bRunning && newScene && !bSkipTransition)
|
|
newScene->AddImageSource(sourceElement);
|
|
}
|
|
}
|
|
|
|
bChangingSources = false;
|
|
SendMessage(hwndSources, WM_SETREDRAW, (WPARAM)TRUE, (LPARAM) 0);
|
|
RedrawWindow(hwndSources, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
|
|
|
|
if(scene && newScene && newScene->HasMissingSources())
|
|
OBSMessageBox(hwndMain, Str("Scene.MissingSources"), NULL, 0);
|
|
|
|
if(bRunning)
|
|
{
|
|
//todo: cache scenes maybe? undecided. not really as necessary with global sources
|
|
OSEnterMutex(hSceneMutex);
|
|
|
|
UINT numSources;
|
|
|
|
if (scene)
|
|
{
|
|
//shutdown previous scene, if any
|
|
numSources = scene->sceneItems.Num();
|
|
for(UINT i=0; i<numSources; i++)
|
|
{
|
|
XElement *source = scene->sceneItems[i]->GetElement();
|
|
String className = source->GetString(TEXT("class"));
|
|
if(scene->sceneItems[i]->bRender && className == "GlobalSource") {
|
|
XElement *globalSourceData = source->GetElement(TEXT("data"));
|
|
String globalSourceName = globalSourceData->GetString(TEXT("name"));
|
|
if(App->GetGlobalSource(globalSourceName) != NULL) {
|
|
App->GetGlobalSource(globalSourceName)->GlobalSourceLeaveScene();
|
|
}
|
|
}
|
|
}
|
|
|
|
scene->EndScene();
|
|
}
|
|
|
|
Scene *previousScene = scene;
|
|
scene = newScene;
|
|
|
|
if(newScene && bSkipTransition) {
|
|
// If we're skipping the transition because of a non-global
|
|
// DirectShow device, delete the scene here and add the
|
|
// ImageSources at this point instead.
|
|
delete previousScene;
|
|
|
|
if(sources)
|
|
{
|
|
UINT numSources = sources->NumElements();
|
|
|
|
for(UINT i=0; i<numSources; i++)
|
|
{
|
|
XElement *sourceElement = sources->GetElementByID(i);
|
|
|
|
if(newScene)
|
|
newScene->AddImageSource(sourceElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
scene->BeginScene();
|
|
|
|
numSources = scene->sceneItems.Num();
|
|
for(UINT i=0; i<numSources; i++)
|
|
{
|
|
XElement *source = scene->sceneItems[i]->GetElement();
|
|
|
|
String className = source->GetString(TEXT("class"));
|
|
if(scene->sceneItems[i]->bRender && className == "GlobalSource") {
|
|
XElement *globalSourceData = source->GetElement(TEXT("data"));
|
|
String globalSourceName = globalSourceData->GetString(TEXT("name"));
|
|
if(App->GetGlobalSource(globalSourceName) != NULL) {
|
|
App->GetGlobalSource(globalSourceName)->GlobalSourceEnterScene();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!bTransitioning && !bSkipTransition)
|
|
{
|
|
bTransitioning = true;
|
|
transitionAlpha = 0.0f;
|
|
}
|
|
|
|
OSLeaveMutex(hSceneMutex);
|
|
|
|
if(!bSkipTransition) {
|
|
// Do not delete the previous scene here, since it has already
|
|
// been deleted.
|
|
delete previousScene;
|
|
}
|
|
|
|
DWORD sceneChangeTime = OSGetTime() - sceneChangeStartTime;
|
|
if (sceneChangeTime >= 500)
|
|
Log(TEXT("PERFORMANCE WARNING: Scene change took %u ms, maybe some sources should be global sources?"), sceneChangeTime);
|
|
}
|
|
|
|
if(API != NULL)
|
|
ReportSwitchScenes(lpScene);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool OBS::SetSceneCollection(CTSTR lpCollection) {
|
|
if (bRunning)
|
|
return false;
|
|
|
|
App->scenesConfig.Save();
|
|
CTSTR collection = GetCurrentSceneCollection();
|
|
String strSceneCollectionPath;
|
|
strSceneCollectionPath = FormattedString(L"%s\\sceneCollection\\%s.xconfig", lpAppDataPath, collection);
|
|
|
|
if (!App->scenesConfig.Open(strSceneCollectionPath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
GlobalConfig->SetString(TEXT("General"), TEXT("SceneCollection"), lpCollection);
|
|
App->scenesConfig.Close();
|
|
App->ReloadSceneCollection();
|
|
ResetSceneCollectionMenu();
|
|
ResetApplicationName();
|
|
App->UpdateNotificationAreaIcon();
|
|
App->scenesConfig.SaveTo(String() << lpAppDataPath << "\\scenes.xconfig");
|
|
|
|
if (API != NULL)
|
|
ReportSwitchSceneCollections(lpCollection);
|
|
|
|
return true;
|
|
}
|
|
|
|
struct HotkeyInfo
|
|
{
|
|
DWORD hotkeyID;
|
|
DWORD hotkey;
|
|
OBSHOTKEYPROC hotkeyProc;
|
|
UPARAM param;
|
|
bool bModifiersDown, bHotkeyDown, bDownSent;
|
|
};
|
|
|
|
class OBSAPIInterface : public APIInterface
|
|
{
|
|
friend class OBS;
|
|
|
|
List<HotkeyInfo> hotkeys;
|
|
DWORD curHotkeyIDVal;
|
|
|
|
void HandleHotkeys();
|
|
|
|
virtual void SetChangedSettings(bool isModified) {App->SetChangedSettings(isModified);}
|
|
virtual void SetAbortApplySettings(bool abort) { App->SetAbortApplySettings(abort); }
|
|
virtual void SetCanOptimizeSettings(bool canOptimize) override { App->SetCanOptimizeSettings(canOptimize); }
|
|
|
|
public:
|
|
virtual void EnterSceneMutex() {App->EnterSceneMutex();}
|
|
virtual void LeaveSceneMutex() {App->LeaveSceneMutex();}
|
|
|
|
virtual void StartStopStream()
|
|
{
|
|
PostMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_STARTSTOP, 0), 0);
|
|
}
|
|
|
|
virtual void StartStopPreview()
|
|
{
|
|
PostMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_TESTSTREAM, 0), 0);
|
|
}
|
|
|
|
virtual void StartStopRecording() override
|
|
{
|
|
PostMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_TOGGLERECORDING, 0), 0);
|
|
}
|
|
|
|
virtual void StartStopRecordingReplayBuffer() override
|
|
{
|
|
PostMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_TOGGLERECORDINGREPLAYBUFFER, 0), 0);
|
|
}
|
|
|
|
virtual void SaveReplayBuffer() override
|
|
{
|
|
::SaveReplayBuffer(App->replayBuffer, (DWORD)(App->GetVideoTime() - App->firstFrameTimestamp));
|
|
}
|
|
|
|
virtual bool GetStreaming()
|
|
{
|
|
return App->bRunning;
|
|
}
|
|
|
|
virtual bool GetPreviewOnly()
|
|
{
|
|
return App->bTestStream;
|
|
}
|
|
|
|
virtual bool GetRecording() const override
|
|
{
|
|
return App->bRecording;
|
|
}
|
|
|
|
virtual bool GetRecordingReplayBuffer() const override
|
|
{
|
|
return App->bRecordingReplayBuffer;
|
|
}
|
|
|
|
virtual bool GetKeepRecording() const override
|
|
{
|
|
return App->bKeepRecording;
|
|
}
|
|
|
|
virtual void RegisterSceneClass(CTSTR lpClassName, CTSTR lpDisplayName, OBSCREATEPROC createProc, OBSCONFIGPROC configProc)
|
|
{
|
|
App->RegisterSceneClass(lpClassName, lpDisplayName, createProc, configProc, false);
|
|
}
|
|
|
|
virtual void RegisterImageSourceClass(CTSTR lpClassName, CTSTR lpDisplayName, OBSCREATEPROC createProc, OBSCONFIGPROC configProc)
|
|
{
|
|
App->RegisterImageSourceClass(lpClassName, lpDisplayName, createProc, configProc, false);
|
|
}
|
|
|
|
virtual ImageSource* CreateImageSource(CTSTR lpClassName, XElement *data)
|
|
{
|
|
return App->CreateImageSource(lpClassName, data);
|
|
}
|
|
|
|
virtual XElement* GetSceneListElement() {return App->scenesConfig.GetElement(TEXT("scenes"));}
|
|
virtual XElement* GetGlobalSourceListElement() {return App->scenesConfig.GetElement(TEXT("global sources"));}
|
|
|
|
virtual void SetSourceOrder(StringList &sourceNames)
|
|
{
|
|
StringList* order = new StringList();
|
|
order->CopyList(sourceNames);
|
|
PostMessage(hwndMain, OBS_SETSOURCEORDER, 0, (LPARAM) order);
|
|
}
|
|
|
|
virtual void SetSourceRender(CTSTR lpSource, bool render)
|
|
{
|
|
if(!lpSource || !*lpSource)
|
|
return;
|
|
|
|
PostMessage(hwndMain, OBS_SETSOURCERENDER, (WPARAM)sdup(lpSource), (LPARAM) render);
|
|
}
|
|
|
|
virtual bool SetScene(CTSTR lpScene, bool bPost)
|
|
{
|
|
assert(lpScene && *lpScene);
|
|
|
|
if(!lpScene || !*lpScene)
|
|
return false;
|
|
|
|
if(bPost)
|
|
{
|
|
PostMessage(hwndMain, OBS_SETSCENE, 0, (LPARAM)sdup(lpScene));
|
|
return true;
|
|
}
|
|
|
|
return App->SetScene(lpScene);
|
|
}
|
|
virtual Scene* GetScene() const {return App->scene;}
|
|
|
|
virtual CTSTR GetSceneName() const {return App->GetSceneElement()->GetName();}
|
|
virtual XElement* GetSceneElement() {return App->GetSceneElement();}
|
|
|
|
virtual UINT CreateHotkey(DWORD hotkey, OBSHOTKEYPROC hotkeyProc, UPARAM param);
|
|
virtual void DeleteHotkey(UINT hotkeyID);
|
|
|
|
virtual Vect2 GetBaseSize() const {return Vect2(float(App->baseCX), float(App->baseCY));}
|
|
virtual Vect2 GetRenderFrameSize() const {return Vect2(float(App->renderFrameWidth), float(App->renderFrameHeight));}
|
|
virtual Vect2 GetRenderFrameOffset() const {return Vect2(float(App->renderFrameX), float(App->renderFrameY));}
|
|
virtual Vect2 GetRenderFrameControlSize() const {return Vect2(float(App->renderFrameCtrlWidth), float(App->renderFrameCtrlHeight));}
|
|
virtual Vect2 GetOutputSize() const {return Vect2(float(App->outputCX), float(App->outputCY));}
|
|
|
|
virtual void GetBaseSize(UINT &width, UINT &height) const {App->GetBaseSize(width, height);}
|
|
virtual void GetRenderFrameSize(UINT &width, UINT &height) const {App->GetRenderFrameSize(width, height);}
|
|
virtual void GetRenderFrameOffset(UINT &x, UINT &y) const {App->GetRenderFrameOffset(x, y);}
|
|
virtual void GetRenderFrameControlSize(UINT &width, UINT &height) const {App->GetRenderFrameControlSize(width, height);}
|
|
virtual void GetOutputSize(UINT &width, UINT &height) const {App->GetOutputSize(width, height);}
|
|
|
|
virtual Vect2 MapWindowToFramePos(Vect2 mousePos) const {return App->MapWindowToFramePos(mousePos);}
|
|
virtual Vect2 MapFrameToWindowPos(Vect2 framePos) const {return App->MapFrameToWindowPos(framePos);}
|
|
virtual Vect2 MapWindowToFrameSize(Vect2 windowSize) const {return App->MapWindowToFrameSize(windowSize);}
|
|
virtual Vect2 MapFrameToWindowSize(Vect2 frameSize) const {return App->MapFrameToWindowSize(frameSize);}
|
|
virtual Vect2 GetWindowToFrameScale() const {return App->GetWindowToFrameScale();}
|
|
virtual Vect2 GetFrameToWindowScale() const {return App->GetFrameToWindowScale();}
|
|
|
|
virtual UINT GetMaxFPS() const {return App->bRunning ? App->fps : AppConfig->GetInt(TEXT("Video"), TEXT("FPS"), 30);}
|
|
virtual bool GetRenderFrameIn1To1Mode() const {return App->renderFrameIn1To1Mode;}
|
|
|
|
virtual CTSTR GetLanguage() const {return App->strLanguage;}
|
|
|
|
virtual CTSTR GetAppDataPath() const {return lpAppDataPath;}
|
|
virtual String GetPluginDataPath() const {return String() << lpAppDataPath << TEXT("\\pluginData");}
|
|
|
|
virtual HWND GetMainWindow() const {return hwndMain;}
|
|
|
|
virtual UINT AddStreamInfo(CTSTR lpInfo, StreamInfoPriority priority) {return App->AddStreamInfo(lpInfo, priority);}
|
|
virtual void SetStreamInfo(UINT infoID, CTSTR lpInfo) {App->SetStreamInfo(infoID, lpInfo);}
|
|
virtual void SetStreamInfoPriority(UINT infoID, StreamInfoPriority priority) {App->SetStreamInfoPriority(infoID, priority);}
|
|
virtual void RemoveStreamInfo(UINT infoID) {App->RemoveStreamInfo(infoID);}
|
|
|
|
virtual bool UseMultithreadedOptimizations() const {return App->bUseMultithreadedOptimizations;}
|
|
|
|
virtual void AddAudioSource(AudioSource *source) {App->AddAudioSource(source);}
|
|
virtual void RemoveAudioSource(AudioSource *source) {App->RemoveAudioSource(source);}
|
|
|
|
virtual QWORD GetAudioTime() const {return App->GetAudioTime();}
|
|
|
|
virtual CTSTR GetAppPath() const {return lpAppPath;}
|
|
|
|
virtual void SetDesktopVolume(float val, bool finalValue) {App->SetDesktopVolume(val, finalValue);}
|
|
virtual float GetDesktopVolume() {return App->GetDesktopVolume();}
|
|
virtual void ToggleDesktopMute() {App->ToggleDesktopMute();}
|
|
virtual bool GetDesktopMuted() {return App->GetDesktopMuted();}
|
|
|
|
virtual void SetMicVolume(float val, bool finalValue) {App->SetMicVolume(val, finalValue);}
|
|
virtual float GetMicVolume() {return App->GetMicVolume();}
|
|
virtual void ToggleMicMute() {App->ToggleMicMute();}
|
|
virtual bool GetMicMuted() {return App->GetMicMuted();}
|
|
|
|
virtual DWORD GetOBSVersion() const {return OBS_VERSION;}
|
|
|
|
#ifdef OBS_TEST_BUILD
|
|
virtual bool IsTestVersion() const {return 1;}
|
|
#else
|
|
virtual bool IsTestVersion() const {return 0;}
|
|
#endif
|
|
|
|
virtual UINT NumAuxAudioSources() const
|
|
{
|
|
return App->auxAudioSources.Num();
|
|
}
|
|
|
|
virtual AudioSource* GetAuxAudioSource(UINT id)
|
|
{
|
|
if(App->auxAudioSources.Num() > id)
|
|
return App->auxAudioSources[id];
|
|
|
|
AppWarning(TEXT("Tried to get an aux audio source that doesn't exist!"));
|
|
return NULL;
|
|
}
|
|
|
|
virtual AudioSource* GetDesktopAudioSource() {return App->desktopAudio;}
|
|
virtual AudioSource* GetMicAudioSource() {return App->micAudio;}
|
|
|
|
virtual void GetCurDesktopVolumeStats(float *rms, float *max, float *peak) const
|
|
{
|
|
*rms = App->desktopMag;
|
|
*max = App->desktopMax;
|
|
*peak = App->desktopPeak;
|
|
}
|
|
|
|
virtual void GetCurMicVolumeStats(float *rms, float *max, float *peak) const
|
|
{
|
|
*rms = App->micMag;
|
|
*max = App->micMax;
|
|
*peak = App->micPeak;
|
|
}
|
|
|
|
virtual void AddSettingsPane(SettingsPane *pane) {App->AddSettingsPane(pane);}
|
|
virtual void RemoveSettingsPane(SettingsPane *pane) {App->RemoveSettingsPane(pane);}
|
|
|
|
virtual UINT GetSampleRateHz() const {return App->GetSampleRateHz();}
|
|
|
|
virtual UINT GetCaptureFPS() const {return App->captureFPS;}
|
|
virtual UINT GetTotalFrames() const {return App->network ? App->network->NumTotalVideoFrames() : 0;}
|
|
virtual UINT GetFramesDropped() const {return App->curFramesDropped;}
|
|
virtual UINT GetTotalStreamTime() const {return App->totalStreamTime;}
|
|
virtual UINT GetBytesPerSec() const {return App->bytesPerSec;}
|
|
|
|
virtual bool SetSceneCollection(CTSTR lpCollection, CTSTR lpScene)
|
|
{
|
|
assert(lpCollection && *lpCollection);
|
|
|
|
if (!lpCollection || !*lpCollection)
|
|
return false;
|
|
|
|
bool success = App->SetSceneCollection(lpCollection);
|
|
if (lpScene != NULL && success)
|
|
{
|
|
SetScene(lpScene, true);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
virtual CTSTR GetSceneCollectionName() const { return App->GetCurrentSceneCollection(); }
|
|
virtual void GetSceneCollectionNames(StringList &list) const { return App->GetSceneCollection(list); }
|
|
virtual void DisableTransitions() { App->performTransition = false; }
|
|
virtual void EnableTransitions() { App->performTransition = true; }
|
|
virtual bool TransitionsEnabled() const { return App->performTransition; }
|
|
};
|
|
|
|
APIInterface* CreateOBSApiInterface()
|
|
{
|
|
return new OBSAPIInterface;
|
|
}
|
|
|
|
|
|
void STDCALL SceneHotkey(DWORD hotkey, UPARAM param, bool bDown)
|
|
{
|
|
if(!bDown) return;
|
|
|
|
XElement *scenes = API->GetSceneListElement();
|
|
if(scenes)
|
|
{
|
|
UINT numScenes = scenes->NumElements();
|
|
for(UINT i=0; i<numScenes; i++)
|
|
{
|
|
XElement *scene = scenes->GetElementByID(i);
|
|
DWORD sceneHotkey = (DWORD)scene->GetInt(TEXT("hotkey"));
|
|
if(sceneHotkey == hotkey)
|
|
{
|
|
App->SetScene(scene->GetName());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DWORD STDCALL OBS::HotkeyThread(LPVOID lpUseless)
|
|
{
|
|
//-----------------------------------------------
|
|
// check hotkeys.
|
|
// Why are we handling hotkeys like this? Because RegisterHotkey and WM_HOTKEY
|
|
// does not work with fullscreen apps. Therefore, we use GetAsyncKeyState once
|
|
// per frame instead.
|
|
|
|
while(!App->bShuttingDown)
|
|
{
|
|
static_cast<OBSAPIInterface*>(API)->HandleHotkeys();
|
|
OSSleep(30);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void OBS::CallHotkey(DWORD hotkeyID, bool bDown)
|
|
{
|
|
OBSAPIInterface *apiInterface = (OBSAPIInterface*)API;
|
|
OBSHOTKEYPROC hotkeyProc = NULL;
|
|
DWORD hotkey = 0;
|
|
UPARAM param = NULL;
|
|
|
|
OSEnterMutex(hHotkeyMutex);
|
|
|
|
for(UINT i=0; i<apiInterface->hotkeys.Num(); i++)
|
|
{
|
|
HotkeyInfo &hi = apiInterface->hotkeys[i];
|
|
if(hi.hotkeyID == hotkeyID)
|
|
{
|
|
if (!hi.hotkeyProc)
|
|
{
|
|
OSLeaveMutex(hHotkeyMutex);
|
|
return;
|
|
}
|
|
|
|
hotkeyProc = hi.hotkeyProc;
|
|
param = hi.param;
|
|
hotkey = hi.hotkey;
|
|
break;
|
|
}
|
|
}
|
|
|
|
OSLeaveMutex(hHotkeyMutex);
|
|
|
|
if (!hotkeyProc)
|
|
return;
|
|
|
|
hotkeyProc(hotkey, param, bDown);
|
|
}
|
|
|
|
UINT OBSAPIInterface::CreateHotkey(DWORD hotkey, OBSHOTKEYPROC hotkeyProc, UPARAM param)
|
|
{
|
|
if(!hotkey)
|
|
return 0;
|
|
|
|
//FIXME: vk and fsModifiers aren't used?
|
|
DWORD vk = LOBYTE(hotkey);
|
|
DWORD modifier = HIBYTE(hotkey);
|
|
DWORD fsModifiers = 0;
|
|
|
|
if(modifier & HOTKEYF_ALT)
|
|
fsModifiers |= MOD_ALT;
|
|
if(modifier & HOTKEYF_CONTROL)
|
|
fsModifiers |= MOD_CONTROL;
|
|
if(modifier & HOTKEYF_SHIFT)
|
|
fsModifiers |= MOD_SHIFT;
|
|
|
|
OSEnterMutex(App->hHotkeyMutex);
|
|
HotkeyInfo &hi = *hotkeys.CreateNew();
|
|
hi.hotkeyID = ++curHotkeyIDVal;
|
|
hi.hotkey = hotkey;
|
|
hi.hotkeyProc = hotkeyProc;
|
|
hi.param = param;
|
|
hi.bModifiersDown = false;
|
|
hi.bHotkeyDown = false;
|
|
OSLeaveMutex(App->hHotkeyMutex);
|
|
|
|
return curHotkeyIDVal;
|
|
}
|
|
|
|
void OBSAPIInterface::DeleteHotkey(UINT hotkeyID)
|
|
{
|
|
OSEnterMutex(App->hHotkeyMutex);
|
|
for(UINT i=0; i<hotkeys.Num(); i++)
|
|
{
|
|
if(hotkeys[i].hotkeyID == hotkeyID)
|
|
{
|
|
hotkeys.Remove(i);
|
|
break;
|
|
}
|
|
}
|
|
OSLeaveMutex(App->hHotkeyMutex);
|
|
}
|
|
|
|
void OBSAPIInterface::HandleHotkeys()
|
|
{
|
|
List<DWORD> hitKeys;
|
|
|
|
static bool allow_other_hotkey_modifiers;
|
|
static bool uplay_overlay_compatibility;
|
|
static bool set_vars = false;
|
|
|
|
/* only query these config variables once */
|
|
if (!set_vars)
|
|
{
|
|
allow_other_hotkey_modifiers = !!GlobalConfig->GetInt(TEXT("General"), TEXT("AllowOtherHotkeyModifiers"), true);
|
|
uplay_overlay_compatibility = !!GlobalConfig->GetInt(L"General", L"UplayOverlayCompatibility", false);
|
|
set_vars = true;
|
|
}
|
|
|
|
DWORD modifiers = 0;
|
|
if(GetAsyncKeyState(VK_MENU) & 0x8000)
|
|
modifiers |= HOTKEYF_ALT;
|
|
if(GetAsyncKeyState(VK_CONTROL) & 0x8000)
|
|
modifiers |= HOTKEYF_CONTROL;
|
|
if (!uplay_overlay_compatibility)
|
|
if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
|
|
modifiers |= HOTKEYF_SHIFT;
|
|
|
|
OSEnterMutex(App->hHotkeyMutex);
|
|
|
|
for(UINT i=0; i<hotkeys.Num(); i++)
|
|
{
|
|
HotkeyInfo &info = hotkeys[i];
|
|
|
|
DWORD hotkeyVK = LOBYTE(info.hotkey);
|
|
DWORD hotkeyModifiers = HIBYTE(info.hotkey);
|
|
DWORD xinputNum = LOWORD(info.hotkey);
|
|
DWORD xinputButton = HIWORD(info.hotkey);
|
|
|
|
hotkeyModifiers &= ~(HOTKEYF_EXT);
|
|
|
|
if(xinputButton)
|
|
{
|
|
XINPUT_STATE state = { 0 };
|
|
|
|
if(XInputGetState(xinputNum, &state) == ERROR_SUCCESS)
|
|
{
|
|
if(state.Gamepad.bLeftTrigger >= 85)
|
|
state.Gamepad.wButtons |= XINPUT_GAMEPAD_LEFT_TRIGGER;
|
|
|
|
if(state.Gamepad.bRightTrigger >= 85)
|
|
state.Gamepad.wButtons |= XINPUT_GAMEPAD_RIGHT_TRIGGER;
|
|
|
|
if((state.Gamepad.wButtons & xinputButton) != 0 && !info.bHotkeyDown)
|
|
{
|
|
PostMessage(hwndMain, OBS_CALLHOTKEY, TRUE, info.hotkeyID);
|
|
info.bDownSent = true;
|
|
info.bHotkeyDown = true;
|
|
}
|
|
}
|
|
|
|
info.bModifiersDown = 0;
|
|
}
|
|
else
|
|
{
|
|
bool bModifiersMatch = false;
|
|
if(allow_other_hotkey_modifiers)
|
|
bModifiersMatch = ((hotkeyModifiers & modifiers) == hotkeyModifiers); //allows other modifiers to be pressed
|
|
else
|
|
bModifiersMatch = (hotkeyModifiers == modifiers);
|
|
|
|
if(hotkeyModifiers && !hotkeyVK) //modifier-only hotkey
|
|
{
|
|
if((hotkeyModifiers & modifiers) == hotkeyModifiers)
|
|
{
|
|
if(!info.bHotkeyDown)
|
|
{
|
|
PostMessage(hwndMain, OBS_CALLHOTKEY, TRUE, info.hotkeyID);
|
|
info.bDownSent = true;
|
|
info.bHotkeyDown = true;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bModifiersMatch && !(uplay_overlay_compatibility && hotkeyVK == VK_F2))
|
|
{
|
|
short keyState = GetAsyncKeyState(hotkeyVK);
|
|
bool bDown = (keyState & 0x8000) != 0;
|
|
bool bWasPressed = (keyState & 0x1) != 0;
|
|
|
|
if(bDown || bWasPressed)
|
|
{
|
|
if(!info.bHotkeyDown && info.bModifiersDown) //only triggers the hotkey if the actual main key was pressed second
|
|
{
|
|
PostMessage(hwndMain, OBS_CALLHOTKEY, TRUE, info.hotkeyID);
|
|
info.bDownSent = true;
|
|
}
|
|
|
|
info.bHotkeyDown = true;
|
|
if(bDown)
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
info.bModifiersDown = bModifiersMatch;
|
|
}
|
|
|
|
if(info.bHotkeyDown) //key up
|
|
{
|
|
if(info.bDownSent)
|
|
{
|
|
PostMessage(hwndMain, OBS_CALLHOTKEY, FALSE, info.hotkeyID);
|
|
info.bDownSent = false;
|
|
}
|
|
|
|
info.bHotkeyDown = false;
|
|
}
|
|
}
|
|
|
|
OSLeaveMutex(App->hHotkeyMutex);
|
|
}
|
|
|
|
UINT OBS::AddStreamInfo(CTSTR lpInfo, StreamInfoPriority priority)
|
|
{
|
|
OSEnterMutex(hInfoMutex);
|
|
|
|
StreamInfo &streamInfo = *streamInfoList.CreateNew();
|
|
UINT id = streamInfo.id = ++streamInfoIDCounter;
|
|
streamInfo.priority = priority;
|
|
streamInfo.strInfo = lpInfo;
|
|
|
|
OSLeaveMutex(hInfoMutex);
|
|
|
|
return id;
|
|
}
|
|
|
|
void OBS::SetStreamInfo(UINT infoID, CTSTR lpInfo)
|
|
{
|
|
OSEnterMutex(hInfoMutex);
|
|
|
|
for(UINT i=0; i<streamInfoList.Num(); i++)
|
|
{
|
|
if(streamInfoList[i].id == infoID)
|
|
{
|
|
streamInfoList[i].strInfo = lpInfo;
|
|
break;
|
|
}
|
|
}
|
|
|
|
OSLeaveMutex(hInfoMutex);
|
|
}
|
|
|
|
void OBS::SetStreamInfoPriority(UINT infoID, StreamInfoPriority priority)
|
|
{
|
|
OSEnterMutex(hInfoMutex);
|
|
|
|
for(UINT i=0; i<streamInfoList.Num(); i++)
|
|
{
|
|
if(streamInfoList[i].id == infoID)
|
|
{
|
|
streamInfoList[i].priority = priority;
|
|
break;
|
|
}
|
|
}
|
|
|
|
OSLeaveMutex(hInfoMutex);
|
|
}
|
|
|
|
void OBS::RemoveStreamInfo(UINT infoID)
|
|
{
|
|
OSEnterMutex(hInfoMutex);
|
|
|
|
for(UINT i=0; i<streamInfoList.Num(); i++)
|
|
{
|
|
if(streamInfoList[i].id == infoID)
|
|
{
|
|
streamInfoList[i].FreeData();
|
|
streamInfoList.Remove(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
OSLeaveMutex(hInfoMutex);
|
|
}
|
|
|
|
//todo: get rid of this and use some sort of info window. this is a really dumb design. what was I thinking?
|
|
String OBS::GetMostImportantInfo(StreamInfoPriority &priority)
|
|
{
|
|
OSEnterMutex(hInfoMutex);
|
|
|
|
int bestInfoPriority = -1;
|
|
CTSTR lpBestInfo = NULL;
|
|
|
|
for(UINT i=0; i<streamInfoList.Num(); i++)
|
|
{
|
|
if((int)streamInfoList[i].priority > bestInfoPriority)
|
|
{
|
|
lpBestInfo = streamInfoList[i].strInfo;
|
|
bestInfoPriority = streamInfoList[i].priority;
|
|
}
|
|
}
|
|
|
|
priority = (StreamInfoPriority)bestInfoPriority;
|
|
String strInfo = lpBestInfo;
|
|
OSLeaveMutex(hInfoMutex);
|
|
|
|
return strInfo;
|
|
}
|
|
|
|
void OBS::SetDesktopVolume(float val, bool finalValue)
|
|
{
|
|
val = min(1.0f, max(0, val));
|
|
HWND desktop = GetDlgItem(hwndMain, ID_DESKTOPVOLUME);
|
|
|
|
/* float in lParam hack */
|
|
LPARAM temp;
|
|
float* tempFloatPointer = (float*)&temp;
|
|
*tempFloatPointer = val;
|
|
|
|
/*Send message to desktop volume control and have it handle it*/
|
|
PostMessage(desktop, WM_COMMAND,
|
|
MAKEWPARAM(ID_DESKTOPVOLUME, finalValue?VOLN_FINALVALUE:VOLN_ADJUSTING),
|
|
(LPARAM)temp);
|
|
}
|
|
|
|
float OBS::GetDesktopVolume()
|
|
{
|
|
HWND desktop = GetDlgItem(hwndMain, ID_DESKTOPVOLUME);
|
|
return GetVolumeControlValue(desktop);
|
|
}
|
|
|
|
void OBS::ToggleDesktopMute()
|
|
{
|
|
HWND desktop = GetDlgItem(hwndMain, ID_DESKTOPVOLUME);
|
|
|
|
/*Send message to desktop volume control and have it handle it*/
|
|
PostMessage(desktop, WM_COMMAND, MAKEWPARAM(ID_DESKTOPVOLUME, VOLN_TOGGLEMUTE), 0);
|
|
}
|
|
|
|
bool OBS::GetDesktopMuted()
|
|
{
|
|
return GetDesktopVolume() < VOLN_MUTELEVEL;
|
|
}
|
|
|
|
void OBS::SetMicVolume(float val, bool finalValue)
|
|
{
|
|
val = min(1.0f, max(0, val));
|
|
HWND mic = GetDlgItem(hwndMain, ID_MICVOLUME);
|
|
|
|
/* float in lParam hack */
|
|
LPARAM temp;
|
|
float* tempFloatPointer = (float*)&temp;
|
|
*tempFloatPointer = val;
|
|
|
|
/*Send message to microphone volume control and have it handle it*/
|
|
PostMessage(mic, WM_COMMAND ,
|
|
MAKEWPARAM(ID_MICVOLUME, finalValue?VOLN_FINALVALUE:VOLN_ADJUSTING),
|
|
(LPARAM)temp);
|
|
}
|
|
|
|
float OBS::GetMicVolume()
|
|
{
|
|
HWND mic = GetDlgItem(hwndMain, ID_MICVOLUME);
|
|
return GetVolumeControlValue(mic);
|
|
}
|
|
|
|
void OBS::ToggleMicMute()
|
|
{
|
|
HWND mic = GetDlgItem(hwndMain, ID_MICVOLUME);
|
|
|
|
/*Send message to microphone volume control and have it handle it*/
|
|
PostMessage(mic, WM_COMMAND, MAKEWPARAM(ID_MICVOLUME, VOLN_TOGGLEMUTE), 0);
|
|
}
|
|
|
|
bool OBS::GetMicMuted()
|
|
{
|
|
return GetMicVolume() < VOLN_MUTELEVEL;
|
|
}
|