├── pch.h ├── Main.ico ├── SetHTMLEditor.reg ├── Support.h ├── pch.cpp ├── UIElementTracker.cpp ├── packages.config ├── Support.cpp ├── docs └── index.md ├── FileWatcher.h ├── framework.h ├── Component.cpp ├── cpp.hint ├── LICENSE.txt ├── UIElementTracker.h ├── SharedBuffer.h ├── Exceptions.h ├── Encoding.h ├── Exceptions.cpp ├── DUIElement.h ├── FrameTemplate.html ├── ProcessLocationsHandler.h ├── Configuration.h ├── CUIElement.cpp ├── .gitattributes ├── foo_uie_webview.vcxproj.filters ├── Rendering.cpp ├── FileWatcher.cpp ├── SharedBuffer.cpp ├── Resources.h ├── PreferencesLayout.h ├── DUIElement.cpp ├── Resources.rc ├── CUIElement.h ├── HostObject.idl ├── Build-FB2KComponent.ps1 ├── Configuration.cpp ├── .gitignore ├── Encoding.cpp ├── HostObjectImpl.h ├── UIElementPlaylistCallback.cpp ├── UIElement.h ├── HostObjectImplFiles.cpp ├── foo_uie_webview.sln ├── HostObjectImplPlaylists.cpp ├── Preferences.cpp ├── UIElement.cpp └── Template.html /pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "framework.h" 4 | -------------------------------------------------------------------------------- /Main.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuerp/foo_uie_webview/HEAD/Main.ico -------------------------------------------------------------------------------- /SetHTMLEditor.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuerp/foo_uie_webview/HEAD/SetHTMLEditor.reg -------------------------------------------------------------------------------- /Support.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Support.h (2024.05.28) P. Stuer **/ 3 | 4 | #pragma once 5 | 6 | #include "pch.h" 7 | 8 | extern HMODULE GetCurrentModule() noexcept; 9 | -------------------------------------------------------------------------------- /pch.cpp: -------------------------------------------------------------------------------- 1 | // pch.cpp: source file corresponding to the pre-compiled header 2 | 3 | #include "pch.h" 4 | 5 | // When you are using pre-compiled headers, this source file is necessary for compilation to succeed. 6 | -------------------------------------------------------------------------------- /UIElementTracker.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: UIElementTracker.cpp (2024.06.12) P. Stuer - Tracks the instances of the panel. **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "UIElementTracker.h" 7 | 8 | uielement_tracker_t _UIElementTracker; 9 | -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Support.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Support.cpp (2024.05.28) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | /// 7 | /// Gets the handle of the module that contains the executing code. 8 | /// 9 | HMODULE GetCurrentModule() noexcept 10 | { 11 | HMODULE hModule = NULL; 12 | 13 | ::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCWSTR) GetCurrentModule, &hModule); 14 | 15 | return hModule; 16 | } 17 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: navigation 3 | --- 4 | 5 | [foo_uie_webview](https://github.com/stuerp/foo_uie_webview/releases) is a [foobar2000](https://www.foobar2000.org/) component that exposes the [Microsoft WebView2](https://learn.microsoft.com/en-us/microsoft-edge/webview2/) control as UI panel. The component started as foo_vis_text. 6 | 7 | It takes an HTML file that receives playback notifications from foobar2000. The panel can react to those notifications and adjust its output using JavaScript code. 8 | -------------------------------------------------------------------------------- /FileWatcher.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: FileWatcher.h (2024.05.27) P. Stuer - Implements a file system watcher. **/ 3 | 4 | #pragma once 5 | 6 | #include "framework.h" 7 | 8 | /// 9 | /// Implements a file system watcher. 10 | /// 11 | class FileWatcher 12 | { 13 | public: 14 | FileWatcher() : _ThreadParameters(), _hThread() { } 15 | 16 | void Start(HWND hWnd, const std::wstring & filePath); 17 | void Stop() noexcept; 18 | 19 | private: 20 | static DWORD WINAPI ThreadProc(LPVOID lParam) noexcept; 21 | 22 | struct thread_parameters_t 23 | { 24 | HWND hWnd; 25 | std::wstring FilePath; 26 | } _ThreadParameters; 27 | 28 | HANDLE _hThread; 29 | }; 30 | -------------------------------------------------------------------------------- /framework.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: framework.h (2024.11.27) P. Stuer **/ 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #pragma warning(disable: 4100 4625 4626 4710 4711 4738 5045 ALL_CPPCORECHECK_WARNINGS) 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | EXTERN_C IMAGE_DOS_HEADER __ImageBase; 33 | #define THIS_INSTANCE ((HINSTANCE) &__ImageBase) 34 | -------------------------------------------------------------------------------- /Component.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Component.cpp (2024.05.23) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include 7 | 8 | #include "Resources.h" 9 | 10 | #pragma hdrstop 11 | 12 | namespace 13 | { 14 | #pragma warning(disable: 4265 5026 5027 26433 26436 26455) 15 | DECLARE_COMPONENT_VERSION 16 | ( 17 | STR_COMPONENT_NAME, 18 | STR_COMPONENT_VERSION, 19 | STR_COMPONENT_BASENAME " " STR_COMPONENT_VERSION "\n" 20 | STR_COMPONENT_COPYRIGHT "\n" 21 | "\n" 22 | STR_COMPONENT_DESCRIPTION "\n" 23 | STR_COMPONENT_COMMENT "\n" 24 | "\n" 25 | "Built with foobar2000 SDK " TOSTRING(FOOBAR2000_SDK_VERSION) "\n" 26 | "on " __DATE__ " " __TIME__ "." 27 | ) 28 | VALIDATE_COMPONENT_FILENAME(STR_COMPONENT_FILENAME) 29 | } 30 | -------------------------------------------------------------------------------- /cpp.hint: -------------------------------------------------------------------------------- 1 | // Hint files help the Visual Studio IDE interpret Visual C++ identifiers 2 | // such as names of functions and macros. 3 | // For more information see https://go.microsoft.com/fwlink/?linkid=865984 4 | 5 | #define BEGIN_MSG_MAP_EX(theClass) public: BOOL m_bMsgHandled; BOOL IsMsgHandled() const { return m_bMsgHandled; } void SetMsgHandled(BOOL bHandled) { m_bMsgHandled = bHandled; } BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) { BOOL bOldMsgHandled = m_bMsgHandled; BOOL bRet = _ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult, dwMsgMapID); m_bMsgHandled = bOldMsgHandled; return bRet; } BOOL _ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID) { BOOL bHandled = TRUE; (hWnd); (uMsg); (wParam); (lParam); (lResult); (bHandled); switch(dwMsgMapID) { case 0: 6 | #define NOVTABLE 7 | #define PFC_STATIC_ASSERT(X) { ::pfc::static_assert_t<(X)>(); } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Peter Stuer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /UIElementTracker.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: UIElementTracker.h (2024.06.12) P. Stuer - Tracks the instances of the panel. **/ 3 | 4 | #pragma once 5 | 6 | #include "framework.h" 7 | 8 | #include "UIElement.h" 9 | 10 | class uielement_tracker_t 11 | { 12 | public: 13 | uielement_tracker_t() : _CurrentUIElement() 14 | { 15 | } 16 | 17 | void Add(UIElement * element) noexcept 18 | { 19 | _UIElements.push_back(element); 20 | 21 | SetCurrentElement(element); 22 | } 23 | 24 | void Remove(UIElement * element) noexcept 25 | { 26 | auto Iter = std::find(_UIElements.begin(), _UIElements.end(), element); 27 | 28 | if (Iter != _UIElements.end()) 29 | { 30 | _UIElements.erase(Iter); 31 | 32 | SetCurrentElement(nullptr); 33 | } 34 | } 35 | 36 | UIElement * GetCurrentElement() const noexcept 37 | { 38 | return _CurrentUIElement; 39 | } 40 | 41 | void SetCurrentElement(UIElement * element) noexcept 42 | { 43 | _CurrentUIElement = element; 44 | } 45 | 46 | private: 47 | UIElement * _CurrentUIElement; 48 | std::vector _UIElements; 49 | }; 50 | 51 | extern uielement_tracker_t _UIElementTracker; 52 | -------------------------------------------------------------------------------- /SharedBuffer.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: SharedBuffer.h (2024.06.26) P. Stuer - Implements a buffer shared by the component and WebView. **/ 3 | 4 | #pragma once 5 | 6 | #include "framework.h" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | /// 14 | /// Implements a buffer shared between the component and the WebView2 control. 15 | /// 16 | class SharedBuffer 17 | { 18 | public: 19 | SharedBuffer() : _SampleCount(), _SampleRate(), _ChannelCount(), _ChannelConfig() { } 20 | 21 | virtual ~SharedBuffer(); 22 | 23 | HRESULT Ensure(wil::com_ptr & environment, wil::com_ptr & webView, size_t sampleCount, uint32_t sampleRate, uint32_t channelCount, uint32_t channelConfig) noexcept; 24 | void Release() noexcept; 25 | 26 | void Copy(const BYTE * data, size_t size) noexcept; 27 | void Convert(const float * sampleData, size_t sampleCount) noexcept; 28 | 29 | private: 30 | size_t _SampleCount; 31 | uint32_t _SampleRate; 32 | uint32_t _ChannelCount; 33 | uint32_t _ChannelConfig; 34 | 35 | wil::com_ptr _Environment12; 36 | wil::com_ptr _WebView17; 37 | wil::com_ptr _SharedBuffer; 38 | UINT64 _Size; 39 | BYTE * _Buffer; 40 | }; 41 | -------------------------------------------------------------------------------- /Exceptions.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Exceptions.h (2024.06.23) P. Stuer **/ 3 | 4 | #pragma once 5 | 6 | #include "framework.h" 7 | 8 | std::string GetErrorMessage(DWORD errorCode, const std::string & errorMessage) noexcept; 9 | 10 | inline std::string GetErrorMessage(HRESULT hResult, const std::string & errorMessage) noexcept 11 | { 12 | return GetErrorMessage((DWORD) hResult, errorMessage); 13 | } 14 | 15 | #pragma warning(disable: 4820) // 'x' bytes padding added after data member 'y' 16 | 17 | class ComponentException : public std::runtime_error 18 | { 19 | public: 20 | ComponentException(const std::string & errorMessage) noexcept : std::runtime_error(errorMessage) { } 21 | }; 22 | 23 | class Win32Exception : public std::runtime_error 24 | { 25 | public: 26 | Win32Exception(const std::string & errorMessage) noexcept : Win32Exception(::GetLastError(), errorMessage) { } 27 | Win32Exception(DWORD errorCode, const std::string & errorMessage) noexcept : std::runtime_error(GetErrorMessage(errorCode, errorMessage)), _ErrorCode(errorCode) { } 28 | Win32Exception(HRESULT hResult, const std::string & errorMessage) noexcept : std::runtime_error(GetErrorMessage((DWORD) hResult, errorMessage)), _ErrorCode((DWORD) hResult) { } 29 | 30 | DWORD GetErrorCode() const { return _ErrorCode; } 31 | 32 | private: 33 | DWORD _ErrorCode; 34 | }; 35 | -------------------------------------------------------------------------------- /Encoding.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Encoding.h (2024.05.18) P. Stuer **/ 3 | 4 | #pragma once 5 | 6 | #include "framework.h" 7 | 8 | std::string WideToUTF8(const wchar_t * wide, size_t size) noexcept; 9 | std::wstring TextToWide(const char * text, size_t size = 0) noexcept; 10 | 11 | std::wstring CodePageToWide(uint32_t codePage, const char * text, size_t size) noexcept; 12 | std::string CodePageToUTF8(uint32_t codePage, const char * text, size_t size) noexcept; 13 | 14 | std::string TextToUTF8(const char * text, size_t size = 0) noexcept; 15 | 16 | inline std::wstring UTF8ToWide(const char * text, size_t size) noexcept 17 | { 18 | return CodePageToWide(CP_UTF8, text, size); 19 | } 20 | 21 | inline std::string WideToUTF8(const std::wstring & text) noexcept 22 | { 23 | return ::WideToUTF8(text.c_str(), text.length()); 24 | } 25 | 26 | inline std::wstring CodePageToWide(uint32_t codePage, const std::string & text) noexcept 27 | { 28 | return CodePageToWide(codePage, text.c_str(), text.length()); 29 | } 30 | 31 | inline std::wstring UTF8ToWide(const std::string & text) noexcept 32 | { 33 | return CodePageToWide(CP_UTF8, text.c_str(), text.length()); 34 | } 35 | 36 | std::string FormatText(const char * format, ...) noexcept; 37 | std::wstring FormatText(const wchar_t * format, ...) noexcept; 38 | 39 | bool IsEUCJP(const char * text, size_t size) noexcept; 40 | bool IsShiftJIS(const char * text, size_t size) noexcept; 41 | bool IsUTF8(const char * text, size_t size) noexcept; 42 | bool IsASCII(const char * text, size_t size) noexcept; 43 | -------------------------------------------------------------------------------- /Exceptions.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Exceptions.cpp (2024.05.27) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "Exceptions.h" 7 | #include "Encoding.h" 8 | 9 | const char * strrstr(const char * __restrict s1, const char *__restrict s2) noexcept; 10 | 11 | /// 12 | /// Gets the error message of the specified error code. 13 | /// 14 | std::string GetErrorMessage(DWORD errorCode, const std::string & errorMessage) noexcept 15 | { 16 | std::string Text; 17 | 18 | Text.resize(256); 19 | 20 | if (::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, errorCode, 0, Text.data(), (DWORD) Text.size(), nullptr) != 0) 21 | { 22 | // Remove a trailing "\r\n". 23 | char * p = (char *) ::strrstr(Text.c_str(), "\r\n"); 24 | 25 | if (p != nullptr) 26 | *p = '\0'; 27 | 28 | // Remove a trailing period ('.'). 29 | p = (char *) ::strrchr(Text.c_str(), '.'); 30 | 31 | if (p != nullptr) 32 | *p = '\0'; 33 | } 34 | else 35 | Text = ::FormatText("Failed to get error message for error code (0x%08X)", ::GetLastError()); 36 | 37 | return ::FormatText("%s: %s (0x%08X)", errorMessage.c_str(), Text.c_str(), errorCode); 38 | } 39 | 40 | /// 41 | /// Returns a pointer to the last occurance of a string. 42 | /// 43 | const char * strrstr(const char * __restrict s1, const char *__restrict s2) noexcept 44 | { 45 | const size_t l1 = ::strlen(s1); 46 | const size_t l2 = ::strlen(s2); 47 | 48 | if (l2 > l1) 49 | return nullptr; 50 | 51 | for (const char * s = s1 + (l1 - l2); s >= s1; --s) 52 | if (::strncmp(s, s2, l2) == 0) 53 | return s; 54 | 55 | return nullptr; 56 | } 57 | -------------------------------------------------------------------------------- /DUIElement.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: DUIElement.h (2024.07.07) P. Stuer - Default User Interface support **/ 3 | 4 | #pragma once 5 | 6 | #include "framework.h" 7 | 8 | #include "UIElement.h" 9 | 10 | #include 11 | 12 | /// 13 | /// Implements a Default UI element. 14 | /// 15 | class DUIElement : public UIElement, public ui_element_instance 16 | { 17 | public: 18 | DUIElement(ui_element_config::ptr data, ui_element_instance_callback::ptr callback); 19 | 20 | DUIElement(const DUIElement &) = delete; 21 | DUIElement & operator=(const DUIElement &) = delete; 22 | DUIElement(DUIElement &&) = delete; 23 | DUIElement & operator=(DUIElement &&) = delete; 24 | 25 | virtual ~DUIElement() { }; 26 | 27 | #pragma region ui_element_instance interface 28 | 29 | static void g_get_name(pfc::string_base & p_out); 30 | static const char * g_get_description(); 31 | static GUID g_get_guid(); 32 | static GUID g_get_subclass(); 33 | static ui_element_config::ptr g_get_default_configuration(); 34 | 35 | void initialize_window(HWND p_parent); 36 | virtual void set_configuration(ui_element_config::ptr p_data); 37 | virtual ui_element_config::ptr get_configuration(); 38 | virtual void notify(const GUID & what, t_size param1, const void * param2, t_size param2Size); 39 | 40 | #pragma endregion 41 | 42 | virtual bool IsWebViewVisible() const noexcept 43 | { 44 | return !m_callback->is_edit_mode_enabled(); // Hide the WebView to allow the default foobar2000 context menu to appear in "Layout Edit" mode. 45 | } 46 | 47 | void GetColors() noexcept override; 48 | 49 | protected: 50 | ui_element_instance_callback::ptr m_callback; // Don't rename this. BumpableElement uses it. 51 | }; 52 | -------------------------------------------------------------------------------- /FrameTemplate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example template with iframe 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 17 | 18 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /ProcessLocationsHandler.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: ProcessLocationsHandler.h (2024.11.27) P. Stuer **/ 3 | 4 | #pragma once 5 | 6 | #include "pch.h" 7 | 8 | /// 9 | /// Handles notifications while processing locations. 10 | /// 11 | class ProcessLocationsHandler : public process_locations_notify 12 | { 13 | public: 14 | ProcessLocationsHandler(size_t playlistIndex, size_t itemIndex, bool selectAddedItems) : _PlayListIndex(playlistIndex), _ItemIndex(itemIndex), _SelectAddedItems(selectAddedItems) 15 | { 16 | } 17 | 18 | ProcessLocationsHandler(const ProcessLocationsHandler &) = delete; 19 | ProcessLocationsHandler & operator=(const ProcessLocationsHandler &) = delete; 20 | ProcessLocationsHandler(ProcessLocationsHandler &&) = delete; 21 | ProcessLocationsHandler & operator=(ProcessLocationsHandler &&) = delete; 22 | 23 | virtual ~ProcessLocationsHandler() {} 24 | 25 | void on_completion(metadb_handle_list_cref items) override 26 | { 27 | auto Manager = playlist_manager::get(); 28 | 29 | const size_t PlayListIndex = (_PlayListIndex == (size_t) -1) ? Manager->get_active_playlist() : _PlayListIndex; 30 | 31 | if (PlayListIndex >= Manager->get_playlist_count() || (Manager->playlist_lock_get_filter_mask(PlayListIndex) & playlist_lock::filter_add)) 32 | return; 33 | 34 | pfc::bit_array_val selection(_SelectAddedItems); 35 | 36 | Manager->playlist_insert_items(PlayListIndex, _ItemIndex, items, selection); 37 | 38 | if (_SelectAddedItems) 39 | Manager->playlist_set_focus_item(PlayListIndex, _ItemIndex); 40 | } 41 | 42 | void on_aborted() override 43 | { 44 | } 45 | 46 | private: 47 | const size_t _PlayListIndex; 48 | const size_t _ItemIndex; 49 | const bool _SelectAddedItems; 50 | }; 51 | -------------------------------------------------------------------------------- /Configuration.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Configuration.h (2024.08.04) P. Stuer **/ 3 | 4 | #pragma once 5 | 6 | #include "pch.h" 7 | 8 | enum WindowSizeUnit : uint32_t 9 | { 10 | Milliseconds = 0, 11 | Samples, 12 | 13 | Count 14 | }; 15 | 16 | enum ClearOnStartup : uint32_t 17 | { 18 | None = 0, 19 | 20 | BrowsingHistory, 21 | DownloadHistory, 22 | Cookies, 23 | Cache, 24 | Passwords, 25 | Autofill, 26 | SitePermissions, 27 | 28 | All = (uint32_t) ~0, 29 | }; 30 | 31 | enum ScrollbarStyle : uint32_t 32 | { 33 | Default = 0, 34 | Fluent, 35 | }; 36 | 37 | /// 38 | /// Represents the configuration of the component. 39 | /// 40 | class configuration_t 41 | { 42 | public: 43 | configuration_t(); 44 | 45 | configuration_t & operator=(const configuration_t & other); 46 | 47 | virtual ~configuration_t() { } 48 | 49 | void Reset() noexcept; 50 | 51 | void Read(stream_reader * reader, size_t size, abort_callback & abortHandler = fb2k::noAbort, bool isPreset = false) noexcept; 52 | void Write(stream_writer * writer, abort_callback & abortHandler = fb2k::noAbort, bool isPreset = false) const noexcept; 53 | 54 | public: 55 | std::wstring _Name; 56 | std::wstring _TemplateFilePath; 57 | std::wstring _UserDataFolderPath; 58 | 59 | uint32_t _WindowSize; // Milliseconds or samples 60 | WindowSizeUnit _WindowSizeUnit; 61 | double _ReactionAlignment; // Like in Vizzy.io. Controls the delay between the actual playback and the visualization. 62 | // < 0: All samples are ahead the actual playback (with the first sample equal to the actual playback) 63 | // 0: The first half of samples are behind the actual playback and the second half are ahead of it (just like original foo_musical_spectrum and basically any get_spectrum_absolute() visualizations 64 | // > 0: All samples are behind the playback (similar to VST audio analyzer plugins like Voxengo SPAN) with the last sample equal to the actual playback. 65 | 66 | std::wstring _ProfileName; 67 | ClearOnStartup _ClearOnStartup; 68 | bool _InPrivateMode; 69 | ScrollbarStyle _ScrollbarStyle; 70 | 71 | private: 72 | const int32_t _CurrentVersion = 7; 73 | }; 74 | -------------------------------------------------------------------------------- /CUIElement.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: CUIElement.cpp (2024.07.03) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "CUIElement.h" 7 | 8 | #pragma hdrstop 9 | 10 | namespace uie 11 | { 12 | #pragma region CUIElement 13 | 14 | /// 15 | /// Initializes a new instance. 16 | /// 17 | CUIElement::CUIElement() 18 | { 19 | GetColors(); 20 | } 21 | 22 | /// 23 | /// Destroys this instance. 24 | /// 25 | CUIElement::~CUIElement() 26 | { 27 | } 28 | 29 | /// 30 | /// Creates or transfers the window. 31 | /// 32 | HWND CUIElement::create_or_transfer_window(HWND hParent, const window_host_ptr & newHost, const ui_helpers::window_position_t & position) 33 | { 34 | _hParent = hParent; 35 | 36 | if (*this == nullptr) 37 | { 38 | _Host = newHost; 39 | 40 | CRect r; 41 | 42 | position.convert_to_rect(r); 43 | 44 | Create(hParent, r, 0, WS_CHILD, 0); 45 | } 46 | else 47 | { 48 | ShowWindow(SW_HIDE); 49 | SetParent(hParent); 50 | 51 | _Host->relinquish_ownership(*this); 52 | _Host = newHost; 53 | 54 | SetWindowPos(NULL, position.x, position.y, (int) position.cx, (int) position.cy, SWP_NOZORDER); 55 | } 56 | 57 | CUIColorClient::Register(this); 58 | 59 | return *this; 60 | } 61 | 62 | /// 63 | /// Destroys the window. 64 | /// 65 | void CUIElement::destroy_window() 66 | { 67 | CUIColorClient::Unregister(this); 68 | 69 | ::DestroyWindow(*this); 70 | 71 | _Host.release(); 72 | } 73 | 74 | /// 75 | /// Gets the colors. 76 | /// 77 | void CUIElement::GetColors() noexcept 78 | { 79 | cui::colours::helper Helper(pfc::guid_null); 80 | 81 | _ForegroundColor = Helper.get_colour(cui::colours::colour_text); 82 | _BackgroundColor = Helper.get_colour(cui::colours::colour_background); 83 | } 84 | 85 | static uie::window_factory _WindowFactory; 86 | 87 | #pragma endregion 88 | 89 | #pragma region CUIColorClient 90 | 91 | void CUIColorClient::on_colour_changed(uint32_t changed_items_mask) const 92 | { 93 | for (auto Iter : _Elements) 94 | Iter->OnColorsChanged(); 95 | } 96 | 97 | void CUIColorClient::on_bool_changed(uint32_t changed_items_mask) const 98 | { 99 | for (auto Iter : _Elements) 100 | Iter->OnColorsChanged(); 101 | } 102 | 103 | static cui::colours::client::factory _CUIColorClientFactory; 104 | 105 | #pragma endregion 106 | } 107 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /foo_uie_webview.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Rendering.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Rendering.cpp (2024.07.03) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "UIElement.h" 7 | #include "Encoding.h" 8 | #include "Exceptions.h" 9 | #include "Support.h" 10 | 11 | #pragma hdrstop 12 | 13 | /// 14 | /// Starts the timer. 15 | /// 16 | void UIElement::StartTimer() noexcept 17 | { 18 | ::SetTimer(m_hWnd, (UINT_PTR) this, 1000 / (DWORD) 50, (TIMERPROC) TimerCallback); 19 | } 20 | 21 | /// 22 | /// Stops the timer. 23 | /// 24 | void UIElement::StopTimer() noexcept 25 | { 26 | ::KillTimer(m_hWnd, (UINT_PTR) this); 27 | } 28 | 29 | /// 30 | /// Handles a timer tick. 31 | /// 32 | void CALLBACK UIElement::TimerCallback(HWND hWnd, UINT msg, UINT_PTR timerId, DWORD time) noexcept 33 | { 34 | ((UIElement *) timerId)->OnTimer(); 35 | } 36 | 37 | /// 38 | /// Handles a timer tick. 39 | /// 40 | void UIElement::OnTimer() noexcept 41 | { 42 | if (_IsFrozen || _IsHidden || ::IsIconic(core_api::get_main_window()) || (_WebView == nullptr) || !_IsNavigationCompleted) 43 | return; 44 | 45 | double PlaybackTime; // in seconds 46 | 47 | if (!_VisualisationStream.is_valid() || !_VisualisationStream->get_absolute_time(PlaybackTime) || (PlaybackTime == _LastPlaybackTime)) 48 | return; 49 | 50 | _LastPlaybackTime = PlaybackTime; 51 | 52 | audio_chunk_impl Chunk; 53 | 54 | const double WindowSize = _Configuration._WindowSize / ((_Configuration._WindowSizeUnit == WindowSizeUnit::Milliseconds) ? 1000. : (double) _SampleRate); // in seconds 55 | const double WindoOffset = PlaybackTime - (WindowSize * (0.5 + _Configuration._ReactionAlignment)); // in seconds 56 | 57 | if (!_VisualisationStream->get_chunk_absolute(Chunk, WindoOffset, WindowSize)) 58 | return; 59 | 60 | const audio_sample * Samples = Chunk.get_data(); 61 | size_t SampleCount = Chunk.get_sample_count(); 62 | _SampleRate = Chunk.get_sample_rate(); 63 | uint32_t ChannelCount = Chunk.get_channel_count(); 64 | uint32_t ChannelConfig = Chunk.get_channel_config(); 65 | 66 | HRESULT hr = PostChunk(Samples, SampleCount, _SampleRate, ChannelCount, ChannelConfig); 67 | 68 | if (!SUCCEEDED(hr)) 69 | return; 70 | 71 | hr = _WebView->ExecuteScript(::FormatText(L"onTimer(%d, %d, %d, %d)", SampleCount, _SampleRate, ChannelCount, ChannelConfig).c_str(), nullptr); // Silently continue 72 | 73 | if (!SUCCEEDED(hr)) 74 | { 75 | console::print(::GetErrorMessage(hr, STR_COMPONENT_BASENAME " failed to call onTimer()").c_str()); 76 | 77 | StopTimer(); 78 | } 79 | } 80 | 81 | /// 82 | /// Posts a chunk to the script via a shared buffer. 83 | /// 84 | HRESULT UIElement::PostChunk(const audio_sample * samples, size_t sampleCount, uint32_t sampleRate, uint32_t channelCount, uint32_t channelConfig) noexcept 85 | { 86 | HRESULT hr = _SharedBuffer.Ensure(_Environment, _WebView, sampleCount, sampleRate, channelCount, channelConfig); 87 | 88 | if (SUCCEEDED(hr)) 89 | if (audio_sample_size == 64) 90 | _SharedBuffer.Copy((const BYTE *) samples, sizeof(audio_sample) * sampleCount * channelCount); 91 | else 92 | _SharedBuffer.Convert((const float *) samples, sampleCount); 93 | 94 | return S_OK; 95 | } 96 | -------------------------------------------------------------------------------- /FileWatcher.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: FileWatcher.cpp (2024.05.25) P. Stuer - Implements a file system watcher. **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "FileWatcher.h" 7 | #include "Exceptions.h" 8 | #include "Resources.h" 9 | 10 | #include 11 | #pragma comment(lib, "pathcch") 12 | 13 | /// 14 | /// 15 | /// 16 | void FileWatcher::Start(HWND hWnd, const std::wstring & filePath) 17 | { 18 | _ThreadParameters.hWnd = hWnd; 19 | _ThreadParameters.FilePath = filePath; 20 | 21 | _hThread = ::CreateThread(nullptr, 0, ThreadProc, &_ThreadParameters, 0, nullptr); 22 | 23 | if (_hThread == NULL) 24 | throw Win32Exception(::GetLastError(), "Failed to create file system watcher thread"); 25 | } 26 | 27 | /// 28 | /// 29 | /// 30 | void FileWatcher::Stop() noexcept 31 | { 32 | if (_hThread != NULL) 33 | { 34 | ::CloseHandle(_hThread); 35 | _hThread = NULL; 36 | } 37 | } 38 | 39 | /// 40 | /// Thread procedure 41 | /// 42 | DWORD WINAPI FileWatcher::ThreadProc(LPVOID lParam) noexcept 43 | { 44 | auto Parameters = (FileWatcher::thread_parameters_t *) lParam; 45 | 46 | if (Parameters == nullptr) 47 | return 1; 48 | 49 | wchar_t DirectoryPath[MAX_PATH]; ::wcscpy_s(DirectoryPath, _countof(DirectoryPath), Parameters->FilePath.c_str()); 50 | 51 | wchar_t * FileName = ::PathFindFileNameW(DirectoryPath); 52 | 53 | HRESULT hr = ::PathCchRemoveFileSpec(DirectoryPath, _countof(DirectoryPath)); 54 | 55 | if (!SUCCEEDED(hr)) 56 | return (DWORD) hr; 57 | 58 | HANDLE hDirectory = ::CreateFileW(DirectoryPath, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); 59 | 60 | if (hDirectory == INVALID_HANDLE_VALUE) 61 | return ::GetLastError(); 62 | 63 | HANDLE hIoCompletionPort = ::CreateIoCompletionPort(hDirectory, NULL, 0, 1); 64 | 65 | if (hIoCompletionPort == NULL) 66 | return ::GetLastError(); 67 | 68 | const DWORD NotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY; 69 | 70 | uint8_t Data[1024] = {}; 71 | OVERLAPPED Overlapped = {}; 72 | 73 | BOOL Success = ::ReadDirectoryChangesW(hDirectory, Data, sizeof(Data), FALSE, NotifyFilter, nullptr, &Overlapped, nullptr); 74 | 75 | while (Success) 76 | { 77 | DWORD BytesRead; 78 | ULONG_PTR CompletionKey; 79 | LPOVERLAPPED OverlappedComplete; 80 | 81 | Success = ::GetQueuedCompletionStatus(hIoCompletionPort, &BytesRead, &CompletionKey, &OverlappedComplete, INFINITE); 82 | 83 | auto fni = (FILE_NOTIFY_INFORMATION *) Data; 84 | 85 | for (;;) 86 | { 87 | if (::wcsncmp(FileName, fni->FileName, fni->FileNameLength / 2) == 0) 88 | ::PostMessageW(Parameters->hWnd, UM_TEMPLATE_CHANGED, 0, 0); 89 | 90 | if (fni->NextEntryOffset == 0) 91 | break; 92 | 93 | fni = (FILE_NOTIFY_INFORMATION *)(((uint8_t *) fni) + fni->NextEntryOffset); 94 | } 95 | 96 | Success = ::ReadDirectoryChangesW(hDirectory, Data, sizeof(Data), FALSE, NotifyFilter, nullptr, &Overlapped, nullptr); 97 | } 98 | 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /SharedBuffer.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: SharedBuffer.cpp (2024.07.03) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "SharedBuffer.h" 7 | 8 | #include "Encoding.h" 9 | 10 | #pragma hdrstop 11 | 12 | using namespace Microsoft::WRL; 13 | 14 | /// 15 | /// Ensures that a buffer of the correct size is posted to the WebView. 16 | /// 17 | HRESULT SharedBuffer::Ensure(wil::com_ptr & environment, wil::com_ptr & webView, size_t sampleCount, uint32_t sampleRate, uint32_t channelCount, uint32_t channelConfig) noexcept 18 | { 19 | if ((_Buffer != nullptr) && (_SampleCount == sampleCount) && (_SampleRate == sampleRate) && (_ChannelCount == channelCount) && (_ChannelConfig == channelConfig)) 20 | return S_OK; 21 | 22 | Release(); 23 | 24 | HRESULT hr = environment->QueryInterface(IID_PPV_ARGS(&_Environment12)); 25 | 26 | if (!SUCCEEDED(hr)) 27 | return hr; 28 | 29 | _WebView17 = webView.try_query(); 30 | 31 | if (_WebView17 == nullptr) 32 | return E_NOINTERFACE; 33 | 34 | _Size = sizeof(double) * sampleCount * channelCount; // Don't use audio_sample. 35 | 36 | hr = _Environment12->CreateSharedBuffer(_Size, &_SharedBuffer); 37 | 38 | if (!SUCCEEDED(hr)) 39 | return hr; 40 | 41 | hr = _SharedBuffer->get_Buffer(&_Buffer); 42 | 43 | if (!SUCCEEDED(hr)) 44 | return hr; 45 | 46 | std::wstring AdditionalDataAsJson = ::FormatText(L"{\"SampleCount\":%d,\"SampleRate\":%d,\"ChannelCount\":%d,\"ChannelConfig\":%d}", (int) sampleCount, (int) sampleRate, (int) channelCount, (int) channelConfig); 47 | 48 | hr = _WebView17->PostSharedBufferToScript(_SharedBuffer.get(), COREWEBVIEW2_SHARED_BUFFER_ACCESS_READ_WRITE, AdditionalDataAsJson.c_str()); 49 | 50 | if (!SUCCEEDED(hr)) 51 | return hr; 52 | 53 | _SampleCount = sampleCount; 54 | _SampleRate = sampleRate; 55 | _ChannelCount = channelCount; 56 | _ChannelConfig = channelConfig; 57 | 58 | return S_OK; 59 | } 60 | 61 | /// 62 | /// Releases the resources of this instance. 63 | /// 64 | void SharedBuffer::Release() noexcept 65 | { 66 | _ChannelConfig = 0; 67 | _ChannelCount = 0; 68 | _SampleRate = 0; 69 | _SampleCount = 0; 70 | 71 | _Buffer = nullptr; 72 | _SharedBuffer = nullptr; 73 | _WebView17 = nullptr; 74 | _Environment12 = nullptr; 75 | } 76 | 77 | /// 78 | /// Copies data to the buffer. 79 | /// 80 | void SharedBuffer::Copy(const BYTE * data, size_t size) noexcept 81 | { 82 | if (_Buffer == nullptr) 83 | return; 84 | 85 | if (_Size < size) 86 | size = (size_t) _Size; 87 | 88 | ::memcpy(_Buffer, data, size); 89 | } 90 | 91 | /// 92 | /// Fills the buffer with samples converted from 32-bit to 64-floats. 93 | /// 94 | void SharedBuffer::Convert(const float * sampleData, size_t sampleCount) noexcept 95 | { 96 | if (_Buffer == nullptr) 97 | return; 98 | 99 | size_t Size = sizeof(double) * sampleCount * _ChannelCount; 100 | 101 | if (_Size < Size) 102 | sampleCount = _SampleCount; 103 | 104 | const float * p = sampleData; 105 | double * q = (double *) _Buffer; 106 | 107 | for (size_t i = 0; i < sampleCount * _ChannelCount; ++i) 108 | *q++ = (double) *p++; 109 | } 110 | 111 | /// 112 | /// Deletes this instance. 113 | /// 114 | SharedBuffer::~SharedBuffer() 115 | { 116 | Release(); 117 | } 118 | -------------------------------------------------------------------------------- /Resources.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Resources.h (2024.12.15) P. Stuer **/ 3 | 4 | #pragma once 5 | 6 | #define TOSTRING_IMPL(x) #x 7 | #define TOSTRING(x) TOSTRING_IMPL(x) 8 | 9 | #define NUM_FILE_MAJOR 0 10 | #define NUM_FILE_MINOR 2 11 | #define NUM_FILE_PATCH 1 12 | #define NUM_FILE_PRERELEASE 0 13 | 14 | #define NUM_PRODUCT_MAJOR 0 15 | #define NUM_PRODUCT_MINOR 2 16 | #define NUM_PRODUCT_PATCH 1 17 | #define NUM_PRODUCT_PRERELEASE 0 18 | 19 | #define STR_PRERELEASE "" 20 | 21 | /** Component specific **/ 22 | 23 | #define STR_COMPONENT_NAME "WebView" 24 | #define STR_COMPONENT_VERSION TOSTRING(NUM_FILE_MAJOR) "." TOSTRING(NUM_FILE_MINOR) "." TOSTRING(NUM_FILE_PATCH) "." TOSTRING(NUM_FILE_PRERELEASE) STR_PRERELEASE 25 | #define STR_COMPONENT_BASENAME "foo_uie_webview" 26 | #define STR_COMPONENT_FILENAME STR_COMPONENT_BASENAME ".dll" 27 | #define STR_COMPONENT_COMPANY_NAME "" 28 | #define STR_COMPONENT_COPYRIGHT "Copyright (c) 2024 P. Stuer. All rights reserved." 29 | #define STR_COMPONENT_COMMENTS "" 30 | #define STR_COMPONENT_DESCRIPTION "A WebView2 wrapper for foobar2000" 31 | #define STR_COMPONENT_COMMENT "" 32 | 33 | /** Generic **/ 34 | 35 | #define STR_COMPANY_NAME TEXT(STR_COMPONENT_COMPANY_NAME) 36 | #define STR_INTERNAL_NAME TEXT(STR_COMPONENT_NAME) 37 | #define STR_COMMENTS TEXT(STR_COMPONENT_COMMENTS) 38 | #define STR_COPYRIGHT TEXT(STR_COMPONENT_COPYRIGHT) 39 | 40 | #define STR_FILE_NAME TEXT(STR_COMPONENT_FILENAME) 41 | #define STR_FILE_VERSION TOSTRING(NUM_FILE_MAJOR) TEXT(".") TOSTRING(NUM_FILE_MINOR) TEXT(".") TOSTRING(NUM_FILE_PATCH) TEXT(".") TOSTRING(NUM_FILE_PRERELEASE) STR_PRERELEASE 42 | #define STR_FILE_DESCRIPTION TEXT(STR_COMPONENT_DESCRIPTION) 43 | 44 | #define STR_PRODUCT_NAME STR_INTERNAL_NAME 45 | #define STR_PRODUCT_VERSION TOSTRING(NUM_PRODUCT_MAJOR) TEXT(".") TOSTRING(NUM_PRODUCT_MINOR) TEXT(".") TOSTRING(NUM_PRODUCT_PATCH) TEXT(".") TOSTRING(NUM_PRODUCT_PRERELEASE) STR_PRERELEASE 46 | 47 | #define STR_ABOUT_NAME STR_INTERNAL_NAME 48 | #define STR_ABOUT_WEB TEXT("https://github.com/stuerp/") STR_COMPONENT_BASENAME 49 | #define STR_ABOUT_EMAIL TEXT("mailto:peter.stuer@outlook.com") 50 | 51 | /** Window **/ 52 | 53 | #define GUID_UI_ELEMENT {0xdabae3e2, 0xd31d, 0x4faa, { 0x88, 0xae, 0xf1, 0x50, 0xd6, 0x80, 0x33, 0x85}}; 54 | #define GUID_PREFERENCES {0xb18587e0, 0x9c95, 0x4ee3, { 0x8e, 0x9f, 0xaa, 0x8c, 0x77, 0xec, 0x2f, 0x85}}; 55 | #define STR_WINDOW_CLASS_NAME STR_COMPONENT_BASENAME "_{A1D51583-D8B7-40CF-88EC-B4C0AB194140}" 56 | 57 | /** Messages **/ 58 | 59 | #define UM_TEMPLATE_CHANGED WM_USER + 100 60 | #define UM_WEB_VIEW_READY WM_USER + 101 61 | #define UM_ASYNC WM_USER + 102 62 | 63 | /** Configuration **/ 64 | 65 | #define IDD_PREFERENCES 101 66 | 67 | #define IDC_NAME 1000 68 | 69 | #define IDC_USER_DATA_FOLDER_PATH 1002 70 | #define IDC_USER_DATA_FOLDER_PATH_SELECT 1004 71 | 72 | #define IDC_FILE_PATH 1006 73 | #define IDC_FILE_PATH_SELECT 1008 74 | #define IDC_FILE_PATH_EDIT 1010 75 | 76 | #define IDC_WINDOW_SIZE 1020 77 | #define IDC_WINDOW_SIZE_UNIT 1022 78 | 79 | #define IDC_REACTION_ALIGNMENT 1030 80 | #define IDC_WINDOW_OFFSET 1032 81 | 82 | #define IDC_CLEAR_BROWSING_DATA 1040 83 | #define IDC_IN_PRIVATE_MODE 1042 84 | #define IDC_SCROLLBAR_STYLE 1044 85 | 86 | #define IDC_WARNING 9999 87 | 88 | #define IDR_CONTEXT_MENU_ICON 2000 89 | -------------------------------------------------------------------------------- /PreferencesLayout.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: PreferencesLayout.h (2024.08.04) **/ 3 | 4 | #pragma once 5 | 6 | #define W_A00 332 // Dialog width as set by foobar2000, in dialog units 7 | #define H_A00 288 // Dialog height as set by foobar2000, in dialog units 8 | 9 | #define H_LBL 8 // Label 10 | 11 | #define W_BTN 50 // Button 12 | #define H_BTN 14 // Button 13 | 14 | #define H_EBX 14 // Edit box 15 | #define H_CBX 14 // Combo box 16 | 17 | #define W_CHB 10 // Check box 18 | #define H_CHB 10 // Check box 19 | 20 | #define DX 7 21 | #define DY 7 22 | 23 | #define IX 4 // Spacing between two related controls 24 | #define IY 3 25 | 26 | #pragma region Name 27 | 28 | // Label 29 | #define X_D11 0 30 | #define Y_D11 0 31 | #define W_D11 76 32 | #define H_D11 H_LBL 33 | 34 | // EditBox 35 | #define X_D12 X_D11 + W_D11 + IX 36 | #define Y_D12 Y_D11 37 | #define W_D12 160 38 | #define H_D12 H_EBX 39 | 40 | #pragma endregion 41 | 42 | #pragma region User Data Folder Path 43 | 44 | // Label 45 | #define X_D13 0 46 | #define Y_D13 Y_D12 + H_D12 + IY 47 | #define W_D13 76 48 | #define H_D13 H_LBL 49 | 50 | // EditBox 51 | #define X_D14 X_D13 52 | #define Y_D14 Y_D13 + H_D13 + IY 53 | #define W_D14 240 54 | #define H_D14 H_EBX 55 | 56 | // Button: Select 57 | #define X_D15 X_D14 + W_D14 + IX 58 | #define Y_D15 Y_D14 59 | #define W_D15 16 60 | #define H_D15 H_BTN 61 | 62 | #pragma endregion 63 | 64 | #pragma region Template File Path 65 | 66 | // Label 67 | #define X_D16 0 68 | #define Y_D16 Y_D14 + H_D14 + IY 69 | #define W_D16 76 70 | #define H_D16 H_LBL 71 | 72 | // EditBox 73 | #define X_D17 X_D16 74 | #define Y_D17 Y_D16 + H_D16 + IY 75 | #define W_D17 240 76 | #define H_D17 H_EBX 77 | 78 | // Button: Select 79 | #define X_D18 X_D17 + W_D17 + IX 80 | #define Y_D18 Y_D17 81 | #define W_D18 16 82 | #define H_D18 H_BTN 83 | 84 | // Button: Edit 85 | #define X_D19 X_D18 + W_D18 + IX 86 | #define Y_D19 Y_D18 87 | #define W_D19 W_BTN 88 | #define H_D19 H_BTN 89 | 90 | #pragma endregion 91 | 92 | #pragma region Window Size 93 | 94 | // Label 95 | #define X_D20 0 96 | #define Y_D20 Y_D17 + H_D17 + IY 97 | #define W_D20 76 98 | #define H_D20 H_LBL 99 | 100 | // EditBox 101 | #define X_D21 X_D20 + W_D20 + IX 102 | #define Y_D21 Y_D20 103 | #define W_D21 30 104 | #define H_D21 H_EBX 105 | 106 | // ComboBox 107 | #define X_D22 X_D21 + W_D21 + IX 108 | #define Y_D22 Y_D20 109 | #define W_D22 44 110 | #define H_D22 H_CBX 111 | 112 | #pragma endregion 113 | 114 | #pragma region Reaction Alignment 115 | 116 | // Label 117 | #define X_D23 0 118 | #define Y_D23 Y_D21 + H_D21 + IY 119 | #define W_D23 76 120 | #define H_D23 H_LBL 121 | 122 | // EditBox 123 | #define X_D24 X_D23 + W_D23 + IX 124 | #define Y_D24 Y_D23 125 | #define W_D24 30 126 | #define H_D24 H_EBX 127 | 128 | // Label 129 | #define X_D25 X_D24 + W_D24 + IX 130 | #define Y_D25 Y_D24 131 | #define W_D25 100 132 | #define H_D25 H_LBL 133 | 134 | #pragma endregion 135 | 136 | // Checkbox: Clear browsing data on startup 137 | #define X_D26 0 138 | #define Y_D26 Y_D24 + H_D24 + IY 139 | #define W_D26 160 140 | #define H_D26 H_LBL 141 | 142 | // Checkbox: In Private mode 143 | #define X_D27 0 144 | #define Y_D27 Y_D26 + H_D26 + IY 145 | #define W_D27 160 146 | #define H_D27 H_LBL 147 | 148 | // Checkbox: Fluent scrollbar style 149 | #define X_D28 0 150 | #define Y_D28 Y_D27 + H_D27 + IY 151 | #define W_D28 160 152 | #define H_D28 H_LBL 153 | 154 | // Warning 155 | #define X_D99 0 156 | #define Y_D99 H_A00 - H_LBL 157 | #define W_D99 W_A00 158 | #define H_D99 H_LBL 159 | -------------------------------------------------------------------------------- /DUIElement.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: DUIElement.cpp (2024.07.07) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "DUIElement.h" 7 | 8 | #pragma hdrstop 9 | 10 | #pragma region ui_element_instance interface 11 | 12 | /// 13 | /// Initializes a new instance. 14 | /// 15 | DUIElement::DUIElement(ui_element_config::ptr data, ui_element_instance_callback::ptr callback) : m_callback(callback) 16 | { 17 | set_configuration(data); 18 | 19 | GetColors(); 20 | } 21 | 22 | /// 23 | /// Retrieves the name of the element. 24 | /// 25 | void DUIElement::g_get_name(pfc::string_base & name) 26 | { 27 | name = STR_COMPONENT_NAME; 28 | } 29 | 30 | /// 31 | /// Retrieves the description of the element. 32 | /// 33 | const char * DUIElement::g_get_description() 34 | { 35 | return "Provides access to a WebView2 control"; 36 | } 37 | 38 | /// 39 | /// Retrieves the GUID of the element. 40 | /// 41 | GUID DUIElement::g_get_guid() 42 | { 43 | return UIElement::GetGUID(); 44 | } 45 | 46 | /// 47 | /// Retrieves the subclass GUID of the element. 48 | /// 49 | GUID DUIElement::g_get_subclass() 50 | { 51 | return ui_element_subclass_utility; 52 | } 53 | 54 | /// 55 | /// Retrieves the default configuration of the element. 56 | /// 57 | ui_element_config::ptr DUIElement::g_get_default_configuration() 58 | { 59 | configuration_t DefaultConfiguration; 60 | 61 | ui_element_config_builder Builder; 62 | 63 | DefaultConfiguration.Write(&Builder.m_stream); 64 | 65 | return Builder.finish(g_get_guid()); 66 | } 67 | 68 | /// 69 | /// Initializes the element's windows. 70 | /// 71 | void DUIElement::initialize_window(HWND p_parent) 72 | { 73 | const DWORD Style = 0; 74 | const DWORD ExStyle = 0; 75 | 76 | this->Create(p_parent, nullptr, nullptr, Style, ExStyle); 77 | } 78 | 79 | /// 80 | /// Alters element's current configuration. Specified ui_element_config's GUID must be the same as this element's GUID. 81 | /// 82 | void DUIElement::set_configuration(ui_element_config::ptr data) 83 | { 84 | ui_element_config_parser Parser(data); 85 | 86 | _Configuration.Read(&Parser.m_stream, Parser.get_remaining()); 87 | } 88 | 89 | /// 90 | /// Retrieves element's current configuration. Returned object's GUID must be set to your element's GUID so your element can be re-instantiated with stored settings. 91 | /// 92 | ui_element_config::ptr DUIElement::get_configuration() 93 | { 94 | ui_element_config_builder Builder; 95 | 96 | _Configuration.Write(&Builder.m_stream); 97 | 98 | return Builder.finish(g_get_guid()); 99 | } 100 | 101 | /// 102 | /// Used by the host to notify the element about various events. 103 | /// See ui_element_notify_* GUIDs for possible "what" parameter; meaning of other parameters depends on the "what" value. 104 | /// Container classes should dispatch all notifications to their children. 105 | /// 106 | void DUIElement::notify(const GUID & what, t_size param1, const void * param2, t_size param2Size) 107 | { 108 | if (what == ui_element_notify_edit_mode_changed) 109 | { 110 | SetWebViewVisibility(IsWebViewVisible()); 111 | } 112 | else 113 | if (what == ui_element_notify_colors_changed) 114 | { 115 | OnColorsChanged(); 116 | } 117 | /* 118 | else 119 | if (what == ui_element_notify_font_changed) 120 | { 121 | } 122 | else 123 | if (what == ui_element_notify_visibility_changed) 124 | { 125 | } 126 | */ 127 | } 128 | 129 | /// 130 | /// Gets the colors. 131 | /// 132 | void DUIElement::GetColors() noexcept 133 | { 134 | _ForegroundColor = (COLORREF) m_callback->query_std_color(ui_color_text); 135 | _BackgroundColor = (COLORREF) m_callback->query_std_color(ui_color_background); 136 | } 137 | 138 | static service_factory_single_t> _Factory; 139 | 140 | #pragma endregion 141 | -------------------------------------------------------------------------------- /Resources.rc: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Resources.rc (2024.08.04) P. Stuer **/ 3 | 4 | #include "Resources.h" 5 | 6 | #include 7 | 8 | language LANG_ENGLISH, SUBLANG_ENGLISH_US 9 | #pragma code_page(1252) 10 | 11 | VS_VERSION_INFO versioninfo 12 | fileversion NUM_FILE_MAJOR, NUM_FILE_MINOR, NUM_FILE_PATCH, NUM_FILE_PRERELEASE 13 | productversion NUM_PRODUCT_MAJOR, NUM_PRODUCT_MINOR, NUM_PRODUCT_PATCH, NUM_PRODUCT_PRERELEASE 14 | fileflagsmask 0x3FL 15 | #ifdef _DEBUG 16 | fileflags VS_FF_DEBUG 17 | #else 18 | fileflags 0 19 | #endif 20 | fileos VOS_NT_WINDOWS32 21 | filetype VFT_DLL 22 | filesubtype VFT2_UNKNOWN 23 | { 24 | block "StringFileInfo" 25 | { 26 | block "040904E4" // U.S. English, Multilingual character set 27 | { 28 | value "FileVersion", STR_FILE_VERSION "\0" 29 | value "FileDescription", STR_FILE_DESCRIPTION "\0" 30 | value "LegalCopyright", STR_COPYRIGHT "\0" 31 | value "LegalTrademarks", "\0" 32 | value "Comments", STR_COMMENTS "\0" 33 | value "CompanyName", STR_COMPANY_NAME "\0" 34 | value "InternalName", STR_INTERNAL_NAME "\0" 35 | value "OriginalFilename", STR_FILE_NAME "\0" 36 | value "ProductName", STR_PRODUCT_NAME "\0" 37 | value "ProductVersion", STR_PRODUCT_VERSION "\0" 38 | } 39 | } 40 | 41 | block "VarFileInfo" 42 | { 43 | value "Translation", 0x409, 1252 // U.S. English, Multilingual character set 44 | } 45 | } 46 | 47 | #include "PreferencesLayout.h" 48 | 49 | IDD_PREFERENCES dialogex 0, 0, 0, 0 50 | style DS_SETFONT | WS_CHILD 51 | font 8, "Segoe UI", 400, 0, 1 52 | { 53 | rtext "Name:", IDC_STATIC, X_D11, Y_D11 + 2, W_D11, H_D11 54 | edittext IDC_NAME, X_D12, Y_D12, W_D12, H_D12, ES_AUTOHSCROLL 55 | 56 | ltext "User data folder path", IDC_STATIC, X_D13, Y_D13 + 2, W_D13, H_D13 57 | edittext IDC_USER_DATA_FOLDER_PATH, X_D14, Y_D14, W_D14, H_D14, ES_AUTOHSCROLL 58 | pushbutton "...", IDC_USER_DATA_FOLDER_PATH_SELECT, X_D15, Y_D15, W_D15, H_D15 59 | 60 | ltext "Template file path", IDC_STATIC, X_D16, Y_D16 + 2, W_D16, H_D16 61 | edittext IDC_FILE_PATH, X_D17, Y_D17, W_D17, H_D17, ES_AUTOHSCROLL 62 | pushbutton "...", IDC_FILE_PATH_SELECT, X_D18, Y_D18, W_D18, H_D18 63 | pushbutton "&Edit", IDC_FILE_PATH_EDIT, X_D19, Y_D19, W_D19, H_D19 64 | 65 | rtext "Window size:", IDC_STATIC, X_D20, Y_D20 + 2, W_D20, H_D20 66 | edittext IDC_WINDOW_SIZE, X_D21, Y_D21, W_D21, H_D21, ES_RIGHT | ES_AUTOHSCROLL | WS_TABSTOP 67 | combobox IDC_WINDOW_SIZE_UNIT, X_D22, Y_D22, W_D22, H_D22, CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP 68 | 69 | rtext "Reaction alignment:" IDC_STATIC, X_D23, Y_D23 + 2, W_D23, H_D23 70 | edittext IDC_REACTION_ALIGNMENT X_D24, Y_D24, W_D24, H_D24, ES_RIGHT | ES_AUTOHSCROLL | WS_TABSTOP 71 | ltext "", IDC_WINDOW_OFFSET, X_D25, Y_D25 + 2, W_D25, H_D25 72 | 73 | control "Clear browsing data on startup", IDC_CLEAR_BROWSING_DATA, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, X_D26, Y_D26, W_D26, H_D26 74 | control "In Private mode", IDC_IN_PRIVATE_MODE, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, X_D27, Y_D27, W_D27, H_D27 75 | control "Fluent scrollbar style", IDC_SCROLLBAR_STYLE, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, X_D28, Y_D28, W_D28, H_D28 76 | 77 | ltext "Restart the component to activate changed settings", IDC_WARNING, X_D99, Y_D99, W_D99, H_D99, NOT WS_VISIBLE 78 | } 79 | 80 | IDR_CONTEXT_MENU_ICON rcdata "Main.ico" 81 | 82 | 1 typelib "foo_uie_webview.tlb" 83 | -------------------------------------------------------------------------------- /CUIElement.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: CUIElement.h (2024.07.05) P. Stuer - Columns User Interface support **/ 3 | 4 | #pragma once 5 | 6 | #include "framework.h" 7 | 8 | #include "UIElement.h" 9 | 10 | #include 11 | 12 | namespace uie 13 | { 14 | class CUIColorClient; 15 | 16 | /// 17 | /// Implements a Columns UI element. 18 | /// 19 | class CUIElement : public UIElement, public uie::window 20 | { 21 | public: 22 | CUIElement(); 23 | 24 | CUIElement(const CUIElement &) = delete; 25 | CUIElement & operator=(const CUIElement &) = delete; 26 | CUIElement(CUIElement &&) = delete; 27 | CUIElement & operator=(CUIElement &&) = delete; 28 | 29 | virtual ~CUIElement(); 30 | 31 | #pragma region uie::window interface 32 | 33 | /// 34 | /// Gets the category of the extension. 35 | /// 36 | void get_category(pfc::string_base & out) const final 37 | { 38 | out = "Panels"; 39 | } 40 | 41 | /// 42 | /// Gets the type of the extension. 43 | /// 44 | uint32_t get_type() const final 45 | { 46 | return uie::type_panel; 47 | } 48 | 49 | HWND create_or_transfer_window(HWND parent, const window_host_ptr & newHost, const ui_helpers::window_position_t & position); 50 | 51 | virtual void destroy_window(); 52 | 53 | /// 54 | /// Returns true if the extension is available. 55 | /// 56 | virtual bool is_available(const window_host_ptr & p) const 57 | { 58 | return true; 59 | } 60 | 61 | /// 62 | /// Gets the window handle of the extension. 63 | /// 64 | virtual HWND get_wnd() const 65 | { 66 | return *this; 67 | } 68 | 69 | #pragma endregion 70 | 71 | #pragma region extension_base interface 72 | 73 | /// 74 | /// Gets the unique ID of extension. 75 | /// 76 | const GUID & get_extension_guid() const 77 | { 78 | return GetGUID(); 79 | } 80 | 81 | /// 82 | /// Gets a user-readable name of the extension. 83 | /// 84 | void get_name(pfc::string_base & out) const 85 | { 86 | out = STR_COMPONENT_NAME; 87 | } 88 | 89 | /// 90 | /// Sets an instance of the configuration data. 91 | /// 92 | void set_config(stream_reader * reader, size_t size, abort_callback & abortHandler) final 93 | { 94 | _Configuration.Read(reader, size, abortHandler); 95 | } 96 | 97 | /// 98 | /// Gets an instance of the configuration data. 99 | /// 100 | void get_config(stream_writer * writer, abort_callback & abortHandler) const final 101 | { 102 | _Configuration.Write(writer, abortHandler); 103 | } 104 | 105 | #pragma endregion 106 | 107 | virtual bool IsWebViewVisible() const noexcept 108 | { 109 | return true; 110 | } 111 | 112 | void GetColors() noexcept override; 113 | 114 | private: 115 | window_host_ptr _Host; 116 | HWND _hParent; 117 | }; 118 | 119 | static std::vector _Elements; // Very ugly but necessary because of the weird CUI notification mechanism. 120 | 121 | /// 122 | /// Receives notifications from CUI when the colors change. 123 | /// 124 | class CUIColorClient : public cui::colours::client 125 | { 126 | public: 127 | CUIColorClient() { } 128 | 129 | CUIColorClient(const CUIColorClient &) = delete; 130 | CUIColorClient & operator=(const CUIColorClient &) = delete; 131 | CUIColorClient(CUIColorClient &&) = delete; 132 | CUIColorClient & operator=(CUIColorClient &&) = delete; 133 | 134 | virtual ~CUIColorClient() { } 135 | 136 | #pragma region cui::colours::client 137 | 138 | virtual const GUID & get_client_guid() const 139 | { 140 | static const GUID guid = GUID_UI_ELEMENT; 141 | 142 | return guid; 143 | } 144 | 145 | virtual void get_name(pfc::string_base & out) const 146 | { 147 | out = STR_COMPONENT_NAME; 148 | } 149 | 150 | /// 151 | /// Return a combination of bool_flag_t to indicate which boolean flags are supported. 152 | /// 153 | virtual uint32_t get_supported_bools() const override 154 | { 155 | return cui::colours::bool_dark_mode_enabled; 156 | } 157 | 158 | /// 159 | /// Indicates whether the Theme API is supported. 160 | /// 161 | virtual bool get_themes_supported() const override 162 | { 163 | return false; 164 | } 165 | 166 | virtual void on_colour_changed(uint32_t changed_items_mask) const override; 167 | 168 | /// 169 | /// Called whenever a supported boolean flag changes. Support for a flag is determined using the get_supported_bools() method. 170 | /// 171 | virtual void on_bool_changed(uint32_t changed_items_mask) const override; 172 | 173 | #pragma endregion 174 | 175 | /// 176 | /// Registers a CUIElement with this client. 177 | /// 178 | static void Register(CUIElement * element) 179 | { 180 | if (element == nullptr) 181 | return; 182 | 183 | _Elements.push_back(element); 184 | } 185 | 186 | /// 187 | /// Unregisters a CUIElement from this client. 188 | /// 189 | static void Unregister(CUIElement * element) 190 | { 191 | auto Element = std::find(_Elements.begin(), _Elements.end(), element); 192 | 193 | if (Element != _Elements.end()) 194 | _Elements.erase(Element); 195 | } 196 | }; 197 | 198 | } 199 | -------------------------------------------------------------------------------- /HostObject.idl: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: HostObject.idl (2024.12.01) P. Stuer **/ 3 | 4 | import "oaidl.idl"; 5 | import "ocidl.idl"; 6 | 7 | [uuid(661566A3-CB7A-4901-A64B-4C0C603BBA48), version(0.2)] 8 | library HostObjectLibrary 9 | { 10 | struct image_t 11 | { 12 | long Width; 13 | long Height; 14 | SAFEARRAY(byte) Bytes; 15 | }; 16 | 17 | //! [AddHostObjectInterface] 18 | [uuid(3a14c9c0-bc3e-453f-a314-4ce4a0ec81d8), object, local] 19 | interface IHostObject : IUnknown 20 | { 21 | [propget] HRESULT componentVersion([out, retval] __int32 * version); 22 | [propget] HRESULT componentVersionText([out, retval] BSTR * versionText); 23 | 24 | HRESULT print([in] BSTR text); 25 | 26 | HRESULT stop(); 27 | HRESULT play([in] VARIANT_BOOL paused); 28 | HRESULT pause([in] VARIANT_BOOL paused); 29 | HRESULT previous(); 30 | HRESULT next(); 31 | HRESULT random(); 32 | 33 | HRESULT togglePause(); 34 | HRESULT toggleMute(); 35 | HRESULT toggleStopAfterCurrent(); 36 | 37 | HRESULT volumeUp(); 38 | HRESULT volumeDown(); 39 | 40 | HRESULT seek([in] double time); 41 | HRESULT seekDelta([in] double delta); 42 | 43 | [propget] HRESULT isPlaying([out, retval] VARIANT_BOOL * value); 44 | [propget] HRESULT isPaused([out, retval] VARIANT_BOOL * value); 45 | 46 | [propget] HRESULT stopAfterCurrent([out, retval] VARIANT_BOOL * value); 47 | [propput] HRESULT stopAfterCurrent([in] VARIANT_BOOL value); 48 | 49 | [propget] HRESULT length([out, retval] double * value); 50 | [propget] HRESULT position([out, retval] double * value); 51 | [propget] HRESULT canSeek([out, retval] VARIANT_BOOL * value); 52 | 53 | [propget] HRESULT volume([out, retval] double * value); 54 | [propput] HRESULT volume([in] double value); 55 | 56 | [propget] HRESULT isMuted([out, retval] VARIANT_BOOL * value); 57 | 58 | HRESULT getFormattedText([in] BSTR text, [out, retval] BSTR * formattedText); 59 | 60 | HRESULT getArtwork([in] BSTR type, [out, retval] BSTR * image); 61 | 62 | // Files 63 | HRESULT readAllText([in] BSTR filePath, [in] __int32 codePage, [out, retval] BSTR * text); 64 | HRESULT readImage([in] BSTR filePath, [out, retval] BSTR * image); 65 | HRESULT readDirectory([in] BSTR pathName, [in, defaultvalue("*.*")] BSTR searchPattern, [out, retval] BSTR * json); 66 | 67 | // Playlists 68 | [propget] HRESULT playlistCount([out, retval] int * count); 69 | 70 | [propget] HRESULT activePlaylist([out, retval] int * index); 71 | [propput] HRESULT activePlaylist([in] int index); 72 | 73 | [propget] HRESULT playingPlaylist([out, retval] int * index); 74 | [propput] HRESULT playingPlaylist([in] int index); 75 | 76 | HRESULT getPlaylistName([in] int index, [out, retval] BSTR * name); 77 | HRESULT setPlaylistName([in] int index, [in] BSTR name); 78 | 79 | HRESULT findPlaylist([in] BSTR name, [out, retval] int * index); 80 | 81 | HRESULT getPlaylistItemCount([in] int playlistIndex, [out, retval] int * itemCount); 82 | HRESULT getSelectedPlaylistItemCount([in] int playlistIndex, [in] int maxItems, [out, retval] int * itemCount); 83 | 84 | HRESULT getFocusedPlaylistItem([in] int playlistIndex, [out, retval] int * itemIndex); 85 | HRESULT setFocusedPlaylistItem([in] int playlistIndex, [in] int itemIndex); 86 | HRESULT ensurePlaylistItemVisible([in] int playlistIndex, [in] int itemIndex); 87 | HRESULT executePlaylistDefaultAction([in] int playlistIndex, [in] int itemIndex); 88 | HRESULT isPlaylistItemSelected([in] int playlistIndex, [in] int itemIndex, [out, retval] VARIANT_BOOL * result); 89 | 90 | HRESULT createPlaylist([in] int playlistIndex, [in] BSTR name, [out, retval] int * newPlaylistIndex); 91 | HRESULT addPath([in] int playlistIndex, [in] int itemIndex, [in] BSTR filePath, [in] VARIANT_BOOL selectAddedItem); 92 | 93 | HRESULT duplicatePlaylist([in] int playlistIndex, [in] BSTR name, [out, retval] int * newPlaylistIndex); 94 | HRESULT getPlaylistItems([in] int playlistIndex, [out, retval] BSTR * json); 95 | 96 | HRESULT selectPlaylistItem([in] int playlistIndex, [in] int itemIndex); 97 | HRESULT deselectPlaylistItem([in] int playlistIndex, [in] int itemIndex); 98 | HRESULT getSelectedPlaylistItems([in] int playlistIndex, [out, retval] BSTR * json); 99 | HRESULT removeSelectedPlaylistItems([in] int playlistIndex); 100 | HRESULT removeUnselectedPlaylistItems([in] int playlistIndex); 101 | HRESULT clearPlaylistSelection([in] int playlistIndex); 102 | 103 | HRESULT removePlaylistItem([in] int playlistIndex, [in] int itemIndex); 104 | 105 | HRESULT clearPlaylist([in] int playlistIndex); 106 | HRESULT deletePlaylist([in] int playlistIndex); 107 | 108 | // Auto Playlists 109 | HRESULT createAutoPlaylist([in] int playlistIndex, [in] BSTR name, [in] BSTR query, [in, defaultvalue("*.*")] BSTR sort, [in, defaultvalue(0)] unsigned __int32 flags, [out, retval] int * newPlaylistIndex); 110 | HRESULT isAutoPlaylist([in] int playlistIndex, [out, retval] VARIANT_BOOL * result); 111 | 112 | // Playback Order 113 | [propget] HRESULT playbackOrder([out, retval] int * index); 114 | [propput] HRESULT playbackOrder([in] int index); 115 | }; 116 | 117 | [uuid(637abc45-11f7-4dde-84b4-317d62a638d3)] 118 | coclass HostObject 119 | { 120 | [default] interface IHostObject; 121 | interface IDispatch; 122 | }; 123 | } -------------------------------------------------------------------------------- /Build-FB2KComponent.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Builds the foobar2000 component package. 4 | .DESCRIPTION 5 | This script will be executed unconditionally during the Post-build step. It copies all the necessary files to an output directory and creates the zip archive. 6 | .EXAMPLE 7 | C:\PS> .\Build-FB2KComponent.ps1 8 | .OUTPUTS 9 | *.fb2k-component 10 | #> 11 | 12 | [CmdletBinding()] 13 | param 14 | ( 15 | [parameter(Mandatory, HelpMessage='Target Name')] 16 | [string] $TargetName, 17 | [parameter(Mandatory, HelpMessage='Target File Name')] 18 | [string] $TargetFileName, 19 | [parameter(Mandatory, HelpMessage='Platform')] 20 | [string] $Platform, 21 | [parameter(Mandatory, HelpMessage='OutputPath')] 22 | [string] $OutputPath 23 | ) 24 | 25 | #Requires -Version 7.2 26 | 27 | Set-StrictMode -Version Latest; 28 | Set-PSDebug -Strict; # Equivalent of VBA "Option Explicit". 29 | 30 | $ErrorActionPreference = 'Stop'; 31 | 32 | # Note: The working directory is the solution directory. 33 | 34 | Write-Host "Building package `"$TargetName`" ($Platform)..."; 35 | 36 | if ($Platform -eq 'x64') 37 | { 38 | $PackagePath = "../out/$TargetName"; 39 | 40 | # Create the package directory (including the x64 subdirectory) 41 | Write-Host "Creating directory `"$PackagePath`"..."; 42 | 43 | $null = New-Item -Path '../out/' -Name "$TargetName/x64" -ItemType 'directory' -Force; 44 | 45 | if (Test-Path -Path "$OutputPath/$TargetFileName") 46 | { 47 | Write-Host "Copying $TargetFileName to `"$PackagePath/x64`"..."; 48 | 49 | Copy-Item "$OutputPath/$TargetFileName" -Destination "$PackagePath/x64" -Force -Verbose; 50 | Copy-Item "$OutputPath/WebView2Loader.dll" -Destination "$PackagePath/x64" -Force -Verbose; 51 | Copy-Item "Template.html" -Destination "$PackagePath/x64/Default-Template.html" -Force -Verbose; 52 | Copy-Item "FrameTemplate.html" -Destination "$PackagePath/x64/Default-FrameTemplate.html" -Force -Verbose; 53 | Copy-Item "PlaylistTemplate.html" -Destination "$PackagePath/x64/Default-PlaylistTemplate.html" -Force -Verbose; 54 | } 55 | 56 | # install the component in the foobar2000 x64 components directory. 57 | $foobar2000Path = '../bin'; 58 | 59 | if (Test-Path -Path "$foobar2000Path/foobar2000.exe") 60 | { 61 | $ComponentPath = "$foobar2000Path/profile/user-components-x64"; 62 | 63 | Write-Host "Creating directory `"$ComponentPath/$TargetName`"..."; 64 | 65 | $null = New-Item -Path "$ComponentPath" -Name "$TargetName" -ItemType 'directory' -Force; 66 | 67 | Write-Host "Installing x64 component in foobar2000 64-bit profile..."; 68 | 69 | Copy-Item "$PackagePath/x64/*.dll" -Destination "$ComponentPath/$TargetName" -Force -Verbose; 70 | Copy-Item "$PackagePath/x64/Default-Template.html" -Destination "$ComponentPath/$TargetName" -Force -Verbose; 71 | Copy-Item "$PackagePath/x64/Default-FrameTemplate.html" -Destination "$ComponentPath/$TargetName" -Force -Verbose; 72 | Copy-Item "$PackagePath/x64/Default-PlaylistTemplate.html" -Destination "$ComponentPath/$TargetName" -Force -Verbose; 73 | } 74 | else 75 | { 76 | Write-Host "Skipped component installation: foobar2000 64-bit directory not found."; 77 | } 78 | } 79 | elseif ($Platform -eq 'Win32') 80 | { 81 | $PackagePath = "../out/$TargetName"; 82 | 83 | # Create the package directory (including the x64 subdirectory) 84 | Write-Host "Creating directory `"$PackagePath`"..."; 85 | 86 | $null = New-Item -Path '../out/' -Name "$TargetName/x64" -ItemType 'directory' -Force; 87 | 88 | if (Test-Path -Path "$OutputPath/$TargetFileName") 89 | { 90 | Write-Host "Copying $TargetFileName to `"$PackagePath`"..."; 91 | 92 | Copy-Item "$OutputPath/$TargetFileName" -Destination "$PackagePath" -Force -Verbose; 93 | Copy-Item "$OutputPath/WebView2Loader.dll" -Destination "$PackagePath" -Force -Verbose; 94 | Copy-Item "Template.html" -Destination "$PackagePath/Default-Template.html" -Force -Verbose; 95 | Copy-Item "FrameTemplate.html" -Destination "$PackagePath/Default-FrameTemplate.html" -Force -Verbose; 96 | Copy-Item "PlaylistTemplate.html" -Destination "$PackagePath/Default-PlaylistTemplate.html" -Force -Verbose; 97 | } 98 | 99 | # install the x86 component in the foobar2000 x86 components directory. 100 | $foobar2000Path = '../bin/x86'; 101 | 102 | if (Test-Path -Path "$foobar2000Path/foobar2000.exe") 103 | { 104 | $ComponentPath = "$foobar2000Path/profile/user-components"; 105 | 106 | Write-Host "Creating directory `"$ComponentPath/$TargetName`"..."; 107 | 108 | $null = New-Item -Path "$ComponentPath" -Name "$TargetName" -ItemType 'directory' -Force; 109 | 110 | Write-Host "Installing x86 component in foobar2000 32-bit profile..."; 111 | 112 | Copy-Item "$PackagePath/*.dll" -Destination "$ComponentPath/$TargetName" -Force -Verbose; 113 | Copy-Item "$PackagePath/Default-Template.html" -Destination "$ComponentPath/$TargetName" -Force -Verbose; 114 | Copy-Item "$PackagePath/Default-FrameTemplate.html" -Destination "$ComponentPath/$TargetName" -Force -Verbose; 115 | Copy-Item "$PackagePath/Default-PlaylistTemplate.html" -Destination "$ComponentPath/$TargetName" -Force -Verbose; 116 | } 117 | else 118 | { 119 | Write-Host "Skipped component installation: foobar2000 32-bit directory not found."; 120 | } 121 | } 122 | else 123 | { 124 | Write-Host "Unknown platform: $Platform"; 125 | exit; 126 | } 127 | 128 | $ArchivePath = "../out/$TargetName.fb2k-component"; 129 | 130 | Write-Host "Creating component archive `"$ArchivePath`"..."; 131 | 132 | Compress-Archive -Force -Path ../out/$TargetName/* -DestinationPath $ArchivePath; 133 | 134 | Write-Host "Done."; 135 | -------------------------------------------------------------------------------- /Configuration.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: configuration_t.cpp (2024.08.04) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "Configuration.h" 7 | #include "Encoding.h" 8 | #include "Resources.h" 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | using namespace pfc; 17 | using namespace stringcvt; 18 | 19 | #include 20 | #pragma comment(lib, "pathcch") 21 | 22 | #pragma hdrstop 23 | 24 | /// 25 | /// Initializes a new instance. 26 | /// 27 | configuration_t::configuration_t() 28 | { 29 | { 30 | GUID Guid; 31 | 32 | (void) ::CoCreateGuid(&Guid); 33 | 34 | wchar_t ProfileName[64]; 35 | 36 | ::swprintf_s(ProfileName, _countof(ProfileName), TEXT(STR_COMPONENT_BASENAME) L"-%08X-%04X-%04X-%02X%02X%02X%02X%02X%02X%02X%02X", (int) Guid.Data1, (int) Guid.Data2, (int) Guid.Data3, Guid.Data4[0], Guid.Data4[1], Guid.Data4[2], Guid.Data4[3], Guid.Data4[4], Guid.Data4[5], Guid.Data4[6], Guid.Data4[7]); 37 | 38 | _ProfileName = ProfileName; 39 | } 40 | 41 | Reset(); 42 | } 43 | 44 | /// 45 | /// Resets this instance. 46 | /// 47 | void configuration_t::Reset() noexcept 48 | { 49 | _Name = TEXT(STR_COMPONENT_NAME); 50 | 51 | pfc::string8 Path = pfc::io::path::combine(core_api::get_profile_path(), STR_COMPONENT_BASENAME); 52 | 53 | if (::_strnicmp(Path, "file://", 7) == 0) 54 | Path = Path.subString(7); 55 | 56 | { 57 | wchar_t FilePath[MAX_PATH]; 58 | 59 | ::wcscpy_s(FilePath, _countof(FilePath), ::UTF8ToWide(Path.c_str()).c_str()); 60 | 61 | HRESULT hr = ::PathCchAppend(FilePath, _countof(FilePath), L"Template.html"); 62 | 63 | if (SUCCEEDED(hr)) 64 | _TemplateFilePath = FilePath; 65 | else 66 | ::wcscpy_s(FilePath, _countof(FilePath), L"Template.html"); 67 | } 68 | 69 | { 70 | _UserDataFolderPath = ::UTF8ToWide(Path.c_str()); 71 | } 72 | 73 | _WindowSize = 100; // ms 74 | _WindowSizeUnit = WindowSizeUnit::Milliseconds; 75 | _ReactionAlignment = 0.25; 76 | 77 | _ClearOnStartup = ClearOnStartup::None; 78 | _InPrivateMode = false; 79 | 80 | _ScrollbarStyle = ScrollbarStyle::Fluent; 81 | } 82 | 83 | /// 84 | /// Implements the = operator. 85 | /// 86 | configuration_t & configuration_t::operator=(const configuration_t & other) 87 | { 88 | _Name = other._Name; 89 | _TemplateFilePath = other._TemplateFilePath; 90 | _UserDataFolderPath = other._UserDataFolderPath; 91 | 92 | _WindowSize = other._WindowSize; 93 | _WindowSizeUnit = other._WindowSizeUnit; 94 | _ReactionAlignment = other._ReactionAlignment; 95 | 96 | _ProfileName = other._ProfileName; 97 | 98 | _ClearOnStartup = other._ClearOnStartup; 99 | _InPrivateMode = other._InPrivateMode; 100 | 101 | _ScrollbarStyle = other._ScrollbarStyle; 102 | 103 | return *this; 104 | } 105 | 106 | /// 107 | /// Reads this instance with the specified reader. 108 | /// 109 | void configuration_t::Read(stream_reader * reader, size_t size, abort_callback & abortHandler, bool isPreset) noexcept 110 | { 111 | Reset(); 112 | 113 | try 114 | { 115 | int32_t Version; 116 | 117 | reader->read(&Version, sizeof(Version), abortHandler); 118 | 119 | pfc::string UTF8String; 120 | 121 | // Version 1, v0.1.4.0 122 | reader->read_string(UTF8String, abortHandler); _TemplateFilePath = pfc::wideFromUTF8(UTF8String); 123 | 124 | // Version 2, v0.1.5.0-alpha1 125 | if (Version >= 2) 126 | { 127 | reader->read_string(UTF8String, abortHandler); _Name = pfc::wideFromUTF8(UTF8String); 128 | reader->read_string(UTF8String, abortHandler); _UserDataFolderPath = pfc::wideFromUTF8(UTF8String); 129 | } 130 | 131 | // Version 3, v0.1.5.0-alpha2 132 | if (Version >= 3) 133 | { 134 | reader->read_object_t(_WindowSize, abortHandler); 135 | uint32_t Value; reader->read_object_t(Value, abortHandler); _WindowSizeUnit = (WindowSizeUnit) Value; 136 | reader->read_object_t(_ReactionAlignment, abortHandler); 137 | } 138 | 139 | // Version 4, v0.1.5.6 140 | if (Version >= 4) 141 | { 142 | reader->read_string(UTF8String, abortHandler); _ProfileName = pfc::wideFromUTF8(UTF8String); 143 | } 144 | 145 | // Version 5, v0.1.6.0 146 | if (Version >= 5) 147 | { 148 | uint32_t Value; reader->read_object_t(Value, abortHandler); _ClearOnStartup = (ClearOnStartup) Value; 149 | } 150 | 151 | // Version 6, v0.1.6.3-alpha3 152 | if (Version >= 6) 153 | { 154 | reader->read_object_t(_InPrivateMode, abortHandler); 155 | } 156 | 157 | // Version 7, v0.1.8.0 158 | if (Version >= 7) 159 | { 160 | uint32_t Value; reader->read_object_t(Value, abortHandler); _ScrollbarStyle = (ScrollbarStyle) Value; 161 | } 162 | } 163 | catch (exception & ex) 164 | { 165 | console::printf(STR_COMPONENT_BASENAME " failed to read configuration: %s", ex.what()); 166 | 167 | Reset(); 168 | } 169 | } 170 | 171 | /// 172 | /// Writes this instance to the specified writer. 173 | /// 174 | void configuration_t::Write(stream_writer * writer, abort_callback & abortHandler, bool isPreset) const noexcept 175 | { 176 | try 177 | { 178 | writer->write_object_t(_CurrentVersion, abortHandler); 179 | 180 | // Version 1, v0.1.4.0 181 | pfc::string UTF8String = pfc::utf8FromWide(_TemplateFilePath.c_str()); writer->write_string(UTF8String, abortHandler); 182 | 183 | // Version 2, v0.1.5.0-alpha1 184 | UTF8String = pfc::utf8FromWide(_Name.c_str()); writer->write_string(UTF8String, abortHandler); 185 | UTF8String = pfc::utf8FromWide(_UserDataFolderPath.c_str()); writer->write_string(UTF8String, abortHandler); 186 | 187 | // Version 3, v0.1.5.0-alpha2 188 | writer->write_object_t(_WindowSize, abortHandler); 189 | uint32_t Value = (uint32_t) _WindowSizeUnit; writer->write_object_t(Value, abortHandler); 190 | writer->write_object_t(_ReactionAlignment, abortHandler); 191 | 192 | // Version 4, v0.1.5.6 193 | UTF8String = pfc::utf8FromWide(_ProfileName.c_str()); writer->write_string(UTF8String, abortHandler); 194 | 195 | // Version 5, v0.1.6.0 196 | Value = (uint32_t) _ClearOnStartup; writer->write_object_t(Value, abortHandler); 197 | 198 | // Version 6, v0.1.6.3-alpha3 199 | writer->write_object_t(_InPrivateMode, abortHandler); 200 | 201 | // Version 7, v0.1.8.0 202 | Value = (uint32_t) _ScrollbarStyle; writer->write_object_t(Value, abortHandler); 203 | } 204 | catch (exception & ex) 205 | { 206 | console::printf(STR_COMPONENT_BASENAME " failed to write configuration: %s", ex.what()); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | /Template.old.html 365 | -------------------------------------------------------------------------------- /Encoding.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Encoding.cpp (2024.11.27) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "Encoding.h" 7 | 8 | /// 9 | /// Converts an UTF-16 string of UTF-8. 10 | /// 11 | std::string WideToUTF8(const wchar_t * wide, size_t size = 0) noexcept 12 | { 13 | if (size == 0) 14 | size = ::wcslen(wide); 15 | 16 | int Size = ::WideCharToMultiByte(CP_UTF8, 0, wide, (int) size, nullptr, 0, nullptr, nullptr); 17 | 18 | std::string UTF8; 19 | 20 | UTF8.resize((size_t) Size); 21 | 22 | ::WideCharToMultiByte(CP_UTF8, 0, wide, (int) size, UTF8.data(), (int) UTF8.size(), nullptr, nullptr); 23 | 24 | return UTF8; 25 | } 26 | 27 | /// 28 | /// Converts a string encoded with the specified code page to an UTF-16 string. 29 | /// 30 | std::wstring CodePageToWide(uint32_t codePage, const char * text, size_t size) noexcept 31 | { 32 | int Size = ::MultiByteToWideChar(codePage, 0, text, (int) size, nullptr, 0); 33 | 34 | std::wstring Wide; 35 | 36 | Wide.resize((size_t) Size); 37 | 38 | ::MultiByteToWideChar(codePage, 0, text, (int) size, Wide.data(), (int) Wide.size()); 39 | 40 | return Wide; 41 | } 42 | 43 | /// 44 | /// Converts a string encoded with the specified code page to an UTF-8 string. 45 | /// 46 | std::string CodePageToUTF8(uint32_t codePage, const char * text, size_t size) noexcept 47 | { 48 | return ::WideToUTF8(CodePageToWide(codePage, text, size)); 49 | } 50 | 51 | /// 52 | /// 53 | /// 54 | std::string FormatText(const char * format, ...) noexcept 55 | { 56 | va_list vl; 57 | 58 | va_start(vl, format); 59 | 60 | std::string Text; 61 | 62 | Text.resize(256); 63 | 64 | ::vsprintf_s(Text.data(), Text.size(), format, vl); 65 | 66 | va_end(vl); 67 | 68 | return Text; 69 | } 70 | 71 | /// 72 | /// 73 | /// 74 | std::wstring FormatText(const wchar_t * format, ...) noexcept 75 | { 76 | va_list vl; 77 | 78 | va_start(vl, format); 79 | 80 | std::wstring Text; 81 | 82 | int Size = _vscwprintf(format, vl) + 1; 83 | 84 | if (Size != -1) 85 | { 86 | Text.resize((size_t) Size); 87 | 88 | ::vswprintf_s(Text.data(), Text.size(), format, vl); 89 | } 90 | 91 | va_end(vl); 92 | 93 | return Text; 94 | } 95 | 96 | /// 97 | /// Returns true if the specified text is EUC-JP encoded. (http://www.rikai.com/library/kanjitables/kanji_codes.euc.shtml) 98 | /// 99 | bool IsEUCJP(const char * text, size_t size) noexcept 100 | { 101 | while (size != 0) 102 | { 103 | uint8_t d1 = (uint8_t) *text++; 104 | size--; 105 | 106 | if (!((d1 >= 0xA1 && d1 <= 0xAD) || (d1 >= 0xB0 && d1 <= 0xFE))) 107 | continue; 108 | 109 | if (size == 0) 110 | return false; 111 | 112 | uint8_t d2 = (uint8_t) *text++; 113 | size--; 114 | 115 | if (!(d2 >= 0xA0 && d1 <= 0xFF)) 116 | return false; 117 | } 118 | 119 | return true; 120 | } 121 | 122 | /// 123 | /// Returns true if the specified text is Shift-JIS encoded. (http://www.rikai.com/library/kanjitables/kanji_codes.sjis.shtml) 124 | /// 125 | bool IsShiftJIS(const char * text, size_t size) noexcept 126 | { 127 | while (size != 0) 128 | { 129 | uint8_t d1 = (uint8_t) *text++; 130 | size--; 131 | 132 | if (!((d1 >= 0x81 && d1 <= 0x84) || (d1 >= 0x87 && d1 <= 0x9F) || (d1 >= 0xE0 && d1 <= 0xEF))) 133 | continue; 134 | 135 | if (size == 0) 136 | return false; 137 | 138 | uint8_t d2 = (uint8_t) *text++; 139 | size--; 140 | 141 | if (!((d2 >= 0x40 && d2 <= 0x9E) || (d2 >= 0x9F && d2 <= 0xFC))) 142 | return false; 143 | } 144 | 145 | return true; 146 | } 147 | 148 | /// 149 | /// Returns true if the specified text is UTF-8 encoded. 150 | /// 151 | bool IsUTF8(const char * text, size_t size) noexcept 152 | { 153 | size_t n = 0; 154 | 155 | for (size_t i = 0; i < size; ++i) 156 | { 157 | uint8_t c = (uint8_t ) text[i]; 158 | 159 | //if (c==0x09 || c==0x0a || c==0x0d || (0x20 <= c && c <= 0x7e) ) n = 0; // is_printable_ascii 160 | if ((0x00 <= c) && (c <= 0x7F)) 161 | n = 0; // 0bbbbbbb 162 | else 163 | if ((c & 0xE0) == 0xC0) 164 | n = 1; // 110bbbbb 165 | else 166 | if ((c == 0xED) && (i < (size - 1)) && (((uint8_t) text[i + 1] & 0xA0) == 0xA0)) 167 | return false; // U+d800 to U+dfff 168 | 169 | if ((c & 0xF0) == 0xE0) 170 | n = 2; // 1110bbbb 171 | else 172 | if ((c & 0xF8) == 0xF0) 173 | n = 3; // 11110bbb 174 | // else 175 | // if ((c & 0xFC) == 0xF8) 176 | // n = 4; // 111110bb // byte 5, unnecessary in 4 byte UTF-8 177 | // if ((c & 0xFE) == 0xFC) 178 | // n = 5; // 1111110b // byte 6, unnecessary in 4 byte UTF-8 179 | else 180 | return false; 181 | 182 | // Are there n bytes matching 10bbbbbb following? 183 | for (size_t j = 0; (j < n) && (i < size); ++j) 184 | { 185 | if ((++i == size) || (((uint8_t) text[i] & 0xC0) != 0x80)) 186 | return false; 187 | } 188 | } 189 | 190 | return true; 191 | } 192 | 193 | /// 194 | /// Returns true if the specified text is ASCII encoded. 195 | /// 196 | bool IsASCII(const char * text) noexcept 197 | { 198 | while (*text) 199 | { 200 | if (*text < 0) 201 | return false; 202 | 203 | ++text; 204 | } 205 | 206 | return true; 207 | } 208 | 209 | /// 210 | /// Returns true if the specified text is ASCII encoded. 211 | /// 212 | bool IsASCII(const char * text, size_t size) noexcept 213 | { 214 | while (size != 0) 215 | { 216 | if (*text < 0) 217 | return false; 218 | 219 | ++text; 220 | --size; 221 | } 222 | 223 | return true; 224 | } 225 | 226 | /// 227 | /// Converts a string in a unknown encoding to UTF-16. 228 | /// 229 | std::wstring TextToWide(const char * text, size_t size) noexcept 230 | { 231 | if (size == 0) 232 | size = ::strlen(text); 233 | 234 | if (IsASCII(text)) 235 | return ::UTF8ToWide(text); 236 | 237 | if (IsUTF8(text, size)) 238 | return ::UTF8ToWide(text); 239 | 240 | if (IsShiftJIS(text, size)) 241 | return CodePageToWide(932, text); 242 | 243 | if (IsEUCJP(text, size)) 244 | return CodePageToWide(20932, text); 245 | 246 | return CodePageToWide(51932, text); 247 | } 248 | 249 | /// 250 | /// Converts a string in a unknown encoding to UTF-8. 251 | /// 252 | std::string TextToUTF8(const char * text, size_t size) noexcept 253 | { 254 | if (size == 0) 255 | size = ::strlen(text); 256 | 257 | if (IsASCII(text, size) || IsUTF8(text, size)) 258 | { 259 | std::string Text; 260 | 261 | Text.resize(size + 1); 262 | 263 | ::memcpy(Text.data(), text, size); 264 | Text[size] = '\0'; 265 | 266 | return Text; 267 | } 268 | 269 | if (IsShiftJIS(text, size)) 270 | return CodePageToUTF8(932, text, size); 271 | 272 | if (IsEUCJP(text, size)) 273 | return CodePageToUTF8(20932, text, size); 274 | 275 | return CodePageToUTF8(51932, text, size); 276 | } 277 | 278 | #ifdef _DEBUG 279 | // Test cases 280 | const uint8_t Data1[] = 281 | { 282 | 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x46, 0x61, 0x6e, 0x74, 0x61, 0x73, 0x79, 0x20, 0x35, 0x20, 0x5b, 0x20, 0x83, 0x72, 0x83, 0x62, 0x83, 0x4f, 283 | 0x83, 0x75, 0x83, 0x8a, 0x83, 0x62, 0x83, 0x61, 0x82, 0xcc, 0x8e, 0x80, 0x93, 0xac, 0x20, 0x81, 0x66, 0x82, 0x58, 0x82, 0x58, 0x20, 0x2d, 0x73, 284 | 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x65, 0x64, 0x69, 0x74, 0x2d, 0x20, 0x5d, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x53, 0x43, 0x2d, 0x38, 0x38, 0x50, 285 | 0x72, 0x6f, 0x20, 0x32, 0x50, 0x6f, 0x72, 0x74, 0x20, 0x76, 0x65, 0x72, 0x37, 0x2e, 0x30, 0x20, 0x62, 0x79, 0x20, 0x4c, 0x69, 0x78, 0x00 286 | }; 287 | const WCHAR * Text1 = L"Final Fantasy 5 [ ビッグブリッヂの死闘 ’99 -single edit- ] for SC-88Pro 2Port ver7.0 by Lix"; 288 | 289 | struct Test 290 | { 291 | const uint8_t * Data; 292 | const WCHAR * Text; 293 | } Tests[] = 294 | { 295 | { Data1, Text1 }, // Mixed ASCII and Shift-JIS 296 | }; 297 | #endif 298 | -------------------------------------------------------------------------------- /HostObjectImpl.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: HostObjectImpl.h (2024.12.02) P. Stuer **/ 3 | 4 | #pragma once 5 | 6 | #include "pch.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | #include "HostObject_h.h" 22 | 23 | #include 24 | #include 25 | 26 | class HostObject : public Microsoft::WRL::RuntimeClass, IHostObject, IDispatch> 27 | { 28 | public: 29 | HostObject(const HostObject &) = delete; 30 | HostObject(const HostObject &&) = delete; 31 | HostObject & operator=(const HostObject &) = delete; 32 | HostObject & operator=(HostObject &&) = delete; 33 | 34 | virtual ~HostObject() { }; 35 | 36 | typedef std::function Callback; 37 | typedef std::function RunCallbackAsync; 38 | 39 | HostObject(RunCallbackAsync runCallbackAsync); 40 | 41 | #pragma region IHostObject 42 | 43 | STDMETHODIMP get_componentVersion(__int32 * version) override; 44 | STDMETHODIMP get_componentVersionText(BSTR * versionText) override; 45 | 46 | STDMETHODIMP print(BSTR text) override; 47 | 48 | /* Playback control **/ 49 | 50 | STDMETHODIMP stop() override; 51 | STDMETHODIMP play(VARIANT_BOOL paused) override; 52 | STDMETHODIMP pause(VARIANT_BOOL paused) override; 53 | STDMETHODIMP previous() override; 54 | STDMETHODIMP next() override; 55 | STDMETHODIMP random() override; 56 | 57 | STDMETHODIMP togglePause() override; 58 | STDMETHODIMP toggleMute() override; 59 | STDMETHODIMP toggleStopAfterCurrent() override; 60 | 61 | STDMETHODIMP volumeUp() override; 62 | STDMETHODIMP volumeDown() override; 63 | 64 | STDMETHODIMP seek(double time) override; 65 | STDMETHODIMP seekDelta(double delta) override; 66 | 67 | STDMETHODIMP get_isPlaying(VARIANT_BOOL * value) override; 68 | STDMETHODIMP get_isPaused(VARIANT_BOOL * value) override; 69 | 70 | STDMETHODIMP get_stopAfterCurrent(VARIANT_BOOL * value) override; 71 | STDMETHODIMP put_stopAfterCurrent(VARIANT_BOOL value) override; 72 | 73 | STDMETHODIMP get_length(double * value) override; 74 | STDMETHODIMP get_position(double * value) override; 75 | STDMETHODIMP get_canSeek(VARIANT_BOOL * value) override; 76 | 77 | STDMETHODIMP get_volume(double * value) override; 78 | STDMETHODIMP put_volume(double value) override; 79 | 80 | STDMETHODIMP get_isMuted(VARIANT_BOOL * value) override; 81 | 82 | STDMETHODIMP getFormattedText(BSTR text, BSTR * formattedText) override; 83 | 84 | STDMETHODIMP getArtwork(BSTR type, BSTR * image) override; 85 | 86 | /* Files */ 87 | 88 | STDMETHODIMP readAllText(BSTR filePath, __int32 codePage, BSTR * text) override; 89 | STDMETHODIMP readImage(BSTR filePath, BSTR * image) override; 90 | STDMETHODIMP readDirectory(BSTR filePath, BSTR searchPattern, BSTR * json) override; 91 | 92 | /* Playlists */ 93 | 94 | STDMETHODIMP get_playlistCount(int * playlistCount) override; 95 | 96 | STDMETHODIMP get_activePlaylist(int * playlistIndex) override; 97 | STDMETHODIMP put_activePlaylist(int playlistIndex) override; 98 | 99 | STDMETHODIMP get_playingPlaylist(int * playlistIndex) override; 100 | STDMETHODIMP put_playingPlaylist(int playlistIndex) override; 101 | 102 | STDMETHODIMP getPlaylistName(int playlistIndex, BSTR * name) override; 103 | STDMETHODIMP setPlaylistName(int playlistIndex, BSTR name) override; 104 | 105 | STDMETHODIMP findPlaylist(BSTR name, int * playlistIndex) override; 106 | 107 | STDMETHODIMP getPlaylistItemCount(int playlistIndex, int * itemCount) override; 108 | STDMETHODIMP getSelectedPlaylistItemCount(int playlistIndex, int maxItems, int * itemCount) override; 109 | 110 | STDMETHODIMP getFocusedPlaylistItem(int playlistIndex, int * itemIndex) override; 111 | STDMETHODIMP setFocusedPlaylistItem(int playlistIndex, int itemIndex) override; 112 | STDMETHODIMP ensurePlaylistItemVisible(int playlistIndex, int itemIndex) override; 113 | STDMETHODIMP executePlaylistDefaultAction(int playlistIndex, int itemIndex) override; 114 | STDMETHODIMP isPlaylistItemSelected(int playlistIndex, int itemIndex, VARIANT_BOOL * result) override; 115 | 116 | STDMETHODIMP createPlaylist(int playlistIndex, BSTR name, int * newPlaylistIndex) override; 117 | STDMETHODIMP addPath(int playlistIndex, int itemIndex, BSTR filePath, VARIANT_BOOL selectAddedItems) override; 118 | 119 | STDMETHODIMP duplicatePlaylist(int playlistIndex, BSTR name, int * newPlaylistIndex) override; 120 | STDMETHODIMP clearPlaylist(int playlistIndex) override; 121 | STDMETHODIMP getPlaylistItems(int playlistIndex, BSTR * json) override; 122 | 123 | STDMETHODIMP selectPlaylistItem(int playlistIndex, int itemIndex) override; 124 | STDMETHODIMP deselectPlaylistItem(int playlistIndex, int itemIndex) override; 125 | STDMETHODIMP getSelectedPlaylistItems(int playlistIndex, BSTR * json) override; 126 | STDMETHODIMP clearPlaylistSelection(int playlistIndex) override; 127 | STDMETHODIMP removeSelectedPlaylistItems(int playlistIndex) override; 128 | STDMETHODIMP removeUnselectedPlaylistItems(int playlistIndex) override; 129 | 130 | STDMETHODIMP removePlaylistItem(int playlistIndex, int itemIndex) override; 131 | 132 | STDMETHODIMP deletePlaylist(int playlistIndex) override; 133 | 134 | /* Auto Playlists */ 135 | 136 | STDMETHODIMP createAutoPlaylist(int playlistIndex, BSTR name, BSTR query, BSTR sort, uint32_t flags, int * newPlaylistIndex) override; 137 | STDMETHODIMP isAutoPlaylist(int playlistIndex, VARIANT_BOOL * result) override; 138 | 139 | /* Playback Order */ 140 | 141 | STDMETHODIMP get_playbackOrder(int * playlistIndex) override; 142 | STDMETHODIMP put_playbackOrder(int playlistIndex) override; 143 | 144 | #pragma endregion 145 | 146 | #pragma region IDispatch 147 | 148 | STDMETHODIMP GetTypeInfoCount(UINT * typeInfoCount) override; 149 | STDMETHODIMP GetTypeInfo(UINT typeInfoIndex, LCID lcid, ITypeInfo ** typeInfo) override; 150 | STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR * names, UINT nameCount, LCID lcid, DISPID * dispIds) override; 151 | STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD flags, DISPPARAMS * dispParams, VARIANT * result, EXCEPINFO * excepInfo, UINT * argErr) override; 152 | 153 | #pragma endregion 154 | 155 | private: 156 | static HRESULT GetTrackIndex(size_t & playlistIndex, size_t & itemIndex) noexcept; 157 | 158 | static HRESULT GetTypeLibFilePath(std::wstring & filePath) noexcept; 159 | 160 | static void NormalizeIndexes(int & playlistIndex, int & itemIndex) noexcept 161 | { 162 | auto Manager = playlist_manager_v4::get(); 163 | 164 | if (playlistIndex == -1) 165 | playlistIndex = (int) Manager->get_active_playlist(); 166 | 167 | if (itemIndex == -1) 168 | itemIndex = (int) Manager->playlist_get_item_count((size_t) playlistIndex) - 1; 169 | } 170 | 171 | private: 172 | wil::com_ptr _TypeLibrary; 173 | 174 | wil::com_ptr _Callback; 175 | RunCallbackAsync _RunCallbackAsync; 176 | 177 | service_ptr_t _PlaybackControl; 178 | 179 | /// 180 | /// Represents an Album Art Manager configuration to allow overriding the default configuration in this component (see album_art_manager_v3::open_v3) 181 | /// 182 | class album_art_manager_config_t : public album_art_manager_config 183 | { 184 | public: 185 | album_art_manager_config_t() { }; 186 | 187 | album_art_manager_config_t(const album_art_manager_config_t &) = delete; 188 | album_art_manager_config_t(const album_art_manager_config_t &&) = delete; 189 | album_art_manager_config_t & operator=(const album_art_manager_config_t &) = delete; 190 | album_art_manager_config_t & operator=(album_art_manager_config_t &&) = delete; 191 | 192 | virtual ~album_art_manager_config_t() { }; 193 | 194 | virtual bool get_external_pattern(pfc::string_base & out, const GUID & albumArtType) override 195 | { 196 | return false; 197 | } 198 | 199 | virtual bool use_embedded_pictures() override 200 | { 201 | return true; 202 | } 203 | 204 | virtual bool use_fallbacks() override 205 | { 206 | return true; 207 | } 208 | }; 209 | 210 | service_ptr_t _AlbumArtManagerConfig = new service_impl_t; 211 | }; 212 | 213 | extern void ToBase64(const BYTE * data, DWORD size, BSTR * base64); 214 | extern const std::string Stringify(const char * s); 215 | extern const std::wstring Stringify(const std::wstring & s); 216 | 217 | extern std::wstring ToJSON(const metadb_handle_list & hItems); 218 | extern std::wstring ToJSON(const bit_array & mask, t_size count); 219 | extern std::wstring ToJSON(const t_size * array, t_size count); 220 | -------------------------------------------------------------------------------- /UIElementPlaylistCallback.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: UIElementPlaylistCallback.cpp (2024.12.02) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "UIElement.h" 7 | #include "UIElementTracker.h" 8 | #include "Encoding.h" 9 | #include "Exceptions.h" 10 | #include "Support.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #pragma hdrstop 17 | 18 | #pragma region playlist_callback 19 | 20 | /// 21 | /// Called when items have been added to the specified playlist. 22 | /// 23 | void UIElement::on_items_added(t_size playlistIndex, t_size startIndex, metadb_handle_list_cref data, const bit_array & selection) 24 | { 25 | const std::wstring Text = Stringify(ToJSON(data)); 26 | 27 | const std::wstring Script = ::FormatText(L"onPlaylistItemsAdded(%d, %d, \"%s\")", (int) playlistIndex, (int) startIndex, Text.c_str()); 28 | 29 | ExecuteScript(Script); 30 | } 31 | 32 | /// 33 | /// Called when the items of the specified playlist have been reordered. 34 | /// 35 | void UIElement::on_items_reordered(t_size playlistIndex, const t_size * itemOrder, t_size itemCount) 36 | { 37 | const std::wstring Text = ToJSON(itemOrder, itemCount); 38 | 39 | const std::wstring Script = ::FormatText(L"onPlaylistItemsReordered(%d, \"%s\")", (int) playlistIndex, Text.c_str()); 40 | 41 | ExecuteScript(Script); 42 | } 43 | 44 | /// 45 | /// Called when items of the specified playlist are being removed. 46 | /// 47 | void UIElement::on_items_removing(t_size playlistIndex, const bit_array & mask, t_size oldCount, t_size newCount) 48 | { 49 | const std::wstring Text = ToJSON(mask, oldCount); 50 | 51 | const std::wstring Script = ::FormatText(L"onPlaylistItemsRemoving(%d, \"%s\", %d)", (int) playlistIndex, Text.c_str(), (int) newCount); 52 | 53 | ExecuteScript(Script); 54 | } 55 | 56 | /// 57 | /// Called when items of the specified playlist have been removed. 58 | /// 59 | void UIElement::on_items_removed(t_size playlistIndex, const bit_array & mask, t_size oldCount, t_size newCount) 60 | { 61 | const std::wstring Text = ToJSON(mask, oldCount); 62 | 63 | const std::wstring Script = ::FormatText(L"onPlaylistItemsRemoved(%d, \"%s\", %d)", (int) playlistIndex, Text.c_str(), (int) newCount); 64 | 65 | ExecuteScript(Script); 66 | } 67 | 68 | /// 69 | /// Called when some playlist items of the specified playlist have been modified. 70 | /// 71 | void UIElement::on_items_modified(t_size playlistIndex, const bit_array & mask) 72 | { 73 | t_size ItemCount = playlist_manager_v4::get()->playlist_get_item_count(playlistIndex); 74 | 75 | const std::wstring Text = ToJSON(mask, ItemCount); 76 | 77 | const std::wstring Script = ::FormatText(L"onPlaylistItemsModified(%d, \"%s\")", (int) playlistIndex, Text.c_str()); 78 | 79 | ExecuteScript(Script); 80 | } 81 | 82 | /// 83 | /// Called when some playlist items of the specified playlist have been modified from playback. 84 | /// 85 | void UIElement::on_items_modified_fromplayback(t_size playlistIndex, const bit_array & mask, play_control::t_display_level displayLevel) 86 | { 87 | t_size ItemCount = playlist_manager_v4::get()->playlist_get_item_count(playlistIndex); 88 | 89 | const std::wstring Text = ToJSON(mask, ItemCount); 90 | 91 | const std::wstring Script = ::FormatText(L"onPlaylistItemsModifiedFromPlayback(%d, \"%s\")", (int) playlistIndex, Text.c_str()); 92 | 93 | ExecuteScript(Script); 94 | } 95 | 96 | /// 97 | /// Called when items of the specified playlist have been replaced. 98 | /// 99 | void UIElement::on_items_replaced(t_size playlistIndex, const bit_array & mask, const pfc::list_base_const_t & replacedItems) 100 | { 101 | t_size ItemCount = playlist_manager_v4::get()->playlist_get_item_count(playlistIndex); 102 | 103 | const std::wstring Text = ToJSON(mask, ItemCount); 104 | 105 | const std::wstring Script = ::FormatText(L"onPlaylistItemsReplaced(%d, \"%s\")", (int) playlistIndex, Text.c_str()); 106 | 107 | ExecuteScript(Script); 108 | } 109 | 110 | /// 111 | /// Called when the specified item of a playlist has been ensured to be visible. 112 | /// 113 | void UIElement::on_item_ensure_visible(t_size playlistIndex, t_size itemIndex) 114 | { 115 | const std::wstring Script = ::FormatText(L"onPlaylistItemEnsureVisible(%d, %d)", (int) playlistIndex, (int) itemIndex); 116 | 117 | ExecuteScript(Script); 118 | } 119 | 120 | /// 121 | /// Called when a new playlist has been created. 122 | /// 123 | void UIElement::on_playlist_created(t_size playlistIndex, const char * name, t_size size) 124 | { 125 | const std::wstring Script = ::FormatText(L"onPlaylistCreated(%d, \"%s\")", (int) playlistIndex, ::UTF8ToWide(name, size).c_str()); 126 | 127 | ExecuteScript(Script); 128 | } 129 | 130 | /// 131 | /// Called when the specified playlist has been renamed. 132 | /// 133 | void UIElement::on_playlist_renamed(t_size playlistIndex, const char * name, t_size size) 134 | { 135 | const std::wstring Script = ::FormatText(L"onPlaylistRenamed(%d, \"%s\")", (int) playlistIndex, ::UTF8ToWide(name, size).c_str()); 136 | 137 | ExecuteScript(Script); 138 | } 139 | 140 | /// 141 | /// Called when the active playlist changes. 142 | /// 143 | void UIElement::on_playlist_activate(t_size oldPlaylistIndex, t_size newPlaylistIndex) 144 | { 145 | const std::wstring Script = ::FormatText(L"onPlaylistActivated(%d, %d)", (int) oldPlaylistIndex, (int) newPlaylistIndex); 146 | 147 | ExecuteScript(Script); 148 | } 149 | 150 | /// 151 | /// Called when the specified playlist has been locked or unlocked. 152 | /// 153 | void UIElement::on_playlist_locked(t_size playlistIndex, bool isLocked) 154 | { 155 | const std::wstring Script = ::FormatText(isLocked ? L"onPlaylistLocked(%d)" : L"onPlaylistUnlocked(%d)", (int) playlistIndex); 156 | 157 | ExecuteScript(Script); 158 | } 159 | 160 | /// 161 | /// Called when the selected items changed. 162 | /// 163 | void UIElement::on_items_selection_change(t_size playlistIndex, const bit_array & affectedItems, const bit_array & state) 164 | { 165 | t_size ItemCount = playlist_manager_v4::get()->playlist_get_item_count(playlistIndex); 166 | 167 | const std::wstring Text = ToJSON(affectedItems, ItemCount); 168 | 169 | const std::wstring Script = ::FormatText(L"onPlaylistSelectedItemsChanged(%d, \"%s\")", (int) playlistIndex, Text.c_str()); 170 | 171 | ExecuteScript(Script); 172 | } 173 | 174 | /// 175 | /// Called when the focused item of a playlist changed. 176 | /// 177 | void UIElement::on_item_focus_change(t_size playlistIndex, t_size fromIndex, t_size toIndex) 178 | { 179 | const std::wstring Script = ::FormatText(L"onPlaylistFocusedItemChanged(%d, %d, %d)", (int) playlistIndex, (int) fromIndex, (int) toIndex); 180 | 181 | ExecuteScript(Script); 182 | } 183 | 184 | /// 185 | /// Called when the playlists have beenn reordered. 186 | /// 187 | void UIElement::on_playlists_reorder(const t_size * playlistOrder, t_size playlistCount) 188 | { 189 | const std::wstring Text = ToJSON(playlistOrder, playlistCount); 190 | 191 | const std::wstring Script = ::FormatText(L"onPlaylistsReordered(\"%s\")", Text.c_str()); 192 | 193 | ExecuteScript(Script); 194 | } 195 | 196 | /// 197 | /// Called when playlists are being removed. 198 | /// 199 | void UIElement::on_playlists_removing(const bit_array & mask, t_size oldCount, t_size newCount) 200 | { 201 | const std::wstring Text = ToJSON(mask, oldCount); 202 | 203 | const std::wstring Script = ::FormatText(L"onPlaylistsRemoving(\"%s\", %d)", Text.c_str(), (int) newCount); 204 | 205 | ExecuteScript(Script); 206 | } 207 | 208 | /// 209 | /// Called when playlists have been removed. 210 | /// 211 | void UIElement::on_playlists_removed(const bit_array & mask, t_size oldCount, t_size newCount) 212 | { 213 | const std::wstring Text = ToJSON(mask, oldCount); 214 | 215 | const std::wstring Script = ::FormatText(L"onPlaylistsRemoved(\"%s\", %d)", Text.c_str(), (int) newCount); 216 | 217 | ExecuteScript(Script); 218 | } 219 | 220 | /// 221 | /// Called when the default format has been changed. 222 | /// 223 | void UIElement::on_default_format_changed() 224 | { 225 | const std::wstring Script = L"onDefaultFormatChanged()"; 226 | 227 | ExecuteScript(Script); 228 | } 229 | 230 | /// 231 | /// Called when the playback order changed. 232 | /// 233 | void UIElement::on_playback_order_changed(t_size playbackOrderIndex) 234 | { 235 | const std::wstring Script = ::FormatText(L"onPlaybackOrderChanged(%d)", (int) playbackOrderIndex); 236 | 237 | ExecuteScript(Script); 238 | } 239 | 240 | /// 241 | /// Executes a script. 242 | /// 243 | void UIElement::ExecuteScript(const std::wstring & script) const noexcept 244 | { 245 | if (_WebView == nullptr) 246 | return; 247 | 248 | HRESULT hr = _WebView->ExecuteScript(script.c_str(), nullptr); 249 | 250 | if (!SUCCEEDED(hr)) 251 | console::print(::GetErrorMessage(hr, ::FormatText(STR_COMPONENT_BASENAME " failed to call %s", ::WideToUTF8(script).c_str())).c_str()); 252 | } 253 | 254 | #pragma endregion 255 | -------------------------------------------------------------------------------- /UIElement.h: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: UIElement.h (2024.12.02) P. Stuer **/ 3 | 4 | #pragma once 5 | 6 | #include "framework.h" 7 | 8 | #include "Resources.h" 9 | #include "FileWatcher.h" 10 | #include "Configuration.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include "HostObjectImpl.h" 29 | #include "SharedBuffer.h" 30 | 31 | using namespace Microsoft::WRL; 32 | 33 | /// 34 | /// Implements the UIElement and Playback interface. 35 | /// 36 | class UIElement : public CWindowImpl, public playlist_callback, private play_callback_impl_base 37 | { 38 | public: 39 | UIElement(); 40 | 41 | UIElement(const UIElement &) = delete; 42 | UIElement & operator=(const UIElement &) = delete; 43 | UIElement(UIElement &&) = delete; 44 | UIElement & operator=(UIElement &&) = delete; 45 | 46 | virtual ~UIElement(); 47 | 48 | #pragma region CWindowImpl 49 | 50 | static CWndClassInfo & GetWndClassInfo(); 51 | 52 | void OnColorsChanged(); 53 | 54 | #pragma endregion 55 | 56 | #pragma region WebView 57 | 58 | ICoreWebView2Controller * GetWebViewController() 59 | { 60 | return _Controller.get(); 61 | } 62 | 63 | ICoreWebView2 * GetWebView() 64 | { 65 | return _WebView.get(); 66 | } 67 | 68 | const wchar_t * const _HostName = TEXT(STR_COMPONENT_BASENAME) L".local"; 69 | 70 | #pragma endregion 71 | 72 | #pragma region HostObject 73 | 74 | void RunAsync(std::function callback) noexcept 75 | { 76 | auto * Task = new std::function(std::move(callback)); 77 | 78 | PostMessage(UM_ASYNC, reinterpret_cast(Task), 0); 79 | } 80 | 81 | void MessageBoxAsync(std::wstring message, std::wstring title) 82 | { 83 | RunAsync 84 | ( 85 | [this, message = std::move(message), title = std::move(title)] 86 | { 87 | MessageBox(message.c_str(), title.c_str(), MB_OK); 88 | } 89 | ); 90 | } 91 | 92 | #pragma endregion 93 | 94 | #pragma region Configuration 95 | 96 | const configuration_t & GetConfiguration() const noexcept 97 | { 98 | return _Configuration; 99 | } 100 | 101 | void SetConfiguration(const configuration_t & configuration) noexcept 102 | { 103 | _Configuration = configuration; 104 | 105 | OnConfigurationChanged(); 106 | } 107 | 108 | #pragma endregion 109 | 110 | protected: 111 | /// 112 | /// Retrieves the GUID of the element. 113 | /// 114 | static const GUID & GetGUID() noexcept 115 | { 116 | static const GUID guid = GUID_UI_ELEMENT; 117 | 118 | return guid; 119 | } 120 | 121 | virtual void GetColors() noexcept = 0; 122 | 123 | virtual bool IsWebViewVisible() const noexcept = 0; 124 | 125 | virtual void SetWebViewVisibility(bool visible) noexcept; 126 | 127 | private: 128 | #pragma region Playback callback methods 129 | 130 | void on_playback_starting(play_control::t_track_command command, bool paused); 131 | void on_playback_new_track(metadb_handle_ptr hTrack); 132 | void on_playback_stop(play_control::t_stop_reason reason); 133 | void on_playback_seek(double time); 134 | void on_playback_pause(bool state); 135 | void on_playback_edited(metadb_handle_ptr hTrack); 136 | void on_playback_dynamic_info(const file_info & fileInfo); 137 | void on_playback_dynamic_info_track(const file_info & fileInfo); 138 | void on_playback_time(double time); 139 | void on_volume_change(float newValue); 140 | 141 | #pragma endregion 142 | 143 | public: 144 | #pragma region playlist_callback 145 | 146 | void on_items_added(t_size playlistIndex, t_size startIndex, metadb_handle_list_cref data, const bit_array & selection); 147 | void on_items_reordered(t_size playlistIndex, const t_size * order, t_size count); 148 | void on_items_removing(t_size playlistIndex, const bit_array & mask, t_size oldCount, t_size newCount); 149 | void on_items_removed(t_size playlistIndex, const bit_array & mask, t_size oldCount, t_size newCount); 150 | 151 | void on_items_selection_change(t_size playlistIndex, const bit_array & affectedItems, const bit_array & state); 152 | 153 | void on_items_modified(t_size playlistIndex, const bit_array & mask); 154 | void on_items_modified_fromplayback(t_size playlistIndex, const bit_array & mask, play_control::t_display_level displayLevel); 155 | void on_items_replaced(t_size playlistIndex, const bit_array & mask, const pfc::list_base_const_t & data); 156 | 157 | void on_item_focus_change(t_size playlistIndex, t_size oldItemIndex, t_size newItemIndex); 158 | void on_item_ensure_visible(t_size playlistIndex, t_size itemIndex); 159 | 160 | void on_playlist_activate(t_size oldPlaylistIndex, t_size newPlaylistIndex); 161 | void on_playlist_created(t_size playlistIndex, const char * name, t_size size); 162 | void on_playlists_reorder(const t_size * order, t_size count); 163 | void on_playlists_removing(const bit_array & mask, t_size oldCount, t_size newCount); 164 | void on_playlists_removed(const bit_array & mask, t_size oldCount, t_size newCount); 165 | void on_playlist_renamed(t_size playlistIndex, const char * name, t_size size); 166 | 167 | void on_playlist_locked(t_size playlistIndex, bool isLocked); 168 | 169 | void on_default_format_changed() override; 170 | 171 | void on_playback_order_changed(t_size playbackOrderIndex); 172 | 173 | #pragma endregion 174 | 175 | private: 176 | void ExecuteScript(const std::wstring & script) const noexcept; 177 | 178 | #pragma region CWindowImpl 179 | 180 | LRESULT OnCreate(LPCREATESTRUCT cs) noexcept; 181 | void OnDestroy() noexcept; 182 | void OnSize(UINT nType, CSize size) noexcept; 183 | BOOL OnEraseBackground(CDCHandle dc) noexcept; 184 | void OnPaint(CDCHandle dc) noexcept; 185 | LRESULT OnTemplateChanged(UINT msg, WPARAM wParam, LPARAM lParam) noexcept; 186 | LRESULT OnWebViewReady(UINT msg, WPARAM wParam, LPARAM lParam) noexcept; 187 | LRESULT OnAsync(UINT msg, WPARAM wParam, LPARAM lParam) noexcept; 188 | 189 | BEGIN_MSG_MAP_EX(UIElement) 190 | MSG_WM_CREATE(OnCreate) 191 | MSG_WM_DESTROY(OnDestroy) 192 | MSG_WM_SIZE(OnSize) 193 | MSG_WM_ERASEBKGND(OnEraseBackground) 194 | MSG_WM_PAINT(OnPaint) 195 | 196 | MESSAGE_HANDLER_EX(UM_TEMPLATE_CHANGED, OnTemplateChanged) 197 | MESSAGE_HANDLER_EX(UM_WEB_VIEW_READY, OnWebViewReady) 198 | MESSAGE_HANDLER_EX(UM_ASYNC, OnAsync) 199 | END_MSG_MAP() 200 | 201 | #pragma endregion 202 | 203 | void Initialize(); 204 | 205 | HRESULT PostChunk(const audio_sample * samples, size_t sampleCount, uint32_t sampleRate, uint32_t channelCount, uint32_t channelConfig) noexcept; 206 | 207 | private: 208 | bool GetWebViewVersion(std::wstring & versionInfo); 209 | 210 | HRESULT CreateWebView(); 211 | HRESULT RecreateWebView() noexcept; 212 | void DeleteWebView() noexcept; 213 | 214 | HRESULT SetDarkMode(bool enabled) const noexcept; 215 | HRESULT SetDefaultBackgroundColor() const noexcept; 216 | 217 | HRESULT CreateContextMenu(const wchar_t * itemLabel, const wchar_t * iconName) noexcept; 218 | HRESULT ClearBrowserData() const noexcept; 219 | HRESULT RequestBrowserProfileDeletion() const noexcept; 220 | 221 | void InitializeFileWatcher(); 222 | void InitializeWebView(); 223 | 224 | std::wstring GetTemplateFilePath() const noexcept; 225 | 226 | void ShowPreferences() noexcept; 227 | 228 | void OnConfigurationChanged() noexcept; 229 | 230 | void StartTimer() noexcept; 231 | void StopTimer() noexcept; 232 | 233 | static void CALLBACK TimerCallback(HWND unnamedParam1, UINT unnamedParam2, UINT_PTR unnamedParam3, DWORD unnamedParam4) noexcept; 234 | 235 | void OnTimer() noexcept; 236 | 237 | protected: 238 | configuration_t _Configuration; 239 | 240 | COLORREF _ForegroundColor; 241 | COLORREF _BackgroundColor; 242 | 243 | private: 244 | fb2k::CCoreDarkModeHooks _DarkMode; 245 | playback_control::ptr _PlaybackControl; 246 | 247 | std::wstring _ExpandedTemplateFilePath; 248 | 249 | wil::com_ptr _Environment; 250 | wil::com_ptr _Controller; 251 | wil::com_ptr _WebView; 252 | wil::com_ptr _ContextSubMenu; 253 | 254 | EventRegistrationToken _NavigationStartingToken = {}; 255 | EventRegistrationToken _NavigationCompletedToken = {}; 256 | EventRegistrationToken _FrameCreatedToken = {}; 257 | EventRegistrationToken _ContextMenuRequestedToken = {}; 258 | EventRegistrationToken _BrowserProcessExitedToken = {}; 259 | 260 | wil::com_ptr _HostObject; 261 | 262 | bool _IsNavigationCompleted; 263 | 264 | FileWatcher _FileWatcher; 265 | 266 | PTP_TIMER _ThreadPoolTimer; 267 | bool _IsFrozen; 268 | bool _IsHidden; 269 | 270 | visualisation_stream_v2::ptr _VisualisationStream; 271 | double _LastPlaybackTime; 272 | uint32_t _SampleRate; 273 | 274 | SharedBuffer _SharedBuffer; 275 | }; 276 | -------------------------------------------------------------------------------- /HostObjectImplFiles.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: HostObjectImplFiles.cpp (2024.12.02) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "HostObjectImpl.h" 7 | 8 | #include 9 | #pragma comment(lib, "pathcch") 10 | 11 | #pragma comment(lib, "crypt32") 12 | 13 | #include "Support.h" 14 | #include "Resources.h" 15 | #include "Encoding.h" 16 | 17 | #include "ProcessLocationsHandler.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | /// 28 | /// Gets the specified artwork of the currently selected item in the current playlist. 29 | /// 30 | STDMETHODIMP HostObject::getArtwork(BSTR type, BSTR * image) 31 | { 32 | *image = ::SysAllocString(L""); // Return an empty string by default and in case of an error. 33 | 34 | if (type == nullptr) 35 | return E_INVALIDARG; 36 | 37 | // Verify the requested artwork type. 38 | GUID AlbumArtId; 39 | 40 | if (::_wcsicmp(type, L"front") == 0) 41 | { 42 | AlbumArtId = album_art_ids::cover_front; 43 | } 44 | else 45 | if (::_wcsicmp(type, L"back") == 0) 46 | { 47 | AlbumArtId = album_art_ids::cover_back; 48 | } 49 | else 50 | if (::_wcsicmp(type, L"disc") == 0) 51 | { 52 | AlbumArtId = album_art_ids::disc; 53 | } 54 | else 55 | if (::_wcsicmp(type, L"icon") == 0) 56 | { 57 | AlbumArtId = album_art_ids::icon; 58 | } 59 | else 60 | if (::_wcsicmp(type, L"artist") == 0) 61 | { 62 | AlbumArtId = album_art_ids::artist; 63 | } 64 | else 65 | return S_OK; 66 | 67 | metadb_handle_ptr Handle; 68 | 69 | if (!_PlaybackControl->get_now_playing(Handle)) 70 | return S_OK; 71 | 72 | static_api_ptr_t Manager; 73 | 74 | album_art_data::ptr aad; 75 | 76 | try 77 | { 78 | album_art_extractor_instance_v2::ptr Extractor = Manager->open_v3(pfc::list_single_ref_t(Handle), pfc::list_single_ref_t(AlbumArtId), nullptr, fb2k::noAbort); 79 | 80 | if (Extractor.is_empty()) 81 | return S_OK; 82 | 83 | // Query the external search patterns first. 84 | try 85 | { 86 | album_art_path_list::ptr Paths = Extractor->query_paths(AlbumArtId, fb2k::noAbort); 87 | 88 | if (Paths.is_valid()) 89 | { 90 | for (size_t i = 0; i < Paths->get_count(); ++i) 91 | { 92 | pfc::string Extension = pfc::io::path::getFileExtension(Paths->get_path(i)); 93 | 94 | if (!Extension.isEmpty() && ((::_stricmp(Extension.c_str(), ".jpg") == 0) || (::_stricmp(Extension.c_str(), ".png") == 0) || (::_stricmp(Extension.c_str(), ".webp") == 0) || (::_stricmp(Extension.c_str(), ".gif") == 0))) 95 | { 96 | ::SysFreeString(*image); // Free the empty string. 97 | 98 | *image = ::SysAllocString(::UTF8ToWide(Paths->get_path(i)).c_str()); 99 | 100 | return S_OK; 101 | } 102 | } 103 | } 104 | } 105 | catch (...) 106 | { 107 | } 108 | 109 | // Query the embedded art. 110 | if (!Extractor->query(AlbumArtId, aad, fb2k::noAbort)) 111 | { 112 | // Query the stub the stub path. 113 | try 114 | { 115 | Extractor = Manager->open_stub(fb2k::noAbort); 116 | 117 | if (!Extractor->query(AlbumArtId, aad, fb2k::noAbort)) 118 | return S_OK; 119 | } 120 | catch (std::exception & e) 121 | { 122 | console::print(STR_COMPONENT_BASENAME " failed to query album art stub: ", e.what()); 123 | } 124 | } 125 | } 126 | catch (...) 127 | { 128 | // Query the stub the stub path. 129 | try 130 | { 131 | album_art_extractor_instance_v2::ptr Extractor = Manager->open_stub(fb2k::noAbort); 132 | 133 | if (!Extractor->query(AlbumArtId, aad, fb2k::noAbort)) 134 | return S_OK; 135 | } 136 | catch (std::exception & e) 137 | { 138 | console::print(STR_COMPONENT_BASENAME " failed to query album art stub: ", e.what()); 139 | } 140 | } 141 | 142 | if (!aad.is_empty()) 143 | ToBase64((const BYTE *) aad->data(), (DWORD) aad->size(), image); 144 | 145 | return S_OK; 146 | } 147 | 148 | #pragma region Files 149 | 150 | /// 151 | /// Reads the specified file and returns it as a string. 152 | /// 153 | STDMETHODIMP HostObject::readAllText(BSTR filePath, __int32 codePage, BSTR * text) 154 | { 155 | if ((filePath == nullptr) || (text == nullptr)) 156 | return E_INVALIDARG; 157 | 158 | if (codePage == 0) 159 | codePage = 65001; 160 | 161 | HANDLE hFile = ::CreateFileW(filePath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); 162 | 163 | if (hFile == INVALID_HANDLE_VALUE) 164 | return HRESULT_FROM_WIN32(::GetLastError()); 165 | 166 | LARGE_INTEGER FileSize; 167 | 168 | HRESULT hr = S_OK; 169 | 170 | if (::GetFileSizeEx(hFile, &FileSize)) 171 | { 172 | if ((FileSize.HighPart == 0) && (FileSize.LowPart < 0xFFFFFFFF - 2)) 173 | { 174 | std::string Text; 175 | 176 | Text.resize((size_t) FileSize.LowPart + 2); 177 | 178 | DWORD BytesRead; 179 | 180 | if (::ReadFile(hFile, (void *) Text.c_str(), FileSize.LowPart, &BytesRead, nullptr) && (BytesRead == FileSize.LowPart)) 181 | *text = ::SysAllocString(::CodePageToWide((uint32_t) codePage, Text).c_str()); 182 | else 183 | hr = HRESULT_FROM_WIN32(::GetLastError()); 184 | } 185 | else 186 | hr = E_OUTOFMEMORY; 187 | } 188 | else 189 | hr = HRESULT_FROM_WIN32(::GetLastError()); 190 | 191 | ::CloseHandle(hFile); 192 | 193 | return hr; 194 | } 195 | 196 | /// 197 | /// Reads the specified file and returns it as a string. 198 | /// 199 | STDMETHODIMP HostObject::readDirectory(BSTR directoryPath, BSTR searchPattern, BSTR * json) 200 | { 201 | if ((directoryPath == nullptr) || (searchPattern == nullptr) || (json == nullptr)) 202 | return E_INVALIDARG; 203 | 204 | *json = ::SysAllocString(L""); // Return an empty string by default and in case of an error. 205 | 206 | WCHAR PathName[MAX_PATH]; 207 | 208 | if (!SUCCEEDED(::PathCchCombineEx(PathName, _countof(PathName), directoryPath, searchPattern, PATHCCH_ALLOW_LONG_PATHS))) 209 | return HRESULT_FROM_WIN32(::GetLastError()); 210 | 211 | WIN32_FIND_DATA fd = {}; 212 | 213 | HANDLE hFind = ::FindFirstFileW(PathName, &fd); 214 | 215 | if (hFind == INVALID_HANDLE_VALUE) 216 | return HRESULT_FROM_WIN32(::GetLastError()); 217 | 218 | BOOL Success = TRUE; 219 | 220 | if (::wcscmp(fd.cFileName, L".") == 0) 221 | { 222 | Success = ::FindNextFileW(hFind, &fd); 223 | 224 | if (Success && ::wcscmp(fd.cFileName, L"..") == 0) 225 | Success = ::FindNextFileW(hFind, &fd); 226 | } 227 | 228 | std::wstring Result = L"["; 229 | bool IsFirstItem = true; 230 | 231 | while (Success) 232 | { 233 | if (!IsFirstItem) 234 | Result.append(L","); 235 | 236 | uint64_t FileSize = (((uint64_t) fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; 237 | 238 | Result.append(::FormatText(LR"({"name": "%s", "size": %lu, "isDirectory": %s})", fd.cFileName, FileSize, ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? L"true" : L"false")).c_str()); 239 | 240 | IsFirstItem = false; 241 | 242 | Success = ::FindNextFileW(hFind, &fd); 243 | } 244 | 245 | Result.append(L"]"); 246 | 247 | ::FindClose(hFind); 248 | 249 | *json = ::SysAllocString(Result.c_str()); 250 | 251 | return S_OK; 252 | } 253 | 254 | /// 255 | /// Reads the specified file and returns it as a string. 256 | /// 257 | STDMETHODIMP HostObject::readImage(BSTR filePath, BSTR * image) 258 | { 259 | *image = ::SysAllocString(L""); // Return an empty string by default and in case of an error. 260 | 261 | if ((filePath == nullptr) || (image == nullptr)) 262 | return E_INVALIDARG; 263 | 264 | HANDLE hFile = ::CreateFileW(filePath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); 265 | 266 | if (hFile == INVALID_HANDLE_VALUE) 267 | return HRESULT_FROM_WIN32(::GetLastError()); 268 | 269 | LARGE_INTEGER FileSize; 270 | 271 | HRESULT hr = S_OK; 272 | 273 | if (::GetFileSizeEx(hFile, &FileSize)) 274 | { 275 | if (FileSize.HighPart == 0) 276 | { 277 | BYTE * Data = new BYTE[FileSize.LowPart]; 278 | 279 | if (Data != nullptr) 280 | { 281 | DWORD BytesRead; 282 | 283 | if (::ReadFile(hFile, Data, FileSize.LowPart, &BytesRead, nullptr) && (BytesRead == FileSize.LowPart)) 284 | ToBase64(Data, FileSize.LowPart, image); 285 | else 286 | hr = HRESULT_FROM_WIN32(::GetLastError()); 287 | 288 | delete[] Data; 289 | } 290 | } 291 | else 292 | hr = E_OUTOFMEMORY; 293 | } 294 | else 295 | hr = HRESULT_FROM_WIN32(::GetLastError()); 296 | 297 | ::CloseHandle(hFile); 298 | 299 | return S_OK; 300 | } 301 | -------------------------------------------------------------------------------- /foo_uie_webview.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.9.34728.123 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foo_uie_webview", "foo_uie_webview.vcxproj", "{7910AC54-167E-476F-A481-A05AA8A11974}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3rdParty", "3rdParty", "{5632D692-D866-444F-9EA2-BA9E987B874D}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_component_client", "..\sdk\foobar2000\foobar2000_component_client\foobar2000_component_client.vcxproj", "{71AD2674-065B-48F5-B8B0-E1F9D3892081}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "foobar2000", "foobar2000", "{A3785D62-C7E4-4CE9-86B4-CBBD04AE0DCD}" 13 | EndProject 14 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_SDK", "..\sdk\foobar2000\SDK\foobar2000_SDK.vcxproj", "{E8091321-D79D-4575-86EF-064EA1A4A20D}" 15 | EndProject 16 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pfc", "..\sdk\pfc\pfc.vcxproj", "{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}" 17 | EndProject 18 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_sdk_helpers", "..\sdk\foobar2000\helpers\foobar2000_sdk_helpers.vcxproj", "{EE47764E-A202-4F85-A767-ABDAB4AFF35F}" 19 | EndProject 20 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libPPUI", "..\sdk\libPPUI\libPPUI.vcxproj", "{7729EB82-4069-4414-964B-AD399091A03F}" 21 | EndProject 22 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "columns_ui_sdk", "..\3rdParty\columns_ui_sdk\columns_ui-sdk-public.vcxproj", "{93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{293E92C8-6881-4837-8400-34B8CE68A6B2}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug FB2K|x64 = Debug FB2K|x64 29 | Debug FB2K|x86 = Debug FB2K|x86 30 | Debug|x64 = Debug|x64 31 | Debug|x86 = Debug|x86 32 | Release FB2K|x64 = Release FB2K|x64 33 | Release FB2K|x86 = Release FB2K|x86 34 | Release|x64 = Release|x64 35 | Release|x86 = Release|x86 36 | EndGlobalSection 37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 38 | {7910AC54-167E-476F-A481-A05AA8A11974}.Debug FB2K|x64.ActiveCfg = Debug|x64 39 | {7910AC54-167E-476F-A481-A05AA8A11974}.Debug FB2K|x64.Build.0 = Debug|x64 40 | {7910AC54-167E-476F-A481-A05AA8A11974}.Debug FB2K|x86.ActiveCfg = Debug|Win32 41 | {7910AC54-167E-476F-A481-A05AA8A11974}.Debug FB2K|x86.Build.0 = Debug|Win32 42 | {7910AC54-167E-476F-A481-A05AA8A11974}.Debug|x64.ActiveCfg = Debug|x64 43 | {7910AC54-167E-476F-A481-A05AA8A11974}.Debug|x64.Build.0 = Debug|x64 44 | {7910AC54-167E-476F-A481-A05AA8A11974}.Debug|x86.ActiveCfg = Debug|Win32 45 | {7910AC54-167E-476F-A481-A05AA8A11974}.Debug|x86.Build.0 = Debug|Win32 46 | {7910AC54-167E-476F-A481-A05AA8A11974}.Release FB2K|x64.ActiveCfg = Release|x64 47 | {7910AC54-167E-476F-A481-A05AA8A11974}.Release FB2K|x64.Build.0 = Release|x64 48 | {7910AC54-167E-476F-A481-A05AA8A11974}.Release FB2K|x86.ActiveCfg = Release|Win32 49 | {7910AC54-167E-476F-A481-A05AA8A11974}.Release FB2K|x86.Build.0 = Release|Win32 50 | {7910AC54-167E-476F-A481-A05AA8A11974}.Release|x64.ActiveCfg = Release|x64 51 | {7910AC54-167E-476F-A481-A05AA8A11974}.Release|x64.Build.0 = Release|x64 52 | {7910AC54-167E-476F-A481-A05AA8A11974}.Release|x86.ActiveCfg = Release|Win32 53 | {7910AC54-167E-476F-A481-A05AA8A11974}.Release|x86.Build.0 = Release|Win32 54 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug FB2K|x64.ActiveCfg = Debug|x64 55 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug FB2K|x64.Build.0 = Debug|x64 56 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug FB2K|x86.ActiveCfg = Debug|Win32 57 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug FB2K|x86.Build.0 = Debug|Win32 58 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|x64.ActiveCfg = Debug|x64 59 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|x64.Build.0 = Debug|x64 60 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|x86.ActiveCfg = Debug|Win32 61 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|x86.Build.0 = Debug|Win32 62 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release FB2K|x64.ActiveCfg = Release|x64 63 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release FB2K|x64.Build.0 = Release|x64 64 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release FB2K|x86.ActiveCfg = Release|Win32 65 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release FB2K|x86.Build.0 = Release|Win32 66 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|x64.ActiveCfg = Release|x64 67 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|x64.Build.0 = Release|x64 68 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|x86.ActiveCfg = Release|Win32 69 | {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|x86.Build.0 = Release|Win32 70 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug FB2K|x64.ActiveCfg = Debug|x64 71 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug FB2K|x64.Build.0 = Debug|x64 72 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug FB2K|x86.ActiveCfg = Debug|Win32 73 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug FB2K|x86.Build.0 = Debug|Win32 74 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|x64.ActiveCfg = Debug|x64 75 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|x64.Build.0 = Debug|x64 76 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|x86.ActiveCfg = Debug|Win32 77 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|x86.Build.0 = Debug|Win32 78 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release FB2K|x64.ActiveCfg = Release|x64 79 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release FB2K|x64.Build.0 = Release|x64 80 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release FB2K|x86.ActiveCfg = Release|Win32 81 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release FB2K|x86.Build.0 = Release|Win32 82 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|x64.ActiveCfg = Release|x64 83 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|x64.Build.0 = Release|x64 84 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|x86.ActiveCfg = Release|Win32 85 | {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|x86.Build.0 = Release|Win32 86 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug FB2K|x64.ActiveCfg = Debug FB2K|x64 87 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug FB2K|x64.Build.0 = Debug FB2K|x64 88 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug FB2K|x86.ActiveCfg = Debug FB2K|Win32 89 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug FB2K|x86.Build.0 = Debug FB2K|Win32 90 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x64.ActiveCfg = Debug|x64 91 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x64.Build.0 = Debug|x64 92 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x86.ActiveCfg = Debug|Win32 93 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x86.Build.0 = Debug|Win32 94 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release FB2K|x64.ActiveCfg = Release FB2K|x64 95 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release FB2K|x64.Build.0 = Release FB2K|x64 96 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release FB2K|x86.ActiveCfg = Release FB2K|Win32 97 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release FB2K|x86.Build.0 = Release FB2K|Win32 98 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x64.ActiveCfg = Release|x64 99 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x64.Build.0 = Release|x64 100 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x86.ActiveCfg = Release|Win32 101 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x86.Build.0 = Release|Win32 102 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug FB2K|x64.ActiveCfg = Debug|x64 103 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug FB2K|x64.Build.0 = Debug|x64 104 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug FB2K|x86.ActiveCfg = Debug|Win32 105 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug FB2K|x86.Build.0 = Debug|Win32 106 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|x64.ActiveCfg = Debug|x64 107 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|x64.Build.0 = Debug|x64 108 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|x86.ActiveCfg = Debug|Win32 109 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|x86.Build.0 = Debug|Win32 110 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release FB2K|x64.ActiveCfg = Release|x64 111 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release FB2K|x64.Build.0 = Release|x64 112 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release FB2K|x86.ActiveCfg = Release|Win32 113 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release FB2K|x86.Build.0 = Release|Win32 114 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|x64.ActiveCfg = Release|x64 115 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|x64.Build.0 = Release|x64 116 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|x86.ActiveCfg = Release|Win32 117 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|x86.Build.0 = Release|Win32 118 | {7729EB82-4069-4414-964B-AD399091A03F}.Debug FB2K|x64.ActiveCfg = Debug|x64 119 | {7729EB82-4069-4414-964B-AD399091A03F}.Debug FB2K|x64.Build.0 = Debug|x64 120 | {7729EB82-4069-4414-964B-AD399091A03F}.Debug FB2K|x86.ActiveCfg = Debug|Win32 121 | {7729EB82-4069-4414-964B-AD399091A03F}.Debug FB2K|x86.Build.0 = Debug|Win32 122 | {7729EB82-4069-4414-964B-AD399091A03F}.Debug|x64.ActiveCfg = Debug|x64 123 | {7729EB82-4069-4414-964B-AD399091A03F}.Debug|x64.Build.0 = Debug|x64 124 | {7729EB82-4069-4414-964B-AD399091A03F}.Debug|x86.ActiveCfg = Debug|Win32 125 | {7729EB82-4069-4414-964B-AD399091A03F}.Debug|x86.Build.0 = Debug|Win32 126 | {7729EB82-4069-4414-964B-AD399091A03F}.Release FB2K|x64.ActiveCfg = Release|x64 127 | {7729EB82-4069-4414-964B-AD399091A03F}.Release FB2K|x64.Build.0 = Release|x64 128 | {7729EB82-4069-4414-964B-AD399091A03F}.Release FB2K|x86.ActiveCfg = Release|Win32 129 | {7729EB82-4069-4414-964B-AD399091A03F}.Release FB2K|x86.Build.0 = Release|Win32 130 | {7729EB82-4069-4414-964B-AD399091A03F}.Release|x64.ActiveCfg = Release|x64 131 | {7729EB82-4069-4414-964B-AD399091A03F}.Release|x64.Build.0 = Release|x64 132 | {7729EB82-4069-4414-964B-AD399091A03F}.Release|x86.ActiveCfg = Release|Win32 133 | {7729EB82-4069-4414-964B-AD399091A03F}.Release|x86.Build.0 = Release|Win32 134 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Debug FB2K|x64.ActiveCfg = Debug|x64 135 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Debug FB2K|x64.Build.0 = Debug|x64 136 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Debug FB2K|x86.ActiveCfg = Debug|Win32 137 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Debug FB2K|x86.Build.0 = Debug|Win32 138 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Debug|x64.ActiveCfg = Debug|x64 139 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Debug|x64.Build.0 = Debug|x64 140 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Debug|x86.ActiveCfg = Debug|Win32 141 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Debug|x86.Build.0 = Debug|Win32 142 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Release FB2K|x64.ActiveCfg = Release|x64 143 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Release FB2K|x64.Build.0 = Release|x64 144 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Release FB2K|x86.ActiveCfg = Release|Win32 145 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Release FB2K|x86.Build.0 = Release|Win32 146 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Release|x64.ActiveCfg = Release|x64 147 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Release|x64.Build.0 = Release|x64 148 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Release|x86.ActiveCfg = Release|Win32 149 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E}.Release|x86.Build.0 = Release|Win32 150 | EndGlobalSection 151 | GlobalSection(SolutionProperties) = preSolution 152 | HideSolutionNode = FALSE 153 | EndGlobalSection 154 | GlobalSection(NestedProjects) = preSolution 155 | {71AD2674-065B-48F5-B8B0-E1F9D3892081} = {A3785D62-C7E4-4CE9-86B4-CBBD04AE0DCD} 156 | {A3785D62-C7E4-4CE9-86B4-CBBD04AE0DCD} = {5632D692-D866-444F-9EA2-BA9E987B874D} 157 | {E8091321-D79D-4575-86EF-064EA1A4A20D} = {A3785D62-C7E4-4CE9-86B4-CBBD04AE0DCD} 158 | {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C} = {A3785D62-C7E4-4CE9-86B4-CBBD04AE0DCD} 159 | {EE47764E-A202-4F85-A767-ABDAB4AFF35F} = {A3785D62-C7E4-4CE9-86B4-CBBD04AE0DCD} 160 | {7729EB82-4069-4414-964B-AD399091A03F} = {A3785D62-C7E4-4CE9-86B4-CBBD04AE0DCD} 161 | {93EC0EDE-01CD-4FB0-B8E8-4F2A027E026E} = {5632D692-D866-444F-9EA2-BA9E987B874D} 162 | EndGlobalSection 163 | GlobalSection(ExtensibilityGlobals) = postSolution 164 | SolutionGuid = {53D0946C-F70B-4D7B-9782-F1166908E10F} 165 | EndGlobalSection 166 | EndGlobal 167 | -------------------------------------------------------------------------------- /HostObjectImplPlaylists.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: HostObjectImplPlaylists.cpp (2024.12.15) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "HostObjectImpl.h" 7 | 8 | #include 9 | #pragma comment(lib, "pathcch") 10 | 11 | #pragma comment(lib, "crypt32") 12 | 13 | #include "Support.h" 14 | #include "Resources.h" 15 | #include "Encoding.h" 16 | 17 | #include "ProcessLocationsHandler.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #pragma region Playlists 28 | 29 | /// 30 | /// Gets the number of playlists. 31 | /// 32 | STDMETHODIMP HostObject::get_playlistCount(int * count) 33 | { 34 | if (count == nullptr) 35 | return E_INVALIDARG; 36 | 37 | *count = (int) playlist_manager::get()->get_playlist_count(); 38 | 39 | return S_OK; 40 | } 41 | 42 | /// 43 | /// Gets the index of the active playlist. 44 | /// 45 | STDMETHODIMP HostObject::get_activePlaylist(int * playlistIndex) 46 | { 47 | if (playlistIndex == nullptr) 48 | return E_INVALIDARG; 49 | 50 | *playlistIndex = (int) playlist_manager::get()->get_active_playlist(); 51 | 52 | return S_OK; 53 | } 54 | 55 | /// 56 | /// Sets the index of the active playlist. 57 | /// 58 | STDMETHODIMP HostObject::put_activePlaylist(int playlistIndex) 59 | { 60 | auto Manager = playlist_manager_v4::get(); 61 | 62 | if (playlistIndex == -1) 63 | playlistIndex = (int) Manager->get_active_playlist(); 64 | 65 | Manager->set_active_playlist((size_t) playlistIndex); 66 | 67 | return S_OK; 68 | } 69 | 70 | /// 71 | /// Gets the index of the playing playlist. 72 | /// 73 | STDMETHODIMP HostObject::get_playingPlaylist(int * playlistIndex) 74 | { 75 | if (playlistIndex == nullptr) 76 | return E_INVALIDARG; 77 | 78 | *playlistIndex = (int) playlist_manager::get()->get_playing_playlist(); 79 | 80 | return S_OK; 81 | } 82 | 83 | /// 84 | /// Sets the index of the playing playlist. 85 | /// 86 | STDMETHODIMP HostObject::put_playingPlaylist(int playlistIndex) 87 | { 88 | auto Manager = playlist_manager_v4::get(); 89 | 90 | if (playlistIndex == -1) 91 | playlistIndex = (int) Manager->get_active_playlist(); 92 | 93 | Manager->set_playing_playlist((size_t) playlistIndex); 94 | 95 | return S_OK; 96 | } 97 | 98 | /// 99 | /// Gets the name of the specified playlist. 100 | /// 101 | STDMETHODIMP HostObject::getPlaylistName(int playlistIndex, BSTR * name) 102 | { 103 | if (name == nullptr) 104 | return E_INVALIDARG; 105 | 106 | auto Manager = playlist_manager_v4::get(); 107 | 108 | if (playlistIndex == -1) 109 | playlistIndex = (int) Manager->get_active_playlist(); 110 | 111 | pfc::string Name; 112 | 113 | Manager->playlist_get_name((size_t) playlistIndex, Name); 114 | 115 | *name = ::SysAllocString(pfc::wideFromUTF8(Name).c_str()); 116 | 117 | return S_OK; 118 | } 119 | 120 | /// 121 | /// Gets the name of the specified playlist. 122 | /// 123 | STDMETHODIMP HostObject::setPlaylistName(int playlistIndex, BSTR name) 124 | { 125 | if (name == nullptr) 126 | return E_INVALIDARG; 127 | 128 | auto Manager = playlist_manager_v4::get(); 129 | 130 | if (playlistIndex == -1) 131 | playlistIndex = (int) Manager->get_active_playlist(); 132 | 133 | pfc::string Name = pfc::utf8FromWide(name).c_str(); 134 | 135 | Manager->playlist_rename((size_t) playlistIndex, Name.c_str(), Name.length()); 136 | 137 | return S_OK; 138 | } 139 | 140 | /// 141 | /// Finds the index of the specified playlist. 142 | /// 143 | STDMETHODIMP HostObject::findPlaylist(BSTR name, int * playlistIndex) 144 | { 145 | if (name == nullptr) 146 | return E_INVALIDARG; 147 | 148 | pfc::string Name = pfc::utf8FromWide(name).c_str(); 149 | 150 | *playlistIndex = (int) playlist_manager::get()->find_playlist(Name.c_str(), Name.length()); 151 | 152 | return S_OK; 153 | } 154 | 155 | /// 156 | /// Gets the number of items of the specified playlist. 157 | /// 158 | STDMETHODIMP HostObject::getPlaylistItemCount(int playlistIndex, int * itemCount) 159 | { 160 | if (itemCount == nullptr) 161 | return E_INVALIDARG; 162 | 163 | auto Manager = playlist_manager_v4::get(); 164 | 165 | if (playlistIndex == -1) 166 | playlistIndex = (int) Manager->get_active_playlist(); 167 | 168 | *itemCount = (int) Manager->playlist_get_item_count((size_t) playlistIndex); 169 | 170 | return S_OK; 171 | } 172 | 173 | /// 174 | /// Gets the number of selected items of the specified playlist. 175 | /// 176 | STDMETHODIMP HostObject::getSelectedPlaylistItemCount(int playlistIndex, int maxItems, int * itemCount) 177 | { 178 | if (itemCount == nullptr) 179 | return E_INVALIDARG; 180 | 181 | auto Manager = playlist_manager_v4::get(); 182 | 183 | if (playlistIndex == -1) 184 | playlistIndex = (int) Manager->get_active_playlist(); 185 | 186 | *itemCount = (int) Manager->playlist_get_selection_count((size_t) playlistIndex, (size_t) maxItems); 187 | 188 | return S_OK; 189 | } 190 | 191 | /// 192 | /// Gets the index of the focused playlist item. 193 | /// 194 | STDMETHODIMP HostObject::getFocusedPlaylistItem(int playlistIndex, int * itemIndex) 195 | { 196 | if (itemIndex == nullptr) 197 | return E_INVALIDARG; 198 | 199 | auto Manager = playlist_manager_v4::get(); 200 | 201 | if (playlistIndex == -1) 202 | playlistIndex = (int) Manager->get_active_playlist(); 203 | 204 | *itemIndex = (int) Manager->playlist_get_focus_item((size_t) playlistIndex); 205 | 206 | return S_OK; 207 | } 208 | 209 | /// 210 | /// Gets the name of the specified playlist. 211 | /// 212 | STDMETHODIMP HostObject::setFocusedPlaylistItem(int playlistIndex, int itemIndex) 213 | { 214 | NormalizeIndexes(playlistIndex, itemIndex); 215 | 216 | auto Manager = playlist_manager_v4::get(); 217 | 218 | Manager->playlist_set_focus_item((size_t) playlistIndex, (size_t) itemIndex); 219 | 220 | return S_OK; 221 | } 222 | 223 | /// 224 | /// Ensures that the specified item in the specified playlist is visible. 225 | /// 226 | STDMETHODIMP HostObject::ensurePlaylistItemVisible(int playlistIndex, int itemIndex) 227 | { 228 | NormalizeIndexes(playlistIndex, itemIndex); 229 | 230 | auto Manager = playlist_manager_v4::get(); 231 | 232 | Manager->playlist_ensure_visible((size_t) playlistIndex, (size_t) itemIndex); 233 | 234 | return S_OK; 235 | } 236 | 237 | /// 238 | /// Returns true if the specified item in the specified playlist is selected. 239 | /// 240 | STDMETHODIMP HostObject::isPlaylistItemSelected(int playlistIndex, int itemIndex, VARIANT_BOOL * result) 241 | { 242 | NormalizeIndexes(playlistIndex, itemIndex); 243 | 244 | auto Manager = playlist_manager_v4::get(); 245 | 246 | *result = Manager->playlist_is_item_selected((size_t) playlistIndex, (size_t) itemIndex) ? VARIANT_TRUE : VARIANT_FALSE; 247 | 248 | return S_OK; 249 | } 250 | 251 | /// 252 | /// Execute the default action on the specified item in the specified playlist. 253 | /// 254 | STDMETHODIMP HostObject::executePlaylistDefaultAction(int playlistIndex, int itemIndex) 255 | { 256 | NormalizeIndexes(playlistIndex, itemIndex); 257 | 258 | auto Manager = playlist_manager_v4::get(); 259 | 260 | Manager->playlist_execute_default_action((size_t) playlistIndex, (size_t) itemIndex); 261 | 262 | return S_OK; 263 | } 264 | 265 | /// 266 | /// Removes the specified item from the specified playlist. 267 | /// 268 | STDMETHODIMP HostObject::removePlaylistItem(int playlistIndex, int itemIndex) 269 | { 270 | NormalizeIndexes(playlistIndex, itemIndex); 271 | 272 | auto Manager = playlist_manager_v4::get(); 273 | 274 | Manager->playlist_clear_selection((size_t) playlistIndex); 275 | Manager->playlist_set_selection_single((size_t) playlistIndex, (size_t) itemIndex, true); 276 | Manager->playlist_remove_selection((size_t) playlistIndex); 277 | 278 | return S_OK; 279 | } 280 | 281 | /// 282 | /// Creates a new playlist at the specified index. 283 | /// 284 | STDMETHODIMP HostObject::createPlaylist(int playlistIndex, BSTR name, int * newPlaylistIndex) 285 | { 286 | if (newPlaylistIndex == nullptr) 287 | return E_INVALIDARG; 288 | 289 | auto Manager = playlist_manager::get(); 290 | 291 | if ((name != nullptr) && (*name != '\0')) 292 | { 293 | pfc::string Name = pfc::utf8FromWide(name).c_str(); 294 | 295 | *newPlaylistIndex = (int) Manager->create_playlist(Name.c_str(), Name.length(), (size_t) playlistIndex); 296 | } 297 | else 298 | *newPlaylistIndex = (int) Manager->create_playlist_autoname((size_t) playlistIndex); 299 | 300 | return S_OK; 301 | } 302 | 303 | /// 304 | /// Adds an item to the specified playlist after the specified item using a location and optionally selects it. 305 | /// 306 | STDMETHODIMP HostObject::addPath(int playlistIndex, int itemIndex, BSTR filePath, VARIANT_BOOL selectAddedItem) 307 | { 308 | NormalizeIndexes(playlistIndex, itemIndex); 309 | 310 | auto Manager = playlist_manager_v4::get(); 311 | 312 | pfc::string_list_impl LocationList; 313 | 314 | LocationList.add_item(::WideToUTF8(filePath).c_str()); 315 | 316 | playlist_incoming_item_filter_v2::get()->process_locations_async 317 | ( 318 | LocationList, 319 | playlist_incoming_item_filter_v2::op_flag_no_filter | playlist_incoming_item_filter_v2::op_flag_delay_ui, 320 | nullptr, 321 | nullptr, 322 | nullptr, 323 | fb2k::service_new(playlistIndex, itemIndex, selectAddedItem == VARIANT_TRUE) 324 | ); 325 | 326 | return S_OK; 327 | } 328 | 329 | /// 330 | /// Duplicates the specified playlist. 331 | /// 332 | STDMETHODIMP HostObject::duplicatePlaylist(int playlistIndex, BSTR name, int * newPlaylistIndex) 333 | { 334 | if (newPlaylistIndex == nullptr) 335 | return E_INVALIDARG; 336 | 337 | auto Manager = playlist_manager_v4::get(); 338 | 339 | if (playlistIndex == -1) 340 | playlistIndex = (int) Manager->get_active_playlist(); 341 | 342 | pfc::string Name; 343 | 344 | if ((name != nullptr) && (*name != '\0')) 345 | Name = pfc::utf8FromWide(name).c_str(); 346 | else 347 | (void) Manager->playlist_get_name((size_t) playlistIndex, Name); 348 | 349 | metadb_handle_list Items; 350 | 351 | Manager->playlist_get_all_items((size_t) playlistIndex, Items); 352 | 353 | stream_reader_dummy sr; 354 | 355 | *newPlaylistIndex = (int) Manager->create_playlist_ex(Name.c_str(), Name.length(), (size_t) playlistIndex + 1, Items, &sr, fb2k::noAbort); 356 | 357 | return S_OK; 358 | } 359 | 360 | /// 361 | /// Clears the specified playlist. 362 | /// 363 | STDMETHODIMP HostObject::clearPlaylist(int playlistIndex) 364 | { 365 | auto Manager = playlist_manager_v4::get(); 366 | 367 | if (playlistIndex == -1) 368 | playlistIndex = (int) Manager->get_active_playlist(); 369 | 370 | Manager->playlist_clear((size_t) playlistIndex); 371 | 372 | return S_OK; 373 | } 374 | 375 | /// 376 | /// Gets the items of the specified playlist. 377 | /// 378 | STDMETHODIMP HostObject::getPlaylistItems(int playlistIndex, BSTR * json) 379 | { 380 | auto Manager = playlist_manager_v4::get(); 381 | 382 | if (playlistIndex == -1) 383 | playlistIndex = (int) Manager->get_active_playlist(); 384 | 385 | metadb_handle_list hItems; 386 | 387 | Manager->playlist_get_all_items((size_t) playlistIndex, hItems); 388 | 389 | *json = ::SysAllocString(ToJSON(hItems).c_str()); 390 | 391 | return S_OK; 392 | } 393 | 394 | /// 395 | /// Selects the specified item of the specified playlist. 396 | /// 397 | STDMETHODIMP HostObject::selectPlaylistItem(int playlistIndex, int itemIndex) 398 | { 399 | NormalizeIndexes(playlistIndex, itemIndex); 400 | 401 | auto Manager = playlist_manager_v4::get(); 402 | 403 | Manager->playlist_set_selection_single((size_t) playlistIndex, (size_t) itemIndex, true); 404 | 405 | return S_OK; 406 | } 407 | 408 | /// 409 | /// Deselects the specified item of the specified playlist. 410 | /// 411 | STDMETHODIMP HostObject::deselectPlaylistItem(int playlistIndex, int itemIndex) 412 | { 413 | NormalizeIndexes(playlistIndex, itemIndex); 414 | 415 | auto Manager = playlist_manager_v4::get(); 416 | 417 | Manager->playlist_set_selection_single((size_t) playlistIndex, (size_t) itemIndex, false); 418 | 419 | return S_OK; 420 | } 421 | 422 | /// 423 | /// Gets the selected items of the specified playlist. 424 | /// 425 | STDMETHODIMP HostObject::getSelectedPlaylistItems(int playlistIndex, BSTR * json) 426 | { 427 | auto Manager = playlist_manager_v4::get(); 428 | 429 | if (playlistIndex == -1) 430 | playlistIndex = (int) Manager->get_active_playlist(); 431 | 432 | metadb_handle_list hItems; 433 | 434 | Manager->playlist_get_selected_items((size_t) playlistIndex, hItems); 435 | 436 | *json = ::SysAllocString(ToJSON(hItems).c_str()); 437 | 438 | return S_OK; 439 | } 440 | 441 | /// 442 | /// Clears the selection of the specified playlist. 443 | /// 444 | STDMETHODIMP HostObject::clearPlaylistSelection(int playlistIndex) 445 | { 446 | auto Manager = playlist_manager_v4::get(); 447 | 448 | if (playlistIndex == -1) 449 | playlistIndex = (int) Manager->get_active_playlist(); 450 | 451 | Manager->playlist_clear_selection((size_t) playlistIndex); 452 | 453 | return S_OK; 454 | } 455 | 456 | /// 457 | /// Removes the selected items in the specified playlist. 458 | /// 459 | STDMETHODIMP HostObject::removeSelectedPlaylistItems(int playlistIndex) 460 | { 461 | auto Manager = playlist_manager_v4::get(); 462 | 463 | if (playlistIndex == -1) 464 | playlistIndex = (int) Manager->get_active_playlist(); 465 | 466 | Manager->playlist_remove_selection((size_t) playlistIndex, false); 467 | 468 | return S_OK; 469 | } 470 | 471 | /// 472 | /// Removes the unselected items in the specified playlist. 473 | /// 474 | STDMETHODIMP HostObject::removeUnselectedPlaylistItems(int playlistIndex) 475 | { 476 | auto Manager = playlist_manager_v4::get(); 477 | 478 | if (playlistIndex == -1) 479 | playlistIndex = (int) Manager->get_active_playlist(); 480 | 481 | Manager->playlist_remove_selection((size_t) playlistIndex, true); 482 | 483 | return S_OK; 484 | } 485 | 486 | /// 487 | /// Deletes the specified playlist. 488 | /// 489 | STDMETHODIMP HostObject::deletePlaylist(int playlistIndex) 490 | { 491 | auto Manager = playlist_manager_v4::get(); 492 | 493 | if (playlistIndex == -1) 494 | playlistIndex = (int) Manager->get_active_playlist(); 495 | 496 | Manager->remove_playlist((size_t) playlistIndex); 497 | 498 | return S_OK; 499 | } 500 | 501 | #pragma endregion 502 | -------------------------------------------------------------------------------- /Preferences.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: Preferences.cpp (2024.08.04) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "UIElement.h" 15 | #include "UIElementTracker.h" 16 | #include "Exceptions.h" 17 | #include "Encoding.h" 18 | #include "Resources.h" 19 | 20 | #pragma hdrstop 21 | 22 | /// 23 | /// Implements the preferences page for the component. 24 | /// 25 | class Preferences : public CDialogImpl, public preferences_page_instance 26 | { 27 | public: 28 | Preferences(preferences_page_callback::ptr callback) : m_bMsgHandled(FALSE), _Callback(callback) 29 | { 30 | _CurrentElement = _UIElementTracker.GetCurrentElement(); 31 | 32 | if (_CurrentElement != nullptr) 33 | { 34 | _Configuration = _CurrentElement->GetConfiguration(); 35 | 36 | _ActiveConfiguration = _Configuration; 37 | } 38 | } 39 | 40 | virtual ~Preferences() 41 | { 42 | if (_CurrentElement != nullptr) 43 | _CurrentElement->SetConfiguration(_Configuration); 44 | } 45 | 46 | enum 47 | { 48 | IDD = IDD_PREFERENCES 49 | }; 50 | 51 | #pragma region preferences_page_instance 52 | 53 | /// 54 | /// Returns a combination of preferences_state constants. 55 | /// 56 | virtual t_uint32 get_state() final 57 | { 58 | t_uint32 State = preferences_state::resettable | preferences_state::dark_mode_supported; 59 | 60 | if (HasChanged()) 61 | State |= preferences_state::changed; 62 | 63 | return State; 64 | } 65 | 66 | /// 67 | /// Applies the changes to the preferences. 68 | /// 69 | virtual void apply() final 70 | { 71 | wchar_t Text[MAX_PATH]; 72 | 73 | { 74 | GetDlgItemTextW(IDC_NAME, Text, _countof(Text)); 75 | 76 | _Configuration._Name = Text; 77 | } 78 | 79 | { 80 | GetDlgItemTextW(IDC_USER_DATA_FOLDER_PATH, Text, _countof(Text)); 81 | 82 | _Configuration._UserDataFolderPath = Text; 83 | } 84 | 85 | { 86 | GetDlgItemTextW(IDC_FILE_PATH, Text, _countof(Text)); 87 | 88 | _Configuration._TemplateFilePath = Text; 89 | } 90 | 91 | { 92 | GetDlgItemTextW(IDC_WINDOW_SIZE, Text, _countof(Text)); 93 | 94 | _Configuration._WindowSize = (uint32_t) ::_wtoi(Text); 95 | } 96 | 97 | { 98 | _Configuration._WindowSizeUnit = (WindowSizeUnit) ((CComboBox) GetDlgItem(IDC_WINDOW_SIZE_UNIT)).GetCurSel(); 99 | } 100 | 101 | { 102 | GetDlgItemTextW(IDC_REACTION_ALIGNMENT, Text, _countof(Text)); 103 | 104 | _Configuration._ReactionAlignment = ::_wtof(Text); 105 | } 106 | 107 | _Configuration._ClearOnStartup = (SendDlgItemMessageW(IDC_CLEAR_BROWSING_DATA, BM_GETCHECK) == BST_CHECKED) ? ClearOnStartup::All : ClearOnStartup::None; 108 | 109 | _Configuration._InPrivateMode = (SendDlgItemMessageW(IDC_IN_PRIVATE_MODE, BM_GETCHECK) == BST_CHECKED); 110 | _Configuration._ScrollbarStyle = (SendDlgItemMessageW(IDC_SCROLLBAR_STYLE, BM_GETCHECK) == BST_CHECKED) ? ScrollbarStyle::Fluent : ScrollbarStyle::Default; 111 | 112 | UIElement * CurrentElement = _UIElementTracker.GetCurrentElement(); 113 | 114 | if (CurrentElement != nullptr) 115 | CurrentElement->SetConfiguration(_Configuration); 116 | 117 | OnChanged(); 118 | } 119 | 120 | /// 121 | /// Resets this page's content to the default values. Does not apply any changes - lets user preview the changes before hitting "apply". 122 | /// 123 | virtual void reset() final 124 | { 125 | _Configuration.Reset(); 126 | 127 | InitializeControls(); 128 | 129 | OnChanged(); 130 | } 131 | 132 | #pragma endregion 133 | 134 | // WTL message map 135 | BEGIN_MSG_MAP_EX(Preferences) 136 | MSG_WM_INITDIALOG(OnInitDialog) 137 | 138 | MSG_WM_CTLCOLORSTATIC(OnCtlColorStatic) 139 | 140 | COMMAND_HANDLER_EX(IDC_NAME, EN_CHANGE, OnEditChange) 141 | COMMAND_HANDLER_EX(IDC_USER_DATA_FOLDER_PATH, EN_CHANGE, OnEditChange) 142 | COMMAND_HANDLER_EX(IDC_FILE_PATH, EN_CHANGE, OnEditChange) 143 | 144 | COMMAND_CODE_HANDLER_EX(CBN_SELCHANGE, OnSelectionChanged) // This also handles LBN_SELCHANGE 145 | 146 | COMMAND_HANDLER_EX(IDC_USER_DATA_FOLDER_PATH_SELECT, BN_CLICKED, OnButtonClicked) 147 | COMMAND_HANDLER_EX(IDC_CLEAR_BROWSING_DATA, BN_CLICKED, OnButtonClicked) 148 | COMMAND_HANDLER_EX(IDC_IN_PRIVATE_MODE, BN_CLICKED, OnButtonClicked) 149 | COMMAND_HANDLER_EX(IDC_SCROLLBAR_STYLE, BN_CLICKED, OnButtonClicked) 150 | 151 | COMMAND_HANDLER_EX(IDC_FILE_PATH_SELECT, BN_CLICKED, OnButtonClicked) 152 | COMMAND_HANDLER_EX(IDC_FILE_PATH_EDIT, BN_CLICKED, OnButtonClicked) 153 | 154 | COMMAND_HANDLER_EX(IDC_WINDOW_SIZE, EN_CHANGE, OnEditChange) 155 | COMMAND_HANDLER_EX(IDC_REACTION_ALIGNMENT, EN_CHANGE, OnEditChange) 156 | END_MSG_MAP() 157 | 158 | private: 159 | /// 160 | /// Initializes the dialog. 161 | /// 162 | BOOL OnInitDialog(CWindow, LPARAM) noexcept 163 | { 164 | _DarkModeHooks.AddDialogWithControls(*this); 165 | 166 | InitializeControls(); 167 | 168 | return FALSE; 169 | } 170 | 171 | /// 172 | /// Initializes the controls. 173 | /// 174 | void InitializeControls() 175 | { 176 | SetDlgItemTextW(IDC_NAME, _Configuration._Name.c_str()); 177 | SetDlgItemTextW(IDC_USER_DATA_FOLDER_PATH, _Configuration._UserDataFolderPath.c_str()); 178 | SetDlgItemTextW(IDC_FILE_PATH, _Configuration._TemplateFilePath.c_str()); 179 | 180 | SetDlgItemTextW(IDC_WINDOW_SIZE, pfc::wideFromUTF8(pfc::format_int(_Configuration._WindowSize))); 181 | 182 | { 183 | auto w = (CComboBox) GetDlgItem(IDC_WINDOW_SIZE_UNIT); 184 | 185 | w.ResetContent(); 186 | 187 | const WCHAR * Labels[] = { L"ms", L"samples" }; 188 | 189 | assert(((size_t) WindowSizeUnit::Count == _countof(Labels))); 190 | 191 | for (auto Label : Labels) 192 | w.AddString(Label); 193 | 194 | w.SetCurSel((int) _Configuration._WindowSizeUnit); 195 | } 196 | 197 | SetDlgItemTextW(IDC_REACTION_ALIGNMENT, pfc::wideFromUTF8(pfc::format_float(_Configuration._ReactionAlignment, 0, 2))); 198 | 199 | SendDlgItemMessageW(IDC_CLEAR_BROWSING_DATA, BM_SETCHECK, (WPARAM) (_Configuration._ClearOnStartup == ClearOnStartup::All ? BST_CHECKED : BST_UNCHECKED)); 200 | SendDlgItemMessageW(IDC_IN_PRIVATE_MODE, BM_SETCHECK, (WPARAM) (_Configuration._InPrivateMode ? BST_CHECKED : BST_UNCHECKED)); 201 | SendDlgItemMessageW(IDC_SCROLLBAR_STYLE, BM_SETCHECK, (WPARAM) ((_Configuration._ScrollbarStyle == ScrollbarStyle::Fluent) ? BST_CHECKED : BST_UNCHECKED)); 202 | } 203 | 204 | /// 205 | /// Handles an update of the selected item of a combo box. 206 | /// 207 | void OnSelectionChanged(UINT, int, CWindow) noexcept 208 | { 209 | OnChanged(); 210 | } 211 | 212 | /// 213 | /// Handles a WM_CTLCOLORSTATIC message. 214 | /// 215 | HBRUSH OnCtlColorStatic(CDCHandle dc, CStatic label) 216 | { 217 | if (label.GetDlgCtrlID() != IDC_WARNING) 218 | return NULL; 219 | 220 | ::SetTextColor(dc, ::GetSysColor(COLOR_INFOTEXT)); 221 | ::SetBkColor(dc, ::GetSysColor(COLOR_INFOBK)); 222 | 223 | return ::GetSysColorBrush(COLOR_INFOBK); 224 | } 225 | 226 | /// 227 | /// Handles a textbox change. 228 | /// 229 | void OnEditChange(UINT, int, CWindow) noexcept 230 | { 231 | OnChanged(); 232 | } 233 | 234 | /// 235 | /// Handles a click on a button. 236 | /// 237 | void OnButtonClicked(UINT, int id, CWindow) noexcept 238 | { 239 | switch (id) 240 | { 241 | case IDC_USER_DATA_FOLDER_PATH_SELECT: 242 | { 243 | char ExpandedDirectoryPath[MAX_PATH]; 244 | 245 | if (::ExpandEnvironmentStringsA(::WideToUTF8(_Configuration._UserDataFolderPath).c_str(), ExpandedDirectoryPath, _countof(ExpandedDirectoryPath)) == 0) 246 | ::strcpy_s(ExpandedDirectoryPath, _countof(ExpandedDirectoryPath), ::WideToUTF8(_Configuration._UserDataFolderPath).c_str()); 247 | 248 | pfc::string8 DirectoryPath = ExpandedDirectoryPath; 249 | 250 | DirectoryPath.truncate_filename(); 251 | 252 | if (::uBrowseForFolder(m_hWnd, "Locate the WebView user data folder...", DirectoryPath)) 253 | { 254 | SetDlgItemTextW(IDC_USER_DATA_FOLDER_PATH, ::UTF8ToWide(DirectoryPath.c_str()).c_str()); 255 | 256 | OnChanged(); 257 | } 258 | break; 259 | } 260 | 261 | case IDC_FILE_PATH_SELECT: 262 | { 263 | char ExpandedFilePath[MAX_PATH]; 264 | 265 | if (::ExpandEnvironmentStringsA(::WideToUTF8(_Configuration._TemplateFilePath).c_str(), ExpandedFilePath, _countof(ExpandedFilePath)) == 0) 266 | ::strcpy_s(ExpandedFilePath, _countof(ExpandedFilePath), ::WideToUTF8(_Configuration._TemplateFilePath).c_str()); 267 | 268 | pfc::string8 DirectoryPath = ExpandedFilePath; 269 | 270 | DirectoryPath.truncate_filename(); 271 | 272 | pfc::string8 FilePath = ExpandedFilePath; 273 | 274 | if (::uGetOpenFileName(m_hWnd, "Template files|*.htm;*.html;*.txt", 0, "html", "Choose a template...", DirectoryPath, FilePath, FALSE)) 275 | { 276 | SetDlgItemTextW(IDC_FILE_PATH, ::UTF8ToWide(FilePath.c_str()).c_str()); 277 | 278 | OnChanged(); 279 | } 280 | break; 281 | } 282 | 283 | case IDC_FILE_PATH_EDIT: 284 | { 285 | char ExpandedFilePath[MAX_PATH]; 286 | 287 | if (::ExpandEnvironmentStringsA(::WideToUTF8(_Configuration._TemplateFilePath).c_str(), ExpandedFilePath, _countof(ExpandedFilePath)) == 0) 288 | ::strcpy_s(ExpandedFilePath, _countof(ExpandedFilePath), ::WideToUTF8(_Configuration._TemplateFilePath).c_str()); 289 | 290 | pfc::string8 DirectoryPath = ExpandedFilePath; 291 | 292 | DirectoryPath.truncate_filename(); 293 | 294 | INT_PTR Result = (INT_PTR) ::ShellExecuteA(m_hWnd, "edit", ExpandedFilePath, nullptr, DirectoryPath.c_str(), SW_NORMAL); 295 | 296 | if (Result <= 32) 297 | MessageBoxW(::UTF8ToWide(::GetErrorMessage(::GetLastError(), "Failed to launch editor")).c_str(), TEXT(STR_COMPONENT_NAME), MB_OK); 298 | break; 299 | } 300 | 301 | default: 302 | { 303 | OnChanged(); 304 | break; 305 | } 306 | } 307 | } 308 | 309 | /// 310 | /// Tells the host that our state has changed to enable/disable the apply button appropriately. 311 | /// 312 | void OnChanged() noexcept 313 | { 314 | WCHAR Text[16]; 315 | 316 | GetDlgItemTextW(IDC_WINDOW_SIZE, Text, _countof(Text)); 317 | uint32_t WindowSize = (uint32_t) ::_wtoi(Text); 318 | 319 | GetDlgItemTextW(IDC_REACTION_ALIGNMENT, Text, _countof(Text)); 320 | double ReactionAlignment = ::_wtof(Text); 321 | 322 | int32_t WindowOffset = (int32_t) (WindowSize * (0.5 + ReactionAlignment)); 323 | 324 | auto w = (CComboBox) GetDlgItem(IDC_WINDOW_SIZE_UNIT); 325 | 326 | const WCHAR * Format = (w.GetCurSel() == (int) WindowSizeUnit::Milliseconds) ? L"%dms %s playback" : L"%d samples %s playback"; 327 | 328 | SetDlgItemTextW(IDC_WINDOW_OFFSET, ::FormatText(Format, ::abs(WindowOffset), (WindowOffset > 0) ? L"behind" : L"ahead of").c_str()); 329 | 330 | _Callback->on_state_changed(); 331 | } 332 | 333 | /// 334 | /// Returns whether our dialog content is different from the current configuration (whether the apply button should be enabled or not) 335 | /// 336 | bool HasChanged() noexcept 337 | { 338 | GetDlgItem(IDC_WARNING).ShowWindow((_ActiveConfiguration._InPrivateMode != (SendDlgItemMessageW(IDC_IN_PRIVATE_MODE, BM_GETCHECK) == BST_CHECKED)) ? SW_SHOW : SW_HIDE); 339 | 340 | wchar_t Text[MAX_PATH]; 341 | 342 | GetDlgItemTextW(IDC_NAME, Text, _countof(Text)); 343 | 344 | if (_Configuration._Name != Text) 345 | return true; 346 | 347 | GetDlgItemTextW(IDC_USER_DATA_FOLDER_PATH, Text, _countof(Text)); 348 | 349 | if (_Configuration._UserDataFolderPath != Text) 350 | return true; 351 | 352 | GetDlgItemTextW(IDC_FILE_PATH, Text, _countof(Text)); 353 | 354 | if (_Configuration._TemplateFilePath != Text) 355 | return true; 356 | 357 | GetDlgItemTextW(IDC_WINDOW_SIZE, Text, _countof(Text)); 358 | 359 | if (_Configuration._WindowSize != (uint32_t) ::_wtoi(Text)) 360 | return true; 361 | 362 | auto w = (CComboBox) GetDlgItem(IDC_WINDOW_SIZE_UNIT); 363 | 364 | if (_Configuration._WindowSizeUnit != (uint32_t) w.GetCurSel()) 365 | return true; 366 | 367 | GetDlgItemTextW(IDC_REACTION_ALIGNMENT, Text, _countof(Text)); 368 | 369 | if (_Configuration._ReactionAlignment != ::_wtof(Text)) 370 | return true; 371 | 372 | if (SendDlgItemMessageW(IDC_CLEAR_BROWSING_DATA, BM_GETCHECK) != (_Configuration._ClearOnStartup == ClearOnStartup::All ? BST_CHECKED : BST_UNCHECKED)) 373 | return true; 374 | 375 | if (SendDlgItemMessageW(IDC_IN_PRIVATE_MODE, BM_GETCHECK) != (_Configuration._InPrivateMode ? BST_CHECKED : BST_UNCHECKED)) 376 | return true; 377 | 378 | if (SendDlgItemMessageW(IDC_SCROLLBAR_STYLE, BM_GETCHECK) != ((_Configuration._ScrollbarStyle == ScrollbarStyle::Fluent) ? BST_CHECKED : BST_UNCHECKED)) 379 | return true; 380 | 381 | return false; 382 | } 383 | 384 | private: 385 | const preferences_page_callback::ptr _Callback; 386 | 387 | fb2k::CDarkModeHooks _DarkModeHooks; 388 | 389 | UIElement * _CurrentElement; 390 | configuration_t _Configuration, _ActiveConfiguration; 391 | }; 392 | 393 | #pragma region PreferencesPage 394 | 395 | /// 396 | /// preferences_page_impl<> helper deals with instantiation of our dialog; inherits from preferences_page_v3. 397 | /// 398 | class PreferencesPage : public preferences_page_impl 399 | { 400 | public: 401 | virtual ~PreferencesPage() { } 402 | 403 | const char * get_name() 404 | { 405 | return STR_COMPONENT_NAME; 406 | } 407 | 408 | GUID get_guid() 409 | { 410 | static constexpr GUID _GUID = GUID_PREFERENCES; 411 | 412 | return _GUID; 413 | } 414 | 415 | GUID get_parent_guid() 416 | { 417 | return guid_display; 418 | } 419 | }; 420 | 421 | static preferences_page_factory_t _Factory; 422 | 423 | #pragma endregion 424 | -------------------------------------------------------------------------------- /UIElement.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** $VER: UIElement.cpp (2024.12.02) P. Stuer **/ 3 | 4 | #include "pch.h" 5 | 6 | #include "UIElement.h" 7 | #include "UIElementTracker.h" 8 | #include "Encoding.h" 9 | #include "Exceptions.h" 10 | #include "Support.h" 11 | 12 | #include 13 | 14 | #pragma comment(lib, "pathcch") 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #pragma hdrstop 22 | 23 | /// 24 | /// Initializes a new instance. 25 | /// 26 | UIElement::UIElement() : m_bMsgHandled(FALSE) 27 | { 28 | _PlaybackControl = playback_control::get(); 29 | 30 | playlist_manager::get()->register_callback(this, (t_uint32) flag_all); 31 | } 32 | 33 | /// 34 | /// Deletes this instance. 35 | /// 36 | UIElement::~UIElement() 37 | { 38 | playlist_manager::get()->unregister_callback(this); 39 | } 40 | 41 | #pragma region User Interface 42 | 43 | /// 44 | /// Creates the window. 45 | /// 46 | LRESULT UIElement::OnCreate(LPCREATESTRUCT cs) noexcept 47 | { 48 | _UIElementTracker.Add(this); 49 | 50 | std::wstring WebViewVersion; 51 | 52 | if (!GetWebViewVersion(WebViewVersion)) 53 | { 54 | console::print(STR_COMPONENT_BASENAME " failed to find a compatible WebView component."); 55 | 56 | return 1; 57 | } 58 | 59 | console::printf(STR_COMPONENT_BASENAME " is using WebView %s.", ::WideToUTF8(WebViewVersion).c_str()); 60 | 61 | _HostObject = Microsoft::WRL::Make 62 | ( 63 | [this](std::function callback) 64 | { 65 | RunAsync(callback); 66 | } 67 | ); 68 | 69 | Initialize(); 70 | 71 | HRESULT hr = CreateWebView(); 72 | 73 | if (!SUCCEEDED(hr)) 74 | console::print(STR_COMPONENT_BASENAME " failed to create WebView control."); 75 | 76 | InitializeFileWatcher(); 77 | 78 | // Create the visualisation stream. 79 | try 80 | { 81 | static_api_ptr_t VisualisationManager; 82 | 83 | VisualisationManager->create_stream(_VisualisationStream, visualisation_manager::KStreamFlagNewFFT); 84 | 85 | _VisualisationStream->set_channel_mode(visualisation_stream_v2::channel_mode_default); 86 | } 87 | catch (std::exception &) 88 | { 89 | console::print(STR_COMPONENT_BASENAME " failed to create visualisation stream."); 90 | } 91 | 92 | return 0; 93 | } 94 | 95 | /// 96 | /// Destroys the window. 97 | /// 98 | void UIElement::OnDestroy() noexcept 99 | { 100 | StopTimer(); 101 | 102 | _VisualisationStream.release(); 103 | 104 | _FileWatcher.Stop(); 105 | 106 | DeleteWebView(); 107 | 108 | _HostObject = nullptr; 109 | 110 | _UIElementTracker.Remove(this); 111 | } 112 | 113 | /// 114 | /// Handles the WM_SIZE message. 115 | /// 116 | void UIElement::OnSize(UINT type, CSize size) noexcept 117 | { 118 | if (_Controller == nullptr) 119 | return; 120 | 121 | RECT Bounds; 122 | 123 | GetClientRect(&Bounds); 124 | 125 | _Controller->put_Bounds(Bounds); 126 | } 127 | 128 | /// 129 | /// Handles the WM_ERASEBKGND message. 130 | /// 131 | BOOL UIElement::OnEraseBackground(CDCHandle dc) noexcept 132 | { 133 | RECT cr; 134 | 135 | GetClientRect(&cr); 136 | 137 | HBRUSH Brush = ::CreateSolidBrush(_BackgroundColor); 138 | 139 | ::FillRect(dc, &cr, Brush); 140 | 141 | ::DeleteObject(Brush); 142 | 143 | return TRUE; 144 | } 145 | 146 | /// 147 | /// Handles the WM_PAINT message. 148 | /// 149 | void UIElement::OnPaint(CDCHandle dc) noexcept 150 | { 151 | PAINTSTRUCT ps = { }; 152 | 153 | BeginPaint(&ps); 154 | 155 | RECT cr; 156 | 157 | GetClientRect(&cr); 158 | 159 | HTHEME hTheme = ::OpenThemeData(m_hWnd, VSCLASS_TEXTSTYLE); 160 | 161 | const DWORD Format = DT_SINGLELINE | DT_CENTER | DT_VCENTER; 162 | 163 | if (hTheme != NULL) 164 | { 165 | DTTOPTS Options = { sizeof(Options) }; 166 | 167 | Options.dwFlags = DTT_TEXTCOLOR; 168 | Options.crText = _ForegroundColor; 169 | 170 | ::DrawThemeTextEx(hTheme, ps.hdc, TEXT_BODYTEXT, 0, _Configuration._Name.c_str(), -1, Format, &cr, &Options); 171 | 172 | ::CloseThemeData(hTheme); 173 | } 174 | else 175 | ::DrawTextW(ps.hdc, _Configuration._Name.c_str(), -1, &cr, (UINT) Format); 176 | 177 | EndPaint(&ps); 178 | } 179 | 180 | /// 181 | /// Handles a change to the template. Either the path name or the content changed. 182 | /// 183 | LRESULT UIElement::OnTemplateChanged(UINT msg, WPARAM wParam, LPARAM lParam) noexcept 184 | { 185 | try 186 | { 187 | InitializeWebView(); 188 | } 189 | catch (std::exception & e) 190 | { 191 | console::error(e.what()); 192 | } 193 | 194 | return 0; 195 | } 196 | 197 | /// 198 | /// The WebView is ready. 199 | /// 200 | LRESULT UIElement::OnWebViewReady(UINT msg, WPARAM wParam, LPARAM lParam) noexcept 201 | { 202 | try 203 | { 204 | SetWebViewVisibility(IsWebViewVisible()); // Work-around for WebView not appearing after foobar2000 starts while being hosted in a hidden tab. 205 | 206 | InitializeWebView(); 207 | } 208 | catch (std::exception & e) 209 | { 210 | console::error(e.what()); 211 | } 212 | 213 | return 0; 214 | } 215 | 216 | /// 217 | /// Handles a notification from the Preferences page that the template file path has changed. 218 | /// 219 | void UIElement::OnConfigurationChanged() noexcept 220 | { 221 | _ExpandedTemplateFilePath = GetTemplateFilePath(); 222 | 223 | try 224 | { 225 | InitializeFileWatcher(); 226 | InitializeWebView(); 227 | } 228 | catch (std::exception & e) 229 | { 230 | console::error(e.what()); 231 | } 232 | } 233 | 234 | /// 235 | /// Handles an async method call. 236 | /// 237 | LRESULT UIElement::OnAsync(UINT msg, WPARAM wParam, LPARAM lParam) noexcept 238 | { 239 | auto * task = reinterpret_cast*>(wParam); 240 | 241 | (*task)(); 242 | 243 | delete task; 244 | 245 | return true; 246 | } 247 | 248 | /// 249 | /// Handles a change of the user interface colors. 250 | /// 251 | void UIElement::OnColorsChanged() 252 | { 253 | GetColors(); 254 | 255 | RecreateWebView(); // There is no way (yet) to update the environment options of an existing WebView2 control so we delete and recreate it. 256 | 257 | if (IsWindow()) 258 | Invalidate(TRUE); 259 | } 260 | 261 | /// 262 | /// Initializes the component. 263 | /// 264 | void UIElement::Initialize() 265 | { 266 | { 267 | if (!::PathFileExistsW(_Configuration._UserDataFolderPath.c_str())) 268 | { 269 | // Create the user data directory. 270 | if (!::CreateDirectoryW(_Configuration._UserDataFolderPath.c_str(), nullptr)) 271 | console::print(::GetErrorMessage(::GetLastError(), ::FormatText(STR_COMPONENT_BASENAME " failed to create user data folder \"%s\"", ::WideToUTF8(_Configuration._UserDataFolderPath).c_str())).c_str()); 272 | } 273 | } 274 | 275 | { 276 | _ExpandedTemplateFilePath = GetTemplateFilePath(); 277 | 278 | if (::PathFileExistsW(_ExpandedTemplateFilePath.c_str())) 279 | return; 280 | } 281 | 282 | // Create a default template file. 283 | { 284 | wchar_t DefaultFilePath[MAX_PATH]; 285 | 286 | HMODULE hModule = GetCurrentModule(); 287 | 288 | if (hModule == NULL) 289 | return; 290 | 291 | if (::GetModuleFileNameW(hModule, DefaultFilePath, _countof(DefaultFilePath)) == 0) 292 | return; 293 | 294 | HRESULT hr = ::PathCchRemoveFileSpec(DefaultFilePath, _countof(DefaultFilePath)); 295 | 296 | if (!SUCCEEDED(hr)) 297 | return; 298 | 299 | hr = ::PathCchAppend(DefaultFilePath, _countof(DefaultFilePath), L"Default-Template.html"); 300 | 301 | if (!SUCCEEDED(hr)) 302 | return; 303 | 304 | if (!::CopyFileW(DefaultFilePath, _ExpandedTemplateFilePath.c_str(), TRUE)) 305 | console::print(::GetErrorMessage(::GetLastError(), ::FormatText(STR_COMPONENT_BASENAME " failed to create default template file \"%s\"", ::WideToUTF8(_ExpandedTemplateFilePath).c_str())).c_str()); 306 | } 307 | } 308 | 309 | /// 310 | /// Initializes the file watcher. 311 | /// 312 | void UIElement::InitializeFileWatcher() 313 | { 314 | _FileWatcher.Stop(); 315 | 316 | try 317 | { 318 | _FileWatcher.Start(m_hWnd, _ExpandedTemplateFilePath); 319 | } 320 | catch (std::exception & e) 321 | { 322 | console::print(::FormatText(STR_COMPONENT_BASENAME " failed to start file system watcher: %s", e.what()).c_str()); 323 | } 324 | } 325 | 326 | /// 327 | /// Initializes the WebView. 328 | /// 329 | void UIElement::InitializeWebView() 330 | { 331 | if (_WebView == nullptr) 332 | return; 333 | 334 | // Navigate to the template. 335 | _IsNavigationCompleted = false; 336 | 337 | HRESULT hr = _WebView->Navigate(_ExpandedTemplateFilePath.c_str()); 338 | 339 | if (!SUCCEEDED(hr)) 340 | { 341 | console::print(::GetErrorMessage(hr, ::FormatText(STR_COMPONENT_BASENAME " failed to navigate to template \"%s\"", ::WideToUTF8(_ExpandedTemplateFilePath).c_str())).c_str()); 342 | 343 | hr = _WebView->Navigate(L"about:blank"); 344 | 345 | if (!SUCCEEDED(hr)) 346 | console::print(::GetErrorMessage(hr, STR_COMPONENT_BASENAME " failed to navigate to about:blank").c_str()); 347 | } 348 | 349 | on_playback_new_track(nullptr); 350 | } 351 | 352 | /// 353 | /// Shows or hides the WebView. 354 | /// 355 | void UIElement::SetWebViewVisibility(bool visible) noexcept 356 | { 357 | // Hack: Don't use put_IsVisible(). It hides the host window. Reduce the WebView's size if we need to show the client area 'behind' the WebView f.e. in Layout Edit mode. 358 | RECT cr = { }; 359 | 360 | if (visible) 361 | GetClientRect(&cr); 362 | 363 | if (_Controller != nullptr) 364 | _Controller->put_Bounds(cr); 365 | 366 | if (visible) 367 | on_playback_new_track(nullptr); // Forces a refresh when the WebView becomes visible again e.g. after exiting Layout Edit mode. 368 | else 369 | InvalidateRect(nullptr, TRUE); 370 | } 371 | 372 | /// 373 | /// Gets the template file path with all environment variables expanded. 374 | /// 375 | std::wstring UIElement::GetTemplateFilePath() const noexcept 376 | { 377 | wchar_t FilePath[MAX_PATH]; 378 | 379 | if (::ExpandEnvironmentStringsW(_Configuration._TemplateFilePath.c_str(), FilePath, _countof(FilePath)) == 0) 380 | ::wcscpy_s(FilePath, _countof(FilePath), _Configuration._TemplateFilePath.c_str()); 381 | 382 | return std::wstring(FilePath); 383 | } 384 | 385 | /// 386 | /// Shows the preferences page. 387 | /// 388 | void UIElement::ShowPreferences() noexcept 389 | { 390 | _UIElementTracker.SetCurrentElement(this); 391 | 392 | static constexpr GUID _GUID = GUID_PREFERENCES; 393 | 394 | static_api_ptr_t uc; 395 | 396 | uc->show_preferences(_GUID); 397 | } 398 | 399 | /// 400 | /// Gets the window class definition. 401 | /// 402 | CWndClassInfo & UIElement::GetWndClassInfo() 403 | { 404 | static ATL::CWndClassInfoW wci = 405 | { 406 | { 407 | sizeof(WNDCLASSEX), 408 | CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, 409 | StartWindowProc, 410 | 0, 0, 411 | NULL, // Instance, 412 | NULL, // Icon 413 | NULL, // Cursor 414 | NULL, // Background brush 415 | NULL, // Menu 416 | TEXT(STR_WINDOW_CLASS_NAME), // Class name 417 | NULL // Small Icon 418 | }, 419 | NULL, NULL, IDC_ARROW, TRUE, 0, L"" 420 | }; 421 | 422 | return wci; 423 | } 424 | 425 | #pragma endregion 426 | 427 | #pragma region play_callback_impl_base 428 | 429 | /// 430 | /// Called when playback is being initialized. 431 | /// 432 | void UIElement::on_playback_starting(play_control::t_track_command command, bool paused) 433 | { 434 | static const wchar_t * CommandName = L"Unknown"; 435 | 436 | if (command == play_control::t_track_command::track_command_play) CommandName = L"Play"; else 437 | if (command == play_control::t_track_command::track_command_next) CommandName = L"Next"; else // Plays the next track from the current playlist according to the current playback order. 438 | if (command == play_control::t_track_command::track_command_prev) CommandName = L"Prev"; else // Plays the previous track from the current playlist according to the current playback order. 439 | if (command == play_control::t_track_command::track_command_rand) CommandName = L"Random"; else // Plays a random track from the current playlist. 440 | 441 | if (command == play_control::t_track_command::track_command_settrack) CommandName = L"Set track"; else // For internal use only, do not use. 442 | if (command == play_control::t_track_command::track_command_resume) CommandName = L"Resume"; // For internal use only, do not use. 443 | 444 | const std::wstring Script = ::FormatText(L"onPlaybackStarting(\"%s\", %s)", CommandName, (paused ? L"true" : L"false")); 445 | 446 | ExecuteScript(Script); 447 | } 448 | 449 | /// 450 | /// Called when playback advances to a new track. 451 | /// 452 | void UIElement::on_playback_new_track(metadb_handle_ptr /*track*/) 453 | { 454 | const std::wstring Script = L"onPlaybackNewTrack()"; 455 | 456 | ExecuteScript(Script); 457 | 458 | _LastPlaybackTime = 0.; 459 | _SampleRate = 44100; // Temporary until we get the sample rate from the chunk. 460 | 461 | StartTimer(); 462 | } 463 | 464 | /// 465 | /// Called when playback stops. 466 | /// 467 | void UIElement::on_playback_stop(play_control::t_stop_reason reason) 468 | { 469 | StopTimer(); 470 | 471 | _LastPlaybackTime = 0.; 472 | 473 | static const wchar_t * Reason = L"unknown"; 474 | 475 | if (reason == play_control::t_stop_reason::stop_reason_user) Reason = L"User"; else 476 | if (reason == play_control::t_stop_reason::stop_reason_eof) Reason = L"EOF"; else 477 | if (reason == play_control::t_stop_reason::stop_reason_starting_another) Reason = L"Starting another"; else 478 | if (reason == play_control::t_stop_reason::stop_reason_shutting_down) Reason = L"Shutting down"; 479 | 480 | const std::wstring Script = ::FormatText(L"onPlaybackStop(\"%s\")", Reason); 481 | 482 | ExecuteScript(Script); 483 | } 484 | 485 | /// 486 | /// Called when the user seeks to a specific time. 487 | /// 488 | void UIElement::on_playback_seek(double time) 489 | { 490 | const std::wstring Script = ::FormatText(L"onPlaybackSeek(%f)", time); 491 | 492 | ExecuteScript(Script); 493 | } 494 | 495 | /// 496 | /// Called when playback pauses or resumes. 497 | /// 498 | void UIElement::on_playback_pause(bool paused) 499 | { 500 | const std::wstring Script = ::FormatText(L"onPlaybackPause(%s)", (paused ? L"true" : L"false")); 501 | 502 | ExecuteScript(Script); 503 | } 504 | 505 | /// 506 | /// Called when the currently played file gets edited. 507 | /// 508 | void UIElement::on_playback_edited(metadb_handle_ptr hTrack) 509 | { 510 | const std::wstring Script = L"onPlaybackEdited()"; 511 | 512 | ExecuteScript(Script); 513 | } 514 | 515 | /// 516 | /// Called when dynamic info (VBR bitrate etc...) changes. 517 | /// 518 | void UIElement::on_playback_dynamic_info(const file_info & fileInfo) 519 | { 520 | const std::wstring Script = L"onPlaybackDynamicInfo()"; 521 | 522 | ExecuteScript(Script); 523 | } 524 | 525 | /// 526 | /// Called when the per-track dynamic info (stream track titles etc...) change. Happens less often than on_playback_dynamic_info(). 527 | /// 528 | void UIElement::on_playback_dynamic_info_track(const file_info & fileInfo) 529 | { 530 | const std::wstring Script = L"onPlaybackDynamicTrackInfo()"; 531 | 532 | ExecuteScript(Script); 533 | } 534 | 535 | /// 536 | /// Called, every second, for time display. 537 | /// 538 | void UIElement::on_playback_time(double time) 539 | { 540 | const std::wstring Script = ::FormatText(L"onPlaybackTime(%f)", time); 541 | 542 | ExecuteScript(Script); 543 | } 544 | 545 | /// 546 | /// Called when the user changes the volume. 547 | /// 548 | void UIElement::on_volume_change(float newValue) // in dBFS 549 | { 550 | const std::wstring Script = ::FormatText(L"onVolumeChange(%f)", (double) newValue); 551 | 552 | ExecuteScript(Script); 553 | } 554 | 555 | #pragma endregion 556 | -------------------------------------------------------------------------------- /Template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 51 | 52 | 53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | 62 | 63 | 64 | 65 |

66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | 86 |
87 |

88 | Events
89 | onStarting: ?
90 | onPaused: ?
91 | onStop: ?
92 | onSeek: ?s
93 | onVolumeChange: ?dBFS
94 |

95 |

96 | Properties
97 | foo_uie_webview ()
98 | volume: ?dBFS
99 | isPlaying: ?
100 | isPaused: ?
101 | stopAfterCurrent: ?
102 | length: ?
103 | position: ?
104 | canSeek: ?
105 |

106 |

107 |

108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
120 | 121 |
122 |

123 |
Timestamp: , samples, Hz, channels ()
124 |
125 |
126 |
127 | 389 | 390 | 391 | --------------------------------------------------------------------------------