/****************************************************************************** Copyright (C) 2014 by Hugh Bailey This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ #include "obs-app.hpp" #include "window-basic-interaction.hpp" #include "window-basic-main.hpp" #include "qt-wrappers.hpp" #include "display-helpers.hpp" #include #include #include using namespace std; OBSBasicInteraction::OBSBasicInteraction(QWidget *parent, OBSSource source_) : QDialog (parent), main (qobject_cast(parent)), resizeTimer (0), ui (new Ui::OBSBasicInteraction), source (source_), removedSignal (obs_source_get_signal_handler(source), "remove", OBSBasicInteraction::SourceRemoved, this), eventFilter (BuildEventFilter()) { int cx = (int)config_get_int(App()->GlobalConfig(), "InteractionWindow", "cx"); int cy = (int)config_get_int(App()->GlobalConfig(), "InteractionWindow", "cy"); ui->setupUi(this); ui->preview->setMouseTracking(true); ui->preview->setFocusPolicy(Qt::StrongFocus); ui->preview->installEventFilter(eventFilter.get()); if (cx > 400 && cy > 400) resize(cx, cy); OBSData settings = obs_source_get_settings(source); obs_data_release(settings); connect(windowHandle(), &QWindow::screenChanged, [this]() { if (resizeTimer) killTimer(resizeTimer); resizeTimer = startTimer(100); }); const char *name = obs_source_get_name(source); setWindowTitle(QTStr("Basic.InteractionWindow").arg(QT_UTF8(name))); } OBSBasicInteraction::~OBSBasicInteraction() { // since QT fakes a mouse movement while destructing a widget // remove our event filter ui->preview->removeEventFilter(eventFilter.get()); } OBSEventFilter *OBSBasicInteraction::BuildEventFilter() { return new OBSEventFilter( [this](QObject *obj, QEvent *event) { UNUSED_PARAMETER(obj); switch(event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: return this->HandleMouseClickEvent( static_cast(event)); case QEvent::MouseMove: case QEvent::Enter: case QEvent::Leave: return this->HandleMouseMoveEvent( static_cast(event)); case QEvent::Wheel: return this->HandleMouseWheelEvent( static_cast(event)); case QEvent::FocusIn: case QEvent::FocusOut: return this->HandleFocusEvent( static_cast(event)); case QEvent::KeyPress: case QEvent::KeyRelease: return this->HandleKeyEvent( static_cast(event)); default: return false; } }); } void OBSBasicInteraction::SourceRemoved(void *data, calldata_t params) { QMetaObject::invokeMethod(static_cast(data), "close"); UNUSED_PARAMETER(params); } void OBSBasicInteraction::DrawPreview(void *data, uint32_t cx, uint32_t cy) { OBSBasicInteraction *window = static_cast(data); if (!window->source) return; uint32_t sourceCX = max(obs_source_get_width(window->source), 1u); uint32_t sourceCY = max(obs_source_get_height(window->source), 1u); int x, y; int newCX, newCY; float scale; GetScaleAndCenterPos(sourceCX, sourceCY, cx, cy, x, y, scale); newCX = int(scale * float(sourceCX)); newCY = int(scale * float(sourceCY)); gs_viewport_push(); gs_projection_push(); gs_ortho(0.0f, float(sourceCX), 0.0f, float(sourceCY), -100.0f, 100.0f); gs_set_viewport(x, y, newCX, newCY); obs_source_video_render(window->source); gs_projection_pop(); gs_viewport_pop(); } void OBSBasicInteraction::OnInteractionResized() { if (resizeTimer) killTimer(resizeTimer); resizeTimer = startTimer(100); } void OBSBasicInteraction::resizeEvent(QResizeEvent *event) { if (isVisible()) { if (resizeTimer) killTimer(resizeTimer); resizeTimer = startTimer(100); } UNUSED_PARAMETER(event); } void OBSBasicInteraction::timerEvent(QTimerEvent *event) { if (event->timerId() == resizeTimer) { killTimer(resizeTimer); resizeTimer = 0; QSize size = GetPixelSize(ui->preview); obs_display_resize(display, size.width(), size.height()); } } void OBSBasicInteraction::closeEvent(QCloseEvent *event) { QDialog::closeEvent(event); if (!event->isAccepted()) return; // remove draw callback and release display in case our drawable // surfaces go away before the destructor gets called obs_display_remove_draw_callback(display, OBSBasicInteraction::DrawPreview, this); display = nullptr; config_set_int(App()->GlobalConfig(), "InteractionWindow", "cx", width()); config_set_int(App()->GlobalConfig(), "InteractionWindow", "cy", height()); } static int TranslateQtKeyboardEventModifiers(QInputEvent *event, bool mouseEvent) { int obsModifiers = INTERACT_NONE; if (event->modifiers().testFlag(Qt::ShiftModifier)) obsModifiers |= INTERACT_SHIFT_KEY; if (event->modifiers().testFlag(Qt::AltModifier)) obsModifiers |= INTERACT_ALT_KEY; #ifdef __APPLE__ // Mac: Meta = Control, Control = Command if (event->modifiers().testFlag(Qt::ControlModifier)) obsModifiers |= INTERACT_COMMAND_KEY; if (event->modifiers().testFlag(Qt::MetaModifier)) obsModifiers |= INTERACT_CONTROL_KEY; #else // Handle windows key? Can a browser even trap that key? if (event->modifiers().testFlag(Qt::ControlModifier)) obsModifiers |= INTERACT_CONTROL_KEY; #endif if (!mouseEvent) { if (event->modifiers().testFlag(Qt::KeypadModifier)) obsModifiers |= INTERACT_IS_KEY_PAD; } return obsModifiers; } static int TranslateQtMouseEventModifiers( QMouseEvent *event) { int modifiers = TranslateQtKeyboardEventModifiers(event, true); if (event->buttons().testFlag(Qt::LeftButton)) modifiers |= INTERACT_MOUSE_LEFT; if (event->buttons().testFlag(Qt::MiddleButton)) modifiers |= INTERACT_MOUSE_MIDDLE; if (event->buttons().testFlag(Qt::RightButton)) modifiers |= INTERACT_MOUSE_RIGHT; return modifiers; } bool OBSBasicInteraction::GetSourceRelativeXY( int mouseX, int mouseY, int &relX, int &relY) { QSize size = GetPixelSize(ui->preview); uint32_t sourceCX = max(obs_source_get_width(source), 1u); uint32_t sourceCY = max(obs_source_get_height(source), 1u); int x, y; float scale; GetScaleAndCenterPos(sourceCX, sourceCY, size.width(), size.height(), x, y, scale); if (x > 0) { relX = int(float(mouseX - x) / scale); relY = int(float(mouseY / scale)); } else { relX = int(float(mouseX / scale)); relY = int(float(mouseY - y) / scale); } // Confirm mouse is inside the source if (relX < 0 || relX > int(sourceCX)) return false; if (relY < 0 || relY > int(sourceCY)) return false; return true; } bool OBSBasicInteraction::HandleMouseClickEvent( QMouseEvent *event) { bool mouseUp = event->type() == QEvent::MouseButtonRelease; int clickCount = 1; if (event->type() == QEvent::MouseButtonDblClick) clickCount = 2; struct obs_mouse_event mouseEvent = {}; mouseEvent.modifiers = TranslateQtMouseEventModifiers(event); int32_t button = 0; switch (event->button()) { case Qt::LeftButton: button = MOUSE_LEFT; break; case Qt::MiddleButton: button = MOUSE_MIDDLE; break; case Qt::RightButton: button = MOUSE_RIGHT; break; default: blog(LOG_WARNING, "unknown button type %d", event->button()); return false; } // Why doesn't this work? //if (event->flags().testFlag(Qt::MouseEventCreatedDoubleClick)) // clickCount = 2; bool insideSource = GetSourceRelativeXY(event->x(), event->y(), mouseEvent.x, mouseEvent.y); if (mouseUp || insideSource) obs_source_send_mouse_click(source, &mouseEvent, button, mouseUp, clickCount); return true; } bool OBSBasicInteraction::HandleMouseMoveEvent(QMouseEvent *event) { struct obs_mouse_event mouseEvent = {}; bool mouseLeave = event->type() == QEvent::Leave; if (!mouseLeave) { mouseEvent.modifiers = TranslateQtMouseEventModifiers(event); mouseLeave = !GetSourceRelativeXY(event->x(), event->y(), mouseEvent.x, mouseEvent.y); } obs_source_send_mouse_move(source, &mouseEvent, mouseLeave); return true; } bool OBSBasicInteraction::HandleMouseWheelEvent(QWheelEvent *event) { struct obs_mouse_event mouseEvent = {}; mouseEvent.modifiers = TranslateQtKeyboardEventModifiers(event, true); int xDelta = 0; int yDelta = 0; if (!event->pixelDelta().isNull()) { if (event->orientation() == Qt::Horizontal) xDelta = event->pixelDelta().x(); else yDelta = event->pixelDelta().y(); } else { if (event->orientation() == Qt::Horizontal) xDelta = event->delta(); else yDelta = event->delta(); } if (GetSourceRelativeXY(event->x(), event->y(), mouseEvent.x, mouseEvent.y)) obs_source_send_mouse_wheel(source, &mouseEvent, xDelta, yDelta); return true; } bool OBSBasicInteraction::HandleFocusEvent(QFocusEvent *event) { bool focus = event->type() == QEvent::FocusIn; obs_source_send_focus(source, focus); return true; } bool OBSBasicInteraction::HandleKeyEvent(QKeyEvent *event) { struct obs_key_event keyEvent; QByteArray text = event->text().toUtf8(); keyEvent.modifiers = TranslateQtKeyboardEventModifiers(event, false); keyEvent.text = text.data(); keyEvent.native_modifiers = event->nativeModifiers(); keyEvent.native_scancode = event->nativeScanCode(); keyEvent.native_vkey = event->nativeVirtualKey(); bool keyUp = event->type() == QEvent::KeyRelease; obs_source_send_key_click(source, &keyEvent, keyUp); return true; } void OBSBasicInteraction::Init() { gs_init_data init_data = {}; show(); QSize previewSize = GetPixelSize(ui->preview); init_data.cx = uint32_t(previewSize.width()); init_data.cy = uint32_t(previewSize.height()); init_data.format = GS_RGBA; QTToGSWindow(ui->preview->winId(), init_data.window); display = obs_display_create(&init_data); if (display) obs_display_add_draw_callback(display, OBSBasicInteraction::DrawPreview, this); }