fix: add wayland dev headers and scanner for pywayland build on NixOS

This commit is contained in:
Celes Renata
2026-05-08 15:55:01 -07:00
commit f143bce273
740 changed files with 86018 additions and 0 deletions
@@ -0,0 +1,190 @@
import qs
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
Button {
id: root
property var imageData
property var rowHeight
property bool manualDownload: true
property string previewDownloadPath
property string downloadPath
property string nsfwPath
property string fileName: decodeURIComponent((imageData.file_url).substring((imageData.file_url).lastIndexOf('/') + 1))
property string filePath: `${root.previewDownloadPath}/${root.fileName}`
property int maxTagStringLineLength: 50
property real imageRadius: Appearance.rounding.small
property bool showActions: false
Process {
id: downloadProcess
running: false
command: ["bash", "-c", `[ -f ${root.filePath} ] || curl -sSL '${root.imageData.preview_url ?? root.imageData.sample_url}' -o '${root.filePath}'`]
onExited: (exitCode, exitStatus) => {
imageObject.source = `${previewDownloadPath}/${root.fileName}`
}
}
Component.onCompleted: {
if (root.manualDownload) {
downloadProcess.running = true
}
}
StyledToolTip {
content: `${StringUtils.wordWrap(root.imageData.tags, root.maxTagStringLineLength)}`
}
padding: 0
implicitWidth: root.rowHeight * modelData.aspect_ratio
implicitHeight: root.rowHeight
background: Rectangle {
implicitWidth: root.rowHeight * modelData.aspect_ratio
implicitHeight: root.rowHeight
radius: imageRadius
color: Appearance.colors.colLayer2
}
contentItem: Item {
anchors.fill: parent
Image {
id: imageObject
anchors.fill: parent
width: root.rowHeight * modelData.aspect_ratio
height: root.rowHeight
visible: opacity > 0
opacity: status === Image.Ready ? 1 : 0
fillMode: Image.PreserveAspectFit
source: modelData.preview_url
sourceSize.width: root.rowHeight * modelData.aspect_ratio
sourceSize.height: root.rowHeight
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: root.rowHeight * modelData.aspect_ratio
height: root.rowHeight
radius: imageRadius
}
}
Behavior on opacity {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
}
RippleButton {
id: menuButton
anchors.top: parent.top
anchors.right: parent.right
property real buttonSize: 30
anchors.margins: Math.max(root.imageRadius - buttonSize / 2, 8)
implicitHeight: buttonSize
implicitWidth: buttonSize
buttonRadius: Appearance.rounding.full
colBackground: ColorUtils.transparentize(Appearance.m3colors.m3surface, 0.3)
colBackgroundHover: ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.8), 0.2)
colRipple: ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.6), 0.1)
contentItem: MaterialSymbol {
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.large
color: Appearance.m3colors.m3onSurface
text: "more_vert"
}
onClicked: {
root.showActions = !root.showActions
}
}
Loader {
id: contextMenuLoader
active: root.showActions
anchors.top: menuButton.bottom
anchors.right: parent.right
anchors.margins: 8
sourceComponent: Item {
width: contextMenu.width
height: contextMenu.height
StyledRectangularShadow {
target: contextMenu
}
Rectangle {
id: contextMenu
anchors.centerIn: parent
opacity: root.showActions ? 1 : 0
visible: opacity > 0
radius: Appearance.rounding.small
color: Appearance.colors.colSurfaceContainer
implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2
implicitWidth: contextMenuColumnLayout.implicitWidth
Behavior on opacity {
NumberAnimation {
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
ColumnLayout {
id: contextMenuColumnLayout
anchors.centerIn: parent
spacing: 0
MenuButton {
id: openFileLinkButton
Layout.fillWidth: true
buttonText: Translation.tr("Open file link")
onClicked: {
root.showActions = false
Hyprland.dispatch("keyword cursor:no_warps true")
Qt.openUrlExternally(root.imageData.file_url)
Hyprland.dispatch("keyword cursor:no_warps false")
}
}
MenuButton {
id: sourceButton
visible: root.imageData.source && root.imageData.source.length > 0
Layout.fillWidth: true
buttonText: Translation.tr("Go to source (%1)").arg(StringUtils.getDomain(root.imageData.source))
enabled: root.imageData.source && root.imageData.source.length > 0
onClicked: {
root.showActions = false
Hyprland.dispatch("keyword cursor:no_warps true")
Qt.openUrlExternally(root.imageData.source)
Hyprland.dispatch("keyword cursor:no_warps false")
}
}
MenuButton {
id: downloadButton
Layout.fillWidth: true
buttonText: Translation.tr("Download")
onClicked: {
root.showActions = false
Quickshell.execDetached(["bash", "-c",
`curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send '${Translation.tr("Download complete")}' '${root.downloadPath}/${root.fileName}' -a 'Shell'`
])
}
}
}
}
}
}
}
}
@@ -0,0 +1,294 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import "../"
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Qt5Compat.GraphicalEffects
Rectangle {
id: root
property var responseData
property var tagInputField
property string previewDownloadPath
property string downloadPath
property string nsfwPath
property real availableWidth: parent.width
property real rowTooShortThreshold: 190
property real imageSpacing: 5
property real responsePadding: 5
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: columnLayout.implicitHeight + root.responsePadding * 2
Component.onCompleted: {
// Break property bind to prevent aggressive updates
availableWidth = parent.width
}
Connections {
target: parent
function onWidthChanged() {
updateWidthTimer.restart()
}
}
Timer {
id: updateWidthTimer
interval: 100
onTriggered: {
availableWidth = parent.width
}
}
radius: Appearance.rounding.normal
color: Appearance.colors.colLayer1
ColumnLayout {
id: columnLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: responsePadding
spacing: root.imageSpacing
RowLayout { // Header
Rectangle { // Provider name
id: providerNameWrapper
color: Appearance.colors.colSecondaryContainer
radius: Appearance.rounding.small
implicitWidth: providerName.implicitWidth + 10 * 2
implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30)
Layout.alignment: Qt.AlignVCenter
StyledText {
id: providerName
anchors.centerIn: parent
font.pixelSize: Appearance.font.pixelSize.large
color: Appearance.m3colors.m3onSecondaryContainer
text: Booru.providers[root.responseData.provider].name
}
}
Item { Layout.fillWidth: true }
Item { // Page number
visible: root.responseData.page != "" && root.responseData.page > 0
implicitWidth: Math.max(pageNumber.implicitWidth + 10 * 2, 30)
implicitHeight: pageNumber.implicitHeight + 5 * 2
Layout.alignment: Qt.AlignVCenter
StyledText {
id: pageNumber
anchors.centerIn: parent
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.colors.colOnLayer2
// text: `Page ${root.responseData.page}`
text: Translation.tr("Page %1").arg(root.responseData.page)
}
}
}
StyledFlickable { // Tag strip
id: tagsFlickable
visible: root.responseData.tags.length > 0
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: {
return true
}
implicitHeight: tagRowLayout.implicitHeight
// height: tagRowLayout.implicitHeight
contentWidth: tagRowLayout.implicitWidth
clip: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: tagsFlickable.width
height: tagsFlickable.height
radius: Appearance.rounding.small
}
}
Behavior on height {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
RowLayout {
id: tagRowLayout
Layout.alignment: Qt.AlignBottom
Repeater {
id: tagRepeater
model: root.responseData.tags
ApiCommandButton {
Layout.fillWidth: false
buttonText: modelData
onClicked: {
if(root.tagInputField.text.length !== 0) root.tagInputField.text += " "
root.tagInputField.text += modelData
}
}
}
}
}
StyledText { // Message
id: messageText
Layout.fillWidth: true
visible: root.responseData.message.length > 0
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnLayer1
text: root.responseData.message
wrapMode: Text.WordWrap
Layout.margins: responsePadding
textFormat: Text.MarkdownText
onLinkActivated: (link) => {
Qt.openUrlExternally(link)
GlobalStates.sidebarLeftOpen = false
}
PointingHandLinkHover {}
}
Repeater {
model: ScriptModel {
values: {
// Greedily add images to a row as long as rowHeight >= rowTooShortThreshold
let i = 0;
let rows = [];
const responseList = root.responseData.images;
const minRowHeight = rowTooShortThreshold;
const availableImageWidth = availableWidth - root.imageSpacing - (responsePadding * 2);
while (i < responseList.length) {
let row = {
height: 0,
images: [],
};
let j = i;
let combinedAspect = 0;
let rowHeight = 0;
// Try to add as many images as possible without going below minRowHeight
while (j < responseList.length) {
combinedAspect += responseList[j].aspect_ratio;
// Subtract imageSpacing for each gap between images in the row
let imagesInRow = j - i + 1;
let totalSpacing = root.imageSpacing * (imagesInRow - 1);
let rowAvailableWidth = availableImageWidth - totalSpacing;
rowHeight = rowAvailableWidth / combinedAspect;
if (rowHeight < minRowHeight) {
combinedAspect -= responseList[j].aspect_ratio;
imagesInRow -= 1;
totalSpacing = root.imageSpacing * (imagesInRow - 1);
rowAvailableWidth = availableImageWidth - totalSpacing;
rowHeight = rowAvailableWidth / combinedAspect;
break;
}
j++;
}
// If we couldn't add any image (shouldn't happen), add at least one
if (j === i) {
row.images.push(responseList[i]);
row.height = availableImageWidth / responseList[i].aspect_ratio;
rows.push(row);
i++;
} else {
for (let k = i; k < j; k++) {
row.images.push(responseList[k]);
}
// Recalculate spacing for the final row
let imagesInRow = j - i;
let totalSpacing = root.imageSpacing * (imagesInRow - 1);
let rowAvailableWidth = availableImageWidth - totalSpacing;
row.height = rowAvailableWidth / combinedAspect;
rows.push(row);
i = j;
}
}
return rows;
}
}
delegate: RowLayout {
id: imageRow
required property var modelData
property var rowHeight: modelData.height
spacing: root.imageSpacing
Repeater {
model: modelData.images
delegate: BooruImage {
required property var modelData
imageData: modelData
rowHeight: imageRow.rowHeight
imageRadius: imageRow.modelData.images.length == 1 ? 50 : Appearance.rounding.normal
// Download manually to reduce redundant requests or make sure downloading works
// manualDownload: ["danbooru", "waifu.im", "t.alcy.cc"].includes(root.responseData.provider)
previewDownloadPath: root.previewDownloadPath
downloadPath: root.downloadPath
nsfwPath: root.nsfwPath
}
}
}
}
RippleButton { // Next page button
id: button
property string buttonText
visible: root.responseData.page != "" && root.responseData.page > 0
Layout.alignment: Qt.AlignRight
implicitHeight: 30
leftPadding: 10
rightPadding: 5
onClicked: {
tagInputField.text = `${responseData.tags.join(" ")} ${parseInt(root.responseData.page) + 1}`
tagInputField.accept()
}
buttonRadius: Appearance.rounding.small
colBackground: Appearance.colors.colSurfaceContainerHighest
colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover
colRipple: Appearance.colors.colSurfaceContainerHighestActive
contentItem: Item {
anchors.fill: parent
implicitHeight: nextPageRow.implicitHeight
implicitWidth: nextPageRow.implicitWidth
RowLayout {
id: nextPageRow
anchors.centerIn: parent
spacing: 0
StyledText {
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
text: "Next page"
color: Appearance.m3colors.m3onSurface
}
MaterialSymbol {
Layout.alignment: Qt.AlignVCenter
iconSize: Appearance.font.pixelSize.larger
color: Appearance.m3colors.m3onSurface
text: "chevron_right"
}
}
}
}
}
}