Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3f38e6414 | |||
| 6aedf6f8b7 | |||
| 4ea74ed516 | |||
| f00af9d70f | |||
| de11767d3b |
+1
-8
@@ -31,13 +31,6 @@ if("shell" IN_LIST ENABLE_MODULES)
|
||||
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
|
||||
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
|
||||
endforeach()
|
||||
|
||||
# Disable watching for changes
|
||||
file(READ shell.qml SHELL_QML)
|
||||
string(REPLACE "settings.watchFiles: true" "settings.watchFiles: false" SHELL_QML "${SHELL_QML}")
|
||||
file(WRITE "${CMAKE_BINARY_DIR}/qml/shell.qml" "${SHELL_QML}")
|
||||
install(FILES "${CMAKE_BINARY_DIR}/qml/shell.qml" DESTINATION "${INSTALL_QSCONFDIR}")
|
||||
|
||||
# Greeter
|
||||
install(FILES shell.qml DESTINATION "${INSTALL_QSCONFDIR}")
|
||||
install(DIRECTORY Greeter/ DESTINATION "${INSTALL_GREETERCONFDIR}")
|
||||
endif()
|
||||
|
||||
@@ -15,7 +15,6 @@ Text {
|
||||
color: DynamicColors.palette.m3onSurface
|
||||
font.family: Appearance.font.family.sans
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
linkColor: DynamicColors.palette.m3onPrimaryFixedVariant
|
||||
renderType: Text.NativeRendering
|
||||
textFormat: Text.PlainText
|
||||
|
||||
|
||||
+2
-12
@@ -22,7 +22,6 @@ Singleton {
|
||||
property alias notifs: adapter.notifs
|
||||
property alias osd: adapter.osd
|
||||
property alias overview: adapter.overview
|
||||
property alias plugins: adapter.plugins
|
||||
property bool recentlySaved: false
|
||||
property alias screenshot: adapter.screenshot
|
||||
property alias services: adapter.services
|
||||
@@ -141,8 +140,7 @@ Singleton {
|
||||
launcher: serializeLauncher(),
|
||||
colors: serializeColors(),
|
||||
dock: serializeDock(),
|
||||
screenshot: serializeScreenshot(),
|
||||
plugins: serializePlugins()
|
||||
screenshot: serializeScreenshot()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -291,13 +289,6 @@ Singleton {
|
||||
};
|
||||
}
|
||||
|
||||
function serializePlugins(): var {
|
||||
return {
|
||||
enabled: plugins.enabled,
|
||||
entries: plugins.entries
|
||||
};
|
||||
}
|
||||
|
||||
function serializeScreenshot(): var {
|
||||
return {
|
||||
enable_pp: screenshot.enable_pp,
|
||||
@@ -306,6 +297,7 @@ Singleton {
|
||||
drop_shadow: screenshot.drop_shadow,
|
||||
rounded_corners: screenshot.rounded_corners,
|
||||
shadow_blur_radius: screenshot.shadow_blur_radius,
|
||||
shadow_blur_passes: screenshot.shadow_blur_passes,
|
||||
shadow_color: screenshot.shadow_color,
|
||||
shadow_offset_x: screenshot.shadow_offset_x,
|
||||
shadow_offset_y: screenshot.shadow_offset_y
|
||||
@@ -467,8 +459,6 @@ Singleton {
|
||||
}
|
||||
property Overview overview: Overview {
|
||||
}
|
||||
property PluginConfig plugins: PluginConfig {
|
||||
}
|
||||
property Screenshot screenshot: Screenshot {
|
||||
}
|
||||
property Services services: Services {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import Quickshell.Io
|
||||
|
||||
JsonObject {
|
||||
property bool enabled: false
|
||||
property list<var> entries: [
|
||||
{
|
||||
id: "Plugin",
|
||||
enabled: false
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -6,6 +6,7 @@ JsonObject {
|
||||
property bool enable_pp: true
|
||||
property string mode: "manual"
|
||||
property bool rounded_corners: false
|
||||
property int shadow_blur_passes: 1
|
||||
property real shadow_blur_radius: 22.0
|
||||
property list<int> shadow_color: [0, 0, 0, 160]
|
||||
property real shadow_offset_x: 5.0
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import ZShell.Models
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property alias plugins: plugins.entries
|
||||
|
||||
FileSystemModel {
|
||||
id: plugins
|
||||
|
||||
nameFilters: ["*.qml"]
|
||||
path: Quickshell.env("HOME") + "/.config/zshell"
|
||||
recursive: false
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import ZShell.Models
|
||||
import qs.Config
|
||||
|
||||
Repeater {
|
||||
model: FetchPlugins.plugins
|
||||
|
||||
LazyLoader {
|
||||
required property FileSystemEntry modelData
|
||||
|
||||
activeAsync: Config.plugins.entries.some(p => {
|
||||
return p.id === modelData.baseName && p.enabled;
|
||||
})
|
||||
source: modelData.path
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ Singleton {
|
||||
PersistentProperties {
|
||||
id: props
|
||||
|
||||
property bool enabled: Hypr.options["animations:enabled"] === 0
|
||||
property bool enabled: Hypr.options.animations.enabled === 0
|
||||
|
||||
reloadableId: "gamemode"
|
||||
}
|
||||
|
||||
@@ -158,6 +158,5 @@ Singleton {
|
||||
|
||||
HyprExtras {
|
||||
id: extras
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -30,12 +30,17 @@ MouseArea {
|
||||
property real ey: screen.height
|
||||
required property LazyLoader loader
|
||||
property bool onClient
|
||||
property real realBorderWidth: onClient ? (Hypr.options["general:border_size"] ?? 1) : 2
|
||||
property real realRounding: onClient ? (Hypr.options["decoration:rounding"] ?? 0) : 0
|
||||
property real realBorderWidth: onClient ? (Hypr.options.general.border_size ?? 1) : 2
|
||||
property real realRounding: onClient ? (Hypr.options.decoration.rounding ?? 0) : 0
|
||||
property real rsx: Math.min(sx, ex)
|
||||
property real rsy: Math.min(sy, ey)
|
||||
readonly property real scaleRatio: Hypr.monitorFor(screen).scale
|
||||
required property ShellScreen screen
|
||||
property real sh: Math.abs(sy - ey)
|
||||
readonly property color shadowColor: Hypr.options.decoration.shadow.color
|
||||
readonly property var shadowOffset: Hypr.options.decoration.shadow.offset
|
||||
readonly property int shadowRange: Hypr.options.decoration.shadow.range
|
||||
readonly property int shadowRenderPower: Hypr.options.decoration.shadow.render_power
|
||||
property real ssx
|
||||
property real ssy
|
||||
property real sw: Math.abs(sx - ex)
|
||||
@@ -66,7 +71,7 @@ MouseArea {
|
||||
|
||||
function save(): void {
|
||||
const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`);
|
||||
const cmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--image"] : ["swappy", "-f"];
|
||||
const cmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--scale", root.scaleRatio, "--shadow-blur-radius", root.shadowRange, "--image"] : ["swappy", "-f"];
|
||||
ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached([...cmd, path]));
|
||||
closeAnim.start();
|
||||
}
|
||||
|
||||
+41
-82
@@ -1,82 +1,41 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property alias elapsed: props.elapsed
|
||||
property bool needsPause
|
||||
property bool needsStart
|
||||
property bool needsStop
|
||||
readonly property alias paused: props.paused
|
||||
readonly property alias running: props.running
|
||||
property list<string> startArgs
|
||||
|
||||
function start(extraArgs = []): void {
|
||||
needsStart = true;
|
||||
startArgs = extraArgs;
|
||||
checkProc.running = true;
|
||||
}
|
||||
|
||||
function stop(): void {
|
||||
needsStop = true;
|
||||
checkProc.running = true;
|
||||
}
|
||||
|
||||
function togglePause(): void {
|
||||
needsPause = true;
|
||||
checkProc.running = true;
|
||||
}
|
||||
|
||||
PersistentProperties {
|
||||
id: props
|
||||
|
||||
property real elapsed: 0
|
||||
property bool paused: false
|
||||
property bool running: false
|
||||
|
||||
reloadableId: "recorder"
|
||||
}
|
||||
|
||||
Process {
|
||||
id: checkProc
|
||||
|
||||
command: ["pidof", "gpu-screen-recorder"]
|
||||
running: true
|
||||
|
||||
onExited: code => {
|
||||
props.running = code === 0;
|
||||
|
||||
if (code === 0) {
|
||||
if (root.needsStop) {
|
||||
Quickshell.execDetached(["zshell-cli", "record", "record"]);
|
||||
props.running = false;
|
||||
props.paused = false;
|
||||
} else if (root.needsPause) {
|
||||
Quickshell.execDetached(["zshell-cli", "record", "record", "-p"]);
|
||||
props.paused = !props.paused;
|
||||
}
|
||||
} else if (root.needsStart) {
|
||||
Quickshell.execDetached(["zshell-cli", "record", "record", ...root.startArgs]);
|
||||
props.running = true;
|
||||
props.paused = false;
|
||||
props.elapsed = 0;
|
||||
}
|
||||
|
||||
root.needsStart = false;
|
||||
root.needsStop = false;
|
||||
root.needsPause = false;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onSecondsChanged(): void {
|
||||
props.elapsed++;
|
||||
}
|
||||
|
||||
target: Time // qmllint disable incompatible-type
|
||||
}
|
||||
}
|
||||
// pragma Singleton
|
||||
//
|
||||
// import Quickshell
|
||||
// import QtQuick
|
||||
//
|
||||
// Singleton {
|
||||
// id: root
|
||||
//
|
||||
// function start(extraArgs = []): void {
|
||||
// needsStart = true;
|
||||
// startArgs = extraArgs;
|
||||
// checkProc.running = true;
|
||||
// }
|
||||
//
|
||||
// PersistentProperties {
|
||||
// id: props
|
||||
//
|
||||
// property real elapsed: 0
|
||||
// property bool paused: false
|
||||
// property bool running: false
|
||||
//
|
||||
// reloadableId: "recorder"
|
||||
// }
|
||||
//
|
||||
// Process {
|
||||
// id: checkProc
|
||||
//
|
||||
// command: ["pidof", "gpu-screen-recorder"]
|
||||
// running: true
|
||||
//
|
||||
// onExited: code => {
|
||||
// props.running = code === 0;
|
||||
//
|
||||
// if (code === 0) {
|
||||
// if (root.needsStop) {
|
||||
// Quickshell.execDetached(["zshell-cli"]);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Quickshell
|
||||
import "../scripts/fzf.js" as Fzf
|
||||
import "../scripts/fuzzysort.js" as Fuzzy
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
property var extraOpts: ({})
|
||||
|
||||
@@ -136,10 +136,7 @@ CustomRect {
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
onLinkActivated: link => {
|
||||
if (Config.launcher.uwsm)
|
||||
Quickshell.execDetached(["app2unit", "-O", "--", link]);
|
||||
else
|
||||
Quickshell.execDetached(["xdg-open", link]);
|
||||
root.visibilities.sidebar = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,290 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Config
|
||||
import qs.Helpers
|
||||
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
required property var props
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: DynamicColors.tPalette.m3surfaceContainer
|
||||
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
|
||||
radius: Appearance.rounding.smallest
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.padding.large
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
RowLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
z: 1
|
||||
|
||||
CustomRect {
|
||||
color: Recorder.running ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3secondaryContainer
|
||||
implicitHeight: {
|
||||
const h = icon.implicitHeight + Appearance.padding.smaller * 2;
|
||||
return h - (h % 2);
|
||||
}
|
||||
implicitWidth: implicitHeight
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: -0.5
|
||||
anchors.verticalCenterOffset: 1.5
|
||||
color: Recorder.running ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSecondaryContainer
|
||||
font.pointSize: Appearance.font.size.large
|
||||
text: "screen_record"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
text: qsTr("Screen Recorder")
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Appearance.font.size.small
|
||||
text: Recorder.paused ? qsTr("Recording paused") : Recorder.running ? qsTr("Recording running") : qsTr("Recording off")
|
||||
}
|
||||
}
|
||||
|
||||
CustomSplitButton {
|
||||
active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0]
|
||||
disabled: Recorder.running
|
||||
|
||||
menuItems: [
|
||||
MenuItem {
|
||||
activeText: qsTr("Fullscreen")
|
||||
icon: "fullscreen"
|
||||
text: qsTr("Record fullscreen")
|
||||
|
||||
onClicked: Recorder.start()
|
||||
},
|
||||
MenuItem {
|
||||
activeText: qsTr("Region")
|
||||
icon: "screenshot_region"
|
||||
text: qsTr("Record region")
|
||||
|
||||
onClicked: Recorder.start(["-r"])
|
||||
},
|
||||
MenuItem {
|
||||
activeText: qsTr("Fullscreen")
|
||||
icon: "select_to_speak"
|
||||
text: qsTr("Record fullscreen with sound")
|
||||
|
||||
onClicked: Recorder.start(["-s"])
|
||||
},
|
||||
MenuItem {
|
||||
activeText: qsTr("Region")
|
||||
icon: "volume_up"
|
||||
text: qsTr("Record region with sound")
|
||||
|
||||
onClicked: Recorder.start(["-s", "-r"])
|
||||
}
|
||||
]
|
||||
|
||||
menu.onItemSelected: item => root.props.recordingMode = item.icon + item.text
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: listOrControls
|
||||
|
||||
property bool running: Recorder.running
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
asynchronous: true
|
||||
sourceComponent: running ? recordingControls : recordingList
|
||||
|
||||
Behavior on Layout.preferredHeight {
|
||||
id: locHeightAnim
|
||||
|
||||
enabled: false
|
||||
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on running {
|
||||
SequentialAnimation {
|
||||
ParallelAnimation {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
easing: Appearance.anim.curves.standardAccel
|
||||
property: "scale"
|
||||
target: listOrControls
|
||||
to: 0.7
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
easing: Appearance.anim.curves.standardAccel
|
||||
property: "opacity"
|
||||
target: listOrControls
|
||||
to: 0
|
||||
}
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "enabled"
|
||||
target: locHeightAnim
|
||||
value: true
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "enabled"
|
||||
target: locHeightAnim
|
||||
value: false
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
easing: Appearance.anim.curves.standardDecel
|
||||
property: "scale"
|
||||
target: listOrControls
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
easing: Appearance.anim.curves.standardDecel
|
||||
property: "opacity"
|
||||
target: listOrControls
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: recordingList
|
||||
|
||||
RecordingList {
|
||||
props: root.props
|
||||
visibilities: root.visibilities
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: recordingControls
|
||||
|
||||
RowLayout {
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
CustomRect {
|
||||
color: Recorder.paused ? DynamicColors.palette.m3tertiary : DynamicColors.palette.m3error
|
||||
implicitHeight: recText.implicitHeight + Appearance.padding.smaller * 2
|
||||
implicitWidth: recText.implicitWidth + Appearance.padding.normal * 2
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
SequentialAnimation on opacity {
|
||||
alwaysRunToEnd: true
|
||||
loops: Animation.Infinite
|
||||
running: !Recorder.paused
|
||||
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.large
|
||||
easing: Appearance.anim.curves.emphasizedAccel
|
||||
from: 1
|
||||
to: 0
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.extraLarge
|
||||
easing: Appearance.anim.curves.emphasizedDecel
|
||||
from: 0
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: recText
|
||||
|
||||
anchors.centerIn: parent
|
||||
animate: true
|
||||
color: Recorder.paused ? DynamicColors.palette.m3onTertiary : DynamicColors.palette.m3onError
|
||||
font.family: Appearance.font.family.mono
|
||||
text: Recorder.paused ? "PAUSED" : "REC"
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
text: {
|
||||
const elapsed = Recorder.elapsed;
|
||||
|
||||
const hours = Math.floor(elapsed / 3600);
|
||||
const mins = Math.floor((elapsed % 3600) / 60);
|
||||
const secs = Math.floor(elapsed % 60).toString().padStart(2, "0");
|
||||
|
||||
let time;
|
||||
if (hours > 0)
|
||||
time = `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
|
||||
else
|
||||
time = `${mins}:${secs}`;
|
||||
|
||||
return qsTr("Recording for %1").arg(time);
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
IconButton {
|
||||
checked: Recorder.paused
|
||||
font.pointSize: Appearance.font.size.large
|
||||
icon: Recorder.paused ? "play_arrow" : "pause"
|
||||
label.animate: true
|
||||
toggle: true
|
||||
type: IconButton.Tonal
|
||||
|
||||
onClicked: {
|
||||
Recorder.togglePause();
|
||||
internalChecked = Recorder.paused;
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
font.pointSize: Appearance.font.size.large
|
||||
icon: "stop"
|
||||
inactiveColour: DynamicColors.palette.m3error
|
||||
inactiveOnColour: DynamicColors.palette.m3onError
|
||||
|
||||
onClicked: Recorder.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import ZShell.Models
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Paths
|
||||
import qs.Config
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
required property var props
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
spacing: 0
|
||||
|
||||
WrapperMouseArea {
|
||||
Layout.fillWidth: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
|
||||
|
||||
RowLayout {
|
||||
spacing: Appearance.spacing.smaller
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pointSize: Appearance.font.size.large
|
||||
text: "list"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
text: qsTr("Recordings")
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: root.props.recordingListExpanded ? "unfold_less" : "unfold_more"
|
||||
label.animate: true
|
||||
type: IconButton.Text
|
||||
|
||||
onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomListView {
|
||||
id: list
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: -Appearance.spacing.small
|
||||
clip: true
|
||||
implicitHeight: (Appearance.font.size.larger + Appearance.padding.small) * (root.props.recordingListExpanded ? 10 : 3)
|
||||
|
||||
CustomScrollBar.vertical: CustomScrollBar {
|
||||
flickable: list
|
||||
}
|
||||
add: Transition {
|
||||
Anim {
|
||||
from: 0
|
||||
property: "opacity"
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
from: 0.5
|
||||
property: "scale"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
delegate: RowLayout {
|
||||
id: recording
|
||||
|
||||
property string baseName
|
||||
required property FileSystemEntry modelData
|
||||
|
||||
anchors.left: list.contentItem.left
|
||||
anchors.right: list.contentItem.right
|
||||
anchors.rightMargin: Appearance.spacing.small
|
||||
spacing: Appearance.spacing.small / 2
|
||||
|
||||
Component.onCompleted: baseName = modelData.baseName
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: Appearance.spacing.small / 2
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
text: {
|
||||
const time = recording.baseName;
|
||||
const matches = time.match(/^recording_(\d{4})(\d{2})(\d{2})_(\d{2})-(\d{2})-(\d{2})/);
|
||||
if (!matches)
|
||||
return time;
|
||||
const date = new Date(...matches.slice(1));
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
return qsTr("Recording at %1").arg(Qt.formatDateTime(date, Qt.locale()));
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "play_arrow"
|
||||
type: IconButton.Text
|
||||
|
||||
onClicked: {
|
||||
root.visibilities.sidebar = false;
|
||||
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.playback, recording.modelData.path]);
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "folder"
|
||||
type: IconButton.Text
|
||||
|
||||
onClicked: {
|
||||
root.visibilities.sidebar = false;
|
||||
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.explorer, recording.modelData.path]);
|
||||
}
|
||||
}
|
||||
}
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
model: FileSystemModel {
|
||||
nameFilters: ["recording_*.mp4"]
|
||||
path: Paths.recsdir
|
||||
sortReverse: true
|
||||
}
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
|
||||
Anim {
|
||||
property: "scale"
|
||||
to: 0.5
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
anchors.centerIn: parent
|
||||
asynchronous: true
|
||||
opacity: list.count === 0 ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredHeight: root.props.recordingListExpanded ? implicitHeight : 0
|
||||
color: DynamicColors.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.extraLarge
|
||||
opacity: root.props.recordingListExpanded ? 1 : 0
|
||||
scale: root.props.recordingListExpanded ? 1 : 0
|
||||
text: "scan_delete"
|
||||
|
||||
Behavior on Layout.preferredHeight {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Appearance.spacing.smaller
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: !root.props.recordingListExpanded ? implicitWidth : 0
|
||||
color: DynamicColors.palette.m3outline
|
||||
opacity: !root.props.recordingListExpanded ? 1 : 0
|
||||
scale: !root.props.recordingListExpanded ? 1 : 0
|
||||
text: "scan_delete"
|
||||
|
||||
Behavior on Layout.preferredWidth {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
color: DynamicColors.palette.m3outline
|
||||
text: qsTr("No recordings found")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Modules.Notifications.Sidebar.Utils.Cards
|
||||
import qs.Config
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Item popouts
|
||||
required property PersistentProperties props
|
||||
required property var props
|
||||
required property var visibilities
|
||||
|
||||
implicitHeight: layout.implicitHeight
|
||||
@@ -23,12 +22,6 @@ Item {
|
||||
IdleInhibit {
|
||||
}
|
||||
|
||||
Record {
|
||||
props: root.props
|
||||
visibilities: root.visibilities
|
||||
z: 1
|
||||
}
|
||||
|
||||
Toggles {
|
||||
popouts: root.popouts
|
||||
visibilities: root.visibilities
|
||||
|
||||
@@ -100,15 +100,13 @@ Item {
|
||||
icon: `brightness_${(Math.round(value * 6) + 1)}`
|
||||
value: root.brightness
|
||||
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
onMoved: {
|
||||
if (Config.osd.allMonBrightness) {
|
||||
root.monitor?.setBrightness(value);
|
||||
} else {
|
||||
for (const mon of Brightness.monitors) {
|
||||
mon.setBrightness(value);
|
||||
}
|
||||
} else {
|
||||
root.monitor?.setBrightness(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,12 +116,6 @@ Item {
|
||||
key: "updates"
|
||||
name: "Updates"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
icon: "extension"
|
||||
key: "plugins"
|
||||
name: "Extensions"
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
|
||||
@@ -19,8 +19,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 0
|
||||
name: "Media update interval"
|
||||
min: 0
|
||||
object: Config.dashboard
|
||||
setting: "mediaUpdateInterval"
|
||||
step: 50
|
||||
@@ -30,8 +30,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 0
|
||||
name: "Resource update interval"
|
||||
min: 0
|
||||
object: Config.dashboard
|
||||
setting: "resourceUpdateInterval"
|
||||
step: 50
|
||||
@@ -41,8 +41,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 0
|
||||
name: "Drag threshold"
|
||||
min: 0
|
||||
object: Config.dashboard
|
||||
setting: "dragThreshold"
|
||||
}
|
||||
@@ -107,112 +107,112 @@ SettingsPage {
|
||||
}
|
||||
}
|
||||
|
||||
// SettingsSection {
|
||||
// sectionId: "Layout Sizes"
|
||||
//
|
||||
// SettingsHeader {
|
||||
// name: "Layout Sizes"
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Tab indicator height"
|
||||
// value: String(Config.dashboard.sizes.tabIndicatorHeight)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Tab indicator spacing"
|
||||
// value: String(Config.dashboard.sizes.tabIndicatorSpacing)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Info width"
|
||||
// value: String(Config.dashboard.sizes.infoWidth)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Info icon size"
|
||||
// value: String(Config.dashboard.sizes.infoIconSize)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Date time width"
|
||||
// value: String(Config.dashboard.sizes.dateTimeWidth)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Media width"
|
||||
// value: String(Config.dashboard.sizes.mediaWidth)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Media progress sweep"
|
||||
// value: String(Config.dashboard.sizes.mediaProgressSweep)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Media progress thickness"
|
||||
// value: String(Config.dashboard.sizes.mediaProgressThickness)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Resource progress thickness"
|
||||
// value: String(Config.dashboard.sizes.resourceProgessThickness)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Weather width"
|
||||
// value: String(Config.dashboard.sizes.weatherWidth)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Media cover art size"
|
||||
// value: String(Config.dashboard.sizes.mediaCoverArtSize)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Media visualiser size"
|
||||
// value: String(Config.dashboard.sizes.mediaVisualiserSize)
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingReadOnly {
|
||||
// name: "Resource size"
|
||||
// value: String(Config.dashboard.sizes.resourceSize)
|
||||
// }
|
||||
// }
|
||||
SettingsSection {
|
||||
sectionId: "Layout Sizes"
|
||||
|
||||
SettingsHeader {
|
||||
name: "Layout Sizes"
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Tab indicator height"
|
||||
value: String(Config.dashboard.sizes.tabIndicatorHeight)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Tab indicator spacing"
|
||||
value: String(Config.dashboard.sizes.tabIndicatorSpacing)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Info width"
|
||||
value: String(Config.dashboard.sizes.infoWidth)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Info icon size"
|
||||
value: String(Config.dashboard.sizes.infoIconSize)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Date time width"
|
||||
value: String(Config.dashboard.sizes.dateTimeWidth)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Media width"
|
||||
value: String(Config.dashboard.sizes.mediaWidth)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Media progress sweep"
|
||||
value: String(Config.dashboard.sizes.mediaProgressSweep)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Media progress thickness"
|
||||
value: String(Config.dashboard.sizes.mediaProgressThickness)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Resource progress thickness"
|
||||
value: String(Config.dashboard.sizes.resourceProgessThickness)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Weather width"
|
||||
value: String(Config.dashboard.sizes.weatherWidth)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Media cover art size"
|
||||
value: String(Config.dashboard.sizes.mediaCoverArtSize)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Media visualiser size"
|
||||
value: String(Config.dashboard.sizes.mediaVisualiserSize)
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingReadOnly {
|
||||
name: "Resource size"
|
||||
value: String(Config.dashboard.sizes.resourceSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,18 +103,6 @@ SettingsPage {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSection {
|
||||
sectionId: "Greeter"
|
||||
|
||||
SettingsHeader {
|
||||
name: "Greeter"
|
||||
}
|
||||
|
||||
SettingsIconButton {
|
||||
name: "Install wallpaper and color scheme to greeter"
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSection {
|
||||
sectionId: "Idle"
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import qs.Modules.Settings.Controls
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property bool shouldBeActive: true
|
||||
|
||||
function addTimeoutEntry() {
|
||||
let list = [...Config.general.idle.timeouts];
|
||||
|
||||
@@ -42,26 +40,8 @@ ColumnLayout {
|
||||
Config.save();
|
||||
}
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: shouldBeActive ? implicitHeight : 0
|
||||
opacity: shouldBeActive ? 1 : 0
|
||||
scale: shouldBeActive ? 1 : 0.8
|
||||
Layout.fillWidth: true
|
||||
spacing: Appearance.spacing.smaller
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
Settings {
|
||||
name: "Idle Monitors"
|
||||
@@ -72,8 +52,6 @@ ColumnLayout {
|
||||
|
||||
SettingList {
|
||||
Layout.fillWidth: true
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
|
||||
onAddActiveActionRequested: {
|
||||
root.updateTimeoutEntry(index, "activeAction", "");
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import qs.Modules.Settings.Controls
|
||||
import qs.Config
|
||||
|
||||
SettingsPage {
|
||||
SettingsSection {
|
||||
sectionId: "Plugins"
|
||||
|
||||
SettingsHeader {
|
||||
name: "Plugins"
|
||||
}
|
||||
|
||||
SettingBarEntryList {
|
||||
name: "Enable or disable plugins"
|
||||
object: Config.plugins
|
||||
setting: "entries"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ SettingsPage {
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
@@ -51,34 +51,34 @@ SettingsPage {
|
||||
name: "Corner radius"
|
||||
object: Config.screenshot
|
||||
setting: "corner_radius"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
step: 1
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Enable drop shadow"
|
||||
object: Config.screenshot
|
||||
setting: "drop_shadow"
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Enable rounded corners"
|
||||
object: Config.screenshot
|
||||
setting: "rounded_corners"
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
@@ -86,23 +86,36 @@ SettingsPage {
|
||||
name: "Shadow blur radius"
|
||||
object: Config.screenshot
|
||||
setting: "shadow_blur_radius"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
step: 1
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Shadow color broken atm"
|
||||
object: Config.Screenshot
|
||||
setting: "shadow_color"
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 1
|
||||
name: "Shadow passes"
|
||||
object: Config.screenshot
|
||||
setting: "shadow_blur_passes"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
step: 1
|
||||
}
|
||||
|
||||
Separator {
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
@@ -110,12 +123,12 @@ SettingsPage {
|
||||
name: "Shadow offset X"
|
||||
object: Config.screenshot
|
||||
setting: "shadow_offset_x"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
step: 1
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
@@ -123,8 +136,8 @@ SettingsPage {
|
||||
name: "Shadow offset Y"
|
||||
object: Config.screenshot
|
||||
setting: "shadow_offset_y"
|
||||
shouldBeActive: Config.screenshot.mode === "manual"
|
||||
step: 1
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 1
|
||||
name: "Max toasts"
|
||||
min: 1
|
||||
object: Config.utilities
|
||||
setting: "maxToasts"
|
||||
}
|
||||
@@ -29,8 +29,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 1
|
||||
name: "Panel width"
|
||||
min: 1
|
||||
object: Config.utilities.sizes
|
||||
setting: "width"
|
||||
}
|
||||
@@ -39,8 +39,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 1
|
||||
name: "Toast width"
|
||||
min: 1
|
||||
object: Config.utilities.sizes
|
||||
setting: "toastWidth"
|
||||
}
|
||||
@@ -77,100 +77,100 @@ SettingsPage {
|
||||
setting: "gameModeChanged"
|
||||
}
|
||||
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingSwitch {
|
||||
// name: "Do not disturb changed"
|
||||
// object: Config.utilities.toasts
|
||||
// setting: "dndChanged"
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingSwitch {
|
||||
// name: "Audio output changed"
|
||||
// object: Config.utilities.toasts
|
||||
// setting: "audioOutputChanged"
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingSwitch {
|
||||
// name: "Audio input changed"
|
||||
// object: Config.utilities.toasts
|
||||
// setting: "audioInputChanged"
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingSwitch {
|
||||
// name: "Caps lock changed"
|
||||
// object: Config.utilities.toasts
|
||||
// setting: "capsLockChanged"
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingSwitch {
|
||||
// name: "Num lock changed"
|
||||
// object: Config.utilities.toasts
|
||||
// setting: "numLockChanged"
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingSwitch {
|
||||
// name: "Keyboard layout changed"
|
||||
// object: Config.utilities.toasts
|
||||
// setting: "kbLayoutChanged"
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingSwitch {
|
||||
// name: "VPN changed"
|
||||
// object: Config.utilities.toasts
|
||||
// setting: "vpnChanged"
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingSwitch {
|
||||
// name: "Now playing"
|
||||
// object: Config.utilities.toasts
|
||||
// setting: "nowPlaying"
|
||||
// }
|
||||
Separator {
|
||||
}
|
||||
|
||||
// SettingsSection {
|
||||
// sectionId: "VPN"
|
||||
//
|
||||
// SettingsHeader {
|
||||
// name: "VPN"
|
||||
// }
|
||||
//
|
||||
// SettingSwitch {
|
||||
// name: "Enable VPN integration"
|
||||
// object: Config.utilities.vpn
|
||||
// setting: "enabled"
|
||||
// }
|
||||
//
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// SettingStringList {
|
||||
// name: "Provider"
|
||||
// addLabel: qsTr("Add VPN provider")
|
||||
// object: Config.utilities.vpn
|
||||
// setting: "provider"
|
||||
// }
|
||||
// }
|
||||
SettingSwitch {
|
||||
name: "Do not disturb changed"
|
||||
object: Config.utilities.toasts
|
||||
setting: "dndChanged"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Audio output changed"
|
||||
object: Config.utilities.toasts
|
||||
setting: "audioOutputChanged"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Audio input changed"
|
||||
object: Config.utilities.toasts
|
||||
setting: "audioInputChanged"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Caps lock changed"
|
||||
object: Config.utilities.toasts
|
||||
setting: "capsLockChanged"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Num lock changed"
|
||||
object: Config.utilities.toasts
|
||||
setting: "numLockChanged"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Keyboard layout changed"
|
||||
object: Config.utilities.toasts
|
||||
setting: "kbLayoutChanged"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "VPN changed"
|
||||
object: Config.utilities.toasts
|
||||
setting: "vpnChanged"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Now playing"
|
||||
object: Config.utilities.toasts
|
||||
setting: "nowPlaying"
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSection {
|
||||
sectionId: "VPN"
|
||||
|
||||
SettingsHeader {
|
||||
name: "VPN"
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Enable VPN integration"
|
||||
object: Config.utilities.vpn
|
||||
setting: "enabled"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingStringList {
|
||||
name: "Provider"
|
||||
addLabel: qsTr("Add VPN provider")
|
||||
object: Config.utilities.vpn
|
||||
setting: "provider"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +79,6 @@ Item {
|
||||
stack.push(screenshot);
|
||||
else if (currentCategory === "updates")
|
||||
stack.push(updates);
|
||||
else if (currentCategory === "plugins")
|
||||
stack.push(plugins);
|
||||
}
|
||||
|
||||
target: root
|
||||
@@ -247,11 +245,4 @@ Item {
|
||||
Cat.SystemUpdates {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: plugins
|
||||
|
||||
Cat.Plugins {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,9 +127,6 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
Separator {
|
||||
Layout.fillWidth: true
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -210,8 +207,6 @@ ColumnLayout {
|
||||
StringListEditor {
|
||||
Layout.fillWidth: true
|
||||
addLabel: qsTr("Add command argument")
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
values: [...(modelData.command ?? [])]
|
||||
|
||||
onListEdited: function (values) {
|
||||
@@ -220,9 +215,6 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
Separator {
|
||||
Layout.fillWidth: true
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -241,9 +233,6 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
Separator {
|
||||
Layout.fillWidth: true
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
||||
@@ -6,7 +6,7 @@ import qs.Components
|
||||
import qs.Config
|
||||
import qs.Helpers
|
||||
|
||||
CustomRect {
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
|
||||
@@ -43,9 +43,10 @@ CustomRect {
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: shouldBeActive ? layout.implicitHeight : 0
|
||||
height: shouldBeActive ? implicitHeight : 0
|
||||
opacity: shouldBeActive ? 1 : 0
|
||||
scale: shouldBeActive ? 1 : 0.8
|
||||
spacing: Appearance.spacing.smaller
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -76,14 +77,6 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Appearance.spacing.smaller
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
@@ -128,17 +121,15 @@ CustomRect {
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 33
|
||||
Layout.preferredWidth: Math.max(Math.min(fromTextField.contentWidth + Appearance.padding.large * 2, 550), 50)
|
||||
color: DynamicColors.tPalette.m3surfaceContainerHigh
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
CustomTextField {
|
||||
id: fromTextField
|
||||
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Appearance.padding.normal
|
||||
anchors.rightMargin: Appearance.padding.normal
|
||||
text: modelData.from ?? ""
|
||||
|
||||
onEditingFinished: root.updateAlias(index, "from", text)
|
||||
@@ -163,17 +154,15 @@ CustomRect {
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 33
|
||||
Layout.preferredWidth: Math.max(Math.min(toTextField.contentWidth + Appearance.padding.large * 2, 550), 50)
|
||||
color: DynamicColors.tPalette.m3surfaceContainerHigh
|
||||
radius: Appearance.rounding.full
|
||||
color: DynamicColors.tPalette.m3surface
|
||||
radius: Appearance.rounding.small
|
||||
|
||||
CustomTextField {
|
||||
id: toTextField
|
||||
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Appearance.padding.normal
|
||||
anchors.rightMargin: Appearance.padding.normal
|
||||
text: modelData.to ?? ""
|
||||
|
||||
onEditingFinished: root.updateAlias(index, "to", text)
|
||||
@@ -200,4 +189,3 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,9 +194,6 @@ Item {
|
||||
}
|
||||
|
||||
Separator {
|
||||
Layout.fillWidth: true
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -228,9 +225,6 @@ Item {
|
||||
}
|
||||
|
||||
Separator {
|
||||
Layout.fillWidth: true
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
}
|
||||
|
||||
Item {
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Paths
|
||||
import qs.Components
|
||||
import qs.Config
|
||||
import qs.Helpers
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property alias button: iButton
|
||||
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
|
||||
required property string name
|
||||
property bool shouldBeActive: true
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
|
||||
opacity: shouldBeActive ? 1 : 0
|
||||
scale: shouldBeActive ? 1 : 0.8
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -Appearance.padding.smaller
|
||||
color: DynamicColors.palette.m3primaryContainer
|
||||
opacity: root.highlighted ? 0.5 : 0
|
||||
radius: Appearance.rounding.small
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.normal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Appearance.padding.small
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
CustomText {
|
||||
id: text
|
||||
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
text: root.name
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: iButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
icon: "download"
|
||||
|
||||
onClicked: {
|
||||
const lockBg = `${Paths.state}/lockscreen_bg.png`;
|
||||
const scheme = `${Paths.state}/scheme.json`;
|
||||
const face = `${Paths.home}/.face`;
|
||||
const destination = "/etc/zshell-greeter/images";
|
||||
Quickshell.execDetached(["pkexec", "sh", "-c", `mkdir -p ${destination}; cp ${lockBg} ${destination}; cp ${scheme} /etc/zshell-greeter; cp ${face} ${destination}`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
#include "hyprextras.hpp"
|
||||
#include "hyprdevices.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <qdir.h>
|
||||
#include <qcolor.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qmetatype.h>
|
||||
#include <qobject.h>
|
||||
#include <qregularexpression.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
@@ -163,6 +170,86 @@ static QString buildHlConfigCall(const QString& key, const QVariant& value) {
|
||||
return out;
|
||||
}
|
||||
|
||||
static QColor colorFromInt(quint32 value) {
|
||||
const int a = (value >> 24) & 0xFF;
|
||||
const int r = (value >> 16) & 0xFF;
|
||||
const int g = (value >> 8) & 0xFF;
|
||||
const int b = value & 0xFF;
|
||||
|
||||
return QColor(r, g, b, a);
|
||||
}
|
||||
|
||||
static QVariant parseGetOptionValue(const QJsonObject& obj) {
|
||||
if (obj.contains(QStringLiteral("bool"))) {
|
||||
return obj.value(QStringLiteral("bool")).toBool();
|
||||
}
|
||||
|
||||
if (obj.contains(QStringLiteral("int"))) {
|
||||
const auto value = obj.value(QStringLiteral("int")).toInt();
|
||||
|
||||
const auto option = obj.value(QStringLiteral("option")).toString();
|
||||
|
||||
if (option.contains(QStringLiteral("color")) || option.contains(QStringLiteral("col."))) {
|
||||
return colorFromInt(static_cast<quint32>(value));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
if (obj.contains(QStringLiteral("float"))) {
|
||||
return obj.value(QStringLiteral("float")).toDouble();
|
||||
}
|
||||
|
||||
if (obj.contains(QStringLiteral("str"))) {
|
||||
return obj.value(QStringLiteral("str")).toString();
|
||||
}
|
||||
|
||||
if (obj.contains(QStringLiteral("current"))) {
|
||||
return obj.value(QStringLiteral("current")).toVariant();
|
||||
}
|
||||
|
||||
if (obj.contains(QStringLiteral("value"))) {
|
||||
return obj.value(QStringLiteral("value")).toVariant();
|
||||
}
|
||||
|
||||
if (obj.contains(QStringLiteral("vec2"))) {
|
||||
return obj.value(QStringLiteral("vec2")).toVariant();
|
||||
}
|
||||
|
||||
if (obj.contains(QStringLiteral("data"))) {
|
||||
const auto data = obj.value(QStringLiteral("data"));
|
||||
if (data.isObject()) {
|
||||
const auto d = data.toObject();
|
||||
if (d.contains(QStringLiteral("current"))) {
|
||||
return d.value(QStringLiteral("current")).toVariant();
|
||||
}
|
||||
if (d.contains(QStringLiteral("value"))) {
|
||||
return d.value(QStringLiteral("value")).toVariant();
|
||||
}
|
||||
} else {
|
||||
return data.toVariant();
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static void insertNestedValue(QVariantMap& root, const QStringList& path, const QVariant& value) {
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.size() == 1) {
|
||||
root.insert(path.first(), value);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString head = path.first();
|
||||
QVariantMap child = root.value(head).toMap();
|
||||
insertNestedValue(child, path.mid(1), value);
|
||||
root.insert(head, child);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
HyprExtras::HyprExtras(QObject* parent)
|
||||
@@ -203,7 +290,7 @@ HyprExtras::HyprExtras(QObject* parent)
|
||||
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
|
||||
}
|
||||
|
||||
QVariantHash HyprExtras::options() const {
|
||||
QVariantMap HyprExtras::options() const {
|
||||
return m_options;
|
||||
}
|
||||
|
||||
@@ -269,30 +356,64 @@ void HyprExtras::refreshOptions() {
|
||||
m_optionsRefresh->close();
|
||||
}
|
||||
|
||||
m_optionsRefresh = makeRequestJson(QStringLiteral("descriptions"), [this](bool success, const QJsonDocument& response) {
|
||||
m_optionsRefresh.reset();
|
||||
if (!success) {
|
||||
++m_optionsRefreshGeneration;
|
||||
const quint64 generation = m_optionsRefreshGeneration;
|
||||
|
||||
static const QStringList optionKeys = {
|
||||
QStringLiteral("general:border_size"),
|
||||
QStringLiteral("decoration:rounding"),
|
||||
QStringLiteral("animations:enabled"),
|
||||
QStringLiteral("decoration:shadow:enabled"),
|
||||
QStringLiteral("decoration:shadow:offset"),
|
||||
QStringLiteral("decoration:shadow:color"),
|
||||
QStringLiteral("decoration:shadow:range"),
|
||||
QStringLiteral("decoration:shadow:render_power"),
|
||||
};
|
||||
|
||||
auto nextOptions = std::make_shared<QVariantMap>();
|
||||
|
||||
auto step = std::make_shared<std::function<void(int)> >();
|
||||
*step = [this, generation, nextOptions, step](int index) {
|
||||
if (generation != m_optionsRefreshGeneration) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto options = response.array();
|
||||
bool dirty = false;
|
||||
|
||||
for (const auto& o : std::as_const(options)) {
|
||||
const auto obj = o.toObject();
|
||||
const auto key = obj.value(QStringLiteral("value")).toString();
|
||||
const auto value = obj.value(QStringLiteral("data")).toObject().value(QStringLiteral("current")).toVariant();
|
||||
|
||||
if (m_options.value(key) != value) {
|
||||
dirty = true;
|
||||
m_options.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
if (index >= optionKeys.size()) {
|
||||
if (m_options != *nextOptions) {
|
||||
m_options = *nextOptions;
|
||||
emit optionsChanged();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const QString key = optionKeys.at(index);
|
||||
|
||||
m_optionsRefresh = makeRequestJson(
|
||||
QStringLiteral("getoption ") + key,
|
||||
[this, generation, nextOptions, step, index, key](bool success, const QJsonDocument& response)
|
||||
{
|
||||
m_optionsRefresh.reset();
|
||||
|
||||
if (generation != m_optionsRefreshGeneration) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (success && response.isObject()) {
|
||||
const QVariant value = parseGetOptionValue(response.object());
|
||||
if (value.isValid()) {
|
||||
insertNestedValue(*nextOptions, key.split(QLatin1Char(':'), Qt::SkipEmptyParts), value);
|
||||
} else {
|
||||
qCWarning(lcHypr) << "refreshOptions: getoption returned no usable value for" << key;
|
||||
}
|
||||
} else if (!success) {
|
||||
qCWarning(lcHypr) << "refreshOptions: getoption request error for" << key;
|
||||
}
|
||||
|
||||
(*step)(index + 1);
|
||||
});
|
||||
};
|
||||
|
||||
(*step)(0);
|
||||
}
|
||||
|
||||
void HyprExtras::refreshDevices() {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <qjsondocument.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qsharedpointer.h>
|
||||
#include <qstringlist.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
namespace ZShell::internal::hypr {
|
||||
@@ -15,13 +19,13 @@ Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_MOC_INCLUDE("hyprdevices.hpp")
|
||||
|
||||
Q_PROPERTY(QVariantHash options READ options NOTIFY optionsChanged)
|
||||
Q_PROPERTY(QVariantMap options READ options NOTIFY optionsChanged)
|
||||
Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
|
||||
|
||||
public:
|
||||
explicit HyprExtras(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QVariantHash options() const;
|
||||
[[nodiscard]] QVariantMap options() const;
|
||||
[[nodiscard]] HyprDevices* devices() const;
|
||||
|
||||
Q_INVOKABLE void message(const QString& message);
|
||||
@@ -42,11 +46,12 @@ QString m_eventSocket;
|
||||
QLocalSocket* m_socket;
|
||||
bool m_socketValid;
|
||||
|
||||
QVariantHash m_options;
|
||||
QVariantMap m_options;
|
||||
HyprDevices* const m_devices;
|
||||
|
||||
SocketPtr m_optionsRefresh;
|
||||
SocketPtr m_devicesRefresh;
|
||||
quint64 m_optionsRefreshGeneration = 0;
|
||||
|
||||
void socketError(QLocalSocket::LocalSocketError error) const;
|
||||
void socketStateChanged(QLocalSocket::LocalSocketState state);
|
||||
|
||||
@@ -12,8 +12,7 @@ FileSystemEntry::FileSystemEntry(const QString& path, const QString& relativePat
|
||||
, m_path(path)
|
||||
, m_relativePath(relativePath)
|
||||
, m_isImageInitialised(false)
|
||||
, m_mimeTypeInitialised(false) {
|
||||
}
|
||||
, m_mimeTypeInitialised(false) {}
|
||||
|
||||
QString FileSystemEntry::path() const {
|
||||
return m_path;
|
||||
@@ -58,8 +57,8 @@ bool FileSystemEntry::isImage() const {
|
||||
|
||||
QString FileSystemEntry::mimeType() const {
|
||||
if (!m_mimeTypeInitialised) {
|
||||
static const QMimeDatabase s_db;
|
||||
m_mimeType = s_db.mimeTypeForFile(m_path).name();
|
||||
const QMimeDatabase db;
|
||||
m_mimeType = db.mimeTypeForFile(m_path).name();
|
||||
m_mimeTypeInitialised = true;
|
||||
}
|
||||
return m_mimeType;
|
||||
@@ -220,7 +219,7 @@ void FileSystemModel::watchDirIfRecursive(const QString& path) {
|
||||
if (m_recursive && m_watchChanges) {
|
||||
const auto currentDir = m_dir;
|
||||
const bool showHidden = m_showHidden;
|
||||
auto future = QtConcurrent::run([showHidden, path]() {
|
||||
const auto future = QtConcurrent::run([showHidden, path]() {
|
||||
QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot;
|
||||
if (showHidden) {
|
||||
filters |= QDir::Hidden;
|
||||
@@ -233,12 +232,16 @@ void FileSystemModel::watchDirIfRecursive(const QString& path) {
|
||||
}
|
||||
return dirs;
|
||||
});
|
||||
future.then(this, [currentDir, showHidden, this](const QStringList& paths) {
|
||||
const auto watcher = new QFutureWatcher<QStringList>(this);
|
||||
connect(watcher, &QFutureWatcher<QStringList>::finished, this, [currentDir, showHidden, watcher, this]() {
|
||||
const auto paths = watcher->result();
|
||||
if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) {
|
||||
// Ignore if dir or showHidden has changed
|
||||
m_watcher.addPaths(paths);
|
||||
}
|
||||
watcher->deleteLater();
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +295,7 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||
oldPaths << entry->path();
|
||||
}
|
||||
|
||||
auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString> > >& promise) {
|
||||
const auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
|
||||
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
|
||||
|
||||
std::optional<QDirIterator> iter;
|
||||
@@ -350,7 +353,7 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||
newPaths.insert(path);
|
||||
}
|
||||
|
||||
if (promise.isCanceled()) {
|
||||
if (promise.isCanceled() || newPaths == oldPaths) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -362,17 +365,23 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||
}
|
||||
m_futures.insert(dir, future);
|
||||
|
||||
future
|
||||
.then(this,
|
||||
[dir, this](QPair<QSet<QString>, QSet<QString> > result) {
|
||||
const auto watcher = new QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>(this);
|
||||
|
||||
connect(watcher, &QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>::finished, this, [dir, watcher, this]() {
|
||||
m_futures.remove(dir);
|
||||
if (!result.first.isEmpty() || !result.second.isEmpty()) {
|
||||
applyChanges(result.first, result.second);
|
||||
|
||||
if (!watcher->future().isResultReadyAt(0)) {
|
||||
watcher->deleteLater();
|
||||
return;
|
||||
}
|
||||
})
|
||||
.onCanceled(this, [dir, this]() {
|
||||
m_futures.remove(dir);
|
||||
|
||||
const auto result = watcher->result();
|
||||
applyChanges(result.first, result.second);
|
||||
|
||||
watcher->deleteLater();
|
||||
});
|
||||
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths) {
|
||||
|
||||
@@ -132,7 +132,7 @@ QString m_path;
|
||||
bool m_recursive;
|
||||
bool m_watchChanges;
|
||||
bool m_showHidden;
|
||||
bool m_sortReverse = false;
|
||||
bool m_sortReverse;
|
||||
Filter m_filter;
|
||||
QStringList m_nameFilters;
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"typer",
|
||||
"pillow",
|
||||
"jinja2",
|
||||
"materialyoucolor"
|
||||
]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
import typer
|
||||
from zshell.subcommands import shell, scheme, screenshot, wallpaper, record
|
||||
from zshell.subcommands import shell, scheme, screenshot, wallpaper
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
@@ -8,7 +8,6 @@ app.add_typer(shell.app, name="shell")
|
||||
app.add_typer(scheme.app, name="scheme")
|
||||
app.add_typer(screenshot.app, name="screenshot")
|
||||
app.add_typer(wallpaper.app, name="wallpaper")
|
||||
app.add_typer(record.app, name="record")
|
||||
# app.add_typer(preset.app, name="preset")
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,214 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import typer
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
RECORDER = "gpu-screen-recorder"
|
||||
HOME = str(os.getenv("HOME", str(Path.home())))
|
||||
CONFIG = Path(HOME) / ".config/zshell/config.json"
|
||||
|
||||
STATE_DIR = Path(HOME) / ".local/state/zshell/record"
|
||||
TEMP_RECORDING = STATE_DIR / "recording.mp4"
|
||||
REPLAY_RECORDING = STATE_DIR / "replay.mp4"
|
||||
NOTIF_ID_FILE = STATE_DIR / "notifid.txt"
|
||||
|
||||
RECORDINGS_DIR = os.getenv("ZSHELL_RECORDINGS_DIR",
|
||||
str(Path(HOME) / "Videos/Recordings"))
|
||||
|
||||
|
||||
def _read_extra_args() -> list[str]:
|
||||
try:
|
||||
if CONFIG.is_file():
|
||||
data = json.loads(CONFIG.read_text())
|
||||
return data.get("record", {}).get("extraArgs", [])
|
||||
except Exception:
|
||||
pass
|
||||
return []
|
||||
|
||||
|
||||
def _is_recording() -> bool:
|
||||
return subprocess.run(["pidof", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
|
||||
|
||||
|
||||
def _notify(summary: str, body: str = "", actions: list = None, timeout: int = 5000) -> Optional[int]:
|
||||
args = ["notify-send", summary, body, "-t", str(timeout), "-p"]
|
||||
if actions:
|
||||
for action in actions:
|
||||
args.extend(["-A", action])
|
||||
try:
|
||||
proc = subprocess.run(args, capture_output=True, text=True)
|
||||
return int(proc.stdout.strip()) if proc.stdout.strip().isdigit() else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _close_notification(notif_id: int):
|
||||
subprocess.run(["notify-send", "--close", str(notif_id)],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
|
||||
def _get_monitors() -> list[dict]:
|
||||
try:
|
||||
res = subprocess.run(["hyprctl", "monitors", "-j"],
|
||||
capture_output=True, text=True)
|
||||
return json.loads(res.stdout)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _focused_monitor_name() -> Optional[str]:
|
||||
for m in _get_monitors():
|
||||
if m.get("focused"):
|
||||
return m["name"]
|
||||
return None
|
||||
|
||||
|
||||
def _monitors_intersecting_region(x: int, y: int, w: int, h: int) -> list[dict]:
|
||||
region = (x, y, x + w, y + h)
|
||||
intersecting = []
|
||||
for m in _get_monitors():
|
||||
mx, my, mw, mh = m["x"], m["y"], m["width"], m["height"]
|
||||
if not (region[2] <= mx or region[0] >= mx + mw or region[3] <= my or region[1] >= my + mh):
|
||||
intersecting.append(m)
|
||||
return intersecting
|
||||
|
||||
|
||||
def _highest_refresh(monitors: list[dict]) -> float:
|
||||
return max((m["refreshRate"] for m in monitors), default=60.0)
|
||||
|
||||
|
||||
def _slurp_region() -> Optional[str]:
|
||||
try:
|
||||
return subprocess.check_output(["slurp", "-f", "%wx%h+%x+%y"], text=True).strip()
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
|
||||
|
||||
def _parse_geometry(geometry: str) -> Optional[tuple[int, int, int, int]]:
|
||||
import re
|
||||
match = re.match(r"(\d+)x(\d+)\+(\d+)\+(\d+)", geometry)
|
||||
if match:
|
||||
return int(match.group(3)), int(match.group(4)), int(match.group(1)), int(match.group(2))
|
||||
return None
|
||||
|
||||
|
||||
def start_recording(region: Optional[str], sound: bool):
|
||||
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
cmd = [RECORDER]
|
||||
extra_args = _read_extra_args()
|
||||
|
||||
if region:
|
||||
if region.lower() == "slurp" or not region:
|
||||
geometry = _slurp_region()
|
||||
if not geometry:
|
||||
typer.echo("Region selection cancelled.")
|
||||
raise typer.Abort()
|
||||
else:
|
||||
geometry = region
|
||||
|
||||
parsed = _parse_geometry(geometry)
|
||||
if not parsed:
|
||||
typer.echo("Invalid geometry format.")
|
||||
raise typer.Abort()
|
||||
x, y, w, h = parsed
|
||||
|
||||
monitors = _monitors_intersecting_region(x, y, w, h)
|
||||
framerate = _highest_refresh(monitors)
|
||||
cmd.extend(["-w", "region", "-region", geometry, "-f", str(int(framerate))])
|
||||
|
||||
else:
|
||||
monitor_name = _focused_monitor_name()
|
||||
if not monitor_name:
|
||||
typer.echo("No focused monitor found.")
|
||||
raise typer.Abort()
|
||||
|
||||
monitors = _get_monitors()
|
||||
mon = next((m for m in monitors if m["name"] == monitor_name), None)
|
||||
rate = int(mon["refreshRate"]) if mon else 60
|
||||
cmd.extend(["-w", monitor_name, "-f", str(rate)])
|
||||
|
||||
if sound:
|
||||
cmd.extend(["-a", "default_output"])
|
||||
|
||||
cmd.extend(extra_args)
|
||||
cmd.extend(["-o", str(TEMP_RECORDING)])
|
||||
|
||||
subprocess.Popen(cmd, start_new_session=True,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
notif_id = _notify("Recording started", f"Saving to {TEMP_RECORDING}")
|
||||
if notif_id is not None:
|
||||
NOTIF_ID_FILE.write_text(str(notif_id))
|
||||
|
||||
time.sleep(1)
|
||||
if not _is_recording():
|
||||
_notify("Recording failed",
|
||||
"Check gpu-screen-recorder output.", timeout=5000)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
|
||||
def stop_recording(clipboard: bool):
|
||||
subprocess.run(["pkill", "-f", RECORDER],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
for _ in range(50):
|
||||
if not _is_recording():
|
||||
break
|
||||
time.sleep(0.1)
|
||||
|
||||
dest_dir = Path(RECORDINGS_DIR)
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
final_path = dest_dir / f"recording_{timestamp}.mp4"
|
||||
|
||||
if TEMP_RECORDING.exists():
|
||||
TEMP_RECORDING.rename(final_path)
|
||||
|
||||
if NOTIF_ID_FILE.is_file():
|
||||
try:
|
||||
_close_notification(int(NOTIF_ID_FILE.read_text().strip()))
|
||||
except Exception:
|
||||
pass
|
||||
NOTIF_ID_FILE.unlink()
|
||||
|
||||
if clipboard:
|
||||
subprocess.run(["wl-copy", "--type", "text/uri-list", f"file://{final_path}"],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
_notify("Recording stopped", f"Saved to {final_path}", timeout=5000)
|
||||
|
||||
|
||||
def toggle_pause():
|
||||
subprocess.run(["pkill", "-USR2", "-f", RECORDER],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
typer.echo("Toggled pause.")
|
||||
|
||||
|
||||
@app.command()
|
||||
def record(
|
||||
region: Optional[str] = typer.Option(
|
||||
None, "--region", "-r",
|
||||
help="Record a region. Use 'slurp' (or omit value) to select interactively, or give 'WxH+X+Y'.",
|
||||
),
|
||||
sound: bool = typer.Option(
|
||||
False, "--sound", "-s", help="Record audio from default output."),
|
||||
pause: bool = typer.Option(
|
||||
False, "--pause", "-p", help="Toggle pause/resume."),
|
||||
clipboard: bool = typer.Option(
|
||||
False, "--clipboard", "-c", help="Copy the final recording path to clipboard."),
|
||||
):
|
||||
"""Start or stop a screen recording with gpu-screen-recorder."""
|
||||
if pause:
|
||||
toggle_pause()
|
||||
raise typer.Exit()
|
||||
|
||||
if _is_recording():
|
||||
stop_recording(clipboard)
|
||||
else:
|
||||
start_recording(region, sound)
|
||||
@@ -1,8 +1,4 @@
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import click
|
||||
import typer
|
||||
|
||||
args = ["qs", "-c", "zshell"]
|
||||
@@ -12,68 +8,35 @@ app = typer.Typer()
|
||||
|
||||
@app.command()
|
||||
def kill():
|
||||
result = subprocess.run(args + ["kill"], capture_output=True)
|
||||
if result.returncode != 0:
|
||||
raise click.ClickException("No running instance to kill.")
|
||||
sys.stderr.write(result.stderr.decode())
|
||||
|
||||
|
||||
def start_instance(no_daemon: bool = False) -> None:
|
||||
result = subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), capture_output=True)
|
||||
stdout = result.stdout.decode().strip()
|
||||
if stdout:
|
||||
if "already running" in stdout.lower():
|
||||
raise click.ClickException(stdout)
|
||||
if result.returncode != 0:
|
||||
stderr = result.stderr.decode().strip()
|
||||
raise click.ClickException(stderr)
|
||||
subprocess.run(args + ["kill"], check=True)
|
||||
|
||||
|
||||
@app.command()
|
||||
def start(no_daemon: bool = False):
|
||||
start_instance(no_daemon)
|
||||
subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), check=True)
|
||||
|
||||
|
||||
@app.command()
|
||||
def restart(no_daemon: bool = False):
|
||||
subprocess.run(args + ["kill"], capture_output=True)
|
||||
deadline = time.monotonic() + 2.5
|
||||
while time.monotonic() < deadline:
|
||||
result = subprocess.run(args + ["kill"], capture_output=True)
|
||||
if result.returncode == 255:
|
||||
break
|
||||
time.sleep(0.25)
|
||||
start_instance(no_daemon=no_daemon)
|
||||
subprocess.run(args + ["kill"], check=False)
|
||||
subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), check=True)
|
||||
|
||||
|
||||
@app.command()
|
||||
def show():
|
||||
result = subprocess.run(args + ["ipc"] + ["show"], capture_output=True)
|
||||
if result.returncode != 0:
|
||||
raise click.ClickException(result.stderr.decode().strip())
|
||||
sys.stderr.write(result.stderr.decode())
|
||||
subprocess.run(args + ["ipc"] + ["show"], check=True)
|
||||
|
||||
|
||||
@app.command()
|
||||
def log():
|
||||
result = subprocess.run(args + ["log"], capture_output=True)
|
||||
if result.returncode != 0:
|
||||
raise click.ClickException(result.stderr.decode().strip())
|
||||
sys.stdout.write(result.stdout.decode())
|
||||
sys.stderr.write(result.stderr.decode())
|
||||
subprocess.run(args + ["log"], check=True)
|
||||
|
||||
|
||||
@app.command()
|
||||
def lock():
|
||||
result = subprocess.run(args + ["ipc"] + ["call"] + ["lock"] + ["lock"], capture_output=True)
|
||||
if result.returncode != 0:
|
||||
raise click.ClickException(result.stderr.decode().strip())
|
||||
sys.stderr.write(result.stderr.decode())
|
||||
subprocess.run(args + ["ipc"] + ["call"] + ["lock"] + ["lock"], check=True)
|
||||
|
||||
|
||||
@app.command()
|
||||
def call(target: str, method: str, method_args: list[str] = typer.Argument(None)):
|
||||
result = subprocess.run(args + ["ipc"] + ["call"] + [target] + [method] + (method_args or []), capture_output=True)
|
||||
if result.returncode != 0:
|
||||
raise click.ClickException(result.stderr.decode().strip())
|
||||
sys.stderr.write(result.stderr.decode())
|
||||
subprocess.run(args + ["ipc"] + ["call"] + [target] + [method] + (method_args or []), check=True)
|
||||
|
||||
Binary file not shown.
+19
-62
@@ -1,15 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from subprocess import CompletedProcess
|
||||
from unittest.mock import patch, call
|
||||
|
||||
from typer.testing import CliRunner
|
||||
from zshell.subcommands.shell import app
|
||||
|
||||
|
||||
def invoke(*args: str) -> None:
|
||||
from typer.testing import CliRunner
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
def invoke(*args: str):
|
||||
result = runner.invoke(app, args)
|
||||
if result.exit_code != 0:
|
||||
raise RuntimeError(result.output)
|
||||
@@ -18,113 +16,72 @@ def invoke(*args: str):
|
||||
|
||||
class TestKill:
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_kill_runs_qs_kill_success(self, mock_run):
|
||||
mock_run.return_value = CompletedProcess([], 0, b"", b"Killed abc\n")
|
||||
def test_kill_runs_qs_kill(self, mock_run):
|
||||
invoke("kill")
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "kill"], capture_output=True)
|
||||
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_kill_no_instance_errors(self, mock_run):
|
||||
mock_run.return_value = CompletedProcess([], 255, b"", b"No running instances\n")
|
||||
result = runner.invoke(app, ["kill"])
|
||||
assert result.exit_code != 0
|
||||
assert "No running instance to kill" in result.output
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "kill"], check=True)
|
||||
|
||||
|
||||
class TestStart:
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_start_default_daemon(self, mock_run):
|
||||
mock_run.return_value = CompletedProcess([], 0, b"", b"Launching config\n")
|
||||
invoke("start")
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "-n", "-d"], capture_output=True)
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "-n", "-d"], check=True)
|
||||
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_start_no_daemon(self, mock_run):
|
||||
mock_run.return_value = CompletedProcess([], 0, b"", b"Launching config\n")
|
||||
invoke("start", "--no-daemon")
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "-n"], capture_output=True)
|
||||
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_start_already_running_errors(self, mock_run):
|
||||
mock_run.return_value = CompletedProcess([], 0, b"An instance of this configuration is already running.\n", b"")
|
||||
result = runner.invoke(app, ["start"])
|
||||
assert result.exit_code != 0
|
||||
assert "already running" in result.output
|
||||
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_start_other_failure_errors(self, mock_run):
|
||||
mock_run.return_value = CompletedProcess([], 1, b"", b"Config error\n")
|
||||
result = runner.invoke(app, ["start"])
|
||||
assert result.exit_code != 0
|
||||
assert "Config error" in result.output
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "-n"], check=True)
|
||||
|
||||
|
||||
class TestShow:
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_show_runs_ipc_show(self, mock_run):
|
||||
mock_run.return_value = CompletedProcess([], 0, b"", b"target visibilities\n")
|
||||
invoke("show")
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "show"], capture_output=True)
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "show"], check=True)
|
||||
|
||||
|
||||
class TestLog:
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_log_runs_qs_log(self, mock_run):
|
||||
mock_run.return_value = CompletedProcess([], 0, b"log output\n", b"")
|
||||
invoke("log")
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "log"], capture_output=True)
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "log"], check=True)
|
||||
|
||||
|
||||
class TestLock:
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_lock_runs_ipc_call_lock(self, mock_run):
|
||||
mock_run.return_value = CompletedProcess([], 0, b"", b"")
|
||||
invoke("lock")
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "lock", "lock"], capture_output=True)
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "lock", "lock"], check=True)
|
||||
|
||||
|
||||
class TestCall:
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_call_no_args(self, mock_run):
|
||||
mock_run.return_value = CompletedProcess([], 0, b"", b"")
|
||||
invoke("call", "target", "method")
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "target", "method"], capture_output=True)
|
||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "target", "method"], check=True)
|
||||
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_call_with_args(self, mock_run):
|
||||
mock_run.return_value = CompletedProcess([], 0, b"", b"")
|
||||
invoke("call", "target", "method", "arg1", "arg2")
|
||||
mock_run.assert_called_once_with(
|
||||
["qs", "-c", "zshell", "ipc", "call", "target", "method", "arg1", "arg2"],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
class TestRestart:
|
||||
@patch("zshell.subcommands.shell.start_instance")
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_restart_kills_then_starts(self, mock_run, mock_start):
|
||||
mock_run.side_effect = [
|
||||
CompletedProcess([], 0, b"", b"Killed abc\n"), # first kill (captured)
|
||||
CompletedProcess([], 255, b"", b""), # poll → no instance
|
||||
]
|
||||
def test_restart_kills_then_starts_daemon(self, mock_run):
|
||||
invoke("restart")
|
||||
assert mock_run.call_args_list == [
|
||||
call(["qs", "-c", "zshell", "kill"], capture_output=True),
|
||||
call(["qs", "-c", "zshell", "kill"], capture_output=True),
|
||||
call(["qs", "-c", "zshell", "kill"], check=False),
|
||||
call(["qs", "-c", "zshell", "-n", "-d"], check=True),
|
||||
]
|
||||
mock_start.assert_called_once_with(no_daemon=False)
|
||||
|
||||
@patch("zshell.subcommands.shell.start_instance")
|
||||
@patch("zshell.subcommands.shell.subprocess.run")
|
||||
def test_restart_no_daemon(self, mock_run, mock_start):
|
||||
mock_run.side_effect = [
|
||||
CompletedProcess([], 0, b"", b"Killed abc\n"),
|
||||
CompletedProcess([], 255, b"", b""),
|
||||
]
|
||||
def test_restart_no_daemon(self, mock_run):
|
||||
invoke("restart", "--no-daemon")
|
||||
assert mock_run.call_args_list == [
|
||||
call(["qs", "-c", "zshell", "kill"], capture_output=True),
|
||||
call(["qs", "-c", "zshell", "kill"], capture_output=True),
|
||||
call(["qs", "-c", "zshell", "kill"], check=False),
|
||||
call(["qs", "-c", "zshell", "-n"], check=True),
|
||||
]
|
||||
mock_start.assert_called_once_with(no_daemon=True)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
//@ pragma Env QT_SCALE_FACTOR_ROUNDING_POLICY=Round
|
||||
//@ pragma DropExpensiveFonts
|
||||
import Quickshell
|
||||
import qs.Extensions
|
||||
import qs.Modules
|
||||
import qs.Modules.Wallpaper
|
||||
import qs.Modules.Lock
|
||||
@@ -15,8 +14,6 @@ import qs.Helpers
|
||||
import qs.Modules.Polkit
|
||||
|
||||
ShellRoot {
|
||||
settings.watchFiles: true
|
||||
|
||||
Windows {
|
||||
}
|
||||
|
||||
@@ -39,7 +36,4 @@ ShellRoot {
|
||||
|
||||
Polkit {
|
||||
}
|
||||
|
||||
LoadExtensions {
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+3
-807
@@ -8,47 +8,12 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aligned"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
|
||||
dependencies = [
|
||||
"as-slice",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned-vec"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b"
|
||||
dependencies = [
|
||||
"equator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
|
||||
[[package]]
|
||||
name = "arg_enum_proc_macro"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
@@ -61,103 +26,18 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "as-slice"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "av-scenechange"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394"
|
||||
dependencies = [
|
||||
"aligned",
|
||||
"anyhow",
|
||||
"arg_enum_proc_macro",
|
||||
"arrayvec",
|
||||
"log",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"pastey",
|
||||
"rayon",
|
||||
"thiserror",
|
||||
"v_frame",
|
||||
"y4m",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "av1-grain"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arrayvec",
|
||||
"log",
|
||||
"nom",
|
||||
"num-rational",
|
||||
"v_frame",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "avif-serialize"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
|
||||
[[package]]
|
||||
name = "bitstream-io"
|
||||
version = "4.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f"
|
||||
dependencies = [
|
||||
"no_std_io2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "built"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.0"
|
||||
@@ -170,30 +50,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
@@ -203,84 +65,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "equator"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc"
|
||||
dependencies = [
|
||||
"equator-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equator-macro"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exr"
|
||||
version = "1.74.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
"half",
|
||||
"lebe",
|
||||
"miniz_oxide",
|
||||
"rayon-core",
|
||||
"smallvec",
|
||||
"zune-inflate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fax"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
@@ -290,12 +74,6 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.9"
|
||||
@@ -306,39 +84,6 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
@@ -347,56 +92,9 @@ checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"color_quant",
|
||||
"exr",
|
||||
"gif",
|
||||
"image-webp",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png 0.18.1",
|
||||
"qoi",
|
||||
"ravif",
|
||||
"rayon",
|
||||
"rgb",
|
||||
"tiff",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
|
||||
dependencies = [
|
||||
"byteorder-lite",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imgref"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2"
|
||||
|
||||
[[package]]
|
||||
name = "interpolate_name"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -405,63 +103,12 @@ version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "loop9"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
|
||||
dependencies = [
|
||||
"imgref",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maybe-rayon"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
@@ -488,77 +135,6 @@ dependencies = [
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "no_std_io2"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b51ed7824b6e07d354605f4abb3d9d300350701299da96642ee084f5ce631550"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "noop_proc_macro"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -568,59 +144,19 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pastey"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
@@ -630,46 +166,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiling"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
|
||||
dependencies = [
|
||||
"profiling-procmacros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiling-procmacros"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||
|
||||
[[package]]
|
||||
name = "qoi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
@@ -679,123 +181,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rav1e"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b"
|
||||
dependencies = [
|
||||
"aligned-vec",
|
||||
"arbitrary",
|
||||
"arg_enum_proc_macro",
|
||||
"arrayvec",
|
||||
"av-scenechange",
|
||||
"av1-grain",
|
||||
"bitstream-io",
|
||||
"built",
|
||||
"cfg-if",
|
||||
"interpolate_name",
|
||||
"itertools",
|
||||
"libc",
|
||||
"libfuzzer-sys",
|
||||
"log",
|
||||
"maybe-rayon",
|
||||
"new_debug_unreachable",
|
||||
"noop_proc_macro",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"profiling",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"simd_helpers",
|
||||
"thiserror",
|
||||
"v_frame",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ravif"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45"
|
||||
dependencies = [
|
||||
"avif-serialize",
|
||||
"imgref",
|
||||
"loop9",
|
||||
"quick-error",
|
||||
"rav1e",
|
||||
"rayon",
|
||||
"rgb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
@@ -839,39 +224,12 @@ dependencies = [
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||
|
||||
[[package]]
|
||||
name = "simd_helpers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "strict-num"
|
||||
version = "0.1.1"
|
||||
@@ -889,40 +247,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
|
||||
dependencies = [
|
||||
"fax",
|
||||
"flate2",
|
||||
"half",
|
||||
"quick-error",
|
||||
"weezl",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
@@ -934,7 +258,6 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"png 0.17.16",
|
||||
"tiny-skia-path",
|
||||
]
|
||||
|
||||
@@ -955,109 +278,6 @@ version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "v_frame"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2"
|
||||
dependencies = [
|
||||
"aligned-vec",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.3+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "y4m"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
@@ -1066,7 +286,7 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zshell-img-tools"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"image",
|
||||
@@ -1074,27 +294,3 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tiny-skia",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
|
||||
|
||||
[[package]]
|
||||
name = "zune-inflate"
|
||||
version = "0.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "zshell-img-tools"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
@@ -8,10 +8,10 @@ name = "zshell-img-tools"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
image = { version = "0.25", features = ["png"] }
|
||||
tiny-skia = "0.11"
|
||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||
tiny-skia = { version = "0.11", default-features = false, features = ["std", "simd"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
anyhow = "1"
|
||||
anyhow = "1.0"
|
||||
serde_json = "1.0.149"
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# What_That_Claude_DO?
|
||||
|
||||
What That Claude Do? (WTCD)
|
||||
A repository of random things I ask Claude to do for me.
|
||||
|
||||
In this case it is creating a screenshot tool
|
||||
@@ -11,13 +11,14 @@ pub struct Config {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EffectsConfig {
|
||||
pub mode: String,
|
||||
pub rounded_corners: bool,
|
||||
pub corner_radius: f32,
|
||||
pub drop_shadow: bool,
|
||||
pub rounded_corners: bool,
|
||||
pub shadow_blur_radius: f32,
|
||||
pub shadow_blur_passes: u32,
|
||||
pub shadow_color: [u8; 4],
|
||||
pub shadow_offset_x: f32,
|
||||
pub shadow_offset_y: f32,
|
||||
pub shadow_color: [u8; 4],
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
||||
@@ -16,6 +16,7 @@ pub fn apply_effects(img: RgbaImage, cfg: &EffectsConfig) -> RgbaImage {
|
||||
cfg.shadow_blur_radius,
|
||||
cfg.shadow_offset_x,
|
||||
cfg.shadow_offset_y,
|
||||
cfg.shadow_blur_passes,
|
||||
cfg.shadow_color,
|
||||
)
|
||||
} else {
|
||||
@@ -52,11 +53,16 @@ pub fn apply_drop_shadow(
|
||||
blur_radius: f32,
|
||||
offset_x: f32,
|
||||
offset_y: f32,
|
||||
blur_passes: u32,
|
||||
shadow_color: [u8; 4],
|
||||
) -> RgbaImage {
|
||||
let (iw, ih) = img.dimensions();
|
||||
let br = blur_radius.ceil() as u32;
|
||||
let spread = br * 2;
|
||||
let bp = blur_passes;
|
||||
// Original idea
|
||||
// let spread = br * bp;
|
||||
// Claude is hallucinating but let's try it **Worked btw**
|
||||
let spread = (br as f32 * (bp as f32).sqrt() * 2.0).ceil() as u32;
|
||||
|
||||
let extra_left = spread + (-offset_x).max(0.0).ceil() as u32;
|
||||
let extra_top = spread + (-offset_y).max(0.0).ceil() as u32;
|
||||
@@ -86,8 +92,11 @@ pub fn apply_drop_shadow(
|
||||
|
||||
tint_pixmap_as_shadow(&mut shadow_pixmap, shadow_color);
|
||||
|
||||
// Shadow
|
||||
let shadow_img = pixmap_to_rgba_image(shadow_pixmap);
|
||||
let blurred = box_blur_rgba(&shadow_img, br);
|
||||
// Shadow blur
|
||||
let blurred = box_blur_rgba(&shadow_img, br, bp);
|
||||
// Shadow pos
|
||||
let blurred_pixmap = rgba_image_to_pixmap(&blurred);
|
||||
|
||||
let mut canvas = Pixmap::new(canvas_w, canvas_h).expect("canvas pixmap");
|
||||
@@ -136,6 +145,7 @@ fn rounded_rect_path(x: f32, y: f32, w: f32, h: f32, r: f32) -> Path {
|
||||
pb.finish().expect("rounded rect path")
|
||||
}
|
||||
|
||||
// Shadow pos
|
||||
fn rgba_image_to_pixmap(img: &RgbaImage) -> Pixmap {
|
||||
let (w, h) = img.dimensions();
|
||||
let mut pixmap = Pixmap::new(w, h).expect("pixmap alloc");
|
||||
@@ -154,6 +164,7 @@ fn rgba_image_to_pixmap(img: &RgbaImage) -> Pixmap {
|
||||
pixmap
|
||||
}
|
||||
|
||||
// Shadow
|
||||
fn pixmap_to_rgba_image(pixmap: Pixmap) -> RgbaImage {
|
||||
let (w, h) = (pixmap.width(), pixmap.height());
|
||||
let mut out = RgbaImage::new(w, h);
|
||||
@@ -176,31 +187,16 @@ fn pixmap_to_rgba_image(pixmap: Pixmap) -> RgbaImage {
|
||||
out
|
||||
}
|
||||
|
||||
fn tint_pixmap_as_shadow(pixmap: &mut Pixmap, color: [u8; 4]) {
|
||||
let [sr, sg, sb, _] = color;
|
||||
for px in pixmap.pixels_mut() {
|
||||
let a = px.alpha();
|
||||
if a > 0 {
|
||||
let af = a as f32 / 255.0;
|
||||
*px = tiny_skia::PremultipliedColorU8::from_rgba(
|
||||
(sr as f32 * af) as u8,
|
||||
(sg as f32 * af) as u8,
|
||||
(sb as f32 * af) as u8,
|
||||
a,
|
||||
)
|
||||
.unwrap_or(tiny_skia::PremultipliedColorU8::TRANSPARENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn box_blur_rgba(img: &RgbaImage, radius: u32) -> RgbaImage {
|
||||
// Shadow blur
|
||||
fn box_blur_rgba(img: &RgbaImage, radius: u32, bp: u32) -> RgbaImage {
|
||||
if radius == 0 {
|
||||
return img.clone();
|
||||
}
|
||||
let mut buf = sliding_horizontal(img, radius);
|
||||
buf = sliding_vertical(&buf, radius);
|
||||
let mut buf = img.clone();
|
||||
for _ in 0..bp {
|
||||
buf = sliding_horizontal(&buf, radius);
|
||||
buf = sliding_vertical(&buf, radius);
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
@@ -250,6 +246,23 @@ fn sliding_horizontal(img: &RgbaImage, radius: u32) -> RgbaImage {
|
||||
out
|
||||
}
|
||||
|
||||
fn tint_pixmap_as_shadow(pixmap: &mut Pixmap, color: [u8; 4]) {
|
||||
let [sr, sg, sb, _] = color;
|
||||
for px in pixmap.pixels_mut() {
|
||||
let a = px.alpha();
|
||||
if a > 0 {
|
||||
let af = a as f32 / 255.0;
|
||||
*px = tiny_skia::PremultipliedColorU8::from_rgba(
|
||||
(sr as f32 * af) as u8,
|
||||
(sg as f32 * af) as u8,
|
||||
(sb as f32 * af) as u8,
|
||||
a,
|
||||
)
|
||||
.unwrap_or(tiny_skia::PremultipliedColorU8::TRANSPARENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sliding_vertical(img: &RgbaImage, radius: u32) -> RgbaImage {
|
||||
let (w, h) = img.dimensions();
|
||||
let r = radius as i32;
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
mod config;
|
||||
mod effects;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::io::Write as _;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
/// CLI overrides that map 1:1 to `EffectsConfig` fields.
|
||||
/// All fields are `Option<T>` so we can tell "not supplied" from any concrete value.
|
||||
#[derive(Default)]
|
||||
struct CliOverrides {
|
||||
rounded_corners: Option<bool>,
|
||||
corner_radius: Option<f32>,
|
||||
drop_shadow: Option<bool>,
|
||||
shadow_blur_radius: Option<f32>,
|
||||
shadow_blur_passes: Option<u32>,
|
||||
shadow_offset_x: Option<f32>,
|
||||
shadow_offset_y: Option<f32>,
|
||||
/// Accepted as four comma-separated u8 values, e.g. `255,0,0,200`
|
||||
// Accepted as four comma-separated u8 values, e.g. `255,0,0,200`
|
||||
shadow_color: Option<[u8; 4]>,
|
||||
}
|
||||
|
||||
@@ -30,24 +29,24 @@ fn parse_bool(s: &str) -> Result<bool> {
|
||||
fn parse_shadow_color(s: &str) -> Result<[u8; 4]> {
|
||||
let parts: Vec<&str> = s.split(',').collect();
|
||||
if parts.len() != 4 {
|
||||
bail!("--shadow_color expects four comma-separated u8 values, e.g. 255,0,0,200");
|
||||
bail!("--shadow-color expects four comma-separated u8 values, e.g. 255,0,0,200");
|
||||
}
|
||||
let r = parts[0]
|
||||
.trim()
|
||||
.parse::<u8>()
|
||||
.context("shadow_color red channel")?;
|
||||
.context("shadow-color red channel")?;
|
||||
let g = parts[1]
|
||||
.trim()
|
||||
.parse::<u8>()
|
||||
.context("shadow_color green channel")?;
|
||||
.context("shadow-color green channel")?;
|
||||
let b = parts[2]
|
||||
.trim()
|
||||
.parse::<u8>()
|
||||
.context("shadow_color blue channel")?;
|
||||
.context("shadow-color blue channel")?;
|
||||
let a = parts[3]
|
||||
.trim()
|
||||
.parse::<u8>()
|
||||
.context("shadow_color alpha channel")?;
|
||||
.context("shadow-color alpha channel")?;
|
||||
Ok([r, g, b, a])
|
||||
}
|
||||
|
||||
@@ -56,6 +55,7 @@ fn main() -> Result<()> {
|
||||
|
||||
let mut image_path: Option<String> = None;
|
||||
let mut overrides = CliOverrides::default();
|
||||
let mut scale: Option<f32> = None;
|
||||
|
||||
let mut i = 0;
|
||||
while i < args.len() {
|
||||
@@ -68,67 +68,82 @@ fn main() -> Result<()> {
|
||||
.context("Expected a path after --image")?,
|
||||
);
|
||||
}
|
||||
"--rounded_corners" => {
|
||||
"--rounded-corners" => {
|
||||
i += 1;
|
||||
let val = args
|
||||
.get(i)
|
||||
.context("Expected true/false after --rounded_corners")?;
|
||||
.context("Expected true/false after --rounded-corners")?;
|
||||
overrides.rounded_corners = Some(parse_bool(val)?);
|
||||
}
|
||||
"--corner_radius" => {
|
||||
"--corner-radius" => {
|
||||
i += 1;
|
||||
let val = args
|
||||
.get(i)
|
||||
.context("Expected a number after --corner_radius")?;
|
||||
.context("Expected a number after --corner-radius")?;
|
||||
overrides.corner_radius = Some(
|
||||
val.parse::<f32>()
|
||||
.context("--corner_radius must be a number")?,
|
||||
.context("--corner-radius must be a number")?,
|
||||
);
|
||||
}
|
||||
"--drop_shadow" => {
|
||||
"--drop-shadow" => {
|
||||
i += 1;
|
||||
let val = args
|
||||
.get(i)
|
||||
.context("Expected true/false after --drop_shadow")?;
|
||||
.context("Expected true/false after --drop-shadow")?;
|
||||
overrides.drop_shadow = Some(parse_bool(val)?);
|
||||
}
|
||||
"--shadow_blur_radius" => {
|
||||
"--shadow-blur-radius" => {
|
||||
i += 1;
|
||||
let val = args
|
||||
.get(i)
|
||||
.context("Expected a number after --shadow_blur_radius")?;
|
||||
.context("Expected a number after --shadow-blur-radius")?;
|
||||
overrides.shadow_blur_radius = Some(
|
||||
val.parse::<f32>()
|
||||
.context("--shadow_blur_radius must be a number")?,
|
||||
.context("--shadow-blur-radius must be a number")?,
|
||||
);
|
||||
}
|
||||
"--shadow_offset_x" => {
|
||||
"--shadow-offset-x" => {
|
||||
i += 1;
|
||||
let val = args
|
||||
.get(i)
|
||||
.context("Expected a number after --shadow_offset_x")?;
|
||||
.context("Expected a number after --shadow-offset-x")?;
|
||||
overrides.shadow_offset_x = Some(
|
||||
val.parse::<f32>()
|
||||
.context("--shadow_offset_x must be a number")?,
|
||||
.context("--shadow-offset-x must be a number")?,
|
||||
);
|
||||
}
|
||||
"--shadow_offset_y" => {
|
||||
"--shadow-offset-y" => {
|
||||
i += 1;
|
||||
let val = args
|
||||
.get(i)
|
||||
.context("Expected a number after --shadow_offset_y")?;
|
||||
.context("Expected a number after --shadow-offset-y")?;
|
||||
overrides.shadow_offset_y = Some(
|
||||
val.parse::<f32>()
|
||||
.context("--shadow_offset_y must be a number")?,
|
||||
.context("--shadow-offset-y must be a number")?,
|
||||
);
|
||||
}
|
||||
"--shadow_color" => {
|
||||
"--shadow-blur-passes" => {
|
||||
i += 1;
|
||||
let val = args
|
||||
.get(i)
|
||||
.context("Expected r,g,b,a after --shadow_color")?;
|
||||
.context("Expected a number after --shadow-blur-passes")?;
|
||||
overrides.shadow_blur_passes = Some(
|
||||
val.parse::<u32>()
|
||||
.context("--shadow-blur-passes must be a number")?,
|
||||
);
|
||||
}
|
||||
"--shadow-color" => {
|
||||
i += 1;
|
||||
let val = args
|
||||
.get(i)
|
||||
.context("Expected r,g,b,a after --shadow-color")?;
|
||||
overrides.shadow_color = Some(parse_shadow_color(val)?);
|
||||
}
|
||||
"--scale" => {
|
||||
i += 1;
|
||||
let val = args.get(i).context("Expected a number after --scale")?;
|
||||
scale = Some(val.parse::<f32>().context("--scale must be a number")?);
|
||||
}
|
||||
unknown => bail!("Unknown argument: {unknown}"),
|
||||
}
|
||||
i += 1;
|
||||
@@ -158,11 +173,22 @@ fn main() -> Result<()> {
|
||||
if let Some(v) = overrides.shadow_offset_y {
|
||||
effects.shadow_offset_y = v;
|
||||
}
|
||||
if let Some(v) = overrides.shadow_blur_passes {
|
||||
effects.shadow_blur_passes = v;
|
||||
}
|
||||
if let Some(v) = overrides.shadow_color {
|
||||
effects.shadow_color = v;
|
||||
}
|
||||
}
|
||||
|
||||
// if scale is set do
|
||||
if let Some(scale) = scale.filter(|&s| s != 1.0) {
|
||||
effects.corner_radius *= scale;
|
||||
effects.shadow_blur_radius *= scale;
|
||||
effects.shadow_offset_x *= scale;
|
||||
effects.shadow_offset_y *= scale;
|
||||
}
|
||||
|
||||
if let Err(e) = process_image(&image_path, &effects) {
|
||||
eprintln!("Error processing '{}': {e:#}", image_path);
|
||||
}
|
||||
@@ -191,20 +217,11 @@ fn process_image(path: &str, effects: &config::EffectsConfig) -> Result<()> {
|
||||
.spawn()
|
||||
.context("Failed to spawn swappy. Is it installed and in PATH?")?;
|
||||
|
||||
child
|
||||
.stdin
|
||||
.take()
|
||||
.context("Failed to get swappy stdin")?
|
||||
// Writes the PNG bytes to swappy's stdin and then closes
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
stdin
|
||||
.write_all(&png_bytes)
|
||||
.context("Failed to write image data to swappy")?;
|
||||
|
||||
let status = child.wait().context("Failed to wait for swappy")?;
|
||||
|
||||
if !status.success() {
|
||||
eprintln!(
|
||||
"swappy exited with non-zero status for '{}': {}",
|
||||
path, status
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user