summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2025-08-29 23:06:55 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2025-08-29 23:06:55 +0200
commitc3a8a171734bfeced58f4611365e85a6daed7db9 (patch)
tree235296a43af10ce96b5dd74e0523e59f4c1a8b12
parent218ac55d86ee49d151c0ba2dfbca6da104c66703 (diff)
downloadnixos-quickshell.tar
nixos-quickshell.tar.gz
nixos-quickshell.tar.bz2
nixos-quickshell.tar.xz
nixos-quickshell.zip
-rw-r--r--accounts/gkleen@sif/default.nix1
-rw-r--r--accounts/gkleen@sif/shell/default.nix20
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt116
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp88
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp57
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h7
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/default.nix20
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Bar.qml81
-rw-r--r--accounts/gkleen@sif/shell/quickshell/shell.qml13
-rw-r--r--home-modules/quickshell.nix68
10 files changed, 471 insertions, 0 deletions
diff --git a/accounts/gkleen@sif/default.nix b/accounts/gkleen@sif/default.nix
index 64434bb8..d509eeef 100644
--- a/accounts/gkleen@sif/default.nix
+++ b/accounts/gkleen@sif/default.nix
@@ -71,6 +71,7 @@ in {
71 imports = [ 71 imports = [
72 ./libvirt 72 ./libvirt
73 ./niri 73 ./niri
74 ./shell
74 ./synadm 75 ./synadm
75 flakeInputs.nix-index-database.homeModules.nix-index 76 flakeInputs.nix-index-database.homeModules.nix-index
76 flakeInputs.impermanence.nixosModules.home-manager.impermanence 77 flakeInputs.impermanence.nixosModules.home-manager.impermanence
diff --git a/accounts/gkleen@sif/shell/default.nix b/accounts/gkleen@sif/shell/default.nix
new file mode 100644
index 00000000..405ae4b6
--- /dev/null
+++ b/accounts/gkleen@sif/shell/default.nix
@@ -0,0 +1,20 @@
1{ config, pkgs, lib, ... }:
2
3{
4 config = {
5 programs.quickshell = {
6 enable = true;
7 config = {
8 src = ./quickshell;
9 replacements = {
10 coreutils = toString pkgs.coreutils;
11 };
12 };
13 };
14 systemd.user.services.quickshell = {
15 Service = {
16 Environment = "QML_IMPORT_PATH=${pkgs.qt6Packages.callPackage ./quickshell-plugins {}}/${pkgs.qt6.qtbase.qtQmlPrefix}";
17 };
18 };
19 };
20}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt b/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt
new file mode 100644
index 00000000..aa363c4c
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt
@@ -0,0 +1,116 @@
1set(INSTALL_QMLDIR "" CACHE STRING "QML install dir")
2set(INSTALL_QML_PREFIX "" CACHE STRING "QML install prefix")
3
4# There doesn't seem to be a standard cross-distro qml install path.
5if ("${INSTALL_QMLDIR}" STREQUAL "" AND "${INSTALL_QML_PREFIX}" STREQUAL "")
6 message(WARNING "Neither INSTALL_QMLDIR nor INSTALL_QML_PREFIX is set. QML modules will not be installed.")
7else()
8 if ("${INSTALL_QMLDIR}" STREQUAL "")
9 set(QML_FULL_INSTALLDIR "${CMAKE_INSTALL_PREFIX}/${INSTALL_QML_PREFIX}")
10 else()
11 set(QML_FULL_INSTALLDIR "${INSTALL_QMLDIR}")
12 endif()
13
14 message(STATUS "QML install dir: ${QML_FULL_INSTALLDIR}")
15endif()
16
17# Install a given target as a QML module. This is mostly pulled from ECM, as there does not seem
18# to be an official way to do it.
19# see https://github.com/KDE/extra-cmake-modules/blob/fe0f606bf7f222e36f7560fd7a2c33ef993e23bb/modules/ECMQmlModule6.cmake#L160
20function(install_qml_module arg_TARGET)
21 if (NOT DEFINED QML_FULL_INSTALLDIR)
22 return()
23 endif()
24
25 qt_query_qml_module(${arg_TARGET}
26 URI module_uri
27 VERSION module_version
28 PLUGIN_TARGET module_plugin_target
29 TARGET_PATH module_target_path
30 QMLDIR module_qmldir
31 TYPEINFO module_typeinfo
32 QML_FILES module_qml_files
33 RESOURCES module_resources
34 )
35
36 set(module_dir "${QML_FULL_INSTALLDIR}/${module_target_path}")
37
38 if (NOT TARGET "${module_plugin_target}")
39 message(FATAL_ERROR "install_qml_modules called for a target without a plugin")
40 endif()
41
42 get_target_property(target_type "${arg_TARGET}" TYPE)
43 if (NOT "${target_type}" STREQUAL "STATIC_LIBRARY")
44 install(
45 TARGETS "${arg_TARGET}"
46 LIBRARY DESTINATION "${module_dir}"
47 RUNTIME DESTINATION "${module_dir}"
48 )
49
50 install(
51 TARGETS "${module_plugin_target}"
52 LIBRARY DESTINATION "${module_dir}"
53 RUNTIME DESTINATION "${module_dir}"
54 )
55 endif()
56
57 install(FILES "${module_qmldir}" DESTINATION "${module_dir}")
58 install(FILES "${module_typeinfo}" DESTINATION "${module_dir}")
59
60 # Install QML files
61 list(LENGTH module_qml_files num_files)
62 if (NOT "${module_qml_files}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0)
63 qt_query_qml_module(${arg_TARGET} QML_FILES_DEPLOY_PATHS qml_files_deploy_paths)
64
65 math(EXPR last_index "${num_files} - 1")
66 foreach(i RANGE 0 ${last_index})
67 list(GET module_qml_files ${i} src_file)
68 list(GET qml_files_deploy_paths ${i} deploy_path)
69 get_filename_component(dst_name "${deploy_path}" NAME)
70 get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
71 install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}")
72 endforeach()
73 endif()
74
75 # Install resources
76 list(LENGTH module_resources num_files)
77 if (NOT "${module_resources}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0)
78 qt_query_qml_module(${arg_TARGET} RESOURCES_DEPLOY_PATHS resources_deploy_paths)
79
80 math(EXPR last_index "${num_files} - 1")
81 foreach(i RANGE 0 ${last_index})
82 list(GET module_resources ${i} src_file)
83 list(GET resources_deploy_paths ${i} deploy_path)
84 get_filename_component(dst_name "${deploy_path}" NAME)
85 get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
86 install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}")
87 endforeach()
88 endif()
89endfunction()
90
91
92cmake_minimum_required(VERSION 3.20)
93project(custom LANGUAGES CXX)
94
95find_package(Qt6 REQUIRED COMPONENTS Core Qml)
96
97qt_standard_project_setup(REQUIRES 6.6)
98
99qt6_policy(SET QTP0001 NEW)
100qt6_add_qml_module(customplugin
101 URI "Custom"
102 PLUGIN_TARGET customplugin
103)
104
105target_sources(customplugin PRIVATE
106 Chrono.cpp Chrono.hpp
107)
108
109target_compile_features(customplugin PUBLIC cxx_std_26)
110
111target_link_libraries(customplugin PRIVATE
112 Qt6::Core
113 Qt6::Qml
114)
115
116install_qml_module(customplugin)
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp
new file mode 100644
index 00000000..929b7be6
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.cpp
@@ -0,0 +1,88 @@
1#include "Chrono.hpp"
2
3#include <format>
4#include <iostream>
5#include <cmath>
6
7Chrono::Chrono(QObject* parent): QObject(parent) {
8 QObject::connect(&this->timer, &QTimer::timeout, this, &Chrono::onTimeout);
9 this->update();
10}
11
12bool Chrono::enabled() const { return this->mEnabled; }
13
14void Chrono::setEnabled(bool enabled) {
15 if (enabled == this->mEnabled) return;
16 this->mEnabled = enabled;
17 emit this->enabledChanged();
18 this->update();
19}
20
21Chrono::Precision Chrono::precision() const { return this->mPrecision; }
22
23void Chrono::setPrecision(Chrono::Precision precision) {
24 if (precision == this->mPrecision) return;
25 this->mPrecision = precision;
26 emit this->precisionChanged();
27 this->update();
28}
29
30void Chrono::onTimeout() {
31 this->setTime(this->targetTime);
32 this->schedule(this->targetTime);
33}
34
35void Chrono::update() {
36 if (this->mEnabled) {
37 this->setTime(std::chrono::time_point<std::chrono::system_clock>());
38 this->schedule(std::chrono::time_point<std::chrono::system_clock>());
39 } else {
40 this->timer.stop();
41 }
42}
43
44void Chrono::setTime(const std::chrono::time_point<std::chrono::system_clock>& targetTime) {
45 auto currentTime = std::chrono::system_clock::now();
46 auto offset = std::chrono::duration_cast<std::chrono::milliseconds>(targetTime - currentTime);
47 this->currentTime = abs(offset.count()) < 500 ? targetTime : currentTime;
48
49 switch (this->mPrecision) {
50 case Chrono::Hours: this->currentTime = std::chrono::time_point_cast<std::chrono::hours>(this->currentTime);
51 case Chrono::Minutes: this->currentTime = std::chrono::time_point_cast<std::chrono::minutes>(this->currentTime);
52 case Chrono::Seconds: this->currentTime = std::chrono::time_point_cast<std::chrono::seconds>(this->currentTime);
53 }
54
55 emit this->dateChanged();
56}
57
58void Chrono::schedule(const std::chrono::time_point<std::chrono::system_clock>& targetTime) {
59 auto currentTime = std::chrono::system_clock::now();
60 auto offset = std::chrono::duration_cast<std::chrono::milliseconds>(targetTime - currentTime);
61 auto nextTime = abs(offset.count()) < 500 ? targetTime : currentTime;
62
63 {
64 using namespace std::chrono_literals;
65
66 switch (this->mPrecision) {
67 case Chrono::Hours: nextTime = std::chrono::time_point_cast<std::chrono::hours>(nextTime) + 1h;
68 case Chrono::Minutes: nextTime = std::chrono::time_point_cast<std::chrono::minutes>(nextTime) + 1min;
69 case Chrono::Seconds: nextTime = std::chrono::time_point_cast<std::chrono::seconds>(nextTime) + 1s;
70 }
71 }
72
73 this->targetTime = nextTime;
74 auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(nextTime - currentTime);
75 this->timer.start(delay);
76}
77
78QString Chrono::format() const { return this->mFormat; }
79void Chrono::setFormat(QString format) {
80 if (format == this->mFormat) return;
81 this->mFormat = format;
82 emit this->formatChanged();
83 this->update();
84}
85
86QString Chrono::date() const {
87 return QString::fromStdString(std::format(std::runtime_format(this->mFormat.toStdString()), std::chrono::zoned_time(std::chrono::current_zone(), std::chrono::time_point_cast<std::chrono::seconds>(this->currentTime))));
88}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp
new file mode 100644
index 00000000..788fa88e
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Chrono.hpp
@@ -0,0 +1,57 @@
1#pragma once
2
3#include <QObject>
4#include <QTimer>
5
6#include <qqmlintegration.h>
7#include <chrono>
8
9class Chrono : public QObject {
10 Q_OBJECT;
11 Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
12 Q_PROPERTY(Chrono::Precision precision READ precision WRITE setPrecision NOTIFY precisionChanged);
13 Q_PROPERTY(QString format READ format WRITE setFormat NOTIFY formatChanged);
14 Q_PROPERTY(QString date READ date NOTIFY dateChanged);
15 QML_ELEMENT;
16
17public:
18 enum Precision : quint8 {
19 Hours = 1,
20 Minutes = 2,
21 Seconds = 3,
22 };
23 Q_ENUM(Precision);
24
25 explicit Chrono(QObject* parent = nullptr);
26
27 bool enabled() const;
28 void setEnabled(bool enabled);
29
30 Chrono::Precision precision() const;
31 void setPrecision(Chrono::Precision precision);
32
33 QString format() const;
34 void setFormat (QString format);
35
36 QString date() const;
37
38signals:
39 void enabledChanged();
40 void precisionChanged();
41 void formatChanged();
42 void dateChanged();
43
44private slots:
45 void onTimeout();
46
47private:
48 bool mEnabled = true;
49 Chrono::Precision mPrecision = Chrono::Seconds;
50 QString mFormat = "{:%c}";
51 QTimer timer;
52 std::chrono::time_point<std::chrono::system_clock> currentTime, targetTime;
53
54 void update();
55 void setTime(const std::chrono::time_point<std::chrono::system_clock>& targetTime);
56 void schedule(const std::chrono::time_point<std::chrono::system_clock>& targetTime);
57};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h b/accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h
new file mode 100644
index 00000000..e66ba9e3
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/customplugin.h
@@ -0,0 +1,7 @@
1#include <QQmlEngineExtensionPlugin>
2
3class CustomPlugin : public QQmlEngineExtensionPlugin
4{
5 Q_OBJECT
6 Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid)
7};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/default.nix b/accounts/gkleen@sif/shell/quickshell-plugins/default.nix
new file mode 100644
index 00000000..fafea90e
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/default.nix
@@ -0,0 +1,20 @@
1{ lib
2, stdenv
3, cmake
4, qt6
5, fmt
6}:
7stdenv.mkDerivation rec {
8 name = "quickshell-custom";
9
10 src = ./.;
11 nativeBuildInputs = [ cmake qt6.wrapQtAppsHook ];
12 buildInputs = [
13 qt6.qtbase
14 qt6.qtdeclarative
15 ];
16
17 cmakeFlags = [
18 (lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
19 ];
20}
diff --git a/accounts/gkleen@sif/shell/quickshell/Bar.qml b/accounts/gkleen@sif/shell/quickshell/Bar.qml
new file mode 100644
index 00000000..b7235a61
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Bar.qml
@@ -0,0 +1,81 @@
1import Quickshell
2import Quickshell.Io
3import Custom as Custom
4import QtQuick
5
6
7PanelWindow {
8 property var modelData
9
10 anchors {
11 bottom: true
12 left: true
13 right: true
14 }
15 margins {
16 left: 26 + 8
17 right: 26 + 8
18 }
19
20 screen: modelData
21 implicitHeight: 21
22 color: Qt.rgba(0, 0, 0, 0.66)
23
24 Row {
25 id: left
26
27 height: parent.height
28 anchors.left: parent.left
29 anchors.leftMargin: 8
30 anchors.verticalCenter: parent.verticalCenter
31 spacing: 5
32
33 Text {
34 color: "white"
35 anchors.verticalCenter: parent.verticalCenter
36 text: "left"
37 }
38 }
39
40 Row {
41 id: center
42
43 height: parent.height
44 anchors.centerIn: parent
45 spacing: 5
46
47 Text {
48 color: "white"
49 anchors.verticalCenter: parent.verticalCenter
50 text: "center"
51 }
52 }
53
54 Row {
55 id: right
56
57 height: parent.height
58 anchors.right: parent.right
59 anchors.rightMargin: 8
60 anchors.verticalCenter: parent.verticalCenter
61 spacing: 5
62
63 Text {
64 id: clock
65 color: "white"
66
67 anchors.verticalCenter: parent.verticalCenter
68
69 Custom.Chrono {
70 id: chrono
71 format: "W{0:%V-%u} {0:%F} {0:%H:%M:%S%Ez}"
72 }
73
74 text: chrono.date
75
76 font.pointSize: 10
77 font.family: "Fira Sans"
78 font.features: { "tnum": 1 }
79 }
80 }
81} \ No newline at end of file
diff --git a/accounts/gkleen@sif/shell/quickshell/shell.qml b/accounts/gkleen@sif/shell/quickshell/shell.qml
new file mode 100644
index 00000000..35fe5344
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/shell.qml
@@ -0,0 +1,13 @@
1import Quickshell
2
3ShellRoot {
4 settings.watchFiles: false
5
6 Variants {
7 model: Quickshell.screens
8
9 delegate: Bar {
10 modelData: item
11 }
12 }
13}
diff --git a/home-modules/quickshell.nix b/home-modules/quickshell.nix
new file mode 100644
index 00000000..dac7089f
--- /dev/null
+++ b/home-modules/quickshell.nix
@@ -0,0 +1,68 @@
1{ config, pkgs, lib, ... }:
2
3let
4 cfg = config.programs.quickshell;
5in {
6 disabledModules = ["programs/quickshell.nix"];
7
8 options = {
9 programs.quickshell = {
10 enable = lib.mkEnableOption "quickshell";
11 package = lib.mkPackageOption pkgs "quickshell" { nullable = true; };
12 config.src = lib.mkOption {
13 type = lib.types.path;
14 };
15 config.replacements = lib.mkOption {
16 type = lib.types.attrsOf lib.types.str;
17 default = {};
18 };
19 };
20 };
21
22 config = lib.mkIf cfg.enable {
23 home.packages = [ cfg.package ];
24
25 xdg.configFile."quickshell".source = pkgs.stdenvNoCC.mkDerivation {
26 name = "quickshell";
27 preferLocalBuild = true;
28 allowSubstitutes = false;
29 dontUnpack = true;
30 inherit (cfg.config) src;
31 buildPhase = ''
32 runHook preBuild
33
34 while IFS= read -r -d $'\0' file <&3; do
35 [[ -z $file ]] && continue
36
37 mkdir -p "$out"/"$(dirname "$file")"
38 substitute "$src"/"$file" "$out"/"$file" \
39 ${lib.concatStringsSep " " (
40 lib.concatLists (lib.mapAttrsToList (name: value: [
41 "--replace-quiet" (lib.escapeShellArg "@${name}@") (lib.escapeShellArg value)
42 ]) cfg.config.replacements)
43 )}
44 done 3< <(find "$src" -type f -printf '%P\0')
45
46 runHook postBuild
47 '';
48 };
49
50 systemd.user.services.quickshell = {
51 Unit = {
52 Description = "quickshell";
53 Documentation = "https://quickshell.org/docs/v${cfg.package.version}";
54 PartOf = [ "graphical-session.target" ];
55 After = [ "graphical-session-pre.target" ];
56 };
57
58 Service = {
59 ExecStart = lib.getExe cfg.package;
60 Restart = "always";
61 };
62
63 Install = {
64 WantedBy = [ "graphical-session.target" ];
65 };
66 };
67 };
68}