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+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) 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 = 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 ##! Utilities
# Screenshot, Record, OCR, Color picker, Clipboard history # 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 scale: 0.18 // Relative to screen size
property real rows: 2 property real rows: 2
property real columns: 5 property real columns: 5
property bool orderRightLeft: false
property bool orderBottomUp: false
property bool centerIcons: true property bool centerIcons: true
} }
@@ -44,6 +44,7 @@ Singleton {
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`) property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`)
property string defaultAiPrompts: Quickshell.shellPath("defaults/ai/prompts") property string defaultAiPrompts: Quickshell.shellPath("defaults/ai/prompts")
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/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 aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`)
property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`) property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`)
property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.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 '${latexOutput}'; mkdir -p '${latexOutput}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`]) Quickshell.execDetached(["bash", "-c", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`])
Quickshell.execDetached(["mkdir", "-p", `${aiChats}`]) Quickshell.execDetached(["mkdir", "-p", `${aiChats}`])
Quickshell.execDetached(["mkdir", "-p", `${userActions}`])
Quickshell.execDetached(["rm", "-rf", `${tempImages}`]) Quickshell.execDetached(["rm", "-rf", `${tempImages}`])
} }
} }
@@ -63,6 +63,10 @@ Singleton {
property real temperature: 0.5 property real temperature: 0.5
} }
property JsonObject cheatsheet: JsonObject {
property int tabIndex: 0
}
property JsonObject sidebar: JsonObject { property JsonObject sidebar: JsonObject {
property JsonObject bottomGroup: JsonObject { property JsonObject bottomGroup: JsonObject {
property bool collapsed: false 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() { function stopFingerPam() {
if (fingerPam.running) { if (fingerPam.active) {
fingerPam.abort(); fingerPam.abort();
} }
} }
@@ -92,7 +92,6 @@ Scope {
WlSessionLock { WlSessionLock {
id: lock id: lock
locked: GlobalStates.screenLocked locked: GlobalStates.screenLocked
surface: root.sessionLockSurface surface: root.sessionLockSurface
} }
@@ -100,7 +100,7 @@ ListView {
to: 1, to: 1,
}), }),
] : [] ] : []
} }
move: Transition { move: Transition {
animations: root.animateMovement ? [ animations: root.animateMovement ? [
@@ -4,6 +4,7 @@ import qs.modules.common.widgets
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt.labs.synchronizer
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import Quickshell.Io import Quickshell.Io
import Quickshell import Quickshell
@@ -135,7 +136,10 @@ Scope { // Scope
ToolbarTabBar { ToolbarTabBar {
id: tabBar id: tabBar
tabButtonList: root.tabButtonList tabButtonList: root.tabButtonList
currentIndex: swipeView.currentIndex
Synchronizer on currentIndex {
property alias source: swipeView.currentIndex
}
} }
} }
@@ -144,8 +148,11 @@ Scope { // Scope
Layout.topMargin: 5 Layout.topMargin: 5
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
currentIndex: tabBar.currentIndex
spacing: 10 spacing: 10
currentIndex: Persistent.states.cheatsheet.tabIndex
onCurrentIndexChanged: {
Persistent.states.cheatsheet.tabIndex = currentIndex;
}
implicitWidth: Math.max.apply(null, contentChildren.map(child => child.implicitWidth || 0)) implicitWidth: Math.max.apply(null, contentChildren.map(child => child.implicitWidth || 0))
implicitHeight: Math.max.apply(null, contentChildren.map(child => child.implicitHeight || 0)) implicitHeight: Math.max.apply(null, contentChildren.map(child => child.implicitHeight || 0))
@@ -50,6 +50,21 @@ Item {
property Component windowComponent: OverviewWindow {} property Component windowComponent: OverviewWindow {}
property list<OverviewWindow> windowWidgets: [] 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 { StyledRectangularShadow {
target: overviewBackground target: overviewBackground
@@ -85,7 +100,7 @@ Item {
id: workspace id: workspace
required property int index required property int index
property int colIndex: 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 defaultWorkspaceColor: ColorUtils.mix(Appearance.colors.colBackgroundSurfaceContainer, Appearance.colors.colSurfaceContainerHigh, 0.8)
property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1)
property color hoveredBorderColor: Appearance.colors.colLayer2Hover property color hoveredBorderColor: Appearance.colors.colLayer2Hover
@@ -182,8 +197,8 @@ Item {
property bool atInitPosition: (initX == x && initY == y) property bool atInitPosition: (initX == x && initY == y)
// Offset on the canvas // Offset on the canvas
property int workspaceColIndex: (windowData?.workspace.id - 1) % Config.options.overview.columns property int workspaceColIndex: getWsColumn(windowData?.workspace.id)
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / Config.options.overview.columns) property int workspaceRowIndex: getWsRow(windowData?.workspace.id)
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
property real xWithinWorkspaceWidget: Math.max((windowData?.at[0] - (monitor?.x ?? 0) - monitorData?.reserved[0]) * root.scale, 0) 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 Rectangle { // Focused workspace indicator
id: focusedWorkspaceIndicator id: focusedWorkspaceIndicator
property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown) property int rowIndex: getWsRow(monitor.activeWorkspace?.id)
property int rowIndex: Math.floor((activeWorkspaceInGroup - 1) / Config.options.overview.columns) property int colIndex: getWsColumn(monitor.activeWorkspace?.id)
property int colIndex: (activeWorkspaceInGroup - 1) % Config.options.overview.columns
x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex
y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex
z: root.windowZ 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 { ContentSection {
@@ -9,8 +9,8 @@ MouseArea {
id: root id: root
Layout.fillHeight: true Layout.fillHeight: true
implicitHeight: row.implicitHeight implicitHeight: appRow.implicitHeight
implicitWidth: row.implicitWidth implicitWidth: appRow.implicitWidth
hoverEnabled: true hoverEnabled: true
function showPreviewPopup(appEntry, button) { function showPreviewPopup(appEntry, button) {
@@ -21,31 +21,31 @@ MouseArea {
animation: Looks.transition.move.createObject(this) animation: Looks.transition.move.createObject(this)
} }
// Apps row WListView {
RowLayout { id: appRow
id: row
anchors { anchors {
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
} }
orientation: Qt.Horizontal
spacing: 0 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 { onHoverPreviewRequested: {
// TODO: Include only apps (and windows) in current workspace only | wait, does that even make sense in a Hyprland workflow? root.showPreviewPopup(appEntry, this);
model: ScriptModel {
objectProp: "appId"
values: TaskbarApps.apps.filter(app => app.appId !== "SEPARATOR")
} }
delegate: TaskAppButton { onHoverPreviewDismissed: {
required property var modelData previewPopup.close();
appEntry: modelData
onHoverPreviewRequested: {
root.showPreviewPopup(appEntry, this)
}
onHoverPreviewDismissed: {
previewPopup.close()
}
} }
} }
} }
@@ -56,5 +56,4 @@ MouseArea {
tasksHovered: root.containsMouse tasksHovered: root.containsMouse
anchor.window: root.QsWindow.window anchor.window: root.QsWindow.window
} }
} }
@@ -36,15 +36,34 @@ LockScreen {
Image { Image {
id: bg id: bg
z: 0 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) sourceSize: Qt.size(lockSurfaceItem.width, lockSurfaceItem.height)
source: Config.options.background.wallpaperPath source: Config.options.background.wallpaperPath
fillMode: Image.PreserveAspectCrop 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 { GaussianBlur {
z: 1 z: 1
anchors.fill: parent anchors.fill: bg
source: bg source: bg
radius: 100 radius: 100
samples: radius * 2 + 1 samples: radius * 2 + 1
@@ -67,7 +86,7 @@ LockScreen {
Interactables { Interactables {
id: interactables id: interactables
z: 2 z: 2
anchors.fill: parent anchors.fill: bg
} }
} }
@@ -83,12 +102,31 @@ LockScreen {
// } // }
function switchToFocusedView() { 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 { Item {
id: unfocusedContent id: unfocusedContent
anchors.fill: parent width: parent.width
height: parent.height
visible: !root.passwordView visible: !root.passwordView
ClockTextGroup { ClockTextGroup {
anchors { anchors {
@@ -177,7 +177,7 @@ Singleton {
property Component color: Component { property Component color: Component {
ColorAnimation { ColorAnimation {
duration: 120 duration: 80
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: transition.easing.bezierCurve.easeIn easing.bezierCurve: transition.easing.bezierCurve.easeIn
} }
@@ -6,5 +6,14 @@ import QtQuick.Controls
ListView { ListView {
id: root id: root
boundsBehavior: Flickable.DragOverBounds
ScrollBar.vertical: WScrollBar {} ScrollBar.vertical: WScrollBar {}
displaced: Transition {
animations: [Looks.transition.enter.createObject(this, {
property: "y"
})]
}
} }
@@ -53,10 +53,9 @@ BodyRectangle {
} }
} }
StyledListView { WListView {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
animateAppearance: false
clip: true clip: true
model: Notifications.appNameList 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
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
@@ -18,10 +19,44 @@ MouseArea {
implicitWidth: contentLayout.implicitWidth implicitWidth: contentLayout.implicitWidth
implicitHeight: contentLayout.implicitHeight 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 { ColumnLayout {
id: contentLayout id: contentLayout
anchors.fill: parent
spacing: 4 spacing: 4
width: root.width
Behavior on x {
animation: Looks.transition.enter.createObject(this)
}
GroupHeader { GroupHeader {
id: notifHeader id: notifHeader
@@ -29,7 +64,9 @@ MouseArea {
Layout.margins: 11 Layout.margins: 11
} }
ListView { WListView {
Layout.leftMargin: -Math.min(35, contentLayout.x)
Layout.rightMargin: -Layout.leftMargin
Layout.fillWidth: true Layout.fillWidth: true
implicitWidth: notifHeader.implicitWidth implicitWidth: notifHeader.implicitWidth
implicitHeight: contentHeight implicitHeight: contentHeight
@@ -40,14 +77,20 @@ MouseArea {
objectProp: "notificationId" objectProp: "notificationId"
} }
delegate: WSingleNotification { delegate: WSingleNotification {
id: singleNotif
required property int index required property int index
required property var modelData required property var modelData
width: ListView.view.width width: ListView.view.width
notification: modelData notification: modelData
groupExpandControlMessage: { groupExpandControlMessage: {
if (root.notifications.length <= 1) return ""; if (root.notifications.length <= 1)
if (!root.expanded) return Translation.tr("+%1 notifications").arg(root.notifications.length - 1); return "";
if (index === root.notifications.length - 1) return Translation.tr("See fewer"); 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 ""; return "";
} }
onGroupExpandToggle: { onGroupExpandToggle: {
@@ -94,11 +137,7 @@ MouseArea {
Layout.rightMargin: 3 Layout.rightMargin: 3
icon.name: "dismiss" icon.name: "dismiss"
onClicked: { onClicked: {
root.notifications.forEach(notif => { root.dismissAll();
Qt.callLater(() => {
Notifications.discardNotification(notif.notificationId);
});
});
} }
} }
} }
@@ -18,6 +18,18 @@ MouseArea {
signal groupExpandToggle signal groupExpandToggle
hoverEnabled: true hoverEnabled: true
function dismiss() {
Qt.callLater(() => {
Notifications.discardNotification(root.notification?.notificationId);
});
removeAnimation.start();
}
WNotificationDismissAnim {
id: removeAnimation
target: root
}
implicitHeight: contentItem.implicitHeight implicitHeight: contentItem.implicitHeight
implicitWidth: contentItem.implicitWidth implicitWidth: contentItem.implicitWidth
@@ -25,9 +37,25 @@ MouseArea {
animation: Looks.transition.enter.createObject(this) 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 { Rectangle {
id: contentItem id: contentItem
anchors.fill: parent width: parent.width
color: Looks.colors.bgPanelBody color: Looks.colors.bgPanelBody
radius: Looks.radius.medium radius: Looks.radius.medium
property real padding: 12 property real padding: 12
@@ -36,6 +64,10 @@ MouseArea {
border.width: 1 border.width: 1
border.color: ColorUtils.applyAlpha(Looks.colors.ambientShadow, 0.1) border.color: ColorUtils.applyAlpha(Looks.colors.ambientShadow, 0.1)
Behavior on x {
animation: Looks.transition.enter.createObject(this)
}
ColumnLayout { ColumnLayout {
id: notificationContent id: notificationContent
anchors.fill: parent anchors.fill: parent
@@ -128,11 +160,7 @@ MouseArea {
opacity: root.containsMouse ? 1 : 0 opacity: root.containsMouse ? 1 : 0
icon.name: "dismiss" icon.name: "dismiss"
implicitSize: 12 implicitSize: 12
onClicked: { onClicked: root.dismiss()
Qt.callLater(() => {
Notifications.discardNotification(root.notification?.notificationId);
});
}
} }
} }
@@ -86,7 +86,7 @@ WBarAttachedPanelContent {
id: searchBar id: searchBar
Layout.fillWidth: true Layout.fillWidth: true
implicitWidth: 832 // TODO: Make sizes naturally inferred 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 // verticalPadding: root.searching ? 32 : 16 // TODO: make this not nuke the panel
Synchronizer on searching { Synchronizer on searching {
property alias target: root.searching property alias target: root.searching
@@ -1,44 +1,214 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs import qs
import qs.services import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.functions import qs.modules.common.functions
import qs.modules.common.models
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.modules.waffle.looks import qs.modules.waffle.looks
import "window-layout.js" as WindowLayout
Rectangle { Rectangle {
id: root id: root
color: ColorUtils.transparentize(Looks.colors.bg1Base, 0.5) color: ColorUtils.transparentize(Looks.colors.bg1Base, 0.5)
property bool draggingWindow: false
property real openProgress: 0 property real openProgress: 0
property Item hoveredWorkspace: null
signal closed
Component.onCompleted: { Component.onCompleted: {
openAnim.start(); openAnim.start();
} }
function close() {
closeAnim.start();
}
PropertyAnimation { PropertyAnimation {
id: openAnim id: openAnim
target: root target: root
property: "openProgress" property: "openProgress"
to: 1 to: 1
duration: 200 duration: 250
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
} }
PropertyAnimation { SequentialAnimation {
id: closeAnim id: closeAnim
target: root
property: "openProgress" PropertyAnimation {
to: 0 target: root
duration: 200 property: "openProgress"
easing.type: Easing.BezierSpline to: 0
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn 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 // Workspaces
Rectangle { Rectangle {
id: wsBorder id: wsBorder
z: root.openProgress == 1 ? 1 : 2
property real sourceEdgeMargin: -(height + 8) + root.openProgress * (height + 16) property real sourceEdgeMargin: -(height + 8) + root.openProgress * (height + 16)
anchors { anchors {
left: parent.left left: parent.left
@@ -64,8 +234,9 @@ Rectangle {
color: Looks.colors.bgPanelFooterBase color: Looks.colors.bgPanelFooterBase
implicitHeight: 174 implicitHeight: 174
ListView { WListView {
id: workspaceListView
anchors { anchors {
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
@@ -73,22 +244,56 @@ Rectangle {
topMargin: 5 topMargin: 5
bottomMargin: 5 bottomMargin: 5
} }
flickableDirection: Flickable.HorizontalFlick
orientation: ListView.Horizontal
interactive: width == parent.width
width: Math.min(contentWidth + leftMargin + rightMargin, parent.width) width: Math.min(contentWidth + leftMargin + rightMargin, parent.width)
leftMargin: 5 leftMargin: 5
rightMargin: 5 rightMargin: 5
clip: true clip: true
orientation: ListView.Horizontal
spacing: 4 spacing: 4
model: ScriptModel { function reposition() {
values: { positionViewAtIndex(HyprlandData.activeWorkspace.id - 1, ListView.Contain);
const maxWorkspaceId = Math.max.apply(null, HyprlandData.workspaces.map(ws => ws.id)) }
return Array(maxWorkspaceId)
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 { delegate: TaskViewWorkspace {
id: workspaceItem
required property int index required property int index
workspace: index + 1 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 id: root
required property int workspace required property int workspace
property bool newWorkspace: false
property bool droppable: false
readonly property real screenWidth: QsWindow.window.width readonly property bool isActiveWorkspace: HyprlandData.activeWorkspace?.id === root.workspace
readonly property real screenHeight: QsWindow.window.height 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 screenAspectRatio: screenWidth / screenHeight
readonly property real screenScale: QsWindow.window.devicePixelRatio readonly property real windowScale: wallpaperHeight / screenHeight
readonly property real scale: 0.1148148148
height: ListView.view.height property real wallpaperHeight: 124
height: ListView.view?.height ?? 100
implicitWidth: 244 // for now implicitWidth: 244 // for now
onClicked: { colBackground: ColorUtils.transparentize(Looks.colors.bg2, (isActiveWorkspace || droppable) ? 0 : 1)
GlobalStates.overviewOpen = false; Behavior on color {
Hyprland.dispatch(`workspace ${root.workspace}`); 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 { ColumnLayout {
id: contentItem
anchors { anchors {
fill: parent fill: parent
leftMargin: 12 leftMargin: 12
@@ -45,15 +60,15 @@ WMouseAreaButton {
Layout.fillHeight: false Layout.fillHeight: false
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight 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 { Rectangle {
id: wsBg id: wsBg
height: 124 height: root.wallpaperHeight
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
color: Looks.colors.bg1Base color: Looks.colors.bg1
layer.enabled: true layer.enabled: true
layer.effect: OpacityMask { layer.effect: OpacityMask {
@@ -64,34 +79,67 @@ WMouseAreaButton {
} }
} }
StyledImage { // Workspace content
Loader {
anchors.fill: parent anchors.fill: parent
cache: true active: !root.newWorkspace
sourceSize: Qt.size(root.screenAspectRatio * 124, 124) sourceComponent: StyledImage {
source: Config.options.background.wallpaperPath cache: true
fillMode: Image.PreserveAspectCrop sourceSize: Qt.size(root.screenAspectRatio * root.wallpaperHeight, root.wallpaperHeight)
source: Config.options.background.wallpaperPath
fillMode: Image.PreserveAspectCrop
Repeater { Repeater {
model: ScriptModel { model: ScriptModel {
values: ToplevelManager.toplevels.values.filter(toplevel => { values: HyprlandData.toplevelsForWorkspace(root.workspace)
const address = `0x${toplevel.HyprlandToplevel?.address}`; }
var win = HyprlandData.windowByAddress[address]; delegate: ScreencopyView {
const inWorkspace = win?.workspace?.id === root.workspace; required property var modelData
return inWorkspace; readonly property var hyprlandWindowData: HyprlandData.windowByAddress[`0x${modelData.HyprlandToplevel?.address}`]
}) captureSource: modelData
} live: true
delegate: ScreencopyView { width: hyprlandWindowData?.size[0] * root.windowScale
required property var modelData height: hyprlandWindowData?.size[1] * root.windowScale
readonly property var hyprlandWindowData: HyprlandData.windowByAddress[`0x${modelData.HyprlandToplevel?.address}`] x: hyprlandWindowData?.at[0] * root.windowScale
captureSource: modelData y: hyprlandWindowData?.at[1] * root.windowScale
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
} }
} }
} }
// 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 { Loader {
id: panelLoader id: panelLoader
required property var modelData required property var modelData
active: GlobalStates.overviewOpen active: false
Connections {
target: GlobalStates
function onOverviewOpenChanged() {
if (GlobalStates.overviewOpen)
panelLoader.active = true;
}
}
sourceComponent: PanelWindow { sourceComponent: PanelWindow {
id: root id: root
property string searchingText: "" property string searchingText: ""
@@ -33,7 +40,7 @@ Scope {
WlrLayershell.namespace: "quickshell:wTaskView" WlrLayershell.namespace: "quickshell:wTaskView"
WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.layer: WlrLayer.Overlay
// WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent" color: "transparent"
anchors { anchors {
@@ -44,7 +51,26 @@ Scope {
} }
TaskViewContent { TaskViewContent {
id: taskViewContent
anchors.fill: parent 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)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate
"$SCRIPT_DIR/thumbgen.py" "$@" GIO_USE_VFS=local "$SCRIPT_DIR/thumbgen.py" "$@"
deactivate deactivate
@@ -15,7 +15,7 @@ import gi
from loguru import logger from loguru import logger
from tqdm import tqdm from tqdm import tqdm
gi.require_version("GnomeDesktop", "3.0") gi.require_version("GnomeDesktop", "4.0")
from gi.repository import Gio, GnomeDesktop # isort:skip from gi.repository import Gio, GnomeDesktop # isort:skip
thumbnail_size_map = { thumbnail_size_map = {
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
/** /**
@@ -21,6 +22,30 @@ Singleton {
property var monitors: [] property var monitors: []
property var layers: ({}) 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() { function updateWindowList() {
getClients.running = true; getClients.running = true;
} }
@@ -63,6 +88,7 @@ Singleton {
function onRawEvent(event) { function onRawEvent(event) {
// console.log("Hyprland raw event:", event.name); // console.log("Hyprland raw event:", event.name);
if (["openlayer", "closelayer", "screencast"].includes(event.name)) return;
updateAll() updateAll()
} }
} }
@@ -4,6 +4,7 @@ import qs.modules.common
import qs.modules.common.models import qs.modules.common.models
import qs.modules.common.functions import qs.modules.common.functions
import QtQuick import QtQuick
import Qt.labs.folderlistmodel
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -31,6 +32,34 @@ Singleton {
return acc; return acc;
}, []).sort() }, []).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: [ property var searchActions: [
{ {
action: "accentcolor", action: "accentcolor",
@@ -90,6 +119,9 @@ Singleton {
}, },
] ]
// Combined built-in and user actions
property var allActions: searchActions.concat(userActionScripts)
property string mathResult: "" property string mathResult: ""
property bool clipboardWorkSafetyActive: { property bool clipboardWorkSafetyActive: {
const enabled = Config.options.workSafety.enable.clipboard; const enabled = Config.options.workSafety.enable.clipboard;
@@ -273,7 +305,7 @@ Singleton {
Qt.openUrlExternally(url); Qt.openUrlExternally(url);
} }
}); });
const launcherActionObjects = root.searchActions.map(action => { const launcherActionObjects = root.allActions.map(action => {
const actionString = `${Config.options.search.prefix.action}${action.action}`; const actionString = `${Config.options.search.prefix.action}${action.action}`;
if (actionString.startsWith(root.query) || root.query.startsWith(actionString)) { if (actionString.startsWith(root.query) || root.query.startsWith(actionString)) {
return resultComp.createObject(null, { return resultComp.createObject(null, {
@@ -111,6 +111,14 @@ ApiStrategy {
return ({}) 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? // No candidates?
if (!dataJson.candidates) return {}; if (!dataJson.candidates) return {};
@@ -58,8 +58,17 @@ ApiStrategy {
// Real stuff // Real stuff
try { try {
const dataJson = JSON.parse(cleanData); 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 = ""; let newContent = "";
const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content; const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content;
const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content; const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content;
@@ -49,8 +49,17 @@ ApiStrategy {
// Real stuff // Real stuff
try { try {
const dataJson = JSON.parse(cleanData); 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 = ""; let newContent = "";
const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content; const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content;
const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content; const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content;
@@ -63,8 +63,6 @@ Wayland and X11.
%endif %endif
-DBUILD_SHARED_LIBS=OFF \ -DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DDISTRIBUTOR="Fedora COPR (errornointernet/quickshell)" \
-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=YES \
-DGIT_REVISION=%{commit} \ -DGIT_REVISION=%{commit} \
-DINSTALL_QML_PREFIX=%{_lib}/qt6/qml -DINSTALL_QML_PREFIX=%{_lib}/qt6/qml
%cmake_build %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 # Fedora dependencies list
# Audio # Audio
@@ -17,6 +27,7 @@ packages = [
"brightnessctl", "brightnessctl",
"ddcutil" "ddcutil"
] ]
install_opts = ["--setopt=install_weak_deps=False"]
# Basic # Basic
[groups.basic] [groups.basic]
+59 -40
View File
@@ -1,9 +1,15 @@
# This script is meant to be sourced. # This script is meant to be sourced.
# It's not for directly running. # 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 # FUNCTIONS
user_config=${REPO_ROOT}/sdata/dist-fedora/user_data.yaml # -------------------------
# Recording DNF Transaction ID # Recording DNF Transaction ID
function r() { function r() {
@@ -16,6 +22,42 @@ function r() {
[ "$original_id" == "$last_id" ] || yq -i ".dnf.transaction_ids += [ $last_id ]" "$user_config" || : [ "$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 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" 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 exit 1
@@ -33,61 +75,38 @@ v sudo dnf versionlock delete quickshell-git 2>/dev/null
# Install yq for parsing config files # Install yq for parsing config files
v sudo dnf install yq -y v sudo dnf install yq -y
# Development-tools # Install development tools
r v sudo dnf install @development-tools fedora-packager rpmdevtools fonts-rpm-macros qt6-rpm-macros -y r v sudo dnf install @development-tools fedora-packager -y
# COPR repositories # Install COPR repositories
v sudo dnf copr enable ririko66z/dots-hyprland -y copr_repos_json=$(yq -o=j '.copr.repos // []' "$deps_data_file")
v sudo dnf copr enable solopasha/hyprland -y eval "$(jq -r '@sh "copr_repos_array+=(\(.[]))"' <<<"$copr_repos_json")" # Fedora distro contains jq
v sudo dnf copr enable deltacopy/darkly -y for copr in ${copr_repos_array[@]}; do
v sudo dnf copr enable alternateved/eza -y v sudo dnf copr enable "$copr" -y
v sudo dnf copr enable atim/starship -y done
# 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}
}
# Build and install locally RPMS
showfun install_RPMS showfun install_RPMS
v 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") deps_data=$(yq -o=j '.' "$deps_data_file")
echo "Starting to install packages from $deps_data_file ..." echo "Starting to install packages from $deps_data_file ..."
while IFS= read -r deps_list_key; do while IFS= read -r deps_list_key; do
echo "Installing package list: $deps_list_key" echo "Installing package list: $deps_list_key"
install_opts=$(echo $deps_data | yq ".groups.\"$deps_list_key\" | select(has(\"install_opts\")) | .install_opts[]") 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 | .[]") package_list=$(echo $deps_data | yq ".groups.\"$deps_list_key\".packages | unique | .[]")
r v sudo dnf install -y $install_opts $package_list </dev/tty r v sudo dnf install -y $install_opts $package_list </dev/tty
echo "----------------------------------------" 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 # 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 -e "\n========================================"
echo "All installations are complete." echo "All installations are completed."
echo "========================================" 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. - 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()`` ` - 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 - 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 "${STY_YELLOW}"
printf "============WARNING/NOTE (1)============\n" 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 "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 "Or you can manually add the use flags for each package that requires it\n"
printf "${STY_RST}" printf "${STY_RST}"
pause pause
printf "${STY_YELLOW}" 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 "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 "Checkout the above README for potential bug fixes or additional information\n\n"
printf "${STY_RST}" printf "${STY_RST}"
+1 -1
View File
@@ -64,7 +64,7 @@ function showfun(){
function pause(){ function pause(){
if [ ! "$ask" == "false" ];then if [ ! "$ask" == "false" ];then
printf "${STY_FAINT}${STY_SLANT}" 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}" printf "${STY_RST}"
fi fi
} }
+1
View File
@@ -20,6 +20,7 @@ patterns:
- from: "dots/.config/fish" - from: "dots/.config/fish"
to: "$XDG_CONFIG_HOME/fish" to: "$XDG_CONFIG_HOME/fish"
mode: "sync" mode: "sync"
excludes: ["conf.d"]
condition: condition:
type: "shell" type: "shell"
value: "fish" value: "fish"
+1 -1
View File
@@ -30,7 +30,7 @@ esac
case "${SKIP_FISH}" in case "${SKIP_FISH}" in
true) sleep 0;; 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 esac
+29 -1
View File
@@ -67,6 +67,22 @@ rsync_dir__sync(){
x mkdir -p "$(dirname ${INSTALLED_LISTFILE})" 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 -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(){ function install_file(){
# NOTE: Do not add prefix `v` or `x` when using this function # NOTE: Do not add prefix `v` or `x` when using this function
local s=$1 local s=$1
@@ -124,6 +140,18 @@ function install_dir__skip_existed(){
v rsync_dir $s $t v rsync_dir $s $t
fi 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(){ function install_google_sans_flex(){
local font_name="Google Sans Flex" local font_name="Google Sans Flex"
local src_name="google-sans-flex" local src_name="google-sans-flex"
@@ -142,7 +170,7 @@ function install_google_sans_flex(){
x fc-cache -fv x fc-cache -fv
x cd $REPO_ROOT x cd $REPO_ROOT
x mkdir -p "$(dirname ${INSTALLED_LISTFILE})" x mkdir -p "$(dirname ${INSTALLED_LISTFILE})"
realpath -se "$2" >> "${INSTALLED_LISTFILE}" realpath -se "$target_dir" >> "${INSTALLED_LISTFILE}"
} }
##################################################################################### #####################################################################################