Merge branch 'end-4:main' into parallax

This commit is contained in:
Ivan Rosinskii
2025-12-18 19:16:43 +01:00
committed by GitHub
46 changed files with 1119 additions and 171 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ bindd = Ctrl+Super, T, Toggle wallpaper selector, global, quickshell:wallpaperSe
bindd = Ctrl+Super+Alt, T, Select random wallpaper, global, quickshell:wallpaperSelectorRandom # Random wallpaper
bindd = Ctrl+Super, T, Change wallpaper, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/quickshell/$qsConfig/scripts/colors/switchwall.sh # [hidden] Change wallpaper (fallback)
bind = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs quickshell; qs -c $qsConfig & # Restart widgets
bind = Super+Alt, W, global, quickshell:panelFamilyCycle # Cycle panel family
bind = Ctrl+Super, P, global, quickshell:panelFamilyCycle # Cycle panel family
##! Utilities
# Screenshot, Record, OCR, Color picker, Clipboard history
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
version="1.1"
id="svg1"
sodipodi:docname=".svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="7.75"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-width="1173"
inkscape:window-height="790"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
</svg>

After

Width:  |  Height:  |  Size: 1007 B

@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m8.5 16.586-3.793-3.793a1 1 0 0 0-1.414 1.414l4.5 4.5a1 1 0 0 0 1.414 0l11-11a1 1 0 0 0-1.414-1.414L8.5 16.586Z" fill="#212121"/></svg>

After

Width:  |  Height:  |  Size: 239 B

@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M4.53 12.97a.75.75 0 0 0-1.06 1.06l4.5 4.5a.75.75 0 0 0 1.06 0l11-11a.75.75 0 0 0-1.06-1.06L8.5 16.94l-3.97-3.97Z" fill="#212121"/></svg>

After

Width:  |  Height:  |  Size: 241 B

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
version="1.1"
id="svg1"
sodipodi:docname=".svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="7.75"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-width="1173"
inkscape:window-height="790"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
</svg>

After

Width:  |  Height:  |  Size: 1007 B

@@ -418,6 +418,8 @@ Singleton {
property real scale: 0.18 // Relative to screen size
property real rows: 2
property real columns: 5
property bool orderRightLeft: false
property bool orderBottomUp: false
property bool centerIcons: true
}
@@ -44,6 +44,7 @@ Singleton {
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`)
property string defaultAiPrompts: Quickshell.shellPath("defaults/ai/prompts")
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)
property string userActions: FileUtils.trimFileProtocol(`${Directories.shellConfig}/actions`)
property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`)
property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`)
property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.sh`)
@@ -59,6 +60,7 @@ Singleton {
Quickshell.execDetached(["bash", "-c", `rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`])
Quickshell.execDetached(["mkdir", "-p", `${aiChats}`])
Quickshell.execDetached(["mkdir", "-p", `${userActions}`])
Quickshell.execDetached(["rm", "-rf", `${tempImages}`])
}
}
@@ -63,6 +63,10 @@ Singleton {
property real temperature: 0.5
}
property JsonObject cheatsheet: JsonObject {
property int tabIndex: 0
}
property JsonObject sidebar: JsonObject {
property JsonObject bottomGroup: JsonObject {
property bool collapsed: false
@@ -0,0 +1,6 @@
import Quickshell
ScriptModel {
required property int count
values: Array(count).map((_, i) => i)
}
@@ -72,7 +72,7 @@ Scope {
}
function stopFingerPam() {
if (fingerPam.running) {
if (fingerPam.active) {
fingerPam.abort();
}
}
@@ -92,7 +92,6 @@ Scope {
WlSessionLock {
id: lock
locked: GlobalStates.screenLocked
surface: root.sessionLockSurface
}
@@ -100,7 +100,7 @@ ListView {
to: 1,
}),
] : []
}
}
move: Transition {
animations: root.animateMovement ? [
@@ -4,6 +4,7 @@ import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.synchronizer
import Qt5Compat.GraphicalEffects
import Quickshell.Io
import Quickshell
@@ -135,7 +136,10 @@ Scope { // Scope
ToolbarTabBar {
id: tabBar
tabButtonList: root.tabButtonList
currentIndex: swipeView.currentIndex
Synchronizer on currentIndex {
property alias source: swipeView.currentIndex
}
}
}
@@ -144,8 +148,11 @@ Scope { // Scope
Layout.topMargin: 5
Layout.fillWidth: true
Layout.fillHeight: true
currentIndex: tabBar.currentIndex
spacing: 10
currentIndex: Persistent.states.cheatsheet.tabIndex
onCurrentIndexChanged: {
Persistent.states.cheatsheet.tabIndex = currentIndex;
}
implicitWidth: Math.max.apply(null, contentChildren.map(child => child.implicitWidth || 0))
implicitHeight: Math.max.apply(null, contentChildren.map(child => child.implicitHeight || 0))
@@ -50,6 +50,21 @@ Item {
property Component windowComponent: OverviewWindow {}
property list<OverviewWindow> windowWidgets: []
function getWsRow(ws) {
// 1-indexed workspace, 0-indexed row
var normalRow = Math.floor((ws - 1) / Config.options.overview.columns) % Config.options.overview.rows;
return (Config.options.overview.orderBottomUp ? Config.options.overview.rows - normalRow - 1 : normalRow);
}
function getWsColumn(ws) {
// 1-indexed workspace, 0-indexed column
var normalCol = (ws - 1) % Config.options.overview.columns;
return (Config.options.overview.orderRightLeft ? Config.options.overview.columns - normalCol - 1 : normalCol);
}
function getWsInCell(ri, ci) {
// 1-indexed workspace, 0-indexed row and column index
return (Config.options.overview.orderBottomUp ? Config.options.overview.rows - ri - 1 : ri) * Config.options.overview.columns + (Config.options.overview.orderRightLeft ? Config.options.overview.columns - ci - 1 : ci) + 1
}
StyledRectangularShadow {
target: overviewBackground
@@ -85,7 +100,7 @@ Item {
id: workspace
required property int index
property int colIndex: index
property int workspaceValue: root.workspaceGroup * root.workspacesShown + row.index * Config.options.overview.columns + colIndex + 1
property int workspaceValue: root.workspaceGroup * root.workspacesShown + getWsInCell(row.index, colIndex)
property color defaultWorkspaceColor: ColorUtils.mix(Appearance.colors.colBackgroundSurfaceContainer, Appearance.colors.colSurfaceContainerHigh, 0.8)
property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1)
property color hoveredBorderColor: Appearance.colors.colLayer2Hover
@@ -182,8 +197,8 @@ Item {
property bool atInitPosition: (initX == x && initY == y)
// Offset on the canvas
property int workspaceColIndex: (windowData?.workspace.id - 1) % Config.options.overview.columns
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / Config.options.overview.columns)
property int workspaceColIndex: getWsColumn(windowData?.workspace.id)
property int workspaceRowIndex: getWsRow(windowData?.workspace.id)
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
property real xWithinWorkspaceWidget: Math.max((windowData?.at[0] - (monitor?.x ?? 0) - monitorData?.reserved[0]) * root.scale, 0)
@@ -286,9 +301,8 @@ Item {
Rectangle { // Focused workspace indicator
id: focusedWorkspaceIndicator
property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown)
property int rowIndex: Math.floor((activeWorkspaceInGroup - 1) / Config.options.overview.columns)
property int colIndex: (activeWorkspaceInGroup - 1) % Config.options.overview.columns
property int rowIndex: getWsRow(monitor.activeWorkspace?.id)
property int colIndex: getWsColumn(monitor.activeWorkspace?.id)
x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex
y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex
z: root.windowZ
@@ -739,6 +739,45 @@ ContentPage {
}
}
}
ConfigRow {
uniform: true
ConfigSelectionArray {
currentValue: Config.options.overview.orderRightLeft
onSelected: newValue => {
Config.options.overview.orderRightLeft = newValue
}
options: [
{
displayName: Translation.tr("Left to right"),
icon: "arrow_forward",
value: 0
},
{
displayName: Translation.tr("Right to left"),
icon: "arrow_back",
value: 1
}
]
}
ConfigSelectionArray {
currentValue: Config.options.overview.orderBottomUp
onSelected: newValue => {
Config.options.overview.orderBottomUp = newValue
}
options: [
{
displayName: Translation.tr("Top-down"),
icon: "arrow_downward",
value: 0
},
{
displayName: Translation.tr("Bottom-up"),
icon: "arrow_upward",
value: 1
}
]
}
}
}
ContentSection {
@@ -9,8 +9,8 @@ MouseArea {
id: root
Layout.fillHeight: true
implicitHeight: row.implicitHeight
implicitWidth: row.implicitWidth
implicitHeight: appRow.implicitHeight
implicitWidth: appRow.implicitWidth
hoverEnabled: true
function showPreviewPopup(appEntry, button) {
@@ -21,31 +21,31 @@ MouseArea {
animation: Looks.transition.move.createObject(this)
}
// Apps row
RowLayout {
id: row
WListView {
id: appRow
anchors {
top: parent.top
bottom: parent.bottom
}
orientation: Qt.Horizontal
spacing: 0
implicitWidth: contentWidth
clip: true
interactive: false
// TODO: Include only apps (and windows) in current workspace only | wait, does that even make sense in a Hyprland workflow?
model: ScriptModel {
objectProp: "appId"
values: TaskbarApps.apps.filter(app => app.appId !== "SEPARATOR")
}
delegate: TaskAppButton {
required property var modelData
appEntry: modelData
Repeater {
// TODO: Include only apps (and windows) in current workspace only | wait, does that even make sense in a Hyprland workflow?
model: ScriptModel {
objectProp: "appId"
values: TaskbarApps.apps.filter(app => app.appId !== "SEPARATOR")
onHoverPreviewRequested: {
root.showPreviewPopup(appEntry, this);
}
delegate: TaskAppButton {
required property var modelData
appEntry: modelData
onHoverPreviewRequested: {
root.showPreviewPopup(appEntry, this)
}
onHoverPreviewDismissed: {
previewPopup.close()
}
onHoverPreviewDismissed: {
previewPopup.close();
}
}
}
@@ -56,5 +56,4 @@ MouseArea {
tasksHovered: root.containsMouse
anchor.window: root.QsWindow.window
}
}
@@ -36,15 +36,34 @@ LockScreen {
Image {
id: bg
z: 0
anchors.fill: parent
width: parent.width
height: parent.height
onStatusChanged: {
if (status === Image.Ready) {
print("Lock wallpaper loaded");
print(lockSurfaceItem.height);
y = -lockSurfaceItem.height;
openAnim.restart();
}
}
sourceSize: Qt.size(lockSurfaceItem.width, lockSurfaceItem.height)
source: Config.options.background.wallpaperPath
fillMode: Image.PreserveAspectCrop
PropertyAnimation {
id: openAnim
target: bg
property: "y"
to: 0
duration: 350
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
}
GaussianBlur {
z: 1
anchors.fill: parent
anchors.fill: bg
source: bg
radius: 100
samples: radius * 2 + 1
@@ -67,7 +86,7 @@ LockScreen {
Interactables {
id: interactables
z: 2
anchors.fill: parent
anchors.fill: bg
}
}
@@ -83,12 +102,31 @@ LockScreen {
// }
function switchToFocusedView() {
root.passwordView = true;
switchToPasswordViewAnim.restart();
}
SequentialAnimation {
id: switchToPasswordViewAnim
PropertyAnimation {
target: unfocusedContent
property: "y"
from: 0
to: -height * 1.1
duration: 250
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
ScriptAction {
script: {
root.passwordView = true;
}
}
}
Item {
id: unfocusedContent
anchors.fill: parent
width: parent.width
height: parent.height
visible: !root.passwordView
ClockTextGroup {
anchors {
@@ -177,7 +177,7 @@ Singleton {
property Component color: Component {
ColorAnimation {
duration: 120
duration: 80
easing.type: Easing.BezierSpline
easing.bezierCurve: transition.easing.bezierCurve.easeIn
}
@@ -6,5 +6,14 @@ import QtQuick.Controls
ListView {
id: root
boundsBehavior: Flickable.DragOverBounds
ScrollBar.vertical: WScrollBar {}
displaced: Transition {
animations: [Looks.transition.enter.createObject(this, {
property: "y"
})]
}
}
@@ -53,10 +53,9 @@ BodyRectangle {
}
}
StyledListView {
WListView {
Layout.fillWidth: true
Layout.fillHeight: true
animateAppearance: false
clip: true
model: Notifications.appNameList
@@ -0,0 +1,32 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.modules.waffle.looks
SequentialAnimation {
id: root
required property var target
PropertyAction {
target: root.target
property: "ListView.delayRemove"
value: true
}
NumberAnimation {
target: root.target
property: "x"
to: root.target.width
duration: 250
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
PropertyAction {
target: root.target
property: "ListView.delayRemove"
value: false
}
}
@@ -1,3 +1,4 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
@@ -18,10 +19,44 @@ MouseArea {
implicitWidth: contentLayout.implicitWidth
implicitHeight: contentLayout.implicitHeight
function dismissAll() {
root.notifications.forEach(notif => {
Qt.callLater(() => {
Notifications.discardNotification(notif.notificationId);
});
});
removeAnimation.start();
}
WNotificationDismissAnim {
id: removeAnimation
target: root
}
property real dragDismissThreshold: 100
drag {
axis: Drag.XAxis
target: contentLayout
minimumX: 0
onActiveChanged: {
if (drag.active)
return;
if (contentLayout.x > root.dragDismissThreshold) {
root.dismissAll();
} else {
contentLayout.x = 0;
}
}
}
ColumnLayout {
id: contentLayout
anchors.fill: parent
spacing: 4
width: root.width
Behavior on x {
animation: Looks.transition.enter.createObject(this)
}
GroupHeader {
id: notifHeader
@@ -29,7 +64,9 @@ MouseArea {
Layout.margins: 11
}
ListView {
WListView {
Layout.leftMargin: -Math.min(35, contentLayout.x)
Layout.rightMargin: -Layout.leftMargin
Layout.fillWidth: true
implicitWidth: notifHeader.implicitWidth
implicitHeight: contentHeight
@@ -40,14 +77,20 @@ MouseArea {
objectProp: "notificationId"
}
delegate: WSingleNotification {
id: singleNotif
required property int index
required property var modelData
width: ListView.view.width
notification: modelData
groupExpandControlMessage: {
if (root.notifications.length <= 1) return "";
if (!root.expanded) return Translation.tr("+%1 notifications").arg(root.notifications.length - 1);
if (index === root.notifications.length - 1) return Translation.tr("See fewer");
if (root.notifications.length <= 1)
return "";
if (!root.expanded)
return Translation.tr("+%1 notifications").arg(root.notifications.length - 1);
if (index === root.notifications.length - 1)
return Translation.tr("See fewer");
return "";
}
onGroupExpandToggle: {
@@ -94,11 +137,7 @@ MouseArea {
Layout.rightMargin: 3
icon.name: "dismiss"
onClicked: {
root.notifications.forEach(notif => {
Qt.callLater(() => {
Notifications.discardNotification(notif.notificationId);
});
});
root.dismissAll();
}
}
}
@@ -18,6 +18,18 @@ MouseArea {
signal groupExpandToggle
hoverEnabled: true
function dismiss() {
Qt.callLater(() => {
Notifications.discardNotification(root.notification?.notificationId);
});
removeAnimation.start();
}
WNotificationDismissAnim {
id: removeAnimation
target: root
}
implicitHeight: contentItem.implicitHeight
implicitWidth: contentItem.implicitWidth
@@ -25,9 +37,25 @@ MouseArea {
animation: Looks.transition.enter.createObject(this)
}
property real dragDismissThreshold: 100
drag {
axis: Drag.XAxis
target: contentItem
minimumX: 0
onActiveChanged: {
if (drag.active)
return;
if (contentItem.x > root.dragDismissThreshold) {
root.dismiss();
} else {
contentItem.x = 0;
}
}
}
Rectangle {
id: contentItem
anchors.fill: parent
width: parent.width
color: Looks.colors.bgPanelBody
radius: Looks.radius.medium
property real padding: 12
@@ -36,6 +64,10 @@ MouseArea {
border.width: 1
border.color: ColorUtils.applyAlpha(Looks.colors.ambientShadow, 0.1)
Behavior on x {
animation: Looks.transition.enter.createObject(this)
}
ColumnLayout {
id: notificationContent
anchors.fill: parent
@@ -128,11 +160,7 @@ MouseArea {
opacity: root.containsMouse ? 1 : 0
icon.name: "dismiss"
implicitSize: 12
onClicked: {
Qt.callLater(() => {
Notifications.discardNotification(root.notification?.notificationId);
});
}
onClicked: root.dismiss()
}
}
@@ -86,7 +86,7 @@ WBarAttachedPanelContent {
id: searchBar
Layout.fillWidth: true
implicitWidth: 832 // TODO: Make sizes naturally inferred
horizontalPadding: root.searching ? 24 : 32
horizontalPadding: 32
// verticalPadding: root.searching ? 32 : 16 // TODO: make this not nuke the panel
Synchronizer on searching {
property alias target: root.searching
@@ -1,44 +1,214 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.models
import qs.modules.common.widgets
import qs.modules.waffle.looks
import "window-layout.js" as WindowLayout
Rectangle {
id: root
color: ColorUtils.transparentize(Looks.colors.bg1Base, 0.5)
property bool draggingWindow: false
property real openProgress: 0
property Item hoveredWorkspace: null
signal closed
Component.onCompleted: {
openAnim.start();
}
function close() {
closeAnim.start();
}
PropertyAnimation {
id: openAnim
target: root
property: "openProgress"
to: 1
duration: 200
duration: 250
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
PropertyAnimation {
SequentialAnimation {
id: closeAnim
target: root
property: "openProgress"
to: 0
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
PropertyAnimation {
target: root
property: "openProgress"
to: 0
duration: 250
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
ScriptAction {
script: {
root.closed();
}
}
}
// Windows
property real maxWindowHeight: 290
property real maxWindowWidth: 738
property real padding: 52
property real spacing: 25
readonly property list<var> toplevels: ToplevelManager.toplevels.values.filter(t => {
const client = HyprlandData.clientForToplevel(t);
return client && client.workspace.id === HyprlandData.activeWorkspace?.id;
})
readonly property list<var> arrangedToplevels: {
const maxRowWidth = width - padding * 2;
const count = toplevels.length;
const resultLayout = [];
var i = 0;
while (i < count) {
var row = [];
var rowWidth = 0;
var j = i;
while (j < count) {
const toplevel = toplevels[j];
const client = HyprlandData.clientForToplevel(toplevel);
const scaledSize = WindowLayout.scaleWindow(client, maxWindowWidth, maxWindowHeight);
if (rowWidth + scaledSize.width <= maxRowWidth || row.length === 0) {
row.push(toplevel);
rowWidth += scaledSize.width;
j++;
} else {
break;
}
}
resultLayout.push(row);
i = j;
}
return resultLayout;
}
MouseArea {
z: 0
anchors.fill: parent
onClicked: {
GlobalStates.overviewOpen = false;
}
}
// Windows
WListView {
id: windowListView
z: root.openProgress == 1 ? 2 : 1
anchors {
left: parent.left
right: parent.right
top: parent.top
topMargin: (root.height - (wsBorder.height + 16) - height) / 2
}
spacing: root.spacing
topMargin: root.padding
bottomMargin: root.padding
leftMargin: root.padding
rightMargin: root.padding
height: Math.min(contentHeight + topMargin + bottomMargin, root.height - (wsBorder.height + 16))
interactive: (height < contentHeight) && !root.draggingWindow
clip: root.openProgress > 0.99 && !root.draggingWindow
model: ScriptModel {
values: root.arrangedToplevels
}
delegate: RowLayout {
id: clientRow
required property var modelData
spacing: root.spacing
anchors.horizontalCenter: parent?.horizontalCenter ?? undefined
Repeater {
model: ScriptModel {
values: clientRow.modelData
}
delegate: Item {
id: clientGridArea
required property int index
required property var modelData
implicitWidth: windowItem.openedSize.width
implicitHeight: windowItem.openedSize.height + windowItem.titleBarImplicitHeight
TaskViewWindow {
id: windowItem
z: Drag.active ? 2 : 1
opacity: openAnim.running ? root.openProgress : 1
property int mappedX: {
// print("AAAWAWAAWAWWA: ", -(clientRow.x + clientGridArea.x + root.padding));
var rootPosToThis = -(clientRow.x + clientGridArea.x + root.padding);
return rootPosToThis + hyprlandClient.at[0];
}
property int mappedY: {
// print("AAAWAWAAWAWWA YYYY YUIUSDFOIU: ", clientRow.y + windowListView.y + root.padding + windowItem.titleBarImplicitHeight)
var rootPosToThis = -(clientRow.y + windowListView.y + root.padding + windowItem.titleBarImplicitHeight);
return rootPosToThis + hyprlandClient.at[1];
}
property int openedX: 0
property int openedY: 0
// property int openedX: Drag.active ? (dragHandler.xAxis.activeValue) : 0
// property int openedY: Drag.active ? (dragHandler.yAxis.activeValue) : 0
scaleSize: (root.openProgress > 0 && !closeAnim.running)
x: mappedX + (openedX - mappedX) * root.openProgress
y: mappedY + (openedY - mappedY) * root.openProgress
droppable: root.hoveredWorkspace !== null
Drag.active: dragHandler.active
Drag.hotSpot.x: mouseX
Drag.hotSpot.y: mouseY
DragHandler {
id: dragHandler
target: null
xAxis.onActiveValueChanged: {
windowItem.openedX = dragHandler.xAxis.activeValue;
}
yAxis.onActiveValueChanged: {
windowItem.openedY = dragHandler.yAxis.activeValue;
}
onActiveChanged: {
if (active) {
root.draggingWindow = true;
} else {
root.draggingWindow = false;
if (root.hoveredWorkspace !== null && root.hoveredWorkspace.workspace !== windowItem.hyprlandClient.workspace.id) {
Hyprland.dispatch(`movetoworkspacesilent ${root.hoveredWorkspace.workspace}, address:${windowItem.hyprlandClient.address}`);
} else {
windowItem.openedX = 0;
windowItem.openedY = 0;
}
}
}
}
Layout.alignment: Qt.AlignTop
maxHeight: root.maxWindowHeight
maxWidth: root.maxWindowWidth
toplevel: clientGridArea.modelData
}
}
}
}
}
// Workspaces
Rectangle {
id: wsBorder
z: root.openProgress == 1 ? 1 : 2
property real sourceEdgeMargin: -(height + 8) + root.openProgress * (height + 16)
anchors {
left: parent.left
@@ -64,8 +234,9 @@ Rectangle {
color: Looks.colors.bgPanelFooterBase
implicitHeight: 174
ListView {
WListView {
id: workspaceListView
anchors {
top: parent.top
bottom: parent.bottom
@@ -73,22 +244,56 @@ Rectangle {
topMargin: 5
bottomMargin: 5
}
flickableDirection: Flickable.HorizontalFlick
orientation: ListView.Horizontal
interactive: width == parent.width
width: Math.min(contentWidth + leftMargin + rightMargin, parent.width)
leftMargin: 5
rightMargin: 5
clip: true
orientation: ListView.Horizontal
spacing: 4
model: ScriptModel {
values: {
const maxWorkspaceId = Math.max.apply(null, HyprlandData.workspaces.map(ws => ws.id))
return Array(maxWorkspaceId)
function reposition() {
positionViewAtIndex(HyprlandData.activeWorkspace.id - 1, ListView.Contain);
}
Connections {
target: HyprlandData
function onActiveWorkspaceChanged() {
workspaceListView.reposition();
}
}
model: IndexModel {
id: workspaceIndexModel
count: {
const maxWorkspaceId = Math.max.apply(null, HyprlandData.workspaces.map(ws => ws.id));
return Math.max(maxWorkspaceId, 1) + 1;
}
}
delegate: TaskViewWorkspace {
id: workspaceItem
required property int index
workspace: index + 1
newWorkspace: index == workspaceIndexModel.count - 1
droppable: root.hoveredWorkspace === workspaceItem
DropArea {
anchors.fill: parent
onEntered: drag => {
root.hoveredWorkspace = workspaceItem;
}
onExited: {
if (root.hoveredWorkspace === workspaceItem) {
root.hoveredWorkspace = null;
}
}
}
onClicked: {
GlobalStates.overviewOpen = false;
root.closed(); // Close immediately to avoid weird animations
Hyprland.dispatch(`workspace ${workspaceItem.workspace}`);
}
}
}
}
@@ -0,0 +1,155 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
import "window-layout.js" as WindowLayout
WMouseAreaButton {
id: root
required property var toplevel
required property int maxHeight
required property int maxWidth
property var hyprlandClient: HyprlandData.clientForToplevel(root.toplevel)
property string address: hyprlandClient?.address
property string iconName: AppSearch.guessIcon(hyprlandClient?.class)
color: drag.active ? ColorUtils.transparentize(Looks.colors.bg1Base) : (containsMouse ? Looks.colors.bg1Base : Looks.colors.bgPanelFooterBase)
borderColor: ColorUtils.transparentize(Looks.colors.bg2Border, drag.active ? 1 : 0)
radius: Looks.radius.xLarge
property real titleBarImplicitHeight: titleBar.implicitHeight
property bool scaleSize: true
property size openedSize: WindowLayout.scaleWindow(hyprlandClient, maxWidth, maxHeight);
property size fullSize: Qt.size(hyprlandClient?.size[0] ?? maxWidth, hyprlandClient?.size[1] ?? maxHeight)
property size size: scaleSize ? openedSize : fullSize
implicitWidth: Math.max(Math.round(contentItem.implicitWidth), 138)
implicitHeight: Math.round(contentItem.implicitHeight)
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Item {
width: root.background.width
height: root.background.height
Rectangle {
radius: root.background.radius
anchors {
fill: parent
topMargin: root.drag.active ? root.titleBarImplicitHeight : 0
}
}
}
}
property bool droppable: false
scale: (root.pressedButtons & Qt.LeftButton || root.Drag.active) ? (droppable ? 0.4 : 0.95) : 1
Behavior on scale {
NumberAnimation {
id: scaleAnim
duration: 200
easing.type: Easing.OutExpo
}
}
function closeWindow() {
Hyprland.dispatch(`closewindow address:${root.hyprlandClient?.address}`);
}
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
onClicked: event => {
if (event.button === Qt.LeftButton) {
GlobalStates.overviewOpen = false;
Hyprland.dispatch(`focuswindow address:${root.hyprlandClient?.address}`);
GlobalStates.overviewOpen = false;
} else if (event.button === Qt.MiddleButton) {
root.closeWindow();
event.accepted = true;
} else if (event.button === Qt.RightButton) {
if (!windowMenu.visible)
windowMenu.popup();
else
windowMenu.close();
}
}
ColumnLayout {
id: contentItem
z: 2
anchors.fill: parent
anchors.margins: 1
spacing: 0
RowLayout {
id: titleBar
opacity: root.drag.active ? 0 : 1
spacing: 8
WAppIcon {
Layout.leftMargin: 10
Layout.alignment: Qt.AlignVCenter
iconName: root.iconName
implicitSize: 16
tryCustomIcon: false
}
WText {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
elide: Text.ElideRight
text: root.hyprlandClient?.title ?? ""
}
CloseButton {
implicitWidth: 38
implicitHeight: 38
padding: 8
onClicked: root.closeWindow()
}
}
ScreencopyView {
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter
implicitWidth: Math.round(root.size.width)
implicitHeight: Math.round(root.size.height)
constraintSize: Qt.size(Math.round(root.size.width), Math.round(root.size.height))
Behavior on implicitWidth {
animation: Looks.transition.enter.createObject(this)
}
Behavior on implicitHeight {
animation: Looks.transition.enter.createObject(this)
}
captureSource: root.toplevel ?? null
live: true
}
}
WMenu {
id: windowMenu
downDirection: true
Action {
enabled: root.hyprlandClient?.floating
property bool isPinned: root.hyprlandClient?.pinned
icon.name: isPinned ? "checkmark" : "empty"
text: Translation.tr("Show this window on all desktops")
onTriggered: {
Hyprland.dispatch(`pin address:${root.hyprlandClient?.address}`);
}
}
Action {
icon.name: "empty"
text: Translation.tr("Close")
onTriggered: root.closeWindow()
}
}
}
@@ -15,22 +15,37 @@ WMouseAreaButton {
id: root
required property int workspace
property bool newWorkspace: false
property bool droppable: false
readonly property real screenWidth: QsWindow.window.width
readonly property real screenHeight: QsWindow.window.height
readonly property bool isActiveWorkspace: HyprlandData.activeWorkspace?.id === root.workspace
readonly property real screenWidth: QsWindow.window?.width ?? 0
readonly property real screenHeight: QsWindow.window?.height ?? 0
readonly property real screenAspectRatio: screenWidth / screenHeight
readonly property real screenScale: QsWindow.window.devicePixelRatio
readonly property real scale: 0.1148148148
readonly property real windowScale: wallpaperHeight / screenHeight
height: ListView.view.height
property real wallpaperHeight: 124
height: ListView.view?.height ?? 100
implicitWidth: 244 // for now
onClicked: {
GlobalStates.overviewOpen = false;
Hyprland.dispatch(`workspace ${root.workspace}`);
colBackground: ColorUtils.transparentize(Looks.colors.bg2, (isActiveWorkspace || droppable) ? 0 : 1)
Behavior on color {
animation: Looks.transition.color.createObject(this)
}
scale: root.containsPress ? 0.95 : 1
Behavior on scale {
NumberAnimation {
id: scaleAnim
duration: 300
easing.type: Easing.OutExpo
}
}
// Content
ColumnLayout {
id: contentItem
anchors {
fill: parent
leftMargin: 12
@@ -45,15 +60,15 @@ WMouseAreaButton {
Layout.fillHeight: false
horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight
text: Translation.tr("Desktop %1").arg(root.workspace)
text: root.newWorkspace ? Translation.tr("New desktop") : Translation.tr("Desktop %1").arg(root.workspace)
}
Rectangle {
id: wsBg
height: 124
height: root.wallpaperHeight
Layout.fillHeight: true
Layout.fillWidth: true
color: Looks.colors.bg1Base
color: Looks.colors.bg1
layer.enabled: true
layer.effect: OpacityMask {
@@ -64,34 +79,67 @@ WMouseAreaButton {
}
}
StyledImage {
// Workspace content
Loader {
anchors.fill: parent
cache: true
sourceSize: Qt.size(root.screenAspectRatio * 124, 124)
source: Config.options.background.wallpaperPath
fillMode: Image.PreserveAspectCrop
active: !root.newWorkspace
sourceComponent: StyledImage {
cache: true
sourceSize: Qt.size(root.screenAspectRatio * root.wallpaperHeight, root.wallpaperHeight)
source: Config.options.background.wallpaperPath
fillMode: Image.PreserveAspectCrop
Repeater {
model: ScriptModel {
values: ToplevelManager.toplevels.values.filter(toplevel => {
const address = `0x${toplevel.HyprlandToplevel?.address}`;
var win = HyprlandData.windowByAddress[address];
const inWorkspace = win?.workspace?.id === root.workspace;
return inWorkspace;
})
}
delegate: ScreencopyView {
required property var modelData
readonly property var hyprlandWindowData: HyprlandData.windowByAddress[`0x${modelData.HyprlandToplevel?.address}`]
captureSource: modelData
live: true
width: hyprlandWindowData?.size[0] * root.scale
height: hyprlandWindowData?.size[1] * root.scale
x: hyprlandWindowData?.at[0] * root.scale
y: hyprlandWindowData?.at[1] * root.scale
Repeater {
model: ScriptModel {
values: HyprlandData.toplevelsForWorkspace(root.workspace)
}
delegate: ScreencopyView {
required property var modelData
readonly property var hyprlandWindowData: HyprlandData.windowByAddress[`0x${modelData.HyprlandToplevel?.address}`]
captureSource: modelData
live: true
width: hyprlandWindowData?.size[0] * root.windowScale
height: hyprlandWindowData?.size[1] * root.windowScale
x: hyprlandWindowData?.at[0] * root.windowScale
y: hyprlandWindowData?.at[1] * root.windowScale
}
}
}
}
// New plus icon
Loader {
anchors.centerIn: parent
active: root.newWorkspace
sourceComponent: FluentIcon {
icon: "add"
}
}
Rectangle {
z: 2
visible: root.droppable && !root.newWorkspace
anchors.fill: parent
color: Looks.colors.accent
opacity: 0.2
}
}
}
// Active indicator
WFadeLoader {
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
}
shown: root.isActiveWorkspace
sourceComponent: Rectangle {
id: activeIndicator
implicitWidth: 32
implicitHeight: 3
color: Looks.colors.accent
radius: height / 2
}
}
}
@@ -23,7 +23,14 @@ Scope {
Loader {
id: panelLoader
required property var modelData
active: GlobalStates.overviewOpen
active: false
Connections {
target: GlobalStates
function onOverviewOpenChanged() {
if (GlobalStates.overviewOpen)
panelLoader.active = true;
}
}
sourceComponent: PanelWindow {
id: root
property string searchingText: ""
@@ -33,7 +40,7 @@ Scope {
WlrLayershell.namespace: "quickshell:wTaskView"
WlrLayershell.layer: WlrLayer.Overlay
// WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent"
anchors {
@@ -44,7 +51,26 @@ Scope {
}
TaskViewContent {
id: taskViewContent
anchors.fill: parent
Component.onCompleted: {
taskViewContent.forceActiveFocus();
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
GlobalStates.overviewOpen = false;
}
}
Connections {
target: GlobalStates
function onOverviewOpenChanged() {
if (!GlobalStates.overviewOpen)
taskViewContent.close();
}
}
onClosed: panelLoader.active = false
}
}
}
@@ -0,0 +1,36 @@
function scaleWindow(hyprlandClient, maxWindowWidth, maxWindowHeight) {
const [width, height] = hyprlandClient.size;
const [xScale, yScale] = [maxWindowWidth / width, maxWindowHeight / height];
const scale = Math.min(xScale, yScale);
return Qt.size(width * scale, height * scale)
}
function arrangedClients(hyprlandClients, maxRowWidth, maxWindowWidth, maxWindowHeight) {
const count = hyprlandClients.length;
const resultLayout = [];
var i = 0;
while (i < count) {
var row = [];
var rowWidth = 0;
var j = i;
while (j < count) {
const client = hyprlandClients[j];
const scaledSize = scaleWindow(client, maxWindowWidth, maxWindowHeight);
if (rowWidth + scaledSize.width <= maxRowWidth || row.length === 0) {
row.push(client);
rowWidth += scaledSize.width;
j++;
} else {
break;
}
}
resultLayout.push(row);
i = j;
}
return resultLayout;
}
@@ -2,6 +2,6 @@
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate
"$SCRIPT_DIR/thumbgen.py" "$@"
GIO_USE_VFS=local "$SCRIPT_DIR/thumbgen.py" "$@"
deactivate
@@ -15,7 +15,7 @@ import gi
from loguru import logger
from tqdm import tqdm
gi.require_version("GnomeDesktop", "3.0")
gi.require_version("GnomeDesktop", "4.0")
from gi.repository import Gio, GnomeDesktop # isort:skip
thumbnail_size_map = {
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
/**
@@ -21,6 +22,30 @@ Singleton {
property var monitors: []
property var layers: ({})
// Convenient stuff
function toplevelsForWorkspace(workspace) {
return ToplevelManager.toplevels.values.filter(toplevel => {
const address = `0x${toplevel.HyprlandToplevel?.address}`;
var win = HyprlandData.windowByAddress[address];
return win?.workspace?.id === workspace;
})
}
function hyprlandClientsForWorkspace(workspace) {
return root.windowList.filter(win => win.workspace.id === workspace);
}
function clientForToplevel(toplevel) {
if (!toplevel || !toplevel.HyprlandToplevel) {
return null;
}
const address = `0x${toplevel?.HyprlandToplevel?.address}`;
return root.windowByAddress[address];
}
// Internals
function updateWindowList() {
getClients.running = true;
}
@@ -63,6 +88,7 @@ Singleton {
function onRawEvent(event) {
// console.log("Hyprland raw event:", event.name);
if (["openlayer", "closelayer", "screencast"].includes(event.name)) return;
updateAll()
}
}
@@ -4,6 +4,7 @@ import qs.modules.common
import qs.modules.common.models
import qs.modules.common.functions
import QtQuick
import Qt.labs.folderlistmodel
import Quickshell
import Quickshell.Io
@@ -31,6 +32,34 @@ Singleton {
return acc;
}, []).sort()
// Load user action scripts from ~/.config/illogical-impulse/actions/
// Uses FolderListModel to auto-reload when scripts are added/removed
property var userActionScripts: {
const actions = [];
for (let i = 0; i < userActionsFolder.count; i++) {
const fileName = userActionsFolder.get(i, "fileName");
const filePath = userActionsFolder.get(i, "filePath");
if (fileName && filePath) {
const actionName = fileName.replace(/\.[^/.]+$/, ""); // strip extension
actions.push({
action: actionName,
execute: ((path) => (args) => {
Quickshell.execDetached([path, ...(args ? args.split(" ") : [])]);
})(FileUtils.trimFileProtocol(filePath.toString()))
});
}
}
return actions;
}
FolderListModel {
id: userActionsFolder
folder: Qt.resolvedUrl(Directories.userActions)
showDirs: false
showHidden: false
sortField: FolderListModel.Name
}
property var searchActions: [
{
action: "accentcolor",
@@ -90,6 +119,9 @@ Singleton {
},
]
// Combined built-in and user actions
property var allActions: searchActions.concat(userActionScripts)
property string mathResult: ""
property bool clipboardWorkSafetyActive: {
const enabled = Config.options.workSafety.enable.clipboard;
@@ -273,7 +305,7 @@ Singleton {
Qt.openUrlExternally(url);
}
});
const launcherActionObjects = root.searchActions.map(action => {
const launcherActionObjects = root.allActions.map(action => {
const actionString = `${Config.options.search.prefix.action}${action.action}`;
if (actionString.startsWith(root.query) || root.query.startsWith(actionString)) {
return resultComp.createObject(null, {
@@ -111,6 +111,14 @@ ApiStrategy {
return ({})
}
// Error response handling
if (dataJson.error) {
const errorMsg = `**Error ${dataJson.error.code}**: ${dataJson.error.message}`;
message.rawContent += errorMsg;
message.content += errorMsg;
return { finished: true };
}
// No candidates?
if (!dataJson.candidates) return {};
@@ -58,8 +58,17 @@ ApiStrategy {
// Real stuff
try {
const dataJson = JSON.parse(cleanData);
// Error response handling
if (dataJson.error) {
const errorMsg = `**Error**: ${dataJson.error.message || JSON.stringify(dataJson.error)}`;
message.rawContent += errorMsg;
message.content += errorMsg;
return { finished: true };
}
let newContent = "";
const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content;
const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content;
@@ -49,8 +49,17 @@ ApiStrategy {
// Real stuff
try {
const dataJson = JSON.parse(cleanData);
// Error response handling
if (dataJson.error) {
const errorMsg = `**Error**: ${dataJson.error.message || JSON.stringify(dataJson.error)}`;
message.rawContent += errorMsg;
message.content += errorMsg;
return { finished: true };
}
let newContent = "";
const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content;
const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content;
@@ -63,8 +63,6 @@ Wayland and X11.
%endif
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=Release \
-DDISTRIBUTOR="Fedora COPR (errornointernet/quickshell)" \
-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=YES \
-DGIT_REVISION=%{commit} \
-DINSTALL_QML_PREFIX=%{_lib}/qt6/qml
%cmake_build
+11
View File
@@ -1,3 +1,13 @@
# COPR repositories list
[copr]
repos = [
"ririko66z/dots-hyprland",
"solopasha/hyprland",
"deltacopy/darkly",
"alternateved/eza",
"atim/starship"
]
# Fedora dependencies list
# Audio
@@ -17,6 +27,7 @@ packages = [
"brightnessctl",
"ddcutil"
]
install_opts = ["--setopt=install_weak_deps=False"]
# Basic
[groups.basic]
+59 -40
View File
@@ -1,9 +1,15 @@
# This script is meant to be sourced.
# It's not for directly running.
# -------------------------
# CONFIG
# -------------------------
user_config="${REPO_ROOT}/sdata/dist-fedora/user_data.yaml"
rpmbuildroot="${REPO_ROOT}/cache/rpmbuild"
deps_data_file="${REPO_ROOT}/sdata/dist-fedora/feddeps.toml"
# Initialize the user configuration file
user_config=${REPO_ROOT}/sdata/dist-fedora/user_data.yaml
# -------------------------
# FUNCTIONS
# -------------------------
# Recording DNF Transaction ID
function r() {
@@ -16,6 +22,42 @@ function r() {
[ "$original_id" == "$last_id" ] || yq -i ".dnf.transaction_ids += [ $last_id ]" "$user_config" || :
}
# Start building and install the missing RPM package locally.
function install_RPMS() {
local local_specs local_rpms
rpmbuildroot="${rpmbuildroot:-${REPO_ROOT}/cache/rpmbuild}"
x mkdir -p "$rpmbuildroot"/{BUILD,RPMS,SOURCES}
x cp -r "${REPO_ROOT}/sdata/dist-fedora/SPECS" "$rpmbuildroot/"
x cd $rpmbuildroot/SPECS
mapfile -t -d '' local_specs < <(find "$rpmbuildroot/SPECS" -maxdepth 1 -type f -name "*.spec" -print0)
for spec_file in ${local_specs[@]}; do
# Download sources
x spectool -g -C "$rpmbuildroot/SOURCES" "$spec_file"
# Install build dependencies
r x sudo dnf builddep -y "$spec_file"
# Build the RPM package locally. If it fails, download it from COPR.
if ! rpmbuild -bb --define "_topdir $rpmbuildroot" --define "debug_package %{nil}" "$spec_file"; then
printf "${STY_RED}Local build encountered an issue. Downloading $(basename "$spec_file" .spec) from COPR. Report the issue to Discussions pls.${STY_RST}\n"
sudo dnf install -y $(basename "$spec_file" .spec)
nolock_qs=true
fi
done
mapfile -t -d '' local_rpms < <(find "$rpmbuildroot/RPMS" -maxdepth 2 -type f -name '*.rpm' -not -name '*debug*' -print0)
if [[ ${#local_rpms[@]} -ge 1 ]]; then
echo -e "${STY_BLUE}Next command:${STY_RST} sudo dnf install ${local_rpms[@]} -y"
r x sudo dnf install "${local_rpms[@]}" -y
fi
x cd ${REPO_ROOT}
}
# -------------------------
# MAIN
# -------------------------
if ! command -v dnf >/dev/null 2>&1; then
printf "${STY_RED}[$0]: dnf not found, it seems that the system is not Fedora 42 or later distros. Aborting...${STY_RST}\n"
exit 1
@@ -33,61 +75,38 @@ v sudo dnf versionlock delete quickshell-git 2>/dev/null
# Install yq for parsing config files
v sudo dnf install yq -y
# Development-tools
r v sudo dnf install @development-tools fedora-packager rpmdevtools fonts-rpm-macros qt6-rpm-macros -y
# Install development tools
r v sudo dnf install @development-tools fedora-packager -y
# COPR repositories
v sudo dnf copr enable ririko66z/dots-hyprland -y
v sudo dnf copr enable solopasha/hyprland -y
v sudo dnf copr enable deltacopy/darkly -y
v sudo dnf copr enable alternateved/eza -y
v sudo dnf copr enable atim/starship -y
# Start building and install the missing RPM package locally.
install_RPMS() {
rpmbuildroot=${REPO_ROOT}/cache/rpmbuild
x mkdir -p $rpmbuildroot/{BUILD,RPMS,SOURCES}
x cp -r ${REPO_ROOT}/sdata/dist-fedora/SPECS $rpmbuildroot/
x cd $rpmbuildroot/SPECS
mapfile -t -d '' local_specs < <(find "$rpmbuildroot/SPECS" -maxdepth 1 -type f -name "*.spec" -print0)
for spec_file in ${local_specs[@]}; do
x spectool -g -C "$rpmbuildroot/SOURCES" $spec_file
r x sudo dnf builddep -y $spec_file
x rpmbuild -bb --define "_topdir $rpmbuildroot" $spec_file
done
mapfile -t -d '' local_rpms < <(find "$rpmbuildroot/RPMS" -maxdepth 2 -type f -name '*.rpm' -not -name '*debug*' -print0)
echo -e "${STY_BLUE}Next command:${STY_RST} sudo dnf install ${local_rpms[@]} -y"
r x sudo dnf install "${local_rpms[@]}" -y
x cd ${REPO_ROOT}
}
# Install COPR repositories
copr_repos_json=$(yq -o=j '.copr.repos // []' "$deps_data_file")
eval "$(jq -r '@sh "copr_repos_array+=(\(.[]))"' <<<"$copr_repos_json")" # Fedora distro contains jq
for copr in ${copr_repos_array[@]}; do
v sudo dnf copr enable "$copr" -y
done
# Build and install locally RPMS
showfun install_RPMS
v install_RPMS
deps_data_file="${REPO_ROOT}/sdata/dist-fedora/feddeps.toml"
# Install packages from toml file
deps_data=$(yq -o=j '.' "$deps_data_file")
echo "Starting to install packages from $deps_data_file ..."
while IFS= read -r deps_list_key; do
echo "Installing package list: $deps_list_key"
install_opts=$(echo $deps_data | yq ".groups.\"$deps_list_key\" | select(has(\"install_opts\")) | .install_opts[]")
package_list=$(echo $deps_data | yq ".groups.\"$deps_list_key\".packages | unique | .[]")
r v sudo dnf install -y $install_opts $package_list </dev/tty
echo "----------------------------------------"
done < <(echo "$deps_data" | yq '
.groups |
keys[] // [] |
select(length > 0)
')
done < <(echo "$deps_data" | yq '.groups | keys[]? | select(length > 0)')
# Add back versionlock at the end
v sudo dnf versionlock add quickshell-git
[ -n $nolock_qs ] || v sudo dnf versionlock add quickshell-git || true
echo -e "\n========================================"
echo "All installations are complete."
echo "All installations are completed."
echo "========================================"
+26
View File
@@ -52,3 +52,29 @@ end
- The Hyprland live ebuild sometimes has linkage issues, deleting _Hyprland_ and _hyprland_ from `/usr/bin/` and then re-emerging usually fixes this.
- When emerging Hyprland if you get an issue relating to `undefined reference to ``Hyprutils::Math::Vector2D::˜Vector2D()`` `
- Clear the cache folder (`rm -fr /var/tmp/portage/gui-wm/hyprland*`) then try again
- If emerging ``hyprland-qtutils`` fails and gives you something like this...
```cmake
CMake Error at utils/dialog/CMakeLists.txt:26 (target_link_libraries):
Target "hyprland-dialog" links to:
Qt6::WaylandClientPrivate
but the target was not found. Possible reasons include:
* There is a typo in the target name.
* A find_package call is missing for an IMPORTED target.
* An ALIAS target is missing.
CMake Error at utils/update-screen/CMakeLists.txt:34 (target_link_libraries):
Target "hyprland-update-screen" links to:
Qt6::WaylandClientPrivate
but the target was not found. Possible reasons include:
* There is a typo in the target name.
* A find_package call is missing for an IMPORTED target.
* An ALIAS target is missing.
CMake Error at utils/donate-screen/CMakeLists.txt:32 (target_link_libraries):
Target "hyprland-donate-screen" links to:
Qt6::WaylandClientPrivate
but the target was not found. Possible reasons include:
* There is a typo in the target name.
* A find_package call is missing for an IMPORTED target.
* An ALIAS target is missing.
```
Try putting ``sdata/dist-gentoo/hyprland-qtutils-private.patch`` into ``/etc/portage/patches/gui-libs/hyprland-qtutils/``.
- Patch Credit: fedeliallalinea on https://forums.gentoo.org/viewtopic-p-8874098.html
@@ -0,0 +1,33 @@
--- a/utils/dialog/CMakeLists.txt
+++ b/utils/dialog/CMakeLists.txt
@@ -8,7 +8,7 @@
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient)
+find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient WaylandClientPrivate)
find_package(PkgConfig REQUIRED)
pkg_check_modules(hyprutils REQUIRED IMPORTED_TARGET hyprutils)
--- a/utils/donate-screen/CMakeLists.txt
+++ b/utils/donate-screen/CMakeLists.txt
@@ -8,7 +8,7 @@
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient)
+find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient WaylandClientPrivate)
find_package(PkgConfig REQUIRED)
pkg_check_modules(hyprutils REQUIRED IMPORTED_TARGET hyprutils)
--- a/utils/update-screen/CMakeLists.txt
+++ b/utils/update-screen/CMakeLists.txt
@@ -8,7 +8,7 @@
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient)
+find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient WaylandClientPrivate)
find_package(PkgConfig REQUIRED)
pkg_check_modules(hyprutils REQUIRED IMPORTED_TARGET hyprutils)
+1 -12
View File
@@ -1,23 +1,12 @@
printf "${STY_YELLOW}"
printf "============WARNING/NOTE (1)============\n"
printf "GCC in use: $(which gcc)\n"
printf "GCC version info: $(gcc --version | grep gcc)\n"
printf "GCC version number: $(gcc --version | grep gcc | awk '{print $3}')\n"
printf "GCC-15>= is required for Hyprland\n"
printf "If you have GCC-15>= and it's currently set then you can safely ignore this\n"
printf "If not, you must ensure you are using the correct GCC version and set it (gcc-config <number>)\n"
printf "It is heavily recommended to re-emerge @world with an empty tree after changing GCC version (emerge -e @world)\n\n"
printf "${STY_RST}"
pause
printf "${STY_YELLOW}"
printf "============WARNING/NOTE (2)============\n"
printf "Ensure you have a global use flag for elogind or systemd in your make.conf for simplicity\n"
printf "Or you can manually add the use flags for each package that requires it\n"
printf "${STY_RST}"
pause
printf "${STY_YELLOW}"
printf "============WARNING/NOTE (2)============\n"
printf "https://github.com/end-4/dots-hyprland/blob/main/sdata/dist-gentoo/README.md\n"
printf "Checkout the above README for potential bug fixes or additional information\n\n"
printf "${STY_RST}"
+1 -1
View File
@@ -64,7 +64,7 @@ function showfun(){
function pause(){
if [ ! "$ask" == "false" ];then
printf "${STY_FAINT}${STY_SLANT}"
local p; read -p "(Ctrl-C to abort, others to proceed)" p
local p; read -p "(Ctrl-C to abort, Enter to proceed)" p
printf "${STY_RST}"
fi
}
+1
View File
@@ -20,6 +20,7 @@ patterns:
- from: "dots/.config/fish"
to: "$XDG_CONFIG_HOME/fish"
mode: "sync"
excludes: ["conf.d"]
condition:
type: "shell"
value: "fish"
+1 -1
View File
@@ -30,7 +30,7 @@ esac
case "${SKIP_FISH}" in
true) sleep 0;;
*)
install_dir__sync dots/.config/fish "$XDG_CONFIG_HOME"/fish
install_dir__sync_exclude dots/.config/fish "$XDG_CONFIG_HOME"/fish "conf.d"
;;
esac
+29 -1
View File
@@ -67,6 +67,22 @@ rsync_dir__sync(){
x mkdir -p "$(dirname ${INSTALLED_LISTFILE})"
rsync -a --delete --out-format='%i %n' "$1"/ "$2"/ | awk -v d="$dest" '$1 ~ /^>/{ sub(/^[^ ]+ /,""); printf d "/" $0 "\n" }' >> "${INSTALLED_LISTFILE}"
}
rsync_dir__sync_exclude(){
# NOTE: This function is only for using in other functions
# Same as rsync_dir__sync but with exclude patterns support
# Usage: rsync_dir__sync_exclude <src> <dest> <exclude_pattern1> [<exclude_pattern2> ...]
local src="$1"
local dest_dir="$2"
shift 2
local excludes=()
for pattern in "$@"; do
excludes+=(--exclude "$pattern")
done
x mkdir -p "$dest_dir"
local dest="$(realpath -se $dest_dir)"
x mkdir -p "$(dirname ${INSTALLED_LISTFILE})"
rsync -a --delete "${excludes[@]}" --out-format='%i %n' "$src"/ "$dest_dir"/ | awk -v d="$dest" '$1 ~ /^>/{ sub(/^[^ ]+ /,""); printf d "/" $0 "\n" }' >> "${INSTALLED_LISTFILE}"
}
function install_file(){
# NOTE: Do not add prefix `v` or `x` when using this function
local s=$1
@@ -124,6 +140,18 @@ function install_dir__skip_existed(){
v rsync_dir $s $t
fi
}
function install_dir__sync_exclude(){
# NOTE: Do not add prefix `v` or `x` when using this function
# Sync directory with exclude patterns
# Usage: install_dir__sync_exclude <src> <dest> <exclude_pattern1> [<exclude_pattern2> ...]
local s=$1
local t=$2
shift 2
if [ -d $t ];then
warning_overwrite
fi
rsync_dir__sync_exclude $s $t "$@"
}
function install_google_sans_flex(){
local font_name="Google Sans Flex"
local src_name="google-sans-flex"
@@ -142,7 +170,7 @@ function install_google_sans_flex(){
x fc-cache -fv
x cd $REPO_ROOT
x mkdir -p "$(dirname ${INSTALLED_LISTFILE})"
realpath -se "$2" >> "${INSTALLED_LISTFILE}"
realpath -se "$target_dir" >> "${INSTALLED_LISTFILE}"
}
#####################################################################################