diff options
Diffstat (limited to 'src/VBox/Main/src-server/RecordingSettingsImpl.cpp')
-rw-r--r-- | src/VBox/Main/src-server/RecordingSettingsImpl.cpp | 707 |
1 files changed, 707 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/RecordingSettingsImpl.cpp b/src/VBox/Main/src-server/RecordingSettingsImpl.cpp new file mode 100644 index 00000000..1f055820 --- /dev/null +++ b/src/VBox/Main/src-server/RecordingSettingsImpl.cpp @@ -0,0 +1,707 @@ +/* $Id: RecordingSettingsImpl.cpp $ */ +/** @file + * + * VirtualBox COM class implementation - Machine capture settings. + */ + +/* + * Copyright (C) 2018-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_MAIN_RECORDINGSETTINGS +#include "LoggingNew.h" + +#include "RecordingSettingsImpl.h" +#include "RecordingScreenSettingsImpl.h" +#include "MachineImpl.h" + +#include <iprt/cpp/utils.h> +#include <VBox/settings.h> + +#include "AutoStateDep.h" +#include "AutoCaller.h" +#include "Global.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// RecordSettings private data definition +// +//////////////////////////////////////////////////////////////////////////////// + +struct RecordingSettings::Data +{ + Data() + : pMachine(NULL) + { } + + Machine * const pMachine; + const ComObjPtr<RecordingSettings> pPeer; + RecordScreenSettingsMap mapScreenObj; + + // use the XML settings structure in the members for simplicity + Backupable<settings::RecordingSettings> bd; +}; + +DEFINE_EMPTY_CTOR_DTOR(RecordingSettings) + +HRESULT RecordingSettings::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void RecordingSettings::FinalRelease() +{ + uninit(); + BaseFinalRelease(); +} + +/** + * Initializes the recording settings object. + * + * @returns COM result indicator + */ +HRESULT RecordingSettings::init(Machine *aParent) +{ + LogFlowThisFuncEnter(); + LogFlowThisFunc(("aParent: %p\n", aParent)); + + ComAssertRet(aParent, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m = new Data(); + + /* share the parent weakly */ + unconst(m->pMachine) = aParent; + + m->bd.allocate(); + + autoInitSpan.setSucceeded(); + + LogFlowThisFuncLeave(); + return S_OK; +} + +/** + * Initializes the capture settings object given another capture settings object + * (a kind of copy constructor). This object shares data with + * the object passed as an argument. + * + * @note This object must be destroyed before the original object + * it shares data with is destroyed. + * + * @note Locks @a aThat object for reading. + */ +HRESULT RecordingSettings::init(Machine *aParent, RecordingSettings *aThat) +{ + LogFlowThisFuncEnter(); + LogFlowThisFunc(("aParent: %p, aThat: %p\n", aParent, aThat)); + + ComAssertRet(aParent && aThat, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m = new Data(); + + unconst(m->pMachine) = aParent; + unconst(m->pPeer) = aThat; + + AutoCaller thatCaller(aThat); + AssertComRCReturnRC(thatCaller.rc()); + + AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS); + + m->bd.share(aThat->m->bd); + m->mapScreenObj = aThat->m->mapScreenObj; + + autoInitSpan.setSucceeded(); + + LogFlowThisFuncLeave(); + return S_OK; +} + +/** + * Initializes the guest object given another guest object + * (a kind of copy constructor). This object makes a private copy of data + * of the original object passed as an argument. + * + * @note Locks @a aThat object for reading. + */ +HRESULT RecordingSettings::initCopy(Machine *aParent, RecordingSettings *aThat) +{ + LogFlowThisFuncEnter(); + LogFlowThisFunc(("aParent: %p, aThat: %p\n", aParent, aThat)); + + ComAssertRet(aParent && aThat, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m = new Data(); + + unconst(m->pMachine) = aParent; + // mPeer is left null + + AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS); + + m->bd.attachCopy(aThat->m->bd); + m->mapScreenObj = aThat->m->mapScreenObj; + + autoInitSpan.setSucceeded(); + + LogFlowThisFuncLeave(); + return S_OK; +} + +/** + * Uninitializes the instance and sets the ready flag to FALSE. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void RecordingSettings::uninit() +{ + LogFlowThisFuncEnter(); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + /* Note: Do *not* call i_reset() here, as the shared recording configuration + * otherwise gets destructed when this object goes out of scope or is destroyed. */ + + m->bd.free(); + + unconst(m->pPeer) = NULL; + unconst(m->pMachine) = NULL; + + delete m; + m = NULL; + + LogFlowThisFuncLeave(); +} + +// IRecordSettings properties +///////////////////////////////////////////////////////////////////////////// + +HRESULT RecordingSettings::getEnabled(BOOL *enabled) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *enabled = m->bd->fEnabled; + + return S_OK; +} + +HRESULT RecordingSettings::setEnabled(BOOL enable) +{ + /* the machine needs to be mutable */ + AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine); + if (FAILED(adep.rc())) return adep.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + const bool fEnabled = RT_BOOL(enable); + + HRESULT rc = S_OK; + + if (m->bd->fEnabled != fEnabled) + { + m->bd.backup(); + m->bd->fEnabled = fEnabled; + + alock.release(); + + rc = m->pMachine->i_onRecordingChange(enable); + if (FAILED(rc)) + { + /* + * Normally we would do the actual change _after_ i_onCaptureChange() succeeded. + * We cannot do this because that function uses RecordSettings::GetEnabled to + * determine if it should start or stop capturing. Therefore we need to manually + * undo change. + */ + alock.acquire(); + m->bd->fEnabled = m->bd.backedUpData()->fEnabled; + } + else + { + AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking + m->pMachine->i_setModified(Machine::IsModified_Recording); + + /* Make sure to release the mutable dependency lock from above before + * actually saving the settings. */ + adep.release(); + + /** Save settings if online - @todo why is this required? -- @bugref{6818} */ + if (Global::IsOnline(m->pMachine->i_getMachineState())) + rc = m->pMachine->i_saveSettings(NULL); + } + } + + return rc; +} + +HRESULT RecordingSettings::getScreens(std::vector<ComPtr<IRecordingScreenSettings> > &aRecordScreenSettings) +{ + LogFlowThisFuncEnter(); + + AssertPtr(m->pMachine); + ComPtr<IGraphicsAdapter> pGraphicsAdapter; + m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam()); + ULONG cMonitors = 0; + if (!pGraphicsAdapter.isNull()) + pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors); + + i_syncToMachineDisplays(cMonitors); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aRecordScreenSettings.clear(); + aRecordScreenSettings.resize(m->mapScreenObj.size()); + + RecordScreenSettingsMap::const_iterator itScreenSettings = m->mapScreenObj.begin(); + size_t i = 0; + while (itScreenSettings != m->mapScreenObj.end()) + { + itScreenSettings->second.queryInterfaceTo(aRecordScreenSettings[i].asOutParam()); + Assert(aRecordScreenSettings[i].isNotNull()); + ++i; + ++itScreenSettings; + } + + Assert(aRecordScreenSettings.size() == m->mapScreenObj.size()); + + return S_OK; +} + +HRESULT RecordingSettings::getScreenSettings(ULONG uScreenId, ComPtr<IRecordingScreenSettings> &aRecordScreenSettings) +{ + LogFlowThisFuncEnter(); + + AssertPtr(m->pMachine); + ComPtr<IGraphicsAdapter> pGraphicsAdapter; + m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam()); + ULONG cMonitors = 0; + if (!pGraphicsAdapter.isNull()) + pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors); + + i_syncToMachineDisplays(cMonitors); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (uScreenId + 1 > m->mapScreenObj.size()) + return setError(E_INVALIDARG, tr("Invalid screen ID specified")); + + RecordScreenSettingsMap::const_iterator itScreenSettings = m->mapScreenObj.find(uScreenId); + if (itScreenSettings != m->mapScreenObj.end()) + { + itScreenSettings->second.queryInterfaceTo(aRecordScreenSettings.asOutParam()); + return S_OK; + } + + return VBOX_E_OBJECT_NOT_FOUND; +} + +// IRecordSettings methods +///////////////////////////////////////////////////////////////////////////// + +// public methods only for internal purposes +///////////////////////////////////////////////////////////////////////////// + +/** + * Adds a screen settings object to a particular map. + * + * @returns IPRT status code. VERR_ALREADY_EXISTS if the object in question already exists. + * @param screenSettingsMap Map to add screen settings to. + * @param uScreenId Screen ID to add settings for. + * @param data Recording screen settings to use for that screen. + */ +int RecordingSettings::i_createScreenObj(RecordScreenSettingsMap &screenSettingsMap, + uint32_t uScreenId, const settings::RecordingScreenSettings &data) +{ + LogFlowThisFunc(("Screen %RU32\n", uScreenId)); + + if (screenSettingsMap.find(uScreenId) != screenSettingsMap.end()) + { + AssertFailed(); + return VERR_ALREADY_EXISTS; + } + + int vrc = VINF_SUCCESS; + + ComObjPtr<RecordingScreenSettings> recordingScreenSettings; + HRESULT rc = recordingScreenSettings.createObject(); + if (SUCCEEDED(rc)) + { + rc = recordingScreenSettings->init(this, uScreenId, data); + if (SUCCEEDED(rc)) + { + try + { + screenSettingsMap[uScreenId] = recordingScreenSettings; + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + } + } + + return vrc; +} + +/** + * Removes a screen settings object from a particular map. + * + * @returns IPRT status code. VERR_NOT_FOUND if specified screen was not found. + * @param screenSettingsMap Map to remove screen settings from. + * @param uScreenId ID of screen to remove. + */ +int RecordingSettings::i_destroyScreenObj(RecordScreenSettingsMap &screenSettingsMap, uint32_t uScreenId) +{ + LogFlowThisFunc(("Screen %RU32\n", uScreenId)); + + AssertReturn(uScreenId > 0, VERR_INVALID_PARAMETER); /* Removing screen 0 isn't a good idea. */ + + RecordScreenSettingsMap::iterator itScreen = screenSettingsMap.find(uScreenId); + if (itScreen == screenSettingsMap.end()) + { + AssertFailed(); + return VERR_NOT_FOUND; + } + + /* Make sure to consume the pointer before the one of the + * iterator gets released. */ + ComObjPtr<RecordingScreenSettings> pScreenSettings = itScreen->second; + + screenSettingsMap.erase(itScreen); + + pScreenSettings.setNull(); + + return VINF_SUCCESS; +} + +/** + * Destroys all screen settings objects of a particular map. + * + * @returns IPRT status code. + * @param screenSettingsMap Map to destroy screen settings objects for. + */ +int RecordingSettings::i_destroyAllScreenObj(RecordScreenSettingsMap &screenSettingsMap) +{ + LogFlowThisFuncEnter(); + + RecordScreenSettingsMap::iterator itScreen = screenSettingsMap.begin(); + while (itScreen != screenSettingsMap.end()) + { + /* Make sure to consume the pointer before the one of the + * iterator gets released. */ + ComObjPtr<RecordingScreenSettings> pScreenSettings = itScreen->second; + + screenSettingsMap.erase(itScreen); + + pScreenSettings.setNull(); + + itScreen = screenSettingsMap.begin(); + } + + Assert(screenSettingsMap.size() == 0); + return VINF_SUCCESS; +} + +/** + * Loads settings from the given settings. + * May be called once right after this object creation. + * + * @param data Capture settings to load from. + * + * @note Locks this object for writing. + */ +HRESULT RecordingSettings::i_loadSettings(const settings::RecordingSettings &data) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + + i_reset(); + + LogFlowThisFunc(("Data has %zu screens\n", data.mapScreens.size())); + + settings::RecordingScreenMap::const_iterator itScreen = data.mapScreens.begin(); + while (itScreen != data.mapScreens.end()) + { + int vrc = i_createScreenObj(m->mapScreenObj, + itScreen->first /* uScreenId */, itScreen->second /* Settings */); + if (RT_FAILURE(vrc)) + { + rc = E_OUTOFMEMORY; + break; + } + + ++itScreen; + } + + if (FAILED(rc)) + return rc; + + ComAssertComRC(rc); + Assert(m->mapScreenObj.size() == data.mapScreens.size()); + + // simply copy + m->bd.assignCopy(&data); + + LogFlowThisFunc(("Returning %Rhrc\n", rc)); + return rc; +} + +/** + * Resets the internal object state by destroying all screen settings objects. + */ +void RecordingSettings::i_reset(void) +{ + LogFlowThisFuncEnter(); + + i_destroyAllScreenObj(m->mapScreenObj); + m->bd->mapScreens.clear(); +} + +/** + * Saves settings to the given settings. + * + * @param data Where to store the capture settings to. + * + * @note Locks this object for reading. + */ +HRESULT RecordingSettings::i_saveSettings(settings::RecordingSettings &data) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AssertPtr(m->pMachine); + ComPtr<IGraphicsAdapter> pGraphicsAdapter; + m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam()); + ULONG cMonitors = 0; + if (!pGraphicsAdapter.isNull()) + pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors); + + int rc2 = i_syncToMachineDisplays(cMonitors); + AssertRC(rc2); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + data = *m->bd.data(); + + settings::RecordingScreenMap::iterator itScreen = data.mapScreens.begin(); + while (itScreen != data.mapScreens.end()) + { + /* Store relative path of capture file if possible. */ + m->pMachine->i_copyPathRelativeToMachine(itScreen->second.File.strName /* Source */, + itScreen->second.File.strName /* Target */); + ++itScreen; + } + + LogFlowThisFuncLeave(); + return S_OK; +} + +void RecordingSettings::i_rollback() +{ + /* sanity */ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + m->bd.rollback(); +} + +void RecordingSettings::i_commit() +{ + /* sanity */ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + /* sanity too */ + AutoCaller peerCaller(m->pPeer); + AssertComRCReturnVoid(peerCaller.rc()); + + /* lock both for writing since we modify both (mPeer is "master" so locked + * first) */ + AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS); + + if (m->bd.isBackedUp()) + { + m->bd.commit(); + if (m->pPeer) + { + /* attach new data to the peer and reshare it */ + m->pPeer->m->bd.attach(m->bd); + } + } +} + +void RecordingSettings::i_copyFrom(RecordingSettings *aThat) +{ + AssertReturnVoid(aThat != NULL); + + /* sanity */ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + /* sanity too */ + AutoCaller thatCaller(aThat); + AssertComRCReturnVoid(thatCaller.rc()); + + /* peer is not modified, lock it for reading (aThat is "master" so locked + * first) */ + AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS); + AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS); + + /* this will back up current data */ + m->bd.assignCopy(aThat->m->bd); +} + +void RecordingSettings::i_applyDefaults(void) +{ + /* sanity */ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AssertPtr(m->pMachine); + ComPtr<IGraphicsAdapter> pGraphicsAdapter; + m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam()); + ULONG cMonitors = 0; + if (!pGraphicsAdapter.isNull()) + pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Initialize default capturing settings here. */ + m->bd->fEnabled = false; + + /* First, do a reset so that all internal screen settings objects are destroyed. */ + i_reset(); + /* Second, sync (again) to configured machine displays to (re-)create screen settings objects. */ + i_syncToMachineDisplays(cMonitors); +} + +/** + * Returns the full path to the default video capture file. + */ +int RecordingSettings::i_getDefaultFilename(Utf8Str &strFile, bool fWithFileExtension) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + strFile = m->pMachine->i_getSettingsFileFull(); // path/to/machinesfolder/vmname/vmname.vbox + strFile.stripSuffix(); // path/to/machinesfolder/vmname/vmname + if (fWithFileExtension) + strFile.append(".webm"); // path/to/machinesfolder/vmname/vmname.webm + + return VINF_SUCCESS; +} + +/** + * Determines whether the recording settings currently can be changed or not. + * + * @returns \c true if the settings can be changed, \c false if not. + */ +bool RecordingSettings::i_canChangeSettings(void) +{ + AutoAnyStateDependency adep(m->pMachine); + if (FAILED(adep.rc())) + return false; + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Only allow settings to be changed when recording is disabled when the machine is running. */ + if ( Global::IsOnline(adep.machineState()) + && m->bd->fEnabled) + { + return false; + } + + return true; +} + +/** + * Gets called when the machine object needs to know that the recording settings + * have been changed. + */ +void RecordingSettings::i_onSettingsChanged(void) +{ + LogFlowThisFuncEnter(); + + AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); + m->pMachine->i_setModified(Machine::IsModified_Recording); + mlock.release(); + + LogFlowThisFuncLeave(); +} + +/** + * Synchronizes the screen settings (COM) objects and configuration data + * to the number of the machine's configured displays. + */ +int RecordingSettings::i_syncToMachineDisplays(uint32_t cMonitors) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("cMonitors=%RU32\n", cMonitors)); + LogFlowThisFunc(("Data screen count = %zu, COM object count = %zu\n", m->bd->mapScreens.size(), m->mapScreenObj.size())); + + /* If counts match, take a shortcut. */ + if (cMonitors == m->mapScreenObj.size()) + return VINF_SUCCESS; + + /* Create all new screen settings objects which are not there yet. */ + for (ULONG i = 0; i < cMonitors; i++) + { + if (m->mapScreenObj.find(i) == m->mapScreenObj.end()) + { + settings::RecordingScreenMap::const_iterator itScreen = m->bd->mapScreens.find(i); + if (itScreen == m->bd->mapScreens.end()) + { + settings::RecordingScreenSettings defaultScreenSettings; /* Apply default settings. */ + m->bd->mapScreens[i] = defaultScreenSettings; + } + + int vrc2 = i_createScreenObj(m->mapScreenObj, i /* Screen ID */, m->bd->mapScreens[i]); + AssertRC(vrc2); + } + } + + /* Remove all left over screen settings objects which are not needed anymore. */ + const ULONG cSettings = (ULONG)m->mapScreenObj.size(); + for (ULONG i = cMonitors; i < cSettings; i++) + { + m->bd->mapScreens.erase(i); + int vrc2 = i_destroyScreenObj(m->mapScreenObj, i /* Screen ID */); + AssertRC(vrc2); + } + + Assert(m->mapScreenObj.size() == cMonitors); + Assert(m->bd->mapScreens.size() == cMonitors); + + LogFlowThisFuncLeave(); + return VINF_SUCCESS; +} + |