mirror of
https://github.com/hyprwm/xdg-desktop-portal-hyprland.git
synced 2024-11-23 14:55:57 +01:00
core: Move to hyprwayland-scanner (#260)
* move to hw-s * hyprland-share-picker: add missing sources to meson, format CMake * CMake: bump hw-s version * CMake: fix protocolnew external protos * CMake: get wayland.xml from wayland-scanner * Nix: add missing deps * Meson: use hw-s for protocols, add hyprutils --------- Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
This commit is contained in:
parent
e695669fd8
commit
eb3f3d9854
24 changed files with 701 additions and 835 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -60,5 +60,5 @@ build-*/
|
||||||
|
|
||||||
hyprland-share-picker/build/
|
hyprland-share-picker/build/
|
||||||
|
|
||||||
protocols/*.c
|
protocols/*.c*
|
||||||
protocols/*.h
|
protocols/*.h*
|
|
@ -61,7 +61,9 @@ pkg_check_modules(
|
||||||
libspa-0.2
|
libspa-0.2
|
||||||
libdrm
|
libdrm
|
||||||
gbm
|
gbm
|
||||||
hyprlang>=0.2.0)
|
hyprlang>=0.2.0
|
||||||
|
hyprutils
|
||||||
|
hyprwayland-scanner>=0.4.2)
|
||||||
|
|
||||||
# check whether we can find sdbus-c++ through pkg-config
|
# check whether we can find sdbus-c++ through pkg-config
|
||||||
pkg_check_modules(SDBUS IMPORTED_TARGET sdbus-c++)
|
pkg_check_modules(SDBUS IMPORTED_TARGET sdbus-c++)
|
||||||
|
@ -76,7 +78,7 @@ pkg_check_modules(HYPRLAND_PROTOS IMPORTED_TARGET hyprland-protocols)
|
||||||
if(HYPRLAND_PROTOS_FOUND)
|
if(HYPRLAND_PROTOS_FOUND)
|
||||||
set(HYPRLAND_PROTOCOLS "${HYPRLAND_PROTOS_PREFIX}/share/hyprland-protocols")
|
set(HYPRLAND_PROTOCOLS "${HYPRLAND_PROTOS_PREFIX}/share/hyprland-protocols")
|
||||||
else()
|
else()
|
||||||
set(HYPRLAND_PROTOCOLS "subprojects/hyprland-protocols")
|
set(HYPRLAND_PROTOCOLS "${CMAKE_SOURCE_DIR}/subprojects/hyprland-protocols")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
|
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
|
||||||
|
@ -86,43 +88,47 @@ target_link_libraries(
|
||||||
PkgConfig::deps)
|
PkgConfig::deps)
|
||||||
|
|
||||||
# protocols
|
# protocols
|
||||||
find_program(WaylandScanner NAMES wayland-scanner)
|
|
||||||
message(STATUS "Found WaylandScanner at ${WaylandScanner}")
|
|
||||||
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
||||||
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
|
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
|
||||||
|
pkg_get_variable(WAYLAND_SCANNER_DIR wayland-scanner pkgdatadir)
|
||||||
|
message(STATUS "Found wayland-scanner at ${WAYLAND_SCANNER_DIR}")
|
||||||
|
|
||||||
function(protocol protoPath protoName external)
|
function(protocolnew protoPath protoName external)
|
||||||
if(external)
|
if(external)
|
||||||
set(path ${protoPath})
|
set(path ${protoPath})
|
||||||
else()
|
else()
|
||||||
set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath})
|
set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath})
|
||||||
endif()
|
endif()
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}-protocol.h
|
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp
|
||||||
COMMAND ${WaylandScanner} client-header ${path}
|
${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp
|
||||||
${CMAKE_SOURCE_DIR}/protocols/${protoName}-protocol.h
|
COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml
|
||||||
|
${CMAKE_SOURCE_DIR}/protocols/
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
|
target_sources(xdg-desktop-portal-hyprland PRIVATE protocols/${protoName}.cpp
|
||||||
|
protocols/${protoName}.hpp)
|
||||||
|
endfunction()
|
||||||
|
function(protocolWayland)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}-protocol.c
|
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp
|
||||||
COMMAND ${WaylandScanner} private-code ${path}
|
${CMAKE_SOURCE_DIR}/protocols/wayland.hpp
|
||||||
${CMAKE_SOURCE_DIR}/protocols/${protoName}-protocol.c
|
COMMAND hyprwayland-scanner --wayland-enums --client
|
||||||
|
${WAYLAND_SCANNER_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
target_sources(xdg-desktop-portal-hyprland
|
target_sources(xdg-desktop-portal-hyprland PRIVATE protocols/wayland.cpp
|
||||||
PRIVATE protocols/${protoName}-protocol.h)
|
protocols/wayland.hpp)
|
||||||
target_sources(xdg-desktop-portal-hyprland
|
|
||||||
PRIVATE protocols/${protoName}-protocol.c)
|
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
protocol("protocols/wlr-foreign-toplevel-management-unstable-v1.xml"
|
protocolwayland()
|
||||||
|
|
||||||
|
protocolnew("${CMAKE_SOURCE_DIR}/protocols"
|
||||||
"wlr-foreign-toplevel-management-unstable-v1" true)
|
"wlr-foreign-toplevel-management-unstable-v1" true)
|
||||||
protocol("protocols/wlr-screencopy-unstable-v1.xml"
|
protocolnew("${CMAKE_SOURCE_DIR}/protocols" "wlr-screencopy-unstable-v1" true)
|
||||||
"wlr-screencopy-unstable-v1" true)
|
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-global-shortcuts-v1"
|
||||||
protocol("${HYPRLAND_PROTOCOLS}/protocols/hyprland-global-shortcuts-v1.xml"
|
true)
|
||||||
"hyprland-global-shortcuts-v1" true)
|
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-toplevel-export-v1"
|
||||||
protocol("${HYPRLAND_PROTOCOLS}/protocols/hyprland-toplevel-export-v1.xml"
|
true)
|
||||||
"hyprland-toplevel-export-v1" true)
|
protocolnew("unstable/linux-dmabuf" "linux-dmabuf-unstable-v1" false)
|
||||||
protocol("unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml"
|
|
||||||
"linux-dmabuf-unstable-v1" false)
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
install(TARGETS hyprland-share-picker)
|
install(TARGETS hyprland-share-picker)
|
||||||
|
|
81
flake.lock
81
flake.lock
|
@ -29,14 +29,16 @@
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"systems": "systems"
|
"systems": [
|
||||||
|
"systems"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1725188252,
|
"lastModified": 1725997860,
|
||||||
"narHash": "sha256-yBH8c4GDaEAtBrh+BqIlrx5vp6gG/Gu8fQQK63KAQgs=",
|
"narHash": "sha256-d/rZ/fHR5l1n7PeyLw0StWMNLXVU9c4HFyfskw568so=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "hyprlang",
|
"repo": "hyprlang",
|
||||||
"rev": "c12ab785ce1982f82594aff03b3104c598186ddd",
|
"rev": "dfeb5811dd6485490cce18d6cc1e38a055eea876",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -70,13 +72,59 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hyprutils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": [
|
||||||
|
"systems"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1724966483,
|
||||||
|
"narHash": "sha256-WXDgKIbzjYKczxSZOsJplCS1i1yrTUpsDPuJV/xpYLo=",
|
||||||
|
"owner": "hyprwm",
|
||||||
|
"repo": "hyprutils",
|
||||||
|
"rev": "8976e3f6a5357da953a09511d0c7f6a890fb6ec2",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hyprwm",
|
||||||
|
"repo": "hyprutils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hyprwayland-scanner": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": [
|
||||||
|
"systems"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1726840673,
|
||||||
|
"narHash": "sha256-HIPEXyRRVZoqD6U+lFS1B0tsIU7p83FaB9m7KT/x6mQ=",
|
||||||
|
"owner": "hyprwm",
|
||||||
|
"repo": "hyprwayland-scanner",
|
||||||
|
"rev": "b68dab23fc922eae99306988133ee80a40b39ca5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hyprwm",
|
||||||
|
"repo": "hyprwayland-scanner",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1725103162,
|
"lastModified": 1726463316,
|
||||||
"narHash": "sha256-Ym04C5+qovuQDYL/rKWSR+WESseQBbNAe5DsXNx5trY=",
|
"narHash": "sha256-gI9kkaH0ZjakJOKrdjaI/VbaMEo9qBbSUl93DnU7f4c=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "12228ff1752d7b7624a54e9c1af4b222b3c1073b",
|
"rev": "99dc8785f6a0adac95f5e2ab05cc2e1bf666d172",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -90,8 +138,10 @@
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"hyprland-protocols": "hyprland-protocols",
|
"hyprland-protocols": "hyprland-protocols",
|
||||||
"hyprlang": "hyprlang",
|
"hyprlang": "hyprlang",
|
||||||
|
"hyprutils": "hyprutils_2",
|
||||||
|
"hyprwayland-scanner": "hyprwayland-scanner",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"systems": "systems_2"
|
"systems": "systems"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems": {
|
"systems": {
|
||||||
|
@ -108,21 +158,6 @@
|
||||||
"repo": "default-linux",
|
"repo": "default-linux",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1689347949,
|
|
||||||
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default-linux",
|
|
||||||
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default-linux",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
18
flake.nix
18
flake.nix
|
@ -16,6 +16,19 @@
|
||||||
hyprlang = {
|
hyprlang = {
|
||||||
url = "github:hyprwm/hyprlang";
|
url = "github:hyprwm/hyprlang";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
inputs.systems.follows = "systems";
|
||||||
|
};
|
||||||
|
|
||||||
|
hyprutils = {
|
||||||
|
url = "github:hyprwm/hyprutils";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
inputs.systems.follows = "systems";
|
||||||
|
};
|
||||||
|
|
||||||
|
hyprwayland-scanner = {
|
||||||
|
url = "github:hyprwm/hyprwayland-scanner";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
inputs.systems.follows = "systems";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,10 +43,7 @@
|
||||||
pkgsFor = eachSystem (system:
|
pkgsFor = eachSystem (system:
|
||||||
import nixpkgs {
|
import nixpkgs {
|
||||||
localSystem = system;
|
localSystem = system;
|
||||||
overlays = [
|
overlays = [self.overlays.default];
|
||||||
inputs.hyprland-protocols.overlays.default
|
|
||||||
self.overlays.xdg-desktop-portal-hyprland
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
in {
|
in {
|
||||||
overlays = import ./nix/overlays.nix {inherit self inputs lib;};
|
overlays = import ./nix/overlays.nix {inherit self inputs lib;};
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
project(hyprland-share-picker VERSION 0.1 LANGUAGES CXX)
|
project(
|
||||||
|
hyprland-share-picker
|
||||||
|
VERSION 0.1
|
||||||
|
LANGUAGES CXX)
|
||||||
|
|
||||||
set(QT_VERSION_MAJOR 6)
|
set(QT_VERSION_MAJOR 6)
|
||||||
|
|
||||||
|
@ -14,49 +17,40 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets)
|
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets)
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
||||||
|
|
||||||
set(PROJECT_SOURCES
|
set(PROJECT_SOURCES main.cpp mainpicker.cpp mainpicker.h mainpicker.ui
|
||||||
main.cpp
|
elidedbutton.h elidedbutton.cpp)
|
||||||
mainpicker.cpp
|
|
||||||
mainpicker.h
|
|
||||||
mainpicker.ui
|
|
||||||
elidedbutton.h
|
|
||||||
elidedbutton.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||||
qt_add_executable(hyprland-share-picker
|
qt_add_executable(hyprland-share-picker MANUAL_FINALIZATION
|
||||||
MANUAL_FINALIZATION
|
${PROJECT_SOURCES})
|
||||||
${PROJECT_SOURCES}
|
# Define target properties for Android with Qt 6 as: set_property(TARGET
|
||||||
)
|
# hyprland-share-picker APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||||
# Define target properties for Android with Qt 6 as:
|
# ${CMAKE_CURRENT_SOURCE_DIR}/android) For more information, see
|
||||||
# set_property(TARGET hyprland-share-picker APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
# https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
|
||||||
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
|
||||||
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
|
|
||||||
else()
|
else()
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
add_library(hyprland-share-picker SHARED
|
add_library(hyprland-share-picker SHARED ${PROJECT_SOURCES})
|
||||||
${PROJECT_SOURCES}
|
# Define properties for Android with Qt 5 after find_package() calls as:
|
||||||
)
|
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
||||||
# Define properties for Android with Qt 5 after find_package() calls as:
|
|
||||||
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
|
||||||
else()
|
else()
|
||||||
add_executable(hyprland-share-picker
|
add_executable(hyprland-share-picker ${PROJECT_SOURCES})
|
||||||
${PROJECT_SOURCES}
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(hyprland-share-picker PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
|
target_link_libraries(hyprland-share-picker
|
||||||
|
PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
|
||||||
|
|
||||||
set_target_properties(hyprland-share-picker PROPERTIES
|
set_target_properties(
|
||||||
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
|
hyprland-share-picker
|
||||||
|
PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
|
||||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
MACOSX_BUNDLE_SHORT_VERSION_STRING
|
||||||
|
${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||||
MACOSX_BUNDLE TRUE
|
MACOSX_BUNDLE TRUE
|
||||||
WIN32_EXECUTABLE TRUE
|
WIN32_EXECUTABLE TRUE)
|
||||||
)
|
|
||||||
|
|
||||||
install(TARGETS hyprland-share-picker
|
install(
|
||||||
|
TARGETS hyprland-share-picker
|
||||||
BUNDLE DESTINATION .
|
BUNDLE DESTINATION .
|
||||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ sources = files([
|
||||||
'main.cpp',
|
'main.cpp',
|
||||||
'mainpicker.cpp',
|
'mainpicker.cpp',
|
||||||
'mainpicker.h',
|
'mainpicker.h',
|
||||||
|
'elidedbutton.h',
|
||||||
|
'elidedbutton.cpp',
|
||||||
])
|
])
|
||||||
|
|
||||||
executable('hyprland-share-picker',
|
executable('hyprland-share-picker',
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
cmake,
|
cmake,
|
||||||
makeWrapper,
|
makeWrapper,
|
||||||
pkg-config,
|
pkg-config,
|
||||||
wayland-scanner,
|
|
||||||
wrapQtAppsHook,
|
wrapQtAppsHook,
|
||||||
hyprland,
|
hyprland,
|
||||||
hyprland-protocols,
|
hyprland-protocols,
|
||||||
hyprlang,
|
hyprlang,
|
||||||
|
hyprutils,
|
||||||
|
hyprwayland-scanner,
|
||||||
libdrm,
|
libdrm,
|
||||||
mesa,
|
mesa,
|
||||||
pipewire,
|
pipewire,
|
||||||
|
@ -20,6 +21,7 @@
|
||||||
systemd,
|
systemd,
|
||||||
wayland,
|
wayland,
|
||||||
wayland-protocols,
|
wayland-protocols,
|
||||||
|
wayland-scanner,
|
||||||
debug ? false,
|
debug ? false,
|
||||||
version ? "git",
|
version ? "git",
|
||||||
}:
|
}:
|
||||||
|
@ -37,13 +39,14 @@ stdenv.mkDerivation {
|
||||||
cmake
|
cmake
|
||||||
makeWrapper
|
makeWrapper
|
||||||
pkg-config
|
pkg-config
|
||||||
wayland-scanner
|
|
||||||
wrapQtAppsHook
|
wrapQtAppsHook
|
||||||
|
hyprwayland-scanner
|
||||||
];
|
];
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
hyprland-protocols
|
hyprland-protocols
|
||||||
hyprlang
|
hyprlang
|
||||||
|
hyprutils
|
||||||
libdrm
|
libdrm
|
||||||
mesa
|
mesa
|
||||||
pipewire
|
pipewire
|
||||||
|
@ -54,6 +57,7 @@ stdenv.mkDerivation {
|
||||||
systemd
|
systemd
|
||||||
wayland
|
wayland
|
||||||
wayland-protocols
|
wayland-protocols
|
||||||
|
wayland-scanner
|
||||||
];
|
];
|
||||||
|
|
||||||
cmakeBuildType =
|
cmakeBuildType =
|
||||||
|
|
|
@ -20,6 +20,8 @@ in {
|
||||||
xdg-desktop-portal-hyprland
|
xdg-desktop-portal-hyprland
|
||||||
inputs.hyprlang.overlays.default
|
inputs.hyprlang.overlays.default
|
||||||
inputs.hyprland-protocols.overlays.default
|
inputs.hyprland-protocols.overlays.default
|
||||||
|
inputs.hyprutils.overlays.default
|
||||||
|
inputs.hyprwayland-scanner.overlays.default
|
||||||
]);
|
]);
|
||||||
xdg-desktop-portal-hyprland = lib.composeManyExtensions [
|
xdg-desktop-portal-hyprland = lib.composeManyExtensions [
|
||||||
(final: prev: {
|
(final: prev: {
|
||||||
|
|
|
@ -11,15 +11,12 @@ hyprland_protos = dependency('hyprland-protocols',
|
||||||
wl_protocol_dir = wayland_protos.get_variable('pkgdatadir')
|
wl_protocol_dir = wayland_protos.get_variable('pkgdatadir')
|
||||||
hl_protocol_dir = hyprland_protos.get_variable('pkgdatadir')
|
hl_protocol_dir = hyprland_protos.get_variable('pkgdatadir')
|
||||||
|
|
||||||
wayland_scanner_dep = dependency('wayland-scanner', required: false, native: true)
|
hyprwayland_scanner_dep = dependency('hyprwayland-scanner', required: true, native: true, version: '>=0.4.2')
|
||||||
if wayland_scanner_dep.found()
|
hyprwayland_scanner = find_program(
|
||||||
wayland_scanner = find_program(
|
hyprwayland_scanner_dep.get_variable(pkgconfig: 'hyprwayland_scanner'),
|
||||||
wayland_scanner_dep.get_variable(pkgconfig: 'wayland_scanner'),
|
|
||||||
native: true,
|
native: true,
|
||||||
)
|
)
|
||||||
else
|
|
||||||
wayland_scanner = find_program('wayland-scanner', native: true)
|
|
||||||
endif
|
|
||||||
client_protocols = [
|
client_protocols = [
|
||||||
'wlr-screencopy-unstable-v1.xml',
|
'wlr-screencopy-unstable-v1.xml',
|
||||||
'wlr-foreign-toplevel-management-unstable-v1.xml',
|
'wlr-foreign-toplevel-management-unstable-v1.xml',
|
||||||
|
@ -31,19 +28,31 @@ client_protocols = [
|
||||||
wl_proto_files = []
|
wl_proto_files = []
|
||||||
|
|
||||||
foreach xml: client_protocols
|
foreach xml: client_protocols
|
||||||
code = custom_target(
|
wl_proto_files += custom_target(
|
||||||
xml.underscorify() + '_c',
|
xml.underscorify() + '_c',
|
||||||
input: xml,
|
input: xml,
|
||||||
output: '@BASENAME@-protocol.c',
|
output: ['@BASENAME@.cpp', '@BASENAME@.hpp'],
|
||||||
command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
|
command: [hyprwayland_scanner, '--client', '@INPUT@', '@OUTDIR@'],
|
||||||
)
|
)
|
||||||
|
|
||||||
client_header = custom_target(
|
|
||||||
xml.underscorify() + '_client_h',
|
|
||||||
input: xml,
|
|
||||||
output: '@BASENAME@-protocol.h',
|
|
||||||
command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
|
|
||||||
)
|
|
||||||
|
|
||||||
wl_proto_files += [code, client_header]
|
|
||||||
endforeach
|
endforeach
|
||||||
|
|
||||||
|
wayland_scanner = dependency('wayland-scanner')
|
||||||
|
wayland_scanner_dir = wayland_scanner.get_variable('pkgdatadir')
|
||||||
|
|
||||||
|
wayland_xml = wayland_scanner_dir / 'wayland.xml'
|
||||||
|
wayland_protocol = custom_target(
|
||||||
|
wayland_xml.underscorify(),
|
||||||
|
input: wayland_xml,
|
||||||
|
output: ['@BASENAME@.cpp', '@BASENAME@.hpp'],
|
||||||
|
command: [hyprwayland_scanner, '--wayland-enums', '--client', '@INPUT@', '@OUTDIR@'],
|
||||||
|
)
|
||||||
|
|
||||||
|
lib_client_protos = static_library(
|
||||||
|
'client_protos',
|
||||||
|
wl_proto_files + wayland_protocol,
|
||||||
|
)
|
||||||
|
|
||||||
|
client_protos = declare_dependency(
|
||||||
|
link_with: lib_client_protos,
|
||||||
|
sources: wl_proto_files + wayland_protocol
|
||||||
|
)
|
||||||
|
|
|
@ -2,12 +2,6 @@
|
||||||
#include "../helpers/Log.hpp"
|
#include "../helpers/Log.hpp"
|
||||||
#include "../helpers/MiscFunctions.hpp"
|
#include "../helpers/MiscFunctions.hpp"
|
||||||
|
|
||||||
#include <protocols/hyprland-global-shortcuts-v1-protocol.h>
|
|
||||||
#include <protocols/hyprland-toplevel-export-v1-protocol.h>
|
|
||||||
#include <protocols/wlr-foreign-toplevel-management-unstable-v1-protocol.h>
|
|
||||||
#include <protocols/wlr-screencopy-unstable-v1-protocol.h>
|
|
||||||
#include <protocols/linux-dmabuf-unstable-v1-protocol.h>
|
|
||||||
|
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
@ -16,189 +10,24 @@
|
||||||
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
void handleGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
|
SOutput::SOutput(SP<CCWlOutput> output_) : output(output_) {
|
||||||
g_pPortalManager->onGlobal(data, registry, name, interface, version);
|
output->setName([this](CCWlOutput* o, const char* name_) {
|
||||||
}
|
if (!name_)
|
||||||
|
|
||||||
void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name) {
|
|
||||||
; // noop
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const wl_registry_listener registryListener = {
|
|
||||||
.global = handleGlobal,
|
|
||||||
.global_remove = handleGlobalRemove,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void handleOutputGeometry(void* data, struct wl_output* wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make,
|
|
||||||
const char* model, int32_t transform) {
|
|
||||||
const auto POUTPUT = (SOutput*)data;
|
|
||||||
|
|
||||||
POUTPUT->transform = (wl_output_transform)transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handleOutputDone(void* data, struct wl_output* wl_output) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handleOutputMode(void* data, struct wl_output* wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
|
|
||||||
const auto POUTPUT = (SOutput*)data;
|
|
||||||
POUTPUT->refreshRate = std::round(refresh / 1000.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handleOutputScale(void* data, struct wl_output* wl_output, int32_t factor) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handleOutputName(void* data, struct wl_output* wl_output, const char* name) {
|
|
||||||
if (!name)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto POUTPUT = (SOutput*)data;
|
name = name_;
|
||||||
POUTPUT->name = name;
|
|
||||||
|
|
||||||
Debug::log(LOG, "Found output name {}", POUTPUT->name);
|
Debug::log(LOG, "Found output name {}", name);
|
||||||
|
});
|
||||||
|
output->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { //
|
||||||
|
refreshRate = refresh;
|
||||||
|
});
|
||||||
|
output->setGeometry([this](CCWlOutput* r, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model,
|
||||||
|
int32_t transform_) { //
|
||||||
|
transform = (wl_output_transform)transform_;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleOutputDescription(void* data, struct wl_output* wl_output, const char* description) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const wl_output_listener outputListener = {
|
|
||||||
.geometry = handleOutputGeometry,
|
|
||||||
.mode = handleOutputMode,
|
|
||||||
.done = handleOutputDone,
|
|
||||||
.scale = handleOutputScale,
|
|
||||||
.name = handleOutputName,
|
|
||||||
.description = handleOutputDescription,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void handleDMABUFFormat(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, uint32_t format) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handleDMABUFModifier(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) {
|
|
||||||
g_pPortalManager->m_vDMABUFMods.push_back({format, (((uint64_t)modifier_hi) << 32) | modifier_lo});
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const zwp_linux_dmabuf_v1_listener dmabufListener = {
|
|
||||||
.format = handleDMABUFFormat,
|
|
||||||
.modifier = handleDMABUFModifier,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void dmabufFeedbackMainDevice(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* device_arr) {
|
|
||||||
Debug::log(LOG, "[core] dmabufFeedbackMainDevice");
|
|
||||||
|
|
||||||
RASSERT(!g_pPortalManager->m_sWaylandConnection.gbm, "double dmabuf feedback");
|
|
||||||
|
|
||||||
dev_t device;
|
|
||||||
assert(device_arr->size == sizeof(device));
|
|
||||||
memcpy(&device, device_arr->data, sizeof(device));
|
|
||||||
|
|
||||||
drmDevice* drmDev;
|
|
||||||
if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) {
|
|
||||||
Debug::log(WARN, "[dmabuf] unable to open main device?");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_pPortalManager->m_sWaylandConnection.gbmDevice = g_pPortalManager->createGBMDevice(drmDev);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dmabufFeedbackFormatTable(void* data, zwp_linux_dmabuf_feedback_v1* feedback, int fd, uint32_t size) {
|
|
||||||
Debug::log(TRACE, "[core] dmabufFeedbackFormatTable");
|
|
||||||
|
|
||||||
g_pPortalManager->m_vDMABUFMods.clear();
|
|
||||||
|
|
||||||
g_pPortalManager->m_sWaylandConnection.dma.formatTable = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
||||||
|
|
||||||
if (g_pPortalManager->m_sWaylandConnection.dma.formatTable == MAP_FAILED) {
|
|
||||||
Debug::log(ERR, "[core] format table failed to mmap");
|
|
||||||
g_pPortalManager->m_sWaylandConnection.dma.formatTable = nullptr;
|
|
||||||
g_pPortalManager->m_sWaylandConnection.dma.formatTableSize = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_pPortalManager->m_sWaylandConnection.dma.formatTableSize = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dmabufFeedbackDone(void* data, zwp_linux_dmabuf_feedback_v1* feedback) {
|
|
||||||
Debug::log(TRACE, "[core] dmabufFeedbackDone");
|
|
||||||
|
|
||||||
if (g_pPortalManager->m_sWaylandConnection.dma.formatTable)
|
|
||||||
munmap(g_pPortalManager->m_sWaylandConnection.dma.formatTable, g_pPortalManager->m_sWaylandConnection.dma.formatTableSize);
|
|
||||||
|
|
||||||
g_pPortalManager->m_sWaylandConnection.dma.formatTable = nullptr;
|
|
||||||
g_pPortalManager->m_sWaylandConnection.dma.formatTableSize = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dmabufFeedbackTrancheTargetDevice(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* device_arr) {
|
|
||||||
Debug::log(TRACE, "[core] dmabufFeedbackTrancheTargetDevice");
|
|
||||||
|
|
||||||
dev_t device;
|
|
||||||
assert(device_arr->size == sizeof(device));
|
|
||||||
memcpy(&device, device_arr->data, sizeof(device));
|
|
||||||
|
|
||||||
drmDevice* drmDev;
|
|
||||||
if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (g_pPortalManager->m_sWaylandConnection.gbmDevice) {
|
|
||||||
drmDevice* drmDevRenderer = NULL;
|
|
||||||
drmGetDevice2(gbm_device_get_fd(g_pPortalManager->m_sWaylandConnection.gbmDevice), /* flags */ 0, &drmDevRenderer);
|
|
||||||
g_pPortalManager->m_sWaylandConnection.dma.deviceUsed = drmDevicesEqual(drmDevRenderer, drmDev);
|
|
||||||
} else {
|
|
||||||
g_pPortalManager->m_sWaylandConnection.gbmDevice = g_pPortalManager->createGBMDevice(drmDev);
|
|
||||||
g_pPortalManager->m_sWaylandConnection.dma.deviceUsed = g_pPortalManager->m_sWaylandConnection.gbm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dmabufFeedbackTrancheFlags(void* data, zwp_linux_dmabuf_feedback_v1* feedback, uint32_t flags) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dmabufFeedbackTrancheFormats(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* indices) {
|
|
||||||
Debug::log(TRACE, "[core] dmabufFeedbackTrancheFormats");
|
|
||||||
|
|
||||||
if (!g_pPortalManager->m_sWaylandConnection.dma.deviceUsed || !g_pPortalManager->m_sWaylandConnection.dma.formatTable)
|
|
||||||
return;
|
|
||||||
|
|
||||||
struct fm_entry {
|
|
||||||
uint32_t format;
|
|
||||||
uint32_t padding;
|
|
||||||
uint64_t modifier;
|
|
||||||
};
|
|
||||||
// An entry in the table has to be 16 bytes long
|
|
||||||
assert(sizeof(struct fm_entry) == 16);
|
|
||||||
|
|
||||||
uint32_t n_modifiers = g_pPortalManager->m_sWaylandConnection.dma.formatTableSize / sizeof(struct fm_entry);
|
|
||||||
fm_entry* fm_entry = (struct fm_entry*)g_pPortalManager->m_sWaylandConnection.dma.formatTable;
|
|
||||||
uint16_t* idx;
|
|
||||||
|
|
||||||
for (idx = (uint16_t*)indices->data; (const char*)idx < (const char*)indices->data + indices->size; idx++) {
|
|
||||||
if (*idx >= n_modifiers)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
g_pPortalManager->m_vDMABUFMods.push_back({(fm_entry + *idx)->format, (fm_entry + *idx)->modifier});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dmabufFeedbackTrancheDone(void* data, struct zwp_linux_dmabuf_feedback_v1* zwp_linux_dmabuf_feedback_v1) {
|
|
||||||
Debug::log(TRACE, "[core] dmabufFeedbackTrancheDone");
|
|
||||||
|
|
||||||
g_pPortalManager->m_sWaylandConnection.dma.deviceUsed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const zwp_linux_dmabuf_feedback_v1_listener dmabufFeedbackListener = {
|
|
||||||
.done = dmabufFeedbackDone,
|
|
||||||
.format_table = dmabufFeedbackFormatTable,
|
|
||||||
.main_device = dmabufFeedbackMainDevice,
|
|
||||||
.tranche_done = dmabufFeedbackTrancheDone,
|
|
||||||
.tranche_target_device = dmabufFeedbackTrancheTargetDevice,
|
|
||||||
.tranche_formats = dmabufFeedbackTrancheFormats,
|
|
||||||
.tranche_flags = dmabufFeedbackTrancheFlags,
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
CPortalManager::CPortalManager() {
|
CPortalManager::CPortalManager() {
|
||||||
const auto XDG_CONFIG_HOME = getenv("XDG_CONFIG_HOME");
|
const auto XDG_CONFIG_HOME = getenv("XDG_CONFIG_HOME");
|
||||||
const auto HOME = getenv("HOME");
|
const auto HOME = getenv("HOME");
|
||||||
|
@ -218,25 +47,31 @@ CPortalManager::CPortalManager() {
|
||||||
m_sConfig.config->parse();
|
m_sConfig.config->parse();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPortalManager::onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
|
void CPortalManager::onGlobal(uint32_t name, const char* interface, uint32_t version) {
|
||||||
const std::string INTERFACE = interface;
|
const std::string INTERFACE = interface;
|
||||||
|
|
||||||
Debug::log(LOG, " | Got interface: {} (ver {})", INTERFACE, version);
|
Debug::log(LOG, " | Got interface: {} (ver {})", INTERFACE, version);
|
||||||
|
|
||||||
if (INTERFACE == zwlr_screencopy_manager_v1_interface.name && m_sPipewire.loop)
|
if (INTERFACE == zwlr_screencopy_manager_v1_interface.name && m_sPipewire.loop) {
|
||||||
m_sPortals.screencopy = std::make_unique<CScreencopyPortal>((zwlr_screencopy_manager_v1*)wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, version));
|
m_sPortals.screencopy = std::make_unique<CScreencopyPortal>(
|
||||||
|
makeShared<CCZwlrScreencopyManagerV1>(wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &zwlr_screencopy_manager_v1_interface, version)));
|
||||||
|
}
|
||||||
|
|
||||||
if (INTERFACE == hyprland_global_shortcuts_manager_v1_interface.name)
|
if (INTERFACE == hyprland_global_shortcuts_manager_v1_interface.name) {
|
||||||
m_sPortals.globalShortcuts = std::make_unique<CGlobalShortcutsPortal>(
|
m_sPortals.globalShortcuts = std::make_unique<CGlobalShortcutsPortal>(makeShared<CCHyprlandGlobalShortcutsManagerV1>(
|
||||||
(hyprland_global_shortcuts_manager_v1*)wl_registry_bind(registry, name, &hyprland_global_shortcuts_manager_v1_interface, version));
|
wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &hyprland_global_shortcuts_manager_v1_interface, version)));
|
||||||
|
}
|
||||||
|
|
||||||
else if (INTERFACE == hyprland_toplevel_export_manager_v1_interface.name)
|
else if (INTERFACE == hyprland_toplevel_export_manager_v1_interface.name) {
|
||||||
m_sWaylandConnection.hyprlandToplevelMgr = wl_registry_bind(registry, name, &hyprland_toplevel_export_manager_v1_interface, version);
|
m_sWaylandConnection.hyprlandToplevelMgr = makeShared<CCHyprlandToplevelExportManagerV1>(
|
||||||
|
wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &hyprland_toplevel_export_manager_v1_interface, version));
|
||||||
|
}
|
||||||
|
|
||||||
else if (INTERFACE == wl_output_interface.name) {
|
else if (INTERFACE == wl_output_interface.name) {
|
||||||
const auto POUTPUT = m_vOutputs.emplace_back(std::make_unique<SOutput>()).get();
|
const auto POUTPUT = m_vOutputs
|
||||||
POUTPUT->output = (wl_output*)wl_registry_bind(registry, name, &wl_output_interface, version);
|
.emplace_back(std::make_unique<SOutput>(
|
||||||
wl_output_add_listener(POUTPUT->output, &outputListener, POUTPUT);
|
makeShared<CCWlOutput>(wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &wl_output_interface, version))))
|
||||||
|
.get();
|
||||||
POUTPUT->id = name;
|
POUTPUT->id = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,16 +81,111 @@ void CPortalManager::onGlobal(void* data, struct wl_registry* registry, uint32_t
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_sWaylandConnection.linuxDmabuf = wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, version);
|
m_sWaylandConnection.linuxDmabuf =
|
||||||
m_sWaylandConnection.linuxDmabufFeedback = zwp_linux_dmabuf_v1_get_default_feedback((zwp_linux_dmabuf_v1*)m_sWaylandConnection.linuxDmabuf);
|
makeShared<CCZwpLinuxDmabufV1>(wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &zwp_linux_dmabuf_v1_interface, version));
|
||||||
zwp_linux_dmabuf_feedback_v1_add_listener((zwp_linux_dmabuf_feedback_v1*)m_sWaylandConnection.linuxDmabufFeedback, &dmabufFeedbackListener, nullptr);
|
m_sWaylandConnection.linuxDmabufFeedback = makeShared<CCZwpLinuxDmabufFeedbackV1>(m_sWaylandConnection.linuxDmabuf->sendGetDefaultFeedback());
|
||||||
|
|
||||||
|
m_sWaylandConnection.linuxDmabufFeedback->setMainDevice([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* device_arr) {
|
||||||
|
Debug::log(LOG, "[core] dmabufFeedbackMainDevice");
|
||||||
|
|
||||||
|
RASSERT(!m_sWaylandConnection.gbm, "double dmabuf feedback");
|
||||||
|
|
||||||
|
dev_t device;
|
||||||
|
assert(device_arr->size == sizeof(device));
|
||||||
|
memcpy(&device, device_arr->data, sizeof(device));
|
||||||
|
|
||||||
|
drmDevice* drmDev;
|
||||||
|
if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) {
|
||||||
|
Debug::log(WARN, "[dmabuf] unable to open main device?");
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (INTERFACE == wl_shm_interface.name)
|
m_sWaylandConnection.gbmDevice = createGBMDevice(drmDev);
|
||||||
m_sWaylandConnection.shm = (wl_shm*)wl_registry_bind(registry, name, &wl_shm_interface, version);
|
});
|
||||||
|
m_sWaylandConnection.linuxDmabufFeedback->setFormatTable([this](CCZwpLinuxDmabufFeedbackV1* r, int fd, uint32_t size) {
|
||||||
|
Debug::log(TRACE, "[core] dmabufFeedbackFormatTable");
|
||||||
|
|
||||||
|
m_vDMABUFMods.clear();
|
||||||
|
|
||||||
|
m_sWaylandConnection.dma.formatTable = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||||
|
|
||||||
|
if (m_sWaylandConnection.dma.formatTable == MAP_FAILED) {
|
||||||
|
Debug::log(ERR, "[core] format table failed to mmap");
|
||||||
|
m_sWaylandConnection.dma.formatTable = nullptr;
|
||||||
|
m_sWaylandConnection.dma.formatTableSize = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sWaylandConnection.dma.formatTableSize = size;
|
||||||
|
});
|
||||||
|
m_sWaylandConnection.linuxDmabufFeedback->setDone([this](CCZwpLinuxDmabufFeedbackV1* r) {
|
||||||
|
Debug::log(TRACE, "[core] dmabufFeedbackDone");
|
||||||
|
|
||||||
|
if (m_sWaylandConnection.dma.formatTable)
|
||||||
|
munmap(m_sWaylandConnection.dma.formatTable, m_sWaylandConnection.dma.formatTableSize);
|
||||||
|
|
||||||
|
m_sWaylandConnection.dma.formatTable = nullptr;
|
||||||
|
m_sWaylandConnection.dma.formatTableSize = 0;
|
||||||
|
});
|
||||||
|
m_sWaylandConnection.linuxDmabufFeedback->setTrancheTargetDevice([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* device_arr) {
|
||||||
|
Debug::log(TRACE, "[core] dmabufFeedbackTrancheTargetDevice");
|
||||||
|
|
||||||
|
dev_t device;
|
||||||
|
assert(device_arr->size == sizeof(device));
|
||||||
|
memcpy(&device, device_arr->data, sizeof(device));
|
||||||
|
|
||||||
|
drmDevice* drmDev;
|
||||||
|
if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_sWaylandConnection.gbmDevice) {
|
||||||
|
drmDevice* drmDevRenderer = NULL;
|
||||||
|
drmGetDevice2(gbm_device_get_fd(m_sWaylandConnection.gbmDevice), /* flags */ 0, &drmDevRenderer);
|
||||||
|
m_sWaylandConnection.dma.deviceUsed = drmDevicesEqual(drmDevRenderer, drmDev);
|
||||||
|
} else {
|
||||||
|
m_sWaylandConnection.gbmDevice = createGBMDevice(drmDev);
|
||||||
|
m_sWaylandConnection.dma.deviceUsed = m_sWaylandConnection.gbm;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
m_sWaylandConnection.linuxDmabufFeedback->setTrancheFormats([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* indices) {
|
||||||
|
Debug::log(TRACE, "[core] dmabufFeedbackTrancheFormats");
|
||||||
|
|
||||||
|
if (!m_sWaylandConnection.dma.deviceUsed || !m_sWaylandConnection.dma.formatTable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct fm_entry {
|
||||||
|
uint32_t format;
|
||||||
|
uint32_t padding;
|
||||||
|
uint64_t modifier;
|
||||||
|
};
|
||||||
|
// An entry in the table has to be 16 bytes long
|
||||||
|
assert(sizeof(struct fm_entry) == 16);
|
||||||
|
|
||||||
|
uint32_t n_modifiers = m_sWaylandConnection.dma.formatTableSize / sizeof(struct fm_entry);
|
||||||
|
fm_entry* fm_entry = (struct fm_entry*)m_sWaylandConnection.dma.formatTable;
|
||||||
|
uint16_t* idx;
|
||||||
|
|
||||||
|
for (idx = (uint16_t*)indices->data; (const char*)idx < (const char*)indices->data + indices->size; idx++) {
|
||||||
|
if (*idx >= n_modifiers)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
m_vDMABUFMods.push_back({(fm_entry + *idx)->format, (fm_entry + *idx)->modifier});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
m_sWaylandConnection.linuxDmabufFeedback->setTrancheDone([this](CCZwpLinuxDmabufFeedbackV1* r) {
|
||||||
|
Debug::log(TRACE, "[core] dmabufFeedbackTrancheDone");
|
||||||
|
|
||||||
|
m_sWaylandConnection.dma.deviceUsed = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (INTERFACE == wl_shm_interface.name) {
|
||||||
|
m_sWaylandConnection.shm = makeShared<CCWlShm>(wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &wl_shm_interface, version));
|
||||||
|
}
|
||||||
|
|
||||||
else if (INTERFACE == zwlr_foreign_toplevel_manager_v1_interface.name) {
|
else if (INTERFACE == zwlr_foreign_toplevel_manager_v1_interface.name) {
|
||||||
m_sHelpers.toplevel = std::make_unique<CToplevelManager>(registry, name, version);
|
m_sHelpers.toplevel = std::make_unique<CToplevelManager>(name, version);
|
||||||
|
|
||||||
// remove when another fix is found for https://github.com/hyprwm/xdg-desktop-portal-hyprland/issues/147
|
// remove when another fix is found for https://github.com/hyprwm/xdg-desktop-portal-hyprland/issues/147
|
||||||
if (!std::any_cast<Hyprlang::INT>(m_sConfig.config->getConfigValue("general:toplevel_dynamic_bind")))
|
if (!std::any_cast<Hyprlang::INT>(m_sConfig.config->getConfigValue("general:toplevel_dynamic_bind")))
|
||||||
|
@ -263,7 +193,7 @@ void CPortalManager::onGlobal(void* data, struct wl_registry* registry, uint32_t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPortalManager::onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name) {
|
void CPortalManager::onGlobalRemoved(uint32_t name) {
|
||||||
std::erase_if(m_vOutputs, [&](const auto& other) { return other->id == name; });
|
std::erase_if(m_vOutputs, [&](const auto& other) { return other->id == name; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,8 +229,9 @@ void CPortalManager::init() {
|
||||||
Debug::log(WARN, "XDG_CURRENT_DESKTOP unset, running on an unknown desktop");
|
Debug::log(WARN, "XDG_CURRENT_DESKTOP unset, running on an unknown desktop");
|
||||||
}
|
}
|
||||||
|
|
||||||
wl_registry* registry = wl_display_get_registry(m_sWaylandConnection.display);
|
m_sWaylandConnection.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(m_sWaylandConnection.display));
|
||||||
wl_registry_add_listener(registry, ®istryListener, nullptr);
|
m_sWaylandConnection.registry->setGlobal([this](CCWlRegistry* r, uint32_t name, const char* iface, uint32_t ver) { onGlobal(name, iface, ver); });
|
||||||
|
m_sWaylandConnection.registry->setGlobalRemove([this](CCWlRegistry* r, uint32_t name) { onGlobalRemoved(name); });
|
||||||
|
|
||||||
pw_init(nullptr, nullptr);
|
pw_init(nullptr, nullptr);
|
||||||
m_sPipewire.loop = pw_loop_new(nullptr);
|
m_sPipewire.loop = pw_loop_new(nullptr);
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sdbus-c++/sdbus-c++.h>
|
#include <sdbus-c++/sdbus-c++.h>
|
||||||
#include <wayland-client.h>
|
|
||||||
#include <hyprlang.hpp>
|
#include <hyprlang.hpp>
|
||||||
|
|
||||||
|
#include "wayland.hpp"
|
||||||
#include "../portals/Screencopy.hpp"
|
#include "../portals/Screencopy.hpp"
|
||||||
#include "../portals/Screenshot.hpp"
|
#include "../portals/Screenshot.hpp"
|
||||||
#include "../portals/GlobalShortcuts.hpp"
|
#include "../portals/GlobalShortcuts.hpp"
|
||||||
|
@ -13,13 +13,22 @@
|
||||||
#include <gbm.h>
|
#include <gbm.h>
|
||||||
#include <xf86drm.h>
|
#include <xf86drm.h>
|
||||||
|
|
||||||
|
#include "hyprland-toplevel-export-v1.hpp"
|
||||||
|
#include "hyprland-global-shortcuts-v1.hpp"
|
||||||
|
#include "linux-dmabuf-unstable-v1.hpp"
|
||||||
|
#include "wlr-foreign-toplevel-management-unstable-v1.hpp"
|
||||||
|
#include "wlr-screencopy-unstable-v1.hpp"
|
||||||
|
|
||||||
|
#include "../includes.hpp"
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
struct pw_loop;
|
struct pw_loop;
|
||||||
|
|
||||||
struct SOutput {
|
struct SOutput {
|
||||||
|
SOutput(SP<CCWlOutput>);
|
||||||
std::string name;
|
std::string name;
|
||||||
wl_output* output = nullptr;
|
SP<CCWlOutput> output = nullptr;
|
||||||
uint32_t id = 0;
|
uint32_t id = 0;
|
||||||
float refreshRate = 60.0;
|
float refreshRate = 60.0;
|
||||||
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
||||||
|
@ -36,8 +45,8 @@ class CPortalManager {
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version);
|
void onGlobal(uint32_t name, const char* interface, uint32_t version);
|
||||||
void onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name);
|
void onGlobalRemoved(uint32_t name);
|
||||||
|
|
||||||
sdbus::IConnection* getConnection();
|
sdbus::IConnection* getConnection();
|
||||||
SOutput* getOutputFromName(const std::string& name);
|
SOutput* getOutputFromName(const std::string& name);
|
||||||
|
@ -58,10 +67,11 @@ class CPortalManager {
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
wl_display* display = nullptr;
|
wl_display* display = nullptr;
|
||||||
void* hyprlandToplevelMgr = nullptr;
|
SP<CCWlRegistry> registry;
|
||||||
void* linuxDmabuf = nullptr;
|
SP<CCHyprlandToplevelExportManagerV1> hyprlandToplevelMgr;
|
||||||
void* linuxDmabufFeedback = nullptr;
|
SP<CCZwpLinuxDmabufV1> linuxDmabuf;
|
||||||
wl_shm* shm = nullptr;
|
SP<CCZwpLinuxDmabufFeedbackV1> linuxDmabufFeedback;
|
||||||
|
SP<CCWlShm> shm;
|
||||||
gbm_bo* gbm = nullptr;
|
gbm_bo* gbm = nullptr;
|
||||||
gbm_device* gbmDevice = nullptr;
|
gbm_device* gbmDevice = nullptr;
|
||||||
struct {
|
struct {
|
||||||
|
|
|
@ -18,7 +18,7 @@ enum eLogLevel {
|
||||||
std::format(reason, ##__VA_ARGS__), __LINE__, \
|
std::format(reason, ##__VA_ARGS__), __LINE__, \
|
||||||
([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })().c_str()); \
|
([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })().c_str()); \
|
||||||
printf("Assertion failed! See the log in /tmp/hypr/hyprland.log for more info."); \
|
printf("Assertion failed! See the log in /tmp/hypr/hyprland.log for more info."); \
|
||||||
*((int*)nullptr) = 1; /* so that we crash and get a coredump */ \
|
abort(); /* so that we crash and get a coredump */ \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define ASSERT(expr) RASSERT(expr, "?")
|
#define ASSERT(expr) RASSERT(expr, "?")
|
||||||
|
|
6
src/includes.hpp
Normal file
6
src/includes.hpp
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
#define SP CSharedPointer
|
||||||
|
#define WP CWeakPointer
|
|
@ -2,10 +2,12 @@ globber = run_command('find', '.', '-name', '*.cpp', check: true)
|
||||||
src = globber.stdout().strip().split('\n')
|
src = globber.stdout().strip().split('\n')
|
||||||
|
|
||||||
executable('xdg-desktop-portal-hyprland',
|
executable('xdg-desktop-portal-hyprland',
|
||||||
[src, wl_proto_files],
|
[src],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
client_protos,
|
||||||
dependency('gbm'),
|
dependency('gbm'),
|
||||||
dependency('hyprlang'),
|
dependency('hyprlang'),
|
||||||
|
dependency('hyprutils'),
|
||||||
dependency('libdrm'),
|
dependency('libdrm'),
|
||||||
dependency('libpipewire-0.3'),
|
dependency('libpipewire-0.3'),
|
||||||
dependency('sdbus-c++'),
|
dependency('sdbus-c++'),
|
||||||
|
|
|
@ -2,25 +2,15 @@
|
||||||
#include "../core/PortalManager.hpp"
|
#include "../core/PortalManager.hpp"
|
||||||
#include "../helpers/Log.hpp"
|
#include "../helpers/Log.hpp"
|
||||||
|
|
||||||
// wayland
|
SKeybind::SKeybind(SP<CCHyprlandGlobalShortcutV1> shortcut_) : shortcut(shortcut_) {
|
||||||
|
shortcut->setPressed([this](CCHyprlandGlobalShortcutV1* r, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
||||||
static void handleActivated(void* data, hyprland_global_shortcut_v1* hyprland_global_shortcut_v1, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
g_pPortalManager->m_sPortals.globalShortcuts->onActivated(this, ((uint64_t)tv_sec_hi << 32) | (uint64_t)(tv_sec_lo));
|
||||||
const auto PKEYBIND = (SKeybind*)data;
|
});
|
||||||
|
shortcut->setReleased([this](CCHyprlandGlobalShortcutV1* r, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
||||||
g_pPortalManager->m_sPortals.globalShortcuts->onActivated(PKEYBIND, ((uint64_t)tv_sec_hi << 32) | (uint64_t)(tv_sec_lo));
|
g_pPortalManager->m_sPortals.globalShortcuts->onDeactivated(this, ((uint64_t)tv_sec_hi << 32) | (uint64_t)(tv_sec_lo));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleDeactivated(void* data, hyprland_global_shortcut_v1* hyprland_global_shortcut_v1, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
|
||||||
const auto PKEYBIND = (SKeybind*)data;
|
|
||||||
|
|
||||||
g_pPortalManager->m_sPortals.globalShortcuts->onDeactivated(PKEYBIND, ((uint64_t)tv_sec_hi << 32) | (uint64_t)(tv_sec_lo));
|
|
||||||
}
|
|
||||||
|
|
||||||
static const hyprland_global_shortcut_v1_listener shortcutListener = {
|
|
||||||
.pressed = handleActivated,
|
|
||||||
.released = handleDeactivated,
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
CGlobalShortcutsPortal::SSession* CGlobalShortcutsPortal::getSession(sdbus::ObjectPath& path) {
|
CGlobalShortcutsPortal::SSession* CGlobalShortcutsPortal::getSession(sdbus::ObjectPath& path) {
|
||||||
|
@ -62,9 +52,10 @@ SKeybind* CGlobalShortcutsPortal::registerShortcut(SSession* session, const DBus
|
||||||
if (PSHORTCUT)
|
if (PSHORTCUT)
|
||||||
Debug::log(WARN, "[globalshortcuts] shortcut {} already registered for appid {}", id, session->appid);
|
Debug::log(WARN, "[globalshortcuts] shortcut {} already registered for appid {}", id, session->appid);
|
||||||
else {
|
else {
|
||||||
PSHORTCUT = session->keybinds.emplace_back(std::make_unique<SKeybind>()).get();
|
PSHORTCUT = session->keybinds
|
||||||
PSHORTCUT->shortcut = hyprland_global_shortcuts_manager_v1_register_shortcut(m_sState.manager, id.c_str(), session->appid.c_str(), description.c_str(), "");
|
.emplace_back(std::make_unique<SKeybind>(
|
||||||
hyprland_global_shortcut_v1_add_listener(PSHORTCUT->shortcut, &shortcutListener, PSHORTCUT);
|
makeShared<CCHyprlandGlobalShortcutV1>(m_sState.manager->sendRegisterShortcut(id.c_str(), session->appid.c_str(), description.c_str(), ""))))
|
||||||
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
PSHORTCUT->id = std::move(id);
|
PSHORTCUT->id = std::move(id);
|
||||||
|
@ -195,7 +186,7 @@ void CGlobalShortcutsPortal::onListShortcuts(sdbus::MethodCall& call) {
|
||||||
reply.send();
|
reply.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
CGlobalShortcutsPortal::CGlobalShortcutsPortal(hyprland_global_shortcuts_manager_v1* mgr) {
|
CGlobalShortcutsPortal::CGlobalShortcutsPortal(SP<CCHyprlandGlobalShortcutsManagerV1> mgr) {
|
||||||
m_sState.manager = mgr;
|
m_sState.manager = mgr;
|
||||||
|
|
||||||
m_pObject = sdbus::createObject(*g_pPortalManager->getConnection(), OBJECT_PATH);
|
m_pObject = sdbus::createObject(*g_pPortalManager->getConnection(), OBJECT_PATH);
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sdbus-c++/sdbus-c++.h>
|
#include <sdbus-c++/sdbus-c++.h>
|
||||||
#include <protocols/hyprland-global-shortcuts-v1-protocol.h>
|
#include "hyprland-global-shortcuts-v1.hpp"
|
||||||
#include "../shared/Session.hpp"
|
#include "../shared/Session.hpp"
|
||||||
|
|
||||||
struct SKeybind {
|
struct SKeybind {
|
||||||
|
SKeybind(SP<CCHyprlandGlobalShortcutV1> shortcut);
|
||||||
std::string id, description, preferredTrigger;
|
std::string id, description, preferredTrigger;
|
||||||
hyprland_global_shortcut_v1* shortcut = nullptr;
|
SP<CCHyprlandGlobalShortcutV1> shortcut = nullptr;
|
||||||
void* session = nullptr;
|
void* session = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CGlobalShortcutsPortal {
|
class CGlobalShortcutsPortal {
|
||||||
public:
|
public:
|
||||||
CGlobalShortcutsPortal(hyprland_global_shortcuts_manager_v1* mgr);
|
CGlobalShortcutsPortal(SP<CCHyprlandGlobalShortcutsManagerV1> mgr);
|
||||||
|
|
||||||
void onCreateSession(sdbus::MethodCall& call);
|
void onCreateSession(sdbus::MethodCall& call);
|
||||||
void onBindShortcuts(sdbus::MethodCall& call);
|
void onBindShortcuts(sdbus::MethodCall& call);
|
||||||
|
@ -36,7 +37,7 @@ class CGlobalShortcutsPortal {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct {
|
struct {
|
||||||
hyprland_global_shortcuts_manager_v1* manager;
|
SP<CCHyprlandGlobalShortcutsManagerV1> manager;
|
||||||
} m_sState;
|
} m_sState;
|
||||||
|
|
||||||
std::unique_ptr<sdbus::IObject> m_pObject;
|
std::unique_ptr<sdbus::IObject> m_pObject;
|
||||||
|
|
|
@ -5,298 +5,11 @@
|
||||||
|
|
||||||
#include <libdrm/drm_fourcc.h>
|
#include <libdrm/drm_fourcc.h>
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
#include <protocols/linux-dmabuf-unstable-v1-protocol.h>
|
#include "linux-dmabuf-unstable-v1.hpp"
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
constexpr static int MAX_RETRIES = 10;
|
constexpr static int MAX_RETRIES = 10;
|
||||||
|
|
||||||
// --------------- Wayland Protocol Handlers --------------- //
|
|
||||||
|
|
||||||
static void wlrOnBuffer(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] wlrOnBuffer for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
PSESSION->sharingData.frameInfoSHM.w = width;
|
|
||||||
PSESSION->sharingData.frameInfoSHM.h = height;
|
|
||||||
PSESSION->sharingData.frameInfoSHM.fmt = drmFourccFromSHM((wl_shm_format)format);
|
|
||||||
PSESSION->sharingData.frameInfoSHM.size = stride * height;
|
|
||||||
PSESSION->sharingData.frameInfoSHM.stride = stride;
|
|
||||||
|
|
||||||
// todo: done if ver < 3
|
|
||||||
}
|
|
||||||
|
|
||||||
static void wlrOnFlags(void* data, zwlr_screencopy_frame_v1* frame, uint32_t flags) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] wlrOnFlags for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
// todo: maybe check for y invert?
|
|
||||||
}
|
|
||||||
|
|
||||||
static void wlrOnReady(void* data, zwlr_screencopy_frame_v1* frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
PSESSION->sharingData.status = FRAME_READY;
|
|
||||||
|
|
||||||
PSESSION->sharingData.tvSec = ((((uint64_t)tv_sec_hi) << 32) + (uint64_t)tv_sec_lo);
|
|
||||||
PSESSION->sharingData.tvNsec = tv_nsec;
|
|
||||||
PSESSION->sharingData.tvTimestampNs = PSESSION->sharingData.tvSec * SPA_NSEC_PER_SEC + PSESSION->sharingData.tvNsec;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] frame timestamp sec: {} nsec: {} combined: {}ns", PSESSION->sharingData.tvSec, PSESSION->sharingData.tvNsec, PSESSION->sharingData.tvTimestampNs);
|
|
||||||
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->enqueue(PSESSION);
|
|
||||||
|
|
||||||
if (g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(PSESSION))
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION);
|
|
||||||
|
|
||||||
zwlr_screencopy_frame_v1_destroy(frame);
|
|
||||||
PSESSION->sharingData.frameCallback = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void wlrOnFailed(void* data, zwlr_screencopy_frame_v1* frame) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] wlrOnFailed for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
PSESSION->sharingData.status = FRAME_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void wlrOnDamage(void* data, zwlr_screencopy_frame_v1* frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] wlrOnDamage for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
if (PSESSION->sharingData.damageCount > 3) {
|
|
||||||
PSESSION->sharingData.damage[0] = {0, 0, PSESSION->sharingData.frameInfoDMA.w, PSESSION->sharingData.frameInfoDMA.h};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PSESSION->sharingData.damage[PSESSION->sharingData.damageCount++] = {x, y, width, height};
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] wlr damage: {} {} {} {}", x, y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void wlrOnDmabuf(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
PSESSION->sharingData.frameInfoDMA.w = width;
|
|
||||||
PSESSION->sharingData.frameInfoDMA.h = height;
|
|
||||||
PSESSION->sharingData.frameInfoDMA.fmt = format;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void wlrOnBufferDone(void* data, zwlr_screencopy_frame_v1* frame) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
const auto PSTREAM = g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(PSESSION);
|
|
||||||
|
|
||||||
if (!PSTREAM) {
|
|
||||||
Debug::log(TRACE, "[sc] wlrOnBufferDone: no stream");
|
|
||||||
zwlr_screencopy_frame_v1_destroy(frame);
|
|
||||||
PSESSION->sharingData.frameCallback = nullptr;
|
|
||||||
PSESSION->sharingData.status = FRAME_NONE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] pw format {} size {}x{}", (int)PSTREAM->pwVideoInfo.format, PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height);
|
|
||||||
Debug::log(TRACE, "[sc] wlr format {} size {}x{}", (int)PSESSION->sharingData.frameInfoSHM.fmt, PSESSION->sharingData.frameInfoSHM.w, PSESSION->sharingData.frameInfoSHM.h);
|
|
||||||
Debug::log(TRACE, "[sc] wlr format dma {} size {}x{}", (int)PSESSION->sharingData.frameInfoDMA.fmt, PSESSION->sharingData.frameInfoDMA.w, PSESSION->sharingData.frameInfoDMA.h);
|
|
||||||
|
|
||||||
const auto FMT = PSTREAM->isDMA ? PSESSION->sharingData.frameInfoDMA.fmt : PSESSION->sharingData.frameInfoSHM.fmt;
|
|
||||||
if ((PSTREAM->pwVideoInfo.format != pwFromDrmFourcc(FMT) && PSTREAM->pwVideoInfo.format != pwStripAlpha(pwFromDrmFourcc(FMT))) ||
|
|
||||||
(PSTREAM->pwVideoInfo.size.width != PSESSION->sharingData.frameInfoDMA.w || PSTREAM->pwVideoInfo.size.height != PSESSION->sharingData.frameInfoDMA.h)) {
|
|
||||||
Debug::log(LOG, "[sc] Incompatible formats, renegotiate stream");
|
|
||||||
PSESSION->sharingData.status = FRAME_RENEG;
|
|
||||||
zwlr_screencopy_frame_v1_destroy(frame);
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM);
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION);
|
|
||||||
PSESSION->sharingData.status = FRAME_NONE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PSTREAM->currentPWBuffer) {
|
|
||||||
Debug::log(TRACE, "[sc] wlrOnBufferDone: dequeue, no current buffer");
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->dequeue(PSESSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PSTREAM->currentPWBuffer) {
|
|
||||||
zwlr_screencopy_frame_v1_destroy(frame);
|
|
||||||
PSESSION->sharingData.frameCallback = nullptr;
|
|
||||||
Debug::log(LOG, "[screencopy/pipewire] Out of buffers");
|
|
||||||
PSESSION->sharingData.status = FRAME_NONE;
|
|
||||||
if (PSESSION->sharingData.copyRetries++ < MAX_RETRIES) {
|
|
||||||
Debug::log(LOG, "[sc] Retrying screencopy ({}/{})", PSESSION->sharingData.copyRetries, MAX_RETRIES);
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM);
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
zwlr_screencopy_frame_v1_copy_with_damage(frame, PSTREAM->currentPWBuffer->wlBuffer);
|
|
||||||
PSESSION->sharingData.copyRetries = 0;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] wlr frame copied");
|
|
||||||
}
|
|
||||||
|
|
||||||
static const zwlr_screencopy_frame_v1_listener wlrFrameListener = {
|
|
||||||
.buffer = wlrOnBuffer,
|
|
||||||
.flags = wlrOnFlags,
|
|
||||||
.ready = wlrOnReady,
|
|
||||||
.failed = wlrOnFailed,
|
|
||||||
.damage = wlrOnDamage,
|
|
||||||
.linux_dmabuf = wlrOnDmabuf,
|
|
||||||
.buffer_done = wlrOnBufferDone,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void hlOnBuffer(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] hlOnBuffer for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
PSESSION->sharingData.frameInfoSHM.w = width;
|
|
||||||
PSESSION->sharingData.frameInfoSHM.h = height;
|
|
||||||
PSESSION->sharingData.frameInfoSHM.fmt = drmFourccFromSHM((wl_shm_format)format);
|
|
||||||
PSESSION->sharingData.frameInfoSHM.size = stride * height;
|
|
||||||
PSESSION->sharingData.frameInfoSHM.stride = stride;
|
|
||||||
|
|
||||||
// todo: done if ver < 3
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hlOnFlags(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t flags) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] hlOnFlags for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
// todo: maybe check for y invert?
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hlOnReady(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] hlOnReady for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
PSESSION->sharingData.status = FRAME_READY;
|
|
||||||
|
|
||||||
PSESSION->sharingData.tvSec = ((((uint64_t)tv_sec_hi) << 32) + (uint64_t)tv_sec_lo);
|
|
||||||
PSESSION->sharingData.tvNsec = tv_nsec;
|
|
||||||
PSESSION->sharingData.tvTimestampNs = PSESSION->sharingData.tvSec * SPA_NSEC_PER_SEC + PSESSION->sharingData.tvNsec;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] frame timestamp sec: {} nsec: {} combined: {}ns", PSESSION->sharingData.tvSec, PSESSION->sharingData.tvNsec, PSESSION->sharingData.tvTimestampNs);
|
|
||||||
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->enqueue(PSESSION);
|
|
||||||
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION);
|
|
||||||
|
|
||||||
hyprland_toplevel_export_frame_v1_destroy(frame);
|
|
||||||
PSESSION->sharingData.windowFrameCallback = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hlOnFailed(void* data, hyprland_toplevel_export_frame_v1* frame) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] hlOnFailed for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
PSESSION->sharingData.status = FRAME_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hlOnDamage(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] hlOnDamage for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
if (PSESSION->sharingData.damageCount > 3) {
|
|
||||||
PSESSION->sharingData.damage[0] = {0, 0, PSESSION->sharingData.frameInfoDMA.w, PSESSION->sharingData.frameInfoDMA.h};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PSESSION->sharingData.damage[PSESSION->sharingData.damageCount++] = {x, y, width, height};
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] hl damage: {} {} {} {}", x, y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hlOnDmabuf(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] hlOnDmabuf for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
PSESSION->sharingData.frameInfoDMA.w = width;
|
|
||||||
PSESSION->sharingData.frameInfoDMA.h = height;
|
|
||||||
PSESSION->sharingData.frameInfoDMA.fmt = format;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hlOnBufferDone(void* data, hyprland_toplevel_export_frame_v1* frame) {
|
|
||||||
const auto PSESSION = (CScreencopyPortal::SSession*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] hlOnBufferDone for {}", (void*)PSESSION);
|
|
||||||
|
|
||||||
const auto PSTREAM = g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(PSESSION);
|
|
||||||
|
|
||||||
if (!PSTREAM) {
|
|
||||||
Debug::log(TRACE, "[sc] hlOnBufferDone: no stream");
|
|
||||||
hyprland_toplevel_export_frame_v1_destroy(frame);
|
|
||||||
PSESSION->sharingData.windowFrameCallback = nullptr;
|
|
||||||
PSESSION->sharingData.status = FRAME_NONE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] pw format {} size {}x{}", (int)PSTREAM->pwVideoInfo.format, PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height);
|
|
||||||
Debug::log(TRACE, "[sc] hl format {} size {}x{}", (int)PSESSION->sharingData.frameInfoSHM.fmt, PSESSION->sharingData.frameInfoSHM.w, PSESSION->sharingData.frameInfoSHM.h);
|
|
||||||
|
|
||||||
const auto FMT = PSTREAM->isDMA ? PSESSION->sharingData.frameInfoDMA.fmt : PSESSION->sharingData.frameInfoSHM.fmt;
|
|
||||||
if ((PSTREAM->pwVideoInfo.format != pwFromDrmFourcc(FMT) && PSTREAM->pwVideoInfo.format != pwStripAlpha(pwFromDrmFourcc(FMT))) ||
|
|
||||||
(PSTREAM->pwVideoInfo.size.width != PSESSION->sharingData.frameInfoDMA.w || PSTREAM->pwVideoInfo.size.height != PSESSION->sharingData.frameInfoDMA.h)) {
|
|
||||||
Debug::log(LOG, "[sc] Incompatible formats, renegotiate stream");
|
|
||||||
PSESSION->sharingData.status = FRAME_RENEG;
|
|
||||||
hyprland_toplevel_export_frame_v1_destroy(frame);
|
|
||||||
PSESSION->sharingData.windowFrameCallback = nullptr;
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM);
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION);
|
|
||||||
PSESSION->sharingData.status = FRAME_NONE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PSTREAM->currentPWBuffer) {
|
|
||||||
Debug::log(TRACE, "[sc] wlrOnBufferDone: dequeue, no current buffer");
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->dequeue(PSESSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PSTREAM->currentPWBuffer) {
|
|
||||||
hyprland_toplevel_export_frame_v1_destroy(frame);
|
|
||||||
PSESSION->sharingData.windowFrameCallback = nullptr;
|
|
||||||
Debug::log(LOG, "[screencopy/pipewire] Out of buffers");
|
|
||||||
PSESSION->sharingData.status = FRAME_NONE;
|
|
||||||
if (PSESSION->sharingData.copyRetries++ < MAX_RETRIES) {
|
|
||||||
Debug::log(LOG, "[sc] Retrying screencopy ({}/{})", PSESSION->sharingData.copyRetries, MAX_RETRIES);
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM);
|
|
||||||
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hyprland_toplevel_export_frame_v1_copy(frame, PSTREAM->currentPWBuffer->wlBuffer, false);
|
|
||||||
PSESSION->sharingData.copyRetries = 0;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[sc] wlr frame copied");
|
|
||||||
}
|
|
||||||
|
|
||||||
static const hyprland_toplevel_export_frame_v1_listener hyprlandFrameListener = {
|
|
||||||
.buffer = hlOnBuffer,
|
|
||||||
.damage = hlOnDamage,
|
|
||||||
.flags = hlOnFlags,
|
|
||||||
.ready = hlOnReady,
|
|
||||||
.failed = hlOnFailed,
|
|
||||||
.linux_dmabuf = hlOnDmabuf,
|
|
||||||
.buffer_done = hlOnBufferDone,
|
|
||||||
};
|
|
||||||
|
|
||||||
// --------------------------------------------------------- //
|
|
||||||
|
|
||||||
void CScreencopyPortal::onCreateSession(sdbus::MethodCall& call) {
|
void CScreencopyPortal::onCreateSession(sdbus::MethodCall& call) {
|
||||||
sdbus::ObjectPath requestHandle, sessionHandle;
|
sdbus::ObjectPath requestHandle, sessionHandle;
|
||||||
|
|
||||||
|
@ -479,15 +192,13 @@ void CScreencopyPortal::onSelectSources(sdbus::MethodCall& call) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool RESTOREDATAVALID = restoreData.exists &&
|
const bool RESTOREDATAVALID = restoreData.exists && g_pPortalManager->getOutputFromName(restoreData.output);
|
||||||
(g_pPortalManager->m_sHelpers.toplevel->exists((zwlr_foreign_toplevel_handle_v1*)restoreData.windowHandle) || g_pPortalManager->getOutputFromName(restoreData.output));
|
|
||||||
|
|
||||||
SSelectionData SHAREDATA;
|
SSelectionData SHAREDATA;
|
||||||
if (RESTOREDATAVALID) {
|
if (RESTOREDATAVALID) {
|
||||||
Debug::log(LOG, "[screencopy] restore data valid, not prompting");
|
Debug::log(LOG, "[screencopy] restore data valid, not prompting");
|
||||||
|
|
||||||
SHAREDATA.output = restoreData.output;
|
SHAREDATA.output = restoreData.output;
|
||||||
SHAREDATA.windowHandle = (zwlr_foreign_toplevel_handle_v1*)restoreData.windowHandle;
|
|
||||||
SHAREDATA.type = restoreData.windowHandle ? TYPE_WINDOW : TYPE_OUTPUT;
|
SHAREDATA.type = restoreData.windowHandle ? TYPE_WINDOW : TYPE_OUTPUT;
|
||||||
SHAREDATA.allowToken = true; // user allowed token before
|
SHAREDATA.allowToken = true; // user allowed token before
|
||||||
PSESSION->cursorMode = restoreData.withCursor;
|
PSESSION->cursorMode = restoreData.withCursor;
|
||||||
|
@ -641,52 +352,263 @@ void CScreencopyPortal::startSharing(CScreencopyPortal::SSession* pSession) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CScreencopyPortal::startFrameCopy(CScreencopyPortal::SSession* pSession) {
|
void CScreencopyPortal::startFrameCopy(CScreencopyPortal::SSession* pSession) {
|
||||||
const auto POUTPUT = g_pPortalManager->getOutputFromName(pSession->selection.output);
|
pSession->startCopy();
|
||||||
|
|
||||||
if (!pSession->sharingData.active) {
|
Debug::log(TRACE, "[screencopy] frame callbacks initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CScreencopyPortal::SSession::startCopy() {
|
||||||
|
const auto POUTPUT = g_pPortalManager->getOutputFromName(selection.output);
|
||||||
|
|
||||||
|
if (!sharingData.active) {
|
||||||
Debug::log(TRACE, "[sc] startFrameCopy: not copying, inactive session");
|
Debug::log(TRACE, "[sc] startFrameCopy: not copying, inactive session");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!POUTPUT && (pSession->selection.type == TYPE_GEOMETRY || pSession->selection.type == TYPE_OUTPUT)) {
|
if (!POUTPUT && (selection.type == TYPE_GEOMETRY || selection.type == TYPE_OUTPUT)) {
|
||||||
Debug::log(ERR, "[screencopy] Output {} not found??", pSession->selection.output);
|
Debug::log(ERR, "[screencopy] Output {} not found??", selection.output);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((pSession->sharingData.frameCallback && (pSession->selection.type == TYPE_GEOMETRY || pSession->selection.type == TYPE_OUTPUT)) ||
|
if ((sharingData.frameCallback && (selection.type == TYPE_GEOMETRY || selection.type == TYPE_OUTPUT)) || (sharingData.windowFrameCallback && selection.type == TYPE_WINDOW)) {
|
||||||
(pSession->sharingData.windowFrameCallback && pSession->selection.type == TYPE_WINDOW)) {
|
Debug::log(ERR, "[screencopy] tried scheduling on already scheduled cb (type {})", (int)selection.type);
|
||||||
Debug::log(ERR, "[screencopy] tried scheduling on already scheduled cb (type {})", (int)pSession->selection.type);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pSession->selection.type == TYPE_GEOMETRY) {
|
if (selection.type == TYPE_GEOMETRY) {
|
||||||
pSession->sharingData.frameCallback = zwlr_screencopy_manager_v1_capture_output_region(m_sState.screencopy, pSession->cursorMode, POUTPUT->output, pSession->selection.x,
|
sharingData.frameCallback = makeShared<CCZwlrScreencopyFrameV1>(g_pPortalManager->m_sPortals.screencopy->m_sState.screencopy->sendCaptureOutputRegion(
|
||||||
pSession->selection.y, pSession->selection.w, pSession->selection.h);
|
cursorMode, POUTPUT->output->resource(), selection.x, selection.y, selection.w, selection.h));
|
||||||
pSession->sharingData.transform = POUTPUT->transform;
|
sharingData.transform = POUTPUT->transform;
|
||||||
} else if (pSession->selection.type == TYPE_OUTPUT) {
|
} else if (selection.type == TYPE_OUTPUT) {
|
||||||
pSession->sharingData.frameCallback = zwlr_screencopy_manager_v1_capture_output(m_sState.screencopy, pSession->cursorMode, POUTPUT->output);
|
sharingData.frameCallback =
|
||||||
pSession->sharingData.transform = POUTPUT->transform;
|
makeShared<CCZwlrScreencopyFrameV1>(g_pPortalManager->m_sPortals.screencopy->m_sState.screencopy->sendCaptureOutput(cursorMode, POUTPUT->output->resource()));
|
||||||
} else if (pSession->selection.type == TYPE_WINDOW) {
|
sharingData.transform = POUTPUT->transform;
|
||||||
if (!pSession->selection.windowHandle) {
|
} else if (selection.type == TYPE_WINDOW) {
|
||||||
|
if (!selection.windowHandle) {
|
||||||
Debug::log(ERR, "[screencopy] selected invalid window?");
|
Debug::log(ERR, "[screencopy] selected invalid window?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pSession->sharingData.windowFrameCallback =
|
sharingData.windowFrameCallback = makeShared<CCHyprlandToplevelExportFrameV1>(
|
||||||
hyprland_toplevel_export_manager_v1_capture_toplevel_with_wlr_toplevel_handle(m_sState.toplevel, pSession->cursorMode, pSession->selection.windowHandle);
|
g_pPortalManager->m_sPortals.screencopy->m_sState.toplevel->sendCaptureToplevelWithWlrToplevelHandle(cursorMode, selection.windowHandle->resource()));
|
||||||
pSession->sharingData.transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
sharingData.transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
||||||
} else {
|
} else {
|
||||||
Debug::log(ERR, "[screencopy] Unsupported selection {}", (int)pSession->selection.type);
|
Debug::log(ERR, "[screencopy] Unsupported selection {}", (int)selection.type);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pSession->sharingData.status = FRAME_QUEUED;
|
sharingData.status = FRAME_QUEUED;
|
||||||
|
|
||||||
if (pSession->sharingData.frameCallback)
|
initCallbacks();
|
||||||
zwlr_screencopy_frame_v1_add_listener(pSession->sharingData.frameCallback, &wlrFrameListener, pSession);
|
}
|
||||||
else if (pSession->sharingData.windowFrameCallback)
|
|
||||||
hyprland_toplevel_export_frame_v1_add_listener(pSession->sharingData.windowFrameCallback, &hyprlandFrameListener, pSession);
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[screencopy] frame callbacks initialized");
|
void CScreencopyPortal::SSession::initCallbacks() {
|
||||||
|
if (sharingData.frameCallback) {
|
||||||
|
sharingData.frameCallback->setBuffer([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
|
||||||
|
Debug::log(TRACE, "[sc] wlrOnBuffer for {}", (void*)this);
|
||||||
|
|
||||||
|
sharingData.frameInfoSHM.w = width;
|
||||||
|
sharingData.frameInfoSHM.h = height;
|
||||||
|
sharingData.frameInfoSHM.fmt = drmFourccFromSHM((wl_shm_format)format);
|
||||||
|
sharingData.frameInfoSHM.size = stride * height;
|
||||||
|
sharingData.frameInfoSHM.stride = stride;
|
||||||
|
|
||||||
|
// todo: done if ver < 3
|
||||||
|
});
|
||||||
|
sharingData.frameCallback->setReady([this](CCZwlrScreencopyFrameV1* r, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
||||||
|
Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)this);
|
||||||
|
|
||||||
|
sharingData.status = FRAME_READY;
|
||||||
|
|
||||||
|
sharingData.tvSec = ((((uint64_t)tv_sec_hi) << 32) + (uint64_t)tv_sec_lo);
|
||||||
|
sharingData.tvNsec = tv_nsec;
|
||||||
|
sharingData.tvTimestampNs = sharingData.tvSec * SPA_NSEC_PER_SEC + sharingData.tvNsec;
|
||||||
|
|
||||||
|
Debug::log(TRACE, "[sc] frame timestamp sec: {} nsec: {} combined: {}ns", sharingData.tvSec, sharingData.tvNsec, sharingData.tvTimestampNs);
|
||||||
|
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->enqueue(this);
|
||||||
|
|
||||||
|
if (g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(this))
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(this);
|
||||||
|
|
||||||
|
sharingData.frameCallback.reset();
|
||||||
|
});
|
||||||
|
sharingData.frameCallback->setFailed([this](CCZwlrScreencopyFrameV1* r) {
|
||||||
|
Debug::log(TRACE, "[sc] wlrOnFailed for {}", (void*)this);
|
||||||
|
sharingData.status = FRAME_FAILED;
|
||||||
|
});
|
||||||
|
sharingData.frameCallback->setDamage([this](CCZwlrScreencopyFrameV1* r, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
|
||||||
|
Debug::log(TRACE, "[sc] wlrOnDamage for {}", (void*)this);
|
||||||
|
|
||||||
|
if (sharingData.damageCount > 3) {
|
||||||
|
sharingData.damage[0] = {0, 0, sharingData.frameInfoDMA.w, sharingData.frameInfoDMA.h};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sharingData.damage[sharingData.damageCount++] = {x, y, width, height};
|
||||||
|
|
||||||
|
Debug::log(TRACE, "[sc] wlr damage: {} {} {} {}", x, y, width, height);
|
||||||
|
});
|
||||||
|
sharingData.frameCallback->setLinuxDmabuf([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height) {
|
||||||
|
Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)this);
|
||||||
|
|
||||||
|
sharingData.frameInfoDMA.w = width;
|
||||||
|
sharingData.frameInfoDMA.h = height;
|
||||||
|
sharingData.frameInfoDMA.fmt = format;
|
||||||
|
});
|
||||||
|
sharingData.frameCallback->setBufferDone([this](CCZwlrScreencopyFrameV1* r) {
|
||||||
|
Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)this);
|
||||||
|
|
||||||
|
const auto PSTREAM = g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(this);
|
||||||
|
|
||||||
|
if (!PSTREAM) {
|
||||||
|
Debug::log(TRACE, "[sc] wlrOnBufferDone: no stream");
|
||||||
|
sharingData.frameCallback.reset();
|
||||||
|
sharingData.status = FRAME_NONE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug::log(TRACE, "[sc] pw format {} size {}x{}", (int)PSTREAM->pwVideoInfo.format, PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height);
|
||||||
|
Debug::log(TRACE, "[sc] wlr format {} size {}x{}", (int)sharingData.frameInfoSHM.fmt, sharingData.frameInfoSHM.w, sharingData.frameInfoSHM.h);
|
||||||
|
Debug::log(TRACE, "[sc] wlr format dma {} size {}x{}", (int)sharingData.frameInfoDMA.fmt, sharingData.frameInfoDMA.w, sharingData.frameInfoDMA.h);
|
||||||
|
|
||||||
|
const auto FMT = PSTREAM->isDMA ? sharingData.frameInfoDMA.fmt : sharingData.frameInfoSHM.fmt;
|
||||||
|
if ((PSTREAM->pwVideoInfo.format != pwFromDrmFourcc(FMT) && PSTREAM->pwVideoInfo.format != pwStripAlpha(pwFromDrmFourcc(FMT))) ||
|
||||||
|
(PSTREAM->pwVideoInfo.size.width != sharingData.frameInfoDMA.w || PSTREAM->pwVideoInfo.size.height != sharingData.frameInfoDMA.h)) {
|
||||||
|
Debug::log(LOG, "[sc] Incompatible formats, renegotiate stream");
|
||||||
|
sharingData.status = FRAME_RENEG;
|
||||||
|
sharingData.frameCallback.reset();
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM);
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(this);
|
||||||
|
sharingData.status = FRAME_NONE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PSTREAM->currentPWBuffer) {
|
||||||
|
Debug::log(TRACE, "[sc] wlrOnBufferDone: dequeue, no current buffer");
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->dequeue(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PSTREAM->currentPWBuffer) {
|
||||||
|
sharingData.frameCallback.reset();
|
||||||
|
Debug::log(LOG, "[screencopy/pipewire] Out of buffers");
|
||||||
|
sharingData.status = FRAME_NONE;
|
||||||
|
if (sharingData.copyRetries++ < MAX_RETRIES) {
|
||||||
|
Debug::log(LOG, "[sc] Retrying screencopy ({}/{})", sharingData.copyRetries, MAX_RETRIES);
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM);
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(this);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sharingData.frameCallback->sendCopyWithDamage(PSTREAM->currentPWBuffer->wlBuffer->resource());
|
||||||
|
sharingData.copyRetries = 0;
|
||||||
|
|
||||||
|
Debug::log(TRACE, "[sc] wlr frame copied");
|
||||||
|
});
|
||||||
|
} else if (sharingData.windowFrameCallback) {
|
||||||
|
sharingData.windowFrameCallback->setBuffer([this](CCHyprlandToplevelExportFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
|
||||||
|
Debug::log(TRACE, "[sc] hlOnBuffer for {}", (void*)this);
|
||||||
|
|
||||||
|
sharingData.frameInfoSHM.w = width;
|
||||||
|
sharingData.frameInfoSHM.h = height;
|
||||||
|
sharingData.frameInfoSHM.fmt = drmFourccFromSHM((wl_shm_format)format);
|
||||||
|
sharingData.frameInfoSHM.size = stride * height;
|
||||||
|
sharingData.frameInfoSHM.stride = stride;
|
||||||
|
|
||||||
|
// todo: done if ver < 3
|
||||||
|
});
|
||||||
|
sharingData.windowFrameCallback->setReady([this](CCHyprlandToplevelExportFrameV1* r, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
||||||
|
Debug::log(TRACE, "[sc] hlOnReady for {}", (void*)this);
|
||||||
|
|
||||||
|
sharingData.status = FRAME_READY;
|
||||||
|
|
||||||
|
sharingData.tvSec = ((((uint64_t)tv_sec_hi) << 32) + (uint64_t)tv_sec_lo);
|
||||||
|
sharingData.tvNsec = tv_nsec;
|
||||||
|
sharingData.tvTimestampNs = sharingData.tvSec * SPA_NSEC_PER_SEC + sharingData.tvNsec;
|
||||||
|
|
||||||
|
Debug::log(TRACE, "[sc] frame timestamp sec: {} nsec: {} combined: {}ns", sharingData.tvSec, sharingData.tvNsec, sharingData.tvTimestampNs);
|
||||||
|
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->enqueue(this);
|
||||||
|
|
||||||
|
if (g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(this))
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(this);
|
||||||
|
|
||||||
|
sharingData.windowFrameCallback.reset();
|
||||||
|
});
|
||||||
|
sharingData.windowFrameCallback->setFailed([this](CCHyprlandToplevelExportFrameV1* r) {
|
||||||
|
Debug::log(TRACE, "[sc] hlOnFailed for {}", (void*)this);
|
||||||
|
sharingData.status = FRAME_FAILED;
|
||||||
|
});
|
||||||
|
sharingData.windowFrameCallback->setDamage([this](CCHyprlandToplevelExportFrameV1* r, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
|
||||||
|
Debug::log(TRACE, "[sc] hlOnDamage for {}", (void*)this);
|
||||||
|
|
||||||
|
if (sharingData.damageCount > 3) {
|
||||||
|
sharingData.damage[0] = {0, 0, sharingData.frameInfoDMA.w, sharingData.frameInfoDMA.h};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sharingData.damage[sharingData.damageCount++] = {x, y, width, height};
|
||||||
|
|
||||||
|
Debug::log(TRACE, "[sc] hl damage: {} {} {} {}", x, y, width, height);
|
||||||
|
});
|
||||||
|
sharingData.windowFrameCallback->setLinuxDmabuf([this](CCHyprlandToplevelExportFrameV1* r, uint32_t format, uint32_t width, uint32_t height) {
|
||||||
|
Debug::log(TRACE, "[sc] hlOnDmabuf for {}", (void*)this);
|
||||||
|
|
||||||
|
sharingData.frameInfoDMA.w = width;
|
||||||
|
sharingData.frameInfoDMA.h = height;
|
||||||
|
sharingData.frameInfoDMA.fmt = format;
|
||||||
|
});
|
||||||
|
sharingData.windowFrameCallback->setBufferDone([this](CCHyprlandToplevelExportFrameV1* r) {
|
||||||
|
Debug::log(TRACE, "[sc] hlOnBufferDone for {}", (void*)this);
|
||||||
|
|
||||||
|
const auto PSTREAM = g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(this);
|
||||||
|
|
||||||
|
if (!PSTREAM) {
|
||||||
|
Debug::log(TRACE, "[sc] hlOnBufferDone: no stream");
|
||||||
|
sharingData.windowFrameCallback.reset();
|
||||||
|
sharingData.status = FRAME_NONE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug::log(TRACE, "[sc] pw format {} size {}x{}", (int)PSTREAM->pwVideoInfo.format, PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height);
|
||||||
|
Debug::log(TRACE, "[sc] hl format {} size {}x{}", (int)sharingData.frameInfoSHM.fmt, sharingData.frameInfoSHM.w, sharingData.frameInfoSHM.h);
|
||||||
|
Debug::log(TRACE, "[sc] hl format dma {} size {}x{}", (int)sharingData.frameInfoDMA.fmt, sharingData.frameInfoDMA.w, sharingData.frameInfoDMA.h);
|
||||||
|
|
||||||
|
const auto FMT = PSTREAM->isDMA ? sharingData.frameInfoDMA.fmt : sharingData.frameInfoSHM.fmt;
|
||||||
|
if ((PSTREAM->pwVideoInfo.format != pwFromDrmFourcc(FMT) && PSTREAM->pwVideoInfo.format != pwStripAlpha(pwFromDrmFourcc(FMT))) ||
|
||||||
|
(PSTREAM->pwVideoInfo.size.width != sharingData.frameInfoDMA.w || PSTREAM->pwVideoInfo.size.height != sharingData.frameInfoDMA.h)) {
|
||||||
|
Debug::log(LOG, "[sc] Incompatible formats, renegotiate stream");
|
||||||
|
sharingData.status = FRAME_RENEG;
|
||||||
|
sharingData.windowFrameCallback.reset();
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM);
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(this);
|
||||||
|
sharingData.status = FRAME_NONE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PSTREAM->currentPWBuffer) {
|
||||||
|
Debug::log(TRACE, "[sc] hlOnBufferDone: dequeue, no current buffer");
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->dequeue(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PSTREAM->currentPWBuffer) {
|
||||||
|
sharingData.windowFrameCallback.reset();
|
||||||
|
Debug::log(LOG, "[screencopy/pipewire] Out of buffers");
|
||||||
|
sharingData.status = FRAME_NONE;
|
||||||
|
if (sharingData.copyRetries++ < MAX_RETRIES) {
|
||||||
|
Debug::log(LOG, "[sc] Retrying screencopy ({}/{})", sharingData.copyRetries, MAX_RETRIES);
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM);
|
||||||
|
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(this);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sharingData.windowFrameCallback->sendCopy(PSTREAM->currentPWBuffer->wlBuffer->resource(), false);
|
||||||
|
sharingData.copyRetries = 0;
|
||||||
|
|
||||||
|
Debug::log(TRACE, "[sc] hl frame copied");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CScreencopyPortal::queueNextShareFrame(CScreencopyPortal::SSession* pSession) {
|
void CScreencopyPortal::queueNextShareFrame(CScreencopyPortal::SSession* pSession) {
|
||||||
|
@ -719,7 +641,7 @@ CScreencopyPortal::SSession* CScreencopyPortal::getSession(sdbus::ObjectPath& pa
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
CScreencopyPortal::CScreencopyPortal(zwlr_screencopy_manager_v1* mgr) {
|
CScreencopyPortal::CScreencopyPortal(SP<CCZwlrScreencopyManagerV1> mgr) {
|
||||||
m_pObject = sdbus::createObject(*g_pPortalManager->getConnection(), OBJECT_PATH);
|
m_pObject = sdbus::createObject(*g_pPortalManager->getConnection(), OBJECT_PATH);
|
||||||
|
|
||||||
m_pObject->registerMethod(INTERFACE_NAME, "CreateSession", "oosa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onCreateSession(c); });
|
m_pObject->registerMethod(INTERFACE_NAME, "CreateSession", "oosa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onCreateSession(c); });
|
||||||
|
@ -737,8 +659,8 @@ CScreencopyPortal::CScreencopyPortal(zwlr_screencopy_manager_v1* mgr) {
|
||||||
Debug::log(LOG, "[screencopy] init successful");
|
Debug::log(LOG, "[screencopy] init successful");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CScreencopyPortal::appendToplevelExport(void* proto) {
|
void CScreencopyPortal::appendToplevelExport(SP<CCHyprlandToplevelExportManagerV1> proto) {
|
||||||
m_sState.toplevel = (hyprland_toplevel_export_manager_v1*)proto;
|
m_sState.toplevel = proto;
|
||||||
|
|
||||||
Debug::log(LOG, "[screencopy] Registered for toplevel export");
|
Debug::log(LOG, "[screencopy] Registered for toplevel export");
|
||||||
}
|
}
|
||||||
|
@ -768,10 +690,8 @@ CPipewireConnection::CPipewireConnection() {
|
||||||
void CPipewireConnection::removeSessionFrameCallbacks(CScreencopyPortal::SSession* pSession) {
|
void CPipewireConnection::removeSessionFrameCallbacks(CScreencopyPortal::SSession* pSession) {
|
||||||
Debug::log(TRACE, "[pipewire] removeSessionFrameCallbacks called");
|
Debug::log(TRACE, "[pipewire] removeSessionFrameCallbacks called");
|
||||||
|
|
||||||
if (pSession->sharingData.frameCallback)
|
pSession->sharingData.frameCallback.reset();
|
||||||
zwlr_screencopy_frame_v1_destroy(pSession->sharingData.frameCallback);
|
pSession->sharingData.windowFrameCallback.reset();
|
||||||
if (pSession->sharingData.windowFrameCallback)
|
|
||||||
hyprland_toplevel_export_frame_v1_destroy(pSession->sharingData.windowFrameCallback);
|
|
||||||
|
|
||||||
pSession->sharingData.windowFrameCallback = nullptr;
|
pSession->sharingData.windowFrameCallback = nullptr;
|
||||||
pSession->sharingData.frameCallback = nullptr;
|
pSession->sharingData.frameCallback = nullptr;
|
||||||
|
@ -1002,7 +922,7 @@ static void pwStreamRemoveBuffer(void* data, pw_buffer* buffer) {
|
||||||
if (PBUFFER->isDMABUF)
|
if (PBUFFER->isDMABUF)
|
||||||
gbm_bo_destroy(PBUFFER->bo);
|
gbm_bo_destroy(PBUFFER->bo);
|
||||||
|
|
||||||
wl_buffer_destroy(PBUFFER->wlBuffer);
|
PBUFFER->wlBuffer.reset();
|
||||||
for (int plane = 0; plane < PBUFFER->planeCount; plane++) {
|
for (int plane = 0; plane < PBUFFER->planeCount; plane++) {
|
||||||
close(PBUFFER->fd[plane]);
|
close(PBUFFER->fd[plane]);
|
||||||
}
|
}
|
||||||
|
@ -1322,7 +1242,7 @@ std::unique_ptr<SBuffer> CPipewireConnection::createBuffer(CPipewireConnection::
|
||||||
|
|
||||||
pBuffer->planeCount = gbm_bo_get_plane_count(pBuffer->bo);
|
pBuffer->planeCount = gbm_bo_get_plane_count(pBuffer->bo);
|
||||||
|
|
||||||
zwp_linux_buffer_params_v1* params = zwp_linux_dmabuf_v1_create_params((zwp_linux_dmabuf_v1*)g_pPortalManager->m_sWaylandConnection.linuxDmabuf);
|
auto params = makeShared<CCZwpLinuxBufferParamsV1>(g_pPortalManager->m_sWaylandConnection.linuxDmabuf->sendCreateParams());
|
||||||
if (!params) {
|
if (!params) {
|
||||||
Debug::log(ERR, "[pw] zwp_linux_dmabuf_v1_create_params failed");
|
Debug::log(ERR, "[pw] zwp_linux_dmabuf_v1_create_params failed");
|
||||||
gbm_bo_destroy(pBuffer->bo);
|
gbm_bo_destroy(pBuffer->bo);
|
||||||
|
@ -1338,7 +1258,7 @@ std::unique_ptr<SBuffer> CPipewireConnection::createBuffer(CPipewireConnection::
|
||||||
|
|
||||||
if (pBuffer->fd[plane] < 0) {
|
if (pBuffer->fd[plane] < 0) {
|
||||||
Debug::log(ERR, "[pw] gbm_bo_get_fd_for_plane failed");
|
Debug::log(ERR, "[pw] gbm_bo_get_fd_for_plane failed");
|
||||||
zwp_linux_buffer_params_v1_destroy(params);
|
params.reset();
|
||||||
gbm_bo_destroy(pBuffer->bo);
|
gbm_bo_destroy(pBuffer->bo);
|
||||||
for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) {
|
for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) {
|
||||||
close(pBuffer->fd[plane_tmp]);
|
close(pBuffer->fd[plane_tmp]);
|
||||||
|
@ -1346,11 +1266,11 @@ std::unique_ptr<SBuffer> CPipewireConnection::createBuffer(CPipewireConnection::
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
zwp_linux_buffer_params_v1_add(params, pBuffer->fd[plane], plane, pBuffer->offset[plane], pBuffer->stride[plane], mod >> 32, mod & 0xffffffff);
|
params->sendAdd(pBuffer->fd[plane], plane, pBuffer->offset[plane], pBuffer->stride[plane], mod >> 32, mod & 0xffffffff);
|
||||||
}
|
}
|
||||||
|
|
||||||
pBuffer->wlBuffer = zwp_linux_buffer_params_v1_create_immed(params, pBuffer->w, pBuffer->h, pBuffer->fmt, /* flags */ 0);
|
pBuffer->wlBuffer = makeShared<CCWlBuffer>(params->sendCreateImmed(pBuffer->w, pBuffer->h, pBuffer->fmt, /* flags */ (zwpLinuxBufferParamsV1Flags)0));
|
||||||
zwp_linux_buffer_params_v1_destroy(params);
|
params.reset();
|
||||||
|
|
||||||
if (!pBuffer->wlBuffer) {
|
if (!pBuffer->wlBuffer) {
|
||||||
Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed");
|
Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <protocols/wlr-screencopy-unstable-v1-protocol.h>
|
#include "wlr-screencopy-unstable-v1.hpp"
|
||||||
#include <protocols/hyprland-toplevel-export-v1-protocol.h>
|
#include "hyprland-toplevel-export-v1.hpp"
|
||||||
#include <sdbus-c++/sdbus-c++.h>
|
#include <sdbus-c++/sdbus-c++.h>
|
||||||
#include "../shared/ScreencopyShared.hpp"
|
#include "../shared/ScreencopyShared.hpp"
|
||||||
#include <gbm.h>
|
#include <gbm.h>
|
||||||
|
@ -43,7 +43,7 @@ struct SBuffer {
|
||||||
|
|
||||||
gbm_bo* bo = nullptr;
|
gbm_bo* bo = nullptr;
|
||||||
|
|
||||||
wl_buffer* wlBuffer = nullptr;
|
SP<CCWlBuffer> wlBuffer = nullptr;
|
||||||
pw_buffer* pwBuffer = nullptr;
|
pw_buffer* pwBuffer = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,9 +51,9 @@ class CPipewireConnection;
|
||||||
|
|
||||||
class CScreencopyPortal {
|
class CScreencopyPortal {
|
||||||
public:
|
public:
|
||||||
CScreencopyPortal(zwlr_screencopy_manager_v1*);
|
CScreencopyPortal(SP<CCZwlrScreencopyManagerV1>);
|
||||||
|
|
||||||
void appendToplevelExport(void*);
|
void appendToplevelExport(SP<CCHyprlandToplevelExportManagerV1>);
|
||||||
|
|
||||||
void onCreateSession(sdbus::MethodCall& call);
|
void onCreateSession(sdbus::MethodCall& call);
|
||||||
void onSelectSources(sdbus::MethodCall& call);
|
void onSelectSources(sdbus::MethodCall& call);
|
||||||
|
@ -69,10 +69,13 @@ class CScreencopyPortal {
|
||||||
std::unique_ptr<SDBusSession> session;
|
std::unique_ptr<SDBusSession> session;
|
||||||
SSelectionData selection;
|
SSelectionData selection;
|
||||||
|
|
||||||
|
void startCopy();
|
||||||
|
void initCallbacks();
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool active = false;
|
bool active = false;
|
||||||
zwlr_screencopy_frame_v1* frameCallback = nullptr;
|
SP<CCZwlrScreencopyFrameV1> frameCallback = nullptr;
|
||||||
hyprland_toplevel_export_frame_v1* windowFrameCallback = nullptr;
|
SP<CCHyprlandToplevelExportFrameV1> windowFrameCallback = nullptr;
|
||||||
frameStatus status = FRAME_NONE;
|
frameStatus status = FRAME_NONE;
|
||||||
uint64_t tvSec = 0;
|
uint64_t tvSec = 0;
|
||||||
uint32_t tvNsec = 0;
|
uint32_t tvNsec = 0;
|
||||||
|
@ -116,12 +119,14 @@ class CScreencopyPortal {
|
||||||
void startSharing(SSession* pSession);
|
void startSharing(SSession* pSession);
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
zwlr_screencopy_manager_v1* screencopy = nullptr;
|
SP<CCZwlrScreencopyManagerV1> screencopy = nullptr;
|
||||||
hyprland_toplevel_export_manager_v1* toplevel = nullptr;
|
SP<CCHyprlandToplevelExportManagerV1> toplevel = nullptr;
|
||||||
} m_sState;
|
} m_sState;
|
||||||
|
|
||||||
const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.ScreenCast";
|
const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.ScreenCast";
|
||||||
const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop";
|
const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop";
|
||||||
|
|
||||||
|
friend struct SSession;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CPipewireConnection {
|
class CPipewireConnection {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sdbus-c++/sdbus-c++.h>
|
#include <sdbus-c++/sdbus-c++.h>
|
||||||
#include <protocols/wlr-screencopy-unstable-v1-protocol.h>
|
|
||||||
|
|
||||||
class CScreenshotPortal {
|
class CScreenshotPortal {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -320,15 +320,14 @@ int anonymous_shm_open() {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
wl_buffer* import_wl_shm_buffer(int fd, wl_shm_format fmt, int width, int height, int stride) {
|
SP<CCWlBuffer> import_wl_shm_buffer(int fd, wl_shm_format fmt, int width, int height, int stride) {
|
||||||
int size = stride * height;
|
int size = stride * height;
|
||||||
|
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return NULL;
|
return nullptr;
|
||||||
|
|
||||||
wl_shm_pool* pool = wl_shm_create_pool(g_pPortalManager->m_sWaylandConnection.shm, fd, size);
|
auto pool = makeShared<CCWlShmPool>(g_pPortalManager->m_sWaylandConnection.shm->sendCreatePool(fd, size));
|
||||||
wl_buffer* buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, fmt);
|
auto buf = makeShared<CCWlBuffer>(pool->sendCreateBuffer(0, width, height, stride, fmt));
|
||||||
wl_shm_pool_destroy(pool);
|
|
||||||
|
|
||||||
return buffer;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,9 @@ extern "C" {
|
||||||
#include <spa/param/video/format-utils.h>
|
#include <spa/param/video/format-utils.h>
|
||||||
#include <spa/pod/dynamic.h>
|
#include <spa/pod/dynamic.h>
|
||||||
}
|
}
|
||||||
#include <wayland-client.h>
|
#include "wayland.hpp"
|
||||||
|
#include "wlr-foreign-toplevel-management-unstable-v1.hpp"
|
||||||
|
#include "../includes.hpp"
|
||||||
|
|
||||||
#define XDPH_PWR_BUFFERS 4
|
#define XDPH_PWR_BUFFERS 4
|
||||||
#define XDPH_PWR_BUFFERS_MIN 2
|
#define XDPH_PWR_BUFFERS_MIN 2
|
||||||
|
@ -31,7 +33,7 @@ struct zwlr_foreign_toplevel_handle_v1;
|
||||||
struct SSelectionData {
|
struct SSelectionData {
|
||||||
eSelectionType type = TYPE_INVALID;
|
eSelectionType type = TYPE_INVALID;
|
||||||
std::string output;
|
std::string output;
|
||||||
zwlr_foreign_toplevel_handle_v1* windowHandle = nullptr;
|
SP<CCZwlrForeignToplevelHandleV1> windowHandle = nullptr;
|
||||||
uint32_t x = 0, y = 0, w = 0, h = 0; // for TYPE_GEOMETRY
|
uint32_t x = 0, y = 0, w = 0, h = 0; // for TYPE_GEOMETRY
|
||||||
bool allowToken = false;
|
bool allowToken = false;
|
||||||
};
|
};
|
||||||
|
@ -48,4 +50,4 @@ spa_pod* build_format(spa_pod_builder* b, spa_video_format format, uint3
|
||||||
spa_pod* fixate_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifier);
|
spa_pod* fixate_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifier);
|
||||||
spa_pod* build_buffer(spa_pod_builder* b, uint32_t blocks, uint32_t size, uint32_t stride, uint32_t datatype);
|
spa_pod* build_buffer(spa_pod_builder* b, uint32_t blocks, uint32_t size, uint32_t stride, uint32_t datatype);
|
||||||
int anonymous_shm_open();
|
int anonymous_shm_open();
|
||||||
wl_buffer* import_wl_shm_buffer(int fd, wl_shm_format fmt, int width, int height, int stride);
|
SP<CCWlBuffer> import_wl_shm_buffer(int fd, wl_shm_format fmt, int width, int height, int stride);
|
|
@ -1,5 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "../includes.hpp"
|
||||||
|
|
||||||
#include <sdbus-c++/sdbus-c++.h>
|
#include <sdbus-c++/sdbus-c++.h>
|
||||||
|
|
||||||
struct SDBusSession {
|
struct SDBusSession {
|
||||||
|
|
|
@ -2,97 +2,28 @@
|
||||||
#include "../helpers/Log.hpp"
|
#include "../helpers/Log.hpp"
|
||||||
#include "../core/PortalManager.hpp"
|
#include "../core/PortalManager.hpp"
|
||||||
|
|
||||||
static void toplevelTitle(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, const char* title) {
|
SToplevelHandle::SToplevelHandle(SP<CCZwlrForeignToplevelHandleV1> handle_) : handle(handle_) {
|
||||||
const auto PTL = (SToplevelHandle*)data;
|
handle->setTitle([this](CCZwlrForeignToplevelHandleV1* r, const char* title) {
|
||||||
|
|
||||||
if (title)
|
if (title)
|
||||||
PTL->windowTitle = title;
|
windowTitle = title;
|
||||||
|
|
||||||
Debug::log(TRACE, "[toplevel] toplevel at {} set title to {}", data, PTL->windowTitle);
|
Debug::log(TRACE, "[toplevel] toplevel at {} set title to {}", (void*)this, windowTitle);
|
||||||
|
});
|
||||||
|
handle->setAppId([this](CCZwlrForeignToplevelHandleV1* r, const char* class_) {
|
||||||
|
if (class_)
|
||||||
|
windowClass = class_;
|
||||||
|
|
||||||
|
Debug::log(TRACE, "[toplevel] toplevel at {} set class to {}", (void*)this, windowClass);
|
||||||
|
});
|
||||||
|
handle->setClosed([this](CCZwlrForeignToplevelHandleV1* r) {
|
||||||
|
Debug::log(TRACE, "[toplevel] toplevel at {} closed", (void*)this);
|
||||||
|
|
||||||
|
std::erase_if(g_pPortalManager->m_sHelpers.toplevel->m_vToplevels, [&](const auto& e) { return e.get() == this; });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void toplevelAppid(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, const char* app_id) {
|
CToplevelManager::CToplevelManager(uint32_t name, uint32_t version) {
|
||||||
const auto PTL = (SToplevelHandle*)data;
|
m_sWaylandConnection = {name, version};
|
||||||
|
|
||||||
if (app_id)
|
|
||||||
PTL->windowClass = app_id;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[toplevel] toplevel at {} set class to {}", data, PTL->windowClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void toplevelEnterOutput(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, wl_output* output) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void toplevelLeaveOutput(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, wl_output* output) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void toplevelState(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, wl_array* state) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void toplevelDone(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void toplevelClosed(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1) {
|
|
||||||
const auto PTL = (SToplevelHandle*)data;
|
|
||||||
|
|
||||||
std::erase_if(PTL->mgr->m_vToplevels, [&](const auto& e) { return e.get() == PTL; });
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[toplevel] toplevel at {} closed", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void toplevelParent(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, struct zwlr_foreign_toplevel_handle_v1* parent) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const zwlr_foreign_toplevel_handle_v1_listener toplevelListener = {
|
|
||||||
.title = toplevelTitle,
|
|
||||||
.app_id = toplevelAppid,
|
|
||||||
.output_enter = toplevelEnterOutput,
|
|
||||||
.output_leave = toplevelLeaveOutput,
|
|
||||||
.state = toplevelState,
|
|
||||||
.done = toplevelDone,
|
|
||||||
.closed = toplevelClosed,
|
|
||||||
.parent = toplevelParent,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void managerToplevel(void* data, zwlr_foreign_toplevel_manager_v1* mgr, zwlr_foreign_toplevel_handle_v1* toplevel) {
|
|
||||||
const auto PMGR = (CToplevelManager*)data;
|
|
||||||
|
|
||||||
Debug::log(TRACE, "[toplevel] New toplevel at {}", (void*)toplevel);
|
|
||||||
|
|
||||||
const auto PTL = PMGR->m_vToplevels.emplace_back(std::make_unique<SToplevelHandle>("?", "?", toplevel, PMGR)).get();
|
|
||||||
|
|
||||||
zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &toplevelListener, PTL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void managerFinished(void* data, zwlr_foreign_toplevel_manager_v1* mgr) {
|
|
||||||
const auto PMGR = (CToplevelManager*)data;
|
|
||||||
|
|
||||||
Debug::log(ERR, "[toplevel] Compositor sent .finished???");
|
|
||||||
|
|
||||||
PMGR->m_vToplevels.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const zwlr_foreign_toplevel_manager_v1_listener managerListener = {
|
|
||||||
.toplevel = managerToplevel,
|
|
||||||
.finished = managerFinished,
|
|
||||||
};
|
|
||||||
|
|
||||||
bool CToplevelManager::exists(zwlr_foreign_toplevel_handle_v1* handle) {
|
|
||||||
for (auto& h : m_vToplevels) {
|
|
||||||
if (h->handle == handle)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CToplevelManager::CToplevelManager(wl_registry* registry, uint32_t name, uint32_t version) {
|
|
||||||
m_sWaylandConnection = {registry, name, version};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CToplevelManager::activate() {
|
void CToplevelManager::activate() {
|
||||||
|
@ -103,9 +34,16 @@ void CToplevelManager::activate() {
|
||||||
if (m_pManager || m_iActivateLocks < 1)
|
if (m_pManager || m_iActivateLocks < 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_pManager = (zwlr_foreign_toplevel_manager_v1*)wl_registry_bind(m_sWaylandConnection.registry, m_sWaylandConnection.name, &zwlr_foreign_toplevel_manager_v1_interface,
|
m_pManager = makeShared<CCZwlrForeignToplevelManagerV1>(wl_registry_bind((wl_registry*)g_pPortalManager->m_sWaylandConnection.registry->resource(), m_sWaylandConnection.name,
|
||||||
m_sWaylandConnection.version);
|
&zwlr_foreign_toplevel_manager_v1_interface, m_sWaylandConnection.version));
|
||||||
zwlr_foreign_toplevel_manager_v1_add_listener(m_pManager, &managerListener, this);
|
|
||||||
|
m_pManager->setToplevel([this](CCZwlrForeignToplevelManagerV1* r, wl_proxy* newHandle) {
|
||||||
|
Debug::log(TRACE, "[toplevel] New toplevel at {}", (void*)newHandle);
|
||||||
|
|
||||||
|
m_vToplevels.emplace_back(std::make_unique<SToplevelHandle>(makeShared<CCZwlrForeignToplevelHandleV1>(newHandle)));
|
||||||
|
});
|
||||||
|
m_pManager->setFinished([this](CCZwlrForeignToplevelManagerV1* r) { m_vToplevels.clear(); });
|
||||||
|
|
||||||
wl_display_roundtrip(g_pPortalManager->m_sWaylandConnection.display);
|
wl_display_roundtrip(g_pPortalManager->m_sWaylandConnection.display);
|
||||||
|
|
||||||
Debug::log(LOG, "[toplevel] Activated, bound to {:x}, toplevels: {}", (uintptr_t)m_pManager, m_vToplevels.size());
|
Debug::log(LOG, "[toplevel] Activated, bound to {:x}, toplevels: {}", (uintptr_t)m_pManager, m_vToplevels.size());
|
||||||
|
@ -119,8 +57,7 @@ void CToplevelManager::deactivate() {
|
||||||
if (!m_pManager || m_iActivateLocks > 0)
|
if (!m_pManager || m_iActivateLocks > 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
zwlr_foreign_toplevel_manager_v1_destroy(m_pManager);
|
m_pManager.reset();
|
||||||
m_pManager = nullptr;
|
|
||||||
m_vToplevels.clear();
|
m_vToplevels.clear();
|
||||||
|
|
||||||
Debug::log(LOG, "[toplevel] unbound manager");
|
Debug::log(LOG, "[toplevel] unbound manager");
|
||||||
|
|
|
@ -1,38 +1,37 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <wayland-client.h>
|
#include "wayland.hpp"
|
||||||
#include <protocols/wlr-foreign-toplevel-management-unstable-v1-protocol.h>
|
#include "wlr-foreign-toplevel-management-unstable-v1.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include "../includes.hpp"
|
||||||
|
|
||||||
class CToplevelManager;
|
class CToplevelManager;
|
||||||
|
|
||||||
struct SToplevelHandle {
|
struct SToplevelHandle {
|
||||||
|
SToplevelHandle(SP<CCZwlrForeignToplevelHandleV1> handle);
|
||||||
std::string windowClass;
|
std::string windowClass;
|
||||||
std::string windowTitle;
|
std::string windowTitle;
|
||||||
zwlr_foreign_toplevel_handle_v1* handle = nullptr;
|
SP<CCZwlrForeignToplevelHandleV1> handle = nullptr;
|
||||||
CToplevelManager* mgr = nullptr;
|
CToplevelManager* mgr = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CToplevelManager {
|
class CToplevelManager {
|
||||||
public:
|
public:
|
||||||
CToplevelManager(wl_registry* registry, uint32_t name, uint32_t version);
|
CToplevelManager(uint32_t name, uint32_t version);
|
||||||
|
|
||||||
void activate();
|
void activate();
|
||||||
void deactivate();
|
void deactivate();
|
||||||
|
|
||||||
bool exists(zwlr_foreign_toplevel_handle_v1* handle);
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<SToplevelHandle>> m_vToplevels;
|
std::vector<std::unique_ptr<SToplevelHandle>> m_vToplevels;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
zwlr_foreign_toplevel_manager_v1* m_pManager = nullptr;
|
SP<CCZwlrForeignToplevelManagerV1> m_pManager = nullptr;
|
||||||
|
|
||||||
int64_t m_iActivateLocks = 0;
|
int64_t m_iActivateLocks = 0;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
wl_registry* registry = nullptr;
|
|
||||||
uint32_t name = 0;
|
uint32_t name = 0;
|
||||||
uint32_t version = 0;
|
uint32_t version = 0;
|
||||||
} m_sWaylandConnection;
|
} m_sWaylandConnection;
|
||||||
|
|
Loading…
Reference in a new issue