/********************************************************************** Audacity: A Digital Audio Editor PluginManager.cpp Leland Lucius *******************************************************************//*! \file PluginManager.cpp \brief ************************************************************************//** \class PluginManager \brief PluginManager maintains a list of all plug ins. That covers modules, effects, generators, analysis-effects, commands. It also has functions for shared and private configs - which need to move out. *****************************************************************************/ #include "Audacity.h" #include "PluginManager.h" #include "Experimental.h" #include #include // for wxUSE_* macros #include #include #include #include #include #include #include #include #include #include #include #include #include "audacity/EffectInterface.h" #include "FileNames.h" #include "ModuleManager.h" #include "PlatformCompatibility.h" #include "Prefs.h" #include "ShuttleGui.h" #include "wxFileNameWrapper.h" #include "widgets/AudacityMessageBox.h" #include "widgets/ProgressDialog.h" #include // ============================================================================ // // // // ============================================================================ #if wxUSE_ACCESSIBILITY #include "widgets/WindowAccessible.h" class CheckListAx final : public WindowAccessible { public: CheckListAx(wxListCtrl * window); virtual ~ CheckListAx(); // Retrieves the address of an IDispatch interface for the specified child. // All objects must support this property. wxAccStatus GetChild( int childId, wxAccessible **child ) override; // Gets the number of children. wxAccStatus GetChildCount( int *childCount ) override; // Gets the default action for this object (0) or > 0 (the action for a child). // Return wxACC_OK even if there is no action. actionName is the action, or the empty // string if there is no action. // The retrieved string describes the action that is performed on an object, // not what the object does as a result. For example, a toolbar button that prints // a document has a default action of "Press" rather than "Prints the current document." wxAccStatus GetDefaultAction( int childId, wxString *actionName ) override; // Returns the description for this object or a child. wxAccStatus GetDescription( int childId, wxString *description ) override; // Gets the window with the keyboard focus. // If childId is 0 and child is NULL, no object in // this subhierarchy has the focus. // If this object has the focus, child should be 'this'. wxAccStatus GetFocus( int *childId, wxAccessible **child ) override; // Returns help text for this object or a child, similar to tooltip text. wxAccStatus GetHelpText( int childId, wxString *helpText ) override; // Returns the keyboard shortcut for this object or child. // Return e.g. ALT+K wxAccStatus GetKeyboardShortcut( int childId, wxString *shortcut ) override; // Returns the rectangle for this object (id = 0) or a child element (id > 0). // rect is in screen coordinates. wxAccStatus GetLocation( wxRect& rect, int elementId ) override; // Gets the name of the specified object. wxAccStatus GetName( int childId, wxString *name ) override; // Returns a role constant. wxAccStatus GetRole( int childId, wxAccRole *role ) override; // Gets a variant representing the selected children // of this object. // Acceptable values: // - a null variant (IsNull() returns TRUE) // - a list variant (GetType() == wxT("list")) // - an integer representing the selected child element, // or 0 if this object is selected (GetType() == wxT("long")) // - a "void*" pointer to a wxAccessible child object wxAccStatus GetSelections( wxVariant *selections ) override; // Returns a state constant. wxAccStatus GetState( int childId, long* state ) override; // Returns a localized string representing the value for the object // or child. wxAccStatus GetValue( int childId, wxString *strValue ) override; void SetSelected( int item, bool focused = true ); private: wxListCtrl *mParent; int mLastId; }; CheckListAx::CheckListAx( wxListCtrl * window ) : WindowAccessible( window ) { mParent = window; mLastId = -1; } CheckListAx::~CheckListAx() { } void CheckListAx::SetSelected( int item, bool focused ) { if (mLastId != -1) { NotifyEvent( wxACC_EVENT_OBJECT_SELECTIONREMOVE, mParent, wxOBJID_CLIENT, mLastId ); mLastId = -1; } if (item != -1) { if (focused) { NotifyEvent( wxACC_EVENT_OBJECT_FOCUS, mParent, wxOBJID_CLIENT, item + 1 ); } NotifyEvent( wxACC_EVENT_OBJECT_SELECTION, mParent, wxOBJID_CLIENT, item + 1 ); mLastId = item + 1; } } // Retrieves the address of an IDispatch interface for the specified child. // All objects must support this property. wxAccStatus CheckListAx::GetChild( int childId, wxAccessible** child ) { if( childId == wxACC_SELF ) { *child = this; } else { *child = NULL; } return wxACC_OK; } // Gets the number of children. wxAccStatus CheckListAx::GetChildCount( int *childCount ) { *childCount = mParent->GetItemCount(); return wxACC_OK; } // Gets the default action for this object (0) or > 0 (the action for a child). // Return wxACC_OK even if there is no action. actionName is the action, or the empty // string if there is no action. // The retrieved string describes the action that is performed on an object, // not what the object does as a result. For example, a toolbar button that prints // a document has a default action of "Press" rather than "Prints the current document." wxAccStatus CheckListAx::GetDefaultAction( int WXUNUSED(childId), wxString *actionName ) { actionName->clear(); return wxACC_OK; } // Returns the description for this object or a child. wxAccStatus CheckListAx::GetDescription( int WXUNUSED(childId), wxString *description ) { description->clear(); return wxACC_OK; } // Gets the window with the keyboard focus. // If childId is 0 and child is NULL, no object in // this subhierarchy has the focus. // If this object has the focus, child should be 'this'. wxAccStatus CheckListAx::GetFocus( int *childId, wxAccessible **child ) { *childId = 0; *child = this; return wxACC_OK; } // Returns help text for this object or a child, similar to tooltip text. wxAccStatus CheckListAx::GetHelpText( int WXUNUSED(childId), wxString *helpText ) { helpText->clear(); return wxACC_OK; } // Returns the keyboard shortcut for this object or child. // Return e.g. ALT+K wxAccStatus CheckListAx::GetKeyboardShortcut( int WXUNUSED(childId), wxString *shortcut ) { shortcut->clear(); return wxACC_OK; } // Returns the rectangle for this object (id = 0) or a child element (id > 0). // rect is in screen coordinates. wxAccStatus CheckListAx::GetLocation( wxRect& rect, int elementId ) { if( elementId == wxACC_SELF ) { rect = mParent->GetRect(); rect.SetPosition( mParent->GetParent()->ClientToScreen( rect.GetPosition() ) ); } else { if( elementId <= mParent->GetItemCount() ) { mParent->GetItemRect( elementId - 1, rect, wxLIST_RECT_LABEL ); rect.SetPosition( mParent->ClientToScreen( rect.GetPosition() ) ); } } return wxACC_OK; } // Gets the name of the specified object. wxAccStatus CheckListAx::GetName( int WXUNUSED(childId), wxString *name ) { *name = mParent->GetName(); return wxACC_OK; } // Returns a role constant. wxAccStatus CheckListAx::GetRole( int childId, wxAccRole *role ) { if( childId == wxACC_SELF ) { *role = wxROLE_SYSTEM_LIST; } else { *role = wxROLE_SYSTEM_LISTITEM; } return wxACC_OK; } // Gets a variant representing the selected children // of this object. // Acceptable values: // - a null variant (IsNull() returns TRUE) // - a list variant (GetType() == wxT("list")) // - an integer representing the selected child element, // or 0 if this object is selected (GetType() == wxT("long")) // - a "void*" pointer to a wxAccessible child object wxAccStatus CheckListAx::GetSelections( wxVariant * WXUNUSED(selections) ) { return wxACC_NOT_IMPLEMENTED; } // Returns a state constant. wxAccStatus CheckListAx::GetState( int childId, long *pState ) { int flag = wxACC_STATE_SYSTEM_FOCUSABLE; if( childId == wxACC_SELF ) { flag |= wxACC_STATE_SYSTEM_FOCUSED; } else { wxListItem item; item.SetId( childId - 1 ); item.SetState( wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED ); item.SetMask( wxLIST_MASK_STATE ); if( mParent->GetItem( item ) ) { flag |= wxACC_STATE_SYSTEM_SELECTABLE; long state = item.GetState(); if( state & wxLIST_STATE_FOCUSED ) { flag |= wxACC_STATE_SYSTEM_FOCUSED; } if( state & wxLIST_STATE_SELECTED ) { flag |= wxACC_STATE_SYSTEM_SELECTED; } } } *pState = flag; return wxACC_OK; } // Returns a localized string representing the value for the object // or child. wxAccStatus CheckListAx::GetValue( int childId, wxString *strValue ) { if( childId == 0 ) { return wxACC_OK; } else { *strValue = mParent->GetItemText( childId - 1 ); } return wxACC_OK; } #endif // ============================================================================ // // // // ============================================================================ enum { STATE_Enabled, STATE_Disabled, STATE_New, STATE_COUNT }; struct ItemData { std::vector plugs; wxString name; PluginPath path; int state; bool valid; int nameWidth; int pathWidth; int stateWidth; }; using ItemDataMap = std::unordered_map; enum { ID_ShowAll = 10000, ID_ShowEnabled, ID_ShowDisabled, ID_ShowNew, ID_List, ID_ClearAll, ID_SelectAll, ID_Enable, ID_Disable, }; enum { COL_Name, COL_State, COL_Path, COL_COUNT }; class PluginRegistrationDialog final : public wxDialogWrapper { public: // constructors and destructors PluginRegistrationDialog(wxWindow *parent, EffectType type); private: void Populate(); void PopulateOrExchange(ShuttleGui & S); void RegenerateEffectsList(int iShowWhat); void SetState(int i, bool toggle, bool state = true); static int wxCALLBACK SortCompare(long item1, long item2, long sortData); int SortCompare(ItemData *item1, ItemData *item2); void OnChangedVisibility(wxCommandEvent & evt); void OnSort(wxListEvent & evt); void OnListChar(wxKeyEvent & evt); void OnOK(wxCommandEvent & evt); void OnCancel(wxCommandEvent & evt); void OnSelectAll(wxCommandEvent & evt); void OnClearAll(wxCommandEvent & evt); void OnEnable(wxCommandEvent & evt); void OnDisable(wxCommandEvent & evt); private: ModuleInterface *mMod; EffectType mType; int mFilter; wxArrayString mStates; ItemDataMap mItems; int mSortColumn; int mSortDirection; PluginPath mLongestPath; wxListCtrl *mEffects; #if wxUSE_ACCESSIBILITY CheckListAx *mAx; #endif DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(PluginRegistrationDialog, wxDialogWrapper) EVT_LIST_COL_CLICK(ID_List, PluginRegistrationDialog::OnSort) EVT_BUTTON(wxID_OK, PluginRegistrationDialog::OnOK) EVT_BUTTON(wxID_CANCEL, PluginRegistrationDialog::OnCancel) EVT_BUTTON(ID_ClearAll, PluginRegistrationDialog::OnClearAll) EVT_BUTTON(ID_SelectAll, PluginRegistrationDialog::OnSelectAll) EVT_BUTTON(ID_Enable, PluginRegistrationDialog::OnEnable) EVT_BUTTON(ID_Disable, PluginRegistrationDialog::OnDisable) EVT_RADIOBUTTON(ID_ShowAll, PluginRegistrationDialog::OnChangedVisibility) EVT_RADIOBUTTON(ID_ShowEnabled, PluginRegistrationDialog::OnChangedVisibility) EVT_RADIOBUTTON(ID_ShowDisabled, PluginRegistrationDialog::OnChangedVisibility) EVT_RADIOBUTTON(ID_ShowNew, PluginRegistrationDialog::OnChangedVisibility) END_EVENT_TABLE() PluginRegistrationDialog::PluginRegistrationDialog(wxWindow *parent, EffectType type) : wxDialogWrapper(parent, wxID_ANY, _("Manage Plug-ins"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { mType = type; mEffects = NULL; SetName(GetTitle()); mStates.resize(STATE_COUNT); mStates[STATE_Enabled] = _("Enabled"); mStates[STATE_Disabled] = _("Disabled"); mStates[STATE_New] = _("New"); mSortColumn = COL_Name; mSortDirection = 1; Populate(); } void PluginRegistrationDialog::Populate() { //------------------------- Main section -------------------- ShuttleGui S(this, eIsCreating); PopulateOrExchange(S); // ----------------------- End of main section -------------- } /// Defines the dialog and does data exchange with it. void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S) { S.StartVerticalLay(true); { /*i18n-hint: The dialog shows a list of plugins with check-boxes beside each one.*/ // S.StartStatic(_("Effects"), true); S.StartVerticalLay(); { S.StartHorizontalLay(wxEXPAND, 0); { S.StartHorizontalLay(wxALIGN_LEFT, 0); { S.AddPrompt(_("Select effects, click the Enable or Disable button, then click OK.")); } S.EndHorizontalLay(); S.StartHorizontalLay(wxCENTER, 1); { S.AddSpace(1); } S.EndHorizontalLay(); S.StartHorizontalLay(wxALIGN_NOT | wxALIGN_LEFT, 0); { wxRadioButton* rb; /* i18n-hint: This is before radio buttons selecting which effects to show */ S.AddPrompt(_("Show:")); /* i18n-hint: Radio button to show all effects */ rb = S.Id(ID_ShowAll).AddRadioButton(_("&All")); #if wxUSE_ACCESSIBILITY // so that name can be set on a standard control rb->SetAccessible(safenew WindowAccessible(rb)); #endif rb->SetName(_("Show all")); /* i18n-hint: Radio button to show just the currently disabled effects */ rb = S.Id(ID_ShowDisabled).AddRadioButtonToGroup(_("D&isabled")); #if wxUSE_ACCESSIBILITY // so that name can be set on a standard control rb->SetAccessible(safenew WindowAccessible(rb)); #endif rb->SetName(_("Show disabled")); /* i18n-hint: Radio button to show just the currently enabled effects */ rb = S.Id(ID_ShowEnabled).AddRadioButtonToGroup(_("E&nabled")); #if wxUSE_ACCESSIBILITY // so that name can be set on a standard control rb->SetAccessible(safenew WindowAccessible(rb)); #endif rb->SetName(_("Show enabled")); /* i18n-hint: Radio button to show just the newly discovered effects */ rb = S.Id(ID_ShowNew).AddRadioButtonToGroup(_("Ne&w")); #if wxUSE_ACCESSIBILITY // so that name can be set on a standard control rb->SetAccessible(safenew WindowAccessible(rb)); #endif rb->SetName(_("Show new")); } S.EndHorizontalLay(); } S.EndHorizontalLay(); S.SetStyle(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES ); mEffects = S.Id(ID_List).AddListControlReportMode(); mEffects->Bind(wxEVT_KEY_DOWN, &PluginRegistrationDialog::OnListChar, this); #if wxUSE_ACCESSIBILITY mEffects->SetAccessible(mAx = safenew CheckListAx(mEffects)); #endif mEffects->InsertColumn(COL_Name, _("Name")); mEffects->InsertColumn(COL_State, _("State")); mEffects->InsertColumn(COL_Path, _("Path")); S.StartHorizontalLay(wxALIGN_LEFT | wxEXPAND, 0); { S.Id(ID_SelectAll).AddButton(_("&Select All")); S.Id(ID_ClearAll).AddButton(_("C&lear All")); S.StartHorizontalLay(wxALIGN_CENTER); { S.AddSpace(1); } S.EndHorizontalLay(); S.Id(ID_Enable).AddButton(_("&Enable")); S.Id(ID_Disable).AddButton(_("&Disable")); } S.EndHorizontalLay(); } // S.EndStatic(); S.EndVerticalLay(); S.AddStandardButtons(eOkButton | eCancelButton); } S.EndVerticalLay(); std::vector colWidths; for (int i = 0, cnt = mEffects->GetColumnCount(); i < cnt; i++) { colWidths.push_back(0); } for (int i = 0, cnt = mStates.size(); i < cnt; i++) { int x; mEffects->GetTextExtent(mStates[i], &x, NULL); colWidths[COL_State] = wxMax(colWidths[COL_State], x + 4); // 2 pixel margin on each side } PluginManager & pm = PluginManager::Get(); for (PluginMap::iterator iter = pm.mPlugins.begin(); iter != pm.mPlugins.end(); ++iter) { PluginDescriptor & plug = iter->second; PluginType plugType = plug.GetPluginType(); if (plugType != PluginTypeEffect && plugType != PluginTypeStub) { continue; } const auto &path = plug.GetPath(); ItemData & item = mItems[path]; // will create NEW entry item.plugs.push_back(&plug); item.path = path; item.state = plug.IsEnabled() ? STATE_Enabled : STATE_Disabled; item.valid = plug.IsValid(); if (plugType == PluginTypeEffect) { item.name = plug.GetSymbol().Translation(); } // This is not right and will not work when other plugin types are added. // But it's presumed that the plugin manager dialog will be fully developed // by then. else if (plugType == PluginTypeStub) { wxFileName fname { path }; item.name = fname.GetName().Trim(false).Trim(true); if (!item.valid) { item.state = STATE_New; } } int x; mEffects->GetTextExtent(item.name, &x, NULL); colWidths[COL_Name] = wxMax(colWidths[COL_Name], x); mEffects->GetTextExtent(item.path, &x, NULL); if (x > colWidths[COL_Path]) { mLongestPath = item.path; } colWidths[COL_Path] = wxMax(colWidths[COL_Path], x); } wxRect r = wxGetClientDisplayRect(); int maxW = 0; for (int i = 0, cnt = mEffects->GetColumnCount(); i < cnt; i++) { int w = colWidths[i] + /* fudge */ 10; mEffects->SetColumnWidth(i, w); maxW += w; } // Keep dialog from getting too wide int w = r.GetWidth() - (GetClientSize().GetWidth() - mEffects->GetSize().GetWidth()); mEffects->SetSizeHints(wxSize(wxMin(maxW, w), 200), wxSize(w, -1)); RegenerateEffectsList(ID_ShowAll); Layout(); Fit(); wxSize sz = GetSize(); sz.SetWidth(wxMin(sz.GetWidth(), r.GetWidth())); sz.SetHeight(wxMin(sz.GetHeight(), r.GetHeight())); SetMinSize(sz); // Parent window is usually not there yet, so centre on screen rather than on parent. CenterOnScreen(); if (mEffects->GetItemCount() > 0) { // Make sure first item is selected/focused. mEffects->SetFocus(); mEffects->SetItemState(0, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED); #if wxUSE_ACCESSIBILITY mAx->SetSelected(0); #endif } } void PluginRegistrationDialog::RegenerateEffectsList(int filter) { mFilter = filter; mEffects->DeleteAllItems(); int i = 0; for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) { ItemData & item = iter->second; bool add = false; switch (mFilter) { case ID_ShowAll: add = true; break; case ID_ShowNew: if (item.state == STATE_New) { add = true; } break; case ID_ShowEnabled: if (item.state == STATE_Enabled) { add = true; } break; case ID_ShowDisabled: if (item.state == STATE_Disabled) { add = true; } break; } if (add) { mEffects->InsertItem(i, item.name); mEffects->SetItem(i, COL_State, mStates[item.state]); mEffects->SetItem(i, COL_Path, item.path); mEffects->SetItemPtrData(i, (wxUIntPtr) &item); ++i; } } mEffects->SortItems(SortCompare, (wxUIntPtr) this); if (mEffects->GetItemCount() > 0) { // Make sure first item is selected/focused. // mEffects->SetFocus(); mEffects->SetItemState(0, wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED); #if wxUSE_ACCESSIBILITY mAx->SetSelected(0, false); #endif } } void PluginRegistrationDialog::SetState(int i, bool toggle, bool state) { wxListItem li; li.m_mask = wxLIST_MASK_DATA; li.m_itemId = i; mEffects->GetItem(li); ItemData *item = (ItemData *) li.m_data; // If changing the state of a "New" (stub) entry, then we mark it as valid // since it will either be registered if "Enabled" or ignored if "Disabled". if (item->state == STATE_New) { item->valid = true; } if (toggle) { item->state = item->state == STATE_Enabled ? STATE_Disabled : STATE_Enabled; } else { item->state = state; } if (mFilter == ID_ShowNew && item->state != STATE_New) { mEffects->DeleteItem(i); } else if (mFilter == ID_ShowDisabled && item->state != STATE_Disabled) { mEffects->DeleteItem(i); } else if (mFilter == ID_ShowEnabled && item->state != STATE_Enabled) { mEffects->DeleteItem(i); } else { mEffects->SetItem(i, COL_State, mStates[item->state]); #if wxUSE_ACCESSIBILITY mAx->SetSelected(i); #endif } } int wxCALLBACK PluginRegistrationDialog::SortCompare(long item1, long item2, long sortData) { PluginRegistrationDialog *dlg = (PluginRegistrationDialog *) sortData; ItemData *i1 = (ItemData *) item1; ItemData *i2 = (ItemData *) item2; return dlg->SortCompare(i1, i2); } int PluginRegistrationDialog::SortCompare(ItemData *item1, ItemData *item2) { // This function is a three-valued comparator wxString *str1; wxString *str2; switch (mSortColumn) { case COL_Name: str1 = &item1->name; str2 = &item2->name; break; case COL_State: str1 = &mStates[item1->state]; str2 = &mStates[item2->state]; break; case COL_Path: str1 = &item1->path; str2 = &item2->path; break; default: return 0; } #if defined(__WXMAC__) return str2->Cmp(*str1) * mSortDirection; #else return str1->Cmp(*str2) * mSortDirection; #endif } void PluginRegistrationDialog::OnChangedVisibility(wxCommandEvent & evt) { // Go and show the relevant items. RegenerateEffectsList(evt.GetId()); } void PluginRegistrationDialog::OnSort(wxListEvent & evt) { int col = evt.GetColumn(); if (col != mSortColumn) { mSortDirection = 1; } else { mSortDirection *= -1; } mSortColumn = col; mEffects->SortItems(SortCompare, (wxUIntPtr) this); } void PluginRegistrationDialog::OnListChar(wxKeyEvent & evt) { switch (evt.GetKeyCode()) { case WXK_SPACE: { int item = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED); if (item != wxNOT_FOUND) { SetState(item, true); } } break; case WXK_RETURN: // Don't know why wxListCtrls prevent default dialog action, // but they do, so handle it. EmulateButtonClickIfPresent(GetAffirmativeId()); break; default: evt.Skip(); break; } } void PluginRegistrationDialog::OnSelectAll(wxCommandEvent & WXUNUSED(evt)) { for (int i = 0, cnt = mEffects->GetItemCount(); i < cnt; i++) { mEffects->SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } } void PluginRegistrationDialog::OnClearAll(wxCommandEvent & WXUNUSED(evt)) { for (int i = 0, cnt = mEffects->GetItemCount(); i < cnt; i++) { mEffects->SetItemState(i, 0, wxLIST_STATE_SELECTED); } } void PluginRegistrationDialog::OnEnable(wxCommandEvent & WXUNUSED(evt)) { std::vector items; { long i = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); while (i != wxNOT_FOUND) { items.insert(items.begin(), i); i = mEffects->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); } } for (size_t i = 0, cnt = items.size(); i < cnt; i++) { SetState(items[i], false, STATE_Enabled); } } void PluginRegistrationDialog::OnDisable(wxCommandEvent & WXUNUSED(evt)) { std::vector items; { long i = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); while (i != wxNOT_FOUND) { items.insert(items.begin(), i); i = mEffects->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); } } for (size_t i = 0, cnt = items.size(); i < cnt; i++) { SetState(items[i], false, STATE_Disabled); } } void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) { PluginManager & pm = PluginManager::Get(); ModuleManager & mm = ModuleManager::Get(); int enableCount = 0; for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) { ItemData & item = iter->second; wxString path = item.path; if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) { enableCount++; } } wxString last3 = mLongestPath + wxT("\n") + mLongestPath + wxT("\n") + mLongestPath + wxT("\n"); wxString msg; msg.Printf(_("Enabling effects or commands:\n\n%s"), last3); // Make sure the progress dialog is deleted before we call EndModal() or // we will leave the project window in an unusable state on OSX. // See bug #1192. { ProgressDialog progress(GetTitle(), msg, pdlgHideStopButton); progress.CenterOnParent(); int i = 0; for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) { ItemData & item = iter->second; wxString path = item.path; if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) { last3 = last3.AfterFirst(wxT('\n')) + item.path + wxT("\n"); auto status = progress.Update(++i, enableCount, wxString::Format(_("Enabling effect or command:\n\n%s"), last3)); if (status == ProgressResult::Cancelled) { break; } wxString errMsgs; // Try to register the plugin via each provider until one succeeds for (size_t j = 0, cntj = item.plugs.size(); j < cntj; j++) { wxString errMsg; if (mm.RegisterEffectPlugin(item.plugs[j]->GetProviderID(), path, errMsg)) { for (size_t k = 0, cntk = item.plugs.size(); k < cntk; k++) { pm.mPlugins.erase(item.plugs[k]->GetProviderID() + wxT("_") + path); } // Bug 1893. We've found a provider that works. // Error messages from any that failed are no longer useful. errMsgs.clear(); break; } else { if (errMsgs.empty()) errMsgs += '\n'; errMsgs += errMsg; } } if (!errMsgs.empty()) AudacityMessageBox( wxString::Format( _("Effect or Command at %s failed to register:\n%s"), path, errMsgs ) ); } else if (item.state == STATE_New) { for (size_t j = 0, cnt = item.plugs.size(); j < cnt; j++) { item.plugs[j]->SetValid(false); } } else if (item.state != STATE_New) { for (size_t j = 0, cnt = item.plugs.size(); j < cnt; j++) { item.plugs[j]->SetEnabled(item.state == STATE_Enabled); item.plugs[j]->SetValid(item.valid); } } } pm.Save(); } EndModal(wxID_OK); } void PluginRegistrationDialog::OnCancel(wxCommandEvent & WXUNUSED(evt)) { EndModal(wxID_CANCEL); } /////////////////////////////////////////////////////////////////////////////// // // Plugindescriptor // /////////////////////////////////////////////////////////////////////////////// PluginDescriptor::PluginDescriptor() { mPluginType = PluginTypeNone; mEnabled = false; mValid = false; mInstance = NULL; mEffectType = EffectTypeNone; mEffectInteractive = false; mEffectDefault = false; mEffectLegacy = false; mEffectRealtime = false; mEffectAutomatable = false; } PluginDescriptor::~PluginDescriptor() { DeleteInstance(); } void PluginDescriptor::DeleteInstance() { if (mInstance) { ModuleManager::Get().DeleteInstance(GetProviderID(), mInstance); mInstance = nullptr; } } bool PluginDescriptor::IsInstantiated() const { return mInstance != NULL; } ComponentInterface *PluginDescriptor::GetInstance() { if (!mInstance) { if (GetPluginType() == PluginTypeModule) { mInstance = ModuleManager::Get().CreateProviderInstance(GetID(), GetPath()); } else { mInstance = ModuleManager::Get().CreateInstance(GetProviderID(), GetPath()); } } return mInstance; } void PluginDescriptor::SetInstance(ComponentInterface *instance) { if (mInstance && mInstance != instance) { // Be sure not to leak resources!! DeleteInstance(); } mInstance = instance; return; } PluginType PluginDescriptor::GetPluginType() const { return mPluginType; } const PluginID & PluginDescriptor::GetID() const { return mID; } const PluginID & PluginDescriptor::GetProviderID() const { return mProviderID; } const PluginPath & PluginDescriptor::GetPath() const { return mPath; } const ComponentInterfaceSymbol & PluginDescriptor::GetSymbol() const { return mSymbol; } wxString PluginDescriptor::GetUntranslatedVersion() const { return mVersion; } wxString PluginDescriptor::GetVendor() const { return mVendor; } bool PluginDescriptor::IsEnabled() const { return mEnabled; } bool PluginDescriptor::IsValid() const { return mValid; } void PluginDescriptor::SetPluginType(PluginType type) { mPluginType = type; } void PluginDescriptor::SetID(const PluginID & ID) { mID = ID; } void PluginDescriptor::SetProviderID(const PluginID & providerID) { mProviderID = providerID; } void PluginDescriptor::SetPath(const PluginPath & path) { mPath = path; } void PluginDescriptor::SetSymbol(const ComponentInterfaceSymbol & symbol) { mSymbol = symbol; } void PluginDescriptor::SetVersion(const wxString & version) { mVersion = version; } void PluginDescriptor::SetVendor(const wxString & vendor) { mVendor = vendor; } void PluginDescriptor::SetEnabled(bool enable) { mEnabled = enable; } void PluginDescriptor::SetValid(bool valid) { mValid = valid; } // Effects wxString PluginDescriptor::GetEffectFamily() const { return mEffectFamily; } EffectType PluginDescriptor::GetEffectType() const { return mEffectType; } bool PluginDescriptor::IsEffectInteractive() const { return mEffectInteractive; } bool PluginDescriptor::IsEffectDefault() const { return mEffectDefault; } bool PluginDescriptor::IsEffectLegacy() const { return mEffectLegacy; } bool PluginDescriptor::IsEffectRealtime() const { return mEffectRealtime; } bool PluginDescriptor::IsEffectAutomatable() const { return mEffectAutomatable; } void PluginDescriptor::SetEffectFamily(const wxString & family) { mEffectFamily = family; } void PluginDescriptor::SetEffectType(EffectType type) { mEffectType = type; } void PluginDescriptor::SetEffectInteractive(bool interactive) { mEffectInteractive = interactive; } void PluginDescriptor::SetEffectDefault(bool dflt) { mEffectDefault = dflt; } void PluginDescriptor::SetEffectLegacy(bool legacy) { mEffectLegacy = legacy; } void PluginDescriptor::SetEffectRealtime(bool realtime) { mEffectRealtime = realtime; } void PluginDescriptor::SetEffectAutomatable(bool automatable) { mEffectAutomatable = automatable; } // Importer const wxString & PluginDescriptor::GetImporterIdentifier() const { return mImporterIdentifier; } void PluginDescriptor::SetImporterIdentifier(const wxString & identifier) { mImporterIdentifier = identifier; } const wxString & PluginDescriptor::GetImporterFilterDescription() const { return mImporterFilterDesc; } void PluginDescriptor::SetImporterFilterDescription(const wxString & filterDesc) { mImporterFilterDesc = filterDesc; } const FileExtensions & PluginDescriptor::GetImporterExtensions() const { return mImporterExtensions; } void PluginDescriptor::SetImporterExtensions( FileExtensions extensions ) { mImporterExtensions = std::move( extensions ); } /////////////////////////////////////////////////////////////////////////////// // // PluginManager // /////////////////////////////////////////////////////////////////////////////// // Registry has the list of plug ins #define REGVERKEY wxString(wxT("/pluginregistryversion")) #define REGVERCUR wxString(wxT("1.1")) #define REGROOT wxString(wxT("/pluginregistry/")) // Settings has the values of the plug in settings. #define SETVERKEY wxString(wxT("/pluginsettingsversion")) #define SETVERCUR wxString(wxT("1.0")) #define SETROOT wxString(wxT("/pluginsettings/")) #define KEY_ID wxT("ID") #define KEY_PATH wxT("Path") #define KEY_SYMBOL wxT("Symbol") #define KEY_NAME wxT("Name") #define KEY_VENDOR wxT("Vendor") #define KEY_VERSION wxT("Version") #define KEY_DESCRIPTION wxT("Description") #define KEY_LASTUPDATED wxT("LastUpdated") #define KEY_ENABLED wxT("Enabled") #define KEY_VALID wxT("Valid") #define KEY_PROVIDERID wxT("ProviderID") #define KEY_EFFECTTYPE wxT("EffectType") #define KEY_EFFECTFAMILY wxT("EffectFamily") #define KEY_EFFECTDEFAULT wxT("EffectDefault") #define KEY_EFFECTINTERACTIVE wxT("EffectInteractive") #define KEY_EFFECTREALTIME wxT("EffectRealtime") #define KEY_EFFECTAUTOMATABLE wxT("EffectAutomatable") #define KEY_EFFECTTYPE_NONE wxT("None") #define KEY_EFFECTTYPE_ANALYZE wxT("Analyze") #define KEY_EFFECTTYPE_GENERATE wxT("Generate") #define KEY_EFFECTTYPE_PROCESS wxT("Process") #define KEY_EFFECTTYPE_TOOL wxT("Tool") #define KEY_EFFECTTYPE_HIDDEN wxT("Hidden") #define KEY_IMPORTERIDENT wxT("ImporterIdent") #define KEY_IMPORTERFILTER wxT("ImporterFilter") #define KEY_IMPORTEREXTENSIONS wxT("ImporterExtensions") // ============================================================================ // // PluginManagerInterface implementation // // ============================================================================ const PluginID &PluginManagerInterface::DefaultRegistrationCallback( ModuleInterface *provider, ComponentInterface *pInterface ) { EffectDefinitionInterface * pEInterface = dynamic_cast(pInterface); if( pEInterface ) return PluginManager::Get().RegisterPlugin(provider, pEInterface, PluginTypeEffect); ComponentInterface * pCInterface = dynamic_cast(pInterface); if( pCInterface ) return PluginManager::Get().RegisterPlugin(provider, pCInterface); static wxString empty; return empty; } const PluginID &PluginManagerInterface::AudacityCommandRegistrationCallback( ModuleInterface *provider, ComponentInterface *pInterface ) { ComponentInterface * pCInterface = dynamic_cast(pInterface); if( pCInterface ) return PluginManager::Get().RegisterPlugin(provider, pCInterface); static wxString empty; return empty; } bool PluginManager::IsPluginRegistered(const PluginPath &path) { for (PluginMap::iterator iter = mPlugins.begin(); iter != mPlugins.end(); ++iter) { if (iter->second.GetPath() == path) { return true; } } return false; } const PluginID & PluginManager::RegisterPlugin(ModuleInterface *module) { PluginDescriptor & plug = CreatePlugin(GetID(module), module, PluginTypeModule); plug.SetEnabled(true); plug.SetValid(true); return plug.GetID(); } const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, ComponentInterface *command) { PluginDescriptor & plug = CreatePlugin(GetID(command), command, (PluginType)PluginTypeAudacityCommand); plug.SetProviderID(PluginManager::GetID(provider)); plug.SetEnabled(true); plug.SetValid(true); return plug.GetID(); } const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, EffectDefinitionInterface *effect, int type) { PluginDescriptor & plug = CreatePlugin(GetID(effect), effect, (PluginType)type); plug.SetProviderID(PluginManager::GetID(provider)); plug.SetEffectType(effect->GetClassification()); plug.SetEffectFamily(effect->GetFamily().Internal()); plug.SetEffectInteractive(effect->IsInteractive()); plug.SetEffectDefault(effect->IsDefault()); plug.SetEffectRealtime(effect->SupportsRealtime()); plug.SetEffectAutomatable(effect->SupportsAutomation()); plug.SetEnabled(true); plug.SetValid(true); return plug.GetID(); } const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, ImporterInterface *importer) { PluginDescriptor & plug = CreatePlugin(GetID(importer), importer, PluginTypeImporter); plug.SetProviderID(PluginManager::GetID(provider)); plug.SetImporterIdentifier(importer->GetPluginStringID()); plug.SetImporterFilterDescription(importer->GetPluginFormatDescription()); plug.SetImporterExtensions(importer->GetSupportedExtensions()); return plug.GetID(); } void PluginManager::FindFilesInPathList(const wxString & pattern, const FilePaths & pathList, FilePaths & files, bool directories) { wxLogNull nolog; // Why bother... if (pattern.empty()) { return; } // TODO: We REALLY need to figure out the "Audacity" plug-in path(s) FilePaths paths; // Add the "per-user" plug-ins directory { const wxFileName &ff = FileNames::PlugInDir(); paths.push_back(ff.GetFullPath()); } // Add the "Audacity" plug-ins directory wxFileName ff = PlatformCompatibility::GetExecutablePath(); #if defined(__WXMAC__) // Path ends for example in "Audacity.app/Contents/MacOSX" //ff.RemoveLastDir(); //ff.RemoveLastDir(); // just remove the MacOSX part. ff.RemoveLastDir(); #endif ff.AppendDir(wxT("plug-ins")); paths.push_back(ff.GetPath()); // Weed out duplicates for (const auto &filePath : pathList) { ff = filePath; const wxString path{ ff.GetFullPath() }; if (paths.Index(path, wxFileName::IsCaseSensitive()) == wxNOT_FOUND) { paths.push_back(path); } } // Find all matching files in each path for (size_t i = 0, cnt = paths.size(); i < cnt; i++) { ff = paths[i] + wxFILE_SEP_PATH + pattern; wxDir::GetAllFiles(ff.GetPath(), &files, ff.GetFullName(), directories ? wxDIR_DEFAULT : wxDIR_FILES); } return; } bool PluginManager::HasSharedConfigGroup(const PluginID & ID, const RegistryPath & group) { return HasGroup(SharedGroup(ID, group)); } bool PluginManager::GetSharedConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths & subgroups) { return GetSubgroups(SharedGroup(ID, group), subgroups); } bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval) { return GetConfig(SharedKey(ID, group, key), value, defval); } bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval) { return GetConfig(SharedKey(ID, group, key), value, defval); } bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval) { return GetConfig(SharedKey(ID, group, key), value, defval); } bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval) { return GetConfig(SharedKey(ID, group, key), value, defval); } bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval) { return GetConfig(SharedKey(ID, group, key), value, defval); } bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) { return SetConfig(SharedKey(ID, group, key), value); } bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) { return SetConfig(SharedKey(ID, group, key), value); } bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) { return SetConfig(SharedKey(ID, group, key), value); } bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) { return SetConfig(SharedKey(ID, group, key), value); } bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) { return SetConfig(SharedKey(ID, group, key), value); } bool PluginManager::RemoveSharedConfigSubgroup(const PluginID & ID, const RegistryPath & group) { bool result = GetSettings()->DeleteGroup(SharedGroup(ID, group)); if (result) { GetSettings()->Flush(); } return result; } bool PluginManager::RemoveSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) { bool result = GetSettings()->DeleteEntry(SharedKey(ID, group, key)); if (result) { GetSettings()->Flush(); } return result; } bool PluginManager::HasPrivateConfigGroup(const PluginID & ID, const RegistryPath & group) { return HasGroup(PrivateGroup(ID, group)); } bool PluginManager::GetPrivateConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths & subgroups) { return GetSubgroups(PrivateGroup(ID, group), subgroups); } bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval) { return GetConfig(PrivateKey(ID, group, key), value, defval); } bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval) { return GetConfig(PrivateKey(ID, group, key), value, defval); } bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval) { return GetConfig(PrivateKey(ID, group, key), value, defval); } bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval) { return GetConfig(PrivateKey(ID, group, key), value, defval); } bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval) { return GetConfig(PrivateKey(ID, group, key), value, defval); } bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) { return SetConfig(PrivateKey(ID, group, key), value); } bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) { return SetConfig(PrivateKey(ID, group, key), value); } bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) { return SetConfig(PrivateKey(ID, group, key), value); } bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) { return SetConfig(PrivateKey(ID, group, key), value); } bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) { return SetConfig(PrivateKey(ID, group, key), value); } bool PluginManager::RemovePrivateConfigSubgroup(const PluginID & ID, const RegistryPath & group) { bool result = GetSettings()->DeleteGroup(PrivateGroup(ID, group)); if (result) { GetSettings()->Flush(); } return result; } bool PluginManager::RemovePrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) { bool result = GetSettings()->DeleteEntry(PrivateKey(ID, group, key)); if (result) { GetSettings()->Flush(); } return result; } // ============================================================================ // // PluginManager // // ============================================================================ // The one and only PluginManager std::unique_ptr PluginManager::mInstance{}; // ---------------------------------------------------------------------------- // Creation/Destruction // ---------------------------------------------------------------------------- PluginManager::PluginManager() { mSettings = NULL; } PluginManager::~PluginManager() { // Ensure termination (harmless if already done) Terminate(); } // ---------------------------------------------------------------------------- // PluginManager implementation // ---------------------------------------------------------------------------- // ============================================================================ // // Return reference to singleton // // (Thread-safe...no active threading during construction or after destruction) // ============================================================================ PluginManager & PluginManager::Get() { if (!mInstance) { mInstance.reset(safenew PluginManager); } return *mInstance; } void PluginManager::Initialize() { // Always load the registry first Load(); // Then look for providers (they may autoregister plugins) ModuleManager::Get().DiscoverProviders(); // And finally check for updates #ifndef EXPERIMENTAL_EFFECT_MANAGEMENT CheckForUpdates(); #else const bool kFast = true; CheckForUpdates( kFast ); #endif } void PluginManager::Terminate() { // Get rid of all non-module plugins first PluginMap::iterator iter = mPlugins.begin(); while (iter != mPlugins.end()) { PluginDescriptor & plug = iter->second; if (plug.GetPluginType() == PluginTypeEffect) { mPlugins.erase(iter++); continue; } ++iter; } // Now get rid of the modules iter = mPlugins.begin(); while (iter != mPlugins.end()) { mPlugins.erase(iter++); } } bool PluginManager::DropFile(const wxString &fileName) { auto &mm = ModuleManager::Get(); const wxFileName src{ fileName }; for (const PluginDescriptor *plug = GetFirstPlugin(PluginTypeModule); plug; plug = GetNextPlugin(PluginTypeModule)) { auto module = static_cast (mm.CreateProviderInstance(plug->GetID(), plug->GetPath())); if (! module) continue; const auto &ff = module->InstallPath(); const auto &extensions = module->GetFileExtensions(); if ( !ff.empty() && extensions.Index(src.GetExt(), false) != wxNOT_FOUND ) { wxString errMsg; // Do dry-run test of the file format unsigned nPlugIns = module->DiscoverPluginsAtPath(fileName, errMsg, {}); if (nPlugIns) { // File contents are good for this module, so check no others. // All branches of this block return true, even in case of // failure for other reasons, to signal that other drag-and-drop // actions should not be tried. // Find path to copy it wxFileName dst; dst.AssignDir( ff ); dst.SetFullName( src.GetFullName() ); if ( dst.Exists() ) { // Query whether to overwrite bool overwrite = (wxYES == ::AudacityMessageBox( wxString::Format(_("Overwrite the plug-in file %s?"), dst.GetFullPath() ), _("Plug-in already exists"), wxYES_NO ) ); if ( !overwrite ) return true; } // Move the file or subtree bool copied = false; auto dstPath = dst.GetFullPath(); if ( src.FileExists() ) // A simple one-file plug-in copied = FileNames::CopyFile( src.GetFullPath(), dstPath, true ); else { // A sub-folder // such as for some VST packages // Recursive copy needed -- to do return true; } if (!copied) { ::AudacityMessageBox( _("Plug-in file is in use. Failed to overwrite")); return true; } // Register for real std::vector ids; std::vector names; nPlugIns = module->DiscoverPluginsAtPath(dstPath, errMsg, [&](ModuleInterface *provider, ComponentInterface *ident) -> const PluginID& { // Register as by default, but also collecting the PluginIDs // and names auto &id = PluginManagerInterface::DefaultRegistrationCallback( provider, ident); ids.push_back(id); names.push_back( ident->GetSymbol().Translation() ); return id; }); if ( ! nPlugIns ) { // Unlikely after the dry run succeeded ::AudacityMessageBox( wxString::Format( _("Failed to register:\n%s"), errMsg ) ); return true; } // Ask whether to enable the plug-ins if (auto nIds = ids.size()) { auto message = wxPLURAL( "Enable this plug-in?", "Enable these plug-ins?", nIds ); message += wxT("\n"); for (const auto &name : names) message += name + wxT("\n"); bool enable = (wxYES == ::AudacityMessageBox( message, _("Enable new plug-ins"), wxYES_NO ) ); for (const auto &id : ids) mPlugins[id].SetEnabled(enable); // Make changes to enabled status persist: this->Save(); } return true; } } } return false; } void PluginManager::Load() { // Create/Open the registry wxFileConfig registry(wxEmptyString, wxEmptyString, FileNames::PluginRegistry()); // If this group doesn't exist then we have something that's not a registry. // We should probably warn the user, but it's pretty unlikely that this will happen. if (!registry.HasGroup(REGROOT)) { // Must start over registry.DeleteAll(); return; } // Check for a registry version that we can understand // TODO: Should also check for a registry file that is newer than // what we can understand. wxString regver = registry.Read(REGVERKEY); if (regver < REGVERCUR ) { // Conversion code here, for when registry version changes. // We iterate through the effects, possibly updating their info. wxString groupName; long groupIndex; wxString group = GetPluginTypeString(PluginTypeEffect); wxString cfgPath = REGROOT + group + wxCONFIG_PATH_SEPARATOR; wxArrayString groupsToDelete; registry.SetPath(cfgPath); for (bool cont = registry.GetFirstGroup(groupName, groupIndex); cont; registry.SetPath(cfgPath), cont = registry.GetNextGroup(groupName, groupIndex)) { registry.SetPath(groupName); wxString effectSymbol = registry.Read(KEY_SYMBOL, ""); wxString effectVersion = registry.Read(KEY_VERSION, ""); // For 2.3.0 the plugins we distribute have moved around. // So we upped the registry version number to 1.1. // These particular config edits were originally written to fix Bug 1914. if (regver <= "1.0") { // Nyquist prompt is a built-in that has moved to the tools menu. if (effectSymbol == "Nyquist Prompt") { registry.Write(KEY_EFFECTTYPE, "Tool"); // Old version of SDE was in Analyze menu. Now it is in Tools. // We don't want both the old and the new. } else if ((effectSymbol == "Sample Data Export") && (effectVersion == "n/a")) { groupsToDelete.push_back(cfgPath + groupName); // Old version of SDI was in Generate menu. Now it is in Tools. } else if ((effectSymbol == "Sample Data Import") && (effectVersion == "n/a")) { groupsToDelete.push_back(cfgPath + groupName); } } } // Doing the deletion within the search loop risked skipping some items, // hence the delayed delete. for (unsigned int i = 0; i < groupsToDelete.size(); i++) { registry.DeleteGroup(groupsToDelete[i]); } registry.SetPath(""); registry.Write(REGVERKEY, REGVERCUR); // Updates done. Make sure we read the updated data later. registry.Flush(); } // Load all provider plugins first LoadGroup(®istry, PluginTypeModule); // Now the rest LoadGroup(®istry, PluginTypeEffect); LoadGroup(®istry, PluginTypeAudacityCommand ); LoadGroup(®istry, PluginTypeExporter); LoadGroup(®istry, PluginTypeImporter); LoadGroup(®istry, PluginTypeStub); return; } void PluginManager::LoadGroup(wxFileConfig *pRegistry, PluginType type) { #ifdef __WXMAC__ // Bug 1590: On Mac, we should purge the registry of Nyquist plug-ins // bundled with other versions of Audacity, assuming both versions // were properly installed in /Applications (or whatever it is called in // your locale) const auto fullExePath = PlatformCompatibility::GetExecutablePath(); // Strip rightmost path components up to *.app wxFileName exeFn{ fullExePath }; exeFn.SetEmptyExt(); exeFn.SetName(wxString{}); while(exeFn.GetDirCount() && !exeFn.GetDirs().back().EndsWith(".app")) exeFn.RemoveLastDir(); const auto goodPath = exeFn.GetPath(); if(exeFn.GetDirCount()) exeFn.RemoveLastDir(); const auto possiblyBadPath = exeFn.GetPath(); auto AcceptPath = [&](const wxString &path) { if (!path.StartsWith(possiblyBadPath)) // Assume it's not under /Applications return true; if (path.StartsWith(goodPath)) // It's bundled with this executable return true; return false; }; #else auto AcceptPath = [](const wxString&){ return true; }; #endif wxString strVal; bool boolVal; wxString groupName; long groupIndex; wxString group = GetPluginTypeString(type); wxString cfgPath = REGROOT + group + wxCONFIG_PATH_SEPARATOR; pRegistry->SetPath(cfgPath); for (bool cont = pRegistry->GetFirstGroup(groupName, groupIndex); cont; pRegistry->SetPath(cfgPath), cont = pRegistry->GetNextGroup(groupName, groupIndex)) { PluginDescriptor plug; pRegistry->SetPath(groupName); groupName = ConvertID(groupName); // Bypass group if the ID is already in use if (mPlugins.find(groupName) != mPlugins.end()) { pRegistry->SetPath(wxT("..")); continue; } // Set the ID and type plug.SetID(groupName); plug.SetPluginType(type); // Get the provider ID and bypass group if not found if (!pRegistry->Read(KEY_PROVIDERID, &strVal, wxEmptyString)) { // Bypass group if the provider isn't valid if (!strVal.empty() && mPlugins.find(strVal) == mPlugins.end()) { continue; } } plug.SetProviderID(PluginID(strVal)); // Get the path (optional) pRegistry->Read(KEY_PATH, &strVal, wxEmptyString); if (!AcceptPath(strVal)) // Ignore the obsolete path in the config file, during session, // but don't remove it from the file. Maybe you really want to // switch back to the other version of Audacity and lose nothing. continue; plug.SetPath(strVal); /* // PRL: Ignore names written in configs before 2.3.0! // use Internal string only! Let the present version of Audacity map // that to a user-visible string. // Get the name and bypass group if not found if (!pRegistry->Read(KEY_NAME, &strVal)) { continue; } plug.SetName(strVal); */ // Get the symbol...Audacity 2.3.0 or later requires it // bypass group if not found // Note, KEY_SYMBOL started getting written to config files in 2.1.0. // KEY_NAME (now ignored) was written before that, but only for VST // effects. if (!pRegistry->Read(KEY_SYMBOL, &strVal)) continue; plug.SetSymbol(strVal); // Get the version and bypass group if not found if (!pRegistry->Read(KEY_VERSION, &strVal)) { continue; } plug.SetVersion(strVal); // Get the vendor and bypass group if not found if (!pRegistry->Read(KEY_VENDOR, &strVal)) { continue; } plug.SetVendor( strVal ); #if 0 // This was done before version 2.2.2, but the value was not really used // But absence of a value will cause early versions to skip the group // Therefore we still write a blank to keep pluginregistry.cfg // backwards-compatible // Get the description and bypass group if not found if (!pRegistry->Read(KEY_DESCRIPTION, &strVal)) { continue; } #endif // Is it enabled...default to no if not found pRegistry->Read(KEY_ENABLED, &boolVal, false); plug.SetEnabled(boolVal); // Is it valid...default to no if not found pRegistry->Read(KEY_VALID, &boolVal, false); plug.SetValid(boolVal); switch (type) { case PluginTypeModule: { // Nothing to do here yet } break; case PluginTypeEffect: { // Get the effect type and bypass group if not found if (!pRegistry->Read(KEY_EFFECTTYPE, &strVal)) continue; if (strVal == KEY_EFFECTTYPE_NONE) plug.SetEffectType(EffectTypeNone); else if (strVal == KEY_EFFECTTYPE_ANALYZE) plug.SetEffectType(EffectTypeAnalyze); else if (strVal == KEY_EFFECTTYPE_GENERATE) plug.SetEffectType(EffectTypeGenerate); else if (strVal == KEY_EFFECTTYPE_PROCESS) plug.SetEffectType(EffectTypeProcess); else if (strVal == KEY_EFFECTTYPE_TOOL) plug.SetEffectType(EffectTypeTool); else if (strVal == KEY_EFFECTTYPE_HIDDEN) plug.SetEffectType(EffectTypeHidden); else continue; // Get the effect family and bypass group if not found if (!pRegistry->Read(KEY_EFFECTFAMILY, &strVal)) { continue; } plug.SetEffectFamily(strVal); // Is it a default (above the line) effect and bypass group if not found if (!pRegistry->Read(KEY_EFFECTDEFAULT, &boolVal)) { continue; } plug.SetEffectDefault(boolVal); // Is it an interactive effect and bypass group if not found if (!pRegistry->Read(KEY_EFFECTINTERACTIVE, &boolVal)) { continue; } plug.SetEffectInteractive(boolVal); // Is it a realtime capable effect and bypass group if not found if (!pRegistry->Read(KEY_EFFECTREALTIME, &boolVal)) { continue; } plug.SetEffectRealtime(boolVal); // Does the effect support automation...bypass group if not found if (!pRegistry->Read(KEY_EFFECTAUTOMATABLE, &boolVal)) { continue; } plug.SetEffectAutomatable(boolVal); } break; case PluginTypeImporter: { // Get the importer identifier and bypass group if not found if (!pRegistry->Read(KEY_IMPORTERIDENT, &strVal)) { continue; } plug.SetImporterIdentifier(strVal); // Get the importer filter description and bypass group if not found if (!pRegistry->Read(KEY_IMPORTERFILTER, &strVal)) { continue; } plug.SetImporterFilterDescription(strVal); // Get the importer extensions and bypass group if not found if (!pRegistry->Read(KEY_IMPORTEREXTENSIONS, &strVal)) { continue; } FileExtensions extensions; wxStringTokenizer tkr(strVal, wxT(":")); while (tkr.HasMoreTokens()) { extensions.push_back(tkr.GetNextToken()); } plug.SetImporterExtensions(extensions); } break; case PluginTypeStub: { // Nothing additional for stubs } break; // Not used by 2.1.1 or greater and should be removed after a few releases past 2.1.0. case PluginTypeNone: { // Used for stub groups } break; default: { continue; } } // Everything checked out...accept the plugin mPlugins[groupName] = plug; } return; } void PluginManager::Save() { // Create/Open the registry wxFileConfig registry(wxEmptyString, wxEmptyString, FileNames::PluginRegistry()); // Clear it out registry.DeleteAll(); // Write the version string registry.Write(REGVERKEY, REGVERCUR); // Save the individual groups SaveGroup(®istry, PluginTypeEffect); SaveGroup(®istry, PluginTypeExporter); SaveGroup(®istry, PluginTypeAudacityCommand); SaveGroup(®istry, PluginTypeImporter); SaveGroup(®istry, PluginTypeStub); // Not used by 2.1.1 or greater, but must save to allow users to switch between 2.1.0 // and 2.1.1+. This should be removed after a few releases past 2.1.0. //SaveGroup(®istry, PluginTypeNone); // And now the providers SaveGroup(®istry, PluginTypeModule); // Just to be safe registry.Flush(); } void PluginManager::SaveGroup(wxFileConfig *pRegistry, PluginType type) { wxString group = GetPluginTypeString(type); for (PluginMap::iterator iter = mPlugins.begin(); iter != mPlugins.end(); ++iter) { PluginDescriptor & plug = iter->second; if (plug.GetPluginType() != type) { continue; } pRegistry->SetPath(REGROOT + group + wxCONFIG_PATH_SEPARATOR + ConvertID(plug.GetID())); pRegistry->Write(KEY_PATH, plug.GetPath()); pRegistry->Write(KEY_SYMBOL, plug.GetSymbol().Internal()); // PRL: Writing KEY_NAME which is no longer read, but older Audacity // versions expect to find it. pRegistry->Write(KEY_NAME, plug.GetSymbol().Msgid()); pRegistry->Write(KEY_VERSION, plug.GetUntranslatedVersion()); pRegistry->Write(KEY_VENDOR, plug.GetVendor()); // Write a blank -- see comments in LoadGroup: pRegistry->Write(KEY_DESCRIPTION, wxString{}); pRegistry->Write(KEY_PROVIDERID, plug.GetProviderID()); pRegistry->Write(KEY_ENABLED, plug.IsEnabled()); pRegistry->Write(KEY_VALID, plug.IsValid()); switch (type) { case PluginTypeModule: break; case PluginTypeEffect: { EffectType etype = plug.GetEffectType(); wxString stype; if (etype == EffectTypeNone) stype = KEY_EFFECTTYPE_NONE; else if (etype == EffectTypeAnalyze) stype = KEY_EFFECTTYPE_ANALYZE; else if (etype == EffectTypeGenerate) stype = KEY_EFFECTTYPE_GENERATE; else if (etype == EffectTypeProcess) stype = KEY_EFFECTTYPE_PROCESS; else if (etype == EffectTypeTool) stype = KEY_EFFECTTYPE_TOOL; else if (etype == EffectTypeHidden) stype = KEY_EFFECTTYPE_HIDDEN; pRegistry->Write(KEY_EFFECTTYPE, stype); pRegistry->Write(KEY_EFFECTFAMILY, plug.GetEffectFamily()); pRegistry->Write(KEY_EFFECTDEFAULT, plug.IsEffectDefault()); pRegistry->Write(KEY_EFFECTINTERACTIVE, plug.IsEffectInteractive()); pRegistry->Write(KEY_EFFECTREALTIME, plug.IsEffectRealtime()); pRegistry->Write(KEY_EFFECTAUTOMATABLE, plug.IsEffectAutomatable()); } break; case PluginTypeImporter: { pRegistry->Write(KEY_IMPORTERIDENT, plug.GetImporterIdentifier()); pRegistry->Write(KEY_IMPORTERFILTER, plug.GetImporterFilterDescription()); const auto & extensions = plug.GetImporterExtensions(); wxString strExt; for (size_t i = 0, cnt = extensions.size(); i < cnt; i++) { strExt += extensions[i] + wxT(":"); } strExt.RemoveLast(1); pRegistry->Write(KEY_IMPORTEREXTENSIONS, strExt); } break; default: break; } } return; } // If bFast is true, do not do a full check. Just check the ones // that are quick to check. Currently (Feb 2017) just Nyquist // and built-ins. void PluginManager::CheckForUpdates(bool bFast) { // Get ModuleManager reference ModuleManager & mm = ModuleManager::Get(); wxArrayString pathIndex; for (PluginMap::iterator iter = mPlugins.begin(); iter != mPlugins.end(); ++iter) { PluginDescriptor & plug = iter->second; // Bypass 2.1.0 placeholders...remove this after a few releases past 2.1.0 if (plug.GetPluginType() == PluginTypeNone) { continue; } pathIndex.push_back(plug.GetPath().BeforeFirst(wxT(';'))); } // Check all known plugins to ensure they are still valid and scan for NEW ones. // // All NEW plugins get a stub entry created that will remain in place until the // user enables or disables the plugin. // // Becuase we use the plugins "path" as returned by the providers, we can actually // have multiple providers report the same path since, at this point, they only // know that the path might possibly be one supported by the provider. // // When the user enables the plugin, each provider that reported it will be asked // to register the plugin. for (PluginMap::iterator iter = mPlugins.begin(); iter != mPlugins.end(); ++iter) { PluginDescriptor & plug = iter->second; const PluginID & plugID = plug.GetID(); const wxString & plugPath = plug.GetPath(); PluginType plugType = plug.GetPluginType(); // Bypass 2.1.0 placeholders...remove this after a few releases past 2.1.0 if (plugType == PluginTypeNone) { continue; } if ( plugType == PluginTypeModule ) { if( bFast ) { // Skip modules, when doing a fast refresh/check. } else if (!mm.IsProviderValid(plugID, plugPath)) { plug.SetEnabled(false); plug.SetValid(false); } else { // Collect plugin paths auto paths = mm.FindPluginsForProvider(plugID, plugPath); for (size_t i = 0, cnt = paths.size(); i < cnt; i++) { wxString path = paths[i].BeforeFirst(wxT(';'));; if ( ! make_iterator_range( pathIndex ).contains( path ) ) { PluginID ID = plugID + wxT("_") + path; PluginDescriptor & plug2 = mPlugins[ID]; // This will create a NEW descriptor plug2.SetPluginType(PluginTypeStub); plug2.SetID(ID); plug2.SetProviderID(plugID); plug2.SetPath(path); plug2.SetEnabled(false); plug2.SetValid(false); } } } } else if (plugType != PluginTypeNone && plugType != PluginTypeStub) { plug.SetValid(mm.IsPluginValid(plug.GetProviderID(), plugPath, bFast)); if (!plug.IsValid()) { plug.SetEnabled(false); } } } Save(); return; } bool PluginManager::ShowManager(wxWindow *parent, EffectType type) { CheckForUpdates(); PluginRegistrationDialog dlg(parent, type); return dlg.ShowModal() == wxID_OK; } // Here solely for the purpose of Nyquist Workbench until // a better solution is devised. const PluginID & PluginManager::RegisterPlugin(EffectDefinitionInterface *effect, PluginType type) { PluginDescriptor & plug = CreatePlugin(GetID(effect), effect, type); plug.SetEffectType(effect->GetType()); plug.SetEffectFamily(effect->GetFamily().Internal()); plug.SetEffectInteractive(effect->IsInteractive()); plug.SetEffectDefault(effect->IsDefault()); plug.SetEffectRealtime(effect->SupportsRealtime()); plug.SetEffectAutomatable(effect->SupportsAutomation()); plug.SetInstance(effect); plug.SetEffectLegacy(true); plug.SetEnabled(true); plug.SetValid(true); return plug.GetID(); } // Here solely for the purpose of Nyquist Workbench until // a better solution is devised. void PluginManager::UnregisterPlugin(const PluginID & ID) { if (mPlugins.find(ID) == mPlugins.end()) { return; } mPlugins.erase(ID); } int PluginManager::GetPluginCount(PluginType type) { int num = 0; for (PluginMap::iterator iter = mPlugins.begin(); iter != mPlugins.end(); ++iter) { if (iter->second.GetPluginType() == type) { num++; } } return num; } const PluginDescriptor *PluginManager::GetPlugin(const PluginID & ID) { if (mPlugins.find(ID) == mPlugins.end()) { return NULL; } return &mPlugins[ID]; } const PluginDescriptor *PluginManager::GetFirstPlugin(int type) { for (mPluginsIter = mPlugins.begin(); mPluginsIter != mPlugins.end(); ++mPluginsIter) { PluginDescriptor & plug = mPluginsIter->second; PluginType plugType = plug.GetPluginType(); if( plug.IsValid() && plug.IsEnabled() && ((plugType & type) != 0)) { bool familyEnabled = true; if( (plugType & PluginTypeEffect) != 0) // This preference may be written by EffectsPrefs gPrefs->Read(plug.GetEffectFamily() + wxT("/Enable"), &familyEnabled, true); if (familyEnabled) return &mPluginsIter->second; } } return NULL; } const PluginDescriptor *PluginManager::GetNextPlugin(int type) { while (++mPluginsIter != mPlugins.end()) { PluginDescriptor & plug = mPluginsIter->second; PluginType plugType = plug.GetPluginType(); if( plug.IsValid() && plug.IsEnabled() && ((plugType & type) != 0)) { bool familyEnabled = true; if( (plugType & PluginTypeEffect) != 0) // This preference may be written by EffectsPrefs gPrefs->Read(plug.GetEffectFamily() + wxT("/Enable"), &familyEnabled, true); if (familyEnabled) return &mPluginsIter->second; } } return NULL; } const PluginDescriptor *PluginManager::GetFirstPluginForEffectType(EffectType type) { for (mPluginsIter = mPlugins.begin(); mPluginsIter != mPlugins.end(); ++mPluginsIter) { PluginDescriptor & plug = mPluginsIter->second; bool familyEnabled; // This preference may be written by EffectsPrefs gPrefs->Read(plug.GetEffectFamily() + wxT("/Enable"), &familyEnabled, true); if (plug.IsValid() && plug.IsEnabled() && plug.GetEffectType() == type && familyEnabled) { return &plug; } } return NULL; } const PluginDescriptor *PluginManager::GetNextPluginForEffectType(EffectType type) { while (++mPluginsIter != mPlugins.end()) { PluginDescriptor & plug = mPluginsIter->second; bool familyEnabled; // This preference may be written by EffectsPrefs gPrefs->Read(plug.GetEffectFamily() + wxT("/Enable"), &familyEnabled, true); if (plug.IsValid() && plug.IsEnabled() && plug.GetEffectType() == type && familyEnabled) { return &plug; } } return NULL; } bool PluginManager::IsPluginEnabled(const PluginID & ID) { if (mPlugins.find(ID) == mPlugins.end()) { return false; } return mPlugins[ID].IsEnabled(); } void PluginManager::EnablePlugin(const PluginID & ID, bool enable) { if (mPlugins.find(ID) == mPlugins.end()) { return; } return mPlugins[ID].SetEnabled(enable); } const ComponentInterfaceSymbol & PluginManager::GetSymbol(const PluginID & ID) { if (mPlugins.find(ID) == mPlugins.end()) { static ComponentInterfaceSymbol empty; return empty; } return mPlugins[ID].GetSymbol(); } ComponentInterface *PluginManager::GetInstance(const PluginID & ID) { if (mPlugins.find(ID) == mPlugins.end()) { return NULL; } PluginDescriptor & plug = mPlugins[ID]; // If not dealing with legacy effects, make sure the provider is loaded if (!plug.IsEffectLegacy()) { const PluginID & prov = plug.GetProviderID(); if (mPlugins.find(prov) == mPlugins.end()) { return NULL; } mPlugins[prov].GetInstance(); } return plug.GetInstance(); } PluginID PluginManager::GetID(ModuleInterface *module) { return wxString::Format(wxT("%s_%s_%s_%s_%s"), GetPluginTypeString(PluginTypeModule), wxEmptyString, module->GetVendor().Internal(), module->GetSymbol().Internal(), module->GetPath()); } PluginID PluginManager::GetID(ComponentInterface *command) { return wxString::Format(wxT("%s_%s_%s_%s_%s"), GetPluginTypeString(PluginTypeAudacityCommand), wxEmptyString, command->GetVendor().Internal(), command->GetSymbol().Internal(), command->GetPath()); } PluginID PluginManager::GetID(EffectDefinitionInterface *effect) { return wxString::Format(wxT("%s_%s_%s_%s_%s"), GetPluginTypeString(PluginTypeEffect), effect->GetFamily().Internal(), effect->GetVendor().Internal(), effect->GetSymbol().Internal(), effect->GetPath()); } PluginID PluginManager::GetID(ImporterInterface *importer) { return wxString::Format(wxT("%s_%s_%s_%s_%s"), GetPluginTypeString(PluginTypeImporter), wxEmptyString, importer->GetVendor().Internal(), importer->GetSymbol().Internal(), importer->GetPath()); } // This string persists in configuration files // So config compatibility will break if it is changed across Audacity versions wxString PluginManager::GetPluginTypeString(PluginType type) { wxString str; switch (type) { default: case PluginTypeNone: str = wxT("Placeholder"); break; case PluginTypeStub: str = wxT("Stub"); break; case PluginTypeEffect: str = wxT("Effect"); break; case PluginTypeAudacityCommand: str = wxT("Generic"); break; case PluginTypeExporter: str = wxT("Exporter"); break; case PluginTypeImporter: str = wxT("Importer"); break; case PluginTypeModule: str = wxT("Module"); break; } return str; } PluginDescriptor & PluginManager::CreatePlugin(const PluginID & id, ComponentInterface *ident, PluginType type) { // This will either create a NEW entry or replace an existing entry PluginDescriptor & plug = mPlugins[id]; plug.SetPluginType(type); plug.SetID(id); plug.SetPath(ident->GetPath()); plug.SetSymbol(ident->GetSymbol()); plug.SetVendor(ident->GetVendor().Internal()); plug.SetVersion(ident->GetVersion()); return plug; } wxFileConfig *PluginManager::GetSettings() { if (!mSettings) { mSettings = std::make_unique(wxEmptyString, wxEmptyString, FileNames::PluginSettings()); // Check for a settings version that we can understand if (mSettings->HasEntry(SETVERKEY)) { wxString setver = mSettings->Read(SETVERKEY, SETVERKEY); if (setver < SETVERCUR ) { // This is where we'd put in conversion code when the // settings version changes. // // Should also check for a settings file that is newer than // what we can understand. } } else { // Make sure is has a version string mSettings->Write(SETVERKEY, SETVERCUR); mSettings->Flush(); } } return mSettings.get(); } bool PluginManager::HasGroup(const RegistryPath & group) { wxFileConfig *settings = GetSettings(); bool res = settings->HasGroup(group); if (res) { // The group exists, but empty groups aren't considered valid wxString oldPath = settings->GetPath(); settings->SetPath(group); res = settings->GetNumberOfEntries() || settings->GetNumberOfGroups(); settings->SetPath(oldPath); } return res; } bool PluginManager::GetSubgroups(const RegistryPath & group, RegistryPaths & subgroups) { if (group.empty() || !HasGroup(group)) { return false; } wxString path = GetSettings()->GetPath(); GetSettings()->SetPath(group); wxString name; long index = 0; if (GetSettings()->GetFirstGroup(name, index)) { do { subgroups.push_back(name); } while (GetSettings()->GetNextGroup(name, index)); } GetSettings()->SetPath(path); return true; } bool PluginManager::GetConfig(const RegistryPath & key, int & value, int defval) { bool result = false; if (!key.empty()) { result = GetSettings()->Read(key, &value, defval); } return result; } bool PluginManager::GetConfig(const RegistryPath & key, wxString & value, const wxString & defval) { bool result = false; if (!key.empty()) { wxString wxval; result = GetSettings()->Read(key, &wxval, defval); value = wxval; } return result; } bool PluginManager::GetConfig(const RegistryPath & key, bool & value, bool defval) { bool result = false; if (!key.empty()) { result = GetSettings()->Read(key, &value, defval); } return result; } bool PluginManager::GetConfig(const RegistryPath & key, float & value, float defval) { bool result = false; if (!key.empty()) { double dval = 0.0; result = GetSettings()->Read(key, &dval, (double) defval); value = (float) dval; } return result; } bool PluginManager::GetConfig(const RegistryPath & key, double & value, double defval) { bool result = false; if (!key.empty()) { result = GetSettings()->Read(key, &value, defval); } return result; } bool PluginManager::SetConfig(const RegistryPath & key, const wxString & value) { bool result = false; if (!key.empty()) { wxString wxval = value; result = GetSettings()->Write(key, wxval); if (result) { result = GetSettings()->Flush(); } } return result; } bool PluginManager::SetConfig(const RegistryPath & key, const int & value) { bool result = false; if (!key.empty()) { result = GetSettings()->Write(key, value); if (result) { result = GetSettings()->Flush(); } } return result; } bool PluginManager::SetConfig(const RegistryPath & key, const bool & value) { bool result = false; if (!key.empty()) { result = GetSettings()->Write(key, value); if (result) { result = GetSettings()->Flush(); } } return result; } bool PluginManager::SetConfig(const RegistryPath & key, const float & value) { bool result = false; if (!key.empty()) { result = GetSettings()->Write(key, value); if (result) { result = GetSettings()->Flush(); } } return result; } bool PluginManager::SetConfig(const RegistryPath & key, const double & value) { bool result = false; if (!key.empty()) { result = GetSettings()->Write(key, value); if (result) { result = GetSettings()->Flush(); } } return result; } /* Return value is a key for lookup in a config file */ RegistryPath PluginManager::SettingsPath(const PluginID & ID, bool shared) { // All the strings reported by PluginDescriptor and used in this function // persist in the plugin settings configuration file, so they should not // be changed across Audacity versions, or else compatibility of the // configuration files will break. if (mPlugins.find(ID) == mPlugins.end()) { return wxEmptyString; } const PluginDescriptor & plug = mPlugins[ID]; wxString id = GetPluginTypeString(plug.GetPluginType()) + wxT("_") + plug.GetEffectFamily() + // is empty for non-Effects wxT("_") + plug.GetVendor() + wxT("_") + (shared ? wxT("") : plug.GetSymbol().Internal()); return SETROOT + ConvertID(id) + wxCONFIG_PATH_SEPARATOR + (shared ? wxT("shared") : wxT("private")) + wxCONFIG_PATH_SEPARATOR; } /* Return value is a key for lookup in a config file */ RegistryPath PluginManager::SharedGroup(const PluginID & ID, const RegistryPath & group) { wxString path = SettingsPath(ID, true); wxFileName ff(group); if (!ff.GetName().empty()) { path += ff.GetFullPath(wxPATH_UNIX) + wxCONFIG_PATH_SEPARATOR; } return path; } /* Return value is a key for lookup in a config file */ RegistryPath PluginManager::SharedKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) { auto path = SharedGroup(ID, group); if (path.empty()) { return path; } return path + key; } /* Return value is a key for lookup in a config file */ RegistryPath PluginManager::PrivateGroup(const PluginID & ID, const RegistryPath & group) { auto path = SettingsPath(ID, false); wxFileName ff(group); if (!ff.GetName().empty()) { path += ff.GetFullPath(wxPATH_UNIX) + wxCONFIG_PATH_SEPARATOR; } return path; } /* Return value is a key for lookup in a config file */ RegistryPath PluginManager::PrivateKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) { auto path = PrivateGroup(ID, group); if (path.empty()) { return path; } return path + key; } // Sanitize the ID...not the best solution, but will suffice until this // is converted to XML. We use base64 encoding to preserve case. wxString PluginManager::ConvertID(const PluginID & ID) { if (ID.StartsWith(wxT("base64:"))) { wxString id = ID.Mid(7); ArrayOf buf{ id.length() / 4 * 3 }; id = wxString::FromUTF8(buf.get(), b64decode(id, buf.get())); return id; } const wxCharBuffer & buf = ID.ToUTF8(); return wxT("base64:") + b64encode(buf, strlen(buf)); } //////////////////////////////////////////////////////////////////////////////// // Base64 en/decoding // // Original routines marked as public domain and found at: // // http://en.wikibooks.org/wiki/Algorithm_implementation/Miscellaneous/Base64 // //////////////////////////////////////////////////////////////////////////////// // Lookup table for encoding const static wxChar cset[] = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); const static char padc = wxT('='); wxString PluginManager::b64encode(const void *in, int len) { unsigned char *p = (unsigned char *) in; wxString out; unsigned long temp; for (int i = 0; i < len / 3; i++) { temp = (*p++) << 16; //Convert to big endian temp += (*p++) << 8; temp += (*p++); out += cset[(temp & 0x00FC0000) >> 18]; out += cset[(temp & 0x0003F000) >> 12]; out += cset[(temp & 0x00000FC0) >> 6]; out += cset[(temp & 0x0000003F)]; } switch (len % 3) { case 1: temp = (*p++) << 16; //Convert to big endian out += cset[(temp & 0x00FC0000) >> 18]; out += cset[(temp & 0x0003F000) >> 12]; out += padc; out += padc; break; case 2: temp = (*p++) << 16; //Convert to big endian temp += (*p++) << 8; out += cset[(temp & 0x00FC0000) >> 18]; out += cset[(temp & 0x0003F000) >> 12]; out += cset[(temp & 0x00000FC0) >> 6]; out += padc; break; } return out; } int PluginManager::b64decode(const wxString &in, void *out) { int len = in.length(); unsigned char *p = (unsigned char *) out; if (len % 4) //Sanity check { return 0; } int padding = 0; if (len) { if (in[len - 1] == padc) { padding++; } if (in[len - 2] == padc) { padding++; } } //const char *a = in.mb_str(); //Setup a vector to hold the result unsigned long temp = 0; //Holds decoded quanta int i = 0; while (i < len) { for (int quantumPosition = 0; quantumPosition < 4; quantumPosition++) { unsigned char c = in[i]; temp <<= 6; if (c >= 0x41 && c <= 0x5A) { temp |= c - 0x41; } else if (c >= 0x61 && c <= 0x7A) { temp |= c - 0x47; } else if (c >= 0x30 && c <= 0x39) { temp |= c + 0x04; } else if (c == 0x2B) { temp |= 0x3E; } else if (c == 0x2F) { temp |= 0x3F; } else if (c == padc) { switch (len - i) { case 1: //One pad character *p++ = (temp >> 16) & 0x000000FF; *p++ = (temp >> 8) & 0x000000FF; return p - (unsigned char *) out; case 2: //Two pad characters *p++ = (temp >> 10) & 0x000000FF; return p - (unsigned char *) out; } } i++; } *p++ = (temp >> 16) & 0x000000FF; *p++ = (temp >> 8) & 0x000000FF; *p++ = temp & 0x000000FF; } return p - (unsigned char *) out; } // These are defined out-of-line here, to keep ComponentInterface free of other // #include directives. const wxString& ComponentInterface::GetTranslatedName() { return GetSymbol().Translation(); }