419 changes: 338 additions & 81 deletions chibiembedwidget.cpp
Expand Up @@ -2,127 +2,384 @@

#include <QResizeEvent>

#ifdef HAVE_X11
#if defined(Q_OS_MAC)
# import <Cocoa/Cocoa.h>
#elif defined(Q_OS_WINDOWS)
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else
# include <QX11Info>
# include <X11/Xlib.h>
# include <X11/Xutil.h>
# include <pthread.h>
#endif

#ifdef HAVE_X11
static pthread_mutex_t gErrorMutex = PTHREAD_MUTEX_INITIALIZER;
static bool gErrorTriggered = false;
static int gErrorHandler(Display*, XErrorEvent*)
{
gErrorTriggered = true;
return 0;
}
#endif

struct ChibiEmbedWidget::PrivateData
{
bool wasResized = false;
#ifdef HAVE_X11
::Window window = 0;
QWidget* const widget;
// void* const ourWindowPtr;
const uintptr_t ourWindowId;

#if defined(Q_OS_MAC)
NSView* const view;
NSView* subview;
#elif defined(Q_OS_WINDOWS)
HWND pluginWindow;
#else
Display* const display;
Window pluginWindow;
#endif

Callback* callback;
uint xOffset, yOffset;
bool lookingForChildren;

PrivateData(QWidget* const w)
: widget(w),
ourWindowId(w->winId()),
#if defined(Q_OS_MAC)
view([[NSView new]retain]),
subview(nullptr),
#elif defined(Q_OS_WINDOWS)
pluginWindow(nullptr),
#else
display(QX11Info::display()),
pluginWindow(0),
#endif
callback(nullptr),
xOffset(0),
yOffset(0),
lookingForChildren(false)
{
#ifdef Q_OS_MAC
[view setAutoresizingMask:NSViewNotSizable];
[view setAutoresizesSubviews:NO];
[view setHidden:YES];
[(NSView*)ourWindowPtr addSubview:view];
#endif
}

~PrivateData()
{
#ifdef Q_OS_MAC
[view release];
#endif
}

void* prepare(Callback* const cb)
{
callback = cb;
lookingForChildren = true;
#if defined(Q_OS_MAC)
subview = nullptr;
return view;
#elif defined(Q_OS_WINDOWS)
pluginWindow = nullptr;
return ourWindowPtr;
#else
pluginWindow = 0;
return (void*)ourWindowId;
#endif
}

bool hide()
{
#if defined(Q_OS_MAC)
[view setHidden:YES];
[NSOpenGLContext clearCurrentContext];
return true;
#elif defined(Q_OS_WINDOWS)
if (pluginWindow != nullptr)
{
ShowWindow(pluginWindow, SW_HIDE);
pluginWindow = nullptr;
return true;
}
#else
if (pluginWindow != 0)
{
XUnmapWindow(display, pluginWindow);
XSync(display, True);
pluginWindow = 0;
return true;
}
#endif
return false;
}

void idle()
{
if (lookingForChildren)
{
#if defined(Q_OS_MAC)
if (subview == nullptr)
{
for (NSView* subview2 in [view subviews])
{
subview = subview2;
break;
}
}
#elif defined(Q_OS_WINDOWS)
if (pluginWindow == nullptr)
pluginWindow = FindWindowExA((::HWND)ourWindowPtr, nullptr, nullptr, nullptr);
#else
if (pluginWindow == 0)
{
::Window rootWindow, parentWindow;
::Window* childWindows = nullptr;
uint numChildren = 0;

XQueryTree(display, ourWindowId, &rootWindow, &parentWindow, &childWindows, &numChildren);

if (numChildren > 0 && childWindows != nullptr)
{
// pick last child, needed for NTK based UIs which do not delete/remove previous windows.
//sadly this breaks ildaeil-within-ildaeil recursion.. :(
pluginWindow = childWindows[numChildren - 1];
XFree(childWindows);
}
}
#endif
}

#if defined(Q_OS_MAC)
if (subview != nullptr)
{
const double scaleFactor = [[[view window] screen] backingScaleFactor];
const NSSize size = [subview frame].size;
const double width = size.width;
const double height = size.height;

// if (lookingForChildren)
// carla_stdout("child window bounds %f %f | offset %u %u", width, height, xOffset, yOffset);

if (width > 1.0 && height > 1.0)
{
lookingForChildren = false;
[view setFrameSize:size];
[view setHidden:NO];
[view setNeedsDisplay:YES];
callback->pluginWindowResized(width * scaleFactor, height * scaleFactor);
}
}
#elif defined(Q_OS_WINDOWS)
if (pluginWindow != nullptr)
{
int width = 0;
int height = 0;

RECT rect;
if (GetWindowRect(pluginWindow, &rect))
{
width = rect.right - rect.left;
height = rect.bottom - rect.top;
}

if (lookingForChildren)
d_stdout("child window bounds %i %i | offset %u %u", width, height, xOffset, yOffset);

if (width > 1 && height > 1)
{
lookingForChildren = false;
SetWindowPos(pluginWindow, 0, xOffset, yOffset, 0, 0,
SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOZORDER);
callback->pluginWindowResized(width, height);
}
}
#else
if (pluginWindow != 0)
{
int width = 0;
int height = 0;

XWindowAttributes attrs;
memset(&attrs, 0, sizeof(attrs));

pthread_mutex_lock(&gErrorMutex);
const XErrorHandler oldErrorHandler = XSetErrorHandler(gErrorHandler);
gErrorTriggered = false;

if (XGetWindowAttributes(display, pluginWindow, &attrs) && ! gErrorTriggered)
{
width = attrs.width;
height = attrs.height;
}

XSetErrorHandler(oldErrorHandler);
pthread_mutex_unlock(&gErrorMutex);

if (width == 0 && height == 0)
{
XSizeHints sizeHints;
memset(&sizeHints, 0, sizeof(sizeHints));

if (XGetNormalHints(display, pluginWindow, &sizeHints))
{
if (sizeHints.flags & PSize)
{
width = sizeHints.width;
height = sizeHints.height;
}
else if (sizeHints.flags & PBaseSize)
{
width = sizeHints.base_width;
height = sizeHints.base_height;
}
}
}

// if (lookingForChildren)
// carla_stdout("child window bounds %i %i | offset %u %u", width, height, xOffset, yOffset);

if (width > 1 && height > 1)
{
lookingForChildren = false;
XMoveWindow(display, pluginWindow, xOffset, yOffset);
XSync(display, True);
widget->setFixedSize(width, height);
callback->pluginWindowResized(width, height);
}
}
#endif
}

void setPositionAndSize(const uint x, const uint y, const uint width, const uint height)
{
#if defined(Q_OS_MAC)
const double scaleFactor = [[[view window] screen] backingScaleFactor];
[view setFrame:NSMakeRect(x / scaleFactor, y / scaleFactor, width / scaleFactor, height / scaleFactor)];
#else
// unused
(void)width;
(void)height;
#endif

xOffset = x;
yOffset = y;
}
};

ChibiEmbedWidget::ChibiEmbedWidget(QWidget* const parent)
static QWidget* widgetSetup(QWidget* const w)
{
w->setAttribute(Qt::WA_OpaquePaintEvent);
return w;
}

ChibiEmbedWidget::ChibiEmbedWidget(QWidget*)
: QWidget(nullptr, Qt::Window)
, pData(new PrivateData())
, pData(new PrivateData(widgetSetup(this)))
{
setAttribute(Qt::WA_OpaquePaintEvent);
}

ChibiEmbedWidget::~ChibiEmbedWidget()
{
delete pData;
}

bool ChibiEmbedWidget::canEmbed() const
void* ChibiEmbedWidget::prepare(Callback* const callback)
{
#ifdef HAVE_X11
return QX11Info::isPlatformX11();
#else
return false;
#endif
return pData->prepare(callback);
}

bool ChibiEmbedWidget::wasResized() const
void ChibiEmbedWidget::idle()
{
return pData->wasResized;
return pData->idle();
}

void ChibiEmbedWidget::setup(void* const ptr)
{
#ifdef HAVE_X11
if (const ::Window window = reinterpret_cast<::Window>(ptr))
{
::Display* const display = QX11Info::display();

::XWindowAttributes attrs{};
::XSizeHints hints{};
long supplied{};

XSync(display, False);
XGetWindowAttributes(display, window, &attrs);
XGetWMNormalHints(display, window, &hints, &supplied);
idle();

if (hints.flags & PBaseSize) {
setBaseSize(hints.base_width, hints.base_height);
}

if (hints.flags & PMinSize) {
setMinimumSize(hints.min_width, hints.min_height);
}

if (hints.flags & PMaxSize) {
setMaximumSize(hints.max_width, hints.max_height);
}

if ((hints.flags & PSize)) {
pData->wasResized = true;
resize(hints.width, hints.height);
}

pData->window = window;
}
#endif
// #ifdef HAVE_X11
// if (const ::Window window = reinterpret_cast<::Window>(ptr))
// {
// ::Display* const display = QX11Info::display();
//
// ::XWindowAttributes attrs{};
// ::XSizeHints hints{};
// long supplied{};
//
// XSync(display, False);
// XGetWindowAttributes(display, window, &attrs);
// XGetWMNormalHints(display, window, &hints, &supplied);
//
// if (hints.flags & PBaseSize) {
// setBaseSize(hints.base_width, hints.base_height);
// }
//
// if (hints.flags & PMinSize) {
// setMinimumSize(hints.min_width, hints.min_height);
// }
//
// if (hints.flags & PMaxSize) {
// setMaximumSize(hints.max_width, hints.max_height);
// }
//
// if ((hints.flags & PSize)) {
// // pData->wasResized = true;
// resize(hints.width, hints.height);
// }
//
// pData->window = window;
// }
// #endif
}

void ChibiEmbedWidget::resizeView(int width, int height)
{
#ifdef HAVE_X11
if (pData->window)
{
XResizeWindow(QX11Info::display(), pData->window, width, height);
}
#endif
// #ifdef HAVE_X11
// if (pData->window)
// {
// XResizeWindow(QX11Info::display(), pData->window, width, height);
// }
// #endif
}

QSize ChibiEmbedWidget::sizeHint() const
{
printf("sizeHint\n");
#ifdef HAVE_X11
if (pData->window)
{
XWindowAttributes attrs{};
// TODO check return value
XGetWindowAttributes(QX11Info::display(), pData->window, &attrs);
return {attrs.width, attrs.height};
}
#endif
// #ifdef HAVE_X11
// if (pData->pluginWindow != 0)
// {
// XWindowAttributes attrs{};
// // TODO check return value
// XGetWindowAttributes(QX11Info::display(), pData->pluginWindow, &attrs);
// return {attrs.width, attrs.height};
// }
// #endif

return {};
return size();

}
QSize ChibiEmbedWidget::minimumSizeHint() const
{
printf("minimumSizeHint\n");
#ifdef HAVE_X11
if (pData->window)
{
XSizeHints hints{};
long supplied{};

// TODO check return value
XGetWMNormalHints(QX11Info::display(), pData->window, &hints, &supplied);
// #ifdef HAVE_X11
// if (pData->pluginWindow != 0)
// {
// XSizeHints hints{};
// long supplied{};
//
// // TODO check return value
// XGetWMNormalHints(QX11Info::display(), pData->pluginWindow, &hints, &supplied);
//
// if (hints.flags & PMinSize)
// return {hints.min_width, hints.min_height};
// }
// #endif

if (hints.flags & PMinSize)
return {hints.min_width, hints.min_height};
}
#endif

return {};
return sizeHint();
}

void ChibiEmbedWidget::resizeEvent(QResizeEvent* const event)
Expand All @@ -133,8 +390,8 @@ void ChibiEmbedWidget::resizeEvent(QResizeEvent* const event)
event->size().width(),
event->size().height());

#ifdef HAVE_X11
if (pData->window)
resizeView(event->size().width(), event->size().height());
#endif
// #ifdef HAVE_X11
// if (pData->window)
// resizeView(event->size().width(), event->size().height());
// #endif
}
14 changes: 10 additions & 4 deletions chibiembedwidget.h
@@ -1,6 +1,6 @@
/*
* Chibi - Carla's mini-host plugin loader
* Copyright (C) 2020 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2020-2023 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
Expand All @@ -24,15 +24,21 @@ class ChibiEmbedWidget : public QWidget
Q_OBJECT

public:
explicit ChibiEmbedWidget(QWidget* parent);
struct Callback {
virtual ~Callback() {}
virtual void pluginWindowResized(uint width, uint height) = 0;
};

explicit ChibiEmbedWidget(QWidget*);
~ChibiEmbedWidget() override;

QSize sizeHint() const override;
QSize minimumSizeHint() const override;

bool canEmbed() const;
bool wasResized() const;
// bool wasResized() const;

void* prepare(Callback* callback);
void idle();
void setup(void* ptr);
void resizeView(int width, int height);

Expand Down
40 changes: 21 additions & 19 deletions chibiwindow.cpp
Expand Up @@ -24,15 +24,15 @@

CARLA_BACKEND_USE_NAMESPACE;

ChibiWindow::ChibiWindow(const CarlaHostHandle h, const PluginListDialogResults* const res)
ChibiWindow::ChibiWindow(const CarlaHostHandle h, const char* const name)
: QMainWindow(nullptr)
, ui(new Ui::ChibiWindow)
, handle(h)
, idleTimer(startTimer(30))
{
ui->setupUi(this);

const QString properName = QString::fromUtf8("Chibi - %1").arg(res->name);
const QString properName = QString::fromUtf8("Chibi - %1").arg(name);
setWindowTitle(properName);

{
Expand All @@ -58,23 +58,13 @@ ChibiWindow::ChibiWindow(const CarlaHostHandle h, const PluginListDialogResults*
carla_set_engine_callback(handle, _engine_callback, this);
carla_set_file_callback(handle, _file_callback, this);

if (! carla_add_plugin(handle,
static_cast<BinaryType>(res->build),
static_cast<PluginType>(res->type),
res->filename, res->name, res->label,
res->uniqueId, nullptr, PLUGIN_OPTIONS_NULL))
{
carla_stderr2("Failed to add plugin, error was: %s", carla_get_last_error(handle));
return;
}

void* const ptr = carla_embed_custom_ui(handle, 0, (void*)(intptr_t)ui->embedwidget->winId());
void* const ptr = carla_embed_custom_ui(handle, 0, ui->embedwidget->prepare(this));
ui->embedwidget->setup(ptr);

if (ui->embedwidget->wasResized())
resize(ui->embedwidget->size());
else
adjustSize();
// if (ui->embedwidget->wasResized())
// resize(ui->embedwidget->size());
// else
adjustSize();
}

ChibiWindow::~ChibiWindow()
Expand All @@ -97,12 +87,24 @@ void ChibiWindow::closeEvent(QCloseEvent* const event)

void ChibiWindow::timerEvent(QTimerEvent* const event)
{
if (carla_is_engine_running(handle))
carla_engine_idle(handle);
if (event->timerId() == idleTimer)
{
if (carla_is_engine_running(handle))
carla_engine_idle(handle);

ui->embedwidget->idle();
}

QMainWindow::timerEvent(event);
}

void ChibiWindow::pluginWindowResized(const uint /*width*/, const uint /*height*/)
{
adjustSize();
setFixedSize(width(), height());
// setFixedSize(width, height);
}

void ChibiWindow::engineCallback(const EngineCallbackOpcode action, const uint pluginId,
const int value1, const int value2, const int value3, const float valuef, const char* const valueStr)
{
Expand Down
7 changes: 5 additions & 2 deletions chibiwindow.h
Expand Up @@ -22,21 +22,24 @@
#include "CarlaHost.h"
#include "CarlaFrontend.h"

#include "chibiembedwidget.h"

QT_BEGIN_NAMESPACE
namespace Ui { class ChibiWindow; }
QT_END_NAMESPACE

class ChibiWindow : public QMainWindow
class ChibiWindow : public QMainWindow, public ChibiEmbedWidget::Callback
{
Q_OBJECT

public:
ChibiWindow(CarlaHostHandle handle, const PluginListDialogResults* res);
ChibiWindow(CarlaHostHandle handle, const char* name);
~ChibiWindow();

protected:
void closeEvent(QCloseEvent* event) override;
void timerEvent(QTimerEvent* event) override;
void pluginWindowResized(uint width, uint height) override;

private:
Ui::ChibiWindow* const ui;
Expand Down
24 changes: 17 additions & 7 deletions main.cpp
Expand Up @@ -52,18 +52,28 @@ int main(int argc, char *argv[])

const PluginListDialogResults* const res = carla_frontend_createAndExecPluginListDialog(nullptr);

int r = 1;

if (res == nullptr)
goto cleanup;

if (! carla_add_plugin(handle,
static_cast<BinaryType>(res->build),
static_cast<PluginType>(res->type),
res->filename, res->name, res->label,
res->uniqueId, nullptr, PLUGIN_OPTIONS_NULL))
{
if (carla_is_engine_running(handle))
carla_engine_close(handle);
return 1;
QMessageBox::critical(nullptr, "Error", carla_get_last_error(handle));
goto cleanup;
}

ChibiWindow w(handle, res);
w.show();

const int r = app.exec();
{
ChibiWindow w(handle, res->name);
w.show();
r = app.exec();
}

cleanup:
if (carla_is_engine_running(handle))
carla_engine_close(handle);

Expand Down