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]);
|
||||
Quickshell.execDetached(["app2unit", "-O", "--", 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,14 +100,12 @@ Item {
|
||||
icon: `brightness_${(Math.round(value * 6) + 1)}`
|
||||
value: root.brightness
|
||||
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
if (Config.osd.allMonBrightness) {
|
||||
for (const mon of Brightness.monitors) {
|
||||
mon.setBrightness(value);
|
||||
}
|
||||
} else {
|
||||
root.monitor?.setBrightness(value);
|
||||
onMoved: {
|
||||
if (Config.osd.allMonBrightness) {
|
||||
root.monitor?.setBrightness(value);
|
||||
} else {
|
||||
for (const mon of Brightness.monitors) {
|
||||
mon.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 {
|
||||
}
|
||||
|
||||
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"
|
||||
// }
|
||||
// }
|
||||
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,128 +77,115 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
text: root.name
|
||||
}
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Appearance.spacing.smaller
|
||||
Repeater {
|
||||
model: [...root.object[root.setting]]
|
||||
|
||||
Item {
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
text: root.name
|
||||
}
|
||||
Layout.preferredHeight: layout.implicitHeight + Appearance.padding.smaller * 2
|
||||
|
||||
Repeater {
|
||||
model: [...root.object[root.setting]]
|
||||
CustomRect {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -(Appearance.spacing.smaller / 2)
|
||||
color: DynamicColors.tPalette.m3outlineVariant
|
||||
implicitHeight: 1
|
||||
visible: index !== 0
|
||||
}
|
||||
|
||||
Item {
|
||||
required property int index
|
||||
required property var modelData
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: layout.implicitHeight + Appearance.padding.smaller * 2
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Appearance.padding.small
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
CustomRect {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -(Appearance.spacing.smaller / 2)
|
||||
color: DynamicColors.tPalette.m3outlineVariant
|
||||
implicitHeight: 1
|
||||
visible: index !== 0
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Appearance.padding.small
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
RowLayout {
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("From")
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("From")
|
||||
}
|
||||
CustomRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 33
|
||||
color: DynamicColors.tPalette.m3surfaceContainerHigh
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
CustomRect {
|
||||
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 {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Appearance.padding.normal
|
||||
anchors.rightMargin: Appearance.padding.normal
|
||||
text: modelData.from ?? ""
|
||||
|
||||
CustomTextField {
|
||||
id: fromTextField
|
||||
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
|
||||
text: modelData.from ?? ""
|
||||
|
||||
onEditingFinished: root.updateAlias(index, "from", text)
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
font.pointSize: Appearance.font.size.large
|
||||
icon: "delete"
|
||||
type: IconButton.Tonal
|
||||
|
||||
onClicked: root.removeAlias(index)
|
||||
onEditingFinished: root.updateAlias(index, "from", text)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
IconButton {
|
||||
font.pointSize: Appearance.font.size.large
|
||||
icon: "delete"
|
||||
type: IconButton.Tonal
|
||||
|
||||
onClicked: root.removeAlias(index)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("To")
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("To")
|
||||
}
|
||||
CustomRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 33
|
||||
color: DynamicColors.tPalette.m3surface
|
||||
radius: Appearance.rounding.small
|
||||
|
||||
CustomRect {
|
||||
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
|
||||
CustomTextField {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Appearance.padding.normal
|
||||
anchors.rightMargin: Appearance.padding.normal
|
||||
text: modelData.to ?? ""
|
||||
|
||||
CustomTextField {
|
||||
id: toTextField
|
||||
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
|
||||
text: modelData.to ?? ""
|
||||
|
||||
onEditingFinished: root.updateAlias(index, "to", text)
|
||||
}
|
||||
onEditingFinished: root.updateAlias(index, "to", text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
IconButton {
|
||||
font.pointSize: Appearance.font.size.large
|
||||
icon: "add"
|
||||
|
||||
onClicked: root.addAlias()
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Add alias")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
IconButton {
|
||||
font.pointSize: Appearance.font.size.large
|
||||
icon: "add"
|
||||
|
||||
onClicked: root.addAlias()
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Add alias")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (index >= optionKeys.size()) {
|
||||
if (m_options != *nextOptions) {
|
||||
m_options = *nextOptions;
|
||||
emit optionsChanged();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
emit optionsChanged();
|
||||
}
|
||||
});
|
||||
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);
|
||||
|
||||
@@ -7,464 +7,473 @@
|
||||
namespace ZShell::models {
|
||||
|
||||
FileSystemEntry::FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_fileInfo(path)
|
||||
, m_path(path)
|
||||
, m_relativePath(relativePath)
|
||||
, m_isImageInitialised(false)
|
||||
, m_mimeTypeInitialised(false) {
|
||||
}
|
||||
: QObject(parent)
|
||||
, m_fileInfo(path)
|
||||
, m_path(path)
|
||||
, m_relativePath(relativePath)
|
||||
, m_isImageInitialised(false)
|
||||
, m_mimeTypeInitialised(false) {}
|
||||
|
||||
QString FileSystemEntry::path() const {
|
||||
return m_path;
|
||||
return m_path;
|
||||
};
|
||||
|
||||
QString FileSystemEntry::relativePath() const {
|
||||
return m_relativePath;
|
||||
return m_relativePath;
|
||||
};
|
||||
|
||||
QString FileSystemEntry::name() const {
|
||||
return m_fileInfo.fileName();
|
||||
return m_fileInfo.fileName();
|
||||
};
|
||||
|
||||
QString FileSystemEntry::baseName() const {
|
||||
return m_fileInfo.baseName();
|
||||
return m_fileInfo.baseName();
|
||||
};
|
||||
|
||||
QString FileSystemEntry::parentDir() const {
|
||||
return m_fileInfo.absolutePath();
|
||||
return m_fileInfo.absolutePath();
|
||||
};
|
||||
|
||||
QString FileSystemEntry::suffix() const {
|
||||
return m_fileInfo.completeSuffix();
|
||||
return m_fileInfo.completeSuffix();
|
||||
};
|
||||
|
||||
qint64 FileSystemEntry::size() const {
|
||||
return m_fileInfo.size();
|
||||
return m_fileInfo.size();
|
||||
};
|
||||
|
||||
bool FileSystemEntry::isDir() const {
|
||||
return m_fileInfo.isDir();
|
||||
return m_fileInfo.isDir();
|
||||
};
|
||||
|
||||
bool FileSystemEntry::isImage() const {
|
||||
if (!m_isImageInitialised) {
|
||||
QImageReader reader(m_path);
|
||||
m_isImage = reader.canRead();
|
||||
m_isImageInitialised = true;
|
||||
}
|
||||
return m_isImage;
|
||||
if (!m_isImageInitialised) {
|
||||
QImageReader reader(m_path);
|
||||
m_isImage = reader.canRead();
|
||||
m_isImageInitialised = true;
|
||||
}
|
||||
return m_isImage;
|
||||
}
|
||||
|
||||
QString FileSystemEntry::mimeType() const {
|
||||
if (!m_mimeTypeInitialised) {
|
||||
static const QMimeDatabase s_db;
|
||||
m_mimeType = s_db.mimeTypeForFile(m_path).name();
|
||||
m_mimeTypeInitialised = true;
|
||||
}
|
||||
return m_mimeType;
|
||||
if (!m_mimeTypeInitialised) {
|
||||
const QMimeDatabase db;
|
||||
m_mimeType = db.mimeTypeForFile(m_path).name();
|
||||
m_mimeTypeInitialised = true;
|
||||
}
|
||||
return m_mimeType;
|
||||
}
|
||||
|
||||
void FileSystemEntry::updateRelativePath(const QDir& dir) {
|
||||
const auto relPath = dir.relativeFilePath(m_path);
|
||||
if (m_relativePath != relPath) {
|
||||
m_relativePath = relPath;
|
||||
emit relativePathChanged();
|
||||
}
|
||||
const auto relPath = dir.relativeFilePath(m_path);
|
||||
if (m_relativePath != relPath) {
|
||||
m_relativePath = relPath;
|
||||
emit relativePathChanged();
|
||||
}
|
||||
}
|
||||
|
||||
FileSystemModel::FileSystemModel(QObject* parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_recursive(false)
|
||||
, m_watchChanges(true)
|
||||
, m_showHidden(false)
|
||||
, m_filter(NoFilter) {
|
||||
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive);
|
||||
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateEntriesForDir);
|
||||
: QAbstractListModel(parent)
|
||||
, m_recursive(false)
|
||||
, m_watchChanges(true)
|
||||
, m_showHidden(false)
|
||||
, m_filter(NoFilter) {
|
||||
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive);
|
||||
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateEntriesForDir);
|
||||
}
|
||||
|
||||
int FileSystemModel::rowCount(const QModelIndex& parent) const {
|
||||
if (parent != QModelIndex()) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int>(m_entries.size());
|
||||
if (parent != QModelIndex()) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int>(m_entries.size());
|
||||
}
|
||||
|
||||
QVariant FileSystemModel::data(const QModelIndex& index, int role) const {
|
||||
if (role != Qt::UserRole || !index.isValid() || index.row() >= m_entries.size()) {
|
||||
return QVariant();
|
||||
}
|
||||
return QVariant::fromValue(m_entries.at(index.row()));
|
||||
if (role != Qt::UserRole || !index.isValid() || index.row() >= m_entries.size()) {
|
||||
return QVariant();
|
||||
}
|
||||
return QVariant::fromValue(m_entries.at(index.row()));
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> FileSystemModel::roleNames() const {
|
||||
return { { Qt::UserRole, "modelData" } };
|
||||
return { { Qt::UserRole, "modelData" } };
|
||||
}
|
||||
|
||||
QString FileSystemModel::path() const {
|
||||
return m_path;
|
||||
return m_path;
|
||||
}
|
||||
|
||||
void FileSystemModel::setPath(const QString& path) {
|
||||
if (m_path == path) {
|
||||
return;
|
||||
}
|
||||
if (m_path == path) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_path = path;
|
||||
emit pathChanged();
|
||||
m_path = path;
|
||||
emit pathChanged();
|
||||
|
||||
m_dir.setPath(m_path);
|
||||
m_dir.setPath(m_path);
|
||||
|
||||
for (const auto& entry : std::as_const(m_entries)) {
|
||||
entry->updateRelativePath(m_dir);
|
||||
}
|
||||
for (const auto& entry : std::as_const(m_entries)) {
|
||||
entry->updateRelativePath(m_dir);
|
||||
}
|
||||
|
||||
update();
|
||||
update();
|
||||
}
|
||||
|
||||
bool FileSystemModel::recursive() const {
|
||||
return m_recursive;
|
||||
return m_recursive;
|
||||
}
|
||||
|
||||
void FileSystemModel::setRecursive(bool recursive) {
|
||||
if (m_recursive == recursive) {
|
||||
return;
|
||||
}
|
||||
if (m_recursive == recursive) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_recursive = recursive;
|
||||
emit recursiveChanged();
|
||||
m_recursive = recursive;
|
||||
emit recursiveChanged();
|
||||
|
||||
update();
|
||||
update();
|
||||
}
|
||||
|
||||
bool FileSystemModel::watchChanges() const {
|
||||
return m_watchChanges;
|
||||
return m_watchChanges;
|
||||
}
|
||||
|
||||
void FileSystemModel::setWatchChanges(bool watchChanges) {
|
||||
if (m_watchChanges == watchChanges) {
|
||||
return;
|
||||
}
|
||||
if (m_watchChanges == watchChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_watchChanges = watchChanges;
|
||||
emit watchChangesChanged();
|
||||
m_watchChanges = watchChanges;
|
||||
emit watchChangesChanged();
|
||||
|
||||
update();
|
||||
update();
|
||||
}
|
||||
|
||||
bool FileSystemModel::showHidden() const {
|
||||
return m_showHidden;
|
||||
return m_showHidden;
|
||||
}
|
||||
|
||||
void FileSystemModel::setShowHidden(bool showHidden) {
|
||||
if (m_showHidden == showHidden) {
|
||||
return;
|
||||
}
|
||||
if (m_showHidden == showHidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_showHidden = showHidden;
|
||||
emit showHiddenChanged();
|
||||
m_showHidden = showHidden;
|
||||
emit showHiddenChanged();
|
||||
|
||||
update();
|
||||
update();
|
||||
}
|
||||
|
||||
bool FileSystemModel::sortReverse() const {
|
||||
return m_sortReverse;
|
||||
return m_sortReverse;
|
||||
}
|
||||
|
||||
void FileSystemModel::setSortReverse(bool sortReverse) {
|
||||
if (m_sortReverse == sortReverse) {
|
||||
return;
|
||||
}
|
||||
if (m_sortReverse == sortReverse) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_sortReverse = sortReverse;
|
||||
emit sortReverseChanged();
|
||||
m_sortReverse = sortReverse;
|
||||
emit sortReverseChanged();
|
||||
|
||||
update();
|
||||
update();
|
||||
}
|
||||
|
||||
FileSystemModel::Filter FileSystemModel::filter() const {
|
||||
return m_filter;
|
||||
return m_filter;
|
||||
}
|
||||
|
||||
void FileSystemModel::setFilter(Filter filter) {
|
||||
if (m_filter == filter) {
|
||||
return;
|
||||
}
|
||||
if (m_filter == filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_filter = filter;
|
||||
emit filterChanged();
|
||||
m_filter = filter;
|
||||
emit filterChanged();
|
||||
|
||||
update();
|
||||
update();
|
||||
}
|
||||
|
||||
QStringList FileSystemModel::nameFilters() const {
|
||||
return m_nameFilters;
|
||||
return m_nameFilters;
|
||||
}
|
||||
|
||||
void FileSystemModel::setNameFilters(const QStringList& nameFilters) {
|
||||
if (m_nameFilters == nameFilters) {
|
||||
return;
|
||||
}
|
||||
if (m_nameFilters == nameFilters) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_nameFilters = nameFilters;
|
||||
emit nameFiltersChanged();
|
||||
m_nameFilters = nameFilters;
|
||||
emit nameFiltersChanged();
|
||||
|
||||
update();
|
||||
update();
|
||||
}
|
||||
|
||||
QQmlListProperty<FileSystemEntry> FileSystemModel::entries() {
|
||||
return QQmlListProperty<FileSystemEntry>(this, &m_entries);
|
||||
return QQmlListProperty<FileSystemEntry>(this, &m_entries);
|
||||
}
|
||||
|
||||
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]() {
|
||||
QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot;
|
||||
if (showHidden) {
|
||||
filters |= QDir::Hidden;
|
||||
}
|
||||
if (m_recursive && m_watchChanges) {
|
||||
const auto currentDir = m_dir;
|
||||
const bool showHidden = m_showHidden;
|
||||
const auto future = QtConcurrent::run([showHidden, path]() {
|
||||
QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot;
|
||||
if (showHidden) {
|
||||
filters |= QDir::Hidden;
|
||||
}
|
||||
|
||||
QDirIterator iter(path, filters, QDirIterator::Subdirectories);
|
||||
QStringList dirs;
|
||||
while (iter.hasNext()) {
|
||||
dirs << iter.next();
|
||||
}
|
||||
return dirs;
|
||||
});
|
||||
future.then(this, [currentDir, showHidden, this](const QStringList& paths) {
|
||||
if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) {
|
||||
// Ignore if dir or showHidden has changed
|
||||
m_watcher.addPaths(paths);
|
||||
}
|
||||
});
|
||||
}
|
||||
QDirIterator iter(path, filters, QDirIterator::Subdirectories);
|
||||
QStringList dirs;
|
||||
while (iter.hasNext()) {
|
||||
dirs << iter.next();
|
||||
}
|
||||
return dirs;
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void FileSystemModel::update() {
|
||||
updateWatcher();
|
||||
updateEntries();
|
||||
updateWatcher();
|
||||
updateEntries();
|
||||
}
|
||||
|
||||
void FileSystemModel::updateWatcher() {
|
||||
if (!m_watcher.directories().isEmpty()) {
|
||||
m_watcher.removePaths(m_watcher.directories());
|
||||
}
|
||||
if (!m_watcher.directories().isEmpty()) {
|
||||
m_watcher.removePaths(m_watcher.directories());
|
||||
}
|
||||
|
||||
if (!m_watchChanges || m_path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!m_watchChanges || m_path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_watcher.addPath(m_path);
|
||||
watchDirIfRecursive(m_path);
|
||||
m_watcher.addPath(m_path);
|
||||
watchDirIfRecursive(m_path);
|
||||
}
|
||||
|
||||
void FileSystemModel::updateEntries() {
|
||||
if (m_path.isEmpty()) {
|
||||
if (!m_entries.isEmpty()) {
|
||||
beginResetModel();
|
||||
qDeleteAll(m_entries);
|
||||
m_entries.clear();
|
||||
endResetModel();
|
||||
emit entriesChanged();
|
||||
}
|
||||
if (m_path.isEmpty()) {
|
||||
if (!m_entries.isEmpty()) {
|
||||
beginResetModel();
|
||||
qDeleteAll(m_entries);
|
||||
m_entries.clear();
|
||||
endResetModel();
|
||||
emit entriesChanged();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& future : m_futures) {
|
||||
future.cancel();
|
||||
}
|
||||
m_futures.clear();
|
||||
for (auto& future : m_futures) {
|
||||
future.cancel();
|
||||
}
|
||||
m_futures.clear();
|
||||
|
||||
updateEntriesForDir(m_path);
|
||||
updateEntriesForDir(m_path);
|
||||
}
|
||||
|
||||
void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||
const auto recursive = m_recursive;
|
||||
const auto showHidden = m_showHidden;
|
||||
const auto filter = m_filter;
|
||||
const auto nameFilters = m_nameFilters;
|
||||
const auto recursive = m_recursive;
|
||||
const auto showHidden = m_showHidden;
|
||||
const auto filter = m_filter;
|
||||
const auto nameFilters = m_nameFilters;
|
||||
|
||||
QSet<QString> oldPaths;
|
||||
for (const auto& entry : std::as_const(m_entries)) {
|
||||
oldPaths << entry->path();
|
||||
}
|
||||
QSet<QString> oldPaths;
|
||||
for (const auto& entry : std::as_const(m_entries)) {
|
||||
oldPaths << entry->path();
|
||||
}
|
||||
|
||||
auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString> > >& promise) {
|
||||
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
|
||||
const auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
|
||||
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
|
||||
|
||||
std::optional<QDirIterator> iter;
|
||||
std::optional<QDirIterator> iter;
|
||||
|
||||
if (filter == Images) {
|
||||
QStringList extraNameFilters = nameFilters;
|
||||
const auto formats = QImageReader::supportedImageFormats();
|
||||
for (const auto& format : formats) {
|
||||
extraNameFilters << "*." + format;
|
||||
}
|
||||
if (filter == Images) {
|
||||
QStringList extraNameFilters = nameFilters;
|
||||
const auto formats = QImageReader::supportedImageFormats();
|
||||
for (const auto& format : formats) {
|
||||
extraNameFilters << "*." + format;
|
||||
}
|
||||
|
||||
QDir::Filters filters = QDir::Files;
|
||||
if (showHidden) {
|
||||
filters |= QDir::Hidden;
|
||||
}
|
||||
QDir::Filters filters = QDir::Files;
|
||||
if (showHidden) {
|
||||
filters |= QDir::Hidden;
|
||||
}
|
||||
|
||||
iter.emplace(dir, extraNameFilters, filters, flags);
|
||||
} else {
|
||||
QDir::Filters filters;
|
||||
iter.emplace(dir, extraNameFilters, filters, flags);
|
||||
} else {
|
||||
QDir::Filters filters;
|
||||
|
||||
if (filter == Files) {
|
||||
filters = QDir::Files;
|
||||
} else if (filter == Dirs) {
|
||||
filters = QDir::Dirs | QDir::NoDotAndDotDot;
|
||||
} else {
|
||||
filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot;
|
||||
}
|
||||
if (filter == Files) {
|
||||
filters = QDir::Files;
|
||||
} else if (filter == Dirs) {
|
||||
filters = QDir::Dirs | QDir::NoDotAndDotDot;
|
||||
} else {
|
||||
filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot;
|
||||
}
|
||||
|
||||
if (showHidden) {
|
||||
filters |= QDir::Hidden;
|
||||
}
|
||||
if (showHidden) {
|
||||
filters |= QDir::Hidden;
|
||||
}
|
||||
|
||||
if (nameFilters.isEmpty()) {
|
||||
iter.emplace(dir, filters, flags);
|
||||
} else {
|
||||
iter.emplace(dir, nameFilters, filters, flags);
|
||||
}
|
||||
}
|
||||
if (nameFilters.isEmpty()) {
|
||||
iter.emplace(dir, filters, flags);
|
||||
} else {
|
||||
iter.emplace(dir, nameFilters, filters, flags);
|
||||
}
|
||||
}
|
||||
|
||||
QSet<QString> newPaths;
|
||||
while (iter->hasNext()) {
|
||||
if (promise.isCanceled()) {
|
||||
return;
|
||||
}
|
||||
QSet<QString> newPaths;
|
||||
while (iter->hasNext()) {
|
||||
if (promise.isCanceled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString path = iter->next();
|
||||
QString path = iter->next();
|
||||
|
||||
if (filter == Images) {
|
||||
QImageReader reader(path);
|
||||
if (!reader.canRead()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (filter == Images) {
|
||||
QImageReader reader(path);
|
||||
if (!reader.canRead()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
newPaths.insert(path);
|
||||
}
|
||||
newPaths.insert(path);
|
||||
}
|
||||
|
||||
if (promise.isCanceled()) {
|
||||
return;
|
||||
}
|
||||
if (promise.isCanceled() || newPaths == oldPaths) {
|
||||
return;
|
||||
}
|
||||
|
||||
promise.addResult(qMakePair(oldPaths - newPaths, newPaths - oldPaths));
|
||||
});
|
||||
promise.addResult(qMakePair(oldPaths - newPaths, newPaths - oldPaths));
|
||||
});
|
||||
|
||||
if (m_futures.contains(dir)) {
|
||||
m_futures[dir].cancel();
|
||||
}
|
||||
m_futures.insert(dir, future);
|
||||
if (m_futures.contains(dir)) {
|
||||
m_futures[dir].cancel();
|
||||
}
|
||||
m_futures.insert(dir, future);
|
||||
|
||||
future
|
||||
.then(this,
|
||||
[dir, this](QPair<QSet<QString>, QSet<QString> > result) {
|
||||
m_futures.remove(dir);
|
||||
if (!result.first.isEmpty() || !result.second.isEmpty()) {
|
||||
applyChanges(result.first, result.second);
|
||||
}
|
||||
})
|
||||
.onCanceled(this, [dir, this]() {
|
||||
m_futures.remove(dir);
|
||||
});
|
||||
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 (!watcher->future().isResultReadyAt(0)) {
|
||||
watcher->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
QList<int> removedIndices;
|
||||
for (int i = 0; i < m_entries.size(); ++i) {
|
||||
if (removedPaths.contains(m_entries[i]->path())) {
|
||||
removedIndices << i;
|
||||
}
|
||||
}
|
||||
std::sort(removedIndices.begin(), removedIndices.end(), std::greater<int>());
|
||||
QList<int> removedIndices;
|
||||
for (int i = 0; i < m_entries.size(); ++i) {
|
||||
if (removedPaths.contains(m_entries[i]->path())) {
|
||||
removedIndices << i;
|
||||
}
|
||||
}
|
||||
std::sort(removedIndices.begin(), removedIndices.end(), std::greater<int>());
|
||||
|
||||
// Batch remove old entries
|
||||
int start = -1;
|
||||
int end = -1;
|
||||
for (int idx : std::as_const(removedIndices)) {
|
||||
if (start == -1) {
|
||||
start = idx;
|
||||
end = idx;
|
||||
} else if (idx == end - 1) {
|
||||
end = idx;
|
||||
} else {
|
||||
beginRemoveRows(QModelIndex(), end, start);
|
||||
for (int i = start; i >= end; --i) {
|
||||
m_entries.takeAt(i)->deleteLater();
|
||||
}
|
||||
endRemoveRows();
|
||||
// Batch remove old entries
|
||||
int start = -1;
|
||||
int end = -1;
|
||||
for (int idx : std::as_const(removedIndices)) {
|
||||
if (start == -1) {
|
||||
start = idx;
|
||||
end = idx;
|
||||
} else if (idx == end - 1) {
|
||||
end = idx;
|
||||
} else {
|
||||
beginRemoveRows(QModelIndex(), end, start);
|
||||
for (int i = start; i >= end; --i) {
|
||||
m_entries.takeAt(i)->deleteLater();
|
||||
}
|
||||
endRemoveRows();
|
||||
|
||||
start = idx;
|
||||
end = idx;
|
||||
}
|
||||
}
|
||||
if (start != -1) {
|
||||
beginRemoveRows(QModelIndex(), end, start);
|
||||
for (int i = start; i >= end; --i) {
|
||||
m_entries.takeAt(i)->deleteLater();
|
||||
}
|
||||
endRemoveRows();
|
||||
}
|
||||
start = idx;
|
||||
end = idx;
|
||||
}
|
||||
}
|
||||
if (start != -1) {
|
||||
beginRemoveRows(QModelIndex(), end, start);
|
||||
for (int i = start; i >= end; --i) {
|
||||
m_entries.takeAt(i)->deleteLater();
|
||||
}
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
// Create new entries
|
||||
QList<FileSystemEntry*> newEntries;
|
||||
for (const auto& path : addedPaths) {
|
||||
newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this);
|
||||
}
|
||||
std::sort(newEntries.begin(), newEntries.end(), [this](const FileSystemEntry* a, const FileSystemEntry* b) {
|
||||
return compareEntries(a, b);
|
||||
});
|
||||
// Create new entries
|
||||
QList<FileSystemEntry*> newEntries;
|
||||
for (const auto& path : addedPaths) {
|
||||
newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this);
|
||||
}
|
||||
std::sort(newEntries.begin(), newEntries.end(), [this](const FileSystemEntry* a, const FileSystemEntry* b) {
|
||||
return compareEntries(a, b);
|
||||
});
|
||||
|
||||
// Batch insert new entries
|
||||
int insertStart = -1;
|
||||
QList<FileSystemEntry*> batchItems;
|
||||
for (const auto& entry : std::as_const(newEntries)) {
|
||||
const auto it = std::lower_bound(
|
||||
m_entries.begin(), m_entries.end(), entry, [this](const FileSystemEntry* a, const FileSystemEntry* b) {
|
||||
return compareEntries(a, b);
|
||||
});
|
||||
const auto row = static_cast<int>(it - m_entries.begin());
|
||||
// Batch insert new entries
|
||||
int insertStart = -1;
|
||||
QList<FileSystemEntry*> batchItems;
|
||||
for (const auto& entry : std::as_const(newEntries)) {
|
||||
const auto it = std::lower_bound(
|
||||
m_entries.begin(), m_entries.end(), entry, [this](const FileSystemEntry* a, const FileSystemEntry* b) {
|
||||
return compareEntries(a, b);
|
||||
});
|
||||
const auto row = static_cast<int>(it - m_entries.begin());
|
||||
|
||||
if (insertStart == -1) {
|
||||
insertStart = row;
|
||||
batchItems << entry;
|
||||
} else if (row == insertStart + batchItems.size()) {
|
||||
batchItems << entry;
|
||||
} else {
|
||||
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
|
||||
for (int i = 0; i < batchItems.size(); ++i) {
|
||||
m_entries.insert(insertStart + i, batchItems[i]);
|
||||
}
|
||||
endInsertRows();
|
||||
if (insertStart == -1) {
|
||||
insertStart = row;
|
||||
batchItems << entry;
|
||||
} else if (row == insertStart + batchItems.size()) {
|
||||
batchItems << entry;
|
||||
} else {
|
||||
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
|
||||
for (int i = 0; i < batchItems.size(); ++i) {
|
||||
m_entries.insert(insertStart + i, batchItems[i]);
|
||||
}
|
||||
endInsertRows();
|
||||
|
||||
insertStart = row;
|
||||
batchItems.clear();
|
||||
batchItems << entry;
|
||||
}
|
||||
}
|
||||
if (!batchItems.isEmpty()) {
|
||||
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
|
||||
for (int i = 0; i < batchItems.size(); ++i) {
|
||||
m_entries.insert(insertStart + i, batchItems[i]);
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
insertStart = row;
|
||||
batchItems.clear();
|
||||
batchItems << entry;
|
||||
}
|
||||
}
|
||||
if (!batchItems.isEmpty()) {
|
||||
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
|
||||
for (int i = 0; i < batchItems.size(); ++i) {
|
||||
m_entries.insert(insertStart + i, batchItems[i]);
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
emit entriesChanged();
|
||||
emit entriesChanged();
|
||||
}
|
||||
|
||||
bool FileSystemModel::compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const {
|
||||
if (a->isDir() != b->isDir()) {
|
||||
return m_sortReverse ^ a->isDir();
|
||||
}
|
||||
const auto cmp = a->relativePath().localeAwareCompare(b->relativePath());
|
||||
return m_sortReverse ? cmp > 0 : cmp < 0;
|
||||
if (a->isDir() != b->isDir()) {
|
||||
return m_sortReverse ^ a->isDir();
|
||||
}
|
||||
const auto cmp = a->relativePath().localeAwareCompare(b->relativePath());
|
||||
return m_sortReverse ? cmp > 0 : cmp < 0;
|
||||
}
|
||||
|
||||
} // namespace ZShell::models
|
||||
|
||||
@@ -13,136 +13,136 @@
|
||||
namespace ZShell::models {
|
||||
|
||||
class FileSystemEntry : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel")
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel")
|
||||
|
||||
Q_PROPERTY(QString path READ path CONSTANT)
|
||||
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
|
||||
Q_PROPERTY(QString name READ name CONSTANT)
|
||||
Q_PROPERTY(QString baseName READ baseName CONSTANT)
|
||||
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
|
||||
Q_PROPERTY(QString suffix READ suffix CONSTANT)
|
||||
Q_PROPERTY(qint64 size READ size CONSTANT)
|
||||
Q_PROPERTY(bool isDir READ isDir CONSTANT)
|
||||
Q_PROPERTY(bool isImage READ isImage CONSTANT)
|
||||
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
|
||||
Q_PROPERTY(QString path READ path CONSTANT)
|
||||
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
|
||||
Q_PROPERTY(QString name READ name CONSTANT)
|
||||
Q_PROPERTY(QString baseName READ baseName CONSTANT)
|
||||
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
|
||||
Q_PROPERTY(QString suffix READ suffix CONSTANT)
|
||||
Q_PROPERTY(qint64 size READ size CONSTANT)
|
||||
Q_PROPERTY(bool isDir READ isDir CONSTANT)
|
||||
Q_PROPERTY(bool isImage READ isImage CONSTANT)
|
||||
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
|
||||
|
||||
public:
|
||||
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr);
|
||||
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QString path() const;
|
||||
[[nodiscard]] QString relativePath() const;
|
||||
[[nodiscard]] QString name() const;
|
||||
[[nodiscard]] QString baseName() const;
|
||||
[[nodiscard]] QString parentDir() const;
|
||||
[[nodiscard]] QString suffix() const;
|
||||
[[nodiscard]] qint64 size() const;
|
||||
[[nodiscard]] bool isDir() const;
|
||||
[[nodiscard]] bool isImage() const;
|
||||
[[nodiscard]] QString mimeType() const;
|
||||
[[nodiscard]] QString path() const;
|
||||
[[nodiscard]] QString relativePath() const;
|
||||
[[nodiscard]] QString name() const;
|
||||
[[nodiscard]] QString baseName() const;
|
||||
[[nodiscard]] QString parentDir() const;
|
||||
[[nodiscard]] QString suffix() const;
|
||||
[[nodiscard]] qint64 size() const;
|
||||
[[nodiscard]] bool isDir() const;
|
||||
[[nodiscard]] bool isImage() const;
|
||||
[[nodiscard]] QString mimeType() const;
|
||||
|
||||
void updateRelativePath(const QDir& dir);
|
||||
void updateRelativePath(const QDir& dir);
|
||||
|
||||
signals:
|
||||
void relativePathChanged();
|
||||
void relativePathChanged();
|
||||
|
||||
private:
|
||||
const QFileInfo m_fileInfo;
|
||||
const QFileInfo m_fileInfo;
|
||||
|
||||
const QString m_path;
|
||||
QString m_relativePath;
|
||||
const QString m_path;
|
||||
QString m_relativePath;
|
||||
|
||||
mutable bool m_isImage;
|
||||
mutable bool m_isImageInitialised;
|
||||
mutable bool m_isImage;
|
||||
mutable bool m_isImageInitialised;
|
||||
|
||||
mutable QString m_mimeType;
|
||||
mutable bool m_mimeTypeInitialised;
|
||||
mutable QString m_mimeType;
|
||||
mutable bool m_mimeTypeInitialised;
|
||||
};
|
||||
|
||||
class FileSystemModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
||||
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
|
||||
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
|
||||
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
|
||||
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
|
||||
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
|
||||
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
||||
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
|
||||
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
|
||||
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
|
||||
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
|
||||
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
|
||||
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
|
||||
|
||||
Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged)
|
||||
Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged)
|
||||
|
||||
public:
|
||||
enum Filter {
|
||||
NoFilter,
|
||||
Images,
|
||||
Files,
|
||||
Dirs
|
||||
};
|
||||
Q_ENUM(Filter)
|
||||
enum Filter {
|
||||
NoFilter,
|
||||
Images,
|
||||
Files,
|
||||
Dirs
|
||||
};
|
||||
Q_ENUM(Filter)
|
||||
|
||||
explicit FileSystemModel(QObject* parent = nullptr);
|
||||
explicit FileSystemModel(QObject* parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
[[nodiscard]] QString path() const;
|
||||
void setPath(const QString& path);
|
||||
[[nodiscard]] QString path() const;
|
||||
void setPath(const QString& path);
|
||||
|
||||
[[nodiscard]] bool recursive() const;
|
||||
void setRecursive(bool recursive);
|
||||
[[nodiscard]] bool recursive() const;
|
||||
void setRecursive(bool recursive);
|
||||
|
||||
[[nodiscard]] bool watchChanges() const;
|
||||
void setWatchChanges(bool watchChanges);
|
||||
[[nodiscard]] bool watchChanges() const;
|
||||
void setWatchChanges(bool watchChanges);
|
||||
|
||||
[[nodiscard]] bool showHidden() const;
|
||||
void setShowHidden(bool showHidden);
|
||||
[[nodiscard]] bool showHidden() const;
|
||||
void setShowHidden(bool showHidden);
|
||||
|
||||
[[nodiscard]] bool sortReverse() const;
|
||||
void setSortReverse(bool sortReverse);
|
||||
[[nodiscard]] bool sortReverse() const;
|
||||
void setSortReverse(bool sortReverse);
|
||||
|
||||
[[nodiscard]] Filter filter() const;
|
||||
void setFilter(Filter filter);
|
||||
[[nodiscard]] Filter filter() const;
|
||||
void setFilter(Filter filter);
|
||||
|
||||
[[nodiscard]] QStringList nameFilters() const;
|
||||
void setNameFilters(const QStringList& nameFilters);
|
||||
[[nodiscard]] QStringList nameFilters() const;
|
||||
void setNameFilters(const QStringList& nameFilters);
|
||||
|
||||
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
|
||||
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
|
||||
|
||||
signals:
|
||||
void pathChanged();
|
||||
void recursiveChanged();
|
||||
void watchChangesChanged();
|
||||
void showHiddenChanged();
|
||||
void sortReverseChanged();
|
||||
void filterChanged();
|
||||
void nameFiltersChanged();
|
||||
void entriesChanged();
|
||||
void pathChanged();
|
||||
void recursiveChanged();
|
||||
void watchChangesChanged();
|
||||
void showHiddenChanged();
|
||||
void sortReverseChanged();
|
||||
void filterChanged();
|
||||
void nameFiltersChanged();
|
||||
void entriesChanged();
|
||||
|
||||
private:
|
||||
QDir m_dir;
|
||||
QFileSystemWatcher m_watcher;
|
||||
QList<FileSystemEntry*> m_entries;
|
||||
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString> > > > m_futures;
|
||||
QDir m_dir;
|
||||
QFileSystemWatcher m_watcher;
|
||||
QList<FileSystemEntry*> m_entries;
|
||||
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString>>>> m_futures;
|
||||
|
||||
QString m_path;
|
||||
bool m_recursive;
|
||||
bool m_watchChanges;
|
||||
bool m_showHidden;
|
||||
bool m_sortReverse = false;
|
||||
Filter m_filter;
|
||||
QStringList m_nameFilters;
|
||||
QString m_path;
|
||||
bool m_recursive;
|
||||
bool m_watchChanges;
|
||||
bool m_showHidden;
|
||||
bool m_sortReverse;
|
||||
Filter m_filter;
|
||||
QStringList m_nameFilters;
|
||||
|
||||
void watchDirIfRecursive(const QString& path);
|
||||
void update();
|
||||
void updateWatcher();
|
||||
void updateEntries();
|
||||
void updateEntriesForDir(const QString& dir);
|
||||
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
|
||||
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
|
||||
void watchDirIfRecursive(const QString& path);
|
||||
void update();
|
||||
void updateWatcher();
|
||||
void updateEntries();
|
||||
void updateEntriesForDir(const QString& dir);
|
||||
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
|
||||
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
|
||||
};
|
||||
|
||||
} // namespace ZShell::models
|
||||
|
||||
@@ -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.
+18
-61
@@ -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
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
def invoke(*args: str) -> None:
|
||||
from typer.testing import CliRunner
|
||||
|
||||
def invoke(*args: str):
|
||||
runner = CliRunner()
|
||||
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)
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
//@ pragma UseQApplication
|
||||
//@ pragma Env QSG_RENDER_LOOP=threaded
|
||||
//@ pragma Env QSG_RHI_BACKEND=vulkan
|
||||
// @ pragma Env QSG_RHI_BACKEND=vulkan
|
||||
//@ pragma Env QSG_NO_VSYNC=1
|
||||
//@ pragma Env QS_NO_RELOAD_POPUP=1
|
||||
//@ 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);
|
||||
buf = sliding_horizontal(&buf, 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")?
|
||||
.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
|
||||
);
|
||||
// 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")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user