Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad23da4eda | |||
| 06ebc4ffbf | |||
| f2f9fa1302 | |||
| b4020438f9 | |||
| 184ab20d11 | |||
| 5097e30a77 | |||
| ef71ae8afd | |||
| 6533533936 | |||
| 9688072e93 | |||
|
9c36f0de5b
|
|||
| 9ca46967d9 | |||
| 16e84ca998 | |||
|
c30128cf95
|
|||
| ba9926af18 | |||
| 78fcf33b3a | |||
| 5e9b373405 | |||
| b49165e7ea | |||
| d0cda51639 | |||
| ad57764636 | |||
| 96afbdb30b | |||
| fd620e7487 | |||
| 0ec426e0f0 | |||
| ec5e6d3995 | |||
| 41a129bb90 |
+8
-1
@@ -31,6 +31,13 @@ if("shell" IN_LIST ENABLE_MODULES)
|
|||||||
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
|
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
|
||||||
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
|
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
|
||||||
endforeach()
|
endforeach()
|
||||||
install(FILES shell.qml DESTINATION "${INSTALL_QSCONFDIR}")
|
|
||||||
|
# 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(DIRECTORY Greeter/ DESTINATION "${INSTALL_GREETERCONFDIR}")
|
install(DIRECTORY Greeter/ DESTINATION "${INSTALL_GREETERCONFDIR}")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -8,20 +8,34 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias active: splitButton.active
|
property alias active: splitButton.active
|
||||||
|
property alias buttonAlias: splitButton
|
||||||
property bool enabled: true
|
property bool enabled: true
|
||||||
property alias expanded: splitButton.expanded
|
property alias expanded: splitButton.expanded
|
||||||
property int expandedZ: 100
|
property int expandedZ: 100
|
||||||
required property string label
|
required property string label
|
||||||
property alias menuItems: splitButton.menuItems
|
property alias menuItems: splitButton.menuItems
|
||||||
|
property bool shouldBeActive: true
|
||||||
property alias type: splitButton.type
|
property alias type: splitButton.type
|
||||||
|
|
||||||
signal selected(item: MenuItem)
|
signal selected(item: MenuItem)
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
|
anchors.right: parent.right
|
||||||
clip: false
|
clip: false
|
||||||
|
implicitHeight: row.implicitHeight + Appearance.padding.smaller * 2
|
||||||
|
opacity: shouldBeActive ? 1 : 0
|
||||||
|
scale: shouldBeActive ? 1 : 0.8
|
||||||
z: root.expanded ? expandedZ : -1
|
z: root.expanded ? expandedZ : -1
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on scale {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: row
|
id: row
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Text {
|
|||||||
color: DynamicColors.palette.m3onSurface
|
color: DynamicColors.palette.m3onSurface
|
||||||
font.family: Appearance.font.family.sans
|
font.family: Appearance.font.family.sans
|
||||||
font.pointSize: Appearance.font.size.normal
|
font.pointSize: Appearance.font.size.normal
|
||||||
|
linkColor: DynamicColors.palette.m3onPrimaryFixedVariant
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
|
||||||
JsonObject {
|
JsonObject {
|
||||||
|
property Presets presets: Presets {
|
||||||
|
}
|
||||||
property string schemeType: "vibrant"
|
property string schemeType: "vibrant"
|
||||||
|
|
||||||
|
component Presets: JsonObject {
|
||||||
|
property string accent: ""
|
||||||
|
property string name: ""
|
||||||
|
property string variant: ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-2
@@ -22,6 +22,7 @@ Singleton {
|
|||||||
property alias notifs: adapter.notifs
|
property alias notifs: adapter.notifs
|
||||||
property alias osd: adapter.osd
|
property alias osd: adapter.osd
|
||||||
property alias overview: adapter.overview
|
property alias overview: adapter.overview
|
||||||
|
property alias plugins: adapter.plugins
|
||||||
property bool recentlySaved: false
|
property bool recentlySaved: false
|
||||||
property alias screenshot: adapter.screenshot
|
property alias screenshot: adapter.screenshot
|
||||||
property alias services: adapter.services
|
property alias services: adapter.services
|
||||||
@@ -115,7 +116,12 @@ Singleton {
|
|||||||
|
|
||||||
function serializeColors(): var {
|
function serializeColors(): var {
|
||||||
return {
|
return {
|
||||||
schemeType: colors.schemeType
|
schemeType: colors.schemeType,
|
||||||
|
presets: {
|
||||||
|
name: colors.presets.name,
|
||||||
|
variant: colors.presets.variant,
|
||||||
|
accent: colors.presets.accent
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +141,8 @@ Singleton {
|
|||||||
launcher: serializeLauncher(),
|
launcher: serializeLauncher(),
|
||||||
colors: serializeColors(),
|
colors: serializeColors(),
|
||||||
dock: serializeDock(),
|
dock: serializeDock(),
|
||||||
screenshot: serializeScreenshot()
|
screenshot: serializeScreenshot(),
|
||||||
|
plugins: serializePlugins()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,6 +291,13 @@ Singleton {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function serializePlugins(): var {
|
||||||
|
return {
|
||||||
|
enabled: plugins.enabled,
|
||||||
|
entries: plugins.entries
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function serializeScreenshot(): var {
|
function serializeScreenshot(): var {
|
||||||
return {
|
return {
|
||||||
enable_pp: screenshot.enable_pp,
|
enable_pp: screenshot.enable_pp,
|
||||||
@@ -453,6 +467,8 @@ Singleton {
|
|||||||
}
|
}
|
||||||
property Overview overview: Overview {
|
property Overview overview: Overview {
|
||||||
}
|
}
|
||||||
|
property PluginConfig plugins: PluginConfig {
|
||||||
|
}
|
||||||
property Screenshot screenshot: Screenshot {
|
property Screenshot screenshot: Screenshot {
|
||||||
}
|
}
|
||||||
property Services services: Services {
|
property Services services: Services {
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ Singleton {
|
|||||||
readonly property alias wallLuminance: analyser.luminance
|
readonly property alias wallLuminance: analyser.luminance
|
||||||
|
|
||||||
function alterColor(c: color, a: real, layer: int): color {
|
function alterColor(c: color, a: real, layer: int): color {
|
||||||
const luminance = getLuminance(c);
|
const initLuminance = getLuminance(c);
|
||||||
|
const luminance = Math.max(initLuminance, 0.001);
|
||||||
|
|
||||||
const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (1 - transparency.base) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5));
|
const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (0.2 + 0.3 * (1 - transparency.base)) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5));
|
||||||
const scale = (luminance + offset) / luminance;
|
const scale = (luminance + offset) / luminance;
|
||||||
const r = Math.max(0, Math.min(1, c.r * scale));
|
const r = Math.max(0, Math.min(1, c.r * scale));
|
||||||
const g = Math.max(0, Math.min(1, c.g * scale));
|
const g = Math.max(0, Math.min(1, c.g * scale));
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
JsonObject {
|
||||||
|
property bool enabled: false
|
||||||
|
property list<var> entries: [
|
||||||
|
{
|
||||||
|
id: "Plugin",
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// FetchPresets.qml
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var parsedPresets: ({})
|
||||||
|
readonly property var presets: parsedPresets
|
||||||
|
property bool ready: false
|
||||||
|
|
||||||
|
function accents(presetName, variantName) {
|
||||||
|
const variant = parsedPresets[presetName]?.variants?.[variantName];
|
||||||
|
|
||||||
|
return variant?.accents ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultAccent(presetName, variantName) {
|
||||||
|
const variant = parsedPresets[presetName]?.variants?.[variantName];
|
||||||
|
|
||||||
|
return variant?.default_accent ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function modes(presetName, variantName) {
|
||||||
|
const variant = parsedPresets[presetName]?.variants?.[variantName];
|
||||||
|
|
||||||
|
return variant?.modes ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function presetNames() {
|
||||||
|
return Object.keys(parsedPresets);
|
||||||
|
}
|
||||||
|
|
||||||
|
function variantNames(presetName) {
|
||||||
|
const preset = parsedPresets[presetName];
|
||||||
|
|
||||||
|
if (!preset || !preset.variants)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
return Object.keys(preset.variants);
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
command: ["zshell-cli", "scheme", "list-presets", "--json"]
|
||||||
|
running: true
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(text);
|
||||||
|
|
||||||
|
root.parsedPresets = parsed.presets ?? {};
|
||||||
|
root.ready = true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse presets JSON:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ Singleton {
|
|||||||
|
|
||||||
readonly property int darkEnd: Config.general.color.scheduleDarkEnd
|
readonly property int darkEnd: Config.general.color.scheduleDarkEnd
|
||||||
readonly property int darkStart: Config.general.color.scheduleDarkStart
|
readonly property int darkStart: Config.general.color.scheduleDarkStart
|
||||||
readonly property bool enabled: Config.general.color.scheduleDark
|
readonly property bool enabled: Config.general.color.scheduleDark && Config.general.color.schemeGeneration
|
||||||
|
|
||||||
function applyDarkMode() {
|
function applyDarkMode() {
|
||||||
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "dark"]);
|
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "dark"]);
|
||||||
|
|||||||
+82
-41
@@ -1,41 +1,82 @@
|
|||||||
// pragma Singleton
|
pragma Singleton
|
||||||
//
|
|
||||||
// import Quickshell
|
import Quickshell
|
||||||
// import QtQuick
|
import Quickshell.Io
|
||||||
//
|
import QtQuick
|
||||||
// Singleton {
|
|
||||||
// id: root
|
Singleton {
|
||||||
//
|
id: root
|
||||||
// function start(extraArgs = []): void {
|
|
||||||
// needsStart = true;
|
readonly property alias elapsed: props.elapsed
|
||||||
// startArgs = extraArgs;
|
property bool needsPause
|
||||||
// checkProc.running = true;
|
property bool needsStart
|
||||||
// }
|
property bool needsStop
|
||||||
//
|
readonly property alias paused: props.paused
|
||||||
// PersistentProperties {
|
readonly property alias running: props.running
|
||||||
// id: props
|
property list<string> startArgs
|
||||||
//
|
|
||||||
// property real elapsed: 0
|
function start(extraArgs = []): void {
|
||||||
// property bool paused: false
|
needsStart = true;
|
||||||
// property bool running: false
|
startArgs = extraArgs;
|
||||||
//
|
checkProc.running = true;
|
||||||
// reloadableId: "recorder"
|
}
|
||||||
// }
|
|
||||||
//
|
function stop(): void {
|
||||||
// Process {
|
needsStop = true;
|
||||||
// id: checkProc
|
checkProc.running = true;
|
||||||
//
|
}
|
||||||
// command: ["pidof", "gpu-screen-recorder"]
|
|
||||||
// running: true
|
function togglePause(): void {
|
||||||
//
|
needsPause = true;
|
||||||
// onExited: code => {
|
checkProc.running = true;
|
||||||
// props.running = code === 0;
|
}
|
||||||
//
|
|
||||||
// if (code === 0) {
|
PersistentProperties {
|
||||||
// if (root.needsStop) {
|
id: props
|
||||||
// Quickshell.execDetached(["zshell-cli"]);
|
|
||||||
// }
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Quickshell
|
|
||||||
import "../scripts/fzf.js" as Fzf
|
import "../scripts/fzf.js" as Fzf
|
||||||
import "../scripts/fuzzysort.js" as Fuzzy
|
import "../scripts/fuzzysort.js" as Fuzzy
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
property var extraOpts: ({})
|
property var extraOpts: ({})
|
||||||
|
|||||||
@@ -136,7 +136,10 @@ CustomRect {
|
|||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
onLinkActivated: link => {
|
onLinkActivated: link => {
|
||||||
|
if (Config.launcher.uwsm)
|
||||||
Quickshell.execDetached(["app2unit", "-O", "--", link]);
|
Quickshell.execDetached(["app2unit", "-O", "--", link]);
|
||||||
|
else
|
||||||
|
Quickshell.execDetached(["xdg-open", link]);
|
||||||
root.visibilities.sidebar = false;
|
root.visibilities.sidebar = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
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,13 +1,14 @@
|
|||||||
import qs.Modules.Notifications.Sidebar.Utils.Cards
|
import Quickshell
|
||||||
import qs.Config
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import qs.Modules.Notifications.Sidebar.Utils.Cards
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Item popouts
|
required property Item popouts
|
||||||
required property var props
|
required property PersistentProperties props
|
||||||
required property var visibilities
|
required property var visibilities
|
||||||
|
|
||||||
implicitHeight: layout.implicitHeight
|
implicitHeight: layout.implicitHeight
|
||||||
@@ -22,6 +23,12 @@ Item {
|
|||||||
IdleInhibit {
|
IdleInhibit {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Record {
|
||||||
|
props: root.props
|
||||||
|
visibilities: root.visibilities
|
||||||
|
z: 1
|
||||||
|
}
|
||||||
|
|
||||||
Toggles {
|
Toggles {
|
||||||
popouts: root.popouts
|
popouts: root.popouts
|
||||||
visibilities: root.visibilities
|
visibilities: root.visibilities
|
||||||
|
|||||||
@@ -100,13 +100,15 @@ Item {
|
|||||||
icon: `brightness_${(Math.round(value * 6) + 1)}`
|
icon: `brightness_${(Math.round(value * 6) + 1)}`
|
||||||
value: root.brightness
|
value: root.brightness
|
||||||
|
|
||||||
onMoved: {
|
onPressedChanged: {
|
||||||
|
if (!pressed) {
|
||||||
if (Config.osd.allMonBrightness) {
|
if (Config.osd.allMonBrightness) {
|
||||||
root.monitor?.setBrightness(value);
|
|
||||||
} else {
|
|
||||||
for (const mon of Brightness.monitors) {
|
for (const mon of Brightness.monitors) {
|
||||||
mon.setBrightness(value);
|
mon.setBrightness(value);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
root.monitor?.setBrightness(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,12 @@ Item {
|
|||||||
key: "updates"
|
key: "updates"
|
||||||
name: "Updates"
|
name: "Updates"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ListElement {
|
||||||
|
icon: "extension"
|
||||||
|
key: "plugins"
|
||||||
|
name: "Extensions"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomClippingRect {
|
CustomClippingRect {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import QtQuick
|
||||||
import qs.Modules.Settings.Controls
|
import qs.Modules.Settings.Controls
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ SettingsPage {
|
|||||||
name: "Sans family"
|
name: "Sans family"
|
||||||
object: Config.appearance.font.family
|
object: Config.appearance.font.family
|
||||||
setting: "sans"
|
setting: "sans"
|
||||||
|
stringList: Qt.fontFamilies()
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
@@ -89,6 +91,7 @@ SettingsPage {
|
|||||||
name: "Monospace family"
|
name: "Monospace family"
|
||||||
object: Config.appearance.font.family
|
object: Config.appearance.font.family
|
||||||
setting: "mono"
|
setting: "mono"
|
||||||
|
stringList: Qt.fontFamilies()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ SettingsPage {
|
|||||||
sectionId: "Wallpapers"
|
sectionId: "Wallpapers"
|
||||||
|
|
||||||
WallpaperGrid {
|
WallpaperGrid {
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ SettingsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingSpinBox {
|
SettingSpinBox {
|
||||||
name: "Media update interval"
|
|
||||||
min: 0
|
min: 0
|
||||||
|
name: "Media update interval"
|
||||||
object: Config.dashboard
|
object: Config.dashboard
|
||||||
setting: "mediaUpdateInterval"
|
setting: "mediaUpdateInterval"
|
||||||
step: 50
|
step: 50
|
||||||
@@ -30,8 +30,8 @@ SettingsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingSpinBox {
|
SettingSpinBox {
|
||||||
name: "Resource update interval"
|
|
||||||
min: 0
|
min: 0
|
||||||
|
name: "Resource update interval"
|
||||||
object: Config.dashboard
|
object: Config.dashboard
|
||||||
setting: "resourceUpdateInterval"
|
setting: "resourceUpdateInterval"
|
||||||
step: 50
|
step: 50
|
||||||
@@ -41,8 +41,8 @@ SettingsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingSpinBox {
|
SettingSpinBox {
|
||||||
name: "Drag threshold"
|
|
||||||
min: 0
|
min: 0
|
||||||
|
name: "Drag threshold"
|
||||||
object: Config.dashboard
|
object: Config.dashboard
|
||||||
setting: "dragThreshold"
|
setting: "dragThreshold"
|
||||||
}
|
}
|
||||||
@@ -107,112 +107,112 @@ SettingsPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsSection {
|
// SettingsSection {
|
||||||
sectionId: "Layout Sizes"
|
// sectionId: "Layout Sizes"
|
||||||
|
//
|
||||||
SettingsHeader {
|
// SettingsHeader {
|
||||||
name: "Layout Sizes"
|
// name: "Layout Sizes"
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Tab indicator height"
|
// name: "Tab indicator height"
|
||||||
value: String(Config.dashboard.sizes.tabIndicatorHeight)
|
// value: String(Config.dashboard.sizes.tabIndicatorHeight)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Tab indicator spacing"
|
// name: "Tab indicator spacing"
|
||||||
value: String(Config.dashboard.sizes.tabIndicatorSpacing)
|
// value: String(Config.dashboard.sizes.tabIndicatorSpacing)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Info width"
|
// name: "Info width"
|
||||||
value: String(Config.dashboard.sizes.infoWidth)
|
// value: String(Config.dashboard.sizes.infoWidth)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Info icon size"
|
// name: "Info icon size"
|
||||||
value: String(Config.dashboard.sizes.infoIconSize)
|
// value: String(Config.dashboard.sizes.infoIconSize)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Date time width"
|
// name: "Date time width"
|
||||||
value: String(Config.dashboard.sizes.dateTimeWidth)
|
// value: String(Config.dashboard.sizes.dateTimeWidth)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Media width"
|
// name: "Media width"
|
||||||
value: String(Config.dashboard.sizes.mediaWidth)
|
// value: String(Config.dashboard.sizes.mediaWidth)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Media progress sweep"
|
// name: "Media progress sweep"
|
||||||
value: String(Config.dashboard.sizes.mediaProgressSweep)
|
// value: String(Config.dashboard.sizes.mediaProgressSweep)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Media progress thickness"
|
// name: "Media progress thickness"
|
||||||
value: String(Config.dashboard.sizes.mediaProgressThickness)
|
// value: String(Config.dashboard.sizes.mediaProgressThickness)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Resource progress thickness"
|
// name: "Resource progress thickness"
|
||||||
value: String(Config.dashboard.sizes.resourceProgessThickness)
|
// value: String(Config.dashboard.sizes.resourceProgessThickness)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Weather width"
|
// name: "Weather width"
|
||||||
value: String(Config.dashboard.sizes.weatherWidth)
|
// value: String(Config.dashboard.sizes.weatherWidth)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Media cover art size"
|
// name: "Media cover art size"
|
||||||
value: String(Config.dashboard.sizes.mediaCoverArtSize)
|
// value: String(Config.dashboard.sizes.mediaCoverArtSize)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Media visualiser size"
|
// name: "Media visualiser size"
|
||||||
value: String(Config.dashboard.sizes.mediaVisualiserSize)
|
// value: String(Config.dashboard.sizes.mediaVisualiserSize)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Separator {
|
// Separator {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
SettingReadOnly {
|
// SettingReadOnly {
|
||||||
name: "Resource size"
|
// name: "Resource size"
|
||||||
value: String(Config.dashboard.sizes.resourceSize)
|
// value: String(Config.dashboard.sizes.resourceSize)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
import qs.Modules.Settings.Controls
|
import qs.Modules.Settings.Controls
|
||||||
import qs.Config
|
import qs.Config
|
||||||
import qs.Components
|
import qs.Components
|
||||||
@@ -67,6 +69,7 @@ SettingsPage {
|
|||||||
|
|
||||||
CustomSplitButtonRow {
|
CustomSplitButtonRow {
|
||||||
active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1]
|
active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1]
|
||||||
|
buttonAlias.disabled: !Config.general.color.schemeGeneration
|
||||||
label: qsTr("Scheme mode")
|
label: qsTr("Scheme mode")
|
||||||
|
|
||||||
menuItems: [
|
menuItems: [
|
||||||
@@ -100,6 +103,7 @@ SettingsPage {
|
|||||||
id: schemeType
|
id: schemeType
|
||||||
|
|
||||||
active: root.schemeTypeItem(menuItems, Config.colors.schemeType)
|
active: root.schemeTypeItem(menuItems, Config.colors.schemeType)
|
||||||
|
buttonAlias.disabled: !Config.general.color.schemeGeneration
|
||||||
label: qsTr("Scheme type")
|
label: qsTr("Scheme type")
|
||||||
z: 2
|
z: 2
|
||||||
|
|
||||||
@@ -169,21 +173,69 @@ SettingsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
|
shouldBeActive: Config.general.color.schemeGeneration ? 0 : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
SchemesListView {
|
||||||
|
name: "Color scheme presets"
|
||||||
|
object: Config.colors.presets
|
||||||
|
setting: "name"
|
||||||
|
shouldBeActive: Config.general.color.schemeGeneration ? 0 : 1
|
||||||
|
stringList: FetchPresets.presetNames()
|
||||||
|
}
|
||||||
|
|
||||||
|
Separator {
|
||||||
|
shouldBeActive: Config.colors.presets.name !== "" && !Config.general.color.schemeGeneration
|
||||||
|
}
|
||||||
|
|
||||||
|
SchemesListView {
|
||||||
|
name: "Preset variant"
|
||||||
|
object: Config.colors.presets
|
||||||
|
setting: "variant"
|
||||||
|
shouldBeActive: Config.colors.presets.name !== "" && !Config.general.color.schemeGeneration
|
||||||
|
stringList: FetchPresets.variantNames(Config.colors.presets.name)
|
||||||
|
|
||||||
|
onOptionSet: item => {
|
||||||
|
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${Config.colors.presets.name.toLowerCase()}:${item}`]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Separator {
|
||||||
|
shouldBeActive: Config.colors.presets.variant !== "" && FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant).length > 0 && !Config.general.color.schemeGeneration
|
||||||
|
}
|
||||||
|
|
||||||
|
SchemesListView {
|
||||||
|
name: "Preset accent"
|
||||||
|
object: Config.colors.presets
|
||||||
|
setting: "accent"
|
||||||
|
shouldBeActive: Config.colors.presets.variant !== "" && FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant).length > 0 && !Config.general.color.schemeGeneration
|
||||||
|
stringList: FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant)
|
||||||
|
|
||||||
|
onOptionSet: item => {
|
||||||
|
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${Config.colors.presets.name.toLowerCase()}:${Config.colors.presets.variant}`, "--accent", `${item}`]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Separator {
|
||||||
|
shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingSwitch {
|
SettingSwitch {
|
||||||
name: "Smart color scheme"
|
name: "Smart color scheme"
|
||||||
object: Config.general.color
|
object: Config.general.color
|
||||||
setting: "smart"
|
setting: "smart"
|
||||||
|
shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
|
shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingSpinner {
|
SettingSpinner {
|
||||||
name: "Schedule dark mode"
|
name: "Schedule dark mode"
|
||||||
object: Config.general.color
|
object: Config.general.color
|
||||||
settings: ["scheduleDarkStart", "scheduleDarkEnd", "scheduleDark"]
|
settings: ["scheduleDarkStart", "scheduleDarkEnd", "scheduleDark"]
|
||||||
|
shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
|
|||||||
@@ -103,6 +103,18 @@ SettingsPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsSection {
|
||||||
|
sectionId: "Greeter"
|
||||||
|
|
||||||
|
SettingsHeader {
|
||||||
|
name: "Greeter"
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsIconButton {
|
||||||
|
name: "Install wallpaper and color scheme to greeter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsSection {
|
SettingsSection {
|
||||||
sectionId: "Idle"
|
sectionId: "Idle"
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import qs.Modules.Settings.Controls
|
|||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
function addTimeoutEntry() {
|
function addTimeoutEntry() {
|
||||||
let list = [...Config.general.idle.timeouts];
|
let list = [...Config.general.idle.timeouts];
|
||||||
|
|
||||||
@@ -40,8 +42,26 @@ ColumnLayout {
|
|||||||
Config.save();
|
Config.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
height: shouldBeActive ? implicitHeight : 0
|
||||||
|
opacity: shouldBeActive ? 1 : 0
|
||||||
|
scale: shouldBeActive ? 1 : 0.8
|
||||||
spacing: Appearance.spacing.smaller
|
spacing: Appearance.spacing.smaller
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on scale {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
name: "Idle Monitors"
|
name: "Idle Monitors"
|
||||||
@@ -52,6 +72,8 @@ ColumnLayout {
|
|||||||
|
|
||||||
SettingList {
|
SettingList {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
anchors.left: undefined
|
||||||
|
anchors.right: undefined
|
||||||
|
|
||||||
onAddActiveActionRequested: {
|
onAddActiveActionRequested: {
|
||||||
root.updateTimeoutEntry(index, "activeAction", "");
|
root.updateTimeoutEntry(index, "activeAction", "");
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,8 +19,8 @@ SettingsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingSpinBox {
|
SettingSpinBox {
|
||||||
name: "Max toasts"
|
|
||||||
min: 1
|
min: 1
|
||||||
|
name: "Max toasts"
|
||||||
object: Config.utilities
|
object: Config.utilities
|
||||||
setting: "maxToasts"
|
setting: "maxToasts"
|
||||||
}
|
}
|
||||||
@@ -29,8 +29,8 @@ SettingsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingSpinBox {
|
SettingSpinBox {
|
||||||
name: "Panel width"
|
|
||||||
min: 1
|
min: 1
|
||||||
|
name: "Panel width"
|
||||||
object: Config.utilities.sizes
|
object: Config.utilities.sizes
|
||||||
setting: "width"
|
setting: "width"
|
||||||
}
|
}
|
||||||
@@ -39,8 +39,8 @@ SettingsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingSpinBox {
|
SettingSpinBox {
|
||||||
name: "Toast width"
|
|
||||||
min: 1
|
min: 1
|
||||||
|
name: "Toast width"
|
||||||
object: Config.utilities.sizes
|
object: Config.utilities.sizes
|
||||||
setting: "toastWidth"
|
setting: "toastWidth"
|
||||||
}
|
}
|
||||||
@@ -77,100 +77,100 @@ SettingsPage {
|
|||||||
setting: "gameModeChanged"
|
setting: "gameModeChanged"
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
// 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"
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingSwitch {
|
// SettingsSection {
|
||||||
name: "Do not disturb changed"
|
// sectionId: "VPN"
|
||||||
object: Config.utilities.toasts
|
//
|
||||||
setting: "dndChanged"
|
// SettingsHeader {
|
||||||
}
|
// name: "VPN"
|
||||||
|
// }
|
||||||
Separator {
|
//
|
||||||
}
|
// SettingSwitch {
|
||||||
|
// name: "Enable VPN integration"
|
||||||
SettingSwitch {
|
// object: Config.utilities.vpn
|
||||||
name: "Audio output changed"
|
// setting: "enabled"
|
||||||
object: Config.utilities.toasts
|
// }
|
||||||
setting: "audioOutputChanged"
|
//
|
||||||
}
|
// Separator {
|
||||||
|
// }
|
||||||
Separator {
|
//
|
||||||
}
|
// SettingStringList {
|
||||||
|
// name: "Provider"
|
||||||
SettingSwitch {
|
// addLabel: qsTr("Add VPN provider")
|
||||||
name: "Audio input changed"
|
// object: Config.utilities.vpn
|
||||||
object: Config.utilities.toasts
|
// setting: "provider"
|
||||||
setting: "audioInputChanged"
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Separator {
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingSwitch {
|
|
||||||
name: "Caps lock changed"
|
|
||||||
object: Config.utilities.toasts
|
|
||||||
setting: "capsLockChanged"
|
|
||||||
}
|
|
||||||
|
|
||||||
Separator {
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingSwitch {
|
|
||||||
name: "Num lock changed"
|
|
||||||
object: Config.utilities.toasts
|
|
||||||
setting: "numLockChanged"
|
|
||||||
}
|
|
||||||
|
|
||||||
Separator {
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingSwitch {
|
|
||||||
name: "Keyboard layout changed"
|
|
||||||
object: Config.utilities.toasts
|
|
||||||
setting: "kbLayoutChanged"
|
|
||||||
}
|
|
||||||
|
|
||||||
Separator {
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingSwitch {
|
|
||||||
name: "VPN changed"
|
|
||||||
object: Config.utilities.toasts
|
|
||||||
setting: "vpnChanged"
|
|
||||||
}
|
|
||||||
|
|
||||||
Separator {
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingSwitch {
|
|
||||||
name: "Now playing"
|
|
||||||
object: Config.utilities.toasts
|
|
||||||
setting: "nowPlaying"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsSection {
|
|
||||||
sectionId: "VPN"
|
|
||||||
|
|
||||||
SettingsHeader {
|
|
||||||
name: "VPN"
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingSwitch {
|
|
||||||
name: "Enable VPN integration"
|
|
||||||
object: Config.utilities.vpn
|
|
||||||
setting: "enabled"
|
|
||||||
}
|
|
||||||
|
|
||||||
Separator {
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingStringList {
|
|
||||||
name: "Provider"
|
|
||||||
addLabel: qsTr("Add VPN provider")
|
|
||||||
object: Config.utilities.vpn
|
|
||||||
setting: "provider"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ Item {
|
|||||||
stack.push(screenshot);
|
stack.push(screenshot);
|
||||||
else if (currentCategory === "updates")
|
else if (currentCategory === "updates")
|
||||||
stack.push(updates);
|
stack.push(updates);
|
||||||
|
else if (currentCategory === "plugins")
|
||||||
|
stack.push(plugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
target: root
|
target: root
|
||||||
@@ -134,7 +136,7 @@ Item {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: searchBar.bottom
|
anchors.top: searchBar.bottom
|
||||||
anchors.topMargin: Appearance.spacing.smaller
|
anchors.topMargin: Appearance.spacing.smaller
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
color: DynamicColors.tPalette.m3surfaceContainerLowest
|
||||||
radius: Appearance.rounding.normal
|
radius: Appearance.rounding.normal
|
||||||
|
|
||||||
StackView {
|
StackView {
|
||||||
@@ -245,4 +247,11 @@ Item {
|
|||||||
Cat.SystemUpdates {
|
Cat.SystemUpdates {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: plugins
|
||||||
|
|
||||||
|
Cat.Plugins {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Config
|
||||||
|
import qs.Components
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string name
|
||||||
|
required property var object
|
||||||
|
property alias row: row
|
||||||
|
required property string setting
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
required property list<var> stringList
|
||||||
|
|
||||||
|
signal optionSet(option: string)
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
implicitHeight: shouldBeActive ? row.height : 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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: row
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.small
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: text
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.pointSize: Appearance.font.size.larger
|
||||||
|
text: root.name
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomClippingRect {
|
||||||
|
Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5
|
||||||
|
Layout.preferredWidth: 500
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
radius: (21 + Appearance.padding.normal) * Appearance.rounding.scale
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
id: searchBox
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.normal
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
implicitHeight: 42
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
id: searchIcon
|
||||||
|
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Appearance.padding.large
|
||||||
|
anchors.top: parent.top
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
text: "search"
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomTextField {
|
||||||
|
id: textSearch
|
||||||
|
|
||||||
|
anchors.left: searchIcon.right
|
||||||
|
anchors.leftMargin: Appearance.spacing.small
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Appearance.spacing.normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
placeholderText: "Search..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomClippingRect {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.normal
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: searchBox.bottom
|
||||||
|
bottomLeftRadius: 21
|
||||||
|
bottomRightRadius: 21
|
||||||
|
|
||||||
|
CustomListView {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
delegate: CustomRect {
|
||||||
|
id: delegate
|
||||||
|
|
||||||
|
required property string modelData
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
implicitHeight: 42
|
||||||
|
radius: Appearance.rounding.smallest
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Appearance.padding.normal
|
||||||
|
text: modelData
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: Appearance.padding.normal
|
||||||
|
color: DynamicColors.palette.m3primary
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
text: "check_circle"
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
visible: root.object[root.setting] === delegate.modelData
|
||||||
|
}
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
onClicked: {
|
||||||
|
root.object[root.setting] = delegate.modelData;
|
||||||
|
root.optionSet(delegate.modelData);
|
||||||
|
Config.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model: ScriptModel {
|
||||||
|
values: {
|
||||||
|
const values = root.stringList;
|
||||||
|
const search = textSearch.text;
|
||||||
|
var regex = new RegExp(search, "i");
|
||||||
|
|
||||||
|
return values.filter(n => regex.test(n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,26 @@ import qs.Config
|
|||||||
CustomRect {
|
CustomRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
Layout.fillWidth: true
|
property bool shouldBeActive: true
|
||||||
Layout.preferredHeight: 1
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
color: DynamicColors.tPalette.m3outlineVariant
|
color: DynamicColors.tPalette.m3outlineVariant
|
||||||
|
implicitHeight: shouldBeActive ? 1 : 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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ ColumnLayout {
|
|||||||
required property string name
|
required property string name
|
||||||
required property var object
|
required property var object
|
||||||
required property string setting
|
required property string setting
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
function addAction() {
|
function addAction() {
|
||||||
const list = [...root.object[root.setting]];
|
const list = [...root.object[root.setting]];
|
||||||
@@ -44,8 +45,26 @@ ColumnLayout {
|
|||||||
Config.save();
|
Config.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
height: shouldBeActive ? implicitHeight : 0
|
||||||
|
opacity: shouldBeActive ? 1 : 0
|
||||||
|
scale: shouldBeActive ? 1 : 0.8
|
||||||
spacing: Appearance.spacing.smaller
|
spacing: Appearance.spacing.smaller
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on scale {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -108,6 +127,9 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
anchors.left: undefined
|
||||||
|
anchors.right: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -188,6 +210,8 @@ ColumnLayout {
|
|||||||
StringListEditor {
|
StringListEditor {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
addLabel: qsTr("Add command argument")
|
addLabel: qsTr("Add command argument")
|
||||||
|
anchors.left: undefined
|
||||||
|
anchors.right: undefined
|
||||||
values: [...(modelData.command ?? [])]
|
values: [...(modelData.command ?? [])]
|
||||||
|
|
||||||
onListEdited: function (values) {
|
onListEdited: function (values) {
|
||||||
@@ -196,6 +220,9 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
anchors.left: undefined
|
||||||
|
anchors.right: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -214,6 +241,9 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
anchors.left: undefined
|
||||||
|
anchors.right: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import qs.Components
|
|||||||
import qs.Config
|
import qs.Config
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
|
|
||||||
ColumnLayout {
|
CustomRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
|
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
|
||||||
required property string name
|
required property string name
|
||||||
required property var object
|
required property var object
|
||||||
required property string setting
|
required property string setting
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
function addAlias() {
|
function addAlias() {
|
||||||
const list = [...root.object[root.setting]];
|
const list = [...root.object[root.setting]];
|
||||||
@@ -40,8 +41,25 @@ ColumnLayout {
|
|||||||
Config.save();
|
Config.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
spacing: Appearance.spacing.smaller
|
anchors.right: parent.right
|
||||||
|
height: shouldBeActive ? layout.implicitHeight : 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 {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -58,6 +76,14 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: layout
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Appearance.spacing.smaller
|
||||||
|
|
||||||
CustomText {
|
CustomText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
font.pointSize: Appearance.font.size.larger
|
font.pointSize: Appearance.font.size.larger
|
||||||
@@ -102,15 +128,17 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 33
|
Layout.preferredHeight: 33
|
||||||
|
Layout.preferredWidth: Math.max(Math.min(fromTextField.contentWidth + Appearance.padding.large * 2, 550), 50)
|
||||||
color: DynamicColors.tPalette.m3surfaceContainerHigh
|
color: DynamicColors.tPalette.m3surfaceContainerHigh
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
CustomTextField {
|
CustomTextField {
|
||||||
anchors.fill: parent
|
id: fromTextField
|
||||||
anchors.leftMargin: Appearance.padding.normal
|
|
||||||
anchors.rightMargin: Appearance.padding.normal
|
anchors.centerIn: parent
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
|
||||||
text: modelData.from ?? ""
|
text: modelData.from ?? ""
|
||||||
|
|
||||||
onEditingFinished: root.updateAlias(index, "from", text)
|
onEditingFinished: root.updateAlias(index, "from", text)
|
||||||
@@ -135,15 +163,17 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 33
|
Layout.preferredHeight: 33
|
||||||
color: DynamicColors.tPalette.m3surface
|
Layout.preferredWidth: Math.max(Math.min(toTextField.contentWidth + Appearance.padding.large * 2, 550), 50)
|
||||||
radius: Appearance.rounding.small
|
color: DynamicColors.tPalette.m3surfaceContainerHigh
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
CustomTextField {
|
CustomTextField {
|
||||||
anchors.fill: parent
|
id: toTextField
|
||||||
anchors.leftMargin: Appearance.padding.normal
|
|
||||||
anchors.rightMargin: Appearance.padding.normal
|
anchors.centerIn: parent
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
|
||||||
text: modelData.to ?? ""
|
text: modelData.to ?? ""
|
||||||
|
|
||||||
onEditingFinished: root.updateAlias(index, "to", text)
|
onEditingFinished: root.updateAlias(index, "to", text)
|
||||||
@@ -169,4 +199,5 @@ ColumnLayout {
|
|||||||
text: qsTr("Add alias")
|
text: qsTr("Add alias")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Item {
|
|||||||
required property var object
|
required property var object
|
||||||
property var pendingCommitEntries: []
|
property var pendingCommitEntries: []
|
||||||
required property string setting
|
required property string setting
|
||||||
|
property bool shouldBeActive: true
|
||||||
property int uidCounter: 0
|
property int uidCounter: 0
|
||||||
property var visualEntries: []
|
property var visualEntries: []
|
||||||
|
|
||||||
@@ -146,8 +147,25 @@ Item {
|
|||||||
Config.save();
|
Config.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
implicitHeight: layout.implicitHeight
|
anchors.right: parent.right
|
||||||
|
implicitHeight: shouldBeActive ? layout.implicitHeight : 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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: root.rebuildVisualEntries()
|
Component.onCompleted: root.rebuildVisualEntries()
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Item {
|
|||||||
required property string name
|
required property string name
|
||||||
required property var object
|
required property var object
|
||||||
required property list<string> settings
|
required property list<string> settings
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
function commitChoice(choice: int, setting: string): void {
|
function commitChoice(choice: int, setting: string): void {
|
||||||
root.object[setting] = choice;
|
root.object[setting] = choice;
|
||||||
@@ -32,8 +33,25 @@ Item {
|
|||||||
return Qt.formatTime(d, "h AP");
|
return Qt.formatTime(d, "h AP");
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
|
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 {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Item {
|
|||||||
required property string name
|
required property string name
|
||||||
required property var object
|
required property var object
|
||||||
required property string setting
|
required property string setting
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
function formattedValue(): string {
|
function formattedValue(): string {
|
||||||
const value = root.object[root.setting];
|
const value = root.object[root.setting];
|
||||||
@@ -21,8 +22,25 @@ Item {
|
|||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
|
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 {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -8,13 +8,31 @@ Item {
|
|||||||
|
|
||||||
required property int index
|
required property int index
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
signal addActiveActionRequested
|
signal addActiveActionRequested
|
||||||
signal deleteRequested(int index)
|
signal deleteRequested(int index)
|
||||||
signal fieldEdited(string key, var value)
|
signal fieldEdited(string key, var value)
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
|
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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -176,6 +194,9 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
anchors.left: undefined
|
||||||
|
anchors.right: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -207,6 +228,9 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
anchors.left: undefined
|
||||||
|
anchors.right: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|||||||
@@ -11,10 +11,32 @@ Item {
|
|||||||
|
|
||||||
required property string name
|
required property string name
|
||||||
required property var object
|
required property var object
|
||||||
|
property alias row: row
|
||||||
required property string setting
|
required property string setting
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
required property list<var> stringList
|
||||||
|
|
||||||
Layout.fillWidth: true
|
signal optionSet
|
||||||
Layout.preferredHeight: row.height
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
implicitHeight: shouldBeActive ? row.height : 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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: row
|
id: row
|
||||||
@@ -120,17 +142,18 @@ Item {
|
|||||||
StateLayer {
|
StateLayer {
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.object[root.setting] = fontDelegate.modelData;
|
root.object[root.setting] = fontDelegate.modelData;
|
||||||
|
root.optionSet();
|
||||||
Config.save();
|
Config.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: {
|
values: {
|
||||||
const fonts = Qt.fontFamilies();
|
const values = root.stringList;
|
||||||
const search = fontSearch.text;
|
const search = fontSearch.text;
|
||||||
var regex = new RegExp(search, "i");
|
var regex = new RegExp(search, "i");
|
||||||
|
|
||||||
return fonts.filter(n => regex.test(n));
|
return values.filter(n => regex.test(n));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,28 @@ Item {
|
|||||||
|
|
||||||
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
|
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
|
||||||
required property string name
|
required property string name
|
||||||
|
property bool shouldBeActive: true
|
||||||
required property string value
|
required property string value
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
|
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 {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -13,10 +13,28 @@ Item {
|
|||||||
required property string name
|
required property string name
|
||||||
required property var object
|
required property var object
|
||||||
required property string setting
|
required property string setting
|
||||||
|
property bool shouldBeActive: true
|
||||||
property real step: 1
|
property real step: 1
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
|
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 {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Item {
|
|||||||
required property string name
|
required property string name
|
||||||
required property var object
|
required property var object
|
||||||
required property list<string> settings
|
required property list<string> settings
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
function commitChoice(choice: int, setting: string): void {
|
function commitChoice(choice: int, setting: string): void {
|
||||||
root.object[setting] = choice;
|
root.object[setting] = choice;
|
||||||
@@ -32,8 +33,25 @@ Item {
|
|||||||
return Qt.formatTime(d, "h AP");
|
return Qt.formatTime(d, "h AP");
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
|
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 {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -12,9 +12,27 @@ Item {
|
|||||||
required property string name
|
required property string name
|
||||||
required property var object
|
required property var object
|
||||||
required property string setting
|
required property string setting
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
Layout.preferredHeight: layout.implicitHeight
|
anchors.right: parent.right
|
||||||
|
implicitHeight: shouldBeActive ? layout.implicitHeight : 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 {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -46,6 +64,9 @@ Item {
|
|||||||
StringListEditor {
|
StringListEditor {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
addLabel: root.addLabel
|
addLabel: root.addLabel
|
||||||
|
anchors.left: undefined
|
||||||
|
anchors.right: undefined
|
||||||
|
anchors.verticalCenter: undefined
|
||||||
values: [...(root.object[root.setting] ?? [])]
|
values: [...(root.object[root.setting] ?? [])]
|
||||||
|
|
||||||
onListEdited: function (values) {
|
onListEdited: function (values) {
|
||||||
|
|||||||
@@ -11,9 +11,27 @@ Item {
|
|||||||
required property string name
|
required property string name
|
||||||
required property var object
|
required property var object
|
||||||
required property string setting
|
required property string setting
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
|
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 {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ CustomRect {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property string name
|
required property string name
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
Layout.preferredHeight: 60
|
implicitHeight: 60
|
||||||
Layout.preferredWidth: 200
|
implicitWidth: 200
|
||||||
|
opacity: shouldBeActive ? 1 : 0
|
||||||
|
scale: shouldBeActive ? 1 : 0.8
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
CustomText {
|
CustomText {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
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}`]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,12 +60,18 @@ CustomClippingRect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
Column {
|
||||||
id: clayout
|
id: clayout
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
spacing: Appearance.spacing.small
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
// move: Transition {
|
||||||
|
// Anim {
|
||||||
|
// properties: "y"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,39 @@ CustomRect {
|
|||||||
property real contentPadding: Appearance.padding.large
|
property real contentPadding: Appearance.padding.large
|
||||||
property string sectionId: ""
|
property string sectionId: ""
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
Layout.preferredHeight: layout.implicitHeight + contentPadding * 2
|
anchors.right: parent.right
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
implicitHeight: layout.height + contentPadding * 2
|
||||||
radius: Appearance.rounding.normal - Appearance.padding.smaller
|
radius: Appearance.rounding.normal - Appearance.padding.smaller
|
||||||
|
|
||||||
ColumnLayout {
|
Behavior on implicitHeight {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
id: layout
|
id: layout
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.margins: root.contentPadding
|
anchors.margins: root.contentPadding
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.top: parent.top
|
||||||
|
// anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Appearance.spacing.normal
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
move: Transition {
|
||||||
|
Anim {
|
||||||
|
properties: "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,29 @@ CustomRect {
|
|||||||
property alias expanded: menu.expanded
|
property alias expanded: menu.expanded
|
||||||
property alias label: label
|
property alias label: label
|
||||||
property alias menu: menu
|
property alias menu: menu
|
||||||
|
property bool shouldBeActive: true
|
||||||
property alias text: label.text
|
property alias text: label.text
|
||||||
|
|
||||||
color: enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
color: enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
||||||
|
opacity: shouldBeActive ? 1 : 0
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
|
scale: shouldBeActive ? 1 : 0.8
|
||||||
|
visible: opacity > 0
|
||||||
z: expanded ? 100 : 0
|
z: expanded ? 100 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on scale {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CustomText {
|
CustomText {
|
||||||
id: label
|
id: label
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ ColumnLayout {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string addLabel: qsTr("Add entry")
|
property string addLabel: qsTr("Add entry")
|
||||||
|
property bool shouldBeActive: true
|
||||||
property var values: []
|
property var values: []
|
||||||
|
|
||||||
signal listEdited(var values)
|
signal listEdited(var values)
|
||||||
@@ -31,8 +32,26 @@ ColumnLayout {
|
|||||||
root.listEdited(list);
|
root.listEdited(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
height: shouldBeActive ? implicitHeight : 0
|
||||||
|
opacity: shouldBeActive ? 1 : 0
|
||||||
|
scale: shouldBeActive ? 1 : 0.8
|
||||||
spacing: Appearance.spacing.smaller
|
spacing: Appearance.spacing.smaller
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on scale {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: [...root.values]
|
model: [...root.values]
|
||||||
|
|||||||
@@ -13,11 +13,29 @@ Item {
|
|||||||
id: wrapper
|
id: wrapper
|
||||||
|
|
||||||
property bool changesMade: false
|
property bool changesMade: false
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
signal requestCrop
|
signal requestCrop
|
||||||
|
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
Layout.preferredHeight: 400
|
anchors.right: parent.right
|
||||||
|
implicitHeight: shouldBeActive ? 400 : 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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IconButton {
|
IconButton {
|
||||||
anchors.margins: Appearance.padding.normal
|
anchors.margins: Appearance.padding.normal
|
||||||
|
|||||||
@@ -12,13 +12,19 @@ GridView {
|
|||||||
|
|
||||||
readonly property int columnsCount: Math.max(1, Math.floor(width / minCellWidth))
|
readonly property int columnsCount: Math.max(1, Math.floor(width / minCellWidth))
|
||||||
readonly property int minCellWidth: 200 + Appearance.spacing.normal
|
readonly property int minCellWidth: 200 + Appearance.spacing.normal
|
||||||
|
property bool shouldBeActive: true
|
||||||
|
|
||||||
Layout.preferredHeight: contentHeight
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
cellHeight: 140 + Appearance.spacing.normal
|
cellHeight: 140 + Appearance.spacing.normal
|
||||||
cellWidth: width / columnsCount
|
cellWidth: width / columnsCount
|
||||||
clip: true
|
clip: true
|
||||||
|
implicitHeight: shouldBeActive ? contentHeight : 0
|
||||||
interactive: false
|
interactive: false
|
||||||
model: Wallpapers.list
|
model: Wallpapers.list
|
||||||
|
opacity: shouldBeActive ? 1 : 0
|
||||||
|
scale: shouldBeActive ? 1 : 0.8
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
required property int index
|
required property int index
|
||||||
@@ -137,4 +143,16 @@ GridView {
|
|||||||
radius: itemRadius
|
radius: itemRadius
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on scale {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ FileSystemEntry::FileSystemEntry(const QString& path, const QString& relativePat
|
|||||||
, m_path(path)
|
, m_path(path)
|
||||||
, m_relativePath(relativePath)
|
, m_relativePath(relativePath)
|
||||||
, m_isImageInitialised(false)
|
, m_isImageInitialised(false)
|
||||||
, m_mimeTypeInitialised(false) {}
|
, m_mimeTypeInitialised(false) {
|
||||||
|
}
|
||||||
|
|
||||||
QString FileSystemEntry::path() const {
|
QString FileSystemEntry::path() const {
|
||||||
return m_path;
|
return m_path;
|
||||||
@@ -57,8 +58,8 @@ bool FileSystemEntry::isImage() const {
|
|||||||
|
|
||||||
QString FileSystemEntry::mimeType() const {
|
QString FileSystemEntry::mimeType() const {
|
||||||
if (!m_mimeTypeInitialised) {
|
if (!m_mimeTypeInitialised) {
|
||||||
const QMimeDatabase db;
|
static const QMimeDatabase s_db;
|
||||||
m_mimeType = db.mimeTypeForFile(m_path).name();
|
m_mimeType = s_db.mimeTypeForFile(m_path).name();
|
||||||
m_mimeTypeInitialised = true;
|
m_mimeTypeInitialised = true;
|
||||||
}
|
}
|
||||||
return m_mimeType;
|
return m_mimeType;
|
||||||
@@ -219,7 +220,7 @@ void FileSystemModel::watchDirIfRecursive(const QString& path) {
|
|||||||
if (m_recursive && m_watchChanges) {
|
if (m_recursive && m_watchChanges) {
|
||||||
const auto currentDir = m_dir;
|
const auto currentDir = m_dir;
|
||||||
const bool showHidden = m_showHidden;
|
const bool showHidden = m_showHidden;
|
||||||
const auto future = QtConcurrent::run([showHidden, path]() {
|
auto future = QtConcurrent::run([showHidden, path]() {
|
||||||
QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot;
|
QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot;
|
||||||
if (showHidden) {
|
if (showHidden) {
|
||||||
filters |= QDir::Hidden;
|
filters |= QDir::Hidden;
|
||||||
@@ -232,16 +233,12 @@ void FileSystemModel::watchDirIfRecursive(const QString& path) {
|
|||||||
}
|
}
|
||||||
return dirs;
|
return dirs;
|
||||||
});
|
});
|
||||||
const auto watcher = new QFutureWatcher<QStringList>(this);
|
future.then(this, [currentDir, showHidden, this](const QStringList& paths) {
|
||||||
connect(watcher, &QFutureWatcher<QStringList>::finished, this, [currentDir, showHidden, watcher, this]() {
|
|
||||||
const auto paths = watcher->result();
|
|
||||||
if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) {
|
if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) {
|
||||||
// Ignore if dir or showHidden has changed
|
// Ignore if dir or showHidden has changed
|
||||||
m_watcher.addPaths(paths);
|
m_watcher.addPaths(paths);
|
||||||
}
|
}
|
||||||
watcher->deleteLater();
|
|
||||||
});
|
});
|
||||||
watcher->setFuture(future);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +292,7 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
|||||||
oldPaths << entry->path();
|
oldPaths << entry->path();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
|
auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString> > >& promise) {
|
||||||
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
|
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
|
||||||
|
|
||||||
std::optional<QDirIterator> iter;
|
std::optional<QDirIterator> iter;
|
||||||
@@ -353,7 +350,7 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
|||||||
newPaths.insert(path);
|
newPaths.insert(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promise.isCanceled() || newPaths == oldPaths) {
|
if (promise.isCanceled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,23 +362,17 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
|||||||
}
|
}
|
||||||
m_futures.insert(dir, future);
|
m_futures.insert(dir, future);
|
||||||
|
|
||||||
const auto watcher = new QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>(this);
|
future
|
||||||
|
.then(this,
|
||||||
connect(watcher, &QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>::finished, this, [dir, watcher, this]() {
|
[dir, this](QPair<QSet<QString>, QSet<QString> > result) {
|
||||||
m_futures.remove(dir);
|
m_futures.remove(dir);
|
||||||
|
if (!result.first.isEmpty() || !result.second.isEmpty()) {
|
||||||
if (!watcher->future().isResultReadyAt(0)) {
|
|
||||||
watcher->deleteLater();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto result = watcher->result();
|
|
||||||
applyChanges(result.first, result.second);
|
applyChanges(result.first, result.second);
|
||||||
|
}
|
||||||
watcher->deleteLater();
|
})
|
||||||
|
.onCanceled(this, [dir, this]() {
|
||||||
|
m_futures.remove(dir);
|
||||||
});
|
});
|
||||||
|
|
||||||
watcher->setFuture(future);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths) {
|
void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths) {
|
||||||
|
|||||||
@@ -13,136 +13,136 @@
|
|||||||
namespace ZShell::models {
|
namespace ZShell::models {
|
||||||
|
|
||||||
class FileSystemEntry : public QObject {
|
class FileSystemEntry : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel")
|
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel")
|
||||||
|
|
||||||
Q_PROPERTY(QString path READ path CONSTANT)
|
Q_PROPERTY(QString path READ path CONSTANT)
|
||||||
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
|
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
|
||||||
Q_PROPERTY(QString name READ name CONSTANT)
|
Q_PROPERTY(QString name READ name CONSTANT)
|
||||||
Q_PROPERTY(QString baseName READ baseName CONSTANT)
|
Q_PROPERTY(QString baseName READ baseName CONSTANT)
|
||||||
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
|
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
|
||||||
Q_PROPERTY(QString suffix READ suffix CONSTANT)
|
Q_PROPERTY(QString suffix READ suffix CONSTANT)
|
||||||
Q_PROPERTY(qint64 size READ size CONSTANT)
|
Q_PROPERTY(qint64 size READ size CONSTANT)
|
||||||
Q_PROPERTY(bool isDir READ isDir CONSTANT)
|
Q_PROPERTY(bool isDir READ isDir CONSTANT)
|
||||||
Q_PROPERTY(bool isImage READ isImage CONSTANT)
|
Q_PROPERTY(bool isImage READ isImage CONSTANT)
|
||||||
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
|
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
|
||||||
|
|
||||||
public:
|
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 path() const;
|
||||||
[[nodiscard]] QString relativePath() const;
|
[[nodiscard]] QString relativePath() const;
|
||||||
[[nodiscard]] QString name() const;
|
[[nodiscard]] QString name() const;
|
||||||
[[nodiscard]] QString baseName() const;
|
[[nodiscard]] QString baseName() const;
|
||||||
[[nodiscard]] QString parentDir() const;
|
[[nodiscard]] QString parentDir() const;
|
||||||
[[nodiscard]] QString suffix() const;
|
[[nodiscard]] QString suffix() const;
|
||||||
[[nodiscard]] qint64 size() const;
|
[[nodiscard]] qint64 size() const;
|
||||||
[[nodiscard]] bool isDir() const;
|
[[nodiscard]] bool isDir() const;
|
||||||
[[nodiscard]] bool isImage() const;
|
[[nodiscard]] bool isImage() const;
|
||||||
[[nodiscard]] QString mimeType() const;
|
[[nodiscard]] QString mimeType() const;
|
||||||
|
|
||||||
void updateRelativePath(const QDir& dir);
|
void updateRelativePath(const QDir& dir);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void relativePathChanged();
|
void relativePathChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QFileInfo m_fileInfo;
|
const QFileInfo m_fileInfo;
|
||||||
|
|
||||||
const QString m_path;
|
const QString m_path;
|
||||||
QString m_relativePath;
|
QString m_relativePath;
|
||||||
|
|
||||||
mutable bool m_isImage;
|
mutable bool m_isImage;
|
||||||
mutable bool m_isImageInitialised;
|
mutable bool m_isImageInitialised;
|
||||||
|
|
||||||
mutable QString m_mimeType;
|
mutable QString m_mimeType;
|
||||||
mutable bool m_mimeTypeInitialised;
|
mutable bool m_mimeTypeInitialised;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileSystemModel : public QAbstractListModel {
|
class FileSystemModel : public QAbstractListModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
||||||
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
|
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
|
||||||
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
|
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
|
||||||
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
|
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
|
||||||
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
|
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
|
||||||
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
|
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
|
||||||
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
|
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:
|
public:
|
||||||
enum Filter {
|
enum Filter {
|
||||||
NoFilter,
|
NoFilter,
|
||||||
Images,
|
Images,
|
||||||
Files,
|
Files,
|
||||||
Dirs
|
Dirs
|
||||||
};
|
};
|
||||||
Q_ENUM(Filter)
|
Q_ENUM(Filter)
|
||||||
|
|
||||||
explicit FileSystemModel(QObject* parent = nullptr);
|
explicit FileSystemModel(QObject* parent = nullptr);
|
||||||
|
|
||||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
[[nodiscard]] QString path() const;
|
[[nodiscard]] QString path() const;
|
||||||
void setPath(const QString& path);
|
void setPath(const QString& path);
|
||||||
|
|
||||||
[[nodiscard]] bool recursive() const;
|
[[nodiscard]] bool recursive() const;
|
||||||
void setRecursive(bool recursive);
|
void setRecursive(bool recursive);
|
||||||
|
|
||||||
[[nodiscard]] bool watchChanges() const;
|
[[nodiscard]] bool watchChanges() const;
|
||||||
void setWatchChanges(bool watchChanges);
|
void setWatchChanges(bool watchChanges);
|
||||||
|
|
||||||
[[nodiscard]] bool showHidden() const;
|
[[nodiscard]] bool showHidden() const;
|
||||||
void setShowHidden(bool showHidden);
|
void setShowHidden(bool showHidden);
|
||||||
|
|
||||||
[[nodiscard]] bool sortReverse() const;
|
[[nodiscard]] bool sortReverse() const;
|
||||||
void setSortReverse(bool sortReverse);
|
void setSortReverse(bool sortReverse);
|
||||||
|
|
||||||
[[nodiscard]] Filter filter() const;
|
[[nodiscard]] Filter filter() const;
|
||||||
void setFilter(Filter filter);
|
void setFilter(Filter filter);
|
||||||
|
|
||||||
[[nodiscard]] QStringList nameFilters() const;
|
[[nodiscard]] QStringList nameFilters() const;
|
||||||
void setNameFilters(const QStringList& nameFilters);
|
void setNameFilters(const QStringList& nameFilters);
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
|
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void pathChanged();
|
void pathChanged();
|
||||||
void recursiveChanged();
|
void recursiveChanged();
|
||||||
void watchChangesChanged();
|
void watchChangesChanged();
|
||||||
void showHiddenChanged();
|
void showHiddenChanged();
|
||||||
void sortReverseChanged();
|
void sortReverseChanged();
|
||||||
void filterChanged();
|
void filterChanged();
|
||||||
void nameFiltersChanged();
|
void nameFiltersChanged();
|
||||||
void entriesChanged();
|
void entriesChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDir m_dir;
|
QDir m_dir;
|
||||||
QFileSystemWatcher m_watcher;
|
QFileSystemWatcher m_watcher;
|
||||||
QList<FileSystemEntry*> m_entries;
|
QList<FileSystemEntry*> m_entries;
|
||||||
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString>>>> m_futures;
|
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString> > > > m_futures;
|
||||||
|
|
||||||
QString m_path;
|
QString m_path;
|
||||||
bool m_recursive;
|
bool m_recursive;
|
||||||
bool m_watchChanges;
|
bool m_watchChanges;
|
||||||
bool m_showHidden;
|
bool m_showHidden;
|
||||||
bool m_sortReverse;
|
bool m_sortReverse = false;
|
||||||
Filter m_filter;
|
Filter m_filter;
|
||||||
QStringList m_nameFilters;
|
QStringList m_nameFilters;
|
||||||
|
|
||||||
void watchDirIfRecursive(const QString& path);
|
void watchDirIfRecursive(const QString& path);
|
||||||
void update();
|
void update();
|
||||||
void updateWatcher();
|
void updateWatcher();
|
||||||
void updateEntries();
|
void updateEntries();
|
||||||
void updateEntriesForDir(const QString& dir);
|
void updateEntriesForDir(const QString& dir);
|
||||||
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
|
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
|
||||||
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
|
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::models
|
} // namespace ZShell::models
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"typer",
|
"typer",
|
||||||
"pillow",
|
"pillow",
|
||||||
|
"jinja2",
|
||||||
"materialyoucolor"
|
"materialyoucolor"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import typer
|
import typer
|
||||||
from zshell.subcommands import shell, scheme, screenshot, wallpaper
|
from zshell.subcommands import shell, scheme, screenshot, wallpaper, record
|
||||||
|
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ 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(preset.app, name="preset")
|
# 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.
@@ -0,0 +1,214 @@
|
|||||||
|
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,4 +1,8 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import click
|
||||||
import typer
|
import typer
|
||||||
|
|
||||||
args = ["qs", "-c", "zshell"]
|
args = ["qs", "-c", "zshell"]
|
||||||
@@ -8,35 +12,68 @@ app = typer.Typer()
|
|||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def kill():
|
def kill():
|
||||||
subprocess.run(args + ["kill"], check=True)
|
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)
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def start(no_daemon: bool = False):
|
def start(no_daemon: bool = False):
|
||||||
subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), check=True)
|
start_instance(no_daemon)
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def restart(no_daemon: bool = False):
|
def restart(no_daemon: bool = False):
|
||||||
subprocess.run(args + ["kill"], check=False)
|
subprocess.run(args + ["kill"], capture_output=True)
|
||||||
subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), check=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)
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def show():
|
def show():
|
||||||
subprocess.run(args + ["ipc"] + ["show"], check=True)
|
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())
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def log():
|
def log():
|
||||||
subprocess.run(args + ["log"], check=True)
|
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())
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def lock():
|
def lock():
|
||||||
subprocess.run(args + ["ipc"] + ["call"] + ["lock"] + ["lock"], check=True)
|
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())
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def call(target: str, method: str, method_args: list[str] = typer.Argument(None)):
|
def call(target: str, method: str, method_args: list[str] = typer.Argument(None)):
|
||||||
subprocess.run(args + ["ipc"] + ["call"] + [target] + [method] + (method_args or []), check=True)
|
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())
|
||||||
|
|||||||
Binary file not shown.
+61
-18
@@ -1,13 +1,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from subprocess import CompletedProcess
|
||||||
from unittest.mock import patch, call
|
from unittest.mock import patch, call
|
||||||
|
|
||||||
|
from typer.testing import CliRunner
|
||||||
from zshell.subcommands.shell import app
|
from zshell.subcommands.shell import app
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
def invoke(*args: str) -> None:
|
|
||||||
from typer.testing import CliRunner
|
|
||||||
|
|
||||||
runner = CliRunner()
|
def invoke(*args: str):
|
||||||
result = runner.invoke(app, args)
|
result = runner.invoke(app, args)
|
||||||
if result.exit_code != 0:
|
if result.exit_code != 0:
|
||||||
raise RuntimeError(result.output)
|
raise RuntimeError(result.output)
|
||||||
@@ -16,72 +18,113 @@ def invoke(*args: str) -> None:
|
|||||||
|
|
||||||
class TestKill:
|
class TestKill:
|
||||||
@patch("zshell.subcommands.shell.subprocess.run")
|
@patch("zshell.subcommands.shell.subprocess.run")
|
||||||
def test_kill_runs_qs_kill(self, mock_run):
|
def test_kill_runs_qs_kill_success(self, mock_run):
|
||||||
|
mock_run.return_value = CompletedProcess([], 0, b"", b"Killed abc\n")
|
||||||
invoke("kill")
|
invoke("kill")
|
||||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "kill"], check=True)
|
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
|
||||||
|
|
||||||
|
|
||||||
class TestStart:
|
class TestStart:
|
||||||
@patch("zshell.subcommands.shell.subprocess.run")
|
@patch("zshell.subcommands.shell.subprocess.run")
|
||||||
def test_start_default_daemon(self, mock_run):
|
def test_start_default_daemon(self, mock_run):
|
||||||
|
mock_run.return_value = CompletedProcess([], 0, b"", b"Launching config\n")
|
||||||
invoke("start")
|
invoke("start")
|
||||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "-n", "-d"], check=True)
|
mock_run.assert_called_once_with(["qs", "-c", "zshell", "-n", "-d"], capture_output=True)
|
||||||
|
|
||||||
@patch("zshell.subcommands.shell.subprocess.run")
|
@patch("zshell.subcommands.shell.subprocess.run")
|
||||||
def test_start_no_daemon(self, mock_run):
|
def test_start_no_daemon(self, mock_run):
|
||||||
|
mock_run.return_value = CompletedProcess([], 0, b"", b"Launching config\n")
|
||||||
invoke("start", "--no-daemon")
|
invoke("start", "--no-daemon")
|
||||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "-n"], check=True)
|
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
|
||||||
|
|
||||||
|
|
||||||
class TestShow:
|
class TestShow:
|
||||||
@patch("zshell.subcommands.shell.subprocess.run")
|
@patch("zshell.subcommands.shell.subprocess.run")
|
||||||
def test_show_runs_ipc_show(self, mock_run):
|
def test_show_runs_ipc_show(self, mock_run):
|
||||||
|
mock_run.return_value = CompletedProcess([], 0, b"", b"target visibilities\n")
|
||||||
invoke("show")
|
invoke("show")
|
||||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "show"], check=True)
|
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "show"], capture_output=True)
|
||||||
|
|
||||||
|
|
||||||
class TestLog:
|
class TestLog:
|
||||||
@patch("zshell.subcommands.shell.subprocess.run")
|
@patch("zshell.subcommands.shell.subprocess.run")
|
||||||
def test_log_runs_qs_log(self, mock_run):
|
def test_log_runs_qs_log(self, mock_run):
|
||||||
|
mock_run.return_value = CompletedProcess([], 0, b"log output\n", b"")
|
||||||
invoke("log")
|
invoke("log")
|
||||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "log"], check=True)
|
mock_run.assert_called_once_with(["qs", "-c", "zshell", "log"], capture_output=True)
|
||||||
|
|
||||||
|
|
||||||
class TestLock:
|
class TestLock:
|
||||||
@patch("zshell.subcommands.shell.subprocess.run")
|
@patch("zshell.subcommands.shell.subprocess.run")
|
||||||
def test_lock_runs_ipc_call_lock(self, mock_run):
|
def test_lock_runs_ipc_call_lock(self, mock_run):
|
||||||
|
mock_run.return_value = CompletedProcess([], 0, b"", b"")
|
||||||
invoke("lock")
|
invoke("lock")
|
||||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "lock", "lock"], check=True)
|
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "lock", "lock"], capture_output=True)
|
||||||
|
|
||||||
|
|
||||||
class TestCall:
|
class TestCall:
|
||||||
@patch("zshell.subcommands.shell.subprocess.run")
|
@patch("zshell.subcommands.shell.subprocess.run")
|
||||||
def test_call_no_args(self, mock_run):
|
def test_call_no_args(self, mock_run):
|
||||||
|
mock_run.return_value = CompletedProcess([], 0, b"", b"")
|
||||||
invoke("call", "target", "method")
|
invoke("call", "target", "method")
|
||||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "target", "method"], check=True)
|
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "target", "method"], capture_output=True)
|
||||||
|
|
||||||
@patch("zshell.subcommands.shell.subprocess.run")
|
@patch("zshell.subcommands.shell.subprocess.run")
|
||||||
def test_call_with_args(self, mock_run):
|
def test_call_with_args(self, mock_run):
|
||||||
|
mock_run.return_value = CompletedProcess([], 0, b"", b"")
|
||||||
invoke("call", "target", "method", "arg1", "arg2")
|
invoke("call", "target", "method", "arg1", "arg2")
|
||||||
mock_run.assert_called_once_with(
|
mock_run.assert_called_once_with(
|
||||||
["qs", "-c", "zshell", "ipc", "call", "target", "method", "arg1", "arg2"],
|
["qs", "-c", "zshell", "ipc", "call", "target", "method", "arg1", "arg2"],
|
||||||
check=True,
|
capture_output=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestRestart:
|
class TestRestart:
|
||||||
|
@patch("zshell.subcommands.shell.start_instance")
|
||||||
@patch("zshell.subcommands.shell.subprocess.run")
|
@patch("zshell.subcommands.shell.subprocess.run")
|
||||||
def test_restart_kills_then_starts_daemon(self, mock_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
|
||||||
|
]
|
||||||
invoke("restart")
|
invoke("restart")
|
||||||
assert mock_run.call_args_list == [
|
assert mock_run.call_args_list == [
|
||||||
call(["qs", "-c", "zshell", "kill"], check=False),
|
call(["qs", "-c", "zshell", "kill"], capture_output=True),
|
||||||
call(["qs", "-c", "zshell", "-n", "-d"], check=True),
|
call(["qs", "-c", "zshell", "kill"], capture_output=True),
|
||||||
]
|
]
|
||||||
|
mock_start.assert_called_once_with(no_daemon=False)
|
||||||
|
|
||||||
|
@patch("zshell.subcommands.shell.start_instance")
|
||||||
@patch("zshell.subcommands.shell.subprocess.run")
|
@patch("zshell.subcommands.shell.subprocess.run")
|
||||||
def test_restart_no_daemon(self, mock_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""),
|
||||||
|
]
|
||||||
invoke("restart", "--no-daemon")
|
invoke("restart", "--no-daemon")
|
||||||
assert mock_run.call_args_list == [
|
assert mock_run.call_args_list == [
|
||||||
call(["qs", "-c", "zshell", "kill"], check=False),
|
call(["qs", "-c", "zshell", "kill"], capture_output=True),
|
||||||
call(["qs", "-c", "zshell", "-n"], check=True),
|
call(["qs", "-c", "zshell", "kill"], capture_output=True),
|
||||||
]
|
]
|
||||||
|
mock_start.assert_called_once_with(no_daemon=True)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
//@ pragma UseQApplication
|
//@ pragma UseQApplication
|
||||||
//@ pragma Env QSG_RENDER_LOOP=threaded
|
//@ 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 QSG_NO_VSYNC=1
|
||||||
//@ pragma Env QS_NO_RELOAD_POPUP=1
|
//@ pragma Env QS_NO_RELOAD_POPUP=1
|
||||||
//@ 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 qs.Extensions
|
||||||
import qs.Modules
|
import qs.Modules
|
||||||
import qs.Modules.Wallpaper
|
import qs.Modules.Wallpaper
|
||||||
import qs.Modules.Lock
|
import qs.Modules.Lock
|
||||||
@@ -14,6 +15,8 @@ import qs.Helpers
|
|||||||
import qs.Modules.Polkit
|
import qs.Modules.Polkit
|
||||||
|
|
||||||
ShellRoot {
|
ShellRoot {
|
||||||
|
settings.watchFiles: true
|
||||||
|
|
||||||
Windows {
|
Windows {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,4 +39,7 @@ ShellRoot {
|
|||||||
|
|
||||||
Polkit {
|
Polkit {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoadExtensions {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user