summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Hämisch <tim@thaemisch.net>2025-06-15 13:40:47 +0200
committerGitHub <noreply@github.com>2025-06-15 13:40:47 +0200
commit68874082b4cfee63feaecc0640646ad0ba753da7 (patch)
tree815a1113a4bd83373283253a35f0220a08a8cfe6
parentlauncher: use standard logout command (diff)
parentdashboard: fix uptime (diff)
downloadcaelestia-shell-68874082b4cfee63feaecc0640646ad0ba753da7.tar.gz
caelestia-shell-68874082b4cfee63feaecc0640646ad0ba753da7.tar.bz2
caelestia-shell-68874082b4cfee63feaecc0640646ad0ba753da7.zip
Merge branch 'main' into betteractions
-rw-r--r--README.md9
-rwxr-xr-xassets/beat_detector.cpp568
-rwxr-xr-xassets/realtime-beat-detector.py73
-rw-r--r--config/BarConfig.qml16
-rw-r--r--config/BorderConfig.qml14
-rw-r--r--config/Config.qml36
-rw-r--r--config/DashboardConfig.qml14
-rw-r--r--config/LauncherConfig.qml26
-rw-r--r--config/NotifsConfig.qml26
-rw-r--r--config/OsdConfig.qml16
-rw-r--r--config/SessionConfig.qml14
-rw-r--r--modules/bar/Bar.qml6
-rw-r--r--modules/bar/components/workspaces/ActiveIndicator.qml16
-rw-r--r--modules/bar/components/workspaces/OccupiedBg.qml6
-rw-r--r--modules/bar/components/workspaces/Workspace.qml16
-rw-r--r--modules/bar/components/workspaces/Workspaces.qml8
-rw-r--r--modules/bar/popouts/ActiveWindow.qml4
-rw-r--r--modules/bar/popouts/Background.qml4
-rw-r--r--modules/bar/popouts/Battery.qml2
-rw-r--r--modules/bar/popouts/TrayMenu.qml4
-rw-r--r--modules/dashboard/Background.qml4
-rw-r--r--modules/dashboard/Dash.qml2
-rw-r--r--modules/dashboard/Media.qml16
-rw-r--r--modules/dashboard/Performance.qml6
-rw-r--r--modules/dashboard/Tabs.qml17
-rw-r--r--modules/dashboard/dash/DateTime.qml2
-rw-r--r--modules/dashboard/dash/Media.qml26
-rw-r--r--modules/dashboard/dash/Resources.qml2
-rw-r--r--modules/dashboard/dash/User.qml9
-rw-r--r--modules/drawers/Backgrounds.qml2
-rw-r--r--modules/drawers/Border.qml6
-rw-r--r--modules/drawers/Drawers.qml8
-rw-r--r--modules/drawers/Exclusions.qml3
-rw-r--r--modules/drawers/Interactions.qml15
-rw-r--r--modules/drawers/Panels.qml4
-rw-r--r--modules/launcher/ActionItem.qml2
-rw-r--r--modules/launcher/Actions.qml4
-rw-r--r--modules/launcher/AppItem.qml2
-rw-r--r--modules/launcher/AppList.qml8
-rw-r--r--modules/launcher/Background.qml4
-rw-r--r--modules/launcher/Content.qml4
-rw-r--r--modules/launcher/ContentList.qml8
-rw-r--r--modules/launcher/WallpaperItem.qml2
-rw-r--r--modules/launcher/WallpaperList.qml6
-rw-r--r--modules/notifications/Background.qml6
-rw-r--r--modules/notifications/Content.qml10
-rw-r--r--modules/notifications/Notification.qml27
-rw-r--r--modules/osd/Background.qml4
-rw-r--r--modules/osd/Content.qml8
-rw-r--r--modules/osd/Interactions.qml2
-rw-r--r--modules/session/Background.qml4
-rw-r--r--modules/session/Content.qml8
-rw-r--r--services/BeatDetector.qml8
-rw-r--r--services/Bluetooth.qml52
-rw-r--r--services/Brightness.qml25
-rw-r--r--services/Cava.qml2
-rw-r--r--services/Colours.qml25
-rw-r--r--services/Hyprland.qml15
-rw-r--r--services/Network.qml48
-rw-r--r--services/Notifs.qml4
-rw-r--r--services/SystemUsage.qml83
-rw-r--r--services/Wallpapers.qml19
-rw-r--r--services/Weather.qml28
-rw-r--r--utils/Paths.qml1
-rw-r--r--widgets/CachingImage.qml4
65 files changed, 976 insertions, 447 deletions
diff --git a/README.md b/README.md
index acfebe4..66a56b2 100644
--- a/README.md
+++ b/README.md
@@ -31,14 +31,7 @@ https://github.com/user-attachments/assets/0840f496-575c-4ca6-83a8-87bb01a85c5f
## Installation
-### Automated installation (recommended)
-
-Install [`caelestia-scripts`](https://github.com/caelestia-dots/scripts) and run `caelestia install shell`.
-
-### Manual installation
-
-Install all [dependencies](https://github.com/caelestia-dots/scripts/blob/main/install/shell.fish#L10), then
-clone this repo into `$XDG_CONFIG_HOME/quickshell/caelestia` and run `qs -c caelestia`.
+This is not implemented as of now.
## Usage
diff --git a/assets/beat_detector.cpp b/assets/beat_detector.cpp
new file mode 100755
index 0000000..4eb9b48
--- /dev/null
+++ b/assets/beat_detector.cpp
@@ -0,0 +1,568 @@
+#include <algorithm>
+#include <pipewire/pipewire.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/props.h>
+#include <aubio/aubio.h>
+#include <memory>
+#include <iostream>
+#include <fstream>
+#include <csignal>
+#include <atomic>
+#include <vector>
+#include <cstring>
+#include <chrono>
+#include <iomanip>
+#include <sstream>
+#include <thread>
+#include <cmath>
+
+class EnhancedBeatDetector {
+private:
+ static constexpr uint32_t SAMPLE_RATE = 44100;
+ static constexpr uint32_t CHANNELS = 1;
+
+ // PipeWire objects
+ pw_main_loop* main_loop_;
+ pw_context* context_;
+ pw_core* core_;
+ pw_stream* stream_;
+
+ // Aubio objects
+ std::unique_ptr<aubio_tempo_t, decltype(&del_aubio_tempo)> tempo_;
+ std::unique_ptr<fvec_t, decltype(&del_fvec)> input_buffer_;
+ std::unique_ptr<fvec_t, decltype(&del_fvec)> output_buffer_;
+
+ // Additional aubio objects for enhanced features
+ std::unique_ptr<aubio_onset_t, decltype(&del_aubio_onset)> onset_;
+ std::unique_ptr<aubio_pitch_t, decltype(&del_aubio_pitch)> pitch_;
+ std::unique_ptr<fvec_t, decltype(&del_fvec)> pitch_buffer_;
+
+ const uint32_t buf_size_;
+ const uint32_t fft_size_;
+
+ static std::atomic<bool> should_quit_;
+ static EnhancedBeatDetector* instance_;
+
+ // Enhanced features
+ std::ofstream log_file_;
+ bool enable_logging_;
+ bool enable_performance_stats_;
+ bool enable_pitch_detection_;
+ bool enable_visual_feedback_;
+
+ // Performance tracking
+ std::chrono::high_resolution_clock::time_point last_process_time_;
+ std::vector<double> process_times_;
+ uint64_t total_beats_;
+ uint64_t total_onsets_;
+ std::chrono::steady_clock::time_point start_time_;
+
+ // Beat analysis
+ std::vector<float> recent_bpms_;
+ static constexpr size_t BPM_HISTORY_SIZE = 10;
+ float last_bpm_;
+ std::chrono::steady_clock::time_point last_beat_time_;
+
+ // Useless Visual feedback
+ std::string generate_beat_visual(float bpm, bool is_beat) {
+ if (!enable_visual_feedback_) return "";
+
+ std::stringstream ss;
+ if (is_beat) {
+ // Useless Animated beat indicator based on BPM intensity
+ int intensity = static_cast<int>(std::min(bpm / 20.0f, 10.0f));
+ ss << "\r ";
+ for (int i = 0; i < intensity; ++i) ss << "█";
+ for (int i = intensity; i < 10; ++i) ss << "░";
+ ss << " BPM: " << std::fixed << std::setprecision(1) << bpm;
+ ss << " | Avg: " << get_average_bpm();
+ }
+ return ss.str();
+ }
+
+public:
+ explicit EnhancedBeatDetector(uint32_t buf_size = 512,
+ bool enable_logging = true,
+ bool enable_performance_stats = true,
+ bool enable_pitch_detection = false,
+ bool enable_visual_feedback = true)
+ : main_loop_(nullptr)
+ , context_(nullptr)
+ , core_(nullptr)
+ , stream_(nullptr)
+ , tempo_(nullptr, &del_aubio_tempo)
+ , input_buffer_(nullptr, &del_fvec)
+ , output_buffer_(nullptr, &del_fvec)
+ , onset_(nullptr, &del_aubio_onset)
+ , pitch_(nullptr, &del_aubio_pitch)
+ , pitch_buffer_(nullptr, &del_fvec)
+ , buf_size_(buf_size)
+ , fft_size_(buf_size * 2)
+ , enable_logging_(enable_logging)
+ , enable_performance_stats_(enable_performance_stats)
+ , enable_pitch_detection_(enable_pitch_detection)
+ , enable_visual_feedback_(enable_visual_feedback)
+ , total_beats_(0)
+ , total_onsets_(0)
+ , last_bpm_(0.0f)
+ {
+ instance_ = this;
+ recent_bpms_.reserve(BPM_HISTORY_SIZE);
+ if (enable_performance_stats_) {
+ process_times_.reserve(1000); // Reserve space for performance data
+ }
+ initialize();
+ }
+
+ ~EnhancedBeatDetector() {
+ print_final_stats();
+ cleanup();
+ instance_ = nullptr;
+ }
+
+ // Delete copy constructor and assignment operator
+ EnhancedBeatDetector(const EnhancedBeatDetector&) = delete;
+ EnhancedBeatDetector& operator=(const EnhancedBeatDetector&) = delete;
+
+ bool initialize() {
+ start_time_ = std::chrono::steady_clock::now();
+
+ // Useless Initialize logging (actually useful)
+ if (enable_logging_) {
+ auto now = std::chrono::system_clock::now();
+ auto time_t = std::chrono::system_clock::to_time_t(now);
+ std::stringstream filename;
+ filename << "beat_log_" << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S") << ".txt";
+ log_file_.open(filename.str());
+ if (log_file_.is_open()) {
+ log_file_ << "# Beat Detection Log - " << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S") << "\n";
+ log_file_ << "# Timestamp,BPM,Onset,Pitch(Hz),ProcessTime(ms)\n";
+ std::cout << " Logging to: " << filename.str() << std::endl;
+ }
+ }
+
+ // Initialize PipeWire
+ pw_init(nullptr, nullptr);
+
+ main_loop_ = pw_main_loop_new(nullptr);
+ if (!main_loop_) {
+ std::cerr << " Failed to create main loop" << std::endl;
+ return false;
+ }
+
+ context_ = pw_context_new(pw_main_loop_get_loop(main_loop_), nullptr, 0);
+ if (!context_) {
+ std::cerr << " Failed to create context" << std::endl;
+ return false;
+ }
+
+ core_ = pw_context_connect(context_, nullptr, 0);
+ if (!core_) {
+ std::cerr << " Failed to connect to PipeWire" << std::endl;
+ return false;
+ }
+
+ // Initialize Aubio objects
+ tempo_.reset(new_aubio_tempo("default", fft_size_, buf_size_, SAMPLE_RATE));
+ if (!tempo_) {
+ std::cerr << " Failed to create aubio tempo detector" << std::endl;
+ return false;
+ }
+
+ input_buffer_.reset(new_fvec(buf_size_));
+ output_buffer_.reset(new_fvec(1));
+
+ if (!input_buffer_ || !output_buffer_) {
+ std::cerr << " Failed to create aubio buffers" << std::endl;
+ return false;
+ }
+
+ // Initialize onset detection
+ onset_.reset(new_aubio_onset("default", fft_size_, buf_size_, SAMPLE_RATE));
+ if (!onset_) {
+ std::cerr << " Failed to create aubio onset detector" << std::endl;
+ return false;
+ }
+
+ // Initialize pitch detection if enabled
+ if (enable_pitch_detection_) {
+ pitch_.reset(new_aubio_pitch("default", fft_size_, buf_size_, SAMPLE_RATE));
+ pitch_buffer_.reset(new_fvec(1));
+ if (!pitch_ || !pitch_buffer_) {
+ std::cerr << " Failed to create aubio pitch detector" << std::endl;
+ return false;
+ }
+ aubio_pitch_set_unit(pitch_.get(), "Hz");
+ }
+
+ return setup_stream();
+ }
+
+ void run() {
+ if (!main_loop_) return;
+
+ print_startup_info();
+ pw_main_loop_run(main_loop_);
+ }
+
+ void stop() {
+ should_quit_ = true;
+ if (main_loop_) {
+ pw_main_loop_quit(main_loop_);
+ }
+ }
+
+ static void signal_handler(int sig) {
+ if (instance_) {
+ std::cout << "\n Received signal " << sig << ", stopping gracefullllly..." << std::endl;
+ instance_->stop();
+ }
+ }
+
+private:
+ void print_startup_info() {
+ std::cout << "\n󰝚 Beat Detector Started!" << std::endl;
+ std::cout << " Buffer size: " << buf_size_ << " samples" << std::endl;
+ std::cout << " Sample rate: " << SAMPLE_RATE << " Hz" << std::endl;
+ std::cout << " Features enabled:" << std::endl;
+ std::cout << "  Logging: " << (enable_logging_ ? " " : "") << std::endl;
+ std::cout << "  Performance stats: " << (enable_performance_stats_ ? " " : "") << std::endl;
+ std::cout << " 󰗅 Pitch detection: " << (enable_pitch_detection_ ? " " : "") << std::endl;
+ std::cout << "\n Listening for beats... Press Ctrl+C to stop.\n" << std::endl;
+ }
+
+ void print_final_stats() {
+ if (!enable_performance_stats_) return;
+
+ auto end_time = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time_);
+
+ std::cout << "\n Final Statistics:" << std::endl;
+ std::cout << " 󱎫 Total runtime: " << duration.count() << " seconds" << std::endl;
+ std::cout << "  Total beats detected: " << total_beats_ << std::endl;
+ std::cout << "  Total onsets detected: " << total_onsets_ << std::endl;
+
+ if (!process_times_.empty()) {
+ double avg_time = 0;
+ for (double t : process_times_) avg_time += t;
+ avg_time /= process_times_.size();
+
+ auto max_time = *std::max_element(process_times_.begin(), process_times_.end());
+ auto min_time = *std::min_element(process_times_.begin(), process_times_.end());
+
+ std::cout << " ⚡ Average processing time: " << std::fixed << std::setprecision(3)
+ << avg_time << " ms" << std::endl;
+ std::cout << " 📈 Max processing time: " << max_time << " ms" << std::endl;
+ std::cout << " 📉 Min processing time: " << min_time << " ms" << std::endl;
+ }
+
+ if (!recent_bpms_.empty()) {
+ std::cout << " 󰝚 Final average BPM: " << get_average_bpm() << std::endl;
+ }
+ }
+
+ float get_average_bpm() const {
+ if (recent_bpms_.empty()) return 0.0f;
+ float sum = 0;
+ for (float bpm : recent_bpms_) sum += bpm;
+ return sum / recent_bpms_.size();
+ }
+
+ bool setup_stream() {
+ // Stream events
+ static const pw_stream_events stream_events = {
+ .version = PW_VERSION_STREAM_EVENTS,
+ .destroy = nullptr,
+ .state_changed = on_state_changed,
+ .control_info = nullptr,
+ .io_changed = nullptr,
+ .param_changed = nullptr,
+ .add_buffer = nullptr,
+ .remove_buffer = nullptr,
+ .process = on_process,
+ .drained = nullptr,
+ .command = nullptr,
+ .trigger_done = nullptr,
+ };
+
+ stream_ = pw_stream_new_simple(
+ pw_main_loop_get_loop(main_loop_),
+ "enhanced-beat-detector",
+ pw_properties_new(
+ PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Capture",
+ PW_KEY_MEDIA_ROLE, "Music",
+ nullptr
+ ),
+ &stream_events,
+ this
+ );
+
+ if (!stream_) {
+ std::cerr << " Failed to create stream" << std::endl;
+ return false;
+ }
+
+ // Audio format parameters
+ uint8_t buffer[1024];
+ spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+
+ struct spa_audio_info_raw audio_info = {};
+ audio_info.format = SPA_AUDIO_FORMAT_F32_LE;
+ audio_info.channels = CHANNELS;
+ audio_info.rate = SAMPLE_RATE;
+ audio_info.flags = 0;
+
+ const spa_pod* params[1];
+ params[0] = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_EnumFormat, &audio_info);
+
+ if (pw_stream_connect(stream_,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ static_cast<pw_stream_flags>(
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS),
+ params, 1) < 0) {
+ std::cerr << " Failed to connect stream" << std::endl;
+ return false;
+ }
+
+ return true;
+ }
+
+ static void on_state_changed(void* userdata, enum pw_stream_state /* old_state */,
+ enum pw_stream_state state, const char* error) {
+ auto* detector = static_cast<EnhancedBeatDetector*>(userdata);
+
+ const char* state_emoji = "󰑓 ";
+ switch (state) {
+ case PW_STREAM_STATE_CONNECTING: state_emoji = "󰄙 "; break;
+ case PW_STREAM_STATE_PAUSED: state_emoji = ""; break;
+ case PW_STREAM_STATE_STREAMING: state_emoji = "󰝚 "; break;
+ case PW_STREAM_STATE_ERROR: state_emoji = ""; break;
+ case PW_STREAM_STATE_UNCONNECTED: state_emoji = " "; break;
+ }
+
+ std::cout << state_emoji << " Stream state: " << pw_stream_state_as_string(state) << std::endl;
+
+ if (state == PW_STREAM_STATE_ERROR) {
+ std::cerr << " Stream error: " << (error ? error : "unknown") << std::endl;
+ detector->stop();
+ }
+ }
+
+ static void on_process(void* userdata) {
+ auto* detector = static_cast<EnhancedBeatDetector*>(userdata);
+ detector->process_audio();
+ }
+
+ void process_audio() {
+ if (should_quit_) return;
+
+ auto process_start = std::chrono::high_resolution_clock::now();
+
+ pw_buffer* buffer = pw_stream_dequeue_buffer(stream_);
+ if (!buffer) return;
+
+ spa_buffer* spa_buf = buffer->buffer;
+ if (!spa_buf->datas[0].data) {
+ pw_stream_queue_buffer(stream_, buffer);
+ return;
+ }
+
+ const float* audio_data = static_cast<const float*>(spa_buf->datas[0].data);
+ const uint32_t n_samples = spa_buf->datas[0].chunk->size / sizeof(float);
+
+ // Process in chunks
+ for (uint32_t offset = 0; offset + buf_size_ <= n_samples; offset += buf_size_) {
+ std::memcpy(input_buffer_->data, audio_data + offset, buf_size_ * sizeof(float));
+
+ // Beat detection
+ aubio_tempo_do(tempo_.get(), input_buffer_.get(), output_buffer_.get());
+ bool is_beat = output_buffer_->data[0] != 0.0f;
+
+ // Onset detection
+ aubio_onset_do(onset_.get(), input_buffer_.get(), output_buffer_.get());
+ bool is_onset = output_buffer_->data[0] != 0.0f;
+
+ float pitch_hz = 0.0f;
+ if (enable_pitch_detection_) {
+ aubio_pitch_do(pitch_.get(), input_buffer_.get(), pitch_buffer_.get());
+ pitch_hz = pitch_buffer_->data[0];
+ }
+
+ if (is_beat) {
+ total_beats_++;
+ last_bpm_ = aubio_tempo_get_bpm(tempo_.get());
+ last_beat_time_ = std::chrono::steady_clock::now();
+
+ // Update BPM history
+ recent_bpms_.push_back(last_bpm_);
+ if (recent_bpms_.size() > BPM_HISTORY_SIZE) {
+ recent_bpms_.erase(recent_bpms_.begin());
+ }
+
+ // Visual feedback
+ if (enable_visual_feedback_) {
+ std::cout << generate_beat_visual(last_bpm_, true) << std::flush;
+ } else {
+ std::cout << " BPM: " << std::fixed << std::setprecision(1) << last_bpm_ << std::endl;
+ }
+ }
+
+ if (is_onset) {
+ total_onsets_++;
+ }
+
+ // Logging
+ if (enable_logging_ && log_file_.is_open() && (is_beat || is_onset)) {
+ auto now = std::chrono::system_clock::now();
+ auto time_t = std::chrono::system_clock::to_time_t(now);
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+ now.time_since_epoch()) % 1000;
+
+ log_file_ << std::put_time(std::localtime(&time_t), "%H:%M:%S")
+ << "." << std::setfill('0') << std::setw(3) << ms.count() << ","
+ << (is_beat ? last_bpm_ : 0) << ","
+ << (is_onset ? 1 : 0) << ","
+ << pitch_hz << ",";
+ }
+ }
+
+ pw_stream_queue_buffer(stream_, buffer);
+
+ // Performance tracking
+ if (enable_performance_stats_) {
+ auto process_end = std::chrono::high_resolution_clock::now();
+ auto process_time = std::chrono::duration<double, std::milli>(process_end - process_start).count();
+
+ if (log_file_.is_open() && (total_beats_ > 0 || total_onsets_ > 0)) {
+ log_file_ << std::fixed << std::setprecision(3) << process_time << "\n";
+ }
+
+ if (process_times_.size() < 1000) {
+ process_times_.push_back(process_time);
+ }
+ }
+ }
+
+ void cleanup() {
+ if (log_file_.is_open()) {
+ log_file_.close();
+ }
+
+ if (stream_) {
+ pw_stream_destroy(stream_);
+ stream_ = nullptr;
+ }
+
+ if (core_) {
+ pw_core_disconnect(core_);
+ core_ = nullptr;
+ }
+
+ if (context_) {
+ pw_context_destroy(context_);
+ context_ = nullptr;
+ }
+
+ if (main_loop_) {
+ pw_main_loop_destroy(main_loop_);
+ main_loop_ = nullptr;
+ }
+
+ tempo_.reset();
+ input_buffer_.reset();
+ output_buffer_.reset();
+ onset_.reset();
+ pitch_.reset();
+ pitch_buffer_.reset();
+
+ pw_deinit();
+
+ std::cout << "\n Cleanup complete - All resources freed!" << std::endl;
+ }
+};
+
+// Static member definitions
+std::atomic<bool> EnhancedBeatDetector::should_quit_{false};
+EnhancedBeatDetector* EnhancedBeatDetector::instance_{nullptr};
+
+void print_usage() {
+ std::cout << " Beat Detector Usage:" << std::endl;
+ std::cout << " ./beat_detector [buffer_size] [options]" << std::endl;
+ std::cout << "\nOptions:" << std::endl;
+ std::cout << " --no-log Disable logging to file" << std::endl;
+ std::cout << " --no-stats Disable performance statistics" << std::endl;
+ std::cout << " --pitch Enable pitch detection" << std::endl;
+ std::cout << " --no-visual Disable visual feedback" << std::endl;
+ std::cout << " --help Show this help" << std::endl;
+ std::cout << "\nExamples:" << std::endl;
+ std::cout << " ./beat_detector # Default settings" << std::endl;
+ std::cout << " ./beat_detector 256 --pitch # Smaller buffer with pitch detection" << std::endl;
+ std::cout << " ./beat_detector 1024 --no-visual # Larger buffer, no visual feedback" << std::endl;
+}
+
+int main(int argc, char* argv[]) {
+ // Parse command line arguments
+ uint32_t buffer_size = 512;
+ bool enable_logging = true;
+ bool enable_performance_stats = true;
+ bool enable_pitch_detection = false;
+ bool enable_visual_feedback = true;
+
+ for (int i = 1; i < argc; ++i) {
+ std::string arg = argv[i];
+
+ if (arg == "--help" || arg == "-h") {
+ print_usage();
+ return 0;
+ } else if (arg == "--no-log") {
+ enable_logging = false;
+ } else if (arg == "--no-stats") {
+ enable_performance_stats = false;
+ } else if (arg == "--pitch") {
+ enable_pitch_detection = true;
+ } else if (arg == "--no-visual") {
+ enable_visual_feedback = false;
+ } else if (arg[0] != '-') {
+ // Assume it's a buffer size
+ try {
+ buffer_size = std::stoul(arg);
+ if (buffer_size < 64 || buffer_size > 8192) {
+ std::cerr << " Buffer size must be between 64 and 8192" << std::endl;
+ return 1;
+ }
+ } catch (...) {
+ std::cerr << " Invalid buffer size: " << arg << std::endl;
+ return 1;
+ }
+ } else {
+ std::cerr << " Unknown option: " << arg << std::endl;
+ print_usage();
+ return 1;
+ }
+ }
+
+ // Set up signal handling
+ std::signal(SIGINT, EnhancedBeatDetector::signal_handler);
+ std::signal(SIGTERM, EnhancedBeatDetector::signal_handler);
+
+ try {
+ EnhancedBeatDetector detector(buffer_size, enable_logging, enable_performance_stats,
+ enable_pitch_detection, enable_visual_feedback);
+ detector.run();
+ } catch (const std::exception& e) {
+ std::cerr << " Error: " << e.what() << std::endl;
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Compilation command:
+ * g++ -std=c++17 -O3 -Wall -Wextra -I/usr/include/pipewire-0.3 -I/usr/include/spa-0.2 -I/usr/include/aubio \
+ * -o beat_detector beat_detector.cpp -lpipewire-0.3 -laubio -pthread
+ */
diff --git a/assets/realtime-beat-detector.py b/assets/realtime-beat-detector.py
deleted file mode 100755
index 0f4f54a..0000000
--- a/assets/realtime-beat-detector.py
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/usr/bin/env python
-
-import pyaudio
-import numpy as np
-import aubio
-import signal
-import sys
-
-from typing import List, Tuple
-
-
-class BeatDetector:
- def __init__(self, buf_size: int):
- self.buf_size: int = buf_size
-
- # Set up pyaudio and aubio beat detector
- self.audio: pyaudio.PyAudio = pyaudio.PyAudio()
- samplerate: int = 44100
-
- self.stream: pyaudio.Stream = self.audio.open(
- format=pyaudio.paFloat32,
- channels=1,
- rate=samplerate,
- input=True,
- frames_per_buffer=self.buf_size,
- stream_callback=self._pyaudio_callback
- )
-
- fft_size: int = self.buf_size * 2
-
- # tempo detection
- self.tempo: aubio.tempo = aubio.tempo("default", fft_size, self.buf_size, samplerate)
-
- # this one is called every time enough audio data (buf_size) has been read by the stream
- def _pyaudio_callback(self, in_data, frame_count, time_info, status):
- # Interpret a buffer as a 1-dimensional array (aubio do not work with raw data)
- audio_data = np.frombuffer(in_data, dtype=np.float32)
- # true if beat present
- beat = self.tempo(audio_data)
-
- # if beat detected, calculate BPM and send to OSC
- if beat[0]:
- print(self.tempo.get_bpm(), flush=True)
-
- return None, pyaudio.paContinue # Tell pyAudio to continue
-
- def __del__(self):
- self.stream.close()
- self.audio.terminate()
- print('--- Stopped ---')
-
-
-# main
-def main():
- bd = BeatDetector(512)
-
- # capture ctrl+c to stop gracefully process
- def signal_handler(none, frame):
- bd.stream.stop_stream()
- bd.stream.close()
- bd.audio.terminate()
- print(' ===> Ctrl + C')
- sys.exit(0)
-
- signal.signal(signal.SIGINT, signal_handler)
-
- # Audio processing happens in separate thread, so put this thread to sleep
- signal.pause()
-
-
-# main run
-if __name__ == "__main__":
- main()
diff --git a/config/BarConfig.qml b/config/BarConfig.qml
index 21f9314..31577c4 100644
--- a/config/BarConfig.qml
+++ b/config/BarConfig.qml
@@ -1,22 +1,14 @@
-pragma Singleton
+import Quickshell.Io
-import Quickshell
-import QtQuick
-
-Singleton {
- id: root
-
- readonly property Sizes sizes: Sizes {}
- readonly property Workspaces workspaces: Workspaces {}
-
- component Sizes: QtObject {
+JsonObject {
+ property JsonObject sizes: JsonObject {
property int innerHeight: 30
property int windowPreviewSize: 400
property int trayMenuWidth: 300
property int batteryWidth: 250
}
- component Workspaces: QtObject {
+ property JsonObject workspaces: JsonObject {
property int shown: 5
property bool rounded: true
property bool activeIndicator: true
diff --git a/config/BorderConfig.qml b/config/BorderConfig.qml
index a69cf59..7e95054 100644
--- a/config/BorderConfig.qml
+++ b/config/BorderConfig.qml
@@ -1,13 +1,9 @@
-pragma Singleton
-
import "root:/services"
-import Quickshell
+import Quickshell.Io
import QtQuick
-Singleton {
- id: root
-
- readonly property color colour: Colours.palette.m3surface
- readonly property int thickness: Appearance.padding.normal
- readonly property int rounding: Appearance.rounding.large
+JsonObject {
+ property color colour: Colours.palette.m3surface
+ property int thickness: Appearance.padding.normal
+ property int rounding: Appearance.rounding.large
}
diff --git a/config/Config.qml b/config/Config.qml
new file mode 100644
index 0000000..2e1e48b
--- /dev/null
+++ b/config/Config.qml
@@ -0,0 +1,36 @@
+pragma Singleton
+
+import "root:/utils"
+import Quickshell
+import Quickshell.Io
+
+Singleton {
+ id: root
+
+ property alias bar: adapter.bar
+ property alias border: adapter.border
+ property alias dashboard: adapter.dashboard
+ property alias launcher: adapter.launcher
+ property alias notifs: adapter.notifs
+ property alias osd: adapter.osd
+ property alias session: adapter.session
+
+ FileView {
+ path: `${Paths.config}/shell.json`
+ watchChanges: true
+ onFileChanged: reload()
+ onAdapterUpdated: writeAdapter()
+
+ JsonAdapter {
+ id: adapter
+
+ property JsonObject bar: BarConfig {}
+ property JsonObject border: BorderConfig {}
+ property JsonObject dashboard: DashboardConfig {}
+ property JsonObject launcher: LauncherConfig {}
+ property JsonObject notifs: NotifsConfig {}
+ property JsonObject osd: OsdConfig {}
+ property JsonObject session: SessionConfig {}
+ }
+ }
+}
diff --git a/config/DashboardConfig.qml b/config/DashboardConfig.qml
index 3c32e07..269d4b4 100644
--- a/config/DashboardConfig.qml
+++ b/config/DashboardConfig.qml
@@ -1,14 +1,10 @@
-pragma Singleton
+import Quickshell.Io
-import Quickshell
-import QtQuick
+JsonObject {
+ property int mediaUpdateInterval: 500
+ property int visualiserBars: 45
-Singleton {
- readonly property int mediaUpdateInterval: 500
- readonly property int visualiserBars: 45
- readonly property Sizes sizes: Sizes {}
-
- component Sizes: QtObject {
+ property JsonObject sizes: JsonObject {
readonly property int tabIndicatorHeight: 3
readonly property int tabIndicatorSpacing: 5
readonly property int infoWidth: 200
diff --git a/config/LauncherConfig.qml b/config/LauncherConfig.qml
index a3c154f..1757c7e 100644
--- a/config/LauncherConfig.qml
+++ b/config/LauncherConfig.qml
@@ -1,19 +1,15 @@
-pragma Singleton
+import Quickshell.Io
-import Quickshell
-import QtQuick
+JsonObject {
+ property int maxShown: 8
+ property int maxWallpapers: 9 // Warning: even numbers look bad
+ property string actionPrefix: ">"
+ property bool allowDangerousActions: false // Allow actions that can change the system state, like shutdown, reboot and logout
-Singleton {
- readonly property int maxShown: 8
- readonly property int maxWallpapers: 9 // Warning: even numbers look bad
- readonly property string actionPrefix: ">"
- readonly property Sizes sizes: Sizes {}
- readonly property bool allowDangerousActions: false // Allow actions that can change the system state, like shutdown, reboot and logout
-
- component Sizes: QtObject {
- readonly property int itemWidth: 600
- readonly property int itemHeight: 57
- readonly property int wallpaperWidth: 280
- readonly property int wallpaperHeight: 200
+ property JsonObject sizes: JsonObject {
+ property int itemWidth: 600
+ property int itemHeight: 57
+ property int wallpaperWidth: 280
+ property int wallpaperHeight: 200
}
}
diff --git a/config/NotifsConfig.qml b/config/NotifsConfig.qml
index 34b9226..f84def2 100644
--- a/config/NotifsConfig.qml
+++ b/config/NotifsConfig.qml
@@ -1,19 +1,15 @@
-pragma Singleton
+import Quickshell.Io
-import Quickshell
-import QtQuick
+JsonObject {
+ property bool expire: false
+ property int defaultExpireTimeout: 3000
+ property real clearThreshold: 0.3
+ property int expandThreshold: 20
+ property bool actionOnClick: false
-Singleton {
- readonly property bool expire: false
- readonly property int defaultExpireTimeout: 3000
- readonly property real clearThreshold: 0.3
- readonly property int expandThreshold: 20
- readonly property bool actionOnClick: false
- readonly property Sizes sizes: Sizes {}
-
- component Sizes: QtObject {
- readonly property int width: 400
- readonly property int image: 41
- readonly property int badge: 20
+ property JsonObject sizes: JsonObject {
+ property int width: 400
+ property int image: 41
+ property int badge: 20
}
}
diff --git a/config/OsdConfig.qml b/config/OsdConfig.qml
index 467d8e7..7275e22 100644
--- a/config/OsdConfig.qml
+++ b/config/OsdConfig.qml
@@ -1,14 +1,10 @@
-pragma Singleton
+import Quickshell.Io
-import Quickshell
-import QtQuick
+JsonObject {
+ property int hideDelay: 2000
-Singleton {
- readonly property int hideDelay: 2000
- readonly property Sizes sizes: Sizes {}
-
- component Sizes: QtObject {
- readonly property int sliderWidth: 30
- readonly property int sliderHeight: 150
+ property JsonObject sizes: JsonObject {
+ property int sliderWidth: 30
+ property int sliderHeight: 150
}
}
diff --git a/config/SessionConfig.qml b/config/SessionConfig.qml
index c72d2f1..0915469 100644
--- a/config/SessionConfig.qml
+++ b/config/SessionConfig.qml
@@ -1,13 +1,9 @@
-pragma Singleton
+import Quickshell.Io
-import Quickshell
-import QtQuick
+JsonObject {
+ property int dragThreshold: 30
-Singleton {
- readonly property int dragThreshold: 30
- readonly property Sizes sizes: Sizes {}
-
- component Sizes: QtObject {
- readonly property int button: 80
+ property JsonObject sizes: JsonObject {
+ property int button: 80
}
}
diff --git a/modules/bar/Bar.qml b/modules/bar/Bar.qml
index 2e8ab89..1d0db8d 100644
--- a/modules/bar/Bar.qml
+++ b/modules/bar/Bar.qml
@@ -63,7 +63,7 @@ Item {
anchors.bottom: parent.bottom
anchors.left: parent.left
- implicitWidth: child.implicitWidth + BorderConfig.thickness * 2
+ implicitWidth: child.implicitWidth + Config.border.thickness * 2
Item {
id: child
@@ -97,8 +97,8 @@ Item {
MouseArea {
anchors.fill: parent
- anchors.leftMargin: -BorderConfig.thickness
- anchors.rightMargin: -BorderConfig.thickness
+ anchors.leftMargin: -Config.border.thickness
+ anchors.rightMargin: -Config.border.thickness
onWheel: event => {
const activeWs = Hyprland.activeClient?.workspace?.name;
diff --git a/modules/bar/components/workspaces/ActiveIndicator.qml b/modules/bar/components/workspaces/ActiveIndicator.qml
index 3f3ca62..6807397 100644
--- a/modules/bar/components/workspaces/ActiveIndicator.qml
+++ b/modules/bar/components/workspaces/ActiveIndicator.qml
@@ -20,7 +20,7 @@ StyledRect {
property real offset: Math.min(leading, trailing)
property real size: {
const s = Math.abs(leading - trailing) + currentSize;
- if (BarConfig.workspaces.activeTrail && lastWs > currentWsIdx)
+ if (Config.bar.workspaces.activeTrail && lastWs > currentWsIdx)
return Math.min(getWsY(lastWs) + (workspaces[lastWs]?.size ?? 0) - offset, s);
return s;
}
@@ -43,9 +43,9 @@ StyledRect {
clip: true
x: 1
y: offset + 1
- implicitWidth: BarConfig.sizes.innerHeight - 2
+ implicitWidth: Config.bar.sizes.innerHeight - 2
implicitHeight: size - 2
- radius: BarConfig.workspaces.rounded ? Appearance.rounding.full : 0
+ radius: Config.bar.workspaces.rounded ? Appearance.rounding.full : 0
color: Colours.palette.m3primary
StyledRect {
@@ -72,13 +72,13 @@ StyledRect {
}
Behavior on leading {
- enabled: BarConfig.workspaces.activeTrail
+ enabled: Config.bar.workspaces.activeTrail
Anim {}
}
Behavior on trailing {
- enabled: BarConfig.workspaces.activeTrail
+ enabled: Config.bar.workspaces.activeTrail
Anim {
duration: Appearance.anim.durations.normal * 2
@@ -86,19 +86,19 @@ StyledRect {
}
Behavior on currentSize {
- enabled: BarConfig.workspaces.activeTrail
+ enabled: Config.bar.workspaces.activeTrail
Anim {}
}
Behavior on offset {
- enabled: !BarConfig.workspaces.activeTrail
+ enabled: !Config.bar.workspaces.activeTrail
Anim {}
}
Behavior on size {
- enabled: !BarConfig.workspaces.activeTrail
+ enabled: !Config.bar.workspaces.activeTrail
Anim {}
}
diff --git a/modules/bar/components/workspaces/OccupiedBg.qml b/modules/bar/components/workspaces/OccupiedBg.qml
index 4fba2f4..c5ed7cc 100644
--- a/modules/bar/components/workspaces/OccupiedBg.qml
+++ b/modules/bar/components/workspaces/OccupiedBg.qml
@@ -18,7 +18,7 @@ Item {
onOccupiedChanged: {
let count = 0;
const start = groupOffset;
- const end = start + BarConfig.workspaces.shown;
+ const end = start + Config.bar.workspaces.shown;
for (const [ws, occ] of Object.entries(occupied)) {
if (ws > start && ws <= end && occ) {
if (!occupied[ws - 1]) {
@@ -52,11 +52,11 @@ Item {
readonly property Workspace end: root.workspaces[modelData.end - 1 - root.groupOffset] ?? null
color: Colours.alpha(Colours.palette.m3surfaceContainerHigh, true)
- radius: BarConfig.workspaces.rounded ? Appearance.rounding.full : 0
+ radius: Config.bar.workspaces.rounded ? Appearance.rounding.full : 0
x: start?.x ?? 0
y: start?.y ?? 0
- implicitWidth: BarConfig.sizes.innerHeight
+ implicitWidth: Config.bar.sizes.innerHeight
implicitHeight: end?.y + end?.height - start?.y
anchors.horizontalCenter: parent.horizontalCenter
diff --git a/modules/bar/components/workspaces/Workspace.qml b/modules/bar/components/workspaces/Workspace.qml
index fa5fe62..6cf5c40 100644
--- a/modules/bar/components/workspaces/Workspace.qml
+++ b/modules/bar/components/workspaces/Workspace.qml
@@ -19,7 +19,7 @@ Item {
readonly property int ws: groupOffset + index + 1
readonly property bool isOccupied: occupied[ws] ?? false
- readonly property bool hasWindows: isOccupied && BarConfig.workspaces.showWindows
+ readonly property bool hasWindows: isOccupied && Config.bar.workspaces.showWindows
Layout.preferredWidth: childrenRect.width
Layout.preferredHeight: size
@@ -27,24 +27,24 @@ Item {
StyledText {
id: indicator
- readonly property string label: BarConfig.workspaces.label || root.ws
- readonly property string occupiedLabel: BarConfig.workspaces.occupiedLabel || label
- readonly property string activeLabel: BarConfig.workspaces.activeLabel || (root.isOccupied ? occupiedLabel : label)
+ readonly property string label: Config.bar.workspaces.label || root.ws
+ readonly property string occupiedLabel: Config.bar.workspaces.occupiedLabel || label
+ readonly property string activeLabel: Config.bar.workspaces.activeLabel || (root.isOccupied ? occupiedLabel : label)
animate: true
text: Hyprland.activeWsId === root.ws ? activeLabel : root.isOccupied ? occupiedLabel : label
- color: BarConfig.workspaces.occupiedBg || root.isOccupied || Hyprland.activeWsId === root.ws ? Colours.palette.m3onSurface : Colours.palette.m3outlineVariant
+ color: Config.bar.workspaces.occupiedBg || root.isOccupied || Hyprland.activeWsId === root.ws ? Colours.palette.m3onSurface : Colours.palette.m3outlineVariant
horizontalAlignment: StyledText.AlignHCenter
verticalAlignment: StyledText.AlignVCenter
- width: BarConfig.sizes.innerHeight
- height: BarConfig.sizes.innerHeight
+ width: Config.bar.sizes.innerHeight
+ height: Config.bar.sizes.innerHeight
}
Loader {
id: windows
- active: BarConfig.workspaces.showWindows
+ active: Config.bar.workspaces.showWindows
asynchronous: true
anchors.horizontalCenter: indicator.horizontalCenter
diff --git a/modules/bar/components/workspaces/Workspaces.qml b/modules/bar/components/workspaces/Workspaces.qml
index 4f4c75b..9264762 100644
--- a/modules/bar/components/workspaces/Workspaces.qml
+++ b/modules/bar/components/workspaces/Workspaces.qml
@@ -14,7 +14,7 @@ Item {
acc[curr.id] = curr.lastIpcObject.windows > 0;
return acc;
}, {})
- readonly property int groupOffset: Math.floor((Hyprland.activeWsId - 1) / BarConfig.workspaces.shown) * BarConfig.workspaces.shown
+ readonly property int groupOffset: Math.floor((Hyprland.activeWsId - 1) / Config.bar.workspaces.shown) * Config.bar.workspaces.shown
implicitWidth: layout.implicitWidth
implicitHeight: layout.implicitHeight
@@ -27,7 +27,7 @@ Item {
layer.smooth: true
Repeater {
- model: BarConfig.workspaces.shown
+ model: Config.bar.workspaces.shown
Workspace {
occupied: root.occupied
@@ -37,7 +37,7 @@ Item {
}
Loader {
- active: BarConfig.workspaces.occupiedBg
+ active: Config.bar.workspaces.occupiedBg
asynchronous: true
z: -1
@@ -51,7 +51,7 @@ Item {
}
Loader {
- active: BarConfig.workspaces.activeIndicator
+ active: Config.bar.workspaces.activeIndicator
asynchronous: true
sourceComponent: ActiveIndicator {
diff --git a/modules/bar/popouts/ActiveWindow.qml b/modules/bar/popouts/ActiveWindow.qml
index f3a5269..03130f1 100644
--- a/modules/bar/popouts/ActiveWindow.qml
+++ b/modules/bar/popouts/ActiveWindow.qml
@@ -61,8 +61,8 @@ Item {
captureSource: ToplevelManager.toplevels.values.find(t => t.title === Hyprland.activeClient?.title) ?? null
live: visible
- constraintSize.width: BarConfig.sizes.windowPreviewSize
- constraintSize.height: BarConfig.sizes.windowPreviewSize
+ constraintSize.width: Config.bar.sizes.windowPreviewSize
+ constraintSize.height: Config.bar.sizes.windowPreviewSize
}
}
}
diff --git a/modules/bar/popouts/Background.qml b/modules/bar/popouts/Background.qml
index a56b0b7..c099118 100644
--- a/modules/bar/popouts/Background.qml
+++ b/modules/bar/popouts/Background.qml
@@ -8,13 +8,13 @@ ShapePath {
required property Wrapper wrapper
required property bool invertBottomRounding
- readonly property real rounding: BorderConfig.rounding
+ readonly property real rounding: Config.border.rounding
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
property real ibr: invertBottomRounding ? -1 : 1
strokeWidth: -1
- fillColor: BorderConfig.colour
+ fillColor: Config.border.colour
PathArc {
relativeX: root.roundingX
diff --git a/modules/bar/popouts/Battery.qml b/modules/bar/popouts/Battery.qml
index 44e51ce..ebd0fc8 100644
--- a/modules/bar/popouts/Battery.qml
+++ b/modules/bar/popouts/Battery.qml
@@ -10,7 +10,7 @@ Column {
id: root
spacing: Appearance.spacing.normal
- width: BarConfig.sizes.batteryWidth
+ width: Config.bar.sizes.batteryWidth
StyledText {
text: UPower.displayDevice.isLaptopBattery ? qsTr("Remaining: %1%").arg(Math.round(UPower.displayDevice.percentage * 100)) : qsTr("No battery detected")
diff --git a/modules/bar/popouts/TrayMenu.qml b/modules/bar/popouts/TrayMenu.qml
index d69bf38..433de7f 100644
--- a/modules/bar/popouts/TrayMenu.qml
+++ b/modules/bar/popouts/TrayMenu.qml
@@ -80,7 +80,7 @@ StackView {
required property QsMenuEntry modelData
- implicitWidth: BarConfig.sizes.trayMenuWidth
+ implicitWidth: Config.bar.sizes.trayMenuWidth
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
radius: Appearance.rounding.full
@@ -153,7 +153,7 @@ StackView {
font.family: label.font.family
elide: Text.ElideRight
- elideWidth: BarConfig.sizes.trayMenuWidth - (icon.active ? icon.implicitWidth + label.anchors.leftMargin : 0) - (expand.active ? expand.implicitWidth + Appearance.spacing.normal : 0)
+ elideWidth: Config.bar.sizes.trayMenuWidth - (icon.active ? icon.implicitWidth + label.anchors.leftMargin : 0) - (expand.active ? expand.implicitWidth + Appearance.spacing.normal : 0)
}
Loader {
diff --git a/modules/dashboard/Background.qml b/modules/dashboard/Background.qml
index ebc60c8..876311e 100644
--- a/modules/dashboard/Background.qml
+++ b/modules/dashboard/Background.qml
@@ -7,12 +7,12 @@ ShapePath {
id: root
required property Wrapper wrapper
- readonly property real rounding: BorderConfig.rounding
+ readonly property real rounding: Config.border.rounding
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
strokeWidth: -1
- fillColor: BorderConfig.colour
+ fillColor: Config.border.colour
PathArc {
relativeX: root.rounding
diff --git a/modules/dashboard/Dash.qml b/modules/dashboard/Dash.qml
index eaf4344..22132af 100644
--- a/modules/dashboard/Dash.qml
+++ b/modules/dashboard/Dash.qml
@@ -26,7 +26,7 @@ GridLayout {
Rect {
Layout.row: 0
Layout.columnSpan: 2
- Layout.preferredWidth: DashboardConfig.sizes.weatherWidth
+ Layout.preferredWidth: Config.dashboard.sizes.weatherWidth
Layout.fillHeight: true
Weather {}
diff --git a/modules/dashboard/Media.qml b/modules/dashboard/Media.qml
index 43ef597..2d004d5 100644
--- a/modules/dashboard/Media.qml
+++ b/modules/dashboard/Media.qml
@@ -28,8 +28,8 @@ Item {
return `${Math.floor(length / 60)}:${Math.floor(length % 60).toString().padStart(2, "0")}`;
}
- implicitWidth: cover.implicitWidth + DashboardConfig.sizes.mediaVisualiserSize * 2 + details.implicitWidth + details.anchors.leftMargin + bongocat.implicitWidth + bongocat.anchors.leftMargin * 2 + Appearance.padding.large * 2
- implicitHeight: Math.max(cover.implicitHeight + DashboardConfig.sizes.mediaVisualiserSize * 2, details.implicitHeight, bongocat.implicitHeight) + Appearance.padding.large * 2
+ implicitWidth: cover.implicitWidth + Config.dashboard.sizes.mediaVisualiserSize * 2 + details.implicitWidth + details.anchors.leftMargin + bongocat.implicitWidth + bongocat.anchors.leftMargin * 2 + Appearance.padding.large * 2
+ implicitHeight: Math.max(cover.implicitHeight + Config.dashboard.sizes.mediaVisualiserSize * 2, details.implicitHeight, bongocat.implicitHeight) + Appearance.padding.large * 2
Behavior on playerProgress {
NumberAnimation {
@@ -41,7 +41,7 @@ Item {
Timer {
running: root.shouldUpdate && (Players.active?.isPlaying ?? false)
- interval: DashboardConfig.mediaUpdateInterval
+ interval: Config.dashboard.mediaUpdateInterval
triggeredOnStart: true
repeat: true
onTriggered: Players.active?.positionChanged()
@@ -66,7 +66,7 @@ Item {
property color colour: Colours.palette.m3primary
anchors.fill: cover
- anchors.margins: -DashboardConfig.sizes.mediaVisualiserSize
+ anchors.margins: -Config.dashboard.sizes.mediaVisualiserSize
onColourChanged: requestPaint()
@@ -81,7 +81,7 @@ Item {
ctx.lineWidth = 360 / len - Appearance.spacing.small / 4;
ctx.lineCap = "round";
- const size = DashboardConfig.sizes.mediaVisualiserSize;
+ const size = Config.dashboard.sizes.mediaVisualiserSize;
const cx = centerX;
const cy = centerY;
const rx = innerX + ctx.lineWidth / 2;
@@ -116,10 +116,10 @@ Item {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
- anchors.leftMargin: Appearance.padding.large + DashboardConfig.sizes.mediaVisualiserSize
+ anchors.leftMargin: Appearance.padding.large + Config.dashboard.sizes.mediaVisualiserSize
- implicitWidth: DashboardConfig.sizes.mediaCoverArtSize
- implicitHeight: DashboardConfig.sizes.mediaCoverArtSize
+ implicitWidth: Config.dashboard.sizes.mediaCoverArtSize
+ implicitHeight: Config.dashboard.sizes.mediaCoverArtSize
color: Colours.palette.m3surfaceContainerHigh
radius: Appearance.rounding.full
diff --git a/modules/dashboard/Performance.qml b/modules/dashboard/Performance.qml
index 8513d19..b7bf8d0 100644
--- a/modules/dashboard/Performance.qml
+++ b/modules/dashboard/Performance.qml
@@ -65,7 +65,7 @@ Row {
property bool primary
readonly property real primaryMult: primary ? 1.2 : 1
- readonly property real thickness: DashboardConfig.sizes.resourceProgessThickness * primaryMult
+ readonly property real thickness: Config.dashboard.sizes.resourceProgessThickness * primaryMult
property color fg1: Colours.palette.m3primary
property color fg2: Colours.palette.m3secondary
@@ -74,8 +74,8 @@ Row {
anchors.verticalCenter: parent.verticalCenter
- implicitWidth: DashboardConfig.sizes.resourceSize * primaryMult
- implicitHeight: DashboardConfig.sizes.resourceSize * primaryMult
+ implicitWidth: Config.dashboard.sizes.resourceSize * primaryMult
+ implicitHeight: Config.dashboard.sizes.resourceSize * primaryMult
onValue1Changed: canvas.requestPaint()
onValue2Changed: canvas.requestPaint()
diff --git a/modules/dashboard/Tabs.qml b/modules/dashboard/Tabs.qml
index 46cbd76..e678e6a 100644
--- a/modules/dashboard/Tabs.qml
+++ b/modules/dashboard/Tabs.qml
@@ -48,14 +48,14 @@ Item {
id: indicator
anchors.top: bar.bottom
- anchors.topMargin: DashboardConfig.sizes.tabIndicatorSpacing
+ anchors.topMargin: Config.dashboard.sizes.tabIndicatorSpacing
implicitWidth: bar.currentItem.implicitWidth
- implicitHeight: DashboardConfig.sizes.tabIndicatorHeight
+ implicitHeight: Config.dashboard.sizes.tabIndicatorHeight
x: {
const tab = bar.currentItem;
- const width = (root.nonAnimWidth - DashboardConfig.sizes.tabIndicatorSpacing * (bar.count - 1) * 2) / bar.count
+ const width = (root.nonAnimWidth - Config.dashboard.sizes.tabIndicatorSpacing * (bar.count - 1) * 2) / bar.count;
return width * tab.TabBar.index + (width - tab.implicitWidth) / 2;
}
@@ -69,7 +69,6 @@ Item {
color: Colours.palette.m3primary
radius: Appearance.rounding.full
-
}
Behavior on x {
@@ -108,14 +107,17 @@ Item {
cursorShape: Qt.PointingHandCursor
- onPressed: ({x,y}) => {
+ onPressed: ({
+ x,
+ y
+ }) => {
tab.TabBar.tabBar.setCurrentIndex(tab.TabBar.index);
const stateY = stateWrapper.y;
rippleAnim.x = x;
rippleAnim.y = y - stateY;
- const dist = (ox,oy) => ox * ox + oy * oy;
+ const dist = (ox, oy) => ox * ox + oy * oy;
const stateEndY = stateY + stateWrapper.height;
rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)));
@@ -176,7 +178,7 @@ Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
- implicitHeight: parent.height + DashboardConfig.sizes.tabIndicatorSpacing * 2
+ implicitHeight: parent.height + Config.dashboard.sizes.tabIndicatorSpacing * 2
color: "transparent"
radius: Appearance.rounding.small
@@ -237,7 +239,6 @@ Item {
text: tab.text
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
}
-
}
}
diff --git a/modules/dashboard/dash/DateTime.qml b/modules/dashboard/dash/DateTime.qml
index 738fed1..25df7a5 100644
--- a/modules/dashboard/dash/DateTime.qml
+++ b/modules/dashboard/dash/DateTime.qml
@@ -8,7 +8,7 @@ Item {
anchors.top: parent.top
anchors.bottom: parent.bottom
- implicitWidth: DashboardConfig.sizes.dateTimeWidth
+ implicitWidth: Config.dashboard.sizes.dateTimeWidth
StyledText {
id: hours
diff --git a/modules/dashboard/dash/Media.qml b/modules/dashboard/dash/Media.qml
index 7122d69..f47079d 100644
--- a/modules/dashboard/dash/Media.qml
+++ b/modules/dashboard/dash/Media.qml
@@ -19,7 +19,7 @@ Item {
anchors.top: parent.top
anchors.bottom: parent.bottom
- implicitWidth: DashboardConfig.sizes.mediaWidth
+ implicitWidth: Config.dashboard.sizes.mediaWidth
Behavior on playerProgress {
NumberAnimation {
@@ -31,7 +31,7 @@ Item {
Timer {
running: root.shouldUpdate && (Players.active?.isPlaying ?? false)
- interval: DashboardConfig.mediaUpdateInterval
+ interval: Config.dashboard.mediaUpdateInterval
triggeredOnStart: true
repeat: true
onTriggered: Players.active?.positionChanged()
@@ -43,16 +43,16 @@ Item {
ShapePath {
fillColor: "transparent"
strokeColor: Colours.palette.m3surfaceContainerHigh
- strokeWidth: DashboardConfig.sizes.mediaProgressThickness
+ strokeWidth: Config.dashboard.sizes.mediaProgressThickness
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: cover.x + cover.width / 2
centerY: cover.y + cover.height / 2
- radiusX: (cover.width + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
- radiusY: (cover.height + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
- startAngle: -90 - DashboardConfig.sizes.mediaProgressSweep / 2
- sweepAngle: DashboardConfig.sizes.mediaProgressSweep
+ radiusX: (cover.width + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
+ radiusY: (cover.height + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
+ startAngle: -90 - Config.dashboard.sizes.mediaProgressSweep / 2
+ sweepAngle: Config.dashboard.sizes.mediaProgressSweep
}
Behavior on strokeColor {
@@ -67,16 +67,16 @@ Item {
ShapePath {
fillColor: "transparent"
strokeColor: Colours.palette.m3primary
- strokeWidth: DashboardConfig.sizes.mediaProgressThickness
+ strokeWidth: Config.dashboard.sizes.mediaProgressThickness
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: cover.x + cover.width / 2
centerY: cover.y + cover.height / 2
- radiusX: (cover.width + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
- radiusY: (cover.height + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
- startAngle: -90 - DashboardConfig.sizes.mediaProgressSweep / 2
- sweepAngle: DashboardConfig.sizes.mediaProgressSweep * root.playerProgress
+ radiusX: (cover.width + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
+ radiusY: (cover.height + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
+ startAngle: -90 - Config.dashboard.sizes.mediaProgressSweep / 2
+ sweepAngle: Config.dashboard.sizes.mediaProgressSweep * root.playerProgress
}
Behavior on strokeColor {
@@ -95,7 +95,7 @@ Item {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
- anchors.margins: Appearance.padding.large + DashboardConfig.sizes.mediaProgressThickness + Appearance.spacing.small
+ anchors.margins: Appearance.padding.large + Config.dashboard.sizes.mediaProgressThickness + Appearance.spacing.small
implicitHeight: width
color: Colours.palette.m3surfaceContainerHigh
diff --git a/modules/dashboard/dash/Resources.qml b/modules/dashboard/dash/Resources.qml
index b22b2d1..9a1f07a 100644
--- a/modules/dashboard/dash/Resources.qml
+++ b/modules/dashboard/dash/Resources.qml
@@ -49,7 +49,7 @@ Row {
anchors.bottom: icon.top
anchors.bottomMargin: Appearance.spacing.small
- implicitWidth: DashboardConfig.sizes.resourceProgessThickness
+ implicitWidth: Config.dashboard.sizes.resourceProgessThickness
color: Colours.palette.m3surfaceContainerHigh
radius: Appearance.rounding.full
diff --git a/modules/dashboard/dash/User.qml b/modules/dashboard/dash/User.qml
index f1b9b18..bf989c6 100644
--- a/modules/dashboard/dash/User.qml
+++ b/modules/dashboard/dash/User.qml
@@ -36,6 +36,7 @@ Row {
Column {
id: info
+ anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
InfoLine {
@@ -69,8 +70,8 @@ Row {
running: true
command: ["uptime", "-p"]
- stdout: SplitParser {
- onRead: data => uptimeProc.uptime = data
+ stdout: StdioCollector {
+ onStreamFinished: uptimeProc.uptime = text.trim()
}
}
}
@@ -90,7 +91,7 @@ Row {
id: icon
anchors.left: parent.left
- anchors.leftMargin: (DashboardConfig.sizes.infoIconSize - implicitWidth) / 2
+ anchors.leftMargin: (Config.dashboard.sizes.infoIconSize - implicitWidth) / 2
text: line.icon
color: line.colour
@@ -109,7 +110,7 @@ Row {
text: `: ${line.text}`
font.pointSize: Appearance.font.size.normal
- width: DashboardConfig.sizes.infoWidth
+ width: Config.dashboard.sizes.infoWidth
elide: Text.ElideRight
}
}
diff --git a/modules/drawers/Backgrounds.qml b/modules/drawers/Backgrounds.qml
index 1840da1..46ca477 100644
--- a/modules/drawers/Backgrounds.qml
+++ b/modules/drawers/Backgrounds.qml
@@ -16,7 +16,7 @@ Shape {
required property Item bar
anchors.fill: parent
- anchors.margins: BorderConfig.thickness
+ anchors.margins: Config.border.thickness
anchors.leftMargin: bar.implicitWidth
preferredRendererType: Shape.CurveRenderer
opacity: Colours.transparency.enabled ? Colours.transparency.base : 1
diff --git a/modules/drawers/Border.qml b/modules/drawers/Border.qml
index 9014d07..62dc277 100644
--- a/modules/drawers/Border.qml
+++ b/modules/drawers/Border.qml
@@ -16,7 +16,7 @@ Item {
id: rect
anchors.fill: parent
- color: Colours.alpha(BorderConfig.colour, false)
+ color: Colours.alpha(Config.border.colour, false)
visible: false
}
@@ -29,9 +29,9 @@ Item {
Rectangle {
anchors.fill: parent
- anchors.margins: BorderConfig.thickness
+ anchors.margins: Config.border.thickness
anchors.leftMargin: root.bar.implicitWidth
- radius: BorderConfig.rounding
+ radius: Config.border.rounding
}
}
diff --git a/modules/drawers/Drawers.qml b/modules/drawers/Drawers.qml
index 9930c6d..5cab75b 100644
--- a/modules/drawers/Drawers.qml
+++ b/modules/drawers/Drawers.qml
@@ -33,9 +33,9 @@ Variants {
mask: Region {
x: bar.implicitWidth
- y: BorderConfig.thickness
- width: win.width - bar.implicitWidth - BorderConfig.thickness
- height: win.height - BorderConfig.thickness * 2
+ y: Config.border.thickness
+ width: win.width - bar.implicitWidth - Config.border.thickness
+ height: win.height - Config.border.thickness * 2
intersection: Intersection.Xor
regions: regions.instances
@@ -55,7 +55,7 @@ Variants {
required property Item modelData
x: modelData.x + bar.implicitWidth
- y: modelData.y + BorderConfig.thickness
+ y: modelData.y + Config.border.thickness
width: modelData.width
height: modelData.height
intersection: Intersection.Subtract
diff --git a/modules/drawers/Exclusions.qml b/modules/drawers/Exclusions.qml
index 188aadb..5557cdb 100644
--- a/modules/drawers/Exclusions.qml
+++ b/modules/drawers/Exclusions.qml
@@ -31,6 +31,7 @@ Scope {
component ExclusionZone: StyledWindow {
screen: root.screen
name: "border-exclusion"
- exclusiveZone: BorderConfig.thickness
+ exclusiveZone: Config.border.thickness
+ mask: Region {}
}
}
diff --git a/modules/drawers/Interactions.qml b/modules/drawers/Interactions.qml
index 10e37a8..d38cc8a 100644
--- a/modules/drawers/Interactions.qml
+++ b/modules/drawers/Interactions.qml
@@ -20,8 +20,8 @@ MouseArea {
property bool osdShortcutActive
function withinPanelHeight(panel: Item, x: real, y: real): bool {
- const panelY = BorderConfig.thickness + panel.y;
- return y >= panelY - BorderConfig.rounding && y <= panelY + panel.height + BorderConfig.rounding;
+ const panelY = Config.border.thickness + panel.y;
+ return y >= panelY - Config.border.rounding && y <= panelY + panel.height + Config.border.rounding;
}
function inRightPanel(panel: Item, x: real, y: real): bool {
@@ -30,7 +30,7 @@ MouseArea {
function inTopPanel(panel: Item, x: real, y: real): bool {
const panelX = bar.implicitWidth + panel.x;
- return y < BorderConfig.thickness + panel.y + panel.height && x >= panelX - BorderConfig.rounding && x <= panelX + panel.width + BorderConfig.rounding;
+ return y < Config.border.thickness + panel.y + panel.height && x >= panelX - Config.border.rounding && x <= panelX + panel.width + Config.border.rounding;
}
anchors.fill: parent
@@ -51,7 +51,10 @@ MouseArea {
}
}
- onPositionChanged: ({x, y}) => {
+ onPositionChanged: ({
+ x,
+ y
+ }) => {
// Show osd on hover
const showOsd = inRightPanel(panels.osd, x, y);
@@ -68,9 +71,9 @@ MouseArea {
// Show/hide session on drag
if (pressed && withinPanelHeight(panels.session, x, y)) {
const dragX = x - dragStart.x;
- if (dragX < -SessionConfig.dragThreshold)
+ if (dragX < -Config.session.dragThreshold)
visibilities.session = true;
- else if (dragX > SessionConfig.dragThreshold)
+ else if (dragX > Config.session.dragThreshold)
visibilities.session = false;
}
diff --git a/modules/drawers/Panels.qml b/modules/drawers/Panels.qml
index 6c6d892..a422fcc 100644
--- a/modules/drawers/Panels.qml
+++ b/modules/drawers/Panels.qml
@@ -24,7 +24,7 @@ Item {
readonly property BarPopouts.Wrapper popouts: popouts
anchors.fill: parent
- anchors.margins: BorderConfig.thickness
+ anchors.margins: Config.border.thickness
anchors.leftMargin: bar.implicitWidth
Component.onCompleted: Visibilities.panels[screen] = this
@@ -83,7 +83,7 @@ Item {
anchors.left: parent.left
anchors.verticalCenter: parent.top
anchors.verticalCenterOffset: {
- const off = root.popouts.currentCenter - BorderConfig.thickness;
+ const off = root.popouts.currentCenter - Config.border.thickness;
const diff = root.height - Math.floor(off + implicitHeight / 2);
if (diff < 0)
return off + diff;
diff --git a/modules/launcher/ActionItem.qml b/modules/launcher/ActionItem.qml
index 20638e3..720b272 100644
--- a/modules/launcher/ActionItem.qml
+++ b/modules/launcher/ActionItem.qml
@@ -9,7 +9,7 @@ Item {
required property Actions.Action modelData
required property var list
- implicitHeight: LauncherConfig.sizes.itemHeight
+ implicitHeight: Config.launcher.sizes.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
diff --git a/modules/launcher/Actions.qml b/modules/launcher/Actions.qml
index d612cb0..1f6e72b 100644
--- a/modules/launcher/Actions.qml
+++ b/modules/launcher/Actions.qml
@@ -129,7 +129,7 @@ Singleton {
}))
function fuzzyQuery(search: string): var {
- return Fuzzy.go(search.slice(LauncherConfig.actionPrefix.length), preppedActions, {
+ return Fuzzy.go(search.slice(Config.launcher.actionPrefix.length), preppedActions, {
all: true,
keys: ["name", "desc"],
scoreFn: r => r[0].score > 0 ? r[0].score * 0.9 + r[1].score * 0.1 : 0
@@ -137,7 +137,7 @@ Singleton {
}
function autocomplete(list: AppList, text: string): void {
- list.search.text = `${LauncherConfig.actionPrefix}${text} `;
+ list.search.text = `${Config.launcher.actionPrefix}${text} `;
}
function handleDangerousAction(list: AppList, process: QtObject): void {
diff --git a/modules/launcher/AppItem.qml b/modules/launcher/AppItem.qml
index b6a0bf5..e3f6720 100644
--- a/modules/launcher/AppItem.qml
+++ b/modules/launcher/AppItem.qml
@@ -11,7 +11,7 @@ Item {
required property DesktopEntry modelData
required property PersistentProperties visibilities
- implicitHeight: LauncherConfig.sizes.itemHeight
+ implicitHeight: Config.launcher.sizes.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
diff --git a/modules/launcher/AppList.qml b/modules/launcher/AppList.qml
index a431395..b2b9f57 100644
--- a/modules/launcher/AppList.qml
+++ b/modules/launcher/AppList.qml
@@ -14,14 +14,14 @@ ListView {
required property TextField search
required property PersistentProperties visibilities
- property bool isAction: search.text.startsWith(LauncherConfig.actionPrefix)
+ property bool isAction: search.text.startsWith(Config.launcher.actionPrefix)
function getModelValues() {
let text = search.text;
if (isAction)
return Actions.fuzzyQuery(text);
- if (text.startsWith(LauncherConfig.actionPrefix))
- text = search.text.slice(LauncherConfig.actionPrefix.length);
+ if (text.startsWith(Config.launcher.actionPrefix))
+ text = search.text.slice(Config.launcher.actionPrefix.length);
return Apps.fuzzyQuery(text);
}
@@ -32,7 +32,7 @@ ListView {
spacing: Appearance.spacing.small
orientation: Qt.Vertical
- implicitHeight: (LauncherConfig.sizes.itemHeight + spacing) * Math.min(LauncherConfig.maxShown, count) - spacing
+ implicitHeight: (Config.launcher.sizes.itemHeight + spacing) * Math.min(Config.launcher.maxShown, count) - spacing
highlightMoveDuration: Appearance.anim.durations.normal
highlightResizeDuration: 0
diff --git a/modules/launcher/Background.qml b/modules/launcher/Background.qml
index 5a3d5d4..82c5f4a 100644
--- a/modules/launcher/Background.qml
+++ b/modules/launcher/Background.qml
@@ -7,12 +7,12 @@ ShapePath {
id: root
required property Wrapper wrapper
- readonly property real rounding: BorderConfig.rounding
+ readonly property real rounding: Config.border.rounding
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
strokeWidth: -1
- fillColor: BorderConfig.colour
+ fillColor: Config.border.colour
PathArc {
relativeX: root.rounding
diff --git a/modules/launcher/Content.qml b/modules/launcher/Content.qml
index 54cc872..b6d1056 100644
--- a/modules/launcher/Content.qml
+++ b/modules/launcher/Content.qml
@@ -74,7 +74,7 @@ Item {
topPadding: Appearance.padding.larger
bottomPadding: Appearance.padding.larger
- placeholderText: qsTr("Type \"%1\" for commands").arg(LauncherConfig.actionPrefix)
+ placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix)
background: null
onAccepted: {
@@ -83,7 +83,7 @@ Item {
if (list.showWallpapers) {
Wallpapers.setWallpaper(currentItem.modelData.path);
root.visibilities.launcher = false;
- } else if (text.startsWith(LauncherConfig.actionPrefix)) {
+ } else if (text.startsWith(Config.launcher.actionPrefix)) {
currentItem.modelData.onClicked(list.currentList);
} else {
Apps.launch(currentItem.modelData);
diff --git a/modules/launcher/ContentList.qml b/modules/launcher/ContentList.qml
index 9818118..ad116f7 100644
--- a/modules/launcher/ContentList.qml
+++ b/modules/launcher/ContentList.qml
@@ -15,7 +15,7 @@ Item {
required property int padding
required property int rounding
- property bool showWallpapers: search.text.startsWith(`${LauncherConfig.actionPrefix}wallpaper `)
+ property bool showWallpapers: search.text.startsWith(`${Config.launcher.actionPrefix}wallpaper `)
property var currentList: (showWallpapers ? wallpaperList : appList).item
anchors.horizontalCenter: parent.horizontalCenter
@@ -29,7 +29,7 @@ Item {
name: "apps"
PropertyChanges {
- root.implicitWidth: LauncherConfig.sizes.itemWidth
+ root.implicitWidth: Config.launcher.sizes.itemWidth
root.implicitHeight: Math.max(empty.height, appList.height)
appList.active: true
}
@@ -43,8 +43,8 @@ Item {
name: "wallpapers"
PropertyChanges {
- root.implicitWidth: Math.max(LauncherConfig.sizes.itemWidth, wallpaperList.width)
- root.implicitHeight: LauncherConfig.sizes.wallpaperHeight
+ root.implicitWidth: Math.max(Config.launcher.sizes.itemWidth, wallpaperList.width)
+ root.implicitHeight: Config.launcher.sizes.wallpaperHeight
wallpaperList.active: true
}
}
diff --git a/modules/launcher/WallpaperItem.qml b/modules/launcher/WallpaperItem.qml
index dbd482e..862e910 100644
--- a/modules/launcher/WallpaperItem.qml
+++ b/modules/launcher/WallpaperItem.qml
@@ -42,7 +42,7 @@ StyledRect {
path: root.modelData.path
smooth: !root.PathView.view.moving
- width: LauncherConfig.sizes.wallpaperWidth
+ width: Config.launcher.sizes.wallpaperWidth
height: width / 16 * 9
}
diff --git a/modules/launcher/WallpaperList.qml b/modules/launcher/WallpaperList.qml
index fef1726..fd82a2b 100644
--- a/modules/launcher/WallpaperList.qml
+++ b/modules/launcher/WallpaperList.qml
@@ -14,8 +14,8 @@ PathView {
const screenWidth = QsWindow.window?.screen.width * 0.8;
if (!screenWidth)
return 0;
- const itemWidth = LauncherConfig.sizes.wallpaperWidth * 0.8;
- const max = LauncherConfig.maxWallpapers;
+ const itemWidth = Config.launcher.sizes.wallpaperWidth * 0.8;
+ const max = Config.launcher.maxWallpapers;
if (max * itemWidth > screenWidth) {
const items = Math.floor(screenWidth / itemWidth);
return items > 1 && items % 2 === 0 ? items - 1 : items;
@@ -43,7 +43,7 @@ PathView {
Wallpapers.preview(currentItem.modelData.path);
}
- implicitWidth: Math.min(numItems, count) * (LauncherConfig.sizes.wallpaperWidth * 0.8 + Appearance.padding.larger * 2)
+ implicitWidth: Math.min(numItems, count) * (Config.launcher.sizes.wallpaperWidth * 0.8 + Appearance.padding.larger * 2)
pathItemCount: numItems
cacheItemCount: 4
diff --git a/modules/notifications/Background.qml b/modules/notifications/Background.qml
index 2fd05f8..4a38510 100644
--- a/modules/notifications/Background.qml
+++ b/modules/notifications/Background.qml
@@ -8,13 +8,13 @@ ShapePath {
id: root
required property Wrapper wrapper
- readonly property real rounding: BorderConfig.rounding
+ readonly property real rounding: Config.border.rounding
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
- property real fullHeightRounding: wrapper.height >= QsWindow.window?.height - BorderConfig.thickness * 2 ? -rounding : rounding
+ property real fullHeightRounding: wrapper.height >= QsWindow.window?.height - Config.border.thickness * 2 ? -rounding : rounding
strokeWidth: -1
- fillColor: BorderConfig.colour
+ fillColor: Config.border.colour
PathLine {
relativeX: -(root.wrapper.width + root.rounding)
diff --git a/modules/notifications/Content.qml b/modules/notifications/Content.qml
index a64ccb9..6983b95 100644
--- a/modules/notifications/Content.qml
+++ b/modules/notifications/Content.qml
@@ -14,7 +14,7 @@ Item {
anchors.bottom: parent.bottom
anchors.right: parent.right
- implicitWidth: NotifsConfig.sizes.width + padding * 2
+ implicitWidth: Config.notifs.sizes.width + padding * 2
implicitHeight: {
const count = list.count;
if (count === 0)
@@ -29,19 +29,19 @@ Item {
const panel = Visibilities.panels[screen];
if (visibilities && panel) {
if (visibilities.osd) {
- const h = panel.osd.y - BorderConfig.rounding * 2;
+ const h = panel.osd.y - Config.border.rounding * 2;
if (height > h)
height = h;
}
if (visibilities.session) {
- const h = panel.session.y - BorderConfig.rounding * 2;
+ const h = panel.session.y - Config.border.rounding * 2;
if (height > h)
height = h;
}
}
- return Math.min((screen?.height ?? 0) - BorderConfig.thickness * 2, height + padding * 2);
+ return Math.min((screen?.height ?? 0) - Config.border.thickness * 2, height + padding * 2);
}
ClippingWrapperRectangle {
@@ -108,7 +108,7 @@ Item {
Anim {
target: notif
property: "x"
- to: (notif.x >= 0 ? NotifsConfig.sizes.width : -NotifsConfig.sizes.width) * 2
+ to: (notif.x >= 0 ? Config.notifs.sizes.width : -Config.notifs.sizes.width) * 2
duration: Appearance.anim.durations.normal
easing.bezierCurve: Appearance.anim.curves.emphasized
}
diff --git a/modules/notifications/Notification.qml b/modules/notifications/Notification.qml
index 51ed5f5..ebc5ce6 100644
--- a/modules/notifications/Notification.qml
+++ b/modules/notifications/Notification.qml
@@ -20,10 +20,10 @@ StyledRect {
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainer
radius: Appearance.rounding.normal
- implicitWidth: NotifsConfig.sizes.width
+ implicitWidth: Config.notifs.sizes.width
implicitHeight: inner.implicitHeight
- x: NotifsConfig.sizes.width
+ x: Config.notifs.sizes.width
Component.onCompleted: x = 0
RetainableLock {
@@ -52,7 +52,7 @@ StyledRect {
root.modelData.notification.dismiss();
}
onReleased: event => {
- if (Math.abs(root.x) < NotifsConfig.sizes.width * NotifsConfig.clearThreshold)
+ if (Math.abs(root.x) < Config.notifs.sizes.width * Config.notifs.clearThreshold)
root.x = 0;
else
root.modelData.popup = false;
@@ -60,12 +60,12 @@ StyledRect {
onPositionChanged: event => {
if (pressed) {
const diffY = event.y - startY;
- if (Math.abs(diffY) > NotifsConfig.expandThreshold)
+ if (Math.abs(diffY) > Config.notifs.expandThreshold)
root.expanded = diffY > 0;
}
}
onClicked: event => {
- if (!NotifsConfig.actionOnClick || event.button !== Qt.LeftButton)
+ if (!Config.notifs.actionOnClick || event.button !== Qt.LeftButton)
return;
const actions = root.modelData.actions;
@@ -93,7 +93,10 @@ StyledRect {
implicitHeight: root.nonAnimHeight
Behavior on implicitHeight {
- Anim {}
+ Anim {
+ duration: Appearance.anim.durations.expressiveDefaultSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
+ }
}
Loader {
@@ -104,14 +107,14 @@ StyledRect {
anchors.left: parent.left
anchors.top: parent.top
- width: NotifsConfig.sizes.image
- height: NotifsConfig.sizes.image
+ width: Config.notifs.sizes.image
+ height: Config.notifs.sizes.image
visible: root.hasImage || root.hasAppIcon
sourceComponent: ClippingRectangle {
radius: Appearance.rounding.full
- implicitWidth: NotifsConfig.sizes.image
- implicitHeight: NotifsConfig.sizes.image
+ implicitWidth: Config.notifs.sizes.image
+ implicitHeight: Config.notifs.sizes.image
Image {
anchors.fill: parent
@@ -137,8 +140,8 @@ StyledRect {
sourceComponent: StyledRect {
radius: Appearance.rounding.full
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3surfaceContainerHighest : Colours.palette.m3tertiaryContainer
- implicitWidth: root.hasImage ? NotifsConfig.sizes.badge : NotifsConfig.sizes.image
- implicitHeight: root.hasImage ? NotifsConfig.sizes.badge : NotifsConfig.sizes.image
+ implicitWidth: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
+ implicitHeight: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
Loader {
id: icon
diff --git a/modules/osd/Background.qml b/modules/osd/Background.qml
index 551637b..12cce6a 100644
--- a/modules/osd/Background.qml
+++ b/modules/osd/Background.qml
@@ -7,12 +7,12 @@ ShapePath {
id: root
required property Wrapper wrapper
- readonly property real rounding: BorderConfig.rounding
+ readonly property real rounding: Config.border.rounding
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
strokeWidth: -1
- fillColor: BorderConfig.colour
+ fillColor: Config.border.colour
PathArc {
relativeX: -root.roundingX
diff --git a/modules/osd/Content.qml b/modules/osd/Content.qml
index 6814966..4bd43af 100644
--- a/modules/osd/Content.qml
+++ b/modules/osd/Content.qml
@@ -28,8 +28,8 @@ Column {
value: Audio.volume
onMoved: Audio.setVolume(value)
- implicitWidth: OsdConfig.sizes.sliderWidth
- implicitHeight: OsdConfig.sizes.sliderHeight
+ implicitWidth: Config.osd.sizes.sliderWidth
+ implicitHeight: Config.osd.sizes.sliderHeight
}
VerticalSlider {
@@ -37,7 +37,7 @@ Column {
value: root.monitor?.brightness ?? 0
onMoved: root.monitor?.setBrightness(value)
- implicitWidth: OsdConfig.sizes.sliderWidth
- implicitHeight: OsdConfig.sizes.sliderHeight
+ implicitWidth: Config.osd.sizes.sliderWidth
+ implicitHeight: Config.osd.sizes.sliderHeight
}
}
diff --git a/modules/osd/Interactions.qml b/modules/osd/Interactions.qml
index eecf0b6..fa75aa0 100644
--- a/modules/osd/Interactions.qml
+++ b/modules/osd/Interactions.qml
@@ -39,7 +39,7 @@ Scope {
Timer {
id: timer
- interval: OsdConfig.hideDelay
+ interval: Config.osd.hideDelay
onTriggered: {
if (!root.hovered)
root.visibilities.osd = false;
diff --git a/modules/session/Background.qml b/modules/session/Background.qml
index 551637b..12cce6a 100644
--- a/modules/session/Background.qml
+++ b/modules/session/Background.qml
@@ -7,12 +7,12 @@ ShapePath {
id: root
required property Wrapper wrapper
- readonly property real rounding: BorderConfig.rounding
+ readonly property real rounding: Config.border.rounding
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
strokeWidth: -1
- fillColor: BorderConfig.colour
+ fillColor: Config.border.colour
PathArc {
relativeX: -root.roundingX
diff --git a/modules/session/Content.qml b/modules/session/Content.qml
index 7c69bbd..f6b1258 100644
--- a/modules/session/Content.qml
+++ b/modules/session/Content.qml
@@ -48,8 +48,8 @@ Column {
}
AnimatedImage {
- width: SessionConfig.sizes.button
- height: SessionConfig.sizes.button
+ width: Config.session.sizes.button
+ height: Config.session.sizes.button
sourceSize.width: width
sourceSize.height: height
@@ -84,8 +84,8 @@ Column {
required property string icon
required property list<string> command
- implicitWidth: SessionConfig.sizes.button
- implicitHeight: SessionConfig.sizes.button
+ implicitWidth: Config.session.sizes.button
+ implicitHeight: Config.session.sizes.button
radius: Appearance.rounding.large
color: button.activeFocus ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainer
diff --git a/services/BeatDetector.qml b/services/BeatDetector.qml
index 66bf2d4..65dd88e 100644
--- a/services/BeatDetector.qml
+++ b/services/BeatDetector.qml
@@ -10,9 +10,13 @@ Singleton {
Process {
running: true
- command: [`${Quickshell.shellRoot}/assets/realtime-beat-detector.py`]
+ command: ["/usr/lib/caelestia/beat_detector", "--no-log", "--no-stats", "--no-visual"]
stdout: SplitParser {
- onRead: data => root.bpm = parseFloat(data)
+ onRead: data => {
+ const match = data.match(/BPM: ([0-9]+\.[0-9])/);
+ if (match)
+ root.bpm = parseFloat(match[1]);
+ }
}
}
}
diff --git a/services/Bluetooth.qml b/services/Bluetooth.qml
index 43f3636..f5e9de1 100644
--- a/services/Bluetooth.qml
+++ b/services/Bluetooth.qml
@@ -24,31 +24,47 @@ Singleton {
Process {
id: getInfo
+
running: true
- command: ["sh", "-c", "bluetoothctl show | paste -s"]
- stdout: SplitParser {
- onRead: data => {
- root.powered = data.includes("Powered: yes");
- root.discovering = data.includes("Discovering: yes");
+ command: ["bluetoothctl", "show"]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ root.powered = text.includes("Powered: yes");
+ root.discovering = text.includes("Discovering: yes");
}
}
}
Process {
id: getDevices
+
running: true
- command: ["fish", "-c", `for a in (bluetoothctl devices | cut -d ' ' -f 2); bluetoothctl info $a | jq -R 'reduce (inputs / ":") as [$key, $value] ({}; .[$key | ltrimstr("\t")] = ($value | ltrimstr(" ")))' | jq -c --arg addr $a '.Address = $addr'; end | jq -sc`]
- stdout: SplitParser {
- onRead: data => {
- const devices = JSON.parse(data).filter(d => d.Name);
+ command: ["fish", "-c", `
+ for a in (bluetoothctl devices)
+ if string match -q 'Device *' $a
+ bluetoothctl info $addr (string split ' ' $a)[2]
+ echo
+ end
+ end`]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const devices = text.trim().split("\n\n").map(d => ({
+ name: d.match(/Name: (.*)/)[1],
+ alias: d.match(/Alias: (.*)/)[1],
+ address: d.match(/Device ([0-9A-Z:]{17})/)[1],
+ icon: d.match(/Icon: (.*)/)[1],
+ connected: d.includes("Connected: yes"),
+ paired: d.includes("Paired: yes"),
+ trusted: d.includes("Trusted: yes")
+ }));
const rDevices = root.devices;
- const destroyed = rDevices.filter(rd => !devices.find(d => d.Address === rd.address));
+ const destroyed = rDevices.filter(rd => !devices.find(d => d.address === rd.address));
for (const device of destroyed)
rDevices.splice(rDevices.indexOf(device), 1).forEach(d => d.destroy());
for (const device of devices) {
- const match = rDevices.find(d => d.address === device.Address);
+ const match = rDevices.find(d => d.address === device.address);
if (match) {
match.lastIpcObject = device;
} else {
@@ -63,13 +79,13 @@ Singleton {
component Device: QtObject {
required property var lastIpcObject
- readonly property string name: lastIpcObject.Name
- readonly property string alias: lastIpcObject.Alias
- readonly property string address: lastIpcObject.Address
- readonly property string icon: lastIpcObject.Icon
- readonly property bool connected: lastIpcObject.Connected === "yes"
- readonly property bool paired: lastIpcObject.Paired === "yes"
- readonly property bool trusted: lastIpcObject.Trusted === "yes"
+ readonly property string name: lastIpcObject.name
+ readonly property string alias: lastIpcObject.alias
+ readonly property string address: lastIpcObject.address
+ readonly property string icon: lastIpcObject.icon
+ readonly property bool connected: lastIpcObject.connected
+ readonly property bool paired: lastIpcObject.paired
+ readonly property bool trusted: lastIpcObject.trusted
}
Component {
diff --git a/services/Brightness.qml b/services/Brightness.qml
index 1687878..3715853 100644
--- a/services/Brightness.qml
+++ b/services/Brightness.qml
@@ -9,7 +9,7 @@ import QtQuick
Singleton {
id: root
- property var ddcMonitors: []
+ property list<var> ddcMonitors: []
readonly property list<Monitor> monitors: variants.instances
function getMonitorForScreen(screen: ShellScreen): var {
@@ -49,19 +49,12 @@ Singleton {
id: ddcProc
command: ["ddcutil", "detect", "--brief"]
- stdout: SplitParser {
- splitMarker: "\n\n"
- onRead: data => {
- if (data.startsWith("Display ")) {
- const lines = data.split("\n").map(l => l.trim());
- root.ddcMonitors.push({
- model: lines.find(l => l.startsWith("Monitor:")).split(":")[2],
- busNum: lines.find(l => l.startsWith("I2C bus:")).split("/dev/i2c-")[1]
- });
- }
- }
+ stdout: StdioCollector {
+ onStreamFinished: root.ddcMonitors = text.trim().split("\n\n").filter(d => d.startsWith("Display ")).map(d => ({
+ model: d.match(/Monitor:.*:(.*):.*/)[1],
+ busNum: d.match(/I2C bus:[ ]*\/dev\/i2c-([0-9]+)/)[1]
+ }))
}
- onExited: root.ddcMonitorsChanged()
}
Process {
@@ -87,9 +80,9 @@ Singleton {
property real brightness
readonly property Process initProc: Process {
- stdout: SplitParser {
- onRead: data => {
- const [, , , current, max] = data.split(" ");
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const [, , , current, max] = text.split(" ");
monitor.brightness = parseInt(current) / parseInt(max);
}
}
diff --git a/services/Cava.qml b/services/Cava.qml
index eaa6e20..3fa5083 100644
--- a/services/Cava.qml
+++ b/services/Cava.qml
@@ -11,7 +11,7 @@ Singleton {
Process {
running: true
- command: ["sh", "-c", `printf '[general]\nframerate=60\nbars=${DashboardConfig.visualiserBars}\n[output]\nchannels=mono\nmethod=raw\nraw_target=/dev/stdout\ndata_format=ascii\nascii_max_range=100' | cava -p /dev/stdin`]
+ command: ["sh", "-c", `printf '[general]\nframerate=60\nbars=${Config.dashboard.visualiserBars}\n[output]\nchannels=mono\nmethod=raw\nraw_target=/dev/stdout\ndata_format=ascii\nascii_max_range=100' | cava -p /dev/stdin`]
stdout: SplitParser {
onRead: data => root.values = data.slice(0, -1).split(";").map(v => parseInt(v, 10))
}
diff --git a/services/Colours.qml b/services/Colours.qml
index 4383972..ea3b02a 100644
--- a/services/Colours.qml
+++ b/services/Colours.qml
@@ -36,12 +36,14 @@ Singleton {
function load(data: string, isPreview: bool): void {
const colours = isPreview ? preview : current;
- for (const line of data.trim().split("\n")) {
- let [name, colour] = line.split(" ");
- name = name.trim();
- name = colourNames.includes(name) ? name : `m3${name}`;
- if (colours.hasOwnProperty(name))
- colours[name] = `#${colour.trim()}`;
+ const scheme = JSON.parse(data);
+
+ light = scheme.mode === "light";
+
+ for (const [name, colour] of Object.entries(scheme.colours)) {
+ const propName = colourNames.includes(name) ? name : `m3${name}`;
+ if (colours.hasOwnProperty(propName))
+ colours[propName] = `#${colour}`;
}
if (!isPreview || (isPreview && endPreviewOnNextChange)) {
@@ -51,7 +53,7 @@ Singleton {
}
function setMode(mode: string): void {
- setModeProc.command = ["caelestia", "scheme", "dynamic", "default", mode];
+ setModeProc.command = ["caelestia", "scheme", "-m", mode];
setModeProc.startDetached();
}
@@ -60,14 +62,7 @@ Singleton {
}
FileView {
- path: `${Paths.state}/scheme/current-mode.txt`
- watchChanges: true
- onFileChanged: reload()
- onLoaded: root.light = text() === "light"
- }
-
- FileView {
- path: `${Paths.state}/scheme/current.txt`
+ path: `${Paths.state}/scheme.json`
watchChanges: true
onFileChanged: reload()
onLoaded: root.load(text(), false)
diff --git a/services/Hyprland.qml b/services/Hyprland.qml
index 610fba2..b8fa7c5 100644
--- a/services/Hyprland.qml
+++ b/services/Hyprland.qml
@@ -41,10 +41,10 @@ Singleton {
Process {
id: getClients
- command: ["sh", "-c", "hyprctl -j clients | jq -c"]
- stdout: SplitParser {
- onRead: data => {
- const clients = JSON.parse(data);
+ command: ["hyprctl", "-j", "clients"]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const clients = JSON.parse(text);
const rClients = root.clients;
const destroyed = rClients.filter(rc => !clients.find(c => c.address === rc.address));
@@ -68,10 +68,9 @@ Singleton {
Process {
id: getActiveClient
command: ["hyprctl", "-j", "activewindow"]
- stdout: SplitParser {
- splitMarker: ""
- onRead: data => {
- const client = JSON.parse(data);
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const client = JSON.parse(text);
const rClient = root.activeClient;
if (client.address) {
if (rClient)
diff --git a/services/Network.qml b/services/Network.qml
index b4827c8..6a7f50e 100644
--- a/services/Network.qml
+++ b/services/Network.qml
@@ -23,31 +23,36 @@ Singleton {
Process {
id: getNetworks
running: true
- command: ["sh", "-c", `nmcli -g ACTIVE,SIGNAL,FREQ,SSID,BSSID d w | jq -ncR '[(inputs | split("(?<!\\\\\\\\):"; "g")) | select(.[3] | length >= 4)]'`]
- stdout: SplitParser {
- onRead: data => {
- const networks = JSON.parse(data).map(n => [n[0] === "yes", parseInt(n[1]), parseInt(n[2]), n[3], n[4].replace(/\\/g, "")]);
+ command: ["nmcli", "-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID", "d", "w"]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED";
+ const rep = new RegExp("\\\\:", "g");
+ const rep2 = new RegExp(PLACEHOLDER, "g");
+
+ const networks = text.trim().split("\n").map(n => {
+ const net = n.replace(rep, PLACEHOLDER).split(":");
+ return {
+ active: net[0] === "yes",
+ strength: parseInt(net[1]),
+ frequency: parseInt(net[2]),
+ ssid: net[3],
+ bssid: net[4].replace(rep2, ":")
+ };
+ });
const rNetworks = root.networks;
- const destroyed = rNetworks.filter(rn => !networks.find(n => n[2] === rn.frequency && n[3] === rn.ssid && n[4] === rn.bssid));
+ const destroyed = rNetworks.filter(rn => !networks.find(n => n.frequency === rn.frequency && n.ssid === rn.ssid && n.bssid === rn.bssid));
for (const network of destroyed)
rNetworks.splice(rNetworks.indexOf(network), 1).forEach(n => n.destroy());
for (const network of networks) {
- const match = rNetworks.find(n => n.frequency === network[2] && n.ssid === network[3] && n.bssid === network[4]);
+ const match = rNetworks.find(n => n.frequency === network.frequency && n.ssid === network.ssid && n.bssid === network.bssid);
if (match) {
- match.active = network[0];
- match.strength = network[1];
- match.frequency = network[2];
- match.ssid = network[3];
- match.bssid = network[4];
+ match.lastIpcObject = network;
} else {
rNetworks.push(apComp.createObject(root, {
- active: network[0],
- strength: network[1],
- frequency: network[2],
- ssid: network[3],
- bssid: network[4]
+ lastIpcObject: network
}));
}
}
@@ -56,11 +61,12 @@ Singleton {
}
component AccessPoint: QtObject {
- required property string ssid
- required property string bssid
- required property int strength
- required property int frequency
- required property bool active
+ required property var lastIpcObject
+ readonly property string ssid: lastIpcObject.ssid
+ readonly property string bssid: lastIpcObject.bssid
+ readonly property int strength: lastIpcObject.strength
+ readonly property int frequency: lastIpcObject.frequency
+ readonly property bool active: lastIpcObject.active
}
Component {
diff --git a/services/Notifs.qml b/services/Notifs.qml
index 4acd56f..73d98a2 100644
--- a/services/Notifs.qml
+++ b/services/Notifs.qml
@@ -80,9 +80,9 @@ Singleton {
readonly property Timer timer: Timer {
running: true
- interval: notif.notification.expireTimeout > 0 ? notif.notification.expireTimeout : NotifsConfig.defaultExpireTimeout
+ interval: notif.notification.expireTimeout > 0 ? notif.notification.expireTimeout : Config.notifs.defaultExpireTimeout
onTriggered: {
- if (NotifsConfig.expire)
+ if (Config.notifs.expire)
notif.popup = false;
}
}
diff --git a/services/SystemUsage.qml b/services/SystemUsage.qml
index ef953f1..9bf665a 100644
--- a/services/SystemUsage.qml
+++ b/services/SystemUsage.qml
@@ -98,38 +98,40 @@ Singleton {
running: true
command: ["sh", "-c", "df | grep '^/dev/' | awk '{print $1, $3, $4}'"]
- stdout: SplitParser {
- splitMarker: ""
- onRead: data => {
- const deviceMap = new Map();
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const deviceMap = new Map();
- for (const line of data.trim().split("\n")) {
- if (line.trim() === "") continue;
+ for (const line of text.trim().split("\n")) {
+ if (line.trim() === "")
+ continue;
- const parts = line.trim().split(/\s+/);
- if (parts.length >= 3) {
- const device = parts[0];
- const used = parseInt(parts[1], 10) || 0;
- const avail = parseInt(parts[2], 10) || 0;
-
- // only keep the entry with the largest total space for each device
- if (!deviceMap.has(device) ||
- (used + avail) > (deviceMap.get(device).used + deviceMap.get(device).avail)) {
- deviceMap.set(device, { used: used, avail: avail });
- }
- }
- }
+ const parts = line.trim().split(/\s+/);
+ if (parts.length >= 3) {
+ const device = parts[0];
+ const used = parseInt(parts[1], 10) || 0;
+ const avail = parseInt(parts[2], 10) || 0;
- let totalUsed = 0;
- let totalAvail = 0;
-
- for (const [device, stats] of deviceMap) {
- totalUsed += stats.used;
- totalAvail += stats.avail;
- }
+ // Only keep the entry with the largest total space for each device
+ if (!deviceMap.has(device) || (used + avail) > (deviceMap.get(device).used + deviceMap.get(device).avail)) {
+ deviceMap.set(device, {
+ used: used,
+ avail: avail
+ });
+ }
+ }
+ }
+
+ let totalUsed = 0;
+ let totalAvail = 0;
- root.storageUsed = totalUsed;
- root.storageTotal = totalUsed + totalAvail;
+ for (const [device, stats] of deviceMap) {
+ totalUsed += stats.used;
+ totalAvail += stats.avail;
+ }
+
+ root.storageUsed = totalUsed;
+ root.storageTotal = totalUsed + totalAvail;
}
}
}
@@ -138,10 +140,10 @@ Singleton {
id: cpuTemp
running: true
- command: ["fish", "-c", "cat /sys/class/thermal/thermal_zone*/temp | string join ' '"]
- stdout: SplitParser {
- onRead: data => {
- const temps = data.trim().split(" ");
+ command: ["sh", "-c", "cat /sys/class/thermal/thermal_zone*/temp"]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const temps = text.trim().split(" ");
const sum = temps.reduce((acc, d) => acc + parseInt(d, 10), 0);
root.cpuTemp = sum / temps.length / 1000;
}
@@ -153,10 +155,9 @@ Singleton {
running: true
command: ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"]
- stdout: SplitParser {
- splitMarker: ""
- onRead: data => {
- const percs = data.trim().split("\n");
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const percs = text.trim().split("\n");
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
root.gpuPerc = sum / percs.length / 100;
}
@@ -167,13 +168,14 @@ Singleton {
id: gpuTemp
running: true
- command: ["sh", "-c", "sensors | jq -nRc '[inputs]'"]
- stdout: SplitParser {
- onRead: data => {
+ command: ["sensors"]
+ stdout: StdioCollector {
+ onStreamFinished: {
let eligible = false;
let sum = 0;
let count = 0;
- for (const line of JSON.parse(data)) {
+
+ for (const line of text.trim().split("\n")) {
if (line === "Adapter: PCI adapter")
eligible = true;
else if (line === "")
@@ -186,6 +188,7 @@ Singleton {
}
}
}
+
root.gpuTemp = count > 0 ? sum / count : 0;
}
}
diff --git a/services/Wallpapers.qml b/services/Wallpapers.qml
index 3f6bf15..a667017 100644
--- a/services/Wallpapers.qml
+++ b/services/Wallpapers.qml
@@ -9,8 +9,9 @@ import QtQuick
Singleton {
id: root
- readonly property string currentNamePath: `${Paths.state}/wallpaper/last.txt`.slice(7)
+ readonly property string currentNamePath: `${Paths.state}/wallpaper/path.txt`.slice(7)
readonly property string path: `${Paths.pictures}/Wallpapers`.slice(7)
+ readonly property list<string> extensions: ["jpg", "jpeg", "png", "webp", "tif", "tiff"]
readonly property list<Wallpaper> list: wallpapers.instances
property bool showPreview: false
@@ -77,11 +78,10 @@ Singleton {
Process {
id: getPreviewColoursProc
- command: ["caelestia", "scheme", "print", root.previewPath]
- stdout: SplitParser {
- splitMarker: ""
- onRead: data => {
- Colours.load(data, true);
+ command: ["caelestia", "wallpaper", "-p", root.previewPath]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ Colours.load(text, true);
Colours.showPreview = true;
}
}
@@ -97,10 +97,9 @@ Singleton {
Process {
running: true
- command: ["fd", ".", root.path, "-t", "f", "-e", "jpg", "-e", "jpeg", "-e", "png", "-e", "svg"]
- stdout: SplitParser {
- splitMarker: ""
- onRead: data => wallpapers.model = data.trim().split("\n")
+ command: ["find", root.path, "-type", "d", "-path", '*/.*', "-prune", "-o", "-not", "-name", '.*', "-type", "f", "-print"]
+ stdout: StdioCollector {
+ onStreamFinished: wallpapers.model = text.trim().split("\n").filter(w => root.extensions.includes(w.slice(w.lastIndexOf(".") + 1))).sort()
}
}
diff --git a/services/Weather.qml b/services/Weather.qml
index 13503f9..4f53d0b 100644
--- a/services/Weather.qml
+++ b/services/Weather.qml
@@ -7,6 +7,7 @@ import Quickshell.Io
Singleton {
id: root
+ property string loc
property string icon
property string description
property real temperature
@@ -15,17 +16,28 @@ Singleton {
wttrProc.running = true;
}
+ onLocChanged: wttrProc.running = true
+
Process {
- id: wttrProc
+ id: ipProc
running: true
- command: ["fish", "-c", `curl "https://wttr.in/$(curl ipinfo.io | jq -r '.city' | string replace -a ' ' '%20')?format=j1" | jq -c '.current_condition[0] | {code: .weatherCode, desc: .weatherDesc[0].value, temp: .temp_C}'`]
- stdout: SplitParser {
- onRead: data => {
- const json = JSON.parse(data);
- root.icon = Icons.getWeatherIcon(json.code);
- root.description = json.desc;
- root.temperature = parseFloat(json.temp);
+ command: ["curl", "ipinfo.io"]
+ stdout: StdioCollector {
+ onStreamFinished: root.loc = JSON.parse(text).loc
+ }
+ }
+
+ Process {
+ id: wttrProc
+
+ command: ["curl", `https://wttr.in/${root.loc}?format=j1`]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const json = JSON.parse(text).current_condition[0];
+ root.icon = Icons.getWeatherIcon(json.weatherCode);
+ root.description = json.weatherDesc[0].value;
+ root.temperature = parseFloat(json.temp_C);
}
}
}
diff --git a/utils/Paths.qml b/utils/Paths.qml
index 88267ea..bd57608 100644
--- a/utils/Paths.qml
+++ b/utils/Paths.qml
@@ -13,6 +13,7 @@ Singleton {
readonly property url data: `${StandardPaths.standardLocations(StandardPaths.GenericDataLocation)[0]}/caelestia`
readonly property url state: `${StandardPaths.standardLocations(StandardPaths.GenericStateLocation)[0]}/caelestia`
readonly property url cache: `${StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]}/caelestia`
+ readonly property url config: `${StandardPaths.standardLocations(StandardPaths.GenericConfigLocation)[0]}/caelestia`
readonly property url imagecache: `${cache}/imagecache`
diff --git a/widgets/CachingImage.qml b/widgets/CachingImage.qml
index 3382615..8f7f711 100644
--- a/widgets/CachingImage.qml
+++ b/widgets/CachingImage.qml
@@ -43,8 +43,8 @@ Image {
property string path
command: ["sha256sum", path]
- stdout: SplitParser {
- onRead: data => root.hash = data.split(" ")[0]
+ stdout: StdioCollector {
+ onStreamFinished: root.hash = text.split(" ")[0]
}
}
}