DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

DistrhoUI.cpp (14273B)


      1 /*
      2  * DISTRHO Plugin Framework (DPF)
      3  * Copyright (C) 2012-2023 Filipe Coelho <falktx@falktx.com>
      4  *
      5  * Permission to use, copy, modify, and/or distribute this software for any purpose with
      6  * or without fee is hereby granted, provided that the above copyright notice and this
      7  * permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
     10  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
     11  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
     12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
     13  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
     14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include "DistrhoDetails.hpp"
     18 #include "src/DistrhoPluginChecks.h"
     19 #include "src/DistrhoDefines.h"
     20 
     21 #include <cstddef>
     22 
     23 #ifdef DISTRHO_PROPER_CPP11_SUPPORT
     24 # include <cstdint>
     25 #else
     26 # include <stdint.h>
     27 #endif
     28 
     29 #if DISTRHO_UI_FILE_BROWSER && !defined(DISTRHO_OS_MAC)
     30 # define DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, SEP, FUNCTION) NS ## SEP ## FUNCTION
     31 # define DISTRHO_PUGL_NAMESPACE_MACRO(NS, FUNCTION) DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, _, FUNCTION)
     32 # define x_fib_add_recent          DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_add_recent)
     33 # define x_fib_cfg_buttons         DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_cfg_buttons)
     34 # define x_fib_cfg_filter_callback DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_cfg_filter_callback)
     35 # define x_fib_close               DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_close)
     36 # define x_fib_configure           DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_configure)
     37 # define x_fib_filename            DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_filename)
     38 # define x_fib_free_recent         DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_free_recent)
     39 # define x_fib_handle_events       DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_handle_events)
     40 # define x_fib_load_recent         DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_load_recent)
     41 # define x_fib_recent_at           DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_at)
     42 # define x_fib_recent_count        DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_count)
     43 # define x_fib_recent_file         DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_file)
     44 # define x_fib_save_recent         DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_save_recent)
     45 # define x_fib_show                DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_show)
     46 # define x_fib_status              DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_status)
     47 # define DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED
     48 # define FILE_BROWSER_DIALOG_NAMESPACE DISTRHO_NAMESPACE
     49 # define FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE
     50 START_NAMESPACE_DISTRHO
     51 # include "../extra/FileBrowserDialogImpl.hpp"
     52 END_NAMESPACE_DISTRHO
     53 # include "../extra/FileBrowserDialogImpl.cpp"
     54 #endif
     55 
     56 #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
     57 # if defined(DISTRHO_OS_WINDOWS)
     58 #  include <winsock2.h>
     59 #  include <windows.h>
     60 # elif defined(HAVE_X11)
     61 #  include <X11/Xresource.h>
     62 # endif
     63 #else
     64 # include "src/TopLevelWidgetPrivateData.hpp"
     65 # include "src/WindowPrivateData.hpp"
     66 #endif
     67 
     68 #include "DistrhoUIPrivateData.hpp"
     69 
     70 START_NAMESPACE_DISTRHO
     71 
     72 /* ------------------------------------------------------------------------------------------------------------
     73  * Static data, see DistrhoUIInternal.hpp */
     74 
     75 const char* g_nextBundlePath  = nullptr;
     76 #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
     77 uintptr_t   g_nextWindowId    = 0;
     78 double      g_nextScaleFactor = 1.0;
     79 #endif
     80 
     81 #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
     82 /* ------------------------------------------------------------------------------------------------------------
     83  * get global scale factor */
     84 
     85 #ifdef DISTRHO_OS_MAC
     86 double getDesktopScaleFactor(uintptr_t parentWindowHandle);
     87 #else
     88 static double getDesktopScaleFactor(const uintptr_t parentWindowHandle)
     89 {
     90     // allow custom scale for testing
     91     if (const char* const scale = getenv("DPF_SCALE_FACTOR"))
     92         return std::max(1.0, std::atof(scale));
     93 
     94 #if defined(DISTRHO_OS_WINDOWS)
     95     if (const HMODULE Shcore = LoadLibraryA("Shcore.dll"))
     96     {
     97         typedef HRESULT(WINAPI* PFN_GetProcessDpiAwareness)(HANDLE, DWORD*);
     98         typedef HRESULT(WINAPI* PFN_GetScaleFactorForMonitor)(HMONITOR, DWORD*);
     99 
    100 # if defined(__GNUC__) && (__GNUC__ >= 9)
    101 #  pragma GCC diagnostic push
    102 #  pragma GCC diagnostic ignored "-Wcast-function-type"
    103 # endif
    104         const PFN_GetProcessDpiAwareness GetProcessDpiAwareness
    105             = (PFN_GetProcessDpiAwareness)GetProcAddress(Shcore, "GetProcessDpiAwareness");
    106         const PFN_GetScaleFactorForMonitor GetScaleFactorForMonitor
    107             = (PFN_GetScaleFactorForMonitor)GetProcAddress(Shcore, "GetScaleFactorForMonitor");
    108 # if defined(__GNUC__) && (__GNUC__ >= 9)
    109 #  pragma GCC diagnostic pop
    110 # endif
    111 
    112         DWORD dpiAware = 0;
    113         DWORD scaleFactor = 100;
    114         if (GetProcessDpiAwareness && GetScaleFactorForMonitor
    115             && GetProcessDpiAwareness(nullptr, &dpiAware) == 0 && dpiAware != 0)
    116         {
    117             const HMONITOR hMon = parentWindowHandle != 0
    118                                 ? MonitorFromWindow((HWND)parentWindowHandle, MONITOR_DEFAULTTOPRIMARY)
    119                                 : MonitorFromPoint(POINT{0,0}, MONITOR_DEFAULTTOPRIMARY);
    120             GetScaleFactorForMonitor(hMon, &scaleFactor);
    121         }
    122 
    123         FreeLibrary(Shcore);
    124         return static_cast<double>(scaleFactor) / 100.0;
    125     }
    126 #elif defined(HAVE_X11)
    127     ::Display* const display = XOpenDisplay(nullptr);
    128     DISTRHO_SAFE_ASSERT_RETURN(display != nullptr, 1.0);
    129 
    130     XrmInitialize();
    131 
    132     double dpi = 96.0;
    133     if (char* const rms = XResourceManagerString(display))
    134     {
    135         if (const XrmDatabase db = XrmGetStringDatabase(rms))
    136         {
    137             char* type = nullptr;
    138             XrmValue value = {};
    139 
    140             if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)
    141                 && type != nullptr
    142                 && std::strcmp(type, "String") == 0
    143                 && value.addr != nullptr)
    144             {
    145                 char*        end    = nullptr;
    146                 const double xftDpi = std::strtod(value.addr, &end);
    147                 if (xftDpi > 0.0 && xftDpi < HUGE_VAL)
    148                     dpi = xftDpi;
    149             }
    150 
    151             XrmDestroyDatabase(db);
    152         }
    153     }
    154 
    155     XCloseDisplay(display);
    156     return dpi / 96;
    157 #endif
    158 
    159     return 1.0;
    160 
    161     // might be unused
    162     (void)parentWindowHandle;
    163 }
    164 #endif // !DISTRHO_OS_MAC
    165 
    166 #endif
    167 
    168 /* ------------------------------------------------------------------------------------------------------------
    169  * UI::PrivateData special handling */
    170 
    171 UI::PrivateData* UI::PrivateData::s_nextPrivateData = nullptr;
    172 
    173 #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
    174 ExternalWindow::PrivateData
    175 #else
    176 PluginWindow&
    177 #endif
    178 UI::PrivateData::createNextWindow(UI* const ui, uint width, uint height, const bool adjustForScaleFactor)
    179 {
    180     UI::PrivateData* const pData = s_nextPrivateData;
    181    #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
    182     const double scaleFactor = d_isNotZero(pData->scaleFactor) ? pData->scaleFactor : getDesktopScaleFactor(pData->winId);
    183 
    184     if (adjustForScaleFactor && d_isNotZero(scaleFactor) && d_isNotEqual(scaleFactor, 1.0))
    185     {
    186         width *= scaleFactor;
    187         height *= scaleFactor;
    188     }
    189 
    190     pData->window = new PluginWindow(ui, pData->app);
    191     ExternalWindow::PrivateData ewData;
    192     ewData.parentWindowHandle = pData->winId;
    193     ewData.width = width;
    194     ewData.height = height;
    195     ewData.scaleFactor = scaleFactor;
    196     ewData.title = DISTRHO_PLUGIN_NAME;
    197     ewData.isStandalone = DISTRHO_UI_IS_STANDALONE;
    198     return ewData;
    199    #else
    200     const double scaleFactor = pData->scaleFactor;
    201 
    202     if (adjustForScaleFactor && d_isNotZero(scaleFactor) && d_isNotEqual(scaleFactor, 1.0))
    203     {
    204         width *= scaleFactor;
    205         height *= scaleFactor;
    206     }
    207 
    208     pData->window = new PluginWindow(ui, pData->app, pData->winId, width, height, scaleFactor);
    209 
    210     // If there are no callbacks, this is most likely a temporary window, so ignore idle callbacks
    211     if (pData->callbacksPtr == nullptr)
    212         pData->window->setIgnoreIdleCallbacks();
    213 
    214     return pData->window.getObject();
    215    #endif
    216 }
    217 
    218 /* ------------------------------------------------------------------------------------------------------------
    219  * UI */
    220 
    221 UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetAsMinimumSize)
    222     : UIWidget(UI::PrivateData::createNextWindow(this,
    223               #ifdef DISTRHO_UI_DEFAULT_WIDTH
    224                width == 0 ? DISTRHO_UI_DEFAULT_WIDTH :
    225               #endif
    226                width,
    227               #ifdef DISTRHO_UI_DEFAULT_HEIGHT
    228                height == 0 ? DISTRHO_UI_DEFAULT_HEIGHT :
    229               #endif
    230                height,
    231               #ifdef DISTRHO_UI_DEFAULT_WIDTH
    232                width == 0
    233               #else
    234                false
    235               #endif
    236                )),
    237       uiData(UI::PrivateData::s_nextPrivateData)
    238 {
    239 #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
    240     if (width != 0 && height != 0)
    241     {
    242         Widget::setSize(width, height);
    243 
    244         if (automaticallyScaleAndSetAsMinimumSize)
    245             setGeometryConstraints(width, height, true, true, true);
    246     }
    247    #ifdef DISTRHO_UI_DEFAULT_WIDTH
    248     else
    249     {
    250         Widget::setSize(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT);
    251     }
    252    #endif
    253 #else
    254     // unused
    255     (void)automaticallyScaleAndSetAsMinimumSize;
    256 #endif
    257 }
    258 
    259 UI::~UI()
    260 {
    261 }
    262 
    263 /* ------------------------------------------------------------------------------------------------------------
    264  * Host state */
    265 
    266 bool UI::isResizable() const noexcept
    267 {
    268 #if DISTRHO_UI_USER_RESIZABLE
    269 # if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
    270     return true;
    271 # else
    272     return uiData->window->isResizable();
    273 # endif
    274 #else
    275     return false;
    276 #endif
    277 }
    278 
    279 uint UI::getBackgroundColor() const noexcept
    280 {
    281     return uiData->bgColor;
    282 }
    283 
    284 uint UI::getForegroundColor() const noexcept
    285 {
    286     return uiData->fgColor;
    287 }
    288 
    289 double UI::getSampleRate() const noexcept
    290 {
    291     return uiData->sampleRate;
    292 }
    293 
    294 const char* UI::getBundlePath() const noexcept
    295 {
    296     return uiData->bundlePath;
    297 }
    298 
    299 void UI::editParameter(uint32_t index, bool started)
    300 {
    301     uiData->editParamCallback(index + uiData->parameterOffset, started);
    302 }
    303 
    304 void UI::setParameterValue(uint32_t index, float value)
    305 {
    306     uiData->setParamCallback(index + uiData->parameterOffset, value);
    307 }
    308 
    309 #if DISTRHO_PLUGIN_WANT_STATE
    310 void UI::setState(const char* key, const char* value)
    311 {
    312     uiData->setStateCallback(key, value);
    313 }
    314 #endif
    315 
    316 #if DISTRHO_PLUGIN_WANT_STATE
    317 bool UI::requestStateFile(const char* key)
    318 {
    319     return uiData->fileRequestCallback(key);
    320 }
    321 #endif
    322 
    323 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    324 void UI::sendNote(uint8_t channel, uint8_t note, uint8_t velocity)
    325 {
    326     uiData->sendNoteCallback(channel, note, velocity);
    327 }
    328 #endif
    329 
    330 #if DISTRHO_UI_FILE_BROWSER
    331 bool UI::openFileBrowser(const FileBrowserOptions& options)
    332 {
    333     return getWindow().openFileBrowser((DGL_NAMESPACE::FileBrowserOptions&)options);
    334 }
    335 #endif
    336 
    337 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
    338 /* ------------------------------------------------------------------------------------------------------------
    339  * Direct DSP access */
    340 
    341 void* UI::getPluginInstancePointer() const noexcept
    342 {
    343     return uiData->dspPtr;
    344 }
    345 #endif
    346 
    347 #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
    348 /* ------------------------------------------------------------------------------------------------------------
    349  * External UI helpers (static calls) */
    350 
    351 const char* UI::getNextBundlePath() noexcept
    352 {
    353     return g_nextBundlePath;
    354 }
    355 
    356 double UI::getNextScaleFactor() noexcept
    357 {
    358     return g_nextScaleFactor;
    359 }
    360 
    361 # if DISTRHO_PLUGIN_HAS_EMBED_UI
    362 uintptr_t UI::getNextWindowId() noexcept
    363 {
    364     return g_nextWindowId;
    365 }
    366 # endif
    367 #endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI
    368 
    369 /* ------------------------------------------------------------------------------------------------------------
    370  * DSP/Plugin Callbacks (optional) */
    371 
    372 void UI::sampleRateChanged(double)
    373 {
    374 }
    375 
    376 /* ------------------------------------------------------------------------------------------------------------
    377  * UI Callbacks (optional) */
    378 
    379 void UI::uiScaleFactorChanged(double)
    380 {
    381 }
    382 
    383 #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
    384 std::vector<DGL_NAMESPACE::ClipboardDataOffer> UI::getClipboardDataOfferTypes()
    385 {
    386     return uiData->window->getClipboardDataOfferTypes();
    387 }
    388 
    389 uint32_t UI::uiClipboardDataOffer()
    390 {
    391     std::vector<DGL_NAMESPACE::ClipboardDataOffer> offers(uiData->window->getClipboardDataOfferTypes());
    392 
    393     for (std::vector<DGL_NAMESPACE::ClipboardDataOffer>::iterator it=offers.begin(), end=offers.end(); it != end;++it)
    394     {
    395         const DGL_NAMESPACE::ClipboardDataOffer offer = *it;
    396         if (std::strcmp(offer.type, "text/plain") == 0)
    397             return offer.id;
    398     }
    399 
    400     return 0;
    401 }
    402 
    403 void UI::uiFocus(bool, DGL_NAMESPACE::CrossingMode)
    404 {
    405 }
    406 
    407 void UI::uiReshape(const uint width, const uint height)
    408 {
    409     // NOTE this must be the same as Window::onReshape
    410     pData->fallbackOnResize(width, height);
    411 }
    412 #endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
    413 
    414 #if DISTRHO_UI_FILE_BROWSER
    415 void UI::uiFileBrowserSelected(const char*)
    416 {
    417 }
    418 #endif
    419 
    420 /* ------------------------------------------------------------------------------------------------------------
    421  * UI Resize Handling, internal */
    422 
    423 #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
    424 void UI::sizeChanged(const uint width, const uint height)
    425 {
    426     UIWidget::sizeChanged(width, height);
    427 
    428     uiData->setSizeCallback(width, height);
    429 }
    430 #else
    431 void UI::onResize(const ResizeEvent& ev)
    432 {
    433     UIWidget::onResize(ev);
    434 
    435    #if ! DISTRHO_UI_USES_SIZE_REQUEST
    436     if (uiData->initializing)
    437         return;
    438 
    439     const uint width = ev.size.getWidth();
    440     const uint height = ev.size.getHeight();
    441     uiData->setSizeCallback(width, height);
    442    #endif
    443 }
    444 
    445 // NOTE: only used for CLAP and VST3
    446 void UI::requestSizeChange(const uint width, const uint height)
    447 {
    448    #if DISTRHO_UI_USES_SIZE_REQUEST
    449     if (uiData->initializing)
    450         uiData->window->setSizeFromHost(width, height);
    451     else
    452         uiData->setSizeCallback(width, height);
    453    #else
    454     // unused
    455     (void)width;
    456     (void)height;
    457    #endif
    458 }
    459 #endif
    460 
    461 // -----------------------------------------------------------------------------------------------------------
    462 
    463 END_NAMESPACE_DISTRHO