initial commit
This commit is contained in:
commit
2d5299c8fa
57
ActivateLinux.qml
Normal file
57
ActivateLinux.qml
Normal file
@ -0,0 +1,57 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import "root:/bar"
|
||||
|
||||
PanelWindow {
|
||||
id: activateLinux
|
||||
|
||||
required property ShellScreen modelData
|
||||
|
||||
screen: modelData
|
||||
|
||||
anchors {
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
margins {
|
||||
right: 50
|
||||
bottom: 50
|
||||
}
|
||||
|
||||
implicitWidth: content.width
|
||||
implicitHeight: content.height
|
||||
|
||||
color: "transparent"
|
||||
|
||||
// Give the window an empty click mask so all clicks pass through it.
|
||||
mask: Region {}
|
||||
|
||||
// Use the wlroots specific layer property to ensure it displays over
|
||||
// fullscreen windows.
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
|
||||
Pill {
|
||||
Text {
|
||||
text: "Ahhh"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
|
||||
Text {
|
||||
text: "Activate Linux"
|
||||
color: "#50ffffff"
|
||||
font.pointSize: 22
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Go to Settings to activate Linux"
|
||||
color: "#50ffffff"
|
||||
font.pointSize: 14
|
||||
}
|
||||
}
|
||||
}
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
# Usefull links
|
||||
|
||||
## Docs
|
||||
https://doc.qt.io/qt-6/qml-qtquick-repeater.html
|
||||
https://quickshell.outfoxxed.me/
|
||||
|
||||
## Example dotfiles
|
||||
https://github.com/caelestia-dots/shell
|
||||
https://github.com/end-4/dots-hyprland
|
||||
https://github.com/Ly-sec/nixos/tree/main/home/quickshell
|
||||
|
30
bar/Music.qml
Normal file
30
bar/Music.qml
Normal file
@ -0,0 +1,30 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.Mpris
|
||||
|
||||
Text {
|
||||
required property MprisPlayer player
|
||||
text: `${player.trackTitle} - ${player.trackArtist || "Unknown Artist"}`
|
||||
|
||||
color: "white"
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
parent.player.canGoNext && parent.player.next()
|
||||
} else if (mouse.button === Qt.LeftButton) {
|
||||
parent.player.canTogglePlaying && parent.player.togglePlaying()
|
||||
}
|
||||
}
|
||||
|
||||
onWheel: (event) => {
|
||||
if (!parent.player.volumeSupported || !parent.player.canControl){
|
||||
return
|
||||
}
|
||||
|
||||
parent.player.volume = parent.player.volume + (event.angleDelta.y / 120 * 0.05)
|
||||
}
|
||||
}
|
||||
}
|
24
bar/Pill.qml
Normal file
24
bar/Pill.qml
Normal file
@ -0,0 +1,24 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import QtQuick.Layouts
|
||||
|
||||
Rectangle {
|
||||
id: pill
|
||||
default property alias content: container.data
|
||||
|
||||
// Center in the middle of the bar
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
implicitWidth: container.childrenRect.width + 15
|
||||
implicitHeight: container.childrenRect.height + 12
|
||||
|
||||
color: "#939ab7"
|
||||
radius: 18
|
||||
|
||||
Loader {
|
||||
id: container
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
54
bar/StatusBar.qml
Normal file
54
bar/StatusBar.qml
Normal file
@ -0,0 +1,54 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Mpris
|
||||
import "root:/singeltons"
|
||||
|
||||
PanelWindow {
|
||||
required property ShellScreen modelData
|
||||
screen: modelData
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
height: 30
|
||||
|
||||
color: "transparent"
|
||||
|
||||
Pill {
|
||||
Workspaces {}
|
||||
}
|
||||
|
||||
Pill {
|
||||
anchors.centerIn: parent
|
||||
|
||||
Text {
|
||||
text: Time.time
|
||||
|
||||
color: "white"
|
||||
font.pixelSize: 14
|
||||
}
|
||||
}
|
||||
|
||||
Pill {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: music.left
|
||||
anchors.rightMargin: 10
|
||||
Volume {}
|
||||
}
|
||||
|
||||
Pill {
|
||||
id: music
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
Music {
|
||||
player: Mpris.players.values[0]
|
||||
}
|
||||
}
|
||||
}
|
16
bar/Volume.qml
Normal file
16
bar/Volume.qml
Normal file
@ -0,0 +1,16 @@
|
||||
import QtQuick
|
||||
import "root:singeltons"
|
||||
|
||||
Text {
|
||||
text: `${Pipewire.volumePercent}% ${Pipewire.micMuted}`
|
||||
|
||||
color: "white"
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onWheel: (event) => {
|
||||
Pipewire.setVolume(Pipewire.volume + (event.angleDelta.y / 120 * 0.05))
|
||||
}
|
||||
}
|
||||
}
|
51
bar/Workspaces.qml
Normal file
51
bar/Workspaces.qml
Normal file
@ -0,0 +1,51 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick.Layouts
|
||||
|
||||
Rectangle {
|
||||
|
||||
implicitWidth: content.implicitWidth
|
||||
implicitHeight: content.implicitHeight
|
||||
color: "transparent"
|
||||
|
||||
Row {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: Hyprland.workspaces.values.filter(e => e.id >= 0)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
required property HyprlandWorkspace modelData
|
||||
property bool hovered: false
|
||||
|
||||
width: 25
|
||||
height: 20
|
||||
color: "transparent"
|
||||
|
||||
Text {
|
||||
text: modelData.name
|
||||
color: modelData.active ? "#09608c" : "#cccccc"
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
modelData.activate()
|
||||
parent.hovered = false
|
||||
}
|
||||
|
||||
onEntered: parent.hovered = !modelData.active
|
||||
onExited: parent.hovered = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1751741127,
|
||||
"narHash": "sha256-t75Shs76NgxjZSgvvZZ9qOmz5zuBE8buUaYD28BMTxg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "29e290002bfff26af1db6f64d070698019460302",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1749285348,
|
||||
"narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3e3afe5174c561dee0df6f2c2b2236990146329f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"quickshell": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1751880110,
|
||||
"narHash": "sha256-5fQ2cetL3rTHqXe2VM3puawL/8u5j6ujBr6Gdt7Iues=",
|
||||
"owner": "quickshell-mirror",
|
||||
"repo": "quickshell",
|
||||
"rev": "5d7e07508ae3e5487edb1ac5a152120434f091d5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "quickshell-mirror",
|
||||
"repo": "quickshell",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"quickshell": "quickshell"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
24
flake.nix
Normal file
24
flake.nix
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
description = "Dev shell with quickshell";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
quickshell.url = "github:quickshell-mirror/quickshell";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, quickshell, ... }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in {
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
packages = [
|
||||
quickshell.packages.${system}.default
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
exec zsh
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
136
launcher/Launcher.qml
Normal file
136
launcher/Launcher.qml
Normal file
@ -0,0 +1,136 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import "root:/singeltons"
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property bool show: true
|
||||
|
||||
implicitWidth: 600
|
||||
implicitHeight: 400
|
||||
visible: show
|
||||
focusable: true
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
|
||||
HyprlandWindow.opacity: 0.9
|
||||
|
||||
// Main window
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.centerIn: parent
|
||||
|
||||
color: Qt.hsla(0, 0, 1, 0.5)
|
||||
border.color: Qt.hsla(0, 0, 1, 0.2)
|
||||
border.width: 2
|
||||
radius: 12
|
||||
|
||||
// Main window layout
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 20
|
||||
|
||||
// Search bar
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
spacing: 8
|
||||
|
||||
TextField {
|
||||
id: searchInput
|
||||
|
||||
Layout.fillWidth: true
|
||||
focus: true
|
||||
placeholderText: "Search..."
|
||||
font.pixelSize: 20
|
||||
background: null
|
||||
color: "white"
|
||||
selectByMouse: true
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
switch (event.key){
|
||||
case (Qt.Key_Escape):
|
||||
root.show = false
|
||||
event.accepted = true
|
||||
break
|
||||
case (Qt.Key_Return):
|
||||
case (Qt.Key_Enter):
|
||||
if (resultsList.currentIndex >= 0) {
|
||||
const item = resultsList.model[resultsList.currentIndex]
|
||||
DesktopApps.launch(item)
|
||||
}
|
||||
root.show = false
|
||||
event.accepted = true
|
||||
break
|
||||
case (Qt.Key_Down):
|
||||
case (Qt.Key_Tab):
|
||||
resultsList.currentIndex = Math.min(resultsList.count - 1, resultsList.currentIndex + 1)
|
||||
event.accepted = true
|
||||
break
|
||||
case (Qt.Key_Up):
|
||||
case (Qt.Key_Backtab):
|
||||
resultsList.currentIndex = Math.max(0, resultsList.currentIndex - 1)
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Results
|
||||
ListView {
|
||||
id: resultsList
|
||||
model: DesktopApps.fuzzySearch(searchInput.text)
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
spacing: 4
|
||||
keyNavigationWraps: true
|
||||
|
||||
// Result item
|
||||
delegate: Rectangle {
|
||||
required property DesktopEntry modelData
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: 6
|
||||
color: ListView.isCurrentItem ? "#3a3a3c" : "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 8
|
||||
|
||||
Image {
|
||||
source: parent.parent.modelData.icon !== "" ? Quickshell.iconPath(parent.parent.modelData.icon,true) : ""
|
||||
visible: parent.parent.modelData.icon !== ""
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize.width: 30
|
||||
sourceSize.height: 30
|
||||
}
|
||||
|
||||
Text {
|
||||
text: parent.parent.modelData.name
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
font.pixelSize: 18
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
shell.qml
Normal file
21
shell.qml
Normal file
@ -0,0 +1,21 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import "./bar"
|
||||
import "./launcher/"
|
||||
|
||||
ShellRoot{
|
||||
// Variants{
|
||||
// model: Quickshell.screens
|
||||
//
|
||||
// ActivateLinux {}
|
||||
// }
|
||||
//
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
StatusBar {}
|
||||
}
|
||||
|
||||
// Launcher {}
|
||||
}
|
18
singeltons/DesktopApps.qml
Normal file
18
singeltons/DesktopApps.qml
Normal file
@ -0,0 +1,18 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import "root:/utils/fuzzySearch.js" as FuzzySearch
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property list<DesktopEntry> all: DesktopEntries.applications.values
|
||||
|
||||
function launch(entry: DesktopEntry): void {
|
||||
entry.execute()
|
||||
}
|
||||
|
||||
function fuzzySearch(query: string): var {
|
||||
return FuzzySearch.fuzzySearch(query, all);
|
||||
}
|
||||
}
|
30
singeltons/Pipewire.qml
Normal file
30
singeltons/Pipewire.qml
Normal file
@ -0,0 +1,30 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property PwNode sink: Pipewire.defaultAudioSink
|
||||
readonly property PwNode source: Pipewire.defaultAudioSource
|
||||
|
||||
readonly property bool muted: sink?.audio?.muted ?? false
|
||||
readonly property real volume: sink?.audio?.volume ?? 0
|
||||
|
||||
readonly property int volumePercent: Math.round(volume * 100)
|
||||
|
||||
|
||||
readonly property bool micMuted: source?.audio?.muted ?? false
|
||||
|
||||
function setVolume(volume: real): void {
|
||||
if (sink?.ready && sink?.audio) {
|
||||
sink.audio.muted = false;
|
||||
sink.audio.volume = volume;
|
||||
}
|
||||
}
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource]
|
||||
}
|
||||
}
|
17
singeltons/Time.qml
Normal file
17
singeltons/Time.qml
Normal file
@ -0,0 +1,17 @@
|
||||
// Time.qml
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
readonly property string time: {
|
||||
Qt.formatDateTime(clock.date, "hh:mm")
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
id: clock
|
||||
precision: SystemClock.Seconds
|
||||
}
|
||||
}
|
70
utils/fuzzySearch.js
Normal file
70
utils/fuzzySearch.js
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Fuzzy search for list of objects.
|
||||
* - Each object in `items` must have a `value`. Or set the search key in options.
|
||||
* - Returns an array of original objects ordered by their relevance.
|
||||
* - Implements a cutoff for non-matching strings based on minimum relevance. Can be set in the options.
|
||||
*
|
||||
* @param {string} query - The search string.
|
||||
* @param {Object[]} items - The array of objects to search. Each must have a 'value' property (string).
|
||||
* @param {object} [options] - Optional settings.
|
||||
* @param {number} [options.cutoff=0.2] - Minimum relevance threshold (0 to 1).
|
||||
* @param {string} [options.key='value'] - The property name to match against (default: 'value').
|
||||
* @param {boolean} [options.allOnEmpty=true] - Return all items on empty query.
|
||||
* @param {Object} [options.boostMap] - An object mapping item keys (by 'value') to normalized boost values (0 to 1).
|
||||
* @param {number} [options.boostWeight=0.1] - The maximum boost to apply for boostMap value 1.0.
|
||||
* @returns {Object[]} - Array of matching objects sorted by relevance.
|
||||
*/
|
||||
function fuzzySearch(query, items, options = {}) {
|
||||
const cutoff = options.cutoff ?? 0.2;
|
||||
const key = options.key ?? 'name';
|
||||
const boostMap = options.boostMap || {};
|
||||
const boostWeight = options.boostWeight ?? 0.1;
|
||||
const allOnEmpty = options.allOnEmpty == undefined || options.allOnEmpty == true;
|
||||
|
||||
if (!query) return allOnEmpty ? items : []
|
||||
|
||||
const q = query.toLowerCase();
|
||||
|
||||
return items
|
||||
.map(item => {
|
||||
const text = (item[key] || '').toLowerCase();
|
||||
let score = 0;
|
||||
|
||||
// Perfect match
|
||||
if (text === q) score = 1.0;
|
||||
// Starts with
|
||||
else if (text.startsWith(q)) score = 0.9 + 0.01 * (1 - q.length / (text.length || 1));
|
||||
// Substring match
|
||||
else {
|
||||
const idx = text.indexOf(q);
|
||||
if (idx !== -1) {
|
||||
const startScore = 0.7 - 0.1 * (idx / (text.length || 1));
|
||||
const lengthScore = 0.2 * (q.length / (text.length || 1));
|
||||
score = startScore + lengthScore;
|
||||
} else {
|
||||
// Fuzzy: all query chars in order
|
||||
let lastIdx = -1;
|
||||
let found = true;
|
||||
for (let c of q) {
|
||||
lastIdx = text.indexOf(c, lastIdx + 1);
|
||||
if (lastIdx === -1) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
score = 0.5 * (q.length / (text.length || 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Boost: expects normalized boostMap (values from 0 to 1)
|
||||
const boost = Math.max(0, Math.min(1, boostMap[item[key]] || 0)) * boostWeight;
|
||||
|
||||
return { item, score: score + boost };
|
||||
})
|
||||
.filter(result => result.score >= cutoff)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.map(result => result.item);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user