mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
269 lines
6.5 KiB
C++
269 lines
6.5 KiB
C++
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/extensions/shape.h>
|
|
|
|
#include "embedded_mpv_wid_types.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <cstdint>
|
|
#include <iostream>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
namespace {
|
|
|
|
std::mutex gX11ErrorMutex;
|
|
bool gX11ErrorTrapped = false;
|
|
XErrorEvent gLastX11Error{};
|
|
|
|
int trapX11Error(Display*, XErrorEvent* event)
|
|
{
|
|
gX11ErrorTrapped = true;
|
|
gLastX11Error = *event;
|
|
return 0;
|
|
}
|
|
|
|
bool isTraceEnabled()
|
|
{
|
|
return std::getenv("IPTVNATOR_TRACE_EMBEDDED_MPV") != nullptr;
|
|
}
|
|
|
|
void trace(const std::string& message)
|
|
{
|
|
if (!isTraceEnabled()) {
|
|
return;
|
|
}
|
|
|
|
std::cerr << "[Embedded MPV Linux] " << message << std::endl;
|
|
}
|
|
|
|
class ScopedX11ErrorTrap {
|
|
public:
|
|
explicit ScopedX11ErrorTrap(Display* display)
|
|
: lock_(gX11ErrorMutex)
|
|
, display_(display)
|
|
, previousHandler_(XSetErrorHandler(trapX11Error))
|
|
{
|
|
gX11ErrorTrapped = false;
|
|
gLastX11Error = {};
|
|
}
|
|
|
|
~ScopedX11ErrorTrap()
|
|
{
|
|
if (display_) {
|
|
XSync(display_, False);
|
|
}
|
|
XSetErrorHandler(previousHandler_);
|
|
}
|
|
|
|
bool failed() const
|
|
{
|
|
return gX11ErrorTrapped;
|
|
}
|
|
|
|
int errorCode() const
|
|
{
|
|
return gLastX11Error.error_code;
|
|
}
|
|
|
|
private:
|
|
std::unique_lock<std::mutex> lock_;
|
|
Display* display_ = nullptr;
|
|
XErrorHandler previousHandler_ = nullptr;
|
|
};
|
|
|
|
class NativeVideoHost {
|
|
public:
|
|
static bool isAvailable()
|
|
{
|
|
return std::getenv("DISPLAY") != nullptr;
|
|
}
|
|
|
|
static std::string lastError()
|
|
{
|
|
return lastError_;
|
|
}
|
|
|
|
bool create(uintptr_t parentHandle, const Bounds& bounds)
|
|
{
|
|
if (!isAvailable()) {
|
|
lastError_ =
|
|
"Embedded MPV on Linux requires X11 or Xwayland; DISPLAY is not set.";
|
|
return false;
|
|
}
|
|
|
|
if (parentHandle <= 1) {
|
|
lastError_ =
|
|
"Electron did not provide a valid X11 parent window; native Wayland embedding is not supported yet.";
|
|
return false;
|
|
}
|
|
|
|
trace("opening X11 display");
|
|
display_ = XOpenDisplay(nullptr);
|
|
if (!display_) {
|
|
lastError_ = "Unable to open the X11 display for embedded MPV.";
|
|
return false;
|
|
}
|
|
|
|
parentWindow_ = static_cast<Window>(parentHandle);
|
|
trace("parent window " + std::to_string(static_cast<unsigned long>(parentWindow_)));
|
|
if (!parentWindow_) {
|
|
lastError_ = "Unable to resolve Electron X11 window handle.";
|
|
destroy();
|
|
return false;
|
|
}
|
|
|
|
XSetWindowAttributes attributes{};
|
|
attributes.background_pixel = BlackPixel(display_, DefaultScreen(display_));
|
|
attributes.event_mask = ExposureMask | StructureNotifyMask;
|
|
|
|
bool createWindowFailed = false;
|
|
{
|
|
ScopedX11ErrorTrap x11Errors(display_);
|
|
trace("creating child window");
|
|
window_ = XCreateWindow(
|
|
display_,
|
|
parentWindow_,
|
|
0,
|
|
0,
|
|
1,
|
|
1,
|
|
0,
|
|
CopyFromParent,
|
|
InputOutput,
|
|
CopyFromParent,
|
|
CWBackPixel | CWEventMask,
|
|
&attributes
|
|
);
|
|
XSync(display_, False);
|
|
if (!window_ || x11Errors.failed()) {
|
|
std::ostringstream message;
|
|
message
|
|
<< "Failed to create embedded MPV X11 child window.";
|
|
if (x11Errors.failed()) {
|
|
message << " X11 error code: " << x11Errors.errorCode()
|
|
<< ".";
|
|
}
|
|
message
|
|
<< " Electron did not provide a valid X11 parent window; "
|
|
"native Wayland embedding is not supported yet.";
|
|
lastError_ = message.str();
|
|
window_ = 0;
|
|
createWindowFailed = true;
|
|
}
|
|
}
|
|
if (createWindowFailed) {
|
|
destroy();
|
|
return false;
|
|
}
|
|
if (!window_) {
|
|
lastError_ = "Failed to create embedded MPV X11 child window.";
|
|
destroy();
|
|
return false;
|
|
}
|
|
|
|
trace("clearing input shape");
|
|
clearInputShape();
|
|
trace("mapping child window");
|
|
XMapWindow(display_, window_);
|
|
trace("setting child bounds");
|
|
setBounds(bounds);
|
|
trace("flushing X11 display");
|
|
XFlush(display_);
|
|
return true;
|
|
}
|
|
|
|
void setBounds(const Bounds& bounds)
|
|
{
|
|
if (!display_ || !window_) {
|
|
return;
|
|
}
|
|
|
|
const int x = static_cast<int>(std::lround(bounds.x));
|
|
const int y = static_cast<int>(std::lround(bounds.y));
|
|
const unsigned int width = static_cast<unsigned int>(
|
|
std::max(1, static_cast<int>(std::lround(bounds.width)))
|
|
);
|
|
const unsigned int height = static_cast<unsigned int>(
|
|
std::max(1, static_cast<int>(std::lround(bounds.height)))
|
|
);
|
|
|
|
XMoveResizeWindow(display_, window_, x, y, width, height);
|
|
XRaiseWindow(display_, window_);
|
|
XFlush(display_);
|
|
drainEvents();
|
|
}
|
|
|
|
std::string wid() const
|
|
{
|
|
return std::to_string(static_cast<unsigned long>(window_));
|
|
}
|
|
|
|
void destroy()
|
|
{
|
|
if (display_ && window_) {
|
|
XDestroyWindow(display_, window_);
|
|
window_ = 0;
|
|
}
|
|
if (display_) {
|
|
XCloseDisplay(display_);
|
|
display_ = nullptr;
|
|
}
|
|
parentWindow_ = 0;
|
|
}
|
|
|
|
private:
|
|
void drainEvents()
|
|
{
|
|
if (!display_) {
|
|
return;
|
|
}
|
|
|
|
while (XPending(display_) > 0) {
|
|
XEvent event{};
|
|
XNextEvent(display_, &event);
|
|
}
|
|
}
|
|
|
|
void clearInputShape()
|
|
{
|
|
if (!display_ || !window_) {
|
|
return;
|
|
}
|
|
|
|
int eventBase = 0;
|
|
int errorBase = 0;
|
|
if (!XShapeQueryExtension(display_, &eventBase, &errorBase)) {
|
|
return;
|
|
}
|
|
|
|
XShapeCombineRectangles(
|
|
display_,
|
|
window_,
|
|
ShapeInput,
|
|
0,
|
|
0,
|
|
nullptr,
|
|
0,
|
|
ShapeSet,
|
|
Unsorted
|
|
);
|
|
}
|
|
|
|
Display* display_ = nullptr;
|
|
Window parentWindow_ = 0;
|
|
Window window_ = 0;
|
|
static inline std::string lastError_ =
|
|
"Failed to create embedded MPV X11 child window.";
|
|
};
|
|
|
|
} // namespace
|
|
|
|
#include "embedded_mpv_wid_common.h"
|