mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-19 15:05:09 +00:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b100e674dd | ||
|
|
c0c4d60565 | ||
|
|
8b18d58aa0 | ||
|
|
5e1ac6df87 | ||
|
|
0c8970364c | ||
|
|
1516464f82 | ||
|
|
fd34a1fd28 | ||
|
|
9067db8915 | ||
|
|
177f845183 | ||
|
|
c9b03f80f8 | ||
|
|
a27589fe35 | ||
|
|
561d824814 |
@@ -42,7 +42,7 @@ jobs:
|
|||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: libsdl-org/SDL
|
repository: libsdl-org/SDL
|
||||||
ref: a5e02a0927a2dd999718c1903f2d59440319ca99
|
ref: release-3.4.12
|
||||||
path: deps/SDL
|
path: deps/SDL
|
||||||
|
|
||||||
- name: Build SDL3
|
- name: Build SDL3
|
||||||
@@ -86,7 +86,7 @@ jobs:
|
|||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: intel/libva
|
repository: intel/libva
|
||||||
ref: 2.23.0
|
ref: 2.24.1
|
||||||
path: deps/libva
|
path: deps/libva
|
||||||
|
|
||||||
- name: Build libva
|
- name: Build libva
|
||||||
|
|||||||
+46
-43
@@ -812,38 +812,64 @@ Flickable {
|
|||||||
ToolTip.text: qsTr("Fullscreen generally provides the best performance, but borderless windowed may work better with features like macOS Spaces, Alt+Tab, screenshot tools, on-screen overlays, etc.")
|
ToolTip.text: qsTr("Fullscreen generally provides the best performance, but borderless windowed may work better with features like macOS Spaces, Alt+Tab, screenshot tools, on-screen overlays, etc.")
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
Row {
|
||||||
id: vsyncCheck
|
spacing: 5
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hoverEnabled: true
|
|
||||||
text: qsTr("V-Sync")
|
CheckBox {
|
||||||
font.pointSize: 12
|
id: vsyncCheck
|
||||||
checked: StreamingPreferences.enableVsync
|
hoverEnabled: true
|
||||||
onCheckedChanged: {
|
text: qsTr("V-Sync")
|
||||||
StreamingPreferences.enableVsync = checked
|
font.pointSize: 12
|
||||||
|
checked: StreamingPreferences.enableVsync
|
||||||
|
onCheckedChanged: {
|
||||||
|
StreamingPreferences.enableVsync = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 5000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Disabling V-Sync allows sub-frame rendering latency, but it can display visible tearing")
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
CheckBox {
|
||||||
ToolTip.timeout: 5000
|
id: framePacingCheck
|
||||||
ToolTip.visible: hovered
|
hoverEnabled: true
|
||||||
ToolTip.text: qsTr("Disabling V-Sync allows sub-frame rendering latency, but it can display visible tearing")
|
text: qsTr("Frame pacing")
|
||||||
|
font.pointSize: 12
|
||||||
|
enabled: StreamingPreferences.enableVsync
|
||||||
|
checked: StreamingPreferences.enableVsync && StreamingPreferences.framePacing
|
||||||
|
onCheckedChanged: {
|
||||||
|
StreamingPreferences.framePacing = checked
|
||||||
|
}
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 5000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Frame pacing reduces micro-stutter by delaying frames that come in too early")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: framePacingCheck
|
id: enableHdr
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hoverEnabled: true
|
text: qsTr("Enable HDR")
|
||||||
text: qsTr("Frame pacing")
|
font.pointSize: 12
|
||||||
font.pointSize: 12
|
|
||||||
enabled: StreamingPreferences.enableVsync
|
enabled: SystemProperties.supportsHdr
|
||||||
checked: StreamingPreferences.enableVsync && StreamingPreferences.framePacing
|
checked: enabled && StreamingPreferences.enableHdr
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
StreamingPreferences.framePacing = checked
|
StreamingPreferences.enableHdr = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updating StreamingPreferences.videoCodecConfig is handled above
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
ToolTip.timeout: 5000
|
ToolTip.timeout: 5000
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: qsTr("Frame pacing reduces micro-stutter by delaying frames that come in too early")
|
ToolTip.text: enabled ?
|
||||||
|
qsTr("The stream will be HDR-capable, but some games may require an HDR monitor on your host PC to enable HDR mode.")
|
||||||
|
:
|
||||||
|
qsTr("HDR streaming is not supported on this PC.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1630,29 +1656,6 @@ Flickable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
id: enableHdr
|
|
||||||
width: parent.width
|
|
||||||
text: qsTr("Enable HDR")
|
|
||||||
font.pointSize: 12
|
|
||||||
|
|
||||||
enabled: SystemProperties.supportsHdr
|
|
||||||
checked: enabled && StreamingPreferences.enableHdr
|
|
||||||
onCheckedChanged: {
|
|
||||||
StreamingPreferences.enableHdr = checked
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updating StreamingPreferences.videoCodecConfig is handled above
|
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
|
||||||
ToolTip.timeout: 5000
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: enabled ?
|
|
||||||
qsTr("The stream will be HDR-capable, but some games may require an HDR monitor on your host PC to enable HDR mode.")
|
|
||||||
:
|
|
||||||
qsTr("HDR streaming is not supported on this PC.")
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: enableYUV444
|
id: enableYUV444
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
@@ -627,6 +627,8 @@ bool EGLRenderer::setupVideoRenderingState() {
|
|||||||
glGenTextures(EGL_MAX_PLANES, m_Textures);
|
glGenTextures(EGL_MAX_PLANES, m_Textures);
|
||||||
for (size_t i = 0; i < EGL_MAX_PLANES; ++i) {
|
for (size_t i = 0; i < EGL_MAX_PLANES; ++i) {
|
||||||
glBindTexture(GL_TEXTURE_EXTERNAL_OES, m_Textures[i]);
|
glBindTexture(GL_TEXTURE_EXTERNAL_OES, m_Textures[i]);
|
||||||
|
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
}
|
}
|
||||||
@@ -784,16 +786,6 @@ void EGLRenderer::renderFrame(AVFrame* frame)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int drawableWidth, drawableHeight;
|
|
||||||
SDL_GL_GetDrawableSize(m_Window, &drawableWidth, &drawableHeight);
|
|
||||||
SDL_Rect src, dst;
|
|
||||||
src.x = src.y = dst.x = dst.y = 0;
|
|
||||||
src.w = frame->width;
|
|
||||||
src.h = frame->height;
|
|
||||||
dst.w = drawableWidth;
|
|
||||||
dst.h = drawableHeight;
|
|
||||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
|
||||||
|
|
||||||
ssize_t plane_count = m_Backend->exportEGLImages(frame, m_EGLDisplay, imgs);
|
ssize_t plane_count = m_Backend->exportEGLImages(frame, m_EGLDisplay, imgs);
|
||||||
if (plane_count < 0)
|
if (plane_count < 0)
|
||||||
return;
|
return;
|
||||||
@@ -801,16 +793,6 @@ void EGLRenderer::renderFrame(AVFrame* frame)
|
|||||||
glActiveTexture(GL_TEXTURE0 + i);
|
glActiveTexture(GL_TEXTURE0 + i);
|
||||||
glBindTexture(GL_TEXTURE_EXTERNAL_OES, m_Textures[i]);
|
glBindTexture(GL_TEXTURE_EXTERNAL_OES, m_Textures[i]);
|
||||||
m_glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, imgs[i]);
|
m_glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, imgs[i]);
|
||||||
|
|
||||||
// Use GL_NEAREST to reduce sampling if the video region is a multiple of the frame size
|
|
||||||
if (dst.w % frame->width == 0 && dst.h % frame->height == 0) {
|
|
||||||
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
||||||
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We already called glClear() after last frame's SDL_GL_SwapWindow()
|
// We already called glClear() after last frame's SDL_GL_SwapWindow()
|
||||||
@@ -819,7 +801,17 @@ void EGLRenderer::renderFrame(AVFrame* frame)
|
|||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int drawableWidth, drawableHeight;
|
||||||
|
SDL_GL_GetDrawableSize(m_Window, &drawableWidth, &drawableHeight);
|
||||||
|
|
||||||
// Set the viewport to the size of the aspect-ratio-scaled video
|
// Set the viewport to the size of the aspect-ratio-scaled video
|
||||||
|
SDL_Rect src, dst;
|
||||||
|
src.x = src.y = dst.x = dst.y = 0;
|
||||||
|
src.w = frame->width;
|
||||||
|
src.h = frame->height;
|
||||||
|
dst.w = drawableWidth;
|
||||||
|
dst.h = drawableHeight;
|
||||||
|
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||||
glViewport(dst.x, dst.y, dst.w, dst.h);
|
glViewport(dst.x, dst.y, dst.w, dst.h);
|
||||||
|
|
||||||
glUseProgram(m_ShaderProgram);
|
glUseProgram(m_ShaderProgram);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <SDL_vulkan.h>
|
#include <SDL_vulkan.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
#include <libavutil/hwcontext_drm.h>
|
||||||
#include <libavutil/hwcontext_vulkan.h>
|
#include <libavutil/hwcontext_vulkan.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -942,6 +943,8 @@ void PlVkRenderer::waitToRender()
|
|||||||
}
|
}
|
||||||
endRenderTiming();
|
endRenderTiming();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
SDL_Delay(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1146,6 +1149,24 @@ bool PlVkRenderer::testRenderFrame(AVFrame *frame)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (frame->format == AV_PIX_FMT_DRM_PRIME) {
|
||||||
|
auto drmFrame = (AVDRMFrameDescriptor*)frame->data[0];
|
||||||
|
|
||||||
|
// This can happen with out-of-tree FFmpeg patches if the V4L2
|
||||||
|
// format lacks a mapping to a DRM format.
|
||||||
|
if (drmFrame->nb_layers == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current versions of libplacebo only support one plane per layer
|
||||||
|
// and will assert if provided a frame that violates this constraint.
|
||||||
|
for (int i = 0; i < drmFrame->nb_layers; i++) {
|
||||||
|
if (drmFrame->layers[i].nb_planes != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test if the frame can be mapped to libplacebo
|
// Test if the frame can be mapped to libplacebo
|
||||||
pl_frame mappedFrame;
|
pl_frame mappedFrame;
|
||||||
if (!mapAvFrameToPlacebo(frame, &mappedFrame)) {
|
if (!mapAvFrameToPlacebo(frame, &mappedFrame)) {
|
||||||
|
|||||||
@@ -576,11 +576,6 @@ ReadbackRetry:
|
|||||||
// Ensure the viewport is set to the desired video region
|
// Ensure the viewport is set to the desired video region
|
||||||
SDL_RenderSetViewport(m_Renderer, &dst);
|
SDL_RenderSetViewport(m_Renderer, &dst);
|
||||||
|
|
||||||
// Use nearest pixel sampling if the video region size is a multiple of the frame size
|
|
||||||
SDL_SetTextureScaleMode(m_Texture,
|
|
||||||
dst.w % frame->width == 0 && dst.h % frame->height == 0 ?
|
|
||||||
SDL_ScaleModeNearest : SDL_ScaleModeLinear);
|
|
||||||
|
|
||||||
// Draw the video content itself
|
// Draw the video content itself
|
||||||
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
|
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
|
||||||
|
|
||||||
|
|||||||
@@ -149,10 +149,12 @@ void OverlayManager::notifyOverlayUpdated(OverlayType type)
|
|||||||
(void**)&m_Overlays[type].surface,
|
(void**)&m_Overlays[type].surface,
|
||||||
m_Overlays[type].enabled ?
|
m_Overlays[type].enabled ?
|
||||||
// The _Wrapped variant is required for line breaks to work
|
// The _Wrapped variant is required for line breaks to work
|
||||||
TTF_RenderText_Blended_Wrapped(m_Overlays[type].font,
|
RenderTextOutlinedWrapped(m_Overlays[type].font,
|
||||||
m_Overlays[type].text,
|
m_Overlays[type].text,
|
||||||
m_Overlays[type].color,
|
m_Overlays[type].color,
|
||||||
1024)
|
{0, 0, 0, 255},
|
||||||
|
4,
|
||||||
|
1024)
|
||||||
: nullptr);
|
: nullptr);
|
||||||
|
|
||||||
// Notify the renderer
|
// Notify the renderer
|
||||||
@@ -163,3 +165,47 @@ void OverlayManager::notifyOverlayUpdated(OverlayType type)
|
|||||||
SDL_FreeSurface(oldSurface);
|
SDL_FreeSurface(oldSurface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_Surface* OverlayManager::RenderTextOutlinedWrapped(TTF_Font* font, const char* text, SDL_Color textColor, SDL_Color outlineColor, int outlineWidth, int wrapWidth) {
|
||||||
|
if (text == nullptr || text[0] == '\0') {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oldOutline = TTF_GetFontOutline(font);
|
||||||
|
TTF_SetFontOutline(font, outlineWidth);
|
||||||
|
|
||||||
|
// Verify that the string won't require wrapping (which could cause the outline and the text
|
||||||
|
// to diverge due to different wrapping positions).
|
||||||
|
//
|
||||||
|
// FIXME: We do this rather than just disabling wrapping entirely (wrapWidth = 0) because we
|
||||||
|
// need further testing to ensure that all renderers can handle non-NPOT overlay textures.
|
||||||
|
for (const QString& line : QString(text).split('\n')) {
|
||||||
|
int extent, count;
|
||||||
|
if (TTF_MeasureUTF8(font, line.toUtf8(), wrapWidth, &extent, &count) == 0 && count < line.size()) {
|
||||||
|
// If it requires wrapping, render it without the outline
|
||||||
|
TTF_SetFontOutline(font, oldOutline);
|
||||||
|
return TTF_RenderUTF8_Blended_Wrapped(font, text, textColor, wrapWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw text twice, but outline is a bit bigger
|
||||||
|
auto outlineSurface = TTF_RenderUTF8_Blended_Wrapped(font, text, outlineColor, wrapWidth);
|
||||||
|
TTF_SetFontOutline(font, 0);
|
||||||
|
auto textSurface = TTF_RenderUTF8_Blended_Wrapped(font, text, textColor, wrapWidth);
|
||||||
|
TTF_SetFontOutline(font, oldOutline);
|
||||||
|
|
||||||
|
if (outlineSurface == nullptr || textSurface == nullptr) {
|
||||||
|
SDL_FreeSurface(outlineSurface);
|
||||||
|
SDL_FreeSurface(textSurface);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the texts
|
||||||
|
SDL_Rect dst = { outlineWidth, outlineWidth, textSurface->w, textSurface->h };
|
||||||
|
SDL_BlitSurface(textSurface, nullptr, outlineSurface, &dst);
|
||||||
|
|
||||||
|
SDL_FreeSurface(textSurface);
|
||||||
|
return outlineSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void notifyOverlayUpdated(OverlayType type);
|
void notifyOverlayUpdated(OverlayType type);
|
||||||
|
SDL_Surface* RenderTextOutlinedWrapped(TTF_Font* font, const char* text, SDL_Color textColor, SDL_Color outlineColor, int outlineWidth, int wrapWidth);
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ CONFIG(release, debug|release) {
|
|||||||
# Enable ASan for Windows
|
# Enable ASan for Windows
|
||||||
#QMAKE_CFLAGS += -fsanitize=address
|
#QMAKE_CFLAGS += -fsanitize=address
|
||||||
#QMAKE_CXXFLAGS += -fsanitize=address
|
#QMAKE_CXXFLAGS += -fsanitize=address
|
||||||
#QMAKE_LFLAGS += -incremental:no -wholearchive:clang_rt.asan_dynamic-x86_64.lib -wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib
|
#QMAKE_LFLAGS += -incremental:no
|
||||||
|
|
||||||
# Propagate environment variable flags
|
# Propagate environment variable flags
|
||||||
QMAKE_CFLAGS += $$(CFLAGS)
|
QMAKE_CFLAGS += $$(CFLAGS)
|
||||||
|
|||||||
Submodule moonlight-common-c/moonlight-common-c updated: 2600beaf13...2ea47752c3
@@ -50,7 +50,9 @@ SOURCES += \
|
|||||||
$$ENET_DIR/protocol.c \
|
$$ENET_DIR/protocol.c \
|
||||||
$$ENET_DIR/unix.c \
|
$$ENET_DIR/unix.c \
|
||||||
$$ENET_DIR/win32.c \
|
$$ENET_DIR/win32.c \
|
||||||
$$COMMON_C_DIR/src/rswrapper.c \
|
$$COMMON_C_DIR/nanors/deps/obl/oblas_common.c \
|
||||||
|
$$COMMON_C_DIR/nanors/deps/obl/oblas_lite.c \
|
||||||
|
$$COMMON_C_DIR/nanors/rs.c \
|
||||||
$$COMMON_C_DIR/src/AudioStream.c \
|
$$COMMON_C_DIR/src/AudioStream.c \
|
||||||
$$COMMON_C_DIR/src/ByteBuffer.c \
|
$$COMMON_C_DIR/src/ByteBuffer.c \
|
||||||
$$COMMON_C_DIR/src/Connection.c \
|
$$COMMON_C_DIR/src/Connection.c \
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ $Organization = "moonlight-stream"
|
|||||||
$PrebuiltRepo = "moonlight-qt-deps"
|
$PrebuiltRepo = "moonlight-qt-deps"
|
||||||
$TargetDir = Join-Path $PSScriptRoot "libs\windows"
|
$TargetDir = Join-Path $PSScriptRoot "libs\windows"
|
||||||
$Assets = @("windows-x64.zip", "windows-ARM64.zip")
|
$Assets = @("windows-x64.zip", "windows-ARM64.zip")
|
||||||
$Tag = "v7.2"
|
$Tag = "v8"
|
||||||
|
|
||||||
if (Test-Path $TargetDir) {
|
if (Test-Path $TargetDir) {
|
||||||
Write-Host "Cleaning target directory..." -ForegroundColor Cyan
|
Write-Host "Cleaning target directory..." -ForegroundColor Cyan
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import shutil
|
|||||||
|
|
||||||
ORGANIZATION = "moonlight-stream"
|
ORGANIZATION = "moonlight-stream"
|
||||||
PREBUILT_REPO = "moonlight-qt-deps"
|
PREBUILT_REPO = "moonlight-qt-deps"
|
||||||
TAG = "v7.2"
|
TAG = "v8"
|
||||||
|
|
||||||
def get_platform_config():
|
def get_platform_config():
|
||||||
system = platform.system()
|
system = platform.system()
|
||||||
|
|||||||
Reference in New Issue
Block a user