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