Merge branch 'main' into betteractions
This commit is contained in:
commit
68874082b4
65 changed files with 976 additions and 447 deletions
|
|
@ -31,14 +31,7 @@ https://github.com/user-attachments/assets/0840f496-575c-4ca6-83a8-87bb01a85c5f
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Automated installation (recommended)
|
This is not implemented as of now.
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
|
||||||
568
assets/beat_detector.cpp
Executable file
568
assets/beat_detector.cpp
Executable file
|
|
@ -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
|
||||||
|
*/
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -1,22 +1,14 @@
|
||||||
pragma Singleton
|
import Quickshell.Io
|
||||||
|
|
||||||
import Quickshell
|
JsonObject {
|
||||||
import QtQuick
|
property JsonObject sizes: JsonObject {
|
||||||
|
|
||||||
Singleton {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
readonly property Sizes sizes: Sizes {}
|
|
||||||
readonly property Workspaces workspaces: Workspaces {}
|
|
||||||
|
|
||||||
component Sizes: QtObject {
|
|
||||||
property int innerHeight: 30
|
property int innerHeight: 30
|
||||||
property int windowPreviewSize: 400
|
property int windowPreviewSize: 400
|
||||||
property int trayMenuWidth: 300
|
property int trayMenuWidth: 300
|
||||||
property int batteryWidth: 250
|
property int batteryWidth: 250
|
||||||
}
|
}
|
||||||
|
|
||||||
component Workspaces: QtObject {
|
property JsonObject workspaces: JsonObject {
|
||||||
property int shown: 5
|
property int shown: 5
|
||||||
property bool rounded: true
|
property bool rounded: true
|
||||||
property bool activeIndicator: true
|
property bool activeIndicator: true
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
pragma Singleton
|
|
||||||
|
|
||||||
import "root:/services"
|
import "root:/services"
|
||||||
import Quickshell
|
import Quickshell.Io
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
Singleton {
|
JsonObject {
|
||||||
id: root
|
property color colour: Colours.palette.m3surface
|
||||||
|
property int thickness: Appearance.padding.normal
|
||||||
readonly property color colour: Colours.palette.m3surface
|
property int rounding: Appearance.rounding.large
|
||||||
readonly property int thickness: Appearance.padding.normal
|
|
||||||
readonly property int rounding: Appearance.rounding.large
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
config/Config.qml
Normal file
36
config/Config.qml
Normal file
|
|
@ -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 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
pragma Singleton
|
import Quickshell.Io
|
||||||
|
|
||||||
import Quickshell
|
JsonObject {
|
||||||
import QtQuick
|
property int mediaUpdateInterval: 500
|
||||||
|
property int visualiserBars: 45
|
||||||
|
|
||||||
Singleton {
|
property JsonObject sizes: JsonObject {
|
||||||
readonly property int mediaUpdateInterval: 500
|
|
||||||
readonly property int visualiserBars: 45
|
|
||||||
readonly property Sizes sizes: Sizes {}
|
|
||||||
|
|
||||||
component Sizes: QtObject {
|
|
||||||
readonly property int tabIndicatorHeight: 3
|
readonly property int tabIndicatorHeight: 3
|
||||||
readonly property int tabIndicatorSpacing: 5
|
readonly property int tabIndicatorSpacing: 5
|
||||||
readonly property int infoWidth: 200
|
readonly property int infoWidth: 200
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,15 @@
|
||||||
pragma Singleton
|
import Quickshell.Io
|
||||||
|
|
||||||
import Quickshell
|
JsonObject {
|
||||||
import QtQuick
|
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 {
|
property JsonObject sizes: JsonObject {
|
||||||
readonly property int maxShown: 8
|
property int itemWidth: 600
|
||||||
readonly property int maxWallpapers: 9 // Warning: even numbers look bad
|
property int itemHeight: 57
|
||||||
readonly property string actionPrefix: ">"
|
property int wallpaperWidth: 280
|
||||||
readonly property Sizes sizes: Sizes {}
|
property int wallpaperHeight: 200
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,15 @@
|
||||||
pragma Singleton
|
import Quickshell.Io
|
||||||
|
|
||||||
import Quickshell
|
JsonObject {
|
||||||
import QtQuick
|
property bool expire: false
|
||||||
|
property int defaultExpireTimeout: 3000
|
||||||
|
property real clearThreshold: 0.3
|
||||||
|
property int expandThreshold: 20
|
||||||
|
property bool actionOnClick: false
|
||||||
|
|
||||||
Singleton {
|
property JsonObject sizes: JsonObject {
|
||||||
readonly property bool expire: false
|
property int width: 400
|
||||||
readonly property int defaultExpireTimeout: 3000
|
property int image: 41
|
||||||
readonly property real clearThreshold: 0.3
|
property int badge: 20
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
pragma Singleton
|
import Quickshell.Io
|
||||||
|
|
||||||
import Quickshell
|
JsonObject {
|
||||||
import QtQuick
|
property int hideDelay: 2000
|
||||||
|
|
||||||
Singleton {
|
property JsonObject sizes: JsonObject {
|
||||||
readonly property int hideDelay: 2000
|
property int sliderWidth: 30
|
||||||
readonly property Sizes sizes: Sizes {}
|
property int sliderHeight: 150
|
||||||
|
|
||||||
component Sizes: QtObject {
|
|
||||||
readonly property int sliderWidth: 30
|
|
||||||
readonly property int sliderHeight: 150
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
pragma Singleton
|
import Quickshell.Io
|
||||||
|
|
||||||
import Quickshell
|
JsonObject {
|
||||||
import QtQuick
|
property int dragThreshold: 30
|
||||||
|
|
||||||
Singleton {
|
property JsonObject sizes: JsonObject {
|
||||||
readonly property int dragThreshold: 30
|
property int button: 80
|
||||||
readonly property Sizes sizes: Sizes {}
|
|
||||||
|
|
||||||
component Sizes: QtObject {
|
|
||||||
readonly property int button: 80
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ Item {
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
||||||
implicitWidth: child.implicitWidth + BorderConfig.thickness * 2
|
implicitWidth: child.implicitWidth + Config.border.thickness * 2
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: child
|
id: child
|
||||||
|
|
@ -97,8 +97,8 @@ Item {
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: -BorderConfig.thickness
|
anchors.leftMargin: -Config.border.thickness
|
||||||
anchors.rightMargin: -BorderConfig.thickness
|
anchors.rightMargin: -Config.border.thickness
|
||||||
|
|
||||||
onWheel: event => {
|
onWheel: event => {
|
||||||
const activeWs = Hyprland.activeClient?.workspace?.name;
|
const activeWs = Hyprland.activeClient?.workspace?.name;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ StyledRect {
|
||||||
property real offset: Math.min(leading, trailing)
|
property real offset: Math.min(leading, trailing)
|
||||||
property real size: {
|
property real size: {
|
||||||
const s = Math.abs(leading - trailing) + currentSize;
|
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 Math.min(getWsY(lastWs) + (workspaces[lastWs]?.size ?? 0) - offset, s);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
@ -43,9 +43,9 @@ StyledRect {
|
||||||
clip: true
|
clip: true
|
||||||
x: 1
|
x: 1
|
||||||
y: offset + 1
|
y: offset + 1
|
||||||
implicitWidth: BarConfig.sizes.innerHeight - 2
|
implicitWidth: Config.bar.sizes.innerHeight - 2
|
||||||
implicitHeight: size - 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
|
color: Colours.palette.m3primary
|
||||||
|
|
||||||
StyledRect {
|
StyledRect {
|
||||||
|
|
@ -72,13 +72,13 @@ StyledRect {
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on leading {
|
Behavior on leading {
|
||||||
enabled: BarConfig.workspaces.activeTrail
|
enabled: Config.bar.workspaces.activeTrail
|
||||||
|
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on trailing {
|
Behavior on trailing {
|
||||||
enabled: BarConfig.workspaces.activeTrail
|
enabled: Config.bar.workspaces.activeTrail
|
||||||
|
|
||||||
Anim {
|
Anim {
|
||||||
duration: Appearance.anim.durations.normal * 2
|
duration: Appearance.anim.durations.normal * 2
|
||||||
|
|
@ -86,19 +86,19 @@ StyledRect {
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on currentSize {
|
Behavior on currentSize {
|
||||||
enabled: BarConfig.workspaces.activeTrail
|
enabled: Config.bar.workspaces.activeTrail
|
||||||
|
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on offset {
|
Behavior on offset {
|
||||||
enabled: !BarConfig.workspaces.activeTrail
|
enabled: !Config.bar.workspaces.activeTrail
|
||||||
|
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on size {
|
Behavior on size {
|
||||||
enabled: !BarConfig.workspaces.activeTrail
|
enabled: !Config.bar.workspaces.activeTrail
|
||||||
|
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ Item {
|
||||||
onOccupiedChanged: {
|
onOccupiedChanged: {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
const start = groupOffset;
|
const start = groupOffset;
|
||||||
const end = start + BarConfig.workspaces.shown;
|
const end = start + Config.bar.workspaces.shown;
|
||||||
for (const [ws, occ] of Object.entries(occupied)) {
|
for (const [ws, occ] of Object.entries(occupied)) {
|
||||||
if (ws > start && ws <= end && occ) {
|
if (ws > start && ws <= end && occ) {
|
||||||
if (!occupied[ws - 1]) {
|
if (!occupied[ws - 1]) {
|
||||||
|
|
@ -52,11 +52,11 @@ Item {
|
||||||
readonly property Workspace end: root.workspaces[modelData.end - 1 - root.groupOffset] ?? null
|
readonly property Workspace end: root.workspaces[modelData.end - 1 - root.groupOffset] ?? null
|
||||||
|
|
||||||
color: Colours.alpha(Colours.palette.m3surfaceContainerHigh, true)
|
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
|
x: start?.x ?? 0
|
||||||
y: start?.y ?? 0
|
y: start?.y ?? 0
|
||||||
implicitWidth: BarConfig.sizes.innerHeight
|
implicitWidth: Config.bar.sizes.innerHeight
|
||||||
implicitHeight: end?.y + end?.height - start?.y
|
implicitHeight: end?.y + end?.height - start?.y
|
||||||
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ Item {
|
||||||
|
|
||||||
readonly property int ws: groupOffset + index + 1
|
readonly property int ws: groupOffset + index + 1
|
||||||
readonly property bool isOccupied: occupied[ws] ?? false
|
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.preferredWidth: childrenRect.width
|
||||||
Layout.preferredHeight: size
|
Layout.preferredHeight: size
|
||||||
|
|
@ -27,24 +27,24 @@ Item {
|
||||||
StyledText {
|
StyledText {
|
||||||
id: indicator
|
id: indicator
|
||||||
|
|
||||||
readonly property string label: BarConfig.workspaces.label || root.ws
|
readonly property string label: Config.bar.workspaces.label || root.ws
|
||||||
readonly property string occupiedLabel: BarConfig.workspaces.occupiedLabel || label
|
readonly property string occupiedLabel: Config.bar.workspaces.occupiedLabel || label
|
||||||
readonly property string activeLabel: BarConfig.workspaces.activeLabel || (root.isOccupied ? occupiedLabel : label)
|
readonly property string activeLabel: Config.bar.workspaces.activeLabel || (root.isOccupied ? occupiedLabel : label)
|
||||||
|
|
||||||
animate: true
|
animate: true
|
||||||
text: Hyprland.activeWsId === root.ws ? activeLabel : root.isOccupied ? occupiedLabel : label
|
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
|
horizontalAlignment: StyledText.AlignHCenter
|
||||||
verticalAlignment: StyledText.AlignVCenter
|
verticalAlignment: StyledText.AlignVCenter
|
||||||
|
|
||||||
width: BarConfig.sizes.innerHeight
|
width: Config.bar.sizes.innerHeight
|
||||||
height: BarConfig.sizes.innerHeight
|
height: Config.bar.sizes.innerHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: windows
|
id: windows
|
||||||
|
|
||||||
active: BarConfig.workspaces.showWindows
|
active: Config.bar.workspaces.showWindows
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
anchors.horizontalCenter: indicator.horizontalCenter
|
anchors.horizontalCenter: indicator.horizontalCenter
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ Item {
|
||||||
acc[curr.id] = curr.lastIpcObject.windows > 0;
|
acc[curr.id] = curr.lastIpcObject.windows > 0;
|
||||||
return acc;
|
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
|
implicitWidth: layout.implicitWidth
|
||||||
implicitHeight: layout.implicitHeight
|
implicitHeight: layout.implicitHeight
|
||||||
|
|
@ -27,7 +27,7 @@ Item {
|
||||||
layer.smooth: true
|
layer.smooth: true
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: BarConfig.workspaces.shown
|
model: Config.bar.workspaces.shown
|
||||||
|
|
||||||
Workspace {
|
Workspace {
|
||||||
occupied: root.occupied
|
occupied: root.occupied
|
||||||
|
|
@ -37,7 +37,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
active: BarConfig.workspaces.occupiedBg
|
active: Config.bar.workspaces.occupiedBg
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
z: -1
|
z: -1
|
||||||
|
|
@ -51,7 +51,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
active: BarConfig.workspaces.activeIndicator
|
active: Config.bar.workspaces.activeIndicator
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
sourceComponent: ActiveIndicator {
|
sourceComponent: ActiveIndicator {
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ Item {
|
||||||
captureSource: ToplevelManager.toplevels.values.find(t => t.title === Hyprland.activeClient?.title) ?? null
|
captureSource: ToplevelManager.toplevels.values.find(t => t.title === Hyprland.activeClient?.title) ?? null
|
||||||
live: visible
|
live: visible
|
||||||
|
|
||||||
constraintSize.width: BarConfig.sizes.windowPreviewSize
|
constraintSize.width: Config.bar.sizes.windowPreviewSize
|
||||||
constraintSize.height: BarConfig.sizes.windowPreviewSize
|
constraintSize.height: Config.bar.sizes.windowPreviewSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ ShapePath {
|
||||||
|
|
||||||
required property Wrapper wrapper
|
required property Wrapper wrapper
|
||||||
required property bool invertBottomRounding
|
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 bool flatten: wrapper.width < rounding * 2
|
||||||
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
|
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
|
||||||
property real ibr: invertBottomRounding ? -1 : 1
|
property real ibr: invertBottomRounding ? -1 : 1
|
||||||
|
|
||||||
strokeWidth: -1
|
strokeWidth: -1
|
||||||
fillColor: BorderConfig.colour
|
fillColor: Config.border.colour
|
||||||
|
|
||||||
PathArc {
|
PathArc {
|
||||||
relativeX: root.roundingX
|
relativeX: root.roundingX
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
spacing: Appearance.spacing.normal
|
spacing: Appearance.spacing.normal
|
||||||
width: BarConfig.sizes.batteryWidth
|
width: Config.bar.sizes.batteryWidth
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: UPower.displayDevice.isLaptopBattery ? qsTr("Remaining: %1%").arg(Math.round(UPower.displayDevice.percentage * 100)) : qsTr("No battery detected")
|
text: UPower.displayDevice.isLaptopBattery ? qsTr("Remaining: %1%").arg(Math.round(UPower.displayDevice.percentage * 100)) : qsTr("No battery detected")
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ StackView {
|
||||||
|
|
||||||
required property QsMenuEntry modelData
|
required property QsMenuEntry modelData
|
||||||
|
|
||||||
implicitWidth: BarConfig.sizes.trayMenuWidth
|
implicitWidth: Config.bar.sizes.trayMenuWidth
|
||||||
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
|
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
|
||||||
|
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
|
|
@ -153,7 +153,7 @@ StackView {
|
||||||
font.family: label.font.family
|
font.family: label.font.family
|
||||||
|
|
||||||
elide: Text.ElideRight
|
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 {
|
Loader {
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ ShapePath {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Wrapper wrapper
|
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 bool flatten: wrapper.height < rounding * 2
|
||||||
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
|
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
|
||||||
|
|
||||||
strokeWidth: -1
|
strokeWidth: -1
|
||||||
fillColor: BorderConfig.colour
|
fillColor: Config.border.colour
|
||||||
|
|
||||||
PathArc {
|
PathArc {
|
||||||
relativeX: root.rounding
|
relativeX: root.rounding
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ GridLayout {
|
||||||
Rect {
|
Rect {
|
||||||
Layout.row: 0
|
Layout.row: 0
|
||||||
Layout.columnSpan: 2
|
Layout.columnSpan: 2
|
||||||
Layout.preferredWidth: DashboardConfig.sizes.weatherWidth
|
Layout.preferredWidth: Config.dashboard.sizes.weatherWidth
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
Weather {}
|
Weather {}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ Item {
|
||||||
return `${Math.floor(length / 60)}:${Math.floor(length % 60).toString().padStart(2, "0")}`;
|
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
|
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 + DashboardConfig.sizes.mediaVisualiserSize * 2, details.implicitHeight, bongocat.implicitHeight) + 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 {
|
Behavior on playerProgress {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
|
|
@ -41,7 +41,7 @@ Item {
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
running: root.shouldUpdate && (Players.active?.isPlaying ?? false)
|
running: root.shouldUpdate && (Players.active?.isPlaying ?? false)
|
||||||
interval: DashboardConfig.mediaUpdateInterval
|
interval: Config.dashboard.mediaUpdateInterval
|
||||||
triggeredOnStart: true
|
triggeredOnStart: true
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: Players.active?.positionChanged()
|
onTriggered: Players.active?.positionChanged()
|
||||||
|
|
@ -66,7 +66,7 @@ Item {
|
||||||
property color colour: Colours.palette.m3primary
|
property color colour: Colours.palette.m3primary
|
||||||
|
|
||||||
anchors.fill: cover
|
anchors.fill: cover
|
||||||
anchors.margins: -DashboardConfig.sizes.mediaVisualiserSize
|
anchors.margins: -Config.dashboard.sizes.mediaVisualiserSize
|
||||||
|
|
||||||
onColourChanged: requestPaint()
|
onColourChanged: requestPaint()
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ Item {
|
||||||
ctx.lineWidth = 360 / len - Appearance.spacing.small / 4;
|
ctx.lineWidth = 360 / len - Appearance.spacing.small / 4;
|
||||||
ctx.lineCap = "round";
|
ctx.lineCap = "round";
|
||||||
|
|
||||||
const size = DashboardConfig.sizes.mediaVisualiserSize;
|
const size = Config.dashboard.sizes.mediaVisualiserSize;
|
||||||
const cx = centerX;
|
const cx = centerX;
|
||||||
const cy = centerY;
|
const cy = centerY;
|
||||||
const rx = innerX + ctx.lineWidth / 2;
|
const rx = innerX + ctx.lineWidth / 2;
|
||||||
|
|
@ -116,10 +116,10 @@ Item {
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
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
|
implicitWidth: Config.dashboard.sizes.mediaCoverArtSize
|
||||||
implicitHeight: DashboardConfig.sizes.mediaCoverArtSize
|
implicitHeight: Config.dashboard.sizes.mediaCoverArtSize
|
||||||
|
|
||||||
color: Colours.palette.m3surfaceContainerHigh
|
color: Colours.palette.m3surfaceContainerHigh
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ Row {
|
||||||
property bool primary
|
property bool primary
|
||||||
readonly property real primaryMult: primary ? 1.2 : 1
|
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 fg1: Colours.palette.m3primary
|
||||||
property color fg2: Colours.palette.m3secondary
|
property color fg2: Colours.palette.m3secondary
|
||||||
|
|
@ -74,8 +74,8 @@ Row {
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
implicitWidth: DashboardConfig.sizes.resourceSize * primaryMult
|
implicitWidth: Config.dashboard.sizes.resourceSize * primaryMult
|
||||||
implicitHeight: DashboardConfig.sizes.resourceSize * primaryMult
|
implicitHeight: Config.dashboard.sizes.resourceSize * primaryMult
|
||||||
|
|
||||||
onValue1Changed: canvas.requestPaint()
|
onValue1Changed: canvas.requestPaint()
|
||||||
onValue2Changed: canvas.requestPaint()
|
onValue2Changed: canvas.requestPaint()
|
||||||
|
|
|
||||||
|
|
@ -48,14 +48,14 @@ Item {
|
||||||
id: indicator
|
id: indicator
|
||||||
|
|
||||||
anchors.top: bar.bottom
|
anchors.top: bar.bottom
|
||||||
anchors.topMargin: DashboardConfig.sizes.tabIndicatorSpacing
|
anchors.topMargin: Config.dashboard.sizes.tabIndicatorSpacing
|
||||||
|
|
||||||
implicitWidth: bar.currentItem.implicitWidth
|
implicitWidth: bar.currentItem.implicitWidth
|
||||||
implicitHeight: DashboardConfig.sizes.tabIndicatorHeight
|
implicitHeight: Config.dashboard.sizes.tabIndicatorHeight
|
||||||
|
|
||||||
x: {
|
x: {
|
||||||
const tab = bar.currentItem;
|
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;
|
return width * tab.TabBar.index + (width - tab.implicitWidth) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +69,6 @@ Item {
|
||||||
|
|
||||||
color: Colours.palette.m3primary
|
color: Colours.palette.m3primary
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
|
|
@ -108,14 +107,17 @@ Item {
|
||||||
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
onPressed: ({x,y}) => {
|
onPressed: ({
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
}) => {
|
||||||
tab.TabBar.tabBar.setCurrentIndex(tab.TabBar.index);
|
tab.TabBar.tabBar.setCurrentIndex(tab.TabBar.index);
|
||||||
|
|
||||||
const stateY = stateWrapper.y;
|
const stateY = stateWrapper.y;
|
||||||
rippleAnim.x = x;
|
rippleAnim.x = x;
|
||||||
rippleAnim.y = y - stateY;
|
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;
|
const stateEndY = stateY + stateWrapper.height;
|
||||||
rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)));
|
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.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
implicitHeight: parent.height + DashboardConfig.sizes.tabIndicatorSpacing * 2
|
implicitHeight: parent.height + Config.dashboard.sizes.tabIndicatorSpacing * 2
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
radius: Appearance.rounding.small
|
radius: Appearance.rounding.small
|
||||||
|
|
@ -237,7 +239,6 @@ Item {
|
||||||
text: tab.text
|
text: tab.text
|
||||||
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ Item {
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
implicitWidth: DashboardConfig.sizes.dateTimeWidth
|
implicitWidth: Config.dashboard.sizes.dateTimeWidth
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: hours
|
id: hours
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ Item {
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
implicitWidth: DashboardConfig.sizes.mediaWidth
|
implicitWidth: Config.dashboard.sizes.mediaWidth
|
||||||
|
|
||||||
Behavior on playerProgress {
|
Behavior on playerProgress {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
|
|
@ -31,7 +31,7 @@ Item {
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
running: root.shouldUpdate && (Players.active?.isPlaying ?? false)
|
running: root.shouldUpdate && (Players.active?.isPlaying ?? false)
|
||||||
interval: DashboardConfig.mediaUpdateInterval
|
interval: Config.dashboard.mediaUpdateInterval
|
||||||
triggeredOnStart: true
|
triggeredOnStart: true
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: Players.active?.positionChanged()
|
onTriggered: Players.active?.positionChanged()
|
||||||
|
|
@ -43,16 +43,16 @@ Item {
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: "transparent"
|
fillColor: "transparent"
|
||||||
strokeColor: Colours.palette.m3surfaceContainerHigh
|
strokeColor: Colours.palette.m3surfaceContainerHigh
|
||||||
strokeWidth: DashboardConfig.sizes.mediaProgressThickness
|
strokeWidth: Config.dashboard.sizes.mediaProgressThickness
|
||||||
capStyle: ShapePath.RoundCap
|
capStyle: ShapePath.RoundCap
|
||||||
|
|
||||||
PathAngleArc {
|
PathAngleArc {
|
||||||
centerX: cover.x + cover.width / 2
|
centerX: cover.x + cover.width / 2
|
||||||
centerY: cover.y + cover.height / 2
|
centerY: cover.y + cover.height / 2
|
||||||
radiusX: (cover.width + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
radiusX: (cover.width + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
||||||
radiusY: (cover.height + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
radiusY: (cover.height + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
||||||
startAngle: -90 - DashboardConfig.sizes.mediaProgressSweep / 2
|
startAngle: -90 - Config.dashboard.sizes.mediaProgressSweep / 2
|
||||||
sweepAngle: DashboardConfig.sizes.mediaProgressSweep
|
sweepAngle: Config.dashboard.sizes.mediaProgressSweep
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on strokeColor {
|
Behavior on strokeColor {
|
||||||
|
|
@ -67,16 +67,16 @@ Item {
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: "transparent"
|
fillColor: "transparent"
|
||||||
strokeColor: Colours.palette.m3primary
|
strokeColor: Colours.palette.m3primary
|
||||||
strokeWidth: DashboardConfig.sizes.mediaProgressThickness
|
strokeWidth: Config.dashboard.sizes.mediaProgressThickness
|
||||||
capStyle: ShapePath.RoundCap
|
capStyle: ShapePath.RoundCap
|
||||||
|
|
||||||
PathAngleArc {
|
PathAngleArc {
|
||||||
centerX: cover.x + cover.width / 2
|
centerX: cover.x + cover.width / 2
|
||||||
centerY: cover.y + cover.height / 2
|
centerY: cover.y + cover.height / 2
|
||||||
radiusX: (cover.width + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
radiusX: (cover.width + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
||||||
radiusY: (cover.height + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
radiusY: (cover.height + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
||||||
startAngle: -90 - DashboardConfig.sizes.mediaProgressSweep / 2
|
startAngle: -90 - Config.dashboard.sizes.mediaProgressSweep / 2
|
||||||
sweepAngle: DashboardConfig.sizes.mediaProgressSweep * root.playerProgress
|
sweepAngle: Config.dashboard.sizes.mediaProgressSweep * root.playerProgress
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on strokeColor {
|
Behavior on strokeColor {
|
||||||
|
|
@ -95,7 +95,7 @@ Item {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
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
|
implicitHeight: width
|
||||||
color: Colours.palette.m3surfaceContainerHigh
|
color: Colours.palette.m3surfaceContainerHigh
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ Row {
|
||||||
anchors.bottom: icon.top
|
anchors.bottom: icon.top
|
||||||
anchors.bottomMargin: Appearance.spacing.small
|
anchors.bottomMargin: Appearance.spacing.small
|
||||||
|
|
||||||
implicitWidth: DashboardConfig.sizes.resourceProgessThickness
|
implicitWidth: Config.dashboard.sizes.resourceProgessThickness
|
||||||
|
|
||||||
color: Colours.palette.m3surfaceContainerHigh
|
color: Colours.palette.m3surfaceContainerHigh
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ Row {
|
||||||
Column {
|
Column {
|
||||||
id: info
|
id: info
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Appearance.spacing.normal
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
InfoLine {
|
InfoLine {
|
||||||
|
|
@ -69,8 +70,8 @@ Row {
|
||||||
|
|
||||||
running: true
|
running: true
|
||||||
command: ["uptime", "-p"]
|
command: ["uptime", "-p"]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
onRead: data => uptimeProc.uptime = data
|
onStreamFinished: uptimeProc.uptime = text.trim()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +91,7 @@ Row {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: (DashboardConfig.sizes.infoIconSize - implicitWidth) / 2
|
anchors.leftMargin: (Config.dashboard.sizes.infoIconSize - implicitWidth) / 2
|
||||||
|
|
||||||
text: line.icon
|
text: line.icon
|
||||||
color: line.colour
|
color: line.colour
|
||||||
|
|
@ -109,7 +110,7 @@ Row {
|
||||||
text: `: ${line.text}`
|
text: `: ${line.text}`
|
||||||
font.pointSize: Appearance.font.size.normal
|
font.pointSize: Appearance.font.size.normal
|
||||||
|
|
||||||
width: DashboardConfig.sizes.infoWidth
|
width: Config.dashboard.sizes.infoWidth
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ Shape {
|
||||||
required property Item bar
|
required property Item bar
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: BorderConfig.thickness
|
anchors.margins: Config.border.thickness
|
||||||
anchors.leftMargin: bar.implicitWidth
|
anchors.leftMargin: bar.implicitWidth
|
||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
opacity: Colours.transparency.enabled ? Colours.transparency.base : 1
|
opacity: Colours.transparency.enabled ? Colours.transparency.base : 1
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ Item {
|
||||||
id: rect
|
id: rect
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Colours.alpha(BorderConfig.colour, false)
|
color: Colours.alpha(Config.border.colour, false)
|
||||||
visible: false
|
visible: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,9 +29,9 @@ Item {
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: BorderConfig.thickness
|
anchors.margins: Config.border.thickness
|
||||||
anchors.leftMargin: root.bar.implicitWidth
|
anchors.leftMargin: root.bar.implicitWidth
|
||||||
radius: BorderConfig.rounding
|
radius: Config.border.rounding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ Variants {
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
x: bar.implicitWidth
|
x: bar.implicitWidth
|
||||||
y: BorderConfig.thickness
|
y: Config.border.thickness
|
||||||
width: win.width - bar.implicitWidth - BorderConfig.thickness
|
width: win.width - bar.implicitWidth - Config.border.thickness
|
||||||
height: win.height - BorderConfig.thickness * 2
|
height: win.height - Config.border.thickness * 2
|
||||||
intersection: Intersection.Xor
|
intersection: Intersection.Xor
|
||||||
|
|
||||||
regions: regions.instances
|
regions: regions.instances
|
||||||
|
|
@ -55,7 +55,7 @@ Variants {
|
||||||
required property Item modelData
|
required property Item modelData
|
||||||
|
|
||||||
x: modelData.x + bar.implicitWidth
|
x: modelData.x + bar.implicitWidth
|
||||||
y: modelData.y + BorderConfig.thickness
|
y: modelData.y + Config.border.thickness
|
||||||
width: modelData.width
|
width: modelData.width
|
||||||
height: modelData.height
|
height: modelData.height
|
||||||
intersection: Intersection.Subtract
|
intersection: Intersection.Subtract
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ Scope {
|
||||||
component ExclusionZone: StyledWindow {
|
component ExclusionZone: StyledWindow {
|
||||||
screen: root.screen
|
screen: root.screen
|
||||||
name: "border-exclusion"
|
name: "border-exclusion"
|
||||||
exclusiveZone: BorderConfig.thickness
|
exclusiveZone: Config.border.thickness
|
||||||
|
mask: Region {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ MouseArea {
|
||||||
property bool osdShortcutActive
|
property bool osdShortcutActive
|
||||||
|
|
||||||
function withinPanelHeight(panel: Item, x: real, y: real): bool {
|
function withinPanelHeight(panel: Item, x: real, y: real): bool {
|
||||||
const panelY = BorderConfig.thickness + panel.y;
|
const panelY = Config.border.thickness + panel.y;
|
||||||
return y >= panelY - BorderConfig.rounding && y <= panelY + panel.height + BorderConfig.rounding;
|
return y >= panelY - Config.border.rounding && y <= panelY + panel.height + Config.border.rounding;
|
||||||
}
|
}
|
||||||
|
|
||||||
function inRightPanel(panel: Item, x: real, y: real): bool {
|
function inRightPanel(panel: Item, x: real, y: real): bool {
|
||||||
|
|
@ -30,7 +30,7 @@ MouseArea {
|
||||||
|
|
||||||
function inTopPanel(panel: Item, x: real, y: real): bool {
|
function inTopPanel(panel: Item, x: real, y: real): bool {
|
||||||
const panelX = bar.implicitWidth + panel.x;
|
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
|
anchors.fill: parent
|
||||||
|
|
@ -51,7 +51,10 @@ MouseArea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onPositionChanged: ({x, y}) => {
|
onPositionChanged: ({
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
}) => {
|
||||||
// Show osd on hover
|
// Show osd on hover
|
||||||
const showOsd = inRightPanel(panels.osd, x, y);
|
const showOsd = inRightPanel(panels.osd, x, y);
|
||||||
|
|
||||||
|
|
@ -68,9 +71,9 @@ MouseArea {
|
||||||
// Show/hide session on drag
|
// Show/hide session on drag
|
||||||
if (pressed && withinPanelHeight(panels.session, x, y)) {
|
if (pressed && withinPanelHeight(panels.session, x, y)) {
|
||||||
const dragX = x - dragStart.x;
|
const dragX = x - dragStart.x;
|
||||||
if (dragX < -SessionConfig.dragThreshold)
|
if (dragX < -Config.session.dragThreshold)
|
||||||
visibilities.session = true;
|
visibilities.session = true;
|
||||||
else if (dragX > SessionConfig.dragThreshold)
|
else if (dragX > Config.session.dragThreshold)
|
||||||
visibilities.session = false;
|
visibilities.session = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ Item {
|
||||||
readonly property BarPopouts.Wrapper popouts: popouts
|
readonly property BarPopouts.Wrapper popouts: popouts
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: BorderConfig.thickness
|
anchors.margins: Config.border.thickness
|
||||||
anchors.leftMargin: bar.implicitWidth
|
anchors.leftMargin: bar.implicitWidth
|
||||||
|
|
||||||
Component.onCompleted: Visibilities.panels[screen] = this
|
Component.onCompleted: Visibilities.panels[screen] = this
|
||||||
|
|
@ -83,7 +83,7 @@ Item {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.verticalCenter: parent.top
|
anchors.verticalCenter: parent.top
|
||||||
anchors.verticalCenterOffset: {
|
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);
|
const diff = root.height - Math.floor(off + implicitHeight / 2);
|
||||||
if (diff < 0)
|
if (diff < 0)
|
||||||
return off + diff;
|
return off + diff;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Item {
|
||||||
required property Actions.Action modelData
|
required property Actions.Action modelData
|
||||||
required property var list
|
required property var list
|
||||||
|
|
||||||
implicitHeight: LauncherConfig.sizes.itemHeight
|
implicitHeight: Config.launcher.sizes.itemHeight
|
||||||
|
|
||||||
anchors.left: parent?.left
|
anchors.left: parent?.left
|
||||||
anchors.right: parent?.right
|
anchors.right: parent?.right
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ Singleton {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
function fuzzyQuery(search: string): var {
|
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,
|
all: true,
|
||||||
keys: ["name", "desc"],
|
keys: ["name", "desc"],
|
||||||
scoreFn: r => r[0].score > 0 ? r[0].score * 0.9 + r[1].score * 0.1 : 0
|
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 {
|
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 {
|
function handleDangerousAction(list: AppList, process: QtObject): void {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Item {
|
||||||
required property DesktopEntry modelData
|
required property DesktopEntry modelData
|
||||||
required property PersistentProperties visibilities
|
required property PersistentProperties visibilities
|
||||||
|
|
||||||
implicitHeight: LauncherConfig.sizes.itemHeight
|
implicitHeight: Config.launcher.sizes.itemHeight
|
||||||
|
|
||||||
anchors.left: parent?.left
|
anchors.left: parent?.left
|
||||||
anchors.right: parent?.right
|
anchors.right: parent?.right
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@ ListView {
|
||||||
required property TextField search
|
required property TextField search
|
||||||
required property PersistentProperties visibilities
|
required property PersistentProperties visibilities
|
||||||
|
|
||||||
property bool isAction: search.text.startsWith(LauncherConfig.actionPrefix)
|
property bool isAction: search.text.startsWith(Config.launcher.actionPrefix)
|
||||||
|
|
||||||
function getModelValues() {
|
function getModelValues() {
|
||||||
let text = search.text;
|
let text = search.text;
|
||||||
if (isAction)
|
if (isAction)
|
||||||
return Actions.fuzzyQuery(text);
|
return Actions.fuzzyQuery(text);
|
||||||
if (text.startsWith(LauncherConfig.actionPrefix))
|
if (text.startsWith(Config.launcher.actionPrefix))
|
||||||
text = search.text.slice(LauncherConfig.actionPrefix.length);
|
text = search.text.slice(Config.launcher.actionPrefix.length);
|
||||||
return Apps.fuzzyQuery(text);
|
return Apps.fuzzyQuery(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ ListView {
|
||||||
|
|
||||||
spacing: Appearance.spacing.small
|
spacing: Appearance.spacing.small
|
||||||
orientation: Qt.Vertical
|
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
|
highlightMoveDuration: Appearance.anim.durations.normal
|
||||||
highlightResizeDuration: 0
|
highlightResizeDuration: 0
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ ShapePath {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Wrapper wrapper
|
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 bool flatten: wrapper.height < rounding * 2
|
||||||
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
|
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
|
||||||
|
|
||||||
strokeWidth: -1
|
strokeWidth: -1
|
||||||
fillColor: BorderConfig.colour
|
fillColor: Config.border.colour
|
||||||
|
|
||||||
PathArc {
|
PathArc {
|
||||||
relativeX: root.rounding
|
relativeX: root.rounding
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ Item {
|
||||||
topPadding: Appearance.padding.larger
|
topPadding: Appearance.padding.larger
|
||||||
bottomPadding: 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
|
background: null
|
||||||
|
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
|
|
@ -83,7 +83,7 @@ Item {
|
||||||
if (list.showWallpapers) {
|
if (list.showWallpapers) {
|
||||||
Wallpapers.setWallpaper(currentItem.modelData.path);
|
Wallpapers.setWallpaper(currentItem.modelData.path);
|
||||||
root.visibilities.launcher = false;
|
root.visibilities.launcher = false;
|
||||||
} else if (text.startsWith(LauncherConfig.actionPrefix)) {
|
} else if (text.startsWith(Config.launcher.actionPrefix)) {
|
||||||
currentItem.modelData.onClicked(list.currentList);
|
currentItem.modelData.onClicked(list.currentList);
|
||||||
} else {
|
} else {
|
||||||
Apps.launch(currentItem.modelData);
|
Apps.launch(currentItem.modelData);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ Item {
|
||||||
required property int padding
|
required property int padding
|
||||||
required property int rounding
|
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
|
property var currentList: (showWallpapers ? wallpaperList : appList).item
|
||||||
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
@ -29,7 +29,7 @@ Item {
|
||||||
name: "apps"
|
name: "apps"
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
root.implicitWidth: LauncherConfig.sizes.itemWidth
|
root.implicitWidth: Config.launcher.sizes.itemWidth
|
||||||
root.implicitHeight: Math.max(empty.height, appList.height)
|
root.implicitHeight: Math.max(empty.height, appList.height)
|
||||||
appList.active: true
|
appList.active: true
|
||||||
}
|
}
|
||||||
|
|
@ -43,8 +43,8 @@ Item {
|
||||||
name: "wallpapers"
|
name: "wallpapers"
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
root.implicitWidth: Math.max(LauncherConfig.sizes.itemWidth, wallpaperList.width)
|
root.implicitWidth: Math.max(Config.launcher.sizes.itemWidth, wallpaperList.width)
|
||||||
root.implicitHeight: LauncherConfig.sizes.wallpaperHeight
|
root.implicitHeight: Config.launcher.sizes.wallpaperHeight
|
||||||
wallpaperList.active: true
|
wallpaperList.active: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ StyledRect {
|
||||||
path: root.modelData.path
|
path: root.modelData.path
|
||||||
smooth: !root.PathView.view.moving
|
smooth: !root.PathView.view.moving
|
||||||
|
|
||||||
width: LauncherConfig.sizes.wallpaperWidth
|
width: Config.launcher.sizes.wallpaperWidth
|
||||||
height: width / 16 * 9
|
height: width / 16 * 9
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ PathView {
|
||||||
const screenWidth = QsWindow.window?.screen.width * 0.8;
|
const screenWidth = QsWindow.window?.screen.width * 0.8;
|
||||||
if (!screenWidth)
|
if (!screenWidth)
|
||||||
return 0;
|
return 0;
|
||||||
const itemWidth = LauncherConfig.sizes.wallpaperWidth * 0.8;
|
const itemWidth = Config.launcher.sizes.wallpaperWidth * 0.8;
|
||||||
const max = LauncherConfig.maxWallpapers;
|
const max = Config.launcher.maxWallpapers;
|
||||||
if (max * itemWidth > screenWidth) {
|
if (max * itemWidth > screenWidth) {
|
||||||
const items = Math.floor(screenWidth / itemWidth);
|
const items = Math.floor(screenWidth / itemWidth);
|
||||||
return items > 1 && items % 2 === 0 ? items - 1 : items;
|
return items > 1 && items % 2 === 0 ? items - 1 : items;
|
||||||
|
|
@ -43,7 +43,7 @@ PathView {
|
||||||
Wallpapers.preview(currentItem.modelData.path);
|
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
|
pathItemCount: numItems
|
||||||
cacheItemCount: 4
|
cacheItemCount: 4
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ ShapePath {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Wrapper wrapper
|
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 bool flatten: wrapper.height < rounding * 2
|
||||||
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
|
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
|
strokeWidth: -1
|
||||||
fillColor: BorderConfig.colour
|
fillColor: Config.border.colour
|
||||||
|
|
||||||
PathLine {
|
PathLine {
|
||||||
relativeX: -(root.wrapper.width + root.rounding)
|
relativeX: -(root.wrapper.width + root.rounding)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ Item {
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
implicitWidth: NotifsConfig.sizes.width + padding * 2
|
implicitWidth: Config.notifs.sizes.width + padding * 2
|
||||||
implicitHeight: {
|
implicitHeight: {
|
||||||
const count = list.count;
|
const count = list.count;
|
||||||
if (count === 0)
|
if (count === 0)
|
||||||
|
|
@ -29,19 +29,19 @@ Item {
|
||||||
const panel = Visibilities.panels[screen];
|
const panel = Visibilities.panels[screen];
|
||||||
if (visibilities && panel) {
|
if (visibilities && panel) {
|
||||||
if (visibilities.osd) {
|
if (visibilities.osd) {
|
||||||
const h = panel.osd.y - BorderConfig.rounding * 2;
|
const h = panel.osd.y - Config.border.rounding * 2;
|
||||||
if (height > h)
|
if (height > h)
|
||||||
height = h;
|
height = h;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (visibilities.session) {
|
if (visibilities.session) {
|
||||||
const h = panel.session.y - BorderConfig.rounding * 2;
|
const h = panel.session.y - Config.border.rounding * 2;
|
||||||
if (height > h)
|
if (height > h)
|
||||||
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 {
|
ClippingWrapperRectangle {
|
||||||
|
|
@ -108,7 +108,7 @@ Item {
|
||||||
Anim {
|
Anim {
|
||||||
target: notif
|
target: notif
|
||||||
property: "x"
|
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
|
duration: Appearance.anim.durations.normal
|
||||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ StyledRect {
|
||||||
|
|
||||||
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainer
|
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainer
|
||||||
radius: Appearance.rounding.normal
|
radius: Appearance.rounding.normal
|
||||||
implicitWidth: NotifsConfig.sizes.width
|
implicitWidth: Config.notifs.sizes.width
|
||||||
implicitHeight: inner.implicitHeight
|
implicitHeight: inner.implicitHeight
|
||||||
|
|
||||||
x: NotifsConfig.sizes.width
|
x: Config.notifs.sizes.width
|
||||||
Component.onCompleted: x = 0
|
Component.onCompleted: x = 0
|
||||||
|
|
||||||
RetainableLock {
|
RetainableLock {
|
||||||
|
|
@ -52,7 +52,7 @@ StyledRect {
|
||||||
root.modelData.notification.dismiss();
|
root.modelData.notification.dismiss();
|
||||||
}
|
}
|
||||||
onReleased: event => {
|
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;
|
root.x = 0;
|
||||||
else
|
else
|
||||||
root.modelData.popup = false;
|
root.modelData.popup = false;
|
||||||
|
|
@ -60,12 +60,12 @@ StyledRect {
|
||||||
onPositionChanged: event => {
|
onPositionChanged: event => {
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
const diffY = event.y - startY;
|
const diffY = event.y - startY;
|
||||||
if (Math.abs(diffY) > NotifsConfig.expandThreshold)
|
if (Math.abs(diffY) > Config.notifs.expandThreshold)
|
||||||
root.expanded = diffY > 0;
|
root.expanded = diffY > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onClicked: event => {
|
onClicked: event => {
|
||||||
if (!NotifsConfig.actionOnClick || event.button !== Qt.LeftButton)
|
if (!Config.notifs.actionOnClick || event.button !== Qt.LeftButton)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const actions = root.modelData.actions;
|
const actions = root.modelData.actions;
|
||||||
|
|
@ -93,7 +93,10 @@ StyledRect {
|
||||||
implicitHeight: root.nonAnimHeight
|
implicitHeight: root.nonAnimHeight
|
||||||
|
|
||||||
Behavior on implicitHeight {
|
Behavior on implicitHeight {
|
||||||
Anim {}
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
|
|
@ -104,14 +107,14 @@ StyledRect {
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
width: NotifsConfig.sizes.image
|
width: Config.notifs.sizes.image
|
||||||
height: NotifsConfig.sizes.image
|
height: Config.notifs.sizes.image
|
||||||
visible: root.hasImage || root.hasAppIcon
|
visible: root.hasImage || root.hasAppIcon
|
||||||
|
|
||||||
sourceComponent: ClippingRectangle {
|
sourceComponent: ClippingRectangle {
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
implicitWidth: NotifsConfig.sizes.image
|
implicitWidth: Config.notifs.sizes.image
|
||||||
implicitHeight: NotifsConfig.sizes.image
|
implicitHeight: Config.notifs.sizes.image
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
@ -137,8 +140,8 @@ StyledRect {
|
||||||
sourceComponent: StyledRect {
|
sourceComponent: StyledRect {
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3surfaceContainerHighest : Colours.palette.m3tertiaryContainer
|
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
|
implicitWidth: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
|
||||||
implicitHeight: root.hasImage ? NotifsConfig.sizes.badge : NotifsConfig.sizes.image
|
implicitHeight: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: icon
|
id: icon
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ ShapePath {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Wrapper wrapper
|
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 bool flatten: wrapper.width < rounding * 2
|
||||||
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
|
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
|
||||||
|
|
||||||
strokeWidth: -1
|
strokeWidth: -1
|
||||||
fillColor: BorderConfig.colour
|
fillColor: Config.border.colour
|
||||||
|
|
||||||
PathArc {
|
PathArc {
|
||||||
relativeX: -root.roundingX
|
relativeX: -root.roundingX
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ Column {
|
||||||
value: Audio.volume
|
value: Audio.volume
|
||||||
onMoved: Audio.setVolume(value)
|
onMoved: Audio.setVolume(value)
|
||||||
|
|
||||||
implicitWidth: OsdConfig.sizes.sliderWidth
|
implicitWidth: Config.osd.sizes.sliderWidth
|
||||||
implicitHeight: OsdConfig.sizes.sliderHeight
|
implicitHeight: Config.osd.sizes.sliderHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
VerticalSlider {
|
VerticalSlider {
|
||||||
|
|
@ -37,7 +37,7 @@ Column {
|
||||||
value: root.monitor?.brightness ?? 0
|
value: root.monitor?.brightness ?? 0
|
||||||
onMoved: root.monitor?.setBrightness(value)
|
onMoved: root.monitor?.setBrightness(value)
|
||||||
|
|
||||||
implicitWidth: OsdConfig.sizes.sliderWidth
|
implicitWidth: Config.osd.sizes.sliderWidth
|
||||||
implicitHeight: OsdConfig.sizes.sliderHeight
|
implicitHeight: Config.osd.sizes.sliderHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ Scope {
|
||||||
Timer {
|
Timer {
|
||||||
id: timer
|
id: timer
|
||||||
|
|
||||||
interval: OsdConfig.hideDelay
|
interval: Config.osd.hideDelay
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!root.hovered)
|
if (!root.hovered)
|
||||||
root.visibilities.osd = false;
|
root.visibilities.osd = false;
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ ShapePath {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Wrapper wrapper
|
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 bool flatten: wrapper.width < rounding * 2
|
||||||
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
|
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
|
||||||
|
|
||||||
strokeWidth: -1
|
strokeWidth: -1
|
||||||
fillColor: BorderConfig.colour
|
fillColor: Config.border.colour
|
||||||
|
|
||||||
PathArc {
|
PathArc {
|
||||||
relativeX: -root.roundingX
|
relativeX: -root.roundingX
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ Column {
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedImage {
|
AnimatedImage {
|
||||||
width: SessionConfig.sizes.button
|
width: Config.session.sizes.button
|
||||||
height: SessionConfig.sizes.button
|
height: Config.session.sizes.button
|
||||||
sourceSize.width: width
|
sourceSize.width: width
|
||||||
sourceSize.height: height
|
sourceSize.height: height
|
||||||
|
|
||||||
|
|
@ -84,8 +84,8 @@ Column {
|
||||||
required property string icon
|
required property string icon
|
||||||
required property list<string> command
|
required property list<string> command
|
||||||
|
|
||||||
implicitWidth: SessionConfig.sizes.button
|
implicitWidth: Config.session.sizes.button
|
||||||
implicitHeight: SessionConfig.sizes.button
|
implicitHeight: Config.session.sizes.button
|
||||||
|
|
||||||
radius: Appearance.rounding.large
|
radius: Appearance.rounding.large
|
||||||
color: button.activeFocus ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainer
|
color: button.activeFocus ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainer
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,13 @@ Singleton {
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
running: true
|
running: true
|
||||||
command: [`${Quickshell.shellRoot}/assets/realtime-beat-detector.py`]
|
command: ["/usr/lib/caelestia/beat_detector", "--no-log", "--no-stats", "--no-visual"]
|
||||||
stdout: SplitParser {
|
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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,31 +24,47 @@ Singleton {
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: getInfo
|
id: getInfo
|
||||||
|
|
||||||
running: true
|
running: true
|
||||||
command: ["sh", "-c", "bluetoothctl show | paste -s"]
|
command: ["bluetoothctl", "show"]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
onRead: data => {
|
onStreamFinished: {
|
||||||
root.powered = data.includes("Powered: yes");
|
root.powered = text.includes("Powered: yes");
|
||||||
root.discovering = data.includes("Discovering: yes");
|
root.discovering = text.includes("Discovering: yes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: getDevices
|
id: getDevices
|
||||||
|
|
||||||
running: true
|
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`]
|
command: ["fish", "-c", `
|
||||||
stdout: SplitParser {
|
for a in (bluetoothctl devices)
|
||||||
onRead: data => {
|
if string match -q 'Device *' $a
|
||||||
const devices = JSON.parse(data).filter(d => d.Name);
|
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 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)
|
for (const device of destroyed)
|
||||||
rDevices.splice(rDevices.indexOf(device), 1).forEach(d => d.destroy());
|
rDevices.splice(rDevices.indexOf(device), 1).forEach(d => d.destroy());
|
||||||
|
|
||||||
for (const device of devices) {
|
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) {
|
if (match) {
|
||||||
match.lastIpcObject = device;
|
match.lastIpcObject = device;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -63,13 +79,13 @@ Singleton {
|
||||||
|
|
||||||
component Device: QtObject {
|
component Device: QtObject {
|
||||||
required property var lastIpcObject
|
required property var lastIpcObject
|
||||||
readonly property string name: lastIpcObject.Name
|
readonly property string name: lastIpcObject.name
|
||||||
readonly property string alias: lastIpcObject.Alias
|
readonly property string alias: lastIpcObject.alias
|
||||||
readonly property string address: lastIpcObject.Address
|
readonly property string address: lastIpcObject.address
|
||||||
readonly property string icon: lastIpcObject.Icon
|
readonly property string icon: lastIpcObject.icon
|
||||||
readonly property bool connected: lastIpcObject.Connected === "yes"
|
readonly property bool connected: lastIpcObject.connected
|
||||||
readonly property bool paired: lastIpcObject.Paired === "yes"
|
readonly property bool paired: lastIpcObject.paired
|
||||||
readonly property bool trusted: lastIpcObject.Trusted === "yes"
|
readonly property bool trusted: lastIpcObject.trusted
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import QtQuick
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var ddcMonitors: []
|
property list<var> ddcMonitors: []
|
||||||
readonly property list<Monitor> monitors: variants.instances
|
readonly property list<Monitor> monitors: variants.instances
|
||||||
|
|
||||||
function getMonitorForScreen(screen: ShellScreen): var {
|
function getMonitorForScreen(screen: ShellScreen): var {
|
||||||
|
|
@ -49,19 +49,12 @@ Singleton {
|
||||||
id: ddcProc
|
id: ddcProc
|
||||||
|
|
||||||
command: ["ddcutil", "detect", "--brief"]
|
command: ["ddcutil", "detect", "--brief"]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
splitMarker: "\n\n"
|
onStreamFinished: root.ddcMonitors = text.trim().split("\n\n").filter(d => d.startsWith("Display ")).map(d => ({
|
||||||
onRead: data => {
|
model: d.match(/Monitor:.*:(.*):.*/)[1],
|
||||||
if (data.startsWith("Display ")) {
|
busNum: d.match(/I2C bus:[ ]*\/dev\/i2c-([0-9]+)/)[1]
|
||||||
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]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
onExited: root.ddcMonitorsChanged()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
|
|
@ -87,9 +80,9 @@ Singleton {
|
||||||
property real brightness
|
property real brightness
|
||||||
|
|
||||||
readonly property Process initProc: Process {
|
readonly property Process initProc: Process {
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
onRead: data => {
|
onStreamFinished: {
|
||||||
const [, , , current, max] = data.split(" ");
|
const [, , , current, max] = text.split(" ");
|
||||||
monitor.brightness = parseInt(current) / parseInt(max);
|
monitor.brightness = parseInt(current) / parseInt(max);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Singleton {
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
running: true
|
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 {
|
stdout: SplitParser {
|
||||||
onRead: data => root.values = data.slice(0, -1).split(";").map(v => parseInt(v, 10))
|
onRead: data => root.values = data.slice(0, -1).split(";").map(v => parseInt(v, 10))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,14 @@ Singleton {
|
||||||
|
|
||||||
function load(data: string, isPreview: bool): void {
|
function load(data: string, isPreview: bool): void {
|
||||||
const colours = isPreview ? preview : current;
|
const colours = isPreview ? preview : current;
|
||||||
for (const line of data.trim().split("\n")) {
|
const scheme = JSON.parse(data);
|
||||||
let [name, colour] = line.split(" ");
|
|
||||||
name = name.trim();
|
light = scheme.mode === "light";
|
||||||
name = colourNames.includes(name) ? name : `m3${name}`;
|
|
||||||
if (colours.hasOwnProperty(name))
|
for (const [name, colour] of Object.entries(scheme.colours)) {
|
||||||
colours[name] = `#${colour.trim()}`;
|
const propName = colourNames.includes(name) ? name : `m3${name}`;
|
||||||
|
if (colours.hasOwnProperty(propName))
|
||||||
|
colours[propName] = `#${colour}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isPreview || (isPreview && endPreviewOnNextChange)) {
|
if (!isPreview || (isPreview && endPreviewOnNextChange)) {
|
||||||
|
|
@ -51,7 +53,7 @@ Singleton {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMode(mode: string): void {
|
function setMode(mode: string): void {
|
||||||
setModeProc.command = ["caelestia", "scheme", "dynamic", "default", mode];
|
setModeProc.command = ["caelestia", "scheme", "-m", mode];
|
||||||
setModeProc.startDetached();
|
setModeProc.startDetached();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,14 +62,7 @@ Singleton {
|
||||||
}
|
}
|
||||||
|
|
||||||
FileView {
|
FileView {
|
||||||
path: `${Paths.state}/scheme/current-mode.txt`
|
path: `${Paths.state}/scheme.json`
|
||||||
watchChanges: true
|
|
||||||
onFileChanged: reload()
|
|
||||||
onLoaded: root.light = text() === "light"
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
path: `${Paths.state}/scheme/current.txt`
|
|
||||||
watchChanges: true
|
watchChanges: true
|
||||||
onFileChanged: reload()
|
onFileChanged: reload()
|
||||||
onLoaded: root.load(text(), false)
|
onLoaded: root.load(text(), false)
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,10 @@ Singleton {
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: getClients
|
id: getClients
|
||||||
command: ["sh", "-c", "hyprctl -j clients | jq -c"]
|
command: ["hyprctl", "-j", "clients"]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
onRead: data => {
|
onStreamFinished: {
|
||||||
const clients = JSON.parse(data);
|
const clients = JSON.parse(text);
|
||||||
const rClients = root.clients;
|
const rClients = root.clients;
|
||||||
|
|
||||||
const destroyed = rClients.filter(rc => !clients.find(c => c.address === rc.address));
|
const destroyed = rClients.filter(rc => !clients.find(c => c.address === rc.address));
|
||||||
|
|
@ -68,10 +68,9 @@ Singleton {
|
||||||
Process {
|
Process {
|
||||||
id: getActiveClient
|
id: getActiveClient
|
||||||
command: ["hyprctl", "-j", "activewindow"]
|
command: ["hyprctl", "-j", "activewindow"]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
splitMarker: ""
|
onStreamFinished: {
|
||||||
onRead: data => {
|
const client = JSON.parse(text);
|
||||||
const client = JSON.parse(data);
|
|
||||||
const rClient = root.activeClient;
|
const rClient = root.activeClient;
|
||||||
if (client.address) {
|
if (client.address) {
|
||||||
if (rClient)
|
if (rClient)
|
||||||
|
|
|
||||||
|
|
@ -23,31 +23,36 @@ Singleton {
|
||||||
Process {
|
Process {
|
||||||
id: getNetworks
|
id: getNetworks
|
||||||
running: true
|
running: true
|
||||||
command: ["sh", "-c", `nmcli -g ACTIVE,SIGNAL,FREQ,SSID,BSSID d w | jq -ncR '[(inputs | split("(?<!\\\\\\\\):"; "g")) | select(.[3] | length >= 4)]'`]
|
command: ["nmcli", "-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID", "d", "w"]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
onRead: data => {
|
onStreamFinished: {
|
||||||
const networks = JSON.parse(data).map(n => [n[0] === "yes", parseInt(n[1]), parseInt(n[2]), n[3], n[4].replace(/\\/g, "")]);
|
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 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)
|
for (const network of destroyed)
|
||||||
rNetworks.splice(rNetworks.indexOf(network), 1).forEach(n => n.destroy());
|
rNetworks.splice(rNetworks.indexOf(network), 1).forEach(n => n.destroy());
|
||||||
|
|
||||||
for (const network of networks) {
|
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) {
|
if (match) {
|
||||||
match.active = network[0];
|
match.lastIpcObject = network;
|
||||||
match.strength = network[1];
|
|
||||||
match.frequency = network[2];
|
|
||||||
match.ssid = network[3];
|
|
||||||
match.bssid = network[4];
|
|
||||||
} else {
|
} else {
|
||||||
rNetworks.push(apComp.createObject(root, {
|
rNetworks.push(apComp.createObject(root, {
|
||||||
active: network[0],
|
lastIpcObject: network
|
||||||
strength: network[1],
|
|
||||||
frequency: network[2],
|
|
||||||
ssid: network[3],
|
|
||||||
bssid: network[4]
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,11 +61,12 @@ Singleton {
|
||||||
}
|
}
|
||||||
|
|
||||||
component AccessPoint: QtObject {
|
component AccessPoint: QtObject {
|
||||||
required property string ssid
|
required property var lastIpcObject
|
||||||
required property string bssid
|
readonly property string ssid: lastIpcObject.ssid
|
||||||
required property int strength
|
readonly property string bssid: lastIpcObject.bssid
|
||||||
required property int frequency
|
readonly property int strength: lastIpcObject.strength
|
||||||
required property bool active
|
readonly property int frequency: lastIpcObject.frequency
|
||||||
|
readonly property bool active: lastIpcObject.active
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,9 @@ Singleton {
|
||||||
|
|
||||||
readonly property Timer timer: Timer {
|
readonly property Timer timer: Timer {
|
||||||
running: true
|
running: true
|
||||||
interval: notif.notification.expireTimeout > 0 ? notif.notification.expireTimeout : NotifsConfig.defaultExpireTimeout
|
interval: notif.notification.expireTimeout > 0 ? notif.notification.expireTimeout : Config.notifs.defaultExpireTimeout
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (NotifsConfig.expire)
|
if (Config.notifs.expire)
|
||||||
notif.popup = false;
|
notif.popup = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,38 +98,40 @@ Singleton {
|
||||||
|
|
||||||
running: true
|
running: true
|
||||||
command: ["sh", "-c", "df | grep '^/dev/' | awk '{print $1, $3, $4}'"]
|
command: ["sh", "-c", "df | grep '^/dev/' | awk '{print $1, $3, $4}'"]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
splitMarker: ""
|
onStreamFinished: {
|
||||||
onRead: data => {
|
const deviceMap = new Map();
|
||||||
const deviceMap = new Map();
|
|
||||||
|
|
||||||
for (const line of data.trim().split("\n")) {
|
for (const line of text.trim().split("\n")) {
|
||||||
if (line.trim() === "") continue;
|
if (line.trim() === "")
|
||||||
|
continue;
|
||||||
|
|
||||||
const parts = line.trim().split(/\s+/);
|
const parts = line.trim().split(/\s+/);
|
||||||
if (parts.length >= 3) {
|
if (parts.length >= 3) {
|
||||||
const device = parts[0];
|
const device = parts[0];
|
||||||
const used = parseInt(parts[1], 10) || 0;
|
const used = parseInt(parts[1], 10) || 0;
|
||||||
const avail = parseInt(parts[2], 10) || 0;
|
const avail = parseInt(parts[2], 10) || 0;
|
||||||
|
|
||||||
// only keep the entry with the largest total space for each device
|
// Only keep the entry with the largest total space for each device
|
||||||
if (!deviceMap.has(device) ||
|
if (!deviceMap.has(device) || (used + avail) > (deviceMap.get(device).used + deviceMap.get(device).avail)) {
|
||||||
(used + avail) > (deviceMap.get(device).used + deviceMap.get(device).avail)) {
|
deviceMap.set(device, {
|
||||||
deviceMap.set(device, { used: used, avail: avail });
|
used: used,
|
||||||
}
|
avail: avail
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let totalUsed = 0;
|
let totalUsed = 0;
|
||||||
let totalAvail = 0;
|
let totalAvail = 0;
|
||||||
|
|
||||||
for (const [device, stats] of deviceMap) {
|
for (const [device, stats] of deviceMap) {
|
||||||
totalUsed += stats.used;
|
totalUsed += stats.used;
|
||||||
totalAvail += stats.avail;
|
totalAvail += stats.avail;
|
||||||
}
|
}
|
||||||
|
|
||||||
root.storageUsed = totalUsed;
|
root.storageUsed = totalUsed;
|
||||||
root.storageTotal = totalUsed + totalAvail;
|
root.storageTotal = totalUsed + totalAvail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -138,10 +140,10 @@ Singleton {
|
||||||
id: cpuTemp
|
id: cpuTemp
|
||||||
|
|
||||||
running: true
|
running: true
|
||||||
command: ["fish", "-c", "cat /sys/class/thermal/thermal_zone*/temp | string join ' '"]
|
command: ["sh", "-c", "cat /sys/class/thermal/thermal_zone*/temp"]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
onRead: data => {
|
onStreamFinished: {
|
||||||
const temps = data.trim().split(" ");
|
const temps = text.trim().split(" ");
|
||||||
const sum = temps.reduce((acc, d) => acc + parseInt(d, 10), 0);
|
const sum = temps.reduce((acc, d) => acc + parseInt(d, 10), 0);
|
||||||
root.cpuTemp = sum / temps.length / 1000;
|
root.cpuTemp = sum / temps.length / 1000;
|
||||||
}
|
}
|
||||||
|
|
@ -153,10 +155,9 @@ Singleton {
|
||||||
|
|
||||||
running: true
|
running: true
|
||||||
command: ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"]
|
command: ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
splitMarker: ""
|
onStreamFinished: {
|
||||||
onRead: data => {
|
const percs = text.trim().split("\n");
|
||||||
const percs = data.trim().split("\n");
|
|
||||||
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
|
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
|
||||||
root.gpuPerc = sum / percs.length / 100;
|
root.gpuPerc = sum / percs.length / 100;
|
||||||
}
|
}
|
||||||
|
|
@ -167,13 +168,14 @@ Singleton {
|
||||||
id: gpuTemp
|
id: gpuTemp
|
||||||
|
|
||||||
running: true
|
running: true
|
||||||
command: ["sh", "-c", "sensors | jq -nRc '[inputs]'"]
|
command: ["sensors"]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
onRead: data => {
|
onStreamFinished: {
|
||||||
let eligible = false;
|
let eligible = false;
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (const line of JSON.parse(data)) {
|
|
||||||
|
for (const line of text.trim().split("\n")) {
|
||||||
if (line === "Adapter: PCI adapter")
|
if (line === "Adapter: PCI adapter")
|
||||||
eligible = true;
|
eligible = true;
|
||||||
else if (line === "")
|
else if (line === "")
|
||||||
|
|
@ -186,6 +188,7 @@ Singleton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
root.gpuTemp = count > 0 ? sum / count : 0;
|
root.gpuTemp = count > 0 ? sum / count : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ import QtQuick
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
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 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
|
readonly property list<Wallpaper> list: wallpapers.instances
|
||||||
property bool showPreview: false
|
property bool showPreview: false
|
||||||
|
|
@ -77,11 +78,10 @@ Singleton {
|
||||||
Process {
|
Process {
|
||||||
id: getPreviewColoursProc
|
id: getPreviewColoursProc
|
||||||
|
|
||||||
command: ["caelestia", "scheme", "print", root.previewPath]
|
command: ["caelestia", "wallpaper", "-p", root.previewPath]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
splitMarker: ""
|
onStreamFinished: {
|
||||||
onRead: data => {
|
Colours.load(text, true);
|
||||||
Colours.load(data, true);
|
|
||||||
Colours.showPreview = true;
|
Colours.showPreview = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,10 +97,9 @@ Singleton {
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
running: true
|
running: true
|
||||||
command: ["fd", ".", root.path, "-t", "f", "-e", "jpg", "-e", "jpeg", "-e", "png", "-e", "svg"]
|
command: ["find", root.path, "-type", "d", "-path", '*/.*', "-prune", "-o", "-not", "-name", '.*', "-type", "f", "-print"]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
splitMarker: ""
|
onStreamFinished: wallpapers.model = text.trim().split("\n").filter(w => root.extensions.includes(w.slice(w.lastIndexOf(".") + 1))).sort()
|
||||||
onRead: data => wallpapers.model = data.trim().split("\n")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import Quickshell.Io
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property string loc
|
||||||
property string icon
|
property string icon
|
||||||
property string description
|
property string description
|
||||||
property real temperature
|
property real temperature
|
||||||
|
|
@ -15,17 +16,28 @@ Singleton {
|
||||||
wttrProc.running = true;
|
wttrProc.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onLocChanged: wttrProc.running = true
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: ipProc
|
||||||
|
|
||||||
|
running: true
|
||||||
|
command: ["curl", "ipinfo.io"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: root.loc = JSON.parse(text).loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: wttrProc
|
id: wttrProc
|
||||||
|
|
||||||
running: true
|
command: ["curl", `https://wttr.in/${root.loc}?format=j1`]
|
||||||
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: StdioCollector {
|
||||||
stdout: SplitParser {
|
onStreamFinished: {
|
||||||
onRead: data => {
|
const json = JSON.parse(text).current_condition[0];
|
||||||
const json = JSON.parse(data);
|
root.icon = Icons.getWeatherIcon(json.weatherCode);
|
||||||
root.icon = Icons.getWeatherIcon(json.code);
|
root.description = json.weatherDesc[0].value;
|
||||||
root.description = json.desc;
|
root.temperature = parseFloat(json.temp_C);
|
||||||
root.temperature = parseFloat(json.temp);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ Singleton {
|
||||||
readonly property url data: `${StandardPaths.standardLocations(StandardPaths.GenericDataLocation)[0]}/caelestia`
|
readonly property url data: `${StandardPaths.standardLocations(StandardPaths.GenericDataLocation)[0]}/caelestia`
|
||||||
readonly property url state: `${StandardPaths.standardLocations(StandardPaths.GenericStateLocation)[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 cache: `${StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]}/caelestia`
|
||||||
|
readonly property url config: `${StandardPaths.standardLocations(StandardPaths.GenericConfigLocation)[0]}/caelestia`
|
||||||
|
|
||||||
readonly property url imagecache: `${cache}/imagecache`
|
readonly property url imagecache: `${cache}/imagecache`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,8 @@ Image {
|
||||||
property string path
|
property string path
|
||||||
|
|
||||||
command: ["sha256sum", path]
|
command: ["sha256sum", path]
|
||||||
stdout: SplitParser {
|
stdout: StdioCollector {
|
||||||
onRead: data => root.hash = data.split(" ")[0]
|
onStreamFinished: root.hash = text.split(" ")[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue