16 Commits

Author SHA1 Message Date
zach 74ce5bb868 Merge branch 'main' into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 25s
Python / lint-format (pull_request) Successful in 31s
Python / test (pull_request) Failing after 54s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m13s
2026-05-26 18:42:18 +02:00
zach a3f38e6414 fetch necessary hyprland options for screenshot tool
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 45s
Python / lint-format (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m50s
2026-05-26 16:46:45 +02:00
zach a2505ee875 add low battery toast, unload if not laptop battery. 2026-05-26 15:57:40 +02:00
zach f475e43c54 Merge pull request '100 shell autocomplete, type fixes, Pillow deprecation cleanup' (#101) from 100-cli-autocompletion into main
Reviewed-on: #101
Reviewed-by: zach <zach@brohn.se>
2026-05-26 13:10:56 +02:00
zach c33d6ae2dd Merge branch 'main' into 100-cli-autocompletion
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 33s
Python / test (pull_request) Successful in 49s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
2026-05-26 13:05:10 +02:00
zach ca19a60e5c temporary fix for focus being stolen even after release. Change to async loaders 2026-05-26 13:03:46 +02:00
Inorishio 6aedf6f8b7 scale fix 2026-05-26 11:41:31 +02:00
AramJonghu 233ea3efb9 accidental duplicate logic removed
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 48s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m45s
2026-05-26 09:30:58 +02:00
AramJonghu d19eead1f5 autocomplete now optional. Includes hint on first command input
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 57s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
2026-05-26 09:25:06 +02:00
zach 4ea74ed516 pass monitor scale to screenshot tool 2026-05-26 01:25:09 +02:00
zach f00af9d70f actually fixed config fetching in hyprland lua 2026-05-26 01:12:36 +02:00
Inorishio de11767d3b zshell-img-tools crate reduction to 53, process release fix, blur-passes, scale impl, settings passes setting, scale only avail in config 2026-05-25 23:15:00 +02:00
AramJonghu d0b2a5fc1d adding hint if is ran without -- flag
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 26s
Python / test (pull_request) Successful in 45s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m47s
2026-05-25 19:19:55 +02:00
AramJonghu 32acfa6b9f pyright/ruff error fixes. Autoinstall check of autocomplete 2026-05-25 19:03:00 +02:00
AramJonghu 17fcf1a02c pyright error fixes. added autocomplete to some commands 2026-05-25 18:42:34 +02:00
AramJonghu 1c11549811 initial commit 2026-05-25 17:45:39 +02:00
25 changed files with 550 additions and 1027 deletions
+5
View File
@@ -214,6 +214,10 @@ Singleton {
}, },
idle: { idle: {
timeouts: general.idle.timeouts timeouts: general.idle.timeouts
},
battery: {
popupThresholds: general.battery.popupThresholds,
critPerc: general.battery.critPerc
} }
}; };
} }
@@ -297,6 +301,7 @@ Singleton {
drop_shadow: screenshot.drop_shadow, drop_shadow: screenshot.drop_shadow,
rounded_corners: screenshot.rounded_corners, rounded_corners: screenshot.rounded_corners,
shadow_blur_radius: screenshot.shadow_blur_radius, shadow_blur_radius: screenshot.shadow_blur_radius,
shadow_blur_passes: screenshot.shadow_blur_passes,
shadow_color: screenshot.shadow_color, shadow_color: screenshot.shadow_color,
shadow_offset_x: screenshot.shadow_offset_x, shadow_offset_x: screenshot.shadow_offset_x,
shadow_offset_y: screenshot.shadow_offset_y shadow_offset_y: screenshot.shadow_offset_y
+13
View File
@@ -4,6 +4,8 @@ import Quickshell
JsonObject { JsonObject {
property Apps apps: Apps { property Apps apps: Apps {
} }
property Battery battery: Battery {
}
property Color color: Color { property Color color: Color {
} }
property string dateFormat: "ddd d MMM - hh:mm:ss" property string dateFormat: "ddd d MMM - hh:mm:ss"
@@ -19,6 +21,17 @@ JsonObject {
property list<string> playback: ["mpv"] property list<string> playback: ["mpv"]
property list<string> terminal: ["kitty"] property list<string> terminal: ["kitty"]
} }
component Battery: JsonObject {
property int critPerc: 5
property list<var> popupThresholds: [
{
perc: 20,
name: qsTr("Low battery"),
message: qsTr("Battery at %1%").arg(Battery.currentPerc * 100),
icon: "battery_android_frame_2"
},
]
}
component Color: JsonObject { component Color: JsonObject {
property int hyprsunsetTemp: 5000 property int hyprsunsetTemp: 5000
property string mode: "dark" property string mode: "dark"
+1
View File
@@ -6,6 +6,7 @@ JsonObject {
property bool enable_pp: true property bool enable_pp: true
property string mode: "manual" property string mode: "manual"
property bool rounded_corners: false property bool rounded_corners: false
property int shadow_blur_passes: 1
property real shadow_blur_radius: 22.0 property real shadow_blur_radius: 22.0
property list<int> shadow_color: [0, 0, 0, 160] property list<int> shadow_color: [0, 0, 0, 160]
property real shadow_offset_x: 5.0 property real shadow_offset_x: 5.0
+46
View File
@@ -0,0 +1,46 @@
import Quickshell
import Quickshell.Services.UPower
import QtQuick
import ZShell
import qs.Config
import qs.Components.Toast
Scope {
id: root
readonly property real currentPerc: UPower.displayDevice.percentage
readonly property list<var> popupThresholds: [...Config.general.battery.popupThresholds].sort((a, b) => b.perc - a.perc)
Connections {
function onOnBatteryChanged(): void {
if (UPower.onBattery) {
if (Config.utilities.toasts.chargingChanged)
Toaster.toast(qsTr("Charger unplugged"), qsTr("Battery is discharging"), "power_off");
} else {
if (Config.utilities.toasts.chargingChanged)
Toaster.toast(qsTr("Charger plugged in"), qsTr("Battery is charging"), "power");
for (const level of root.popupThresholds)
level.warned = false;
}
}
target: UPower
}
Connections {
function onPercentageChanged(): void {
if (!UPower.onBattery)
return;
const p = UPower.displayDevice.percentage * 100;
for (const perc of root.popupThresholds) {
if (p <= perc.perc && !perc.warned) {
perc.warned = true;
Toaster.toast(perc.title ?? qsTr("Battery warning"), perc.message ?? qsTr("Battery perc is low"), perc.icon ?? "battery_android_alert", perc.critical ? Toast.Error : Toast.Warning);
}
}
}
target: UPower.displayDevice
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ Variants {
property var root: Quickshell.shellDir property var root: Quickshell.shellDir
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None // WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
color: "transparent" color: "transparent"
contentItem.focus: true contentItem.focus: true
mask: visibilities.isDrawing ? null : region mask: visibilities.isDrawing ? null : region
+1 -1
View File
@@ -36,7 +36,7 @@ Singleton {
PersistentProperties { PersistentProperties {
id: props id: props
property bool enabled: Hypr.options["animations:enabled"] === 0 property bool enabled: Hypr.options.animations.enabled === 0
reloadableId: "gamemode" reloadableId: "gamemode"
} }
-1
View File
@@ -158,6 +158,5 @@ Singleton {
HyprExtras { HyprExtras {
id: extras id: extras
} }
} }
+8 -3
View File
@@ -30,12 +30,17 @@ MouseArea {
property real ey: screen.height property real ey: screen.height
required property LazyLoader loader required property LazyLoader loader
property bool onClient property bool onClient
property real realBorderWidth: onClient ? (Hypr.options["general:border_size"] ?? 1) : 2 property real realBorderWidth: onClient ? (Hypr.options.general.border_size ?? 1) : 2
property real realRounding: onClient ? (Hypr.options["decoration:rounding"] ?? 0) : 0 property real realRounding: onClient ? (Hypr.options.decoration.rounding ?? 0) : 0
property real rsx: Math.min(sx, ex) property real rsx: Math.min(sx, ex)
property real rsy: Math.min(sy, ey) property real rsy: Math.min(sy, ey)
readonly property real scaleRatio: Hypr.monitorFor(screen).scale
required property ShellScreen screen required property ShellScreen screen
property real sh: Math.abs(sy - ey) 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 ssx
property real ssy property real ssy
property real sw: Math.abs(sx - ex) property real sw: Math.abs(sx - ex)
@@ -66,7 +71,7 @@ MouseArea {
function save(): void { function save(): void {
const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`); 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])); 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(); closeAnim.start();
} }
-1
View File
@@ -6,7 +6,6 @@ import Quickshell
Singleton { Singleton {
property var extraOpts: ({}) property var extraOpts: ({})
readonly property list<var> fuzzyPrepped: useFuzzy ? list.map(e => { readonly property list<var> fuzzyPrepped: useFuzzy ? list.map(e => {
console.log(useFuzzy);
const obj = { const obj = {
_item: e _item: e
}; };
+12 -55
View File
@@ -4,6 +4,7 @@ import Quickshell
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Modules.Launcher.Services
Item { Item {
id: root id: root
@@ -19,26 +20,17 @@ Item {
max -= panels.popouts.nonAnimHeight; max -= panels.popouts.nonAnimHeight;
return max; return max;
} }
property real offsetScale: shouldBeActive ? 0 : 1
required property var panels required property var panels
required property ShellScreen screen required property ShellScreen screen
required property PersistentProperties visibilities
readonly property bool shouldBeActive: visibilities.launcher readonly property bool shouldBeActive: visibilities.launcher
property real offsetScale: shouldBeActive ? 0 : 1 required property PersistentProperties visibilities
onShouldBeActiveChanged: {
if (shouldBeActive) {
implicitHeight = Qt.binding(() => content.implicitHeight);
timer.stop();
} else {
implicitHeight = implicitHeight;
}
}
visible: offsetScale < 1
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 400 implicitWidth: content.implicitWidth || 400
opacity: 1 - offsetScale opacity: 1 - offsetScale
visible: offsetScale < 1
Behavior on offsetScale { Behavior on offsetScale {
Anim { Anim {
@@ -47,61 +39,26 @@ Item {
} }
} }
onMaxHeightChanged: timer.start() Component.onCompleted: Qt.callLater(() => Apps)
onShouldBeActiveChanged: {
Connections { if (shouldBeActive)
function onEnabledChanged(): void { implicitHeight = Qt.binding(() => content.implicitHeight);
timer.start(); else
} implicitHeight = implicitHeight;
function onMaxShownChanged(): void {
timer.start();
}
target: Config.launcher
}
Connections {
function onValuesChanged(): void {
if (DesktopEntries.applications.values.length < Config.launcher.maxAppsShown)
timer.start();
}
target: DesktopEntries.applications
}
Timer {
id: timer
interval: Appearance.anim.durations.small
onRunningChanged: {
if (running && !root.shouldBeActive) {
content.visible = false;
content.active = true;
} else {
root.contentHeight = Math.min(root.maxHeight, content.implicitHeight);
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
}
}
} }
Loader { Loader {
id: content id: content
active: false active: root.shouldBeActive || root.visible
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
asynchronous: true
sourceComponent: Content { sourceComponent: Content {
maxHeight: root.maxHeight maxHeight: root.maxHeight
panels: root.panels panels: root.panels
visibilities: root.visibilities visibilities: root.visibilities
Component.onCompleted: root.contentHeight = implicitHeight
} }
Component.onCompleted: timer.start()
} }
} }
+27 -14
View File
@@ -43,7 +43,7 @@ SettingsPage {
} }
Separator { Separator {
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
} }
SettingSpinBox { SettingSpinBox {
@@ -51,34 +51,34 @@ SettingsPage {
name: "Corner radius" name: "Corner radius"
object: Config.screenshot object: Config.screenshot
setting: "corner_radius" setting: "corner_radius"
shouldBeActive: Config.screenshot.mode === "manual"
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
Separator { Separator {
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
} }
SettingSwitch { SettingSwitch {
name: "Enable drop shadow" name: "Enable drop shadow"
object: Config.screenshot object: Config.screenshot
setting: "drop_shadow" setting: "drop_shadow"
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
} }
Separator { Separator {
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
} }
SettingSwitch { SettingSwitch {
name: "Enable rounded corners" name: "Enable rounded corners"
object: Config.screenshot object: Config.screenshot
setting: "rounded_corners" setting: "rounded_corners"
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
} }
Separator { Separator {
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
} }
SettingSpinBox { SettingSpinBox {
@@ -86,23 +86,36 @@ SettingsPage {
name: "Shadow blur radius" name: "Shadow blur radius"
object: Config.screenshot object: Config.screenshot
setting: "shadow_blur_radius" setting: "shadow_blur_radius"
shouldBeActive: Config.screenshot.mode === "manual"
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
Separator { Separator {
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
} }
SettingSwitch { SettingSwitch {
name: "Shadow color broken atm" name: "Shadow color broken atm"
object: Config.Screenshot object: Config.Screenshot
setting: "shadow_color" setting: "shadow_color"
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
} }
Separator { 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 { SettingSpinBox {
@@ -110,12 +123,12 @@ SettingsPage {
name: "Shadow offset X" name: "Shadow offset X"
object: Config.screenshot object: Config.screenshot
setting: "shadow_offset_x" setting: "shadow_offset_x"
shouldBeActive: Config.screenshot.mode === "manual"
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
Separator { Separator {
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
} }
SettingSpinBox { SettingSpinBox {
@@ -123,8 +136,8 @@ SettingsPage {
name: "Shadow offset Y" name: "Shadow offset Y"
object: Config.screenshot object: Config.screenshot
setting: "shadow_offset_y" setting: "shadow_offset_y"
shouldBeActive: Config.screenshot.mode === "manual"
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@ import qs.Modules.DesktopIcons
Loader { Loader {
active: Config.background.enabled active: Config.background.enabled
asynchronous: true asynchronous: false
sourceComponent: Variants { sourceComponent: Variants {
model: Quickshell.screens model: Quickshell.screens
+140 -19
View File
@@ -1,11 +1,18 @@
#include "hyprextras.hpp" #include "hyprextras.hpp"
#include "hyprdevices.hpp" #include "hyprdevices.hpp"
#include <functional>
#include <memory>
#include <qdir.h> #include <qdir.h>
#include <qcolor.h>
#include <qjsonarray.h> #include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
#include <qlocalsocket.h> #include <qlocalsocket.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <qmetatype.h> #include <qmetatype.h>
#include <qobject.h>
#include <qregularexpression.h> #include <qregularexpression.h>
#include <qvariant.h> #include <qvariant.h>
@@ -163,6 +170,86 @@ static QString buildHlConfigCall(const QString& key, const QVariant& value) {
return out; 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 } // namespace
HyprExtras::HyprExtras(QObject* parent) HyprExtras::HyprExtras(QObject* parent)
@@ -203,7 +290,7 @@ HyprExtras::HyprExtras(QObject* parent)
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly); m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
} }
QVariantHash HyprExtras::options() const { QVariantMap HyprExtras::options() const {
return m_options; return m_options;
} }
@@ -269,30 +356,64 @@ void HyprExtras::refreshOptions() {
m_optionsRefresh->close(); m_optionsRefresh->close();
} }
m_optionsRefresh = makeRequestJson(QStringLiteral("descriptions"), [this](bool success, const QJsonDocument& response) { ++m_optionsRefreshGeneration;
m_optionsRefresh.reset(); const quint64 generation = m_optionsRefreshGeneration;
if (!success) {
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; return;
} }
const auto options = response.array(); if (index >= optionKeys.size()) {
bool dirty = false; if (m_options != *nextOptions) {
m_options = *nextOptions;
for (const auto& o : std::as_const(options)) { emit optionsChanged();
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);
} }
return;
} }
if (dirty) { const QString key = optionKeys.at(index);
emit optionsChanged();
} 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() { void HyprExtras::refreshDevices() {
+8 -3
View File
@@ -1,9 +1,13 @@
#pragma once #pragma once
#include <functional>
#include <qjsondocument.h>
#include <qlocalsocket.h> #include <qlocalsocket.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qsharedpointer.h> #include <qsharedpointer.h>
#include <qstringlist.h>
#include <qvariant.h> #include <qvariant.h>
namespace ZShell::internal::hypr { namespace ZShell::internal::hypr {
@@ -15,13 +19,13 @@ Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_MOC_INCLUDE("hyprdevices.hpp") 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) Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
public: public:
explicit HyprExtras(QObject* parent = nullptr); explicit HyprExtras(QObject* parent = nullptr);
[[nodiscard]] QVariantHash options() const; [[nodiscard]] QVariantMap options() const;
[[nodiscard]] HyprDevices* devices() const; [[nodiscard]] HyprDevices* devices() const;
Q_INVOKABLE void message(const QString& message); Q_INVOKABLE void message(const QString& message);
@@ -42,11 +46,12 @@ QString m_eventSocket;
QLocalSocket* m_socket; QLocalSocket* m_socket;
bool m_socketValid; bool m_socketValid;
QVariantHash m_options; QVariantMap m_options;
HyprDevices* const m_devices; HyprDevices* const m_devices;
SocketPtr m_optionsRefresh; SocketPtr m_optionsRefresh;
SocketPtr m_devicesRefresh; SocketPtr m_devicesRefresh;
quint64 m_optionsRefreshGeneration = 0;
void socketError(QLocalSocket::LocalSocketError error) const; void socketError(QLocalSocket::LocalSocketError error) const;
void socketStateChanged(QLocalSocket::LocalSocketState state); void socketStateChanged(QLocalSocket::LocalSocketState state);
+39 -2
View File
@@ -1,16 +1,53 @@
from __future__ import annotations from __future__ import annotations
import sys
from pathlib import Path
import click
import typer import typer
from typer._completion_shared import install, _get_shell_name
from zshell.subcommands import shell, scheme, screenshot, wallpaper, record from zshell.subcommands import shell, scheme, screenshot, wallpaper, record
app = typer.Typer() app = typer.Typer(name="zshell-cli", add_completion=False)
app.add_typer(shell.app, name="shell") app.add_typer(shell.app, name="shell")
app.add_typer(scheme.app, name="scheme") app.add_typer(scheme.app, name="scheme")
app.add_typer(screenshot.app, name="screenshot") app.add_typer(screenshot.app, name="screenshot")
app.add_typer(wallpaper.app, name="wallpaper") app.add_typer(wallpaper.app, name="wallpaper")
app.add_typer(record.app, name="record") app.add_typer(record.app, name="record")
# app.add_typer(preset.app, name="preset")
def _completion_installed() -> bool:
shell = _get_shell_name()
match shell:
case "zsh":
return (Path.home() / ".zfunc" / "_zshell-cli").exists()
case "bash":
return (Path.home() / ".bash_completions" / "zshell-cli.sh").exists()
case "fish":
return (Path.home() / ".config" / "fish" / "completions" / "zshell-cli.fish").exists()
return False
def _install_completion() -> None:
if _completion_installed():
click.echo("zshell-cli: Shell completion already installed.")
raise typer.Exit()
shell = _get_shell_name()
if shell is None:
click.echo("zshell-cli: Unable to detect shell type.", err=True)
raise typer.Exit(code=1)
try:
_, path = install(prog_name="zshell-cli")
click.secho(f"zshell-cli: Shell completion installed ({shell}: {path})", fg="green")
click.echo("zshell-cli: Restart your shell or source the file to enable tab-completion.")
except Exception:
pass
def main() -> None: def main() -> None:
if "--install-autocomplete" in sys.argv:
_install_completion()
return
if sys.stdout.isatty() and not _completion_installed():
click.echo("zshell-cli: Tip: run with --install-autocomplete for tab completion.", err=True)
app() app()
+20 -24
View File
@@ -18,8 +18,7 @@ TEMP_RECORDING = STATE_DIR / "recording.mp4"
REPLAY_RECORDING = STATE_DIR / "replay.mp4" REPLAY_RECORDING = STATE_DIR / "replay.mp4"
NOTIF_ID_FILE = STATE_DIR / "notifid.txt" NOTIF_ID_FILE = STATE_DIR / "notifid.txt"
RECORDINGS_DIR = os.getenv("ZSHELL_RECORDINGS_DIR", RECORDINGS_DIR = os.getenv("ZSHELL_RECORDINGS_DIR", str(Path(HOME) / "Videos/Recordings"))
str(Path(HOME) / "Videos/Recordings"))
def _read_extra_args() -> list[str]: def _read_extra_args() -> list[str]:
@@ -36,7 +35,7 @@ def _is_recording() -> bool:
return subprocess.run(["pidof", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0 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]: def _notify(summary: str, body: str = "", actions: list | None = None, timeout: int = 5000) -> Optional[int]:
args = ["notify-send", summary, body, "-t", str(timeout), "-p"] args = ["notify-send", summary, body, "-t", str(timeout), "-p"]
if actions: if actions:
for action in actions: for action in actions:
@@ -49,14 +48,12 @@ def _notify(summary: str, body: str = "", actions: list = None, timeout: int = 5
def _close_notification(notif_id: int): def _close_notification(notif_id: int):
subprocess.run(["notify-send", "--close", str(notif_id)], subprocess.run(["notify-send", "--close", str(notif_id)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def _get_monitors() -> list[dict]: def _get_monitors() -> list[dict]:
try: try:
res = subprocess.run(["hyprctl", "monitors", "-j"], res = subprocess.run(["hyprctl", "monitors", "-j"], capture_output=True, text=True)
capture_output=True, text=True)
return json.loads(res.stdout) return json.loads(res.stdout)
except Exception: except Exception:
return [] return []
@@ -92,6 +89,7 @@ def _slurp_region() -> Optional[str]:
def _parse_geometry(geometry: str) -> Optional[tuple[int, int, int, int]]: def _parse_geometry(geometry: str) -> Optional[tuple[int, int, int, int]]:
import re import re
match = re.match(r"(\d+)x(\d+)\+(\d+)\+(\d+)", geometry) match = re.match(r"(\d+)x(\d+)\+(\d+)\+(\d+)", geometry)
if match: if match:
return int(match.group(3)), int(match.group(4)), int(match.group(1)), int(match.group(2)) return int(match.group(3)), int(match.group(4)), int(match.group(1)), int(match.group(2))
@@ -139,8 +137,7 @@ def start_recording(region: Optional[str], sound: bool):
cmd.extend(extra_args) cmd.extend(extra_args)
cmd.extend(["-o", str(TEMP_RECORDING)]) cmd.extend(["-o", str(TEMP_RECORDING)])
subprocess.Popen(cmd, start_new_session=True, subprocess.Popen(cmd, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
notif_id = _notify("Recording started", f"Saving to {TEMP_RECORDING}") notif_id = _notify("Recording started", f"Saving to {TEMP_RECORDING}")
if notif_id is not None: if notif_id is not None:
@@ -148,14 +145,12 @@ def start_recording(region: Optional[str], sound: bool):
time.sleep(1) time.sleep(1)
if not _is_recording(): if not _is_recording():
_notify("Recording failed", _notify("Recording failed", "Check gpu-screen-recorder output.", timeout=5000)
"Check gpu-screen-recorder output.", timeout=5000)
raise typer.Exit(code=1) raise typer.Exit(code=1)
def stop_recording(clipboard: bool): def stop_recording(clipboard: bool):
subprocess.run(["pkill", "-f", RECORDER], subprocess.run(["pkill", "-f", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
for _ in range(50): for _ in range(50):
if not _is_recording(): if not _is_recording():
@@ -178,30 +173,31 @@ def stop_recording(clipboard: bool):
NOTIF_ID_FILE.unlink() NOTIF_ID_FILE.unlink()
if clipboard: if clipboard:
subprocess.run(["wl-copy", "--type", "text/uri-list", f"file://{final_path}"], subprocess.run(
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) ["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) _notify("Recording stopped", f"Saved to {final_path}", timeout=5000)
def toggle_pause(): def toggle_pause():
subprocess.run(["pkill", "-USR2", "-f", RECORDER], subprocess.run(["pkill", "-USR2", "-f", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
typer.echo("Toggled pause.") typer.echo("Toggled pause.")
@app.command() @app.command()
def record( def record(
region: Optional[str] = typer.Option( region: Optional[str] = typer.Option(
None, "--region", "-r", None,
"--region",
"-r",
help="Record a region. Use 'slurp' (or omit value) to select interactively, or give 'WxH+X+Y'.", help="Record a region. Use 'slurp' (or omit value) to select interactively, or give 'WxH+X+Y'.",
), ),
sound: bool = typer.Option( sound: bool = typer.Option(False, "--sound", "-s", help="Record audio from default output."),
False, "--sound", "-s", help="Record audio from default output."), pause: bool = typer.Option(False, "--pause", "-p", help="Toggle pause/resume."),
pause: bool = typer.Option( clipboard: bool = typer.Option(False, "--clipboard", "-c", help="Copy the final recording path to clipboard."),
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.""" """Start or stop a screen recording with gpu-screen-recorder."""
if pause: if pause:
+108 -16
View File
@@ -2,6 +2,7 @@ import typer
import json import json
import shutil import shutil
import os import os
import sys
import re import re
import subprocess import subprocess
@@ -15,11 +16,61 @@ from materialyoucolor.score.score import Score
from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors
from materialyoucolor.hct.hct import Hct from materialyoucolor.hct.hct import Hct
from materialyoucolor.utils.color_utils import argb_from_rgb from materialyoucolor.utils.color_utils import argb_from_rgb
from materialyoucolor.utils.math_utils import difference_degrees, rotation_direction, sanitize_degrees_double from materialyoucolor.utils.math_utils import (
difference_degrees,
rotation_direction,
sanitize_degrees_double,
)
app = typer.Typer() app = typer.Typer()
def _complete_scheme_name(incomplete):
schemes = [
"fruit-salad",
"expressive",
"monochrome",
"rainbow",
"tonal-spot",
"neutral",
"fidelity",
"content",
"vibrant",
]
return [s for s in schemes if incomplete in s]
def _complete_preset(incomplete):
results = []
for sid, meta in list_schemes().items():
for v in meta.variants:
preset = f"{sid}:{v.id}"
if incomplete in preset:
results.append((preset, f"{meta.name} - {v.name}"))
return results
def _complete_mode(incomplete):
return [m for m in ("dark", "light") if incomplete in m]
def _complete_accent(ctx, incomplete):
preset_val = ctx.params.get("preset")
if preset_val:
try:
p_scheme, p_variant = resolve_preset(preset_val)
for v in list_schemes()[p_scheme].variants:
if v.id == p_variant:
return [a for a in v.accents if incomplete in a]
except (ValueError, KeyError):
pass
all_accents = set()
for meta in list_schemes().values():
for v in meta.variants:
all_accents.update(v.accents)
return [a for a in sorted(all_accents) if incomplete in a]
@app.command() @app.command()
def list_presets( def list_presets(
json_format: bool = typer.Option(False, "--json", help="Output in JSON format"), json_format: bool = typer.Option(False, "--json", help="Output in JSON format"),
@@ -30,7 +81,7 @@ def list_presets(
for sid, meta in sorted(schemes.items()): for sid, meta in sorted(schemes.items()):
variants = {} variants = {}
for v in meta.variants: for v in meta.variants:
entry = {"modes": sorted(v.modes)} entry: dict[str, Any] = {"modes": sorted(v.modes)}
if v.accents: if v.accents:
entry["accents"] = sorted(v.accents) entry["accents"] = sorted(v.accents)
entry["default_accent"] = sorted(v.accents)[0] entry["default_accent"] = sorted(v.accents)[0]
@@ -55,14 +106,35 @@ def list_presets(
@app.command() @app.command()
def generate( def generate(
image_path: Optional[Path] = typer.Option(None, help="Path to source image. Required for image mode."), image_path: Optional[Path] = typer.Option(
scheme: Optional[str] = typer.Option( None, help="Path to source image. Required for image mode."
None, help="Color scheme algorithm to use for image mode. Ignored in preset mode." ),
scheme: Optional[str] = typer.Option(
None,
help="Color scheme algorithm to use for image mode. Ignored in preset mode.",
autocompletion=_complete_scheme_name,
),
preset: Optional[str] = typer.Option(
None,
help="Name of a premade scheme in this format: <scheme>:<variant>",
autocompletion=_complete_preset,
),
mode: Optional[str] = typer.Option(
None,
help="Mode of the preset scheme (dark or light).",
autocompletion=_complete_mode,
),
accent: Optional[str] = typer.Option(
None,
help="Accent for schemes that support it (e.g. mauve).",
autocompletion=_complete_accent,
), ),
preset: Optional[str] = typer.Option(None, help="Name of a premade scheme in this format: <scheme>:<variant>"),
mode: Optional[str] = typer.Option(None, help="Mode of the preset scheme (dark or light)."),
accent: Optional[str] = typer.Option(None, help="Accent for schemes that support it (e.g. mauve)."),
): ):
if not any([image_path, scheme, preset, mode, accent]):
print(
"Hint: use --preset <scheme>:<variant> or --image-path <path>",
file=sys.stderr,
)
HOME = str(os.getenv("HOME")) HOME = str(os.getenv("HOME"))
OUTPUT = Path(HOME + "/.local/state/zshell/scheme.json") OUTPUT = Path(HOME + "/.local/state/zshell/scheme.json")
@@ -200,11 +272,15 @@ def generate(
def harmonize(from_hct: Hct, to_hct: Hct, tone_boost: float) -> Hct: def harmonize(from_hct: Hct, to_hct: Hct, tone_boost: float) -> Hct:
diff = difference_degrees(from_hct.hue, to_hct.hue) diff = difference_degrees(from_hct.hue, to_hct.hue)
rotation = min(diff * 0.8, 100) rotation = min(diff * 0.8, 100)
output_hue = sanitize_degrees_double(from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue)) output_hue = sanitize_degrees_double(
from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue)
)
tone = max(0.0, min(100.0, from_hct.tone * (1 + tone_boost))) tone = max(0.0, min(100.0, from_hct.tone * (1 + tone_boost)))
return Hct.from_hct(output_hue, from_hct.chroma, tone) return Hct.from_hct(output_hue, from_hct.chroma, tone)
def terminal_palette(colors: dict[str, str], mode: str, variant: str) -> dict[str, str]: def terminal_palette(
colors: dict[str, str], mode: str, variant: str
) -> dict[str, str]:
light = mode.lower() == "light" light = mode.lower() == "light"
key_hex = ( key_hex = (
@@ -236,7 +312,7 @@ def generate(
image = Image.open(image_path) image = Image.open(image_path)
image = image.convert("RGB") image = image.convert("RGB")
image.thumbnail(size, Image.NEAREST) image.thumbnail(size, Image.Resampling.NEAREST)
thumbnail_file.parent.mkdir(parents=True, exist_ok=True) thumbnail_file.parent.mkdir(parents=True, exist_ok=True)
image.save(thumbnail_path, "JPEG") image.save(thumbnail_path, "JPEG")
@@ -268,8 +344,15 @@ def generate(
is_dark = "" is_dark = ""
with Image.open(image_path) as img: with Image.open(image_path) as img:
img.thumbnail((1, 1), Image.LANCZOS) img.thumbnail((1, 1), Image.Resampling.LANCZOS)
hct = Hct.from_int(argb_from_rgb(*img.getpixel((0, 0)))) px = img.getpixel((0, 0))
if isinstance(px, (int, float)):
r = g = b = int(px)
elif px is not None:
r, g, b = int(px[0]), int(px[1]), int(px[2])
else:
r = g = b = 0
hct = Hct.from_int(argb_from_rgb(r, g, b))
is_dark = "light" if hct.tone > 50 else "dark" is_dark = "light" if hct.tone > 50 else "dark"
return is_dark return is_dark
@@ -431,6 +514,8 @@ def generate(
raw = tpl_path.read_text(encoding="utf-8") raw = tpl_path.read_text(encoding="utf-8")
out_path, body = split_directive_and_body(raw) out_path, body = split_directive_and_body(raw)
if out_path is None:
continue
out_path.parent.mkdir(parents=True, exist_ok=True) out_path.parent.mkdir(parents=True, exist_ok=True)
@@ -484,23 +569,30 @@ def generate(
with CONFIG.open() as f: with CONFIG.open() as f:
config = json.load(f) config = json.load(f)
scheme = scheme or config["colors"]["schemeType"] scheme_type = config["colors"].get("schemeType", "fruit-salad")
scheme = scheme or scheme_type
assert isinstance(scheme, str)
config_mode = config["general"]["color"]["mode"] config_mode = config["general"]["color"]["mode"]
smart = bool(config["general"]["color"].get("smart", False)) smart = bool(config["general"]["color"].get("smart", False))
scheme_class = get_scheme_class(scheme) scheme_class = get_scheme_class(scheme)
p_variant = "default"
if preset: if preset:
p_scheme, p_variant = resolve_preset(preset) p_scheme, p_variant = resolve_preset(preset)
schemes = list_schemes() schemes = list_schemes()
if accent and p_scheme in schemes: if accent and p_scheme in schemes:
meta = schemes[p_scheme] meta = schemes[p_scheme]
var_accents = next((v.accents for v in meta.variants if v.id == p_variant), ()) var_accents = next(
(v.accents for v in meta.variants if v.id == p_variant), ()
)
if accent not in var_accents: if accent not in var_accents:
available = ", ".join(var_accents) if var_accents else "none" available = ", ".join(var_accents) if var_accents else "none"
raise typer.BadParameter( raise typer.BadParameter(
f"Accent '{accent}' not available for '{p_scheme}:{p_variant}'. Available accents: {available}" f"Accent '{accent}' not available for '{p_scheme}:{p_variant}'. Available accents: {available}"
) )
palette_obj = get_palette(p_scheme, p_variant, mode or config_mode, accent=accent) palette_obj = get_palette(
p_scheme, p_variant, mode or config_mode, accent=accent
)
colors = palette_obj.colors colors = palette_obj.colors
effective_mode = palette_obj.mode effective_mode = palette_obj.mode
name = palette_obj.scheme name = palette_obj.scheme
+2 -2
View File
@@ -34,9 +34,9 @@ def lockscreen(
return return
if size[0] < 3840 or size[1] < 2160: if size[0] < 3840 or size[1] < 2160:
img = img.resize((size[0] // 2, size[1] // 2), Image.NEAREST) img = img.resize((size[0] // 2, size[1] // 2), Image.Resampling.NEAREST)
else: else:
img = img.resize((size[0] // 4, size[1] // 4), Image.NEAREST) img = img.resize((size[0] // 4, size[1] // 4), Image.Resampling.NEAREST)
img = img.filter(ImageFilter.GaussianBlur(blur_amount)) img = img.filter(ImageFilter.GaussianBlur(blur_amount))
+13
View File
@@ -6,14 +6,20 @@
//@ pragma Env QT_SCALE_FACTOR_ROUNDING_POLICY=Round //@ pragma Env QT_SCALE_FACTOR_ROUNDING_POLICY=Round
//@ pragma DropExpensiveFonts //@ pragma DropExpensiveFonts
import Quickshell import Quickshell
import Quickshell.Services.UPower
import qs.Modules import qs.Modules
import qs.Modules.Wallpaper import qs.Modules.Wallpaper
import qs.Modules.Lock import qs.Modules.Lock
import qs.Drawers import qs.Drawers
import qs.Helpers import qs.Helpers
import qs.Modules.Polkit import qs.Modules.Polkit
import qs.Daemons
ShellRoot { ShellRoot {
id: root
readonly property bool laptop: UPower.displayDevice.isLaptopBattery
settings.watchFiles: true settings.watchFiles: true
Windows { Windows {
@@ -38,4 +44,11 @@ ShellRoot {
Polkit { Polkit {
} }
LazyLoader {
activeAsync: root.laptop
component: Battery {
}
}
} }
+3 -807
View File
@@ -8,47 +8,12 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 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]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.102" version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" 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]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.9" version = "0.3.9"
@@ -61,103 +26,18 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 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]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 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]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.11.1" version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" 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]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.25.0" version = "1.25.0"
@@ -170,30 +50,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" 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]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.5.0" version = "1.5.0"
@@ -203,84 +65,6 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "fdeflate" name = "fdeflate"
version = "0.3.7" version = "0.3.7"
@@ -290,12 +74,6 @@ dependencies = [
"simd-adler32", "simd-adler32",
] ]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.9" version = "1.1.9"
@@ -306,39 +84,6 @@ dependencies = [
"miniz_oxide", "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]] [[package]]
name = "image" name = "image"
version = "0.25.10" version = "0.25.10"
@@ -347,56 +92,9 @@ checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"byteorder-lite", "byteorder-lite",
"color_quant",
"exr",
"gif",
"image-webp",
"moxcms", "moxcms",
"num-traits", "num-traits",
"png 0.18.1", "png",
"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",
] ]
[[package]] [[package]]
@@ -405,63 +103,12 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" 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]] [[package]]
name = "log" name = "log"
version = "0.4.29" version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 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]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.0" version = "2.8.0"
@@ -488,77 +135,6 @@ dependencies = [
"pxfm", "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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -568,59 +144,19 @@ dependencies = [
"autocfg", "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]] [[package]]
name = "png" name = "png"
version = "0.18.1" version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [ dependencies = [
"bitflags 2.11.1", "bitflags",
"crc32fast", "crc32fast",
"fdeflate", "fdeflate",
"flate2", "flate2",
"miniz_oxide", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.106" version = "1.0.106"
@@ -630,46 +166,12 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "pxfm" name = "pxfm"
version = "0.1.29" version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" 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]] [[package]]
name = "quote" name = "quote"
version = "1.0.45" version = "1.0.45"
@@ -679,123 +181,6 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.228"
@@ -839,39 +224,12 @@ dependencies = [
"zmij", "zmij",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "simd-adler32" name = "simd-adler32"
version = "0.3.9" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" 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]] [[package]]
name = "strict-num" name = "strict-num"
version = "0.1.1" version = "0.1.1"
@@ -889,40 +247,6 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "tiny-skia" name = "tiny-skia"
version = "0.11.4" version = "0.11.4"
@@ -934,7 +258,6 @@ dependencies = [
"bytemuck", "bytemuck",
"cfg-if", "cfg-if",
"log", "log",
"png 0.17.16",
"tiny-skia-path", "tiny-skia-path",
] ]
@@ -955,109 +278,6 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 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]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.21" version = "1.0.21"
@@ -1066,7 +286,7 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]] [[package]]
name = "zshell-img-tools" name = "zshell-img-tools"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"image", "image",
@@ -1074,27 +294,3 @@ dependencies = [
"serde_json", "serde_json",
"tiny-skia", "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",
]
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "zshell-img-tools" name = "zshell-img-tools"
version = "0.1.0" version = "0.2.0"
edition = "2024" edition = "2024"
[[bin]] [[bin]]
@@ -8,10 +8,10 @@ name = "zshell-img-tools"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
image = { version = "0.25", features = ["png"] } image = { version = "0.25", default-features = false, features = ["png"] }
tiny-skia = "0.11" tiny-skia = { version = "0.11", default-features = false, features = ["std", "simd"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
anyhow = "1" anyhow = "1.0"
serde_json = "1.0.149" serde_json = "1.0.149"
[profile.release] [profile.release]
-6
View File
@@ -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
+3 -2
View File
@@ -11,13 +11,14 @@ pub struct Config {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EffectsConfig { pub struct EffectsConfig {
pub mode: String, pub mode: String,
pub rounded_corners: bool,
pub corner_radius: f32, pub corner_radius: f32,
pub drop_shadow: bool, pub drop_shadow: bool,
pub rounded_corners: bool,
pub shadow_blur_radius: f32, pub shadow_blur_radius: f32,
pub shadow_blur_passes: u32,
pub shadow_color: [u8; 4],
pub shadow_offset_x: f32, pub shadow_offset_x: f32,
pub shadow_offset_y: f32, pub shadow_offset_y: f32,
pub shadow_color: [u8; 4],
} }
impl Config { impl Config {
+37 -24
View File
@@ -16,6 +16,7 @@ pub fn apply_effects(img: RgbaImage, cfg: &EffectsConfig) -> RgbaImage {
cfg.shadow_blur_radius, cfg.shadow_blur_radius,
cfg.shadow_offset_x, cfg.shadow_offset_x,
cfg.shadow_offset_y, cfg.shadow_offset_y,
cfg.shadow_blur_passes,
cfg.shadow_color, cfg.shadow_color,
) )
} else { } else {
@@ -52,11 +53,16 @@ pub fn apply_drop_shadow(
blur_radius: f32, blur_radius: f32,
offset_x: f32, offset_x: f32,
offset_y: f32, offset_y: f32,
blur_passes: u32,
shadow_color: [u8; 4], shadow_color: [u8; 4],
) -> RgbaImage { ) -> RgbaImage {
let (iw, ih) = img.dimensions(); let (iw, ih) = img.dimensions();
let br = blur_radius.ceil() as u32; 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_left = spread + (-offset_x).max(0.0).ceil() as u32;
let extra_top = spread + (-offset_y).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); tint_pixmap_as_shadow(&mut shadow_pixmap, shadow_color);
// Shadow
let shadow_img = pixmap_to_rgba_image(shadow_pixmap); 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 blurred_pixmap = rgba_image_to_pixmap(&blurred);
let mut canvas = Pixmap::new(canvas_w, canvas_h).expect("canvas pixmap"); 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") pb.finish().expect("rounded rect path")
} }
// Shadow pos
fn rgba_image_to_pixmap(img: &RgbaImage) -> Pixmap { fn rgba_image_to_pixmap(img: &RgbaImage) -> Pixmap {
let (w, h) = img.dimensions(); let (w, h) = img.dimensions();
let mut pixmap = Pixmap::new(w, h).expect("pixmap alloc"); let mut pixmap = Pixmap::new(w, h).expect("pixmap alloc");
@@ -154,6 +164,7 @@ fn rgba_image_to_pixmap(img: &RgbaImage) -> Pixmap {
pixmap pixmap
} }
// Shadow
fn pixmap_to_rgba_image(pixmap: Pixmap) -> RgbaImage { fn pixmap_to_rgba_image(pixmap: Pixmap) -> RgbaImage {
let (w, h) = (pixmap.width(), pixmap.height()); let (w, h) = (pixmap.width(), pixmap.height());
let mut out = RgbaImage::new(w, h); let mut out = RgbaImage::new(w, h);
@@ -176,31 +187,16 @@ fn pixmap_to_rgba_image(pixmap: Pixmap) -> RgbaImage {
out out
} }
fn tint_pixmap_as_shadow(pixmap: &mut Pixmap, color: [u8; 4]) { // Shadow blur
let [sr, sg, sb, _] = color; fn box_blur_rgba(img: &RgbaImage, radius: u32, bp: u32) -> RgbaImage {
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 {
if radius == 0 { if radius == 0 {
return img.clone(); return img.clone();
} }
let mut buf = sliding_horizontal(img, radius); let mut buf = img.clone();
buf = sliding_vertical(&buf, radius); for _ in 0..bp {
buf = sliding_horizontal(&buf, radius); buf = sliding_horizontal(&buf, radius);
buf = sliding_vertical(&buf, radius); buf = sliding_vertical(&buf, radius);
}
buf buf
} }
@@ -250,6 +246,23 @@ fn sliding_horizontal(img: &RgbaImage, radius: u32) -> RgbaImage {
out 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 { fn sliding_vertical(img: &RgbaImage, radius: u32) -> RgbaImage {
let (w, h) = img.dimensions(); let (w, h) = img.dimensions();
let r = radius as i32; let r = radius as i32;
+58 -41
View File
@@ -1,21 +1,20 @@
mod config; mod config;
mod effects; mod effects;
use anyhow::{Context, Result, bail}; use anyhow::{bail, Context, Result};
use std::io::Write as _; use std::io::Write as _;
use std::process::{Command, Stdio}; 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)] #[derive(Default)]
struct CliOverrides { struct CliOverrides {
rounded_corners: Option<bool>, rounded_corners: Option<bool>,
corner_radius: Option<f32>, corner_radius: Option<f32>,
drop_shadow: Option<bool>, drop_shadow: Option<bool>,
shadow_blur_radius: Option<f32>, shadow_blur_radius: Option<f32>,
shadow_blur_passes: Option<u32>,
shadow_offset_x: Option<f32>, shadow_offset_x: Option<f32>,
shadow_offset_y: 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]>, 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]> { fn parse_shadow_color(s: &str) -> Result<[u8; 4]> {
let parts: Vec<&str> = s.split(',').collect(); let parts: Vec<&str> = s.split(',').collect();
if parts.len() != 4 { 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] let r = parts[0]
.trim() .trim()
.parse::<u8>() .parse::<u8>()
.context("shadow_color red channel")?; .context("shadow-color red channel")?;
let g = parts[1] let g = parts[1]
.trim() .trim()
.parse::<u8>() .parse::<u8>()
.context("shadow_color green channel")?; .context("shadow-color green channel")?;
let b = parts[2] let b = parts[2]
.trim() .trim()
.parse::<u8>() .parse::<u8>()
.context("shadow_color blue channel")?; .context("shadow-color blue channel")?;
let a = parts[3] let a = parts[3]
.trim() .trim()
.parse::<u8>() .parse::<u8>()
.context("shadow_color alpha channel")?; .context("shadow-color alpha channel")?;
Ok([r, g, b, a]) Ok([r, g, b, a])
} }
@@ -56,6 +55,7 @@ fn main() -> Result<()> {
let mut image_path: Option<String> = None; let mut image_path: Option<String> = None;
let mut overrides = CliOverrides::default(); let mut overrides = CliOverrides::default();
let mut scale: Option<f32> = None;
let mut i = 0; let mut i = 0;
while i < args.len() { while i < args.len() {
@@ -68,67 +68,82 @@ fn main() -> Result<()> {
.context("Expected a path after --image")?, .context("Expected a path after --image")?,
); );
} }
"--rounded_corners" => { "--rounded-corners" => {
i += 1; i += 1;
let val = args let val = args
.get(i) .get(i)
.context("Expected true/false after --rounded_corners")?; .context("Expected true/false after --rounded-corners")?;
overrides.rounded_corners = Some(parse_bool(val)?); overrides.rounded_corners = Some(parse_bool(val)?);
} }
"--corner_radius" => { "--corner-radius" => {
i += 1; i += 1;
let val = args let val = args
.get(i) .get(i)
.context("Expected a number after --corner_radius")?; .context("Expected a number after --corner-radius")?;
overrides.corner_radius = Some( overrides.corner_radius = Some(
val.parse::<f32>() val.parse::<f32>()
.context("--corner_radius must be a number")?, .context("--corner-radius must be a number")?,
); );
} }
"--drop_shadow" => { "--drop-shadow" => {
i += 1; i += 1;
let val = args let val = args
.get(i) .get(i)
.context("Expected true/false after --drop_shadow")?; .context("Expected true/false after --drop-shadow")?;
overrides.drop_shadow = Some(parse_bool(val)?); overrides.drop_shadow = Some(parse_bool(val)?);
} }
"--shadow_blur_radius" => { "--shadow-blur-radius" => {
i += 1; i += 1;
let val = args let val = args
.get(i) .get(i)
.context("Expected a number after --shadow_blur_radius")?; .context("Expected a number after --shadow-blur-radius")?;
overrides.shadow_blur_radius = Some( overrides.shadow_blur_radius = Some(
val.parse::<f32>() 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; i += 1;
let val = args let val = args
.get(i) .get(i)
.context("Expected a number after --shadow_offset_x")?; .context("Expected a number after --shadow-offset-x")?;
overrides.shadow_offset_x = Some( overrides.shadow_offset_x = Some(
val.parse::<f32>() 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; i += 1;
let val = args let val = args
.get(i) .get(i)
.context("Expected a number after --shadow_offset_y")?; .context("Expected a number after --shadow-offset-y")?;
overrides.shadow_offset_y = Some( overrides.shadow_offset_y = Some(
val.parse::<f32>() 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; i += 1;
let val = args let val = args
.get(i) .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)?); 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}"), unknown => bail!("Unknown argument: {unknown}"),
} }
i += 1; i += 1;
@@ -158,11 +173,22 @@ fn main() -> Result<()> {
if let Some(v) = overrides.shadow_offset_y { if let Some(v) = overrides.shadow_offset_y {
effects.shadow_offset_y = v; 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 { if let Some(v) = overrides.shadow_color {
effects.shadow_color = v; 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) { if let Err(e) = process_image(&image_path, &effects) {
eprintln!("Error processing '{}': {e:#}", image_path); eprintln!("Error processing '{}': {e:#}", image_path);
} }
@@ -191,20 +217,11 @@ fn process_image(path: &str, effects: &config::EffectsConfig) -> Result<()> {
.spawn() .spawn()
.context("Failed to spawn swappy. Is it installed and in PATH?")?; .context("Failed to spawn swappy. Is it installed and in PATH?")?;
child // Writes the PNG bytes to swappy's stdin and then closes
.stdin if let Some(mut stdin) = child.stdin.take() {
.take() stdin
.context("Failed to get swappy stdin")? .write_all(&png_bytes)
.write_all(&png_bytes) .context("Failed to write image data to swappy")?;
.context("Failed to write image data to swappy")?;
let status = child.wait().context("Failed to wait for swappy")?;
if !status.success() {
eprintln!(
"swappy exited with non-zero status for '{}': {}",
path, status
);
} }
Ok(()) Ok(())