summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/default.nix7
-rw-r--r--docs/guide/libraries/apps.md25
-rw-r--r--docs/guide/libraries/cava.md91
-rw-r--r--docs/vitepress.config.ts1
-rw-r--r--flake.nix4
-rw-r--r--lib/apps/application.vala52
-rw-r--r--lib/apps/apps.vala58
-rw-r--r--lib/astal/io/cli.vala4
-rw-r--r--lib/auth/src/pam.c1
-rw-r--r--lib/cava/.gitignore1
-rw-r--r--lib/cava/astal-cava.h70
-rw-r--r--lib/cava/cava.c659
-rw-r--r--lib/cava/meson.build80
-rw-r--r--lib/cava/meson_options.txt13
-rw-r--r--lib/cava/subprojects/cava.wrap7
-rw-r--r--lib/cava/version1
-rw-r--r--lib/mpris/mpris.vala10
-rw-r--r--lib/mpris/player.vala72
-rw-r--r--lib/notifd/notification.vala12
-rw-r--r--lib/notifd/proxy.vala4
-rw-r--r--lib/notifd/signals.md2
-rw-r--r--nix/libcava.nix60
22 files changed, 1118 insertions, 116 deletions
diff --git a/docs/default.nix b/docs/default.nix
index 0d79ce1..1370fc6 100644
--- a/docs/default.nix
+++ b/docs/default.nix
@@ -154,6 +154,13 @@ in
version = ../lib/bluetooth/version;
}}
${genLib {
+ flakepkg = "cava";
+ gir = "Cava";
+ description = "Audio visualization library using cava";
+ version = ../lib/cava/version;
+ authors = "kotontrion";
+ }}
+ ${genLib {
flakepkg = "hyprland";
gir = "Hyprland";
description = "IPC client for Hyprland";
diff --git a/docs/guide/libraries/apps.md b/docs/guide/libraries/apps.md
index 7349951..1871d18 100644
--- a/docs/guide/libraries/apps.md
+++ b/docs/guide/libraries/apps.md
@@ -55,8 +55,9 @@ astal-apps --help
import Apps from "gi://AstalApps"
const apps = new Apps.Apps({
- includeEntry: true,
- includeExecutable: true,
+ nameMultiplier: 2,
+ entryMultiplier: 0,
+ executableMultiplier: 2,
})
for (const app of apps.fuzzy_query("spotify")) {
@@ -68,8 +69,9 @@ for (const app of apps.fuzzy_query("spotify")) {
from gi.repository import AstalApps as Apps
apps = Apps.Apps(
- include_entry=True,
- include_executable=True,
+ name_multiplier=2,
+ entry_multiplier=0,
+ executable_multiplier=2,
)
for app in apps.fuzzy_query("obsidian"):
@@ -81,8 +83,9 @@ for app in apps.fuzzy_query("obsidian"):
local Apps = require("lgi").require("AstalApps")
local apps = Apps.Apps({
- include_entry = true,
- include_executable = true,
+ name_multiplier = 2,
+ entry_multiplier = 0,
+ executable_multiplier = 2,
})
for _, app in ipairs(apps:fuzzy_query("lutris")) do
@@ -91,7 +94,15 @@ end
```
```vala [<i class="devicon-vala-plain"></i> Vala]
-// Not yet documented, contributions are appreciated
+var apps = new AstalApps.Apps() {
+ name_multiplier = 2,
+ entry_multiplier = 0,
+ executable_multiplier = 2,
+};
+
+foreach (var app in apps.fuzzy_query("firefox")) {
+ print(app.name);
+}
```
:::
diff --git a/docs/guide/libraries/cava.md b/docs/guide/libraries/cava.md
new file mode 100644
index 0000000..e695e16
--- /dev/null
+++ b/docs/guide/libraries/cava.md
@@ -0,0 +1,91 @@
+# Cava
+
+Audio visualizer using [cava](https://github.com/karlstav/cava).
+
+## Installation
+
+1. install dependencies
+
+Note that it requires [libcava](https://github.com/LukashonakV/cava), a fork of cava, which provides cava as a shared library.
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson vala gobject-introspection
+paru -S libcava
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+# Not yet documented
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+# Not yet documented
+```
+
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/cava
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [Cava reference](https://aylur.github.io/libastal/cava).
+
+### CLI
+
+There is no CLI for this library, use the one provided by cava.
+
+```sh
+cava
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import Cava from "gi://AstalCava"
+
+const cava = Cava.get_default()
+
+cava.connect("notify::values", () => {
+ print(cava.get_values())
+})
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+# Not yet documented
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+-- Not yet documented
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::
diff --git a/docs/vitepress.config.ts b/docs/vitepress.config.ts
index 2df3eea..f542a68 100644
--- a/docs/vitepress.config.ts
+++ b/docs/vitepress.config.ts
@@ -104,6 +104,7 @@ export default defineConfig({
{ text: "Auth", link: "/guide/libraries/auth" },
{ text: "Battery", link: "/guide/libraries/battery" },
{ text: "Bluetooth", link: "/guide/libraries/bluetooth" },
+ { text: "Cava", link: "/guide/libraries/cava" },
{ text: "Hyprland", link: "/guide/libraries/hyprland" },
{ text: "Mpris", link: "/guide/libraries/mpris" },
{ text: "Network", link: "/guide/libraries/network" },
diff --git a/flake.nix b/flake.nix
index 7f644bd..2f7189b 100644
--- a/flake.nix
+++ b/flake.nix
@@ -53,6 +53,7 @@
auth = mkPkg "astal-auth" ./lib/auth [pam];
battery = mkPkg "astal-battery" ./lib/battery [json-glib];
bluetooth = mkPkg "astal-bluetooth" ./lib/bluetooth [];
+ cava = mkPkg "astal-cava" ./lib/cava [(pkgs.callPackage ./nix/libcava.nix {})];
hyprland = mkPkg "astal-hyprland" ./lib/hyprland [json-glib];
mpris = mkPkg "astal-mpris" ./lib/mpris [gvfs json-glib];
network = mkPkg "astal-network" ./lib/network [networkmanager];
@@ -61,12 +62,11 @@
river = mkPkg "astal-river" ./lib/river [json-glib];
tray = mkPkg "astal-tray" ./lib/tray [gtk3 gdk-pixbuf libdbusmenu-gtk3 json-glib];
wireplumber = mkPkg "astal-wireplumber" ./lib/wireplumber [wireplumber];
- # polkit = mkPkg "astal-polkit" ./lib/polkit [polkit];
gjs = pkgs.stdenvNoCC.mkDerivation {
src = ./lang/gjs;
name = "astal-gjs";
- buildInputs = [
+ nativeBuildInputs = [
meson
ninja
pkg-config
diff --git a/lib/apps/application.vala b/lib/apps/application.vala
index 0a2f73c..3a9900a 100644
--- a/lib/apps/application.vala
+++ b/lib/apps/application.vala
@@ -1,3 +1,6 @@
+/**
+ * Object representing an applications .desktop file.
+ */
public class AstalApps.Application : Object {
/**
* The underlying DesktopAppInfo.
@@ -47,6 +50,20 @@ public class AstalApps.Application : Object {
*/
public string[] keywords { owned get { return app.get_keywords(); } }
+ /**
+ * `Categories` field from the desktop file.
+ */
+ public string[] categories {
+ owned get {
+ if (app.get_categories() == null)
+ return {};
+
+ var categories = app.get_categories();
+ var arr = categories.split(";");
+ return categories.has_suffix(";") ? arr[0:arr.length-1] : arr;
+ }
+ }
+
internal Application(string id, int? frequency = 0) {
Object(app: new DesktopAppInfo(id));
this.frequency = frequency;
@@ -94,6 +111,12 @@ public class AstalApps.Application : Object {
score.keywords = s;
}
}
+ foreach (var category in categories) {
+ var s = fuzzy_match_string(term, category);
+ if (s > score.categories) {
+ score.categories = s;
+ }
+ }
return score;
}
@@ -117,12 +140,27 @@ public class AstalApps.Application : Object {
score.keywords = keyword.down().contains(term.down()) ? 1 : 0;
}
}
+ foreach (var category in categories) {
+ if (score.categories == 0) {
+ score.categories = category.down().contains(term.down()) ? 1 : 0;
+ }
+ }
return score;
}
internal Json.Node to_json() {
- var builder = new Json.Builder()
+ var keyword_arr = new Json.Builder().begin_array();
+ foreach (string keyword in keywords) {
+ keyword_arr.add_string_value(keyword);
+ }
+
+ var category_arr = new Json.Builder().begin_array();
+ foreach (string category in categories) {
+ category_arr.add_string_value(category);
+ }
+
+ return new Json.Builder()
.begin_object()
.set_member_name("name").add_string_value(name)
.set_member_name("entry").add_string_value(entry)
@@ -130,15 +168,8 @@ public class AstalApps.Application : Object {
.set_member_name("description").add_string_value(description)
.set_member_name("icon_name").add_string_value(icon_name)
.set_member_name("frequency").add_int_value(frequency)
- .set_member_name("keywords")
- .begin_array();
-
- foreach (string keyword in keywords) {
- builder.add_string_value(keyword);
- }
-
- return builder
- .end_array()
+ .set_member_name("keywords").add_value(keyword_arr.end_array().get_root())
+ .set_member_name("categories").add_value(category_arr.end_array().get_root())
.end_object()
.get_root();
}
@@ -150,4 +181,5 @@ public struct AstalApps.Score {
int executable;
int description;
int keywords;
+ int categories;
}
diff --git a/lib/apps/apps.vala b/lib/apps/apps.vala
index dde7d44..999643c 100644
--- a/lib/apps/apps.vala
+++ b/lib/apps/apps.vala
@@ -1,3 +1,8 @@
+/**
+ * This object can be used to query applications.
+ * Multipliers can be set to customize [[email protected]] results
+ * from queries which then are summed and sorted accordingly.
+ */
public class AstalApps.Apps : Object {
private string cache_directory;
private string cache_file;
@@ -27,21 +32,21 @@ public class AstalApps.Apps : Object {
/**
* Extra multiplier to apply when matching the entry of an application.
- * Defaults to `1`
+ * Defaults to `0`
*/
- public double entry_multiplier { get; set; default = 1; }
+ public double entry_multiplier { get; set; default = 0; }
/**
* Extra multiplier to apply when matching the executable of an application.
- * Defaults to `1`
+ * Defaults to `0.5`
*/
- public double executable_multiplier { get; set; default = 1; }
+ public double executable_multiplier { get; set; default = 0.5; }
/**
* Extra multiplier to apply when matching the description of an application.
- * Defaults to `0.5`
+ * Defaults to `0`
*/
- public double description_multiplier { get; set; default = 0.5; }
+ public double description_multiplier { get; set; default = 0; }
/**
* Extra multiplier to apply when matching the keywords of an application.
@@ -50,34 +55,10 @@ public class AstalApps.Apps : Object {
public double keywords_multiplier { get; set; default = 0.5; }
/**
- * Consider the name of an application during queries.
- * Defaults to `true`
- */
- public bool include_name { get; set; default = true; }
-
- /**
- * Consider the entry of an application during queries.
- * Defaults to `false`
- */
- public bool include_entry { get; set; default = false; }
-
- /**
- * Consider the executable of an application during queries.
- * Defaults to `false`
- */
- public bool include_executable { get; set; default = false; }
-
- /**
- * Consider the description of an application during queries.
- * Defaults to `false`
- */
- public bool include_description { get; set; default = false; }
-
- /**
- * Consider the keywords of an application during queries.
- * Defaults to `false`
+ * Extra multiplier to apply when matching the categories of an application.
+ * Defaults to `0`
*/
- public bool include_keywords { get; set; default = false; }
+ public double categories_multiplier { get; set; default = 0; }
construct {
cache_directory = Environment.get_user_cache_dir() + "/astal";
@@ -115,11 +96,12 @@ public class AstalApps.Apps : Object {
if (alg == FUZZY) s = a.fuzzy_match(search);
if (alg == EXACT) s = a.exact_match(search);
- if (include_name) r += s.name * name_multiplier;
- if (include_entry) r += s.entry * entry_multiplier;
- if (include_executable) r += s.executable * executable_multiplier;
- if (include_description) r += s.description * description_multiplier;
- if (include_keywords) r += s.keywords * keywords_multiplier;
+ r += s.name * name_multiplier;
+ r += s.entry * entry_multiplier;
+ r += s.executable * executable_multiplier;
+ r += s.description * description_multiplier;
+ r += s.keywords * keywords_multiplier;
+ r += s.categories * categories_multiplier;
return r;
}
diff --git a/lib/astal/io/cli.vala b/lib/astal/io/cli.vala
index f69cf0b..9e47b53 100644
--- a/lib/astal/io/cli.vala
+++ b/lib/astal/io/cli.vala
@@ -81,7 +81,7 @@ int main(string[] argv) {
return 0;
}
} catch (DBusError.SERVICE_UNKNOWN e) {
- return err(@"there is no \"$instance_name\" instance runnning");
+ return err(@"there is no \"$instance_name\" instance runnning\n");
} catch (Error e) {
return err(e.message);
}
@@ -95,7 +95,7 @@ int main(string[] argv) {
var reply = AstalIO.send_message(instance_name, request);
print("%s\n", reply);
} catch (IOError.NOT_FOUND e) {
- return err(@"there is no \"$instance_name\" instance runnning");
+ return err(@"there is no \"$instance_name\" instance runnning\n");
} catch (Error e) {
return err(e.message);
}
diff --git a/lib/auth/src/pam.c b/lib/auth/src/pam.c
index d0afec4..f50107e 100644
--- a/lib/auth/src/pam.c
+++ b/lib/auth/src/pam.c
@@ -58,7 +58,6 @@ static GParamSpec *astal_auth_pam_properties[ASTAL_AUTH_PAM_N_PROPERTIES] = {
G_DEFINE_TYPE_WITH_PRIVATE(AstalAuthPam, astal_auth_pam, G_TYPE_OBJECT);
/**
- *
* AstalAuthPam
*
* For simple authentication using only a password, using the [[email protected]]
diff --git a/lib/cava/.gitignore b/lib/cava/.gitignore
new file mode 100644
index 0000000..2c7a6aa
--- /dev/null
+++ b/lib/cava/.gitignore
@@ -0,0 +1 @@
+/subprojects/**/
diff --git a/lib/cava/astal-cava.h b/lib/cava/astal-cava.h
new file mode 100644
index 0000000..343234a
--- /dev/null
+++ b/lib/cava/astal-cava.h
@@ -0,0 +1,70 @@
+#ifndef ASTAL_CAVA_H
+#define ASTAL_CAVA_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define ASTAL_CAVA_TYPE_INPUT (astal_cava_input_get_type())
+
+typedef enum {
+ ASTAL_CAVA_INPUT_FIFO,
+ ASTAL_CAVA_INPUT_PORTAUDIO,
+ ASTAL_CAVA_INPUT_PIPEWIRE,
+ ASTAL_CAVA_INPUT_ALSA,
+ ASTAL_CAVA_INPUT_PULSE,
+ ASTAL_CAVA_INPUT_SNDIO,
+ ASTAL_CAVA_INPUT_OSS,
+ ASTAL_CAVA_INPUT_JACK,
+ ASTAL_CAVA_INPUT_SHMEM,
+ ASTAL_CAVA_INPUT_WINSCAP,
+} AstalCavaInput;
+
+#define ASTAL_CAVA_TYPE_CAVA (astal_cava_cava_get_type())
+
+G_DECLARE_FINAL_TYPE(AstalCavaCava, astal_cava_cava, ASTAL_CAVA, CAVA, GObject)
+
+AstalCavaCava* astal_cava_cava_get_default();
+AstalCavaCava* astal_cava_get_default();
+
+gboolean astal_cava_cava_get_active(AstalCavaCava* self);
+void astal_cava_cava_set_active(AstalCavaCava* self, gboolean active);
+
+GArray* astal_cava_cava_get_values(AstalCavaCava* self);
+
+gint astal_cava_cava_get_bars(AstalCavaCava* self);
+void astal_cava_cava_set_bars(AstalCavaCava* self, gint bars);
+
+gboolean astal_cava_cava_get_autosens(AstalCavaCava* self);
+void astal_cava_cava_set_autosens(AstalCavaCava* self, gboolean autosens);
+
+gboolean astal_cava_cava_get_stereo(AstalCavaCava* self);
+void astal_cava_cava_set_stereo(AstalCavaCava* self, gboolean stereo);
+
+gdouble astal_cava_cava_get_noise_reduction(AstalCavaCava* self);
+void astal_cava_cava_set_noise_reduction(AstalCavaCava* self, gdouble noise);
+
+gint astal_cava_cava_get_framerate(AstalCavaCava* self);
+void astal_cava_cava_set_framerate(AstalCavaCava* self, gint framerate);
+
+AstalCavaInput astal_cava_cava_get_input(AstalCavaCava* self);
+void astal_cava_cava_set_input(AstalCavaCava* self, AstalCavaInput input);
+
+gchar* astal_cava_cava_get_source(AstalCavaCava* self);
+void astal_cava_cava_set_source(AstalCavaCava* self, const gchar* source);
+
+gint astal_cava_cava_get_channels(AstalCavaCava* self);
+void astal_cava_cava_set_channels(AstalCavaCava* self, gint channels);
+
+gint astal_cava_cava_get_low_cutoff(AstalCavaCava* self);
+void astal_cava_cava_set_low_cutoff(AstalCavaCava* self, gint low_cutoff);
+
+gint astal_cava_cava_get_high_cutoff(AstalCavaCava* self);
+void astal_cava_cava_set_high_cutoff(AstalCavaCava* self, gint high_cutoff);
+
+gint astal_cava_cava_get_samplerate(AstalCavaCava* self);
+void astal_cava_cava_set_samplerate(AstalCavaCava* self, gint samplerate);
+
+G_END_DECLS
+
+#endif // !ASTAL_CAVA_H
diff --git a/lib/cava/cava.c b/lib/cava/cava.c
new file mode 100644
index 0000000..1c5ef66
--- /dev/null
+++ b/lib/cava/cava.c
@@ -0,0 +1,659 @@
+#include <cava/common.h>
+#include <gio/gio.h>
+
+#include "astal-cava.h"
+#include "cava/config.h"
+#include "glib-object.h"
+#include "glib.h"
+#include "glibconfig.h"
+
+struct _AstalCavaCava {
+ GObject parent_instance;
+
+ gint bars;
+ gboolean autosens;
+ gboolean stereo;
+ gdouble noise_reduction;
+ gint framerate;
+ AstalCavaInput input;
+ gchar* audio_source;
+ gboolean active;
+ gint channels;
+ gint low_cutoff;
+ gint high_cutoff;
+ gint samplerate;
+
+ GArray* values;
+};
+
+typedef struct {
+ struct cava_plan plan;
+ struct config_params cfg;
+ struct audio_data audio_data;
+ struct audio_raw audio_raw;
+ ptr input_src;
+
+ gboolean constructed;
+ GThread* input_thread;
+ guint timer_id;
+
+} AstalCavaCavaPrivate;
+
+G_DEFINE_ENUM_TYPE(AstalCavaInput, astal_cava_input,
+ G_DEFINE_ENUM_VALUE(ASTAL_CAVA_INPUT_FIFO, "fifo"),
+ G_DEFINE_ENUM_VALUE(ASTAL_CAVA_INPUT_PORTAUDIO, "portaudio"),
+ G_DEFINE_ENUM_VALUE(ASTAL_CAVA_INPUT_PIPEWIRE, "pipewire"),
+ G_DEFINE_ENUM_VALUE(ASTAL_CAVA_INPUT_ALSA, "alsa"),
+ G_DEFINE_ENUM_VALUE(ASTAL_CAVA_INPUT_PULSE, "pulse"),
+ G_DEFINE_ENUM_VALUE(ASTAL_CAVA_INPUT_SNDIO, "sndio"),
+ G_DEFINE_ENUM_VALUE(ASTAL_CAVA_INPUT_SHMEM, "shmem"),
+ G_DEFINE_ENUM_VALUE(ASTAL_CAVA_INPUT_WINSCAP, "winscap"));
+
+G_DEFINE_TYPE_WITH_PRIVATE(AstalCavaCava, astal_cava_cava, G_TYPE_OBJECT)
+
+typedef enum {
+ ASTAL_CAVA_CAVA_PROP_VALUES = 1,
+ ASTAL_CAVA_CAVA_PROP_ACTIVE,
+ ASTAL_CAVA_CAVA_PROP_BARS,
+ ASTAL_CAVA_CAVA_PROP_AUTOSENS,
+ ASTAL_CAVA_CAVA_PROP_STEREO,
+ ASTAL_CAVA_CAVA_PROP_NOISE,
+ ASTAL_CAVA_CAVA_PROP_FRAMERATE,
+ ASTAL_CAVA_CAVA_PROP_INPUT,
+ ASTAL_CAVA_CAVA_PROP_SOURCE,
+ ASTAL_CAVA_CAVA_PROP_CHANNELS,
+ ASTAL_CAVA_CAVA_PROP_LOW_CUTOFF,
+ ASTAL_CAVA_CAVA_PROP_HIGH_CUTOFF,
+ ASTAL_CAVA_CAVA_PROP_SAMPLERATE,
+ ASTAL_CAVA_CAVA_N_PROPERTIES
+} AstalCavaProperties;
+
+static GParamSpec* astal_cava_cava_properties[ASTAL_CAVA_CAVA_N_PROPERTIES] = {
+ NULL,
+};
+
+static gboolean exec_cava(AstalCavaCava* self) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+
+ pthread_mutex_lock(&priv->audio_data.lock);
+ cava_execute(priv->audio_data.cava_in, priv->audio_data.samples_counter,
+ priv->audio_raw.cava_out, &priv->plan);
+ if (priv->audio_data.samples_counter > 0) priv->audio_data.samples_counter = 0;
+ pthread_mutex_unlock(&priv->audio_data.lock);
+
+ g_array_remove_range(self->values, 0, priv->audio_raw.number_of_bars);
+ g_array_insert_vals(self->values, 0, priv->audio_raw.cava_out, priv->audio_raw.number_of_bars);
+
+ g_object_notify(G_OBJECT(self), "values");
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void astal_cava_cava_cleanup(AstalCavaCava* self) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+
+ g_source_remove(priv->timer_id);
+ pthread_mutex_lock(&priv->audio_data.lock);
+ priv->audio_data.terminate = 1;
+ pthread_mutex_unlock(&priv->audio_data.lock);
+ g_thread_join(priv->input_thread);
+
+ cava_destroy(&priv->plan);
+
+ g_free(priv->audio_data.cava_in);
+ g_free(priv->audio_data.source);
+
+ g_free(priv->cfg.audio_source);
+ g_free(priv->cfg.raw_target);
+ g_free(priv->cfg.data_format);
+}
+
+static void astal_cava_cava_start(AstalCavaCava* self) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+
+ if (self->framerate < 1) {
+ self->framerate = 1;
+ }
+
+ if (self->low_cutoff < 1) {
+ self->low_cutoff = 1;
+ }
+
+ if (self->high_cutoff < self->low_cutoff) {
+ self->high_cutoff = self->low_cutoff + 1;
+ }
+
+ if (self->samplerate / 2 <= self->high_cutoff) {
+ self->samplerate = self->high_cutoff * 2 + 1;
+ }
+
+ if (self->bars < 1) {
+ self->bars = 1;
+ }
+
+ if (self->channels < 1 || self->channels > 2) {
+ self->channels = 2;
+ }
+
+ priv->cfg = (struct config_params){
+ .inAtty = 0,
+ .output = OUTPUT_RAW,
+ .raw_target = strdup("/dev/stdout"),
+ .data_format = strdup("binary"),
+
+ .fixedbars = self->bars,
+ .autosens = self->autosens,
+ .stereo = self->stereo,
+ .noise_reduction = self->noise_reduction,
+ .framerate = self->framerate,
+ .input = (enum input_method)self->input,
+ .channels = self->channels,
+ .lower_cut_off = self->low_cutoff,
+ .upper_cut_off = self->high_cutoff,
+ .samplerate = self->samplerate,
+
+ // not needed in this lib
+ .mono_opt = AVERAGE,
+ .waves = 0,
+ .userEQ = NULL,
+ .userEQ_keys = 0,
+ .userEQ_enabled = 0,
+ .samplebits = 16,
+ .waveform = 0,
+ .monstercat = 0,
+ .sens = 1,
+ .autoconnect = 2,
+ .reverse = 0,
+ .sleep_timer = 0,
+ .show_idle_bar_heads = 1,
+ .continuous_rendering = 0,
+ .sdl_width = 1000,
+ .sdl_height = 500,
+ .sdl_x = -1,
+ .sdl_y = -1,
+ .sdl_full_screen = 0,
+ .draw_and_quit = 0,
+ .zero_test = 0,
+ .non_zero_test = 0,
+ .sync_updates = 0,
+ .disable_blanking = 1,
+ .bar_height = 32,
+ .col = 0,
+ .bgcol = 0,
+ .autobars = 0,
+ .raw_format = FORMAT_BINARY,
+ .ascii_range = 1000,
+ .bit_format = 16,
+ .gradient = 0,
+ .gradient_count = 0,
+ .bar_width = 2,
+ .bar_spacing = 1,
+ .xaxis = NONE,
+ .orientation = ORIENT_BOTTOM,
+ .color = NULL,
+ .bcolor = NULL,
+ .gradient_colors = NULL,
+ .vertex_shader = NULL,
+ .fragment_shader = NULL,
+ .bar_delim = ';',
+ .frame_delim = '\n',
+ };
+
+ if (g_strcmp0(self->audio_source, "auto") == 0) {
+ switch (priv->cfg.input) {
+ case INPUT_ALSA:
+ priv->cfg.audio_source = g_strdup("hw:Loopback,1");
+ break;
+ case INPUT_FIFO:
+ priv->cfg.audio_source = g_strdup("/tmp/mpd.fifo");
+ break;
+ case INPUT_PULSE:
+ priv->cfg.audio_source = g_strdup("auto");
+ break;
+ case INPUT_PIPEWIRE:
+ priv->cfg.audio_source = g_strdup("auto");
+ break;
+ case INPUT_SNDIO:
+ priv->cfg.audio_source = g_strdup("default");
+ break;
+ case INPUT_OSS:
+ priv->cfg.audio_source = g_strdup("/dev/dsp");
+ break;
+ case INPUT_JACK:
+ priv->cfg.audio_source = g_strdup("default");
+ break;
+ case INPUT_SHMEM:
+ priv->cfg.audio_source = g_strdup("/squeezelite-00:00:00:00:00:00");
+ break;
+ case INPUT_PORTAUDIO:
+ priv->cfg.audio_source = g_strdup("auto");
+ break;
+ default:
+ g_critical("unsupported audio source");
+ }
+ } else {
+ priv->cfg.audio_source = g_strdup(self->audio_source);
+ }
+
+ priv->audio_data = (struct audio_data){
+ .cava_in = calloc(BUFFER_SIZE * priv->cfg.channels * 8, sizeof(gdouble)),
+ .input_buffer_size = BUFFER_SIZE * priv->cfg.channels,
+ .cava_buffer_size = BUFFER_SIZE * priv->cfg.channels * 8,
+ .format = -1,
+ .rate = 0,
+ .channels = priv->cfg.channels,
+ .source = g_strdup(priv->cfg.audio_source),
+ .terminate = 0,
+ .samples_counter = 0,
+ .IEEE_FLOAT = 0,
+ .suspendFlag = false,
+ };
+
+ priv->input_src = get_input(&priv->audio_data, &priv->cfg);
+
+ audio_raw_init(&priv->audio_data, &priv->audio_raw, &priv->cfg, &priv->plan);
+
+ priv->input_thread = g_thread_new("cava_input", priv->input_src, &priv->audio_data);
+
+ priv->timer_id = g_timeout_add(1000 / priv->cfg.framerate, G_SOURCE_FUNC(exec_cava), self);
+}
+
+static void astal_cava_cava_restart(AstalCavaCava* self) {
+ if (!self->active) return;
+ astal_cava_cava_cleanup(self);
+ astal_cava_cava_start(self);
+}
+
+gboolean astal_cava_cava_get_active(AstalCavaCava* self) { return self->active; }
+
+void astal_cava_cava_set_active(AstalCavaCava* self, gboolean active) {
+ if (self->active == active) return;
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+
+ self->active = active;
+
+ if (!priv->constructed) return;
+ if (!active)
+ astal_cava_cava_cleanup(self);
+ else
+ astal_cava_cava_start(self);
+}
+
+/**
+ * astal_cava_cava_get_values
+ * @self: the AstalCavaCava object
+ *
+ * Returns: (transfer none) (element-type gdouble): a list of values
+ *
+ */
+GArray* astal_cava_cava_get_values(AstalCavaCava* self) { return self->values; }
+
+gint astal_cava_cava_get_bars(AstalCavaCava* self) { return self->bars; }
+
+void astal_cava_cava_set_bars(AstalCavaCava* self, gint bars) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ self->bars = bars;
+ if (priv->constructed) {
+ g_array_set_size(self->values, self->bars);
+ astal_cava_cava_restart(self);
+ }
+}
+
+gboolean astal_cava_cava_get_autosens(AstalCavaCava* self) { return self->autosens; }
+
+void astal_cava_cava_set_autosens(AstalCavaCava* self, gboolean autosens) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ self->autosens = autosens;
+ if (priv->constructed) astal_cava_cava_restart(self);
+}
+
+gboolean astal_cava_cava_get_stereo(AstalCavaCava* self) { return self->stereo; }
+
+void astal_cava_cava_set_stereo(AstalCavaCava* self, gboolean stereo) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ self->stereo = stereo;
+ if (priv->constructed) astal_cava_cava_restart(self);
+}
+
+gdouble astal_cava_cava_get_noise_reduction(AstalCavaCava* self) { return self->noise_reduction; }
+
+void astal_cava_cava_set_noise_reduction(AstalCavaCava* self, gdouble noise) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ self->noise_reduction = noise;
+ if (priv->constructed) astal_cava_cava_restart(self);
+}
+
+gint astal_cava_cava_get_framerate(AstalCavaCava* self) { return self->framerate; }
+
+void astal_cava_cava_set_framerate(AstalCavaCava* self, gint framerate) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ self->framerate = framerate;
+ if (priv->constructed) astal_cava_cava_restart(self);
+}
+
+AstalCavaInput astal_cava_cava_get_input(AstalCavaCava* self) { return self->input; }
+
+void astal_cava_cava_set_input(AstalCavaCava* self, AstalCavaInput input) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ self->input = input;
+ if (priv->constructed) astal_cava_cava_restart(self);
+}
+
+gchar* astal_cava_cava_get_source(AstalCavaCava* self) { return self->audio_source; }
+
+void astal_cava_cava_set_source(AstalCavaCava* self, const gchar* source) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ g_free(self->audio_source);
+ self->audio_source = g_strdup(source);
+ if (priv->constructed) astal_cava_cava_restart(self);
+}
+
+gint astal_cava_cava_get_channels(AstalCavaCava* self) { return self->channels; }
+
+void astal_cava_cava_set_channels(AstalCavaCava* self, gint channels) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ self->channels = channels;
+ if (priv->constructed) astal_cava_cava_restart(self);
+}
+
+gint astal_cava_cava_get_low_cutoff(AstalCavaCava* self) { return self->low_cutoff; }
+
+void astal_cava_cava_set_low_cutoff(AstalCavaCava* self, gint low_cutoff) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ self->low_cutoff = low_cutoff;
+ if (priv->constructed) astal_cava_cava_restart(self);
+}
+
+gint astal_cava_cava_get_high_cutoff(AstalCavaCava* self) { return self->high_cutoff; }
+
+void astal_cava_cava_set_high_cutoff(AstalCavaCava* self, gint high_cutoff) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ self->high_cutoff = high_cutoff;
+ if (priv->constructed) astal_cava_cava_restart(self);
+}
+
+gint astal_cava_cava_get_samplerate(AstalCavaCava* self) { return self->samplerate; }
+
+void astal_cava_cava_set_samplerate(AstalCavaCava* self, gint samplerate) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ self->samplerate = samplerate;
+ if (priv->constructed) astal_cava_cava_restart(self);
+}
+
+static void astal_cava_cava_set_property(GObject* object, guint property_id, const GValue* value,
+ GParamSpec* pspec) {
+ AstalCavaCava* self = ASTAL_CAVA_CAVA(object);
+
+ switch (property_id) {
+ case ASTAL_CAVA_CAVA_PROP_BARS:
+ astal_cava_cava_set_bars(self, g_value_get_int(value));
+ break;
+ case ASTAL_CAVA_CAVA_PROP_ACTIVE:
+ astal_cava_cava_set_active(self, g_value_get_boolean(value));
+ break;
+ case ASTAL_CAVA_CAVA_PROP_AUTOSENS:
+ astal_cava_cava_set_autosens(self, g_value_get_boolean(value));
+ break;
+ case ASTAL_CAVA_CAVA_PROP_NOISE:
+ astal_cava_cava_set_noise_reduction(self, g_value_get_double(value));
+ break;
+ case ASTAL_CAVA_CAVA_PROP_STEREO:
+ astal_cava_cava_set_stereo(self, g_value_get_boolean(value));
+ break;
+ case ASTAL_CAVA_CAVA_PROP_FRAMERATE:
+ astal_cava_cava_set_framerate(self, g_value_get_int(value));
+ break;
+ case ASTAL_CAVA_CAVA_PROP_INPUT:
+ astal_cava_cava_set_input(self, g_value_get_enum(value));
+ break;
+ case ASTAL_CAVA_CAVA_PROP_SOURCE:
+ g_free(self->audio_source);
+ astal_cava_cava_set_source(self, g_value_get_string(value));
+ break;
+ case ASTAL_CAVA_CAVA_PROP_CHANNELS:
+ astal_cava_cava_set_channels(self, g_value_get_int(value));
+ break;
+ case ASTAL_CAVA_CAVA_PROP_LOW_CUTOFF:
+ astal_cava_cava_set_low_cutoff(self, g_value_get_int(value));
+ break;
+ case ASTAL_CAVA_CAVA_PROP_HIGH_CUTOFF:
+ astal_cava_cava_set_high_cutoff(self, g_value_get_int(value));
+ break;
+ case ASTAL_CAVA_CAVA_PROP_SAMPLERATE:
+ astal_cava_cava_set_samplerate(self, g_value_get_int(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void astal_cava_cava_get_property(GObject* object, guint property_id, GValue* value,
+ GParamSpec* pspec) {
+ AstalCavaCava* self = ASTAL_CAVA_CAVA(object);
+
+ switch (property_id) {
+ case ASTAL_CAVA_CAVA_PROP_ACTIVE:
+ g_value_set_boolean(value, self->active);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_BARS:
+ g_value_set_int(value, self->bars);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_VALUES:
+ g_value_set_pointer(value, self->values);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_AUTOSENS:
+ g_value_set_boolean(value, self->autosens);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_NOISE:
+ g_value_set_double(value, self->noise_reduction);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_STEREO:
+ g_value_set_boolean(value, self->stereo);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_FRAMERATE:
+ g_value_set_int(value, self->framerate);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_INPUT:
+ g_value_set_enum(value, self->input);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_SOURCE:
+ g_value_set_string(value, self->audio_source);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_CHANNELS:
+ g_value_set_int(value, self->channels);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_LOW_CUTOFF:
+ g_value_set_int(value, self->low_cutoff);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_HIGH_CUTOFF:
+ g_value_set_int(value, self->high_cutoff);
+ break;
+ case ASTAL_CAVA_CAVA_PROP_SAMPLERATE:
+ g_value_set_int(value, self->samplerate);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void astal_cava_cava_constructed(GObject* object) {
+ AstalCavaCava* self = ASTAL_CAVA_CAVA(object);
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+
+ gdouble* data = calloc(self->bars, sizeof(gdouble));
+ memset(data, 0, self->bars * sizeof(gdouble));
+ self->values = g_array_new_take(data, self->bars, TRUE, sizeof(gdouble));
+
+ priv->constructed = true;
+
+ if (self->active) astal_cava_cava_start(self);
+}
+
+static void astal_cava_cava_init(AstalCavaCava* self) {
+ AstalCavaCavaPrivate* priv = astal_cava_cava_get_instance_private(self);
+ priv->constructed = false;
+ self->low_cutoff = 50;
+ self->high_cutoff = 10000;
+ self->samplerate = 44100;
+}
+
+/**
+ * astal_cava_get_default
+ *
+ * gets the default Cava object.
+ *
+ * Returns: (nullable) (transfer none):
+ */
+AstalCavaCava* astal_cava_get_default() { return astal_cava_cava_get_default(); }
+
+/**
+ * astal_cava_cava_get_default
+ *
+ * gets the default Cava object.
+ *
+ * Returns: (nullable) (transfer none):
+ */
+AstalCavaCava* astal_cava_cava_get_default() {
+ static AstalCavaCava* self = NULL;
+
+ if (self == NULL) self = g_object_new(ASTAL_CAVA_TYPE_CAVA, NULL);
+
+ return self;
+}
+
+static void astal_cava_cava_dispose(GObject* object) {
+ AstalCavaCava* self = ASTAL_CAVA_CAVA(object);
+
+ if (self->active) astal_cava_cava_cleanup(self);
+ G_OBJECT_CLASS(astal_cava_cava_parent_class)->dispose(object);
+}
+
+static void astal_cava_cava_finalize(GObject* object) {
+ AstalCavaCava* self = ASTAL_CAVA_CAVA(object);
+
+ g_array_free(self->values, TRUE);
+
+ G_OBJECT_CLASS(astal_cava_cava_parent_class)->finalize(object);
+}
+
+static void astal_cava_cava_class_init(AstalCavaCavaClass* class) {
+ GObjectClass* object_class = G_OBJECT_CLASS(class);
+ object_class->get_property = astal_cava_cava_get_property;
+ object_class->set_property = astal_cava_cava_set_property;
+ object_class->constructed = astal_cava_cava_constructed;
+ object_class->dispose = astal_cava_cava_dispose;
+ object_class->finalize = astal_cava_cava_finalize;
+
+ /**
+ * AstalCavaCava:values: (type GArray(gdouble))
+ *
+ * A list of values, each represent the height of one bar. The values are generally between 0
+ * and 1 but can overshoot occasionally, in which case the sensitivity will be decreased
+ * automatically if [[email protected]:autosens] is set. The array will have
+ * [[email protected]:bars] entries. If [[email protected]:stereo] is set, the first
+ * half of the array will represent the left channel and the second half the right channel, so
+ * there will be only bars/2 bars per channel. If the number of bars is odd, the last value will
+ * be 0.
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_VALUES] =
+ g_param_spec_pointer("values", "values", "a list of values", G_PARAM_READABLE);
+ /**
+ * AstalCavaCava:active:
+ *
+ * whether or not the audio capture and visualization is running. if false the values array will
+ * not be updated.
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_ACTIVE] = g_param_spec_boolean(
+ "active", "active", "active", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ /**
+ * AstalCavaCava:bars:
+ *
+ * the number of bars the visualizer should create.
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_BARS] =
+ g_param_spec_int("bars", "bars", "number of bars per channel", 1, G_MAXINT, 20,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ /**
+ * AstalCavaCava:autosens:
+ *
+ * When set, the sensitivity will automatically be adjusted.
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_AUTOSENS] =
+ g_param_spec_boolean("autosens", "autosens", "dynamically adjust sensitivity", TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ /**
+ * AstalCavaCava:cava:
+ *
+ * When set the output will contain visualization data for both channels.
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_STEREO] = g_param_spec_boolean(
+ "stereo", "stereo", "stereo", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ /**
+ * AstalCavaCava:noise-reduction:
+ *
+ * adjusts the noise-reduction filter. low values are fast and noisy, large values are slow and
+ * smooth.
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_NOISE] =
+ g_param_spec_double("noise_reduction", "noise_reduction", "noise reduction", 0, 1, 0.77,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ /**
+ * AstalCavaCava:framerate:
+ *
+ * how often the values should be updated
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_FRAMERATE] =
+ g_param_spec_int("framerate", "framerate", "framerate", 1, G_MAXINT, 60,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ /**
+ * AstalCavaCava:channels:
+ *
+ * how many input channels to consider
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_CHANNELS] = g_param_spec_int(
+ "channels", "channels", "channels", 1, 2, 2, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ /**
+ * AstalCavaCava:low-cutoff:
+ *
+ * cut off frequencies below this value
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_LOW_CUTOFF] =
+ g_param_spec_int("low-cutoff", "low-cutoff", "lower frequency cutoff", 1, G_MAXINT, 50,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ /**
+ * AstalCavaCava:high-cutoff:
+ *
+ * cut off frequencies above this value
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_HIGH_CUTOFF] =
+ g_param_spec_int("high-cutoff", "high-cutoff", "higher frequency cutoff", 1, G_MAXINT,
+ 10000, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ /**
+ * AstalCavaCava:samplerate:
+ *
+ * the samplerate of the input
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_SAMPLERATE] =
+ g_param_spec_int("samplerate", "samplerate", "samplerate", 1, G_MAXINT, 44100,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ /**
+ * AstalCavaCava:input: (type AstalCavaInput)
+ *
+ * specifies which audio server should be used.
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_INPUT] =
+ g_param_spec_enum("input", "input", "input", ASTAL_CAVA_TYPE_INPUT,
+ ASTAL_CAVA_INPUT_PIPEWIRE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ /**
+ * AstalCavaCava:source:
+ *
+ * specifies which audio source should be used. Refer to the cava docs on how to use this
+ * property.
+ */
+ astal_cava_cava_properties[ASTAL_CAVA_CAVA_PROP_SOURCE] = g_param_spec_string(
+ "source", "source", "source", "auto", G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ g_object_class_install_properties(object_class, ASTAL_CAVA_CAVA_N_PROPERTIES,
+ astal_cava_cava_properties);
+}
diff --git a/lib/cava/meson.build b/lib/cava/meson.build
new file mode 100644
index 0000000..874eaf7
--- /dev/null
+++ b/lib/cava/meson.build
@@ -0,0 +1,80 @@
+project(
+ 'astal-cava',
+ 'c',
+ version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(),
+ default_options: ['c_std=gnu11', 'warning_level=3', 'prefix=/usr'],
+)
+
+add_project_arguments(['-Wno-pedantic', '-Wno-unused-parameter'], language: 'c')
+
+version_split = meson.project_version().split('.')
+lib_so_version = version_split[0] + '.' + version_split[1]
+
+pkg_config = import('pkgconfig')
+gnome = import('gnome')
+
+srcs = files(
+ 'astal-cava.h',
+ 'cava.c',
+)
+
+install_headers('astal-cava.h')
+
+cava = dependency(
+ 'cava',
+ version: '>=0.10.3',
+ required: true,
+ fallback: ['cava', 'cava_dep'],
+)
+
+deps = [
+ dependency('gobject-2.0'),
+ dependency('gio-2.0'),
+ cava,
+]
+
+astal_cava_lib = library(
+ 'astal-cava',
+ sources: srcs,
+ dependencies: deps,
+ version: meson.project_version(),
+ install: true,
+)
+
+libastal_cava = declare_dependency(link_with: astal_cava_lib)
+
+pkg_config_name = 'astal-cava-' + lib_so_version
+
+if get_option('introspection')
+ gir = gnome.generate_gir(
+ astal_cava_lib,
+ sources: srcs,
+ nsversion: '0.1',
+ namespace: 'AstalCava',
+ symbol_prefix: 'astal_cava',
+ identifier_prefix: 'AstalCava',
+ includes: ['GObject-2.0', 'Gio-2.0'],
+ header: 'astal-cava.h',
+ export_packages: pkg_config_name,
+ install: true,
+ )
+
+ if get_option('vapi')
+ gnome.generate_vapi(
+ pkg_config_name,
+ sources: [gir[0]],
+ packages: ['gobject-2.0', 'gio-2.0'],
+ install: true,
+ )
+ endif
+endif
+
+pkg_config.generate(
+ name: 'astal-cava',
+ version: meson.project_version(),
+ libraries: [astal_cava_lib],
+ filebase: pkg_config_name,
+ subdirs: 'astal',
+ description: 'audio analyzing service using cava',
+ url: 'https://github.com/Aylur/astal',
+)
diff --git a/lib/cava/meson_options.txt b/lib/cava/meson_options.txt
new file mode 100644
index 0000000..97aa4e7
--- /dev/null
+++ b/lib/cava/meson_options.txt
@@ -0,0 +1,13 @@
+option(
+ 'introspection',
+ type: 'boolean',
+ value: true,
+ description: 'Build gobject-introspection data',
+)
+
+option(
+ 'vapi',
+ type: 'boolean',
+ value: true,
+ description: 'Generate vapi data (needs vapigen & introspection option)',
+)
diff --git a/lib/cava/subprojects/cava.wrap b/lib/cava/subprojects/cava.wrap
new file mode 100644
index 0000000..f0309bf
--- /dev/null
+++ b/lib/cava/subprojects/cava.wrap
@@ -0,0 +1,7 @@
+[wrap-file]
+directory = cava-0.10.3
+source_url = https://github.com/LukashonakV/cava/archive/0.10.3.tar.gz
+source_filename = cava-0.10.3.tar.gz
+source_hash = aab0a4ed3f999e8461ad9de63ef8a77f28b6b2011f7dd0c69ba81819d442f6f9
+[provide]
+cava = cava_dep
diff --git a/lib/cava/version b/lib/cava/version
new file mode 100644
index 0000000..6e8bf73
--- /dev/null
+++ b/lib/cava/version
@@ -0,0 +1 @@
+0.1.0
diff --git a/lib/mpris/mpris.vala b/lib/mpris/mpris.vala
index 8eaffa5..8039d39 100644
--- a/lib/mpris/mpris.vala
+++ b/lib/mpris/mpris.vala
@@ -73,12 +73,10 @@ public class AstalMpris.Mpris : Object {
var p = new Player(busname);
_players.set(busname, p);
- p.notify["available"].connect(() => {
- if (!p.available) {
- player_closed(p);
- _players.remove(busname);
- notify_property("players");
- }
+ p.closed.connect(() => {
+ player_closed(p);
+ _players.remove(busname);
+ notify_property("players");
});
player_added(p);
diff --git a/lib/mpris/player.vala b/lib/mpris/player.vala
index b72c15e..2050f61 100644
--- a/lib/mpris/player.vala
+++ b/lib/mpris/player.vala
@@ -9,7 +9,12 @@ public class AstalMpris.Player : Object {
private IPlayer proxy;
private uint pollid; // periodically notify position
- // identifiers
+ internal signal void appeared() { available = true; }
+ internal signal void closed() { available = false; }
+
+ /**
+ * Full dbus namae of this player.
+ */
public string bus_name { owned get; private set; }
/**
@@ -37,7 +42,7 @@ public class AstalMpris.Player : Object {
* The media player may refuse to allow clients to shut it down.
* In this case, the [[email protected]:can_quit] property is false and this method does nothing.
*/
- public void quit() throws Error {
+ public void quit() {
try { proxy.quit(); } catch (Error err) { critical(err.message); }
}
@@ -559,15 +564,33 @@ public class AstalMpris.Player : Object {
}
construct {
- try {
- try_proxy();
- sync();
- } catch (Error error) {
- critical(error.message);
- }
+ notify["bus-name"].connect(() => {
+ try {
+ setup_proxy();
+ setup_position_poll();
+ sync();
+ } catch (Error error) {
+ critical(error.message);
+ }
+ });
}
- private void try_proxy() throws Error {
+ private void setup_position_poll() {
+ var current_position = position;
+
+ pollid = Timeout.add_seconds(1, () => {
+ if (!available)
+ return Source.CONTINUE;
+
+ if (position >= 0 && current_position != position) {
+ current_position = position;
+ notify_property("position");
+ }
+ return Source.CONTINUE;
+ }, Priority.DEFAULT);
+ }
+
+ private void setup_proxy() throws Error {
if (proxy != null)
return;
@@ -577,28 +600,19 @@ public class AstalMpris.Player : Object {
"/org/mpris/MediaPlayer2"
);
- if (proxy.g_name_owner != null)
- available = false;
+ if (proxy.g_name_owner != null) {
+ appeared();
+ }
proxy.notify["g-name-owner"].connect(() => {
if (proxy.g_name_owner != null) {
- available = true;
+ appeared();
} else {
- available = false;
+ closed();
}
});
proxy.g_properties_changed.connect(sync);
-
- pollid = Timeout.add_seconds(1, () => {
- if (!available)
- return Source.CONTINUE;
-
- if (position >= 0) {
- notify_property("position");
- }
- return Source.CONTINUE;
- }, Priority.DEFAULT);
}
~Player() {
@@ -622,18 +636,6 @@ public enum AstalMpris.PlaybackStatus {
return STOPPED;
}
}
-
- internal string to_string() {
- switch (this) {
- case PLAYING:
- return "Playing";
- case PAUSED:
- return "Paused";
- case STOPPED:
- default:
- return "Stopped";
- }
- }
}
public enum AstalMpris.Loop {
diff --git a/lib/notifd/notification.vala b/lib/notifd/notification.vala
index 527a352..29c6c56 100644
--- a/lib/notifd/notification.vala
+++ b/lib/notifd/notification.vala
@@ -158,12 +158,6 @@ public class AstalNotifd.Notification : Object {
*/
public signal void resolved(ClosedReason reason);
- /**
- * Emitted when the user dismisses this notification.
- *
- * @see dismiss
- */
- public signal void dismissed();
/**
* Emitted when an [[email protected]] of this notification is invoked.
@@ -173,12 +167,10 @@ public class AstalNotifd.Notification : Object {
public signal void invoked(string action_id);
/**
- * Dismiss this notification popup
- *
- * This method doesn't have any functionality on its own, but should be handled
- * by frontend implementation to hide notification popups.
+ * Resolve this notification with [[email protected]_BY_USER].
*/
public void dismiss() { dismissed(); }
+ internal signal void dismissed();
/**
* Invoke an [[email protected]] of this notification.
diff --git a/lib/notifd/proxy.vala b/lib/notifd/proxy.vala
index bedb8b9..95e7105 100644
--- a/lib/notifd/proxy.vala
+++ b/lib/notifd/proxy.vala
@@ -32,10 +32,6 @@ internal class AstalNotifd.DaemonProxy : Object {
set { proxy.dont_disturb = value; }
}
- public uint[] notification_ids() throws DBusError, IOError {
- return proxy.notification_ids();
- }
-
public Notification get_notification(uint id) {
return notifs.get(id);
}
diff --git a/lib/notifd/signals.md b/lib/notifd/signals.md
index cdc6688..e13b92c 100644
--- a/lib/notifd/signals.md
+++ b/lib/notifd/signals.md
@@ -5,7 +5,7 @@ ignore this, I'm just dumb and can't follow where signals go or get emitted from
## Notification
* resolved(reason) - by daemon/proxy
-* dismissed() - by user with `.dismiss()`
+* dismissed() - by user with `.dismiss()`, used to emit resolved from proxy/daemon
* invoked(action) - by user with `.invoke()`
## Deamon
diff --git a/nix/libcava.nix b/nix/libcava.nix
new file mode 100644
index 0000000..866599d
--- /dev/null
+++ b/nix/libcava.nix
@@ -0,0 +1,60 @@
+{
+ stdenv,
+ fetchFromGitHub,
+ autoreconfHook,
+ autoconf-archive,
+ alsa-lib,
+ fftw,
+ iniparser,
+ libpulseaudio,
+ portaudio,
+ sndio,
+ SDL2,
+ libGL,
+ pipewire,
+ jack2,
+ ncurses,
+ pkgconf,
+ meson,
+ ninja,
+}:
+stdenv.mkDerivation rec {
+ pname = "cava";
+ version = "0.10.3";
+
+ src = fetchFromGitHub {
+ owner = "LukashonakV";
+ repo = "cava";
+ rev = "0.10.3";
+ hash = "sha256-ZDFbI69ECsUTjbhlw2kHRufZbQMu+FQSMmncCJ5pagg=";
+ };
+
+ buildInputs = [
+ alsa-lib
+ libpulseaudio
+ ncurses
+ iniparser
+ sndio
+ SDL2
+ libGL
+ portaudio
+ jack2
+ pipewire
+ ];
+
+ propagatedBuildInputs = [
+ fftw
+ ];
+
+ nativeBuildInputs = [
+ autoreconfHook
+ autoconf-archive
+ pkgconf
+ meson
+ ninja
+ ];
+
+ preAutoreconf = ''
+ echo ${version} > version
+ '';
+}