Files
obs-studio/frontend/widgets/OBSProjector.cpp
Sebastian Beckmann 268e30a52b frontend: Remove unused macOS window icons
Commit [1] originally made a differentiation between the operating
systems for the window icon. However, on macOS the window icon is only
respected for the main window (and used for the entire app, as windows
themselves don't have icons), and that got removed in [2]. This means
that this code doesn't actually do anything anymore, and thus should be
removed.

[1] 9ac92f61be
[2] 4afafaac6d
2025-08-19 14:48:06 -04:00

474 lines
12 KiB
C++

#include "OBSProjector.hpp"
#include <OBSApp.hpp>
#include <components/Multiview.hpp>
#include <utility/display-helpers.hpp>
#include <utility/platform.hpp>
#include <widgets/OBSBasic.hpp>
#include <qt-wrappers.hpp>
#include <QScreen>
#include <QWindow>
#include "moc_OBSProjector.cpp"
static QList<OBSProjector *> multiviewProjectors;
static bool updatingMultiview = false, mouseSwitching, transitionOnDoubleClick;
OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, int monitor, ProjectorType type_)
: OBSQTDisplay(widget, Qt::Window),
weakSource(OBSGetWeakRef(source_))
{
OBSSource source = GetSource();
if (source) {
sigs.emplace_back(obs_source_get_signal_handler(source), "rename", OBSSourceRenamed, this);
sigs.emplace_back(obs_source_get_signal_handler(source), "destroy", OBSSourceDestroyed, this);
}
isAlwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ProjectorAlwaysOnTop");
if (isAlwaysOnTop)
setWindowFlags(Qt::WindowStaysOnTopHint);
// Mark the window as a projector so SetDisplayAffinity
// can skip it
windowHandle()->setProperty("isOBSProjectorWindow", true);
#if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)
// Prevents resizing of projector windows
setAttribute(Qt::WA_PaintOnScreen, false);
#endif
type = type_;
#ifndef __APPLE__
setWindowIcon(QIcon::fromTheme("obs", QIcon(":/res/images/obs.png")));
#endif
if (monitor == -1)
resize(480, 270);
else
SetMonitor(monitor);
if (source)
UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
else
UpdateProjectorTitle(QString());
QAction *action = new QAction(this);
action->setShortcut(Qt::Key_Escape);
addAction(action);
connect(action, &QAction::triggered, this, &OBSProjector::EscapeTriggered);
setAttribute(Qt::WA_DeleteOnClose, true);
//disable application quit when last window closed
setAttribute(Qt::WA_QuitOnClose, false);
installEventFilter(CreateShortcutFilter());
auto addDrawCallback = [this]() {
bool isMultiview = type == ProjectorType::Multiview;
obs_display_add_draw_callback(GetDisplay(), isMultiview ? OBSRenderMultiview : OBSRender, this);
obs_display_set_background_color(GetDisplay(), 0x000000);
};
connect(this, &OBSQTDisplay::DisplayCreated, addDrawCallback);
connect(App(), &QGuiApplication::screenRemoved, this, &OBSProjector::ScreenRemoved);
if (type == ProjectorType::Multiview) {
multiview = new Multiview();
UpdateMultiview();
multiviewProjectors.push_back(this);
}
App()->IncrementSleepInhibition();
if (source)
obs_source_inc_showing(source);
ready = true;
show();
// We need it here to allow keyboard input in X11 to listen to Escape
activateWindow();
}
OBSProjector::~OBSProjector()
{
sigs.clear();
bool isMultiview = type == ProjectorType::Multiview;
obs_display_remove_draw_callback(GetDisplay(), isMultiview ? OBSRenderMultiview : OBSRender, this);
OBSSource source = GetSource();
if (source)
obs_source_dec_showing(source);
if (isMultiview) {
delete multiview;
multiviewProjectors.removeAll(this);
}
App()->DecrementSleepInhibition();
}
void OBSProjector::SetMonitor(int monitor)
{
savedMonitor = monitor;
setGeometry(QGuiApplication::screens()[monitor]->geometry());
showFullScreen();
SetHideCursor();
}
void OBSProjector::SetHideCursor()
{
if (savedMonitor == -1)
return;
bool hideCursor = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideProjectorCursor");
if (hideCursor && type != ProjectorType::Multiview)
setCursor(Qt::BlankCursor);
else
setCursor(Qt::ArrowCursor);
}
void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
{
OBSProjector *window = (OBSProjector *)data;
if (updatingMultiview || !window->ready)
return;
window->multiview->Render(cx, cy);
}
void OBSProjector::OBSRender(void *data, uint32_t cx, uint32_t cy)
{
OBSProjector *window = static_cast<OBSProjector *>(data);
if (!window->ready)
return;
OBSBasic *main = OBSBasic::Get();
OBSSource source = window->GetSource();
uint32_t targetCX;
uint32_t targetCY;
int x, y;
int newCX, newCY;
float scale;
if (source) {
targetCX = std::max(obs_source_get_width(source), 1u);
targetCY = std::max(obs_source_get_height(source), 1u);
} else {
struct obs_video_info ovi;
obs_get_video_info(&ovi);
targetCX = ovi.base_width;
targetCY = ovi.base_height;
}
GetScaleAndCenterPos(targetCX, targetCY, cx, cy, x, y, scale);
newCX = int(scale * float(targetCX));
newCY = int(scale * float(targetCY));
startRegion(x, y, newCX, newCY, 0.0f, float(targetCX), 0.0f, float(targetCY));
if (window->type == ProjectorType::Preview && main->IsPreviewProgramMode()) {
OBSSource curSource = main->GetCurrentSceneSource();
if (source != curSource) {
obs_source_dec_showing(source);
obs_source_inc_showing(curSource);
source = curSource;
window->weakSource = OBSGetWeakRef(source);
}
} else if (window->type == ProjectorType::Preview && !main->IsPreviewProgramMode()) {
window->weakSource = nullptr;
}
if (source)
obs_source_video_render(source);
else
obs_render_main_texture();
endRegion();
}
void OBSProjector::OBSSourceRenamed(void *data, calldata_t *params)
{
OBSProjector *window = static_cast<OBSProjector *>(data);
QString oldName = calldata_string(params, "prev_name");
QString newName = calldata_string(params, "new_name");
QMetaObject::invokeMethod(window, "RenameProjector", Q_ARG(QString, oldName), Q_ARG(QString, newName));
}
void OBSProjector::OBSSourceDestroyed(void *data, calldata_t *)
{
OBSProjector *window = static_cast<OBSProjector *>(data);
QMetaObject::invokeMethod(window, "EscapeTriggered");
}
void OBSProjector::mouseDoubleClickEvent(QMouseEvent *event)
{
OBSQTDisplay::mouseDoubleClickEvent(event);
if (!mouseSwitching)
return;
if (!transitionOnDoubleClick)
return;
// Only MultiView projectors handle double click
if (this->type != ProjectorType::Multiview)
return;
OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window();
if (!main->IsPreviewProgramMode())
return;
if (event->button() == Qt::LeftButton) {
QPoint pos = event->pos();
OBSSource src = multiview->GetSourceByPosition(pos.x(), pos.y());
if (!src)
return;
if (main->GetProgramSource() != src)
main->TransitionToScene(src);
}
}
void OBSProjector::mousePressEvent(QMouseEvent *event)
{
OBSQTDisplay::mousePressEvent(event);
if (event->button() == Qt::RightButton) {
QMenu *projectorMenu = new QMenu(QTStr("Fullscreen"));
OBSBasic::AddProjectorMenuMonitors(projectorMenu, this, &OBSProjector::OpenFullScreenProjector);
QMenu popup(this);
popup.addMenu(projectorMenu);
if (GetMonitor() > -1) {
popup.addAction(QTStr("Windowed"), this, &OBSProjector::OpenWindowedProjector);
} else if (!this->isMaximized()) {
popup.addAction(QTStr("Projector.ResizeWindowToContent"), this, &OBSProjector::ResizeToContent);
}
QAction *alwaysOnTopButton = new QAction(QTStr("Basic.MainMenu.View.AlwaysOnTop"), this);
alwaysOnTopButton->setCheckable(true);
alwaysOnTopButton->setChecked(isAlwaysOnTop);
connect(alwaysOnTopButton, &QAction::toggled, this, &OBSProjector::AlwaysOnTopToggled);
popup.addAction(alwaysOnTopButton);
popup.addAction(QTStr("Close"), this, &OBSProjector::EscapeTriggered);
popup.exec(QCursor::pos());
} else if (event->button() == Qt::LeftButton) {
// Only MultiView projectors handle left click
if (this->type != ProjectorType::Multiview)
return;
if (!mouseSwitching)
return;
QPoint pos = event->pos();
OBSSource src = multiview->GetSourceByPosition(pos.x(), pos.y());
if (!src)
return;
OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window();
if (main->GetCurrentSceneSource() != src)
main->SetCurrentScene(src, false);
}
}
void OBSProjector::EscapeTriggered()
{
OBSBasic *main = OBSBasic::Get();
main->DeleteProjector(this);
}
void OBSProjector::UpdateMultiview()
{
MultiviewLayout multiviewLayout =
static_cast<MultiviewLayout>(config_get_int(App()->GetUserConfig(), "BasicWindow", "MultiviewLayout"));
bool drawLabel = config_get_bool(App()->GetUserConfig(), "BasicWindow", "MultiviewDrawNames");
bool drawSafeArea = config_get_bool(App()->GetUserConfig(), "BasicWindow", "MultiviewDrawAreas");
mouseSwitching = config_get_bool(App()->GetUserConfig(), "BasicWindow", "MultiviewMouseSwitch");
transitionOnDoubleClick = config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick");
multiview->Update(multiviewLayout, drawLabel, drawSafeArea);
}
void OBSProjector::UpdateProjectorTitle(QString name)
{
QString title = nullptr;
switch (type) {
case ProjectorType::Scene:
title = QTStr("Projector.Title") + " - " + QTStr("Projector.Title.Scene").arg(name);
break;
case ProjectorType::Source:
title = QTStr("Projector.Title") + " - " + QTStr("Projector.Title.Source").arg(name);
break;
case ProjectorType::Preview:
title = QTStr("Projector.Title") + " - " + QTStr("StudioMode.Preview");
break;
case ProjectorType::StudioProgram:
title = QTStr("Projector.Title") + " - " + QTStr("StudioMode.Program");
break;
case ProjectorType::Multiview:
title = QTStr("Projector.Title") + " - " + QTStr("Projector.Title.Multiview");
break;
default:
title = name;
break;
}
setWindowTitle(title);
}
OBSSource OBSProjector::GetSource()
{
return OBSGetStrongRef(weakSource);
}
ProjectorType OBSProjector::GetProjectorType()
{
return type;
}
int OBSProjector::GetMonitor()
{
return savedMonitor;
}
void OBSProjector::UpdateMultiviewProjectors()
{
obs_enter_graphics();
updatingMultiview = true;
obs_leave_graphics();
for (auto &projector : multiviewProjectors)
projector->UpdateMultiview();
obs_enter_graphics();
updatingMultiview = false;
obs_leave_graphics();
}
void OBSProjector::RenameProjector(QString oldName, QString newName)
{
if (oldName == newName)
return;
UpdateProjectorTitle(newName);
}
void OBSProjector::OpenFullScreenProjector()
{
if (!isFullScreen())
prevGeometry = geometry();
int monitor = sender()->property("monitor").toInt();
SetMonitor(monitor);
OBSSource source = GetSource();
UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
}
void OBSProjector::OpenWindowedProjector()
{
showFullScreen();
showNormal();
setCursor(Qt::ArrowCursor);
if (!prevGeometry.isNull())
setGeometry(prevGeometry);
else
resize(480, 270);
savedMonitor = -1;
OBSSource source = GetSource();
UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
}
void OBSProjector::ResizeToContent()
{
OBSSource source = GetSource();
uint32_t targetCX;
uint32_t targetCY;
int x, y, newX, newY;
float scale;
if (source) {
targetCX = std::max(obs_source_get_width(source), 1u);
targetCY = std::max(obs_source_get_height(source), 1u);
} else {
struct obs_video_info ovi;
obs_get_video_info(&ovi);
targetCX = ovi.base_width;
targetCY = ovi.base_height;
}
QSize size = this->size();
GetScaleAndCenterPos(targetCX, targetCY, size.width(), size.height(), x, y, scale);
newX = size.width() - (x * 2);
newY = size.height() - (y * 2);
resize(newX, newY);
}
void OBSProjector::AlwaysOnTopToggled(bool isAlwaysOnTop)
{
SetIsAlwaysOnTop(isAlwaysOnTop, true);
}
void OBSProjector::closeEvent(QCloseEvent *event)
{
EscapeTriggered();
event->accept();
}
bool OBSProjector::IsAlwaysOnTop() const
{
return isAlwaysOnTop;
}
bool OBSProjector::IsAlwaysOnTopOverridden() const
{
return isAlwaysOnTopOverridden;
}
void OBSProjector::SetIsAlwaysOnTop(bool isAlwaysOnTop, bool isOverridden)
{
this->isAlwaysOnTop = isAlwaysOnTop;
this->isAlwaysOnTopOverridden = isOverridden;
SetAlwaysOnTop(this, isAlwaysOnTop);
}
void OBSProjector::ScreenRemoved(QScreen *screen)
{
if (GetMonitor() < 0)
return;
if (screen == this->screen())
EscapeTriggered();
}