" + tr("See the ") + QString("").arg(MM::WEBSITE_URL) + tr("%1 website").arg(MM::APPLICATION_NAME) + " for more information on this software.
"; + // Sponsor + commercial services. MapMap is the funnel; Art Plus Code is the + // business. Keep the two calls to action distinct (services vs. donation). + QString servicesText = "" + tr("%1 is developed and sponsored by Art Plus Code. " + "Need a custom video mapping installation, a new feature, integration or training? ") + .arg(MM::APPLICATION_NAME) + + QString("").arg(MM::SERVICES_URL) + tr("Hire us") + ".
"; + QString supportText = "" + tr("Enjoying %1? ").arg(MM::APPLICATION_NAME) + + QString("").arg(MM::DONATE_URL) + tr("Support the project") + ".
"; // Append texts QString aboutText; @@ -112,6 +120,8 @@ void AboutDialog::createAboutTab() aboutText.append(licenseNoticeText); aboutText.append(aboutMappingText); aboutText.append(projectWebsiteText); + aboutText.append(servicesText); + aboutText.append(supportText); // Set about text aboutTextBrowser->setText(aboutText); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 254e2f07..c1414c62 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2248,6 +2248,24 @@ void MainWindow::createActions() connect(displayTestSignalAction, SIGNAL(toggled(bool)), outputWindow, SLOT(setDisplayTestSignal(bool))); // connect(displayTestSignalAction, SIGNAL(toggled(bool)), this, SLOT(update())); +#if defined(HAVE_SYPHON) && defined(SYPHON_OUTPUT_EXPERIMENTAL) + // Publish the output as a Syphon server (opt-in, macOS only). + // EXPERIMENTAL / DISABLED — see the SYPHON_OUTPUT_EXPERIMENTAL note in src/src.pri. + publishSyphonOutputAction = new QAction(tr("&Publish Syphon Output"), this); + publishSyphonOutputAction->setToolTip(tr("Publish the output composition as a Syphon server other apps can receive")); + publishSyphonOutputAction->setIconVisibleInMenu(false); + publishSyphonOutputAction->setCheckable(true); + publishSyphonOutputAction->setChecked(false); + publishSyphonOutputAction->setShortcutContext(Qt::ApplicationShortcut); + addAction(publishSyphonOutputAction); + connect(publishSyphonOutputAction, SIGNAL(toggled(bool)), outputWindow, SLOT(setSyphonOutputEnabled(bool))); + connect(publishSyphonOutputAction, &QAction::toggled, this, [](bool on) { + QSettings s; s.setValue("publishSyphonOutput", on); + }); + // Restore the persisted state (this fires the connections above). + publishSyphonOutputAction->setChecked(settings.value("publishSyphonOutput", false).toBool()); +#endif + // Toggle display of Undo History displayUndoHistoryAction = new QAction(tr("Display &Undo History"), this); displayUndoHistoryAction->setShortcut(Qt::ALT | Qt::Key_U); @@ -2347,9 +2365,14 @@ void MainWindow::createActions() // Bug report bugReportAction = new QAction(tr("Report an issue"), this); connect(bugReportAction, SIGNAL(triggered()), this, SLOT(reportBug())); - // Support - supportAction = new QAction(tr("Technical support"), this); - connect(supportAction, SIGNAL(triggered()), this, SLOT(technicalSupport())); + // Professional services & custom development by Art Plus Code (sponsor). + servicesAction = new QAction(tr("Professional services && custom development…"), this); + servicesAction->setToolTip(tr("Hire Art Plus Code for video mapping installations, custom features, integration and training")); + connect(servicesAction, SIGNAL(triggered()), this, SLOT(professionalServices())); + // Support the project (donations, consolidated on Open Collective). + donateAction = new QAction(tr("Support the project (donate)…"), this); + donateAction->setToolTip(tr("Help fund MapMap's ongoing development")); + connect(donateAction, SIGNAL(triggered()), this, SLOT(donate())); // Documentation docAction = new QAction(tr("Documentation"), this); connect(docAction, SIGNAL(triggered()), this, SLOT(documentation())); @@ -2467,6 +2490,9 @@ void MainWindow::createMenus() viewMenu->addSeparator(); viewMenu->addAction(outputFullScreenAction); viewMenu->addAction(displayTestSignalAction); +#if defined(HAVE_SYPHON) && defined(SYPHON_OUTPUT_EXPERIMENTAL) + viewMenu->addAction(publishSyphonOutputAction); +#endif viewMenu->addAction(displayControlsAction); viewMenu->addAction(displaySourceControlsAction); outputScreenMenu = viewMenu->addMenu(tr("&Output screen")); @@ -2505,9 +2531,12 @@ void MainWindow::createMenus() helpMenu = menuBar->addMenu(tr("&Help")); helpMenu->addAction(docAction); helpMenu->addAction(shortcutAction); - helpMenu->addAction(feedbackAction); - helpMenu->addAction(supportAction); + helpMenu->addSeparator(); helpMenu->addAction(bugReportAction); + helpMenu->addAction(feedbackAction); + helpMenu->addSeparator(); + helpMenu->addAction(servicesAction); + helpMenu->addAction(donateAction); helpMenu->addSeparator(); helpMenu->addAction(aboutAction); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index f70d0011..2a2a639b 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -158,9 +158,15 @@ private slots: void sendFeedback() { QDesktopServices::openUrl(QUrl("mailto:mapmap@artpluscode.com")); } - // Technical support - void technicalSupport() { - QDesktopServices::openUrl(QUrl(MM::WEBSITE_URL)); + // Professional services and custom development by Art Plus Code, the company + // that sponsors MapMap (video mapping installations, custom features, + // integration, training and paid support). + void professionalServices() { + QDesktopServices::openUrl(QUrl(MM::SERVICES_URL)); + } + // Support the project with a donation (consolidated on Open Collective). + void donate() { + QDesktopServices::openUrl(QUrl(MM::DONATE_URL)); } // Report an issues void reportBug() { @@ -452,6 +458,7 @@ public slots: QAction *displayControlsAction; QAction *displaySourceControlsAction; QAction *displayTestSignalAction; + QAction *publishSyphonOutputAction; QAction *stickyVerticesAction; QAction *displayUndoHistoryAction; QAction *displayZoomToolAction; @@ -471,7 +478,8 @@ public slots: // help actions QAction *bugReportAction; - QAction *supportAction; + QAction *servicesAction; + QAction *donateAction; QAction *docAction; QAction *feedbackAction; QAction *shortcutAction; diff --git a/src/gui/OutputGLCanvas.cpp b/src/gui/OutputGLCanvas.cpp index cf6185b3..126bb6d8 100644 --- a/src/gui/OutputGLCanvas.cpp +++ b/src/gui/OutputGLCanvas.cpp @@ -46,8 +46,51 @@ void OutputGLCanvas::setSceneRectToViewportGeometry() setSceneRect(viewport()->geometry()); } +void OutputGLCanvas::setSyphonOutputEnabled(bool on) +{ +#ifdef HAVE_SYPHON + _syphonOutput.setEnabled(on); + // Repaint so the server starts/stops publishing promptly. + if (viewport()) + viewport()->update(); +#else + Q_UNUSED(on); +#endif +} + +bool OutputGLCanvas::isSyphonOutputEnabled() const +{ +#ifdef HAVE_SYPHON + return _syphonOutput.isEnabled(); +#else + return false; +#endif +} + +void OutputGLCanvas::setSyphonServerName(const QString& name) +{ +#ifdef HAVE_SYPHON + _syphonOutput.setServerName(name); +#else + Q_UNUSED(name); +#endif +} + void OutputGLCanvas::drawForeground(QPainter *painter , const QRectF &rect) { +#if defined(HAVE_SYPHON) && defined(SYPHON_OUTPUT_EXPERIMENTAL) + // Publish the clean composition (background + mappings, no editing overlays or + // test signal) to Syphon before the foreground is drawn. drawForeground runs + // after the background and all items, so the framebuffer holds the full frame. + // DISABLED — see the SYPHON_OUTPUT_EXPERIMENTAL note in src/src.pri. + if (_syphonOutput.isEnabled()) + { + painter->beginNativePainting(); + _syphonOutput.publishCurrentFramebuffer(); + painter->endNativePainting(); + } +#endif + QSettings settings; bool controlOnMouseOver = settings.value("showControlOnMouseOver", MM::SHOW_OUTPUT_ON_MOUSE_HOVER).toBool(); diff --git a/src/gui/OutputGLCanvas.h b/src/gui/OutputGLCanvas.h index a62c3883..8a53b47f 100644 --- a/src/gui/OutputGLCanvas.h +++ b/src/gui/OutputGLCanvas.h @@ -24,6 +24,10 @@ #include "MapperGLCanvas.h" +#ifdef HAVE_SYPHON +#include "SyphonOutput.h" +#endif + namespace mmp { class OutputGLCanvas: public MapperGLCanvas @@ -49,6 +53,12 @@ class OutputGLCanvas: public MapperGLCanvas _displayTestSignal = displayTestSignal; } + // Syphon output (macOS): publish the rendered output as a Syphon server. + // No-ops on platforms without Syphon support. + void setSyphonOutputEnabled(bool on); + bool isSyphonOutputEnabled() const; + void setSyphonServerName(const QString& name); + private: void _drawClassicTestSignal(QPainter* painter); void _drawPALTestCard(QPainter *painter); @@ -64,6 +74,10 @@ class OutputGLCanvas: public MapperGLCanvas QImage _ntscTestCard; bool _windowIsHovered; +#ifdef HAVE_SYPHON + SyphonOutput _syphonOutput; +#endif + protected: // overriden from QGlWidget: virtual void resizeGL(int width, int height); diff --git a/src/gui/OutputGLWindow.cpp b/src/gui/OutputGLWindow.cpp index 95858075..f86c3204 100644 --- a/src/gui/OutputGLWindow.cpp +++ b/src/gui/OutputGLWindow.cpp @@ -87,6 +87,11 @@ void OutputGLWindow::setCanvasDisplayCrosshair(bool crosshair) _resetCursor(_isFullScreen); } +void OutputGLWindow::setSyphonOutputEnabled(bool on) +{ + canvas->setSyphonOutputEnabled(on); +} + void OutputGLWindow::setDisplayTestSignal(bool displayTestSignal) { canvas->setDisplayTestSignal(displayTestSignal); diff --git a/src/gui/OutputGLWindow.h b/src/gui/OutputGLWindow.h index 15885181..862d5e6f 100644 --- a/src/gui/OutputGLWindow.h +++ b/src/gui/OutputGLWindow.h @@ -48,6 +48,7 @@ public slots: void setFullScreen(bool fullScreen); void setCanvasDisplayCrosshair(bool crosshair); void setDisplayTestSignal(bool displayTestSignal); + void setSyphonOutputEnabled(bool on); signals: void closed(); diff --git a/src/src.pri b/src/src.pri index bf4ef3e5..466f1c89 100644 --- a/src/src.pri +++ b/src/src.pri @@ -55,6 +55,15 @@ macx { syphon_framework.path = Contents/Frameworks QMAKE_BUNDLE_DATA += syphon_framework + # Syphon OUTPUT (publishing MapMap's output as a Syphon server) is EXPERIMENTAL + # and DISABLED by default: SyphonOpenGLServer cannot create its IOSurface + # texture in MapMap's legacy-OpenGL-over-Metal context on Apple Silicon (it + # floods "cannot create texture, Metal texture cache was released" and never + # publishes). The implementation is kept (SyphonOutput / SyphonServerImpl.mm + + # the OutputGLCanvas hook + the "Publish Syphon Output" menu item) but compiled + # out. Uncomment to build it back in for development. Tracked on the roadmap. + # DEFINES += SYPHON_OUTPUT_EXPERIMENTAL + # With Xcode Tools > 1.5, to reduce the size of your binary even more: # LIBS += -dead_strip # This tells qmake not to put the executable inside a bundle.