From 3e3f045d650a839d21f7b649da7aa5c19bd2e38b Mon Sep 17 00:00:00 2001 From: Aylur Date: Sun, 1 Sep 2024 14:17:36 +0200 Subject: monorepo structuring --- apps/.gitignore | 5 - apps/LICENSE | 502 ------------------- apps/README.md | 8 - apps/flake.lock | 27 - apps/flake.nix | 49 -- apps/meson-install.sh | 16 - apps/meson.build | 19 - apps/meson_options.txt | 11 - apps/src/application.vala | 118 ----- apps/src/apps.vala | 172 ------- apps/src/cli.vala | 66 --- apps/src/config.vala.in | 6 - apps/src/meson.build | 77 --- apps/version | 1 - auth/.gitignore | 3 - auth/LICENSE | 503 ------------------- auth/README.md | 66 --- auth/examples/full_example.c | 66 --- auth/examples/full_example.js | 38 -- auth/examples/meson.build | 18 - auth/examples/simple_example.c | 31 -- auth/examples/simple_example.js | 9 - auth/flake.lock | 27 - auth/flake.nix | 42 -- auth/include/astal-auth.h | 32 -- auth/include/meson.build | 4 - auth/meson.build | 33 -- auth/meson_options.txt | 3 - auth/pam/astal-auth | 5 - auth/src/astal-auth.c | 153 ------ auth/src/meson.build | 65 --- auth/src/pam.c | 524 ------------------- auth/version | 1 - battery/.gitignore | 5 - battery/LICENSE | 502 ------------------- battery/README.md | 7 - battery/flake.lock | 27 - battery/flake.nix | 47 -- battery/meson.build | 19 - battery/meson_options.txt | 11 - battery/src/cli.vala | 74 --- battery/src/config.vala.in | 6 - battery/src/device.vala | 296 ----------- battery/src/ifaces.vala | 65 --- battery/src/meson.build | 76 --- battery/src/upower.vala | 58 --- battery/version | 1 - bluetooth/.gitignore | 5 - bluetooth/LICENSE | 503 ------------------- bluetooth/README.md | 7 - bluetooth/flake.lock | 27 - bluetooth/flake.nix | 52 -- bluetooth/meson.build | 14 - bluetooth/src/adapter.vala | 89 ---- bluetooth/src/bluetooth.vala | 181 ------- bluetooth/src/config.vala.in | 6 - bluetooth/src/device.vala | 106 ---- bluetooth/src/meson.build | 66 --- bluetooth/src/utils.vala | 21 - bluetooth/version | 1 - core/default.nix | 0 flake.nix | 29 +- hyprland/.gitignore | 4 - hyprland/LICENSE | 503 ------------------- hyprland/README.md | 7 - hyprland/flake.lock | 27 - hyprland/flake.nix | 49 -- hyprland/meson.build | 19 - hyprland/meson_options.txt | 11 - hyprland/src/cli.vala | 42 -- hyprland/src/client.vala | 75 --- hyprland/src/config.vala.in | 6 - hyprland/src/hyprland.vala | 451 ----------------- hyprland/src/meson.build | 81 --- hyprland/src/monitor.vala | 71 --- hyprland/src/structs.vala | 42 -- hyprland/src/workspace.vala | 57 --- hyprland/version | 1 - lib/apps/application.vala | 118 +++++ lib/apps/apps.vala | 172 +++++++ lib/apps/cli.vala | 66 +++ lib/apps/config.vala.in | 6 + lib/apps/meson.build | 95 ++++ lib/apps/meson_options.txt | 11 + lib/apps/version | 1 + lib/auth/include/astal-auth.h | 32 ++ lib/auth/include/meson.build | 4 + lib/auth/meson.build | 23 + lib/auth/meson_options.txt | 12 + lib/auth/pam/astal-auth | 5 + lib/auth/src/astal-auth.c | 153 ++++++ lib/auth/src/meson.build | 59 +++ lib/auth/src/pam.c | 524 +++++++++++++++++++ lib/auth/version | 1 + lib/battery/cli.vala | 74 +++ lib/battery/config.vala.in | 6 + lib/battery/device.vala | 296 +++++++++++ lib/battery/ifaces.vala | 65 +++ lib/battery/meson.build | 94 ++++ lib/battery/meson_options.txt | 11 + lib/battery/upower.vala | 58 +++ lib/battery/version | 1 + lib/bluetooth/adapter.vala | 89 ++++ lib/bluetooth/bluetooth.vala | 181 +++++++ lib/bluetooth/config.vala.in | 6 + lib/bluetooth/device.vala | 106 ++++ lib/bluetooth/meson.build | 79 +++ lib/bluetooth/utils.vala | 21 + lib/bluetooth/version | 1 + lib/hyprland/cli.vala | 42 ++ lib/hyprland/client.vala | 75 +++ lib/hyprland/config.vala.in | 6 + lib/hyprland/hyprland.vala | 451 +++++++++++++++++ lib/hyprland/meson.build | 99 ++++ lib/hyprland/meson_options.txt | 11 + lib/hyprland/monitor.vala | 71 +++ lib/hyprland/structs.vala | 42 ++ lib/hyprland/version | 1 + lib/hyprland/workspace.vala | 57 +++ lib/mpris/cli.vala | 331 ++++++++++++ lib/mpris/config.vala.in | 6 + lib/mpris/ifaces.vala | 60 +++ lib/mpris/meson.build | 94 ++++ lib/mpris/meson_options.txt | 11 + lib/mpris/mpris.vala | 66 +++ lib/mpris/player.vala | 467 +++++++++++++++++ lib/mpris/version | 1 + lib/network/accesspoint.vala | 49 ++ lib/network/config.vala.in | 6 + lib/network/meson.build | 80 +++ lib/network/network.vala | 206 ++++++++ lib/network/version | 1 + lib/network/vpn.vala | 1 + lib/network/wifi.vala | 182 +++++++ lib/network/wired.vala | 73 +++ lib/notifd/cli.vala | 115 +++++ lib/notifd/config.vala.in | 6 + lib/notifd/daemon.vala | 255 ++++++++++ lib/notifd/meson.build | 97 ++++ lib/notifd/meson_options.txt | 11 + lib/notifd/notifd.vala | 140 ++++++ lib/notifd/notification.vala | 160 ++++++ lib/notifd/proxy.vala | 129 +++++ lib/notifd/signals.md | 35 ++ lib/notifd/version | 1 + lib/powerprofiles/cli.vala | 80 +++ lib/powerprofiles/config.vala.in | 6 + lib/powerprofiles/meson.build | 93 ++++ lib/powerprofiles/meson_options.txt | 11 + lib/powerprofiles/power-profiles.vala | 205 ++++++++ lib/powerprofiles/version | 1 + lib/river/include/astal-river.h | 59 +++ lib/river/include/meson.build | 6 + lib/river/include/river-private.h | 20 + lib/river/include/wayland-source.h | 16 + lib/river/meson.build | 18 + lib/river/meson_options.txt | 13 + lib/river/protocols/meson.build | 23 + lib/river/protocols/river-control-unstable-v1.xml | 86 ++++ lib/river/protocols/river-status-unstable-v1.xml | 149 ++++++ lib/river/src/astal-river.c | 51 ++ lib/river/src/meson.build | 71 +++ lib/river/src/river-output.c | 343 +++++++++++++ lib/river/src/river.c | 504 +++++++++++++++++++ lib/river/src/wayland-source.c | 104 ++++ lib/river/version | 1 + lib/tray/cli.vala | 54 ++ lib/tray/config.vala.in | 6 + lib/tray/meson.build | 118 +++++ lib/tray/meson_options.txt | 11 + lib/tray/tray.vala | 135 +++++ lib/tray/trayItem.vala | 363 ++++++++++++++ lib/tray/version | 1 + lib/tray/watcher.vala | 59 +++ lib/wireplumber/flake.nix | 54 ++ lib/wireplumber/include/astal-wp.h | 4 + lib/wireplumber/include/astal/wireplumber/audio.h | 33 ++ lib/wireplumber/include/astal/wireplumber/device.h | 29 ++ .../include/astal/wireplumber/endpoint.h | 43 ++ .../include/astal/wireplumber/meson.build | 10 + .../include/astal/wireplumber/profile.h | 17 + lib/wireplumber/include/astal/wireplumber/video.h | 29 ++ lib/wireplumber/include/astal/wireplumber/wp.h | 47 ++ lib/wireplumber/include/meson.build | 8 + lib/wireplumber/include/private/device-private.h | 15 + lib/wireplumber/include/private/endpoint-private.h | 22 + lib/wireplumber/meson.build | 17 + lib/wireplumber/meson_options.txt | 13 + lib/wireplumber/src/audio.c | 503 +++++++++++++++++++ lib/wireplumber/src/device.c | 371 ++++++++++++++ lib/wireplumber/src/endpoint.c | 554 +++++++++++++++++++++ lib/wireplumber/src/meson.build | 72 +++ lib/wireplumber/src/profile.c | 84 ++++ lib/wireplumber/src/video.c | 428 ++++++++++++++++ lib/wireplumber/src/wireplumber.c | 503 +++++++++++++++++++ lib/wireplumber/version | 1 + mpris/.gitignore | 5 - mpris/LICENSE | 503 ------------------- mpris/README.md | 7 - mpris/flake.lock | 27 - mpris/flake.nix | 49 -- mpris/meson.build | 19 - mpris/meson_options.txt | 11 - mpris/src/cli.vala | 331 ------------ mpris/src/config.vala.in | 6 - mpris/src/ifaces.vala | 60 --- mpris/src/meson.build | 77 --- mpris/src/mpris.vala | 66 --- mpris/src/player.vala | 467 ----------------- mpris/version | 1 - network/.gitignore | 5 - network/LICENSE | 503 ------------------- network/README.md | 7 - network/flake.lock | 27 - network/flake.nix | 53 -- network/meson.build | 14 - network/src/accesspoint.vala | 49 -- network/src/config.vala.in | 6 - network/src/meson.build | 67 --- network/src/network.vala | 206 -------- network/src/vpn.vala | 1 - network/src/wifi.vala | 182 ------- network/src/wired.vala | 73 --- network/version | 1 - notifd/LICENSE | 503 ------------------- notifd/README.md | 7 - notifd/flake.lock | 27 - notifd/flake.nix | 54 -- notifd/meson.build | 19 - notifd/meson_options.txt | 11 - notifd/src/cli.vala | 115 ----- notifd/src/config.vala.in | 6 - notifd/src/daemon.vala | 255 ---------- notifd/src/meson.build | 79 --- notifd/src/notifd.vala | 140 ------ notifd/src/notification.vala | 160 ------ notifd/src/proxy.vala | 129 ----- notifd/src/signals.md | 35 -- notifd/version | 1 - powerprofiles/.gitignore | 5 - powerprofiles/LICENSE | 503 ------------------- powerprofiles/README.md | 7 - powerprofiles/flake.lock | 27 - powerprofiles/flake.nix | 53 -- powerprofiles/meson.build | 19 - powerprofiles/meson_options.txt | 11 - powerprofiles/src/cli.vala | 80 --- powerprofiles/src/config.vala.in | 6 - powerprofiles/src/meson.build | 75 --- powerprofiles/src/power-profiles.vala | 205 -------- powerprofiles/version | 1 - river/.gitignore | 3 - river/LICENSE | 504 ------------------- river/README.md | 30 -- river/flake.lock | 27 - river/flake.nix | 54 -- river/include/astal-river.h | 59 --- river/include/meson.build | 6 - river/include/river-private.h | 20 - river/include/wayland-source.h | 16 - river/meson.build | 23 - river/meson_options.txt | 2 - river/protocols/meson.build | 23 - river/protocols/river-control-unstable-v1.xml | 86 ---- river/protocols/river-status-unstable-v1.xml | 149 ------ river/src/astal-river.c | 51 -- river/src/meson.build | 70 --- river/src/river-output.c | 343 ------------- river/src/river.c | 504 ------------------- river/src/wayland-source.c | 104 ---- river/version | 1 - tray/LICENSE | 503 ------------------- tray/README.md | 30 -- tray/flake.lock | 27 - tray/flake.nix | 56 --- tray/meson.build | 19 - tray/meson_options.txt | 11 - tray/src/cli.vala | 54 -- tray/src/config.vala.in | 6 - tray/src/meson.build | 100 ---- tray/src/tray.vala | 135 ----- tray/src/trayItem.vala | 363 -------------- tray/src/watcher.vala | 59 --- tray/version | 1 - wireplumber/.gitignore | 3 - wireplumber/LICENSE | 503 ------------------- wireplumber/README.md | 34 -- wireplumber/flake.lock | 27 - wireplumber/flake.nix | 54 -- wireplumber/include/astal-wp.h | 4 - wireplumber/include/astal/wireplumber/audio.h | 33 -- wireplumber/include/astal/wireplumber/device.h | 29 -- wireplumber/include/astal/wireplumber/endpoint.h | 43 -- wireplumber/include/astal/wireplumber/meson.build | 10 - wireplumber/include/astal/wireplumber/profile.h | 17 - wireplumber/include/astal/wireplumber/video.h | 29 -- wireplumber/include/astal/wireplumber/wp.h | 47 -- wireplumber/include/meson.build | 8 - wireplumber/include/private/device-private.h | 15 - wireplumber/include/private/endpoint-private.h | 22 - wireplumber/meson.build | 22 - wireplumber/meson_options.txt | 2 - wireplumber/src/audio.c | 503 ------------------- wireplumber/src/device.c | 371 -------------- wireplumber/src/endpoint.c | 554 --------------------- wireplumber/src/meson.build | 73 --- wireplumber/src/profile.c | 84 ---- wireplumber/src/video.c | 428 ---------------- wireplumber/src/wireplumber.c | 503 ------------------- wireplumber/version | 1 - 310 files changed, 11298 insertions(+), 18635 deletions(-) delete mode 100644 apps/.gitignore delete mode 100644 apps/LICENSE delete mode 100644 apps/README.md delete mode 100644 apps/flake.lock delete mode 100644 apps/flake.nix delete mode 100644 apps/meson-install.sh delete mode 100644 apps/meson.build delete mode 100644 apps/meson_options.txt delete mode 100644 apps/src/application.vala delete mode 100644 apps/src/apps.vala delete mode 100644 apps/src/cli.vala delete mode 100644 apps/src/config.vala.in delete mode 100644 apps/src/meson.build delete mode 100644 apps/version delete mode 100644 auth/.gitignore delete mode 100644 auth/LICENSE delete mode 100644 auth/README.md delete mode 100644 auth/examples/full_example.c delete mode 100644 auth/examples/full_example.js delete mode 100644 auth/examples/meson.build delete mode 100644 auth/examples/simple_example.c delete mode 100644 auth/examples/simple_example.js delete mode 100644 auth/flake.lock delete mode 100644 auth/flake.nix delete mode 100644 auth/include/astal-auth.h delete mode 100644 auth/include/meson.build delete mode 100644 auth/meson.build delete mode 100644 auth/meson_options.txt delete mode 100644 auth/pam/astal-auth delete mode 100644 auth/src/astal-auth.c delete mode 100644 auth/src/meson.build delete mode 100644 auth/src/pam.c delete mode 100644 auth/version delete mode 100644 battery/.gitignore delete mode 100644 battery/LICENSE delete mode 100644 battery/README.md delete mode 100644 battery/flake.lock delete mode 100644 battery/flake.nix delete mode 100644 battery/meson.build delete mode 100644 battery/meson_options.txt delete mode 100644 battery/src/cli.vala delete mode 100644 battery/src/config.vala.in delete mode 100644 battery/src/device.vala delete mode 100644 battery/src/ifaces.vala delete mode 100644 battery/src/meson.build delete mode 100644 battery/src/upower.vala delete mode 100644 battery/version delete mode 100644 bluetooth/.gitignore delete mode 100644 bluetooth/LICENSE delete mode 100644 bluetooth/README.md delete mode 100644 bluetooth/flake.lock delete mode 100644 bluetooth/flake.nix delete mode 100644 bluetooth/meson.build delete mode 100644 bluetooth/src/adapter.vala delete mode 100644 bluetooth/src/bluetooth.vala delete mode 100644 bluetooth/src/config.vala.in delete mode 100644 bluetooth/src/device.vala delete mode 100644 bluetooth/src/meson.build delete mode 100644 bluetooth/src/utils.vala delete mode 100644 bluetooth/version delete mode 100644 core/default.nix delete mode 100644 hyprland/.gitignore delete mode 100644 hyprland/LICENSE delete mode 100644 hyprland/README.md delete mode 100644 hyprland/flake.lock delete mode 100644 hyprland/flake.nix delete mode 100644 hyprland/meson.build delete mode 100644 hyprland/meson_options.txt delete mode 100644 hyprland/src/cli.vala delete mode 100644 hyprland/src/client.vala delete mode 100644 hyprland/src/config.vala.in delete mode 100644 hyprland/src/hyprland.vala delete mode 100644 hyprland/src/meson.build delete mode 100644 hyprland/src/monitor.vala delete mode 100644 hyprland/src/structs.vala delete mode 100644 hyprland/src/workspace.vala delete mode 100644 hyprland/version create mode 100644 lib/apps/application.vala create mode 100644 lib/apps/apps.vala create mode 100644 lib/apps/cli.vala create mode 100644 lib/apps/config.vala.in create mode 100644 lib/apps/meson.build create mode 100644 lib/apps/meson_options.txt create mode 100644 lib/apps/version create mode 100644 lib/auth/include/astal-auth.h create mode 100644 lib/auth/include/meson.build create mode 100644 lib/auth/meson.build create mode 100644 lib/auth/meson_options.txt create mode 100644 lib/auth/pam/astal-auth create mode 100644 lib/auth/src/astal-auth.c create mode 100644 lib/auth/src/meson.build create mode 100644 lib/auth/src/pam.c create mode 100644 lib/auth/version create mode 100644 lib/battery/cli.vala create mode 100644 lib/battery/config.vala.in create mode 100644 lib/battery/device.vala create mode 100644 lib/battery/ifaces.vala create mode 100644 lib/battery/meson.build create mode 100644 lib/battery/meson_options.txt create mode 100644 lib/battery/upower.vala create mode 100644 lib/battery/version create mode 100644 lib/bluetooth/adapter.vala create mode 100644 lib/bluetooth/bluetooth.vala create mode 100644 lib/bluetooth/config.vala.in create mode 100644 lib/bluetooth/device.vala create mode 100644 lib/bluetooth/meson.build create mode 100644 lib/bluetooth/utils.vala create mode 100644 lib/bluetooth/version create mode 100644 lib/hyprland/cli.vala create mode 100644 lib/hyprland/client.vala create mode 100644 lib/hyprland/config.vala.in create mode 100644 lib/hyprland/hyprland.vala create mode 100644 lib/hyprland/meson.build create mode 100644 lib/hyprland/meson_options.txt create mode 100644 lib/hyprland/monitor.vala create mode 100644 lib/hyprland/structs.vala create mode 100644 lib/hyprland/version create mode 100644 lib/hyprland/workspace.vala create mode 100644 lib/mpris/cli.vala create mode 100644 lib/mpris/config.vala.in create mode 100644 lib/mpris/ifaces.vala create mode 100644 lib/mpris/meson.build create mode 100644 lib/mpris/meson_options.txt create mode 100644 lib/mpris/mpris.vala create mode 100644 lib/mpris/player.vala create mode 100644 lib/mpris/version create mode 100644 lib/network/accesspoint.vala create mode 100644 lib/network/config.vala.in create mode 100644 lib/network/meson.build create mode 100644 lib/network/network.vala create mode 100644 lib/network/version create mode 100644 lib/network/vpn.vala create mode 100644 lib/network/wifi.vala create mode 100644 lib/network/wired.vala create mode 100644 lib/notifd/cli.vala create mode 100644 lib/notifd/config.vala.in create mode 100644 lib/notifd/daemon.vala create mode 100644 lib/notifd/meson.build create mode 100644 lib/notifd/meson_options.txt create mode 100644 lib/notifd/notifd.vala create mode 100644 lib/notifd/notification.vala create mode 100644 lib/notifd/proxy.vala create mode 100644 lib/notifd/signals.md create mode 100644 lib/notifd/version create mode 100644 lib/powerprofiles/cli.vala create mode 100644 lib/powerprofiles/config.vala.in create mode 100644 lib/powerprofiles/meson.build create mode 100644 lib/powerprofiles/meson_options.txt create mode 100644 lib/powerprofiles/power-profiles.vala create mode 100644 lib/powerprofiles/version create mode 100644 lib/river/include/astal-river.h create mode 100644 lib/river/include/meson.build create mode 100644 lib/river/include/river-private.h create mode 100644 lib/river/include/wayland-source.h create mode 100644 lib/river/meson.build create mode 100644 lib/river/meson_options.txt create mode 100644 lib/river/protocols/meson.build create mode 100644 lib/river/protocols/river-control-unstable-v1.xml create mode 100644 lib/river/protocols/river-status-unstable-v1.xml create mode 100644 lib/river/src/astal-river.c create mode 100644 lib/river/src/meson.build create mode 100644 lib/river/src/river-output.c create mode 100644 lib/river/src/river.c create mode 100644 lib/river/src/wayland-source.c create mode 100644 lib/river/version create mode 100644 lib/tray/cli.vala create mode 100644 lib/tray/config.vala.in create mode 100644 lib/tray/meson.build create mode 100644 lib/tray/meson_options.txt create mode 100644 lib/tray/tray.vala create mode 100644 lib/tray/trayItem.vala create mode 100644 lib/tray/version create mode 100644 lib/tray/watcher.vala create mode 100644 lib/wireplumber/flake.nix create mode 100644 lib/wireplumber/include/astal-wp.h create mode 100644 lib/wireplumber/include/astal/wireplumber/audio.h create mode 100644 lib/wireplumber/include/astal/wireplumber/device.h create mode 100644 lib/wireplumber/include/astal/wireplumber/endpoint.h create mode 100644 lib/wireplumber/include/astal/wireplumber/meson.build create mode 100644 lib/wireplumber/include/astal/wireplumber/profile.h create mode 100644 lib/wireplumber/include/astal/wireplumber/video.h create mode 100644 lib/wireplumber/include/astal/wireplumber/wp.h create mode 100644 lib/wireplumber/include/meson.build create mode 100644 lib/wireplumber/include/private/device-private.h create mode 100644 lib/wireplumber/include/private/endpoint-private.h create mode 100644 lib/wireplumber/meson.build create mode 100644 lib/wireplumber/meson_options.txt create mode 100644 lib/wireplumber/src/audio.c create mode 100644 lib/wireplumber/src/device.c create mode 100644 lib/wireplumber/src/endpoint.c create mode 100644 lib/wireplumber/src/meson.build create mode 100644 lib/wireplumber/src/profile.c create mode 100644 lib/wireplumber/src/video.c create mode 100644 lib/wireplumber/src/wireplumber.c create mode 100644 lib/wireplumber/version delete mode 100644 mpris/.gitignore delete mode 100644 mpris/LICENSE delete mode 100644 mpris/README.md delete mode 100644 mpris/flake.lock delete mode 100644 mpris/flake.nix delete mode 100644 mpris/meson.build delete mode 100644 mpris/meson_options.txt delete mode 100644 mpris/src/cli.vala delete mode 100644 mpris/src/config.vala.in delete mode 100644 mpris/src/ifaces.vala delete mode 100644 mpris/src/meson.build delete mode 100644 mpris/src/mpris.vala delete mode 100644 mpris/src/player.vala delete mode 100644 mpris/version delete mode 100644 network/.gitignore delete mode 100644 network/LICENSE delete mode 100644 network/README.md delete mode 100644 network/flake.lock delete mode 100644 network/flake.nix delete mode 100644 network/meson.build delete mode 100644 network/src/accesspoint.vala delete mode 100644 network/src/config.vala.in delete mode 100644 network/src/meson.build delete mode 100644 network/src/network.vala delete mode 100644 network/src/vpn.vala delete mode 100644 network/src/wifi.vala delete mode 100644 network/src/wired.vala delete mode 100644 network/version delete mode 100644 notifd/LICENSE delete mode 100644 notifd/README.md delete mode 100644 notifd/flake.lock delete mode 100644 notifd/flake.nix delete mode 100644 notifd/meson.build delete mode 100644 notifd/meson_options.txt delete mode 100644 notifd/src/cli.vala delete mode 100644 notifd/src/config.vala.in delete mode 100644 notifd/src/daemon.vala delete mode 100644 notifd/src/meson.build delete mode 100644 notifd/src/notifd.vala delete mode 100644 notifd/src/notification.vala delete mode 100644 notifd/src/proxy.vala delete mode 100644 notifd/src/signals.md delete mode 100644 notifd/version delete mode 100644 powerprofiles/.gitignore delete mode 100644 powerprofiles/LICENSE delete mode 100644 powerprofiles/README.md delete mode 100644 powerprofiles/flake.lock delete mode 100644 powerprofiles/flake.nix delete mode 100644 powerprofiles/meson.build delete mode 100644 powerprofiles/meson_options.txt delete mode 100644 powerprofiles/src/cli.vala delete mode 100644 powerprofiles/src/config.vala.in delete mode 100644 powerprofiles/src/meson.build delete mode 100644 powerprofiles/src/power-profiles.vala delete mode 100644 powerprofiles/version delete mode 100644 river/.gitignore delete mode 100644 river/LICENSE delete mode 100644 river/README.md delete mode 100644 river/flake.lock delete mode 100644 river/flake.nix delete mode 100644 river/include/astal-river.h delete mode 100644 river/include/meson.build delete mode 100644 river/include/river-private.h delete mode 100644 river/include/wayland-source.h delete mode 100644 river/meson.build delete mode 100644 river/meson_options.txt delete mode 100644 river/protocols/meson.build delete mode 100644 river/protocols/river-control-unstable-v1.xml delete mode 100644 river/protocols/river-status-unstable-v1.xml delete mode 100644 river/src/astal-river.c delete mode 100644 river/src/meson.build delete mode 100644 river/src/river-output.c delete mode 100644 river/src/river.c delete mode 100644 river/src/wayland-source.c delete mode 100644 river/version delete mode 100644 tray/LICENSE delete mode 100644 tray/README.md delete mode 100644 tray/flake.lock delete mode 100644 tray/flake.nix delete mode 100644 tray/meson.build delete mode 100644 tray/meson_options.txt delete mode 100644 tray/src/cli.vala delete mode 100644 tray/src/config.vala.in delete mode 100644 tray/src/meson.build delete mode 100644 tray/src/tray.vala delete mode 100644 tray/src/trayItem.vala delete mode 100644 tray/src/watcher.vala delete mode 100644 tray/version delete mode 100644 wireplumber/.gitignore delete mode 100644 wireplumber/LICENSE delete mode 100644 wireplumber/README.md delete mode 100644 wireplumber/flake.lock delete mode 100644 wireplumber/flake.nix delete mode 100644 wireplumber/include/astal-wp.h delete mode 100644 wireplumber/include/astal/wireplumber/audio.h delete mode 100644 wireplumber/include/astal/wireplumber/device.h delete mode 100644 wireplumber/include/astal/wireplumber/endpoint.h delete mode 100644 wireplumber/include/astal/wireplumber/meson.build delete mode 100644 wireplumber/include/astal/wireplumber/profile.h delete mode 100644 wireplumber/include/astal/wireplumber/video.h delete mode 100644 wireplumber/include/astal/wireplumber/wp.h delete mode 100644 wireplumber/include/meson.build delete mode 100644 wireplumber/include/private/device-private.h delete mode 100644 wireplumber/include/private/endpoint-private.h delete mode 100644 wireplumber/meson.build delete mode 100644 wireplumber/meson_options.txt delete mode 100644 wireplumber/src/audio.c delete mode 100644 wireplumber/src/device.c delete mode 100644 wireplumber/src/endpoint.c delete mode 100644 wireplumber/src/meson.build delete mode 100644 wireplumber/src/profile.c delete mode 100644 wireplumber/src/video.c delete mode 100644 wireplumber/src/wireplumber.c delete mode 100644 wireplumber/version diff --git a/apps/.gitignore b/apps/.gitignore deleted file mode 100644 index f047207..0000000 --- a/apps/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -build/ -result -.cache/ -test.sh -tmp/ diff --git a/apps/LICENSE b/apps/LICENSE deleted file mode 100644 index 4362b49..0000000 --- a/apps/LICENSE +++ /dev/null @@ -1,502 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/apps/README.md b/apps/README.md deleted file mode 100644 index 04e2629..0000000 --- a/apps/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# astal-apps - -Library and cli tool for querying applications - -## TODO - -- docs -- query PATH executables diff --git a/apps/flake.lock b/apps/flake.lock deleted file mode 100644 index 13f566b..0000000 --- a/apps/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1716137900, - "narHash": "sha256-sowPU+tLQv8GlqtVtsXioTKeaQvlMz/pefcdwg8MvfM=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "6c0b7a92c30122196a761b440ac0d46d3d9954f1", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/apps/flake.nix b/apps/flake.nix deleted file mode 100644 index 419f962..0000000 --- a/apps/flake.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ - description = "Library and cli tool for querying applications"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - - outputs = { self, nixpkgs }: - let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs { inherit system; }; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - ]; - - buildInputs = with pkgs; [ - glib - json-glib - ]; - in { - packages.${system} = rec { - default = apps; - apps = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-apps"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - apps = pkgs.mkShell { - inherit nativeBuildInputs; - buildInputs = buildInputs ++ [ - pkgs.gjs - self.packages.${system}.default - ]; - }; - }; - }; -} diff --git a/apps/meson-install.sh b/apps/meson-install.sh deleted file mode 100644 index 0e6b258..0000000 --- a/apps/meson-install.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -meson setup \ - --prefix /usr \ - --libexecdir lib \ - --sbindir bin \ - --buildtype plain \ - --auto-features enabled \ - --wrap-mode nodownload \ - -D b_lto=false \ - -D b_pie=true \ - -D python.bytecompile=1 \ - --wipe \ - build - -meson install -C build diff --git a/apps/meson.build b/apps/meson.build deleted file mode 100644 index 27f8e7a..0000000 --- a/apps/meson.build +++ /dev/null @@ -1,19 +0,0 @@ -project( - 'astal-apps', - 'vala', - 'c', - version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), - meson_version: '>= 0.62.0', - default_options: [ - 'warning_level=2', - 'werror=false', - 'c_std=gnu11', - ], -) - -assert( - get_option('lib') or get_option('cli'), - 'Either lib or cli option must be set to true.', -) - -subdir('src') diff --git a/apps/meson_options.txt b/apps/meson_options.txt deleted file mode 100644 index f110242..0000000 --- a/apps/meson_options.txt +++ /dev/null @@ -1,11 +0,0 @@ -option( - 'lib', - type: 'boolean', - value: true, -) - -option( - 'cli', - type: 'boolean', - value: true, -) diff --git a/apps/src/application.vala b/apps/src/application.vala deleted file mode 100644 index 5748fc6..0000000 --- a/apps/src/application.vala +++ /dev/null @@ -1,118 +0,0 @@ -namespace AstalApps { -public class Application : Object { - public DesktopAppInfo app { get; construct set; } - public int frequency { get; set; default = 0; } - public string name { get { return app.get_name(); } } - public string entry { get { return app.get_id(); } } - public string description { get { return app.get_description(); } } - public string wm_class { get { return app.get_startup_wm_class(); } } - public string executable { owned get { return app.get_string("Exec"); } } - public string icon_name { owned get { return app.get_string("Icon"); } } - - internal Application(string id, int? frequency = 0) { - Object(app: new DesktopAppInfo(id)); - this.frequency = frequency; - } - - public string get_key(string key) { - return app.get_string(key); - } - - public bool launch() { - try { - var s = app.launch(null, null); - ++frequency; - return s; - } catch (Error err) { - critical(err.message); - return false; - } - } - - public Score fuzzy_match(string term) { - var score = Score(); - if (name != null) - score.name = levenshtein(term, name); - if (entry != null) - score.entry = levenshtein(term, entry); - if (executable != null) - score.executable = levenshtein(term, executable); - if (description != null) - score.description = levenshtein(term, description); - - return score; - } - - public Score exact_match(string term) { - var score = Score(); - if (name != null) - score.name = name.down().contains(term.down()) ? 1 : 0; - if (entry != null) - score.entry = entry.down().contains(term.down()) ? 1 : 0; - if (executable != null) - score.executable = executable.down().contains(term.down()) ? 1 : 0; - if (description != null) - score.description = description.down().contains(term.down()) ? 1 : 0; - - return score; - } - - internal Json.Node to_json() { - return new Json.Builder() - .begin_object() - .set_member_name("name").add_string_value(name) - .set_member_name("entry").add_string_value(entry) - .set_member_name("executable").add_string_value(executable) - .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) - .end_object() - .get_root(); - } -} - -int min3(int a, int b, int c) { - return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); -} - -double levenshtein(string s1, string s2) { - int len1 = s1.length; - int len2 = s2.length; - - int[, ] d = new int[len1 + 1, len2 + 1]; - - for (int i = 0; i <= len1; i++) { - d[i, 0] = i; - } - for (int j = 0; j <= len2; j++) { - d[0, j] = j; - } - - for (int i = 1; i <= len1; i++) { - for (int j = 1; j <= len2; j++) { - int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1; - d[i, j] = min3( - d[i - 1, j] + 1, // deletion - d[i, j - 1] + 1, // insertion - d[i - 1, j - 1] + cost // substitution - ); - } - } - - var distance = d[len1, len2]; - int max_len = len1 > len2 ? len1 : len2; - - if (max_len == 0) { - return 1.0; - } - - return 1.0 - ((double)distance / max_len); -} - -public struct Score { - double name; - double entry; - double executable; - double description; -} -} diff --git a/apps/src/apps.vala b/apps/src/apps.vala deleted file mode 100644 index 2a0d507..0000000 --- a/apps/src/apps.vala +++ /dev/null @@ -1,172 +0,0 @@ -namespace AstalApps { -public class Apps : Object { - private string cache_directory; - private string cache_file; - private List _list; - private HashTable frequents { get; private set; } - - public bool show_hidden { get; set; } - public List list { owned get { return _list.copy(); } } - - public double min_score { get; set; default = 0.5; } - - public double name_multiplier { get; set; default = 2; } - public double entry_multiplier { get; set; default = 1; } - public double executable_multiplier { get; set; default = 1; } - public double description_multiplier { get; set; default = 0.5; } - - public bool include_name { get; set; default = true; } - public bool include_entry { get; set; default = false; } - public bool include_executable { get; set; default = false; } - public bool include_description { get; set; default = false; } - - construct { - cache_directory = Environment.get_user_cache_dir() + "/astal"; - cache_file = cache_directory + "/apps-frequents.json"; - frequents = new HashTable(str_hash, str_equal); - - AppInfoMonitor.get().changed.connect(() => { - reload(); - }); - - if (FileUtils.test(cache_file, FileTest.EXISTS)) { - try { - uint8[] content; - File.new_for_path(cache_file).load_contents(null, out content, null); - - var parser = new Json.Parser(); - parser.load_from_data((string)content); - var obj = parser.get_root().get_object(); - foreach (var member in obj.get_members()) { - var v = obj.get_member(member).get_value().get_int64(); - frequents.set(member, (int)v); - } - } catch (Error err) { - critical("cannot read cache: %s\n", err.message); - } - } - - reload(); - } - - private double score (string search, Application a, bool exact) { - var am = exact ? a.exact_match(search) : a.fuzzy_match(search); - double r = 0; - - if (include_name) - r += am.name * name_multiplier; - if (include_entry) - r += am.entry * entry_multiplier; - if (include_executable) - r += am.executable * executable_multiplier; - if (include_description) - r += am.description * description_multiplier; - - return r; - } - - public List query(string? search = "", bool exact = false) { - if (search == null) - search = ""; - - var arr = list.copy(); - - // empty search, sort by frequency - if (search == "") { - arr.sort_with_data((a, b) => { - return (int)b.frequency - (int)a.frequency; - }); - - return arr; - } - - // single character, sort by frequency and exact match - if (search.length == 1) { - foreach (var app in list) { - if (score(search, app, true) == 0) - arr.remove(app); - } - - arr.sort_with_data((a, b) => { - return (int)b.frequency - (int)a.frequency; - }); - - return arr; - } - - // filter - foreach (var app in list) { - if (score(search, app, exact) < min_score) - arr.remove(app); - } - - // sort by score, frequency - arr.sort_with_data((a, b) => { - var s1 = score(search, a, exact); - var s2 = score(search, b, exact); - - if (s1 == s2) - return (int)b.frequency - (int)a.frequency; - - return s1 < s2 ? 1 : -1; - }); - - return arr; - } - - public List fuzzy_query(string? search = "") { - return query(search, false); - } - - public List exact_query(string? search = "") { - return query(search, true); - } - - public void reload() { - var arr = AppInfo.get_all(); - - _list = new List(); - foreach (var app in arr) { - if (!show_hidden && !app.should_show()) - continue; - - var a = new Application( - app.get_id(), - frequents.get(app.get_id()) - ); - a.notify.connect((pspec) => { - if (pspec.name != "frequency") - return; - - var f = frequents.get(app.get_id()); - frequents.set(app.get_id(), ++f); - - _list.sort((a, b) => { - return (int)a.frequency - (int)b.frequency; - }); - cache(); - }); - _list.append(a); - } - - cache(); - } - - private void cache() { - var json = new Json.Builder().begin_object(); - foreach (string key in frequents.get_keys()) - json.set_member_name(key).add_int_value(frequents.get(key)); - - try { - if (!FileUtils.test(cache_directory, FileTest.EXISTS)) - File.new_for_path(cache_directory).make_directory_with_parents(null); - - var generator = new Json.Generator(); - generator.set_root(json.end_object().get_root()); - FileUtils.set_contents_full(cache_file, generator.to_data(null)); - } catch (Error err) { - critical("cannot cache frequents: %s", err.message); - } - } -} -} diff --git a/apps/src/cli.vala b/apps/src/cli.vala deleted file mode 100644 index d926c87..0000000 --- a/apps/src/cli.vala +++ /dev/null @@ -1,66 +0,0 @@ -static bool help; -static bool version; -static string search; -static string launch; -static bool json; - -const OptionEntry[] options = { - { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, - { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, - { "search", 's', OptionFlags.NONE, OptionArg.STRING, ref search, null, null }, - { "launch", 'l', OptionFlags.NONE, OptionArg.STRING, ref launch, null, null }, - { "json", 'j', OptionFlags.NONE, OptionArg.NONE, ref json, null, null }, - { null }, -}; - -int main(string[] argv) { - try { - var opts = new OptionContext(); - opts.add_main_entries(options, null); - opts.set_help_enabled(false); - opts.set_ignore_unknown_options(false); - opts.parse(ref argv); - } catch (OptionError err) { - printerr (err.message); - return 1; - } - - if (help) { - print("Usage:\n"); - print(" %s [flags]\n\n", argv[0]); - print("Flags:\n"); - print(" -h, --help Print this help and exit\n"); - print(" -v, --version Print version number and exit\n"); - print(" -s, --search Sort by a search term\n"); - print(" -l, --launch Launch an application\n"); - print(" -j, --json Print list in json format\n"); - return 0; - } - - if (version) { - print(AstalApps.VERSION); - return 0; - } - - var apps = new AstalApps.Apps(); - - if (launch != null) { - apps.query(launch).first().data.launch(); - return 0; - } - - if (json) { - var b = new Json.Builder().begin_array(); - foreach (var app in apps.query(search)) - b.add_value(app.to_json()); - - var generator = new Json.Generator(); - generator.set_root(b.end_array().get_root()); - stdout.printf(generator.to_data(null)); - } else { - foreach (var app in apps.query(search)) - stdout.printf("%s\n", app.entry); - } - - return 0; -} diff --git a/apps/src/config.vala.in b/apps/src/config.vala.in deleted file mode 100644 index b3a9f49..0000000 --- a/apps/src/config.vala.in +++ /dev/null @@ -1,6 +0,0 @@ -namespace AstalApps { - public const int MAJOR_VERSION = @MAJOR_VERSION@; - public const int MINOR_VERSION = @MINOR_VERSION@; - public const int MICRO_VERSION = @MICRO_VERSION@; - public const string VERSION = "@VERSION@"; -} diff --git a/apps/src/meson.build b/apps/src/meson.build deleted file mode 100644 index e4f0d59..0000000 --- a/apps/src/meson.build +++ /dev/null @@ -1,77 +0,0 @@ -version_split = meson.project_version().split('.') -api_version = version_split[0] + '.' + version_split[1] -gir = 'AstalApps-' + api_version + '.gir' -typelib = 'AstalApps-' + api_version + '.typelib' - -config = configure_file( - input: 'config.vala.in', - output: 'config.vala', - configuration: { - 'VERSION': meson.project_version(), - 'MAJOR_VERSION': version_split[0], - 'MINOR_VERSION': version_split[1], - 'MICRO_VERSION': version_split[2], - }, -) - -deps = [ - dependency('glib-2.0'), - dependency('gobject-2.0'), - dependency('gio-unix-2.0'), - dependency('json-glib-1.0'), -] - -sources = [ - config, - 'apps.vala', - 'application.vala', - 'cli.vala', -] - -if get_option('lib') - lib = library( - meson.project_name(), - sources, - dependencies: deps, - vala_header: meson.project_name() + '.h', - vala_vapi: meson.project_name() + '-' + api_version + '.vapi', - vala_gir: gir, - version: meson.project_version(), - install: true, - install_dir: [true, true, true, true], - ) - - import('pkgconfig').generate( - lib, - name: meson.project_name(), - filebase: meson.project_name() + '-' + api_version, - version: meson.project_version(), - subdirs: meson.project_name(), - requires: deps, - install_dir: get_option('libdir') / 'pkgconfig', - ) - - custom_target( - typelib, - command: [ - find_program('g-ir-compiler'), - '--output', '@OUTPUT@', - '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', - meson.current_build_dir() / gir, - ], - input: lib, - output: typelib, - depends: lib, - install: true, - install_dir: get_option('libdir') / 'girepository-1.0', - ) -endif - -if get_option('cli') - executable( - meson.project_name(), - ['cli.vala', sources], - dependencies: deps, - install: true, - ) -endif diff --git a/apps/version b/apps/version deleted file mode 100644 index 6e8bf73..0000000 --- a/apps/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/auth/.gitignore b/auth/.gitignore deleted file mode 100644 index 6bf41b5..0000000 --- a/auth/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -result/ -.cache/ diff --git a/auth/LICENSE b/auth/LICENSE deleted file mode 100644 index 67cd97b..0000000 --- a/auth/LICENSE +++ /dev/null @@ -1,503 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - diff --git a/auth/README.md b/auth/README.md deleted file mode 100644 index f5d52a3..0000000 --- a/auth/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# libastal-auth -This library provides a way for authentication using pam for the libastal suite. - -## Build from source -### Dependencies - -- meson -- glib -- gobject-introspection -- pam -- vala (only required for the vapi option) - -### Meson options - -* `-Dintrospection` (default: `true`): build GObject Introspection data (needed for language bindings) -* `-Dvapi` (default: `true`): build VAPI data (required to make this lib usable in vala). Requires `-Dintrospection=true` -* `-Dexamples` (default: `false`): build examples - -```sh -# Clone the repository -git clone https://github.com/astal-sh/libastal-auth -cd libastal-auth - -# Setup and build -meson setup build -meson compile -C build - -# Install -meson install -C build -``` - -> [!NOTE] -> on NixOS you will have to add `security.pam.services.astal-auth = {}` in `configuration.nix` - -## Usage -This library can be used from any language supporting GObject Introspection. -Have a look at the [examples](examples) for how it can be used in C and gjs. - -The authentication is done asynchronously in its own thread, therefore the GLib mainloop is required to run. -This is already given in all gtk application, but has to be started manually in some cases like in the small examples in this repo. - -Until there are better docs, please refer to the [auth.h](include/auth.h) file for detailed usage. - -For simple authentication using only a password, using the `Pam.authenticate()` method is recommended. -Look at the simple examples for how to use it. - -There is also a way to get access to the pam conversation, to allow for a more complex authentication process, like using multiple factor authentication. -The full examples show how this can be achieved. -Generally it can be used like this: - -1. create the Pam object. -2. set username and service if so required. It has sane defaults, so in most cases you can skip this. -3. connect to the signals - - `auth-prompt-hidden`: is emitted when user input is required, and the input should be hidden (eg, passwords) - - `auth-prompt-visible`: is emitted when user input is required, and the input should be visible (eg, OTP) - - `auth-info`: an information message should be displayed (eg, tell the user to touch his security key) - - `auth-error`: an error message should be displayed - - `sucess`: emitted on successful authentication - - `fail`: emitted on failed authentication - - all signals except the `success` signal have a string containing the message as a parameter. - After an `auth-*` signal is emitted, it hs to be responded with exactly one `pam.supply_secret(secret)` call. The secret is a string containing the user input. For `auth-info` and `auth-error` it can be `NULL`. - Not connecting those signals, is equivalent to calling `pam.supply_secret(NULL)` immediately after the signal is emitted. -4. start authentication process using `Pam.start_authentication()`. This function will return whether the authentication was started or not. -5. it is possible to reuse the same Pam object for multiple sequential authentication attempts. Just call `pam.start_authentication()` again after the `success` or `fail` signal was emitted. - diff --git a/auth/examples/full_example.c b/auth/examples/full_example.c deleted file mode 100644 index a20c02b..0000000 --- a/auth/examples/full_example.c +++ /dev/null @@ -1,66 +0,0 @@ -#include - -#include "astal-auth.h" - -GMainLoop *loop; - -static void authenticate(AstalAuthPam *pam) { - if (!astal_auth_pam_start_authenticate(pam)) { - g_print("could not start authentication process\n"); - g_object_unref(pam); - g_main_loop_quit(loop); - } -} - -static void on_visible(AstalAuthPam *pam, const gchar *data) { - gchar passbuf[1024]; - readpassphrase(data, passbuf, sizeof(passbuf), RPP_ECHO_ON); - astal_auth_pam_supply_secret(pam, passbuf); -} - -static void on_hidden(AstalAuthPam *pam, const gchar *data) { - gchar passbuf[1024]; - readpassphrase(data, passbuf, sizeof(passbuf), RPP_ECHO_OFF); - astal_auth_pam_supply_secret(pam, passbuf); -} - -static void on_info(AstalAuthPam *pam, const gchar *data) { - g_print("info: %s\n", data); - astal_auth_pam_supply_secret(pam, NULL); -} - -static void on_error(AstalAuthPam *pam, const gchar *data) { - g_print("error: %s\n", data); - astal_auth_pam_supply_secret(pam, NULL); -} - -static void on_success(AstalAuthPam *pam) { - g_print("success\n"); - g_object_unref(pam); - g_main_loop_quit(loop); -} - -static void on_fail(AstalAuthPam *pam, const gchar *data) { - g_print("fail: %s\n", data); - authenticate(pam); -} - -int main(void) { - GMainContext *loopctx = NULL; - - loop = g_main_loop_new(loopctx, FALSE); - - AstalAuthPam *pam = g_object_new(ASTAL_AUTH_TYPE_PAM, NULL); - - g_signal_connect(pam, "auth-prompt-visible", G_CALLBACK(on_visible), NULL); - g_signal_connect(pam, "auth-prompt-hidden", G_CALLBACK(on_hidden), NULL); - g_signal_connect(pam, "auth-info", G_CALLBACK(on_info), NULL); - g_signal_connect(pam, "auth-error", G_CALLBACK(on_error), NULL); - - g_signal_connect(pam, "success", G_CALLBACK(on_success), NULL); - g_signal_connect(pam, "fail", G_CALLBACK(on_fail), NULL); - - authenticate(pam); - - g_main_loop_run(loop); -} diff --git a/auth/examples/full_example.js b/auth/examples/full_example.js deleted file mode 100644 index 7359784..0000000 --- a/auth/examples/full_example.js +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env -S gjs -m - -import Auth from "gi://AstalAuth"; -import GLib from "gi://GLib"; - -const loop = GLib.MainLoop.new(null, false); - -const pam = new Auth.Pam(); -pam.connect("auth-prompt-visible", (p, msg) => { - print(msg); - p.supply_secret(""); -}); -pam.connect("auth-prompt-hidden", (p, msg) => { - print(msg); - p.supply_secret("password"); -}); -pam.connect("auth-info", (p, msg) => { - print(msg); - p.supply_secret(""); -}); -pam.connect("auth-error", (p, msg) => { - print(msg); - p.supply_secret(""); -}); - -pam.connect("success", p => { - print("authentication sucessful"); - loop.quit(); -}); -pam.connect("fail", (p, msg) => { - print(msg); - loop.quit(); -}); - -pam.start_authenticate(); - -loop.runAsync() - diff --git a/auth/examples/meson.build b/auth/examples/meson.build deleted file mode 100644 index cf23d3f..0000000 --- a/auth/examples/meson.build +++ /dev/null @@ -1,18 +0,0 @@ - -deps_example = [ - dependency('gobject-2.0'), - dependency('libbsd'), - libastal_auth -] - -astal_auth_full_exmple = executable( - 'astal_auth_full_example', - files('full_example.c'), - dependencies : deps_example, - install : false) - -astal_auth_simple_example = executable( - 'astal_auth_simple_example', - files('simple_example.c'), - dependencies : deps_example, - install : false) diff --git a/auth/examples/simple_example.c b/auth/examples/simple_example.c deleted file mode 100644 index d00bad2..0000000 --- a/auth/examples/simple_example.c +++ /dev/null @@ -1,31 +0,0 @@ -#include - -#include "astal-auth.h" - -GMainLoop *loop; - -void ready_callback(AstalAuthPam *pam, GAsyncResult *res, gpointer user_data) { - GError *error = NULL; - astal_auth_pam_authenticate_finish(res, &error); - if (error == NULL) { - g_print("success\n"); - } else { - g_print("failure: %s\n", error->message); - g_error_free(error); - } - - g_main_loop_quit(loop); -} - -int main(void) { - GMainContext *loopctx = NULL; - loop = g_main_loop_new(loopctx, FALSE); - - gchar *passbuf = calloc(1024, sizeof(gchar)); - readpassphrase("Password: ", passbuf, 1024, RPP_ECHO_OFF); - astal_auth_pam_authenticate(passbuf, (GAsyncReadyCallback)ready_callback, NULL); - g_free(passbuf); - - g_main_loop_run(loop); - exit(EXIT_SUCCESS); -} diff --git a/auth/examples/simple_example.js b/auth/examples/simple_example.js deleted file mode 100644 index 2bf38c1..0000000 --- a/auth/examples/simple_example.js +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env -S gjs -m -import Auth from "gi://AstalAuth"; -import Gio from "gi://Gio"; - -Gio._promisify(Auth.Pam, "authenticate"); - -await Auth.Pam.authenticate("password") - .then(_ => print("authentication sucessful")) - .catch(logError); \ No newline at end of file diff --git a/auth/flake.lock b/auth/flake.lock deleted file mode 100644 index 13f566b..0000000 --- a/auth/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1716137900, - "narHash": "sha256-sowPU+tLQv8GlqtVtsXioTKeaQvlMz/pefcdwg8MvfM=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "6c0b7a92c30122196a761b440ac0d46d3d9954f1", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/auth/flake.nix b/auth/flake.nix deleted file mode 100644 index 39b0289..0000000 --- a/auth/flake.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ - description = "Authentication library and cli tool"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; - - outputs = { self, nixpkgs }: - let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs { inherit system; }; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - ]; - - buildInputs = with pkgs; [ - glib - pam - ]; - in { - packages.${system} = rec { - default = auth; - auth = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-auth"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - }; - }; -} diff --git a/auth/include/astal-auth.h b/auth/include/astal-auth.h deleted file mode 100644 index a3073ff..0000000 --- a/auth/include/astal-auth.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef ASTAL_AUTH_PAM_H -#define ASTAL_AUTH_PAM_H - -#include -#include - -G_BEGIN_DECLS - -#define ASTAL_AUTH_TYPE_PAM (astal_auth_pam_get_type()) - -G_DECLARE_FINAL_TYPE(AstalAuthPam, astal_auth_pam, ASTAL_AUTH, PAM, GObject) - -void astal_auth_pam_set_username(AstalAuthPam *self, const gchar *username); - -const gchar *astal_auth_pam_get_username(AstalAuthPam *self); - -void astal_auth_pam_set_service(AstalAuthPam *self, const gchar *service); - -const gchar *astal_auth_pam_get_service(AstalAuthPam *self); - -gboolean astal_auth_pam_start_authenticate(AstalAuthPam *self); - -void astal_auth_pam_supply_secret(AstalAuthPam *self, const gchar *secret); - -gboolean astal_auth_pam_authenticate(const gchar *password, GAsyncReadyCallback result_callback, - gpointer user_data); - -gssize astal_auth_pam_authenticate_finish(GAsyncResult *res, GError **error); - -G_END_DECLS - -#endif // !ASTAL_AUTH_PAM_H diff --git a/auth/include/meson.build b/auth/include/meson.build deleted file mode 100644 index 0575998..0000000 --- a/auth/include/meson.build +++ /dev/null @@ -1,4 +0,0 @@ -astal_auth_inc = include_directories('.') -astal_auth_headers = files('astal-auth.h') - -install_headers('astal-auth.h') diff --git a/auth/meson.build b/auth/meson.build deleted file mode 100644 index e9facb1..0000000 --- a/auth/meson.build +++ /dev/null @@ -1,33 +0,0 @@ -project('astal_auth', - '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'], - language : 'c') - -version_split = meson.project_version().split('.') -lib_so_version = version_split[0] + '.' + version_split[1] - -pkg_config = import('pkgconfig') -gnome = import('gnome') - -subdir('include') -subdir('src') - - -if get_option('examples') - subdir('examples') -endif - - -install_data( - 'pam/astal-auth', - install_dir : get_option('sysconfdir') / 'pam.d' -) diff --git a/auth/meson_options.txt b/auth/meson_options.txt deleted file mode 100644 index e28447e..0000000 --- a/auth/meson_options.txt +++ /dev/null @@ -1,3 +0,0 @@ -option('examples', type : 'boolean', value : false, description : 'Build example applications') -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/auth/pam/astal-auth b/auth/pam/astal-auth deleted file mode 100644 index 41f79d7..0000000 --- a/auth/pam/astal-auth +++ /dev/null @@ -1,5 +0,0 @@ -# PAM configuration file for the astal-auth library. -# By default, it only includes the 'login' -# configuration file (see /etc/pam.d/login) - -auth include login diff --git a/auth/src/astal-auth.c b/auth/src/astal-auth.c deleted file mode 100644 index 1ac2bd7..0000000 --- a/auth/src/astal-auth.c +++ /dev/null @@ -1,153 +0,0 @@ -#include "astal-auth.h" - -#include -#include -#include - -GMainLoop *loop; - -static void cleanup_and_quit(AstalAuthPam *pam, int status) { - g_object_unref(pam); - g_main_loop_quit(loop); - exit(status); -} - -static char *read_secret(const char *msg, gboolean echo) { - struct termios oldt, newt; - char *password = NULL; - size_t size = 0; - ssize_t len; - - if (tcgetattr(STDIN_FILENO, &oldt) != 0) { - return NULL; - } - newt = oldt; - if (echo) { - newt.c_lflag |= ECHO; - } else { - newt.c_lflag &= ~(ECHO); - } - if (tcsetattr(STDIN_FILENO, TCSANOW, &newt) != 0) { - return NULL; - } - g_print("%s", msg); - if ((len = getline(&password, &size, stdin)) == -1) { - g_free(password); - return NULL; - } - - if (password[len - 1] == '\n') { - password[len - 1] = '\0'; - } - - printf("\n"); - - if (tcsetattr(STDIN_FILENO, TCSANOW, &oldt) != 0) { - return NULL; - } - - return password; -} - -static void authenticate(AstalAuthPam *pam) { - static int attempts = 0; - if (attempts >= 3) { - g_print("%d failed attempts.\n", attempts); - cleanup_and_quit(pam, EXIT_FAILURE); - } - if (!astal_auth_pam_start_authenticate(pam)) { - g_print("could not start authentication process\n"); - cleanup_and_quit(pam, EXIT_FAILURE); - } - attempts++; -} - -static void on_visible(AstalAuthPam *pam, const gchar *data) { - char *secret = read_secret(data, TRUE); - if (secret == NULL) cleanup_and_quit(pam, EXIT_FAILURE); - astal_auth_pam_supply_secret(pam, secret); - g_free(secret); -} - -static void on_hidden(AstalAuthPam *pam, const gchar *data, gchar *secret) { - if (!secret) secret = read_secret(data, FALSE); - if (secret == NULL) cleanup_and_quit(pam, EXIT_FAILURE); - astal_auth_pam_supply_secret(pam, secret); - g_free(secret); -} - -static void on_info(AstalAuthPam *pam, const gchar *data) { - g_print("info: %s\n", data); - astal_auth_pam_supply_secret(pam, NULL); -} - -static void on_error(AstalAuthPam *pam, const gchar *data) { - g_print("error: %s\n", data); - astal_auth_pam_supply_secret(pam, NULL); -} - -static void on_success(AstalAuthPam *pam) { - g_print("Authentication successful\n"); - cleanup_and_quit(pam, EXIT_SUCCESS); -} - -static void on_fail(AstalAuthPam *pam, const gchar *data, gboolean retry) { - g_print("%s\n", data); - if (retry) - authenticate(pam); - else - cleanup_and_quit(pam, EXIT_FAILURE); -} - -int main(int argc, char **argv) { - char *password = NULL; - char *username = NULL; - char *service = NULL; - - int opt; - const char *optstring = "p:u:s:"; - - static struct option long_options[] = {{"password", required_argument, NULL, 'p'}, - {"username", required_argument, NULL, 'u'}, - {"service", required_argument, NULL, 's'}, - {NULL, 0, NULL, 0}}; - - while ((opt = getopt_long(argc, argv, optstring, long_options, NULL)) != -1) { - switch (opt) { - case 'p': - password = optarg; - break; - case 'u': - username = optarg; - break; - case 's': - service = optarg; - break; - default: - g_print("Usage: %s [-p password] [-u username] [-s service]\n", argv[0]); - exit(EXIT_FAILURE); - } - } - - loop = g_main_loop_new(NULL, FALSE); - - AstalAuthPam *pam = g_object_new(ASTAL_AUTH_TYPE_PAM, NULL); - - if (username) astal_auth_pam_set_username(pam, username); - if (service) astal_auth_pam_set_service(pam, service); - if (password) { - g_signal_connect(pam, "fail", G_CALLBACK(on_fail), (void *)FALSE); - } else { - g_signal_connect(pam, "auth-prompt-visible", G_CALLBACK(on_visible), NULL); - g_signal_connect(pam, "auth-info", G_CALLBACK(on_info), NULL); - g_signal_connect(pam, "auth-error", G_CALLBACK(on_error), NULL); - g_signal_connect(pam, "fail", G_CALLBACK(on_fail), (void *)TRUE); - } - - g_signal_connect(pam, "auth-prompt-hidden", G_CALLBACK(on_hidden), g_strdup(password)); - g_signal_connect(pam, "success", G_CALLBACK(on_success), NULL); - - authenticate(pam); - - g_main_loop_run(loop); -} diff --git a/auth/src/meson.build b/auth/src/meson.build deleted file mode 100644 index 6a34ae0..0000000 --- a/auth/src/meson.build +++ /dev/null @@ -1,65 +0,0 @@ -srcs = files( - 'pam.c', -) - -deps = [ - dependency('gobject-2.0'), - dependency('gio-2.0'), - dependency('pam') -] - -astal_auth_lib = library( - 'astal-auth', - sources : srcs, - include_directories : astal_auth_inc, - dependencies : deps, - version : meson.project_version(), - install : true -) - -libastal_auth = declare_dependency( - link_with : astal_auth_lib, - include_directories : astal_auth_inc) - -astal_auth_executable = executable( - 'astal-auth', - files('astal-auth.c'), - dependencies : [ - dependency('gobject-2.0'), - libastal_auth - ], - install : true) - -pkg_config_name = 'astal-auth-' + lib_so_version - -if get_option('introspection') - gir = gnome.generate_gir( - astal_auth_lib, - sources : srcs + astal_auth_headers, - nsversion : '0.1', - namespace : 'AstalAuth', - symbol_prefix : 'astal_auth', - identifier_prefix : 'AstalAuth', - includes : ['GObject-2.0', 'Gio-2.0'], - header : 'astal-auth.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-auth', - version : meson.project_version(), - libraries : [astal_auth_lib], - filebase : pkg_config_name, - subdirs : 'astal', - description : 'astal authentication module', - url : 'https://github.com/astal-sh/auth') diff --git a/auth/src/pam.c b/auth/src/pam.c deleted file mode 100644 index d0afec4..0000000 --- a/auth/src/pam.c +++ /dev/null @@ -1,524 +0,0 @@ -#include -#include -#include - -#include "astal-auth.h" - -struct _AstalAuthPam { - GObject parent_instance; - - gchar *username; - gchar *service; -}; - -typedef struct { - GTask *task; - GMainContext *context; - GMutex data_mutex; - GCond data_cond; - - gchar *secret; - gboolean secret_set; -} AstalAuthPamPrivate; - -typedef struct { - AstalAuthPam *pam; - guint signal_id; - gchar *msg; -} AstalAuthPamSignalEmitData; - -static void astal_auth_pam_signal_emit_data_free(AstalAuthPamSignalEmitData *data) { - g_free(data->msg); - g_free(data); -} - -typedef enum { - ASTAL_AUTH_PAM_SIGNAL_PROMPT_VISIBLE, - ASTAL_AUTH_PAM_SIGNAL_PROMPT_HIDDEN, - ASTAL_AUTH_PAM_SIGNAL_INFO, - ASTAL_AUTH_PAM_SIGNAL_ERROR, - ASTAL_AUTH_PAM_SIGNAL_SUCCESS, - ASTAL_AUTH_PAM_SIGNAL_FAIL, - ASTAL_AUTH_PAM_N_SIGNALS -} AstalAuthPamSignals; - -typedef enum { - ASTAL_AUTH_PAM_PROP_USERNAME = 1, - ASTAL_AUTH_PAM_PROP_SERVICE, - ASTAL_AUTH_PAM_N_PROPERTIES -} AstalAuthPamProperties; - -static guint astal_auth_pam_signals[ASTAL_AUTH_PAM_N_SIGNALS] = { - 0, -}; -static GParamSpec *astal_auth_pam_properties[ASTAL_AUTH_PAM_N_PROPERTIES] = { - NULL, -}; - -G_DEFINE_TYPE_WITH_PRIVATE(AstalAuthPam, astal_auth_pam, G_TYPE_OBJECT); - -/** - * - * AstalAuthPam - * - * For simple authentication using only a password, using the [func@AstalAuth.Pam.authenticate] - * method is recommended. Look at the simple examples for how to use it. - * - * There is also a way to get access to the pam conversation, to allow for a more complex - * authentication process, like using multiple factor authentication. Generally it can be used like - * this: - * - * 1. create the Pam object. - * 2. set username and service if so required. It has sane defaults, so in most cases you can skip - * this. - * 3. connect to the signals. - * After an `auth-*` signal is emitted, it has to be responded with exactly one - * [method@AstalAuth.Pam.supply_secret] call. The secret is a string containing the user input. For - * [auth-info][signal@AstalAuth.Pam::auth-info:] and [auth-error][signal@AstalAuth.Pam::auth-error:] - * it should be `NULL`. Not connecting those signals, is equivalent to calling - * [method@AstalAuth.Pam.supply_secret] with `NULL` immediately after the signal is emitted. - * 4. start authentication process using [method@AstalAuth.Pam.start_authenticate]. - * 5. it is possible to reuse the same Pam object for multiple sequential authentication attempts. - * Just call [method@AstalAuth.Pam.start_authenticate] again after the `success` or `fail` signal - * was emitted. - * - */ - -/** - * astal_auth_pam_set_username - * @self: a AstalAuthPam object - * @username: the new username - * - * Sets the username to be used for authentication. This must be set to - * before calling start_authenticate. - * Changing it afterwards has no effect on the authentication process. - * - * Defaults to the owner of the process. - * - */ -void astal_auth_pam_set_username(AstalAuthPam *self, const gchar *username) { - g_return_if_fail(ASTAL_AUTH_IS_PAM(self)); - g_return_if_fail(username != NULL); - - g_free(self->username); - self->username = g_strdup(username); - g_object_notify(G_OBJECT(self), "username"); -} - -/** - * astal_auth_pam_supply_secret - * @self: a AstalAuthPam Object - * @secret: (nullable): the secret to be provided to pam. Can be NULL. - * - * provides pam with a secret. This method must be called exactly once after a - * auth-* signal is emitted. - */ -void astal_auth_pam_supply_secret(AstalAuthPam *self, const gchar *secret) { - g_return_if_fail(ASTAL_AUTH_IS_PAM(self)); - AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); - - g_mutex_lock(&priv->data_mutex); - g_free(priv->secret); - priv->secret = g_strdup(secret); - priv->secret_set = TRUE; - g_cond_signal(&priv->data_cond); - g_mutex_unlock(&priv->data_mutex); -} - -/** - * astal_auth_pam_set_service - * @self: a AstalAuthPam object - * @service: the pam service used for authentication - * - * Sets the service to be used for authentication. This must be set to - * before calling start_authenticate. - * Changing it afterwards has no effect on the authentication process. - * - * Defaults to `astal-auth`. - * - */ -void astal_auth_pam_set_service(AstalAuthPam *self, const gchar *service) { - g_return_if_fail(ASTAL_AUTH_IS_PAM(self)); - g_return_if_fail(service != NULL); - - g_free(self->service); - self->service = g_strdup(service); - g_object_notify(G_OBJECT(self), "service"); -} - -/** - * astal_auth_pam_get_username - * @self: a AstalAuthPam object - * - * Fetches the username from AsalAuthPam object. - * - * Returns: the username of the AsalAuthPam object. This string is - * owned by the object and must not be modified or freed. - */ - -const gchar *astal_auth_pam_get_username(AstalAuthPam *self) { - g_return_val_if_fail(ASTAL_AUTH_IS_PAM(self), NULL); - return self->username; -} - -/** - * astal_auth_pam_get_service - * @self: a AstalAuthPam - * - * Fetches the service from AsalAuthPam object. - * - * Returns: the service of the AsalAuthPam object. This string is - * owned by the object and must not be modified or freed. - */ -const gchar *astal_auth_pam_get_service(AstalAuthPam *self) { - g_return_val_if_fail(ASTAL_AUTH_IS_PAM(self), NULL); - return self->service; -} - -static void astal_auth_pam_set_property(GObject *object, guint property_id, const GValue *value, - GParamSpec *pspec) { - AstalAuthPam *self = ASTAL_AUTH_PAM(object); - - switch (property_id) { - case ASTAL_AUTH_PAM_PROP_USERNAME: - astal_auth_pam_set_username(self, g_value_get_string(value)); - break; - case ASTAL_AUTH_PAM_PROP_SERVICE: - astal_auth_pam_set_service(self, g_value_get_string(value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_auth_pam_get_property(GObject *object, guint property_id, GValue *value, - GParamSpec *pspec) { - AstalAuthPam *self = ASTAL_AUTH_PAM(object); - - switch (property_id) { - case ASTAL_AUTH_PAM_PROP_USERNAME: - g_value_set_string(value, self->username); - break; - case ASTAL_AUTH_PAM_PROP_SERVICE: - g_value_set_string(value, self->service); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_auth_pam_callback(GObject *object, GAsyncResult *res, gpointer user_data) { - AstalAuthPam *self = ASTAL_AUTH_PAM(object); - AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); - - GTask *task = g_steal_pointer(&priv->task); - - GError *error = NULL; - g_task_propagate_int(task, &error); - - if (error == NULL) { - g_signal_emit(self, astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_SUCCESS], 0); - } else { - g_signal_emit(self, astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_FAIL], 0, error->message); - g_error_free(error); - } - g_object_unref(task); -} - -static gboolean astal_auth_pam_emit_signal_in_context(gpointer user_data) { - AstalAuthPamSignalEmitData *data = user_data; - g_signal_emit(data->pam, data->signal_id, 0, data->msg); - return G_SOURCE_REMOVE; -} - -static void astal_auth_pam_emit_signal(AstalAuthPam *pam, guint signal, const gchar *msg) { - GSource *emit_source; - AstalAuthPamSignalEmitData *data; - - data = g_new0(AstalAuthPamSignalEmitData, 1); - data->pam = pam; - data->signal_id = astal_auth_pam_signals[signal]; - data->msg = g_strdup(msg); - - emit_source = g_idle_source_new(); - g_source_set_callback(emit_source, astal_auth_pam_emit_signal_in_context, data, - (GDestroyNotify)astal_auth_pam_signal_emit_data_free); - g_source_set_priority(emit_source, G_PRIORITY_DEFAULT); - g_source_attach(emit_source, - ((AstalAuthPamPrivate *)astal_auth_pam_get_instance_private(pam))->context); - g_source_unref(emit_source); -} - -int astal_auth_pam_handle_conversation(int num_msg, const struct pam_message **msg, - struct pam_response **resp, void *appdata_ptr) { - AstalAuthPam *self = appdata_ptr; - AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); - - struct pam_response *replies = NULL; - if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) { - return PAM_CONV_ERR; - } - replies = (struct pam_response *)calloc(num_msg, sizeof(struct pam_response)); - if (replies == NULL) { - return PAM_BUF_ERR; - } - for (int i = 0; i < num_msg; ++i) { - guint signal; - switch (msg[i]->msg_style) { - case PAM_PROMPT_ECHO_OFF: - signal = ASTAL_AUTH_PAM_SIGNAL_PROMPT_HIDDEN; - break; - case PAM_PROMPT_ECHO_ON: - signal = ASTAL_AUTH_PAM_SIGNAL_PROMPT_VISIBLE; - break; - case PAM_ERROR_MSG: - signal = ASTAL_AUTH_PAM_SIGNAL_ERROR; - ; - break; - case PAM_TEXT_INFO: - signal = ASTAL_AUTH_PAM_SIGNAL_INFO; - break; - default: - g_free(replies); - return PAM_CONV_ERR; - break; - } - guint signal_id = astal_auth_pam_signals[signal]; - if (g_signal_has_handler_pending(self, signal_id, 0, FALSE)) { - astal_auth_pam_emit_signal(self, signal, msg[i]->msg); - g_mutex_lock(&priv->data_mutex); - while (!priv->secret_set) { - g_cond_wait(&priv->data_cond, &priv->data_mutex); - } - replies[i].resp_retcode = 0; - replies[i].resp = g_strdup(priv->secret); - g_free(priv->secret); - priv->secret = NULL; - priv->secret_set = FALSE; - g_mutex_unlock(&priv->data_mutex); - } - } - *resp = replies; - return PAM_SUCCESS; -} - -static void astal_auth_pam_thread(GTask *task, gpointer object, gpointer task_data, - GCancellable *cancellable) { - AstalAuthPam *self = g_task_get_source_object(task); - - pam_handle_t *pamh = NULL; - const struct pam_conv conv = { - .conv = astal_auth_pam_handle_conversation, - .appdata_ptr = self, - }; - - int retval; - retval = pam_start(self->service, self->username, &conv, &pamh); - if (retval == PAM_SUCCESS) { - retval = pam_authenticate(pamh, 0); - pam_end(pamh, retval); - } - if (retval != PAM_SUCCESS) { - g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_FAILED, "%s", - pam_strerror(pamh, retval)); - } else { - g_task_return_int(task, retval); - } -} - -gboolean astal_auth_pam_start_authenticate_with_callback(AstalAuthPam *self, - GAsyncReadyCallback result_callback, - gpointer user_data) { - g_return_val_if_fail(ASTAL_AUTH_IS_PAM(self), FALSE); - AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); - g_return_val_if_fail(priv->task == NULL, FALSE); - - priv->task = g_task_new(self, NULL, result_callback, user_data); - g_task_set_priority(priv->task, 0); - g_task_set_name(priv->task, "[AstalAuth] authenticate"); - g_task_run_in_thread(priv->task, astal_auth_pam_thread); - - return TRUE; -} - -/** - * astal_auth_pam_start_authenticate: - * @self: a AstalAuthPam Object - * - * starts a new authentication process using the PAM (Pluggable Authentication Modules) system. - * Note that this will cancel an already running authentication process - * associated with this AstalAuthPam object. - */ -gboolean astal_auth_pam_start_authenticate(AstalAuthPam *self) { - return astal_auth_pam_start_authenticate_with_callback( - self, (GAsyncReadyCallback)astal_auth_pam_callback, NULL); -} - -static void astal_auth_pam_on_hidden(AstalAuthPam *pam, const gchar *msg, gchar *password) { - astal_auth_pam_supply_secret(pam, password); - g_free(password); -} - -/** - * astal_auth_pam_authenticate: - * @password: the password to be authenticated - * @result_callback: (scope async) (closure user_data): a GAsyncReadyCallback - * to call when the request is satisfied - * @user_data: the data to pass to callback function - * - * Requests authentication of the provided password using the PAM (Pluggable Authentication Modules) - * system. - */ -gboolean astal_auth_pam_authenticate(const gchar *password, GAsyncReadyCallback result_callback, - gpointer user_data) { - AstalAuthPam *pam = g_object_new(ASTAL_AUTH_TYPE_PAM, NULL); - g_signal_connect(pam, "auth-prompt-hidden", G_CALLBACK(astal_auth_pam_on_hidden), - (void *)g_strdup(password)); - - gboolean started = - astal_auth_pam_start_authenticate_with_callback(pam, result_callback, user_data); - g_object_unref(pam); - return started; -} - -gssize astal_auth_pam_authenticate_finish(GAsyncResult *res, GError **error) { - return g_task_propagate_int(G_TASK(res), error); -} - -static void astal_auth_pam_init(AstalAuthPam *self) { - AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); - - priv->secret = NULL; - - g_cond_init(&priv->data_cond); - g_mutex_init(&priv->data_mutex); - - priv->context = g_main_context_get_thread_default(); -} - -static void astal_auth_pam_finalize(GObject *gobject) { - AstalAuthPam *self = ASTAL_AUTH_PAM(gobject); - AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); - - g_free(self->username); - g_free(self->service); - - g_free(priv->secret); - - g_cond_clear(&priv->data_cond); - g_mutex_clear(&priv->data_mutex); - - G_OBJECT_CLASS(astal_auth_pam_parent_class)->finalize(gobject); -} - -static void astal_auth_pam_class_init(AstalAuthPamClass *class) { - GObjectClass *object_class = G_OBJECT_CLASS(class); - - object_class->get_property = astal_auth_pam_get_property; - object_class->set_property = astal_auth_pam_set_property; - - object_class->finalize = astal_auth_pam_finalize; - - struct passwd *passwd = getpwuid(getuid()); - - /** - * AstalAuthPam:username: - * - * The username used for authentication. - * Changing the value of this property has no affect on an already started authentication - * process. - * - * Defaults to the user that owns this process. - */ - astal_auth_pam_properties[ASTAL_AUTH_PAM_PROP_USERNAME] = - g_param_spec_string("username", "username", "username used for authentication", - passwd->pw_name, G_PARAM_CONSTRUCT | G_PARAM_READWRITE); - /** - * AstalAuthPam:service: - * - * The pam service used for authentication. - * Changing the value of this property has no affect on an already started authentication - * process. - * - * Defaults to the astal-auth pam service. - */ - astal_auth_pam_properties[ASTAL_AUTH_PAM_PROP_SERVICE] = - g_param_spec_string("service", "service", "the pam service to use", "astal-auth", - G_PARAM_CONSTRUCT | G_PARAM_READWRITE); - - g_object_class_install_properties(object_class, ASTAL_AUTH_PAM_N_PROPERTIES, - astal_auth_pam_properties); - /** - * AstalAuthPam::auth-prompt-visible: - * @pam: the object which received the signal. - * @msg: the prompt to be shown to the user - * - * This signal is emitted when user input is required. The input should be visible - * when entered (e.g., for One-Time Passwords (OTP)). - * - * This signal has to be matched with exaclty one supply_secret call. - */ - astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_PROMPT_VISIBLE] = - g_signal_new("auth-prompt-visible", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, - NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); - /** - * AstalAuthPam::auth-prompt-hidden: - * @pam: the object which received the signal. - * @msg: the prompt to be shown to the user - * - * This signal is emitted when user input is required. The input should be hidden - * when entered (e.g., for passwords). - * - * This signal has to be matched with exaclty one supply_secret call. - */ - astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_PROMPT_HIDDEN] = - g_signal_new("auth-prompt-hidden", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, - NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); - /** - * AstalAuthPam::auth-info: - * @pam: the object which received the signal. - * @msg: the info mssage to be shown to the user - * - * This signal is emitted when the user should receive an information (e.g., tell the user to - * touch a security key, or the remaining time pam has been locked after multiple failed - * attempts) - * - * This signal has to be matched with exaclty one supply_secret call. - */ - astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_INFO] = - g_signal_new("auth-info", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, - G_TYPE_NONE, 1, G_TYPE_STRING); - /** - * AstalAuthPam::auth-error: - * @pam: the object which received the signal. - * @msg: the error message - * - * This signal is emitted when an authentication error has occured. - * - * This signal has to be matched with exaclty one supply_secret call. - */ - astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_ERROR] = - g_signal_new("auth-error", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, G_TYPE_STRING); - /** - * AstalAuthPam::success: - * @pam: the object which received the signal. - * - * This signal is emitted after successful authentication - */ - astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_SUCCESS] = - g_signal_new("success", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, - G_TYPE_NONE, 0); - /** - * AstalAuthPam::fail: - * @pam: the object which received the signal. - * @msg: the authentication failure message - * - * This signal is emitted when authentication failed. - */ - astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_FAIL] = - g_signal_new("fail", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, - G_TYPE_NONE, 1, G_TYPE_STRING); -} diff --git a/auth/version b/auth/version deleted file mode 100644 index 6e8bf73..0000000 --- a/auth/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/battery/.gitignore b/battery/.gitignore deleted file mode 100644 index f047207..0000000 --- a/battery/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -build/ -result -.cache/ -test.sh -tmp/ diff --git a/battery/LICENSE b/battery/LICENSE deleted file mode 100644 index 4362b49..0000000 --- a/battery/LICENSE +++ /dev/null @@ -1,502 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/battery/README.md b/battery/README.md deleted file mode 100644 index 2f853cc..0000000 --- a/battery/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# astal-battery - -DBus proxy library for upower daemon devices - -## TODO - -- docs diff --git a/battery/flake.lock b/battery/flake.lock deleted file mode 100644 index 2fc5fa2..0000000 --- a/battery/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1718895438, - "narHash": "sha256-k3JqJrkdoYwE3fHE6xGDY676AYmyh4U2Zw+0Bwe5DLU=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "d603719ec6e294f034936c0d0dc06f689d91b6c3", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/battery/flake.nix b/battery/flake.nix deleted file mode 100644 index ba1280b..0000000 --- a/battery/flake.nix +++ /dev/null @@ -1,47 +0,0 @@ -{ - description = "DBus proxy library for upower daemon devices"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - - outputs = { self, nixpkgs }: let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs { inherit system; }; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - ]; - - buildInputs = with pkgs; [ - glib - ]; - in { - packages.${system} = rec { - default = battery; - battery = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-battery"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - battery = pkgs.mkShell { - inherit nativeBuildInputs; - buildInputs = buildInputs ++ [ - self.packages.${system}.default - pkgs.gjs - ]; - }; - }; - }; -} diff --git a/battery/meson.build b/battery/meson.build deleted file mode 100644 index 6621735..0000000 --- a/battery/meson.build +++ /dev/null @@ -1,19 +0,0 @@ -project( - 'astal-battery', - 'vala', - 'c', - version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), - meson_version: '>= 0.62.0', - default_options: [ - 'warning_level=2', - 'werror=false', - 'c_std=gnu11', - ], -) - -assert( - get_option('lib') or get_option('cli'), - 'Either lib or cli option must be set to true.', -) - -subdir('src') diff --git a/battery/meson_options.txt b/battery/meson_options.txt deleted file mode 100644 index f110242..0000000 --- a/battery/meson_options.txt +++ /dev/null @@ -1,11 +0,0 @@ -option( - 'lib', - type: 'boolean', - value: true, -) - -option( - 'cli', - type: 'boolean', - value: true, -) diff --git a/battery/src/cli.vala b/battery/src/cli.vala deleted file mode 100644 index 710edec..0000000 --- a/battery/src/cli.vala +++ /dev/null @@ -1,74 +0,0 @@ -static bool help; -static bool version; -static bool monitor; - -const OptionEntry[] options = { - { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, - { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, - { "monitor", 'm', OptionFlags.NONE, OptionArg.NONE, ref monitor, null, null }, - { null }, -}; - -int main(string[] argv) { - try { - var opts = new OptionContext(); - opts.add_main_entries(options, null); - opts.set_help_enabled(false); - opts.set_ignore_unknown_options(false); - opts.parse(ref argv); - } catch (OptionError err) { - printerr (err.message); - return 1; - } - - if (help) { - print("Usage:\n"); - print(" %s [flags]\n\n", argv[0]); - print("Flags:\n"); - print(" -h, --help Print this help and exit\n"); - print(" -v, --version Print version number and exit\n"); - print(" -m, --monitor Monitor property changes\n"); - return 0; - } - - if (version) { - print(AstalBattery.VERSION); - return 0; - } - - var battery = AstalBattery.get_default(); - print("%s\n", to_json(battery)); - - if (monitor) { - battery.notify.connect((prop) => { - if (prop.get_name() == "percentage" - || prop.get_name() == "state" - || prop.get_name() == "icon-name" - || prop.get_name() == "time-to-full" - || prop.get_name() == "time-to-empty" - ) { - print("%s\n", to_json(battery)); - } - }); - new GLib.MainLoop(null, false).run(); - } - - return 0; -} - -private string to_json(AstalBattery.Device device) { - string s = "unknown"; - if (device.state == AstalBattery.State.CHARGING) - s = "charging"; - if (device.state == AstalBattery.State.DISCHARGING) - s = "discharging"; - if (device.state == AstalBattery.State.FULLY_CHARGED) - s = "fully_charged"; - - var p = device.percentage; - var i = device.icon_name; - var r = device.state == AstalBattery.State.CHARGING - ? device.time_to_full : device.time_to_empty; - - return "{ \"percentage\": %f, \"state\": \"%s\", \"icon_name\": \"%s\", \"time_remaining\": %f }".printf(p, s, i, r); -} diff --git a/battery/src/config.vala.in b/battery/src/config.vala.in deleted file mode 100644 index 6e7f77e..0000000 --- a/battery/src/config.vala.in +++ /dev/null @@ -1,6 +0,0 @@ -namespace AstalBattery { - public const int MAJOR_VERSION = @MAJOR_VERSION@; - public const int MINOR_VERSION = @MINOR_VERSION@; - public const int MICRO_VERSION = @MICRO_VERSION@; - public const string VERSION = "@VERSION@"; -} diff --git a/battery/src/device.vala b/battery/src/device.vala deleted file mode 100644 index eab3770..0000000 --- a/battery/src/device.vala +++ /dev/null @@ -1,296 +0,0 @@ -namespace AstalBattery { -public Device get_default() { - return Device.get_default(); -} - -public class Device : Object { - private static Device display_device; - public static Device? get_default() { - if (display_device != null) - return display_device; - - try { - display_device = new Device("/org/freedesktop/UPower/devices/DisplayDevice"); - - return display_device; - } catch (Error error) { - critical(error.message); - } - return null; - } - - private IUPowerDevice proxy; - - public Device(string path) throws Error { - proxy = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.UPower", path); - proxy.g_properties_changed.connect(sync); - sync(); - } - - public Type device_type { get; private set; } - public string native_path { owned get; private set; } - public string vendor { owned get; private set; } - public string model { owned get; private set; } - public string serial { owned get; private set; } - public uint64 update_time { get; private set; } - public bool power_supply { get; private set; } - public bool has_history { get; private set; } - public bool has_statistics { get; private set; } - public bool online { get; private set; } - public double energy { get; private set; } - public double energy_empty { get; private set; } - public double energy_full { get; private set; } - public double energy_full_design { get; private set; } - public double energy_rate { get; private set; } - public double voltage { get; private set; } - public int charge_cycles { get; private set; } - public double luminosity { get; private set; } - public int64 time_to_empty { get; private set; } - public int64 time_to_full { get; private set;} - public double percentage { get; private set; } - public double temperature { get; private set; } - public bool is_present { get; private set; } - public State state { get; private set; } - public bool is_rechargable { get; private set; } - public double capacity { get; private set; } - public Technology technology { get; private set; } - public WarningLevel warning_level { get; private set; } - public BatteryLevel battery_level { get; private set; } - public string icon_name { owned get; private set; } - - public bool charging { get; private set; } - public bool is_battery { get; private set; } - public string battery_icon_name { get; private set; } - public string device_type_name { get; private set; } - public string device_type_icon { get; private set; } - - private unowned string get_battery_icon() { - if (percentage <= 0) - return "battery-good"; - - if (percentage < 0.10) - return "battery-empty"; - - if (percentage < 0.37) - return "battery-caution"; - - if (percentage < 0.62) - return "battery-low"; - - if (percentage < 0.87) - return "battery-good"; - - return "battery-full"; - } - - - public void sync() { - device_type = (Type)proxy.Type; - native_path = proxy.native_path; - vendor = proxy.vendor; - model = proxy.model; - serial = proxy.serial; - update_time = proxy.update_time; - power_supply = proxy.power_supply; - has_history = proxy.has_history; - has_statistics = proxy.has_statistics; - online = proxy.online; - energy = proxy.energy; - energy_empty = proxy.energy_empty; - energy_full = proxy.energy_full; - energy_full_design = proxy.energy_full_design; - energy_rate = proxy.energy_rate; - voltage = proxy.voltage; - charge_cycles = proxy.charge_cycles; - luminosity = proxy.luminosity; - time_to_empty = proxy.time_to_empty; - time_to_full = proxy.time_to_full; - percentage = proxy.percentage / 100; - temperature = proxy.temperature; - is_present = proxy.is_present; - state = (State)proxy.state; - is_rechargable = proxy.is_rechargable; - capacity = proxy.capacity; - technology = (Technology)proxy.technology; - warning_level = (WarningLevel)proxy.warning_level; - battery_level = (BatteryLevel)proxy.battery_level; - icon_name = proxy.icon_name; - - charging = state == State.FULLY_CHARGED || state == State.CHARGING; - is_battery = device_type != Type.UNKNOWN && device_type != Type.LINE_POWER; - - if (!is_battery) - battery_icon_name = "preferences-system-power"; - else if (percentage == 1.0 && charging) - battery_icon_name = "battery-full-charged"; - else - battery_icon_name = charging ? get_battery_icon() + "-charging" : get_battery_icon(); - - device_type_name = device_type.get_name(); - device_type_icon = device_type.get_icon_name(); - } -} - -[CCode (type_signature = "u")] -public enum State { - UNKNOWN = 0, - CHARGING, - DISCHARGING, - EMPTY, - FULLY_CHARGED, - PENDING_CHARGE, - PENDING_DISCHARGE, -} - -[CCode (type_signature = "u")] -public enum Technology { - UNKNOWN = 0, - LITHIUM_ION, - LITHIUM_POLYMER, - LITHIUM_IRON_PHOSPHATE, - LEAD_ACID, - NICKEL_CADMIUM, - NICKEL_METAL_HYDRIDE, -} - -[CCode (type_signature = "u")] -public enum WarningLevel { - UNKNOWN = 0, - NONE, - DISCHARGING, - LOW, - CRITICIAL, - ACTION, -} - -[CCode (type_signature = "u")] -public enum BatteryLevel { - UNKNOWN = 0, - NONE, - LOW, - CRITICIAL, - NORMAL, - HIGH, - FULL, -} - -[CCode (type_signature = "u")] -public enum Type { - UNKNOWN = 0, - LINE_POWER, - BATTERY, - UPS, - MONITOR, - MOUSE, - KEYBOARD, - PDA, - PHONE, - MEDIA_PLAYER, - TABLET, - COMPUTER, - GAMING_INPUT, - PEN, - TOUCHPAD, - MODEM, - NETWORK, - HEADSET, - SPEAKERS, - HEADPHONES, - VIDEO, - OTHER_AUDIO, - REMOVE_CONTROL, - PRINTER, - SCANNER, - CAMERA, - WEARABLE, - TOY, - BLUETOOTH_GENERIC; - - // TODO: add more icon names - public string? get_icon_name () { - switch (this) { - case UPS: - return "uninterruptible-power-supply"; - case MOUSE: - return "input-mouse"; - case KEYBOARD: - return "input-keyboard"; - case PDA: - case PHONE: - return "phone"; - case MEDIA_PLAYER: - return "multimedia-player"; - case TABLET: - case PEN: - return "input-tablet"; - case GAMING_INPUT: - return "input-gaming"; - default: - return null; - } - } - - public unowned string? get_name () { - switch (this) { - case LINE_POWER: - return "Plugged In"; - case BATTERY: - return "Battery"; - case UPS: - return "UPS"; - case MONITOR: - return "Display"; - case MOUSE: - return "Mouse"; - case KEYBOARD: - return "Keyboard"; - case PDA: - return "PDA"; - case PHONE: - return "Phone"; - case MEDIA_PLAYER: - return "Media Player"; - case TABLET: - return "Tablet"; - case COMPUTER: - return "Computer"; - case GAMING_INPUT: - return "Controller"; - case PEN: - return "Pen"; - case TOUCHPAD: - return "Touchpad"; - case MODEM: - return "Modem"; - case NETWORK: - return "Network"; - case HEADSET: - return "Headset"; - case SPEAKERS: - return "Speakers"; - case HEADPHONES: - return "Headphones"; - case VIDEO: - return "Video"; - case OTHER_AUDIO: - return "Other Audio"; - case REMOVE_CONTROL: - return "Remove Control"; - case PRINTER: - return "Printer"; - case SCANNER: - return "Scanner"; - case CAMERA: - return "Camera"; - case WEARABLE: - return "Wearable"; - case TOY: - return "Toy"; - case BLUETOOTH_GENERIC: - return "Bluetooth Generic"; - default: - return "Unknown"; - } - } -} -} diff --git a/battery/src/ifaces.vala b/battery/src/ifaces.vala deleted file mode 100644 index e6eb849..0000000 --- a/battery/src/ifaces.vala +++ /dev/null @@ -1,65 +0,0 @@ -namespace AstalBattery { -[DBus (name = "org.freedesktop.UPower")] -interface IUPower : DBusProxy { - public abstract string[] enumerate_devices() throws Error; - public abstract string get_display_device() throws Error; - public abstract string get_critical_action() throws Error; - - public signal void device_added(string object_path); - public signal void device_removed(string object_path); - - public abstract string daemon_version { owned get; } - public abstract bool on_battery { get; } - public abstract bool lid_is_closed { get; } - public abstract bool lis_is_present { get; } -} - -[DBus (name = "org.freedesktop.UPower.Device")] -public interface IUPowerDevice : DBusProxy { - public abstract HistoryDataPoint[] get_history (string type, uint32 timespan, uint32 resolution) throws GLib.Error; - public abstract StatisticsDataPoint[] get_statistics (string type) throws GLib.Error; - public abstract void refresh () throws GLib.Error; - - public abstract uint Type { get; } - public abstract string native_path { owned get; } - public abstract string vendor { owned get; } - public abstract string model { owned get; } - public abstract string serial { owned get; } - public abstract uint64 update_time { get; } - public abstract bool power_supply { get; } - public abstract bool has_history { get; } - public abstract bool has_statistics { get; } - public abstract bool online { get; } - public abstract double energy { get; } - public abstract double energy_empty { get; } - public abstract double energy_full { get; } - public abstract double energy_full_design { get; } - public abstract double energy_rate { get; } - public abstract double voltage { get; } - public abstract int32 charge_cycles { get; } - public abstract double luminosity { get; } - public abstract int64 time_to_empty { get; } - public abstract int64 time_to_full { get; } - public abstract double percentage { get; } - public abstract double temperature { get; } - public abstract bool is_present { get; } - public abstract uint state { get; } - public abstract bool is_rechargable { get; } - public abstract double capacity { get; } - public abstract uint technology { get; } - public abstract uint32 warning_level { get; } - public abstract uint32 battery_level { get; } - public abstract string icon_name { owned get; } -} - -public struct HistoryDataPoint { - uint32 time; - double value; - uint32 state; -} - -public struct StatisticsDataPoint { - double value; - double accuracy; -} -} diff --git a/battery/src/meson.build b/battery/src/meson.build deleted file mode 100644 index 0aff901..0000000 --- a/battery/src/meson.build +++ /dev/null @@ -1,76 +0,0 @@ -version_split = meson.project_version().split('.') -api_version = version_split[0] + '.' + version_split[1] -gir = 'AstalBattery-' + api_version + '.gir' -typelib = 'AstalBattery-' + api_version + '.typelib' - -config = configure_file( - input: 'config.vala.in', - output: 'config.vala', - configuration: { - 'VERSION': meson.project_version(), - 'MAJOR_VERSION': version_split[0], - 'MINOR_VERSION': version_split[1], - 'MICRO_VERSION': version_split[2], - }, -) - -deps = [ - dependency('glib-2.0'), - dependency('gio-2.0'), - dependency('gobject-2.0'), -] - -sources = [ - config, - 'ifaces.vala', - 'device.vala', - 'upower.vala', -] - -if get_option('lib') - lib = library( - meson.project_name(), - sources, - dependencies: deps, - vala_header: meson.project_name() + '.h', - vala_vapi: meson.project_name() + '-' + api_version + '.vapi', - vala_gir: gir, - version: meson.project_version(), - install: true, - install_dir: [true, true, true, true], - ) - - import('pkgconfig').generate( - lib, - name: meson.project_name(), - filebase: meson.project_name() + '-' + api_version, - version: meson.project_version(), - subdirs: meson.project_name(), - requires: deps, - install_dir: get_option('libdir') / 'pkgconfig', - ) - - custom_target( - typelib, - command: [ - find_program('g-ir-compiler'), - '--output', '@OUTPUT@', - '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', - meson.current_build_dir() / gir, - ], - input: lib, - output: typelib, - depends: lib, - install: true, - install_dir: get_option('libdir') / 'girepository-1.0', - ) -endif - -if get_option('cli') - executable( - meson.project_name(), - ['cli.vala', sources], - dependencies: deps, - install: true, - ) -endif diff --git a/battery/src/upower.vala b/battery/src/upower.vala deleted file mode 100644 index 9c18ffd..0000000 --- a/battery/src/upower.vala +++ /dev/null @@ -1,58 +0,0 @@ -namespace AstalBattery { -public class UPower : Object { - private IUPower proxy; - private HashTable _devices = - new HashTable(str_hash, str_equal); - - public List devices { - owned get { return _devices.get_values(); } - } - - public signal void device_added(Device device); - public signal void device_removed(Device device); - - public Device display_device { owned get { return Device.get_default(); }} - - public string daemon_version { owned get { return proxy.daemon_version; } } - public bool on_battery { get { return proxy.on_battery; } } - public bool lid_is_closed { get { return proxy.lid_is_closed; } } - public bool lis_is_present { get { return proxy.lid_is_closed; } } - - public string critical_action { - owned get { - try { - return proxy.get_critical_action(); - } catch (Error error) { - critical(error.message); - return ""; - } - } - } - - construct { - try { - proxy = Bus.get_proxy_sync( - BusType.SYSTEM, - "org.freedesktop.UPower", - "/org/freedesktop/UPower" - ); - - foreach (var path in proxy.enumerate_devices()) - _devices.set(path, new Device(path)); - - proxy.device_added.connect((path) => { - _devices.set(path, new Device(path)); - notify_property("devices"); - }); - - proxy.device_removed.connect((path) => { - device_removed(_devices.get(path)); - _devices.remove(path); - notify_property("devices"); - }); - } catch (Error error) { - critical(error.message); - } - } -} -} diff --git a/battery/version b/battery/version deleted file mode 100644 index 6e8bf73..0000000 --- a/battery/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/bluetooth/.gitignore b/bluetooth/.gitignore deleted file mode 100644 index f047207..0000000 --- a/bluetooth/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -build/ -result -.cache/ -test.sh -tmp/ diff --git a/bluetooth/LICENSE b/bluetooth/LICENSE deleted file mode 100644 index 67cd97b..0000000 --- a/bluetooth/LICENSE +++ /dev/null @@ -1,503 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - diff --git a/bluetooth/README.md b/bluetooth/README.md deleted file mode 100644 index b8e3f13..0000000 --- a/bluetooth/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# astal-bluetooth - -Library to control bluez over dbus - -## TODO - -- docs diff --git a/bluetooth/flake.lock b/bluetooth/flake.lock deleted file mode 100644 index 13f566b..0000000 --- a/bluetooth/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1716137900, - "narHash": "sha256-sowPU+tLQv8GlqtVtsXioTKeaQvlMz/pefcdwg8MvfM=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "6c0b7a92c30122196a761b440ac0d46d3d9954f1", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/bluetooth/flake.nix b/bluetooth/flake.nix deleted file mode 100644 index 04effee..0000000 --- a/bluetooth/flake.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ - description = "Library to control bluez over dbus"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - - outputs = { - self, - nixpkgs, - }: let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs {inherit system;}; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - ]; - - buildInputs = with pkgs; [ - glib - ]; - in { - packages.${system} = rec { - default = bluetooth; - bluetooth = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-bluetooth"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - bluetooth = pkgs.mkShell { - inherit nativeBuildInputs; - buildInputs = - buildInputs - ++ [ - self.packages.${system}.default - pkgs.gjs - ]; - }; - }; - }; -} diff --git a/bluetooth/meson.build b/bluetooth/meson.build deleted file mode 100644 index 7877365..0000000 --- a/bluetooth/meson.build +++ /dev/null @@ -1,14 +0,0 @@ -project( - 'astal-bluetooth', - 'vala', - 'c', - version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), - meson_version: '>= 0.62.0', - default_options: [ - 'warning_level=2', - 'werror=false', - 'c_std=gnu11', - ], -) - -subdir('src') diff --git a/bluetooth/src/adapter.vala b/bluetooth/src/adapter.vala deleted file mode 100644 index 0c9d00e..0000000 --- a/bluetooth/src/adapter.vala +++ /dev/null @@ -1,89 +0,0 @@ -namespace AstalBluetooth { -[DBus (name = "org.bluez.Adapter1")] -internal interface IAdapter : DBusProxy { - public abstract void remove_device(ObjectPath device) throws Error; - public abstract void start_discovery() throws Error; - public abstract void stop_discovery() throws Error; - - public abstract string[] uuids { owned get; } - public abstract bool discoverable { get; set; } - public abstract bool discovering { get; } - public abstract bool pairable { get; set; } - public abstract bool powered { get; set; } - public abstract string address { owned get; } - public abstract string alias { owned get; set; } - public abstract string modalias { owned get; } - public abstract string name { owned get; } - public abstract uint class { get; } - public abstract uint discoverable_timeout { get; set; } - public abstract uint pairable_timeout { get; set; } -} - -public class Adapter : Object { - private IAdapter proxy; - public string object_path { owned get; construct set; } - - internal Adapter(IAdapter proxy) { - this.proxy = proxy; - this.object_path = proxy.g_object_path; - proxy.g_properties_changed.connect((props) => { - var map = (HashTable)props; - foreach (var key in map.get_keys()) { - var prop = kebab_case(key); - if (get_class().find_property(prop) != null) { - notify_property(prop); - } - } - }); - } - - public string[] uuids { owned get { return proxy.uuids; } } - public bool discovering { get { return proxy.discovering; } } - public string modalias { owned get { return proxy.modalias; } } - public string name { owned get { return proxy.name; } } - public uint class { get { return proxy.class; } } - public string address { owned get { return proxy.address; } } - - public bool discoverable { - get { return proxy.discoverable; } - set { proxy.discoverable = value; } - } - - public bool pairable { - get { return proxy.pairable; } - set { proxy.pairable = value; } - } - - public bool powered { - get { return proxy.powered; } - set { proxy.powered = value; } - } - - public string alias { - owned get { return proxy.alias; } - set { proxy.alias = value; } - } - - public uint discoverable_timeout { - get { return proxy.discoverable_timeout; } - set { proxy.discoverable_timeout = value; } - } - - public uint pairable_timeout { - get { return proxy.pairable_timeout; } - set { proxy.pairable_timeout = value; } - } - - public void remove_device(Device device) { - try { proxy.remove_device((ObjectPath)device.object_path); } catch (Error err) { critical(err.message); } - } - - public void start_discovery() { - try { proxy.start_discovery(); } catch (Error err) { critical(err.message); } - } - - public void stop_discovery() { - try { proxy.stop_discovery(); } catch (Error err) { critical(err.message); } - } -} -} diff --git a/bluetooth/src/bluetooth.vala b/bluetooth/src/bluetooth.vala deleted file mode 100644 index ce086ba..0000000 --- a/bluetooth/src/bluetooth.vala +++ /dev/null @@ -1,181 +0,0 @@ -namespace AstalBluetooth { -public Bluetooth get_default() { - return Bluetooth.get_default(); -} - -public class Bluetooth : Object { - private static Bluetooth _instance; - - public static Bluetooth get_default() { - if (_instance == null) - _instance = new Bluetooth(); - - return _instance; - } - - private DBusObjectManagerClient manager; - - private HashTable _adapters = - new HashTable(str_hash, str_equal); - - private HashTable _devices = - new HashTable(str_hash, str_equal); - - public signal void device_added (Device device) { - notify_property("devices"); - } - - public signal void device_removed (Device device) { - notify_property("devices"); - } - - public signal void adapter_added (Adapter adapter) { - notify_property("adapters"); - } - - public signal void adapter_removed (Adapter adapter) { - notify_property("adapters"); - } - - public bool is_powered { get; private set; default = false; } - public bool is_connected { get; private set; default = false; } - public Adapter? adapter { get { return adapters.nth_data(0); } } - - public List adapters { - owned get { return _adapters.get_values(); } - } - - public List devices { - owned get { return _devices.get_values(); } - } - - construct { - try { - manager = new DBusObjectManagerClient.for_bus_sync( - BusType.SYSTEM, - DBusObjectManagerClientFlags.NONE, - "org.bluez", - "/", - manager_proxy_get_type, - null - ); - - foreach (var object in manager.get_objects()) { - foreach (var iface in object.get_interfaces()) { - on_interface_added(object, iface); - } - } - - manager.interface_added.connect(on_interface_added); - manager.interface_removed.connect(on_interface_removed); - - manager.object_added.connect((object) => { - foreach (var iface in object.get_interfaces()) { - on_interface_added(object, iface); - } - }); - - manager.object_removed.connect((object) => { - foreach (var iface in object.get_interfaces()) { - on_interface_removed(object, iface); - } - }); - } catch (Error err) { - critical(err.message); - } - } - - public void toggle() { - adapter.powered = !adapter.powered; - } - - [CCode (cname="astal_bluetooth_idevice_proxy_get_type")] - extern static GLib.Type get_idevice_proxy_type(); - - [CCode (cname="astal_bluetooth_iadapter_proxy_get_type")] - extern static GLib.Type get_iadapter_proxy_type(); - - private Type manager_proxy_get_type(DBusObjectManagerClient _, string object_path, string? interface_name) { - if (interface_name == null) - return typeof(DBusObjectProxy); - - switch (interface_name) { - case "org.bluez.Device1": - return get_idevice_proxy_type(); - case "org.bluez.Adapter1": - return get_iadapter_proxy_type(); - default: - return typeof(DBusProxy); - } - } - - private void on_interface_added(DBusObject object, DBusInterface iface) { - if (iface is IDevice) { - var device = new Device((IDevice)iface); - _devices.set(device.object_path, device); - device_added(device); - device.notify.connect(sync); - sync(); - } - - if (iface is IAdapter) { - var adapter = new Adapter((IAdapter)iface); - _adapters.set(adapter.object_path, adapter); - adapter_added(adapter); - adapter.notify.connect(sync); - sync(); - } - } - - private void on_interface_removed (DBusObject object, DBusInterface iface) { - if (iface is IDevice) { - unowned var device = (IDevice)iface; - device_removed(_devices.get(device.g_object_path)); - _devices.remove(device.g_object_path); - } - - if (iface is IAdapter) { - unowned var adapter = (IAdapter)iface; - adapter_removed(_adapters.get(adapter.g_object_path)); - _adapters.remove(adapter.g_object_path); - } - - sync(); - } - - private void sync() { - var powered = get_powered(); - var connected = get_connected(); - - if (powered != is_powered || connected != is_connected) { - if (powered != is_powered) { - is_powered = powered; - } - - if (connected != is_connected) { - is_connected = connected; - } - } - } - - private bool get_powered() { - foreach (var adapter in adapters) { - if (adapter.powered) { - return true; - } - } - - return false; - } - - private bool get_connected() { - foreach (var device in devices) { - if (device.connected) { - return true; - } - } - - return false; - } -} -} diff --git a/bluetooth/src/config.vala.in b/bluetooth/src/config.vala.in deleted file mode 100644 index 9fce720..0000000 --- a/bluetooth/src/config.vala.in +++ /dev/null @@ -1,6 +0,0 @@ -namespace AstalBluetooth { - public const int MAJOR_VERSION = @MAJOR_VERSION@; - public const int MINOR_VERSION = @MINOR_VERSION@; - public const int MICRO_VERSION = @MICRO_VERSION@; - public const string VERSION = "@VERSION@"; -} diff --git a/bluetooth/src/device.vala b/bluetooth/src/device.vala deleted file mode 100644 index 8fe086f..0000000 --- a/bluetooth/src/device.vala +++ /dev/null @@ -1,106 +0,0 @@ -namespace AstalBluetooth { -[DBus (name = "org.bluez.Device1")] -internal interface IDevice : DBusProxy { - public abstract void cancel_pairing() throws Error; - public abstract async void connect() throws Error; - public abstract void connect_profile(string uuid) throws Error; - public abstract async void disconnect() throws Error; - public abstract void disconnect_profile(string uuid) throws Error; - public abstract void pair() throws Error; - - public abstract string[] uuids { owned get; } - public abstract bool blocked { get; set; } - public abstract bool connected { get; } - public abstract bool legacy_pairing { get; } - public abstract bool paired { get; } - public abstract bool trusted { get; set; } - public abstract int16 rssi { get; } - public abstract ObjectPath adapter { owned get; } - public abstract string address { owned get; } - public abstract string alias { owned get; set; } - public abstract string icon { owned get; } - public abstract string modalias { owned get; } - public abstract string name { owned get; } - public abstract uint16 appearance { get; } - public abstract uint32 class { get; } -} - -public class Device : Object { - private IDevice proxy; - public string object_path { owned get; construct set; } - - internal Device(IDevice proxy) { - this.proxy = proxy; - this.object_path = proxy.g_object_path; - proxy.g_properties_changed.connect((props) => { - var map = (HashTable)props; - foreach (var key in map.get_keys()) { - var prop = kebab_case(key); - if (get_class().find_property(prop) != null) { - notify_property(prop); - } - } - }); - } - - public string[] uuids { owned get { return proxy.uuids; } } - public bool connected { get { return proxy.connected; } } - public bool legacy_pairing { get { return proxy.legacy_pairing; } } - public bool paired { get { return proxy.paired; } } - public int16 rssi { get { return proxy.rssi; } } - public ObjectPath adapter { owned get { return proxy.adapter; } } - public string address { owned get { return proxy.address; } } - public string icon { owned get { return proxy.icon; } } - public string modalias { owned get { return proxy.modalias; } } - public string name { owned get { return proxy.name; } } - public uint16 appearance { get { return proxy.appearance; } } - public uint32 class { get { return proxy.class; } } - public bool connecting { get; private set; } - - public bool blocked { - get { return proxy.blocked; } - set { proxy.blocked = value; } - } - - public bool trusted { - get { return proxy.trusted; } - set { proxy.trusted = value; } - } - - public string alias { - owned get { return proxy.alias; } - set { proxy.alias = value; } - } - - public void cancel_pairing() { - try { proxy.cancel_pairing(); } catch (Error err) { critical(err.message); } - } - - public async void connect_device() { - try { - connecting = true; - yield proxy.connect(); - } catch (Error err) { - critical(err.message); - } finally { - connecting = false; - } - } - - public async void disconnect_device() { - try { yield proxy.disconnect(); } catch (Error err) { critical(err.message); } - } - - public void connect_profile(string uuid) { - try { proxy.connect_profile(uuid); } catch (Error err) { critical(err.message); } - } - - public void disconnect_profile(string uuid) { - try { proxy.disconnect_profile(uuid); } catch (Error err) { critical(err.message); } - } - - public void pair() { - try { proxy.pair(); } catch (Error err) { critical(err.message); } - } -} -} diff --git a/bluetooth/src/meson.build b/bluetooth/src/meson.build deleted file mode 100644 index cbc38ce..0000000 --- a/bluetooth/src/meson.build +++ /dev/null @@ -1,66 +0,0 @@ -version_split = meson.project_version().split('.') -api_version = version_split[0] + '.' + version_split[1] -gir = 'AstalBluetooth-' + api_version + '.gir' -typelib = 'AstalBluetooth-' + api_version + '.typelib' - -config = configure_file( - input: 'config.vala.in', - output: 'config.vala', - configuration: { - 'VERSION': meson.project_version(), - 'MAJOR_VERSION': version_split[0], - 'MINOR_VERSION': version_split[1], - 'MICRO_VERSION': version_split[2], - }, -) - -deps = [ - dependency('glib-2.0'), - dependency('gobject-2.0'), - dependency('gio-2.0'), -] - -sources = [ - config, - 'utils.vala', - 'device.vala', - 'adapter.vala', - 'bluetooth.vala', -] - -lib = library( - meson.project_name(), - sources, - dependencies: deps, - vala_header: meson.project_name() + '.h', - vala_vapi: meson.project_name() + '-' + api_version + '.vapi', - vala_gir: gir, - version: meson.project_version(), - install: true, - install_dir: [true, true, true, true], -) - -import('pkgconfig').generate( - lib, - name: meson.project_name(), - filebase: meson.project_name() + '-' + api_version, - version: meson.project_version(), - subdirs: meson.project_name(), - requires: deps, - install_dir: get_option('libdir') / 'pkgconfig', -) - -custom_target( - typelib, - command: [ - find_program('g-ir-compiler'), - '--output', '@OUTPUT@', - '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', - meson.current_build_dir() / gir, - ], - input: lib, - output: typelib, - depends: lib, - install: true, - install_dir: get_option('libdir') / 'girepository-1.0', -) diff --git a/bluetooth/src/utils.vala b/bluetooth/src/utils.vala deleted file mode 100644 index 5dcaff6..0000000 --- a/bluetooth/src/utils.vala +++ /dev/null @@ -1,21 +0,0 @@ -namespace AstalBluetooth { -internal string kebab_case(string pascal_case) { - StringBuilder kebab_case = new StringBuilder(); - - for (int i = 0; i < pascal_case.length; i++) { - char c = pascal_case[i]; - - if (c >= 'A' && c <= 'Z') { - if (i != 0) { - kebab_case.append_c('-'); - } - - kebab_case.append_c((char)(c + 32)); - } else { - kebab_case.append_c(c); - } - } - - return kebab_case.str; -} -} diff --git a/bluetooth/version b/bluetooth/version deleted file mode 100644 index 6e8bf73..0000000 --- a/bluetooth/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/core/default.nix b/core/default.nix deleted file mode 100644 index e69de29..0000000 diff --git a/flake.nix b/flake.nix index d34c09d..5d2503f 100644 --- a/flake.nix +++ b/flake.nix @@ -18,6 +18,7 @@ pkg-config ninja vala + wayland ]; buildInputs = [pkgs.glib] ++ inputs; pname = name; @@ -29,25 +30,47 @@ packages.${system} = rec { default = astal; astal = with pkgs; lib "astal" ./core [gtk3 gtk-layer-shell]; + apps = with pkgs; lib "astal-apps" ./lib/apps [json-glib]; + auth = with pkgs; lib "astal-auth" ./lib/auth [pam]; + battery = lib "astal-battery" ./lib/battery []; + bluetooth = lib "astal-bluetooth" ./lib/bluetooth []; + hyprland = with pkgs; lib "astal-hyprland" ./lib/hyprland [json-glib]; + mpris = with pkgs; lib "astal-mpris" ./lib/mpris [gvfs json-glib]; + network = with pkgs; lib "astal-network" ./lib/network [networkmanager]; + notifd = with pkgs; lib "astal-notifd" ./lib/notifd [json-glib gdk-pixbuf]; + powerprofiles = with pkgs; lib "astal-power-profiles" ./lib/powerprofiles [json-glib]; + river = with pkgs; lib "astal-river" ./lib/river [json-glib]; + tray = with pkgs; lib "astal-tray" ./lib/tray [gtk3 gdk-pixbuf libdbusmenu-gtk3 json-glib]; + wireplumber = lib "astal-wireplumber" ./lib/wireplumber [pkgs.wireplumber]; }; devShells.${system} = let - inputs = with pkgs; [ + buildInputs = with pkgs; [ wrapGAppsHook gobject-introspection meson pkg-config ninja vala + gtk3 + gtk-layer-shell + json-glib + pam + gvfs + networkmanager + gdk-pixbuf + wireplumber + libdbusmenu-gtk3 + wayland (lua.withPackages (ps: [ps.lgi])) gjs ]; in { default = pkgs.mkShell { - inherit inputs; + inherit buildInputs; }; astal = pkgs.mkShell { - inputs = inputs ++ [self.packages.${system}.astal]; + buildInputs = buildInputs ++ (builtins.attrValues self.packages.${system}); }; }; }; diff --git a/hyprland/.gitignore b/hyprland/.gitignore deleted file mode 100644 index f3ced36..0000000 --- a/hyprland/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -build/ -result -.cache/ -tmp/ diff --git a/hyprland/LICENSE b/hyprland/LICENSE deleted file mode 100644 index 67cd97b..0000000 --- a/hyprland/LICENSE +++ /dev/null @@ -1,503 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - diff --git a/hyprland/README.md b/hyprland/README.md deleted file mode 100644 index c47cab0..0000000 --- a/hyprland/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# astal-hyprland - -Library and cli tool for the Hyprland IPC sockets - -## TODO - -- docs diff --git a/hyprland/flake.lock b/hyprland/flake.lock deleted file mode 100644 index 13f566b..0000000 --- a/hyprland/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1716137900, - "narHash": "sha256-sowPU+tLQv8GlqtVtsXioTKeaQvlMz/pefcdwg8MvfM=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "6c0b7a92c30122196a761b440ac0d46d3d9954f1", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/hyprland/flake.nix b/hyprland/flake.nix deleted file mode 100644 index c6b4efd..0000000 --- a/hyprland/flake.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ - description = "Library and cli tool for querying applications"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - - outputs = { self, nixpkgs }: - let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs { inherit system; }; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - ]; - - buildInputs = with pkgs; [ - glib - json-glib - ]; - in { - packages.${system} = rec { - default = hyprland; - hyprland = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-hyprland"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - hyprland = pkgs.mkShell { - inherit nativeBuildInputs; - buildInputs = buildInputs ++ [ - pkgs.gjs - self.packages.${system}.default - ]; - }; - }; - }; -} diff --git a/hyprland/meson.build b/hyprland/meson.build deleted file mode 100644 index 07cbb3f..0000000 --- a/hyprland/meson.build +++ /dev/null @@ -1,19 +0,0 @@ -project( - 'astal-hyprland', - 'vala', - 'c', - version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), - meson_version: '>= 0.62.0', - default_options: [ - 'warning_level=2', - 'werror=false', - 'c_std=gnu11', - ], -) - -assert( - get_option('lib') or get_option('cli'), - 'Either lib or cli option must be set to true.', -) - -subdir('src') diff --git a/hyprland/meson_options.txt b/hyprland/meson_options.txt deleted file mode 100644 index f110242..0000000 --- a/hyprland/meson_options.txt +++ /dev/null @@ -1,11 +0,0 @@ -option( - 'lib', - type: 'boolean', - value: true, -) - -option( - 'cli', - type: 'boolean', - value: true, -) diff --git a/hyprland/src/cli.vala b/hyprland/src/cli.vala deleted file mode 100644 index a68d63b..0000000 --- a/hyprland/src/cli.vala +++ /dev/null @@ -1,42 +0,0 @@ -static bool help; -static bool version; - -const OptionEntry[] options = { - { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, - { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, - { null }, -}; - -int main(string[] argv) { - try { - var opts = new OptionContext(); - opts.add_main_entries(options, null); - opts.set_help_enabled(false); - opts.set_ignore_unknown_options(false); - opts.parse(ref argv); - } catch (OptionError err) { - printerr (err.message); - return 1; - } - - if (help) { - print("Usage:\n"); - print(" %s [flags]\n\n", argv[0]); - print("Flags:\n"); - print(" -h, --help Print this help and exit\n"); - print(" -v, --version Print version number and exit\n"); - return 0; - } - - if (version) { - print(AstalHyprland.VERSION); - return 0; - } - - AstalHyprland.Hyprland.get_default().event.connect((event, args) => { - print("{ event: \"%s\", payload: \"%s\" }\n", event, args); - }); - - new MainLoop(null, false).run(); - return 0; -} diff --git a/hyprland/src/client.vala b/hyprland/src/client.vala deleted file mode 100644 index 6456667..0000000 --- a/hyprland/src/client.vala +++ /dev/null @@ -1,75 +0,0 @@ -namespace AstalHyprland { -public class Client : Object { - public signal void removed (); - public signal void moved_to (Workspace workspace); - - public string address { get; private set; } - public bool mapped { get; private set; } - public bool hidden { get; private set; } - public int x { get; private set; } - public int y { get; private set; } - public int width { get; private set; } - public int height { get; private set; } - public Workspace workspace { get; private set; } - public bool floating { get; private set; } - public Monitor monitor { get; private set; } - public string class { get; private set; } - public string title { get; private set; } - public string initial_class { get; private set; } - public string initial_title { get; private set; } - public uint pid { get; private set; } - public bool xwayland { get; private set; } - public bool pinned { get; private set; } - public bool fullscreen { get; private set; } - public int fullscreen_mode { get; private set; } - public bool fake_fullscreen { get; private set; } - // TODO: public Group[] grouped { get; private set; } - // TODO: public Tag[] tags { get; private set; } - public string swallowing { get; private set; } - public int focus_history_id { get; private set; } - - internal void sync(Json.Object obj) { - var hyprland = Hyprland.get_default(); - - address = obj.get_string_member("address").replace("0x", ""); - mapped = obj.get_boolean_member("mapped"); - hidden = obj.get_boolean_member("hidden"); - floating = obj.get_boolean_member("floating"); - class = obj.get_string_member("class"); - title = obj.get_string_member("title"); - initial_title = obj.get_string_member("initialTitle"); - initial_class = obj.get_string_member("initialClass"); - pid = (uint)obj.get_int_member("pid"); - xwayland = obj.get_boolean_member("xwayland"); - pinned = obj.get_boolean_member("pinned"); - fullscreen = obj.get_boolean_member("fullscreen"); - fullscreen_mode = (int)obj.get_int_member("fullscreenMode"); // is this used? - fake_fullscreen = obj.get_boolean_member("fakeFullscreen"); - swallowing = obj.get_string_member("swallowing"); - focus_history_id = (int)obj.get_int_member("focusHistoryID"); - x = (int)obj.get_array_member("at").get_int_element(0); - y = (int)obj.get_array_member("at").get_int_element(1); - width = (int)obj.get_array_member("size").get_int_element(0); - height = (int)obj.get_array_member("size").get_int_element(1); - - workspace = hyprland.get_workspace((int)obj.get_object_member("workspace").get_int_member("id")); - monitor = hyprland.get_monitor((int)obj.get_int_member("monitor")); - } - - public void kill() { - Hyprland.get_default().dispatch("closewindow", "address:" + "0x" + address); - } - - public void focus() { - Hyprland.get_default().dispatch("focuswindow", "address:" + "0x" + address); - } - - public void move_to(Workspace ws) { - Hyprland.get_default().dispatch("movetoworkspacesilent", ws.id.to_string() + ",address:" + "0x" + address); - } - - public void toggle_floating() { - Hyprland.get_default().dispatch("togglefloating", "address:" + "0x" + address); - } -} -} diff --git a/hyprland/src/config.vala.in b/hyprland/src/config.vala.in deleted file mode 100644 index 65993b2..0000000 --- a/hyprland/src/config.vala.in +++ /dev/null @@ -1,6 +0,0 @@ -namespace AstalHyprland { - public const int MAJOR_VERSION = @MAJOR_VERSION@; - public const int MINOR_VERSION = @MINOR_VERSION@; - public const int MICRO_VERSION = @MICRO_VERSION@; - public const string VERSION = "@VERSION@"; -} diff --git a/hyprland/src/hyprland.vala b/hyprland/src/hyprland.vala deleted file mode 100644 index 5359d2e..0000000 --- a/hyprland/src/hyprland.vala +++ /dev/null @@ -1,451 +0,0 @@ -namespace AstalHyprland { -public Hyprland get_default() { - return Hyprland.get_default(); -} - -public class Hyprland : Object { - private static string HIS = GLib.Environment.get_variable("HYPRLAND_INSTANCE_SIGNATURE"); - private static string RUN_DIR = GLib.Environment.get_user_runtime_dir(); - - private static Hyprland _instance; - public static Hyprland? get_default() { - if (_instance != null) - return _instance; - - var HIS = GLib.Environment.get_variable("HYPRLAND_INSTANCE_SIGNATURE"); - if (HIS == null) { - critical("Hyprland is not running"); - return null; - } - - var h = new Hyprland(); - _instance = h; - - h.socket2 = h.connection("socket2"); - h.watch_socket(new DataInputStream(h.socket2.input_stream)); - try { - h.init(); - } catch (Error err) { - critical("could not initialize: %s", err.message); - return null; - } - - return _instance; - } - - // monitors, workspaces, clients - private HashTable _monitors = - new HashTable((i) => i, (a, b) => a == b); - - private HashTable _workspaces = - new HashTable((i) => i, (a, b) => a == b); - - private HashTable _clients = - new HashTable(str_hash, str_equal); - - public List monitors { owned get { return _monitors.get_values(); } } - public List workspaces { owned get { return _workspaces.get_values(); } } - public List clients { owned get { return _clients.get_values(); } } - - public Monitor get_monitor(int id) { return _monitors.get(id); } - public Workspace get_workspace(int id) { return _workspaces.get(id); } - public Client? get_client(string address) { - if (address == "" || address == null) - return null; - - if (address.substring(0, 2) == "0x") - return _clients.get(address.substring(2, -1)); - - return _clients.get(address); - } - - public Monitor? get_monitor_by_name(string name) { - foreach (var mon in monitors) { - if (mon.name == name) - return mon; - } - return null; - } - - public Workspace? get_workspace_by_name(string name) { - foreach (var ws in workspaces) { - if (ws.name == name) - return ws; - } - return null; - } - - public Workspace focused_workspace { get; private set; } - public Monitor focused_monitor { get; private set; } - public Client focused_client { get; private set; } - - // other props - public List binds { - owned get { - var list = new List(); - try { - var arr = Json.from_string(message("j/binds")).get_array(); - foreach (var b in arr.get_elements()) - list.append(new Bind.from_json(b.get_object())); - } catch (Error err) { - critical(err.message); - } - return list; - } - } - - public Position cursor_position { - owned get { - return new Position.cursorpos(message("cursorpos")); - } - } - - // signals - public signal void event (string event, string args); - - // TODO: nag vaxry for fullscreenv2 - // public signal void fullscreen (bool fullscreen); - public signal void minimize (Client client, bool minimize); - public signal void floating (Client client, bool floating); - public signal void urgent (Client client); - public signal void client_moved (Client client, Workspace ws); - - public signal void submap (string name); - public signal void keyboard_layout (string keyboard, string layout); - public signal void config_reloaded (); - - // state - public signal void client_added (Client client); - public signal void client_removed (string address); - public signal void workspace_added (Workspace workspace); - public signal void workspace_removed (int id); - public signal void monitor_added (Monitor monitor); - public signal void monitor_removed (int id); - - private SocketConnection socket2; - - private SocketConnection? connection(string socket) { - var path = RUN_DIR + "/hypr/" + HIS + "/." + socket + ".sock"; - try { - return new SocketClient().connect(new UnixSocketAddress(path), null); - } catch (Error err) { - critical(err.message); - return null; - } - } - - private void watch_socket(DataInputStream stream) { - stream.read_line_async.begin(Priority.DEFAULT, null, (_, res) => { - try { - var line = stream.read_line_async.end(res); - handle_event.begin(line, (_, res) => { - try { - handle_event.end(res); - } catch (Error err) { - critical(err.message); - } - }); - watch_socket(stream); - } catch (Error err) { - critical(err.message); - } - }); - } - - private void write_socket( - string message, - out SocketConnection conn, - out DataInputStream stream - ) throws Error { - conn = connection("socket"); - conn.output_stream.write(message.data, null); - stream = new DataInputStream(conn.input_stream); - } - - public string message(string message) { - SocketConnection conn; - DataInputStream stream; - try { - write_socket(message, out conn, out stream); - return stream.read_upto("\x04", -1, null, null); - } catch (Error err) { - critical(err.message); - } finally { - try { - if (conn != null) - conn.close(null); - } catch (Error err) { - critical(err.message); - } - } - return ""; - } - - public async string message_async(string message) { - SocketConnection conn; - DataInputStream stream; - try { - write_socket(message, out conn, out stream); - return yield stream.read_upto_async("\x04", -1, Priority.DEFAULT, null, null); - } catch (Error err) { - critical(err.message); - } finally { - try { - conn.close(null); - } catch (Error err) { - critical(err.message); - } - } - return ""; - } - - public void dispatch(string dispatcher, string args) { - var msg = "dispatch " + dispatcher + " " + args; - message_async.begin(msg, (_, res) => { - var err = message_async.end(res); - if (err != "ok") - critical("dispatch error: %s", err); - }); - } - - public void move_cursor(int x, int y) { - dispatch("movecursor", x.to_string() + " " + y.to_string()); - - } - - // TODO: nag vaxry to make socket events and hyprctl more consistent - private void init() throws Error { - var mons = Json.from_string(message("j/monitors")).get_array(); - var wrkspcs = Json.from_string(message("j/workspaces")).get_array(); - var clnts = Json.from_string(message("j/clients")).get_array(); - - // create - foreach (var mon in mons.get_elements()) { - var id = (int)mon.get_object().get_member("id").get_int(); - var m = new Monitor(); - _monitors.insert(id, m); - - if (mon.get_object().get_member("focused").get_boolean()) - focused_monitor = m; - } - foreach (var wrkpsc in wrkspcs.get_elements()) { - var id = (int)wrkpsc.get_object().get_member("id").get_int(); - _workspaces.set(id, new Workspace()); - } - foreach (var clnt in clnts.get_elements()) { - var addr = clnt.get_object().get_member("address").get_string(); - _clients.set(addr.replace("0x", ""), new Client()); - } - - // init - foreach (var c in clnts.get_elements()) { - var addr = c.get_object().get_member("address").get_string(); - get_client(addr).sync(c.get_object()); - } - foreach (var ws in wrkspcs.get_elements()) { - var id = (int)ws.get_object().get_member("id").get_int(); - get_workspace(id).sync(ws.get_object()); - } - foreach (var mon in mons.get_elements()) { - var id = (int)mon.get_object().get_member("id").get_int(); - get_monitor(id).sync(mon.get_object()); - } - - // focused - focused_workspace = get_workspace((int)Json.from_string(message("j/activeworkspace")) - .get_object().get_member("id").get_int()); - - focused_client = get_client(Json.from_string(message("j/activewindow")) - .get_object().get_member("address").get_string()); - } - - ~Hyprland() { - if (socket2 != null) { - try { - socket2.close(null); - } catch (Error err) { - critical(err.message); - } - } - } - - public async void sync_monitors() throws Error { - var str = yield message_async("j/monitors"); - var arr = Json.from_string(str).get_array(); - foreach (var obj in arr.get_elements()) { - var id = (int)obj.get_object().get_int_member("id"); - var m = get_monitor(id); - if (m != null) - m.sync(obj.get_object()); - } - } - - public async void sync_workspaces() throws Error { - var str = yield message_async("j/workspaces"); - var arr = Json.from_string(str).get_array(); - foreach (var obj in arr.get_elements()) { - var id = (int)obj.get_object().get_int_member("id"); - var ws = get_workspace(id); - if (ws != null) - ws.sync(obj.get_object()); - - } - } - - public async void sync_clients() throws Error { - var str = yield message_async("j/clients"); - var arr = Json.from_string(str).get_array(); - foreach (var obj in arr.get_elements()) { - var addr = obj.get_object().get_string_member("address"); - var c = get_client(addr); - if (c != null) - c.sync(obj.get_object()); - } - } - - private async void handle_event(string line) throws Error { - var args = line.split(">>"); - - switch (args[0]) { - case "workspacev2": - focused_workspace = get_workspace(int.parse(args[1])); - break; - - case "focusedmon": - var argv = args[1].split(",", 2); - focused_monitor = get_monitor_by_name(argv[0]); - focused_workspace = get_workspace_by_name(argv[1]); - break; - - case "activewindowv2": - focused_client = get_client(args[1]); - break; - - // TODO: nag vaxry for fullscreenv2 that passes address - case "fullscreen": - yield sync_clients(); - break; - - case "monitorremoved": - var id = get_monitor_by_name(args[1]).id; - _monitors.get(id).removed(); - _monitors.remove(id); - monitor_removed(id); - notify_property("monitors"); - break; - - case "monitoraddedv2": - var id = int.parse(args[1].split(",", 2)[0]); - var mon = new Monitor(); - _monitors.insert(id, mon); - yield sync_monitors(); - monitor_added(mon); - notify_property("monitors"); - break; - - case "createworkspacev2": - var id = int.parse(args[1].split(",", 2)[0]); - var ws = new Workspace(); - _workspaces.insert(id, ws); - yield sync_workspaces(); - workspace_added(ws); - notify_property("workspaces"); - break; - - case "destroyworkspacev2": - var id = int.parse(args[1].split(",", 2)[0]); - _workspaces.get(id).removed(); - _workspaces.remove(id); - workspace_removed(id); - notify_property("workspaces"); - break; - - case "moveworkspacev2": - yield sync_workspaces(); - yield sync_monitors(); - break; - - case "renameworkspace": - yield sync_workspaces(); - break; - - case "activespecial": - yield sync_monitors(); - yield sync_workspaces(); - break; - - case "activelayout": - var argv = args[1].split(","); - keyboard_layout(argv[0], argv[1]); - break; - - case "openwindow": - var addr = args[1].split(",")[0]; - var client = new Client(); - _clients.insert(addr, client); - yield sync_clients(); - yield sync_workspaces(); - client_added(client); - notify_property("clients"); - break; - - case "closewindow": - _clients.get(args[1]).removed(); - _clients.remove(args[1]); - client_removed(args[1]); - yield sync_workspaces(); - notify_property("clients"); - break; - - case "movewindowv2": - yield sync_clients(); - yield sync_workspaces(); - var argv = args[1].split(","); - client_moved(get_client(argv[0]), get_workspace(int.parse(argv[1]))); - get_client(argv[0]).moved_to(get_workspace(int.parse(argv[1]))); - break; - - case "submap": - submap(args[1]); - break; - - case "changefloatingmode": - var argv = args[1].split(","); - yield sync_clients(); - floating(get_client(argv[0]), argv[1] == "0"); - break; - - case "urgent": - urgent(get_client(args[1])); - break; - - case "minimize": - var argv = args[1].split(","); - yield sync_clients(); - minimize(get_client(argv[0]), argv[1] == "0"); - break; - - case "windowtitle": - yield sync_clients(); - break; - - // TODO: - case "togglegroup": - case "moveintogroup": - case "moveoutofgroup": - case "ignoregrouplock": - case "lockgroups": - break; - - case "configreloaded": - config_reloaded(); - break; - - default: - break; - } - - event(args[0], args[1]); - } -} -} diff --git a/hyprland/src/meson.build b/hyprland/src/meson.build deleted file mode 100644 index b6f7681..0000000 --- a/hyprland/src/meson.build +++ /dev/null @@ -1,81 +0,0 @@ -version_split = meson.project_version().split('.') -api_version = version_split[0] + '.' + version_split[1] -gir = 'AstalHyprland-' + api_version + '.gir' -typelib = 'AstalHyprland-' + api_version + '.typelib' - -config = configure_file( - input: 'config.vala.in', - output: 'config.vala', - configuration: { - 'VERSION': meson.project_version(), - 'MAJOR_VERSION': version_split[0], - 'MINOR_VERSION': version_split[1], - 'MICRO_VERSION': version_split[2], - }, -) - -deps = [ - dependency('glib-2.0'), - dependency('gobject-2.0'), - dependency('gio-2.0'), - dependency('gio-unix-2.0'), - dependency('json-glib-1.0'), -] - -sources = [ - config, - 'client.vala', - 'cli.vala', - 'hyprland.vala', - 'monitor.vala', - 'structs.vala', - 'workspace.vala', -] - -if get_option('lib') - lib = library( - meson.project_name(), - sources, - dependencies: deps, - vala_header: meson.project_name() + '.h', - vala_vapi: meson.project_name() + '-' + api_version + '.vapi', - vala_gir: gir, - version: meson.project_version(), - install: true, - install_dir: [true, true, true, true], - ) - - import('pkgconfig').generate( - lib, - name: meson.project_name(), - filebase: meson.project_name() + '-' + api_version, - version: meson.project_version(), - subdirs: meson.project_name(), - requires: deps, - install_dir: get_option('libdir') / 'pkgconfig', - ) - - custom_target( - typelib, - command: [ - find_program('g-ir-compiler'), - '--output', '@OUTPUT@', - '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', - meson.current_build_dir() / gir, - ], - input: lib, - output: typelib, - depends: lib, - install: true, - install_dir: get_option('libdir') / 'girepository-1.0', - ) -endif - -if get_option('cli') - executable( - meson.project_name(), - ['cli.vala', sources], - dependencies: deps, - install: true, - ) -endif diff --git a/hyprland/src/monitor.vala b/hyprland/src/monitor.vala deleted file mode 100644 index d7b8028..0000000 --- a/hyprland/src/monitor.vala +++ /dev/null @@ -1,71 +0,0 @@ -namespace AstalHyprland { -public class Monitor : Object { - public signal void removed (); - - public int id { get; private set; } - public string name { get; private set; } - public string description { get; private set; } - public string make { get; private set; } - public string model { get; private set; } - public string serial { get; private set; } - public int width { get; private set; } - public int height { get; private set; } - public double refresh_rate { get; private set; } - public int x { get; private set; } - public int y { get; private set; } - public Workspace active_workspace { get; private set; } - public Workspace special_workspace { get; private set; } - public int reserved_top { get; private set; } - public int reserved_bottom { get; private set; } - public int reserved_left { get; private set; } - public int reserved_right { get; private set; } - public double scale { get; private set; } - public bool focused { get; private set; } - public bool dpms_status { get; private set; } - public bool vrr { get; private set; } - public bool actively_tearing { get; private set; } - public bool disabled { get; private set; } - public string current_format { get; private set; } - public Array available_modes { get; private set; } - - internal void sync(Json.Object obj) { - var hyprland = Hyprland.get_default(); - - id = (int)obj.get_int_member("id"); - name = obj.get_string_member("name"); - description = obj.get_string_member("description"); - make = obj.get_string_member("make"); - model = obj.get_string_member("model"); - serial = obj.get_string_member("serial"); - width = (int)obj.get_int_member("width"); - height = (int)obj.get_int_member("height"); - refresh_rate = obj.get_double_member("refreshRate"); - x = (int)obj.get_int_member("x"); - y = (int)obj.get_int_member("y"); - scale = obj.get_double_member("scale"); - focused = obj.get_boolean_member("focused"); - dpms_status = obj.get_boolean_member("dpmsStatus"); - vrr = obj.get_boolean_member("vrr"); - actively_tearing = obj.get_boolean_member("activelyTearing"); - disabled = obj.get_boolean_member("disabled"); - current_format = obj.get_string_member("currentFormat"); - - var r = obj.get_array_member("reserved"); - reserved_top = (int)r.get_int_element(0); - reserved_bottom = (int)r.get_int_element(1); - reserved_left = (int)r.get_int_element(2); - reserved_right = (int)r.get_int_element(3); - - var modes = new Array(); - foreach (var mode in obj.get_array_member("availableModes").get_elements()) - modes.append_val(mode.get_string()); - - active_workspace = hyprland.get_workspace((int)obj.get_object_member("activeWorkspace").get_int_member("id")); - special_workspace = hyprland.get_workspace((int)obj.get_object_member("specialWorkspace").get_int_member("id")); - } - - public void focus() { - Hyprland.get_default().dispatch("focusmonitor", id.to_string()); - } -} -} diff --git a/hyprland/src/structs.vala b/hyprland/src/structs.vala deleted file mode 100644 index 25f70c3..0000000 --- a/hyprland/src/structs.vala +++ /dev/null @@ -1,42 +0,0 @@ -namespace AstalHyprland { -public class Bind : Object { - public bool locked { get; construct set; } - public bool mouse { get; construct set; } - public bool release { get; construct set; } - public bool repeat { get; construct set; } - public bool non_consuming { get; construct set; } - public int64 modmask { get; construct set; } - public string submap { get; construct set; } - public string key { get; construct set; } - public int64 keycode { get; construct set; } - public bool catch_all { get; construct set; } - public string dispatcher { get; construct set; } - public string arg { get; construct set; } - - internal Bind.from_json(Json.Object obj) { - locked = obj.get_boolean_member("locked"); - mouse = obj.get_boolean_member("mouse"); - release = obj.get_boolean_member("release"); - repeat = obj.get_boolean_member("repeat"); - non_consuming = obj.get_boolean_member("non_consuming"); - modmask = obj.get_int_member("modmask"); - submap = obj.get_string_member("submap"); - key = obj.get_string_member("key"); - keycode = obj.get_int_member("keycode"); - catch_all = obj.get_boolean_member("catch_all"); - dispatcher = obj.get_string_member("dispatcher"); - arg = obj.get_string_member("arg"); - } -} - -public class Position : Object { - public int x { get; construct set; } - public int y { get; construct set; } - - internal Position.cursorpos(string pos) { - var xy = pos.split(","); - x = int.parse(xy[0].strip()); - y = int.parse(xy[1].strip()); - } -} -} diff --git a/hyprland/src/workspace.vala b/hyprland/src/workspace.vala deleted file mode 100644 index 075f86f..0000000 --- a/hyprland/src/workspace.vala +++ /dev/null @@ -1,57 +0,0 @@ -namespace AstalHyprland { -public class Workspace : Object { - public signal void removed (); - - public List _clients = new List(); - - public int id { get; private set; } - public string name { get; private set; } - public Monitor monitor { get; private set; } - public List clients { owned get { return _clients.copy(); } } - public bool has_fullscreen { get; private set; } - public Client last_client { get; private set; } - - public Workspace.dummy(int id, Monitor? monitor) { - this.id = id; - this.name = id.to_string(); - this.monitor = monitor; - } - - internal List filter_clients() { - var hyprland = Hyprland.get_default(); - var list = new List(); - foreach (var client in hyprland.clients) { - if (client.workspace == this) { - list.append(client); - } - } - - return list; - } - - internal void sync(Json.Object obj) { - var hyprland = Hyprland.get_default(); - - id = (int)obj.get_int_member("id"); - name = obj.get_string_member("name"); - has_fullscreen = obj.get_boolean_member("hasfullscreen"); - - monitor = hyprland.get_monitor((int)obj.get_int_member("monitorID")); - last_client = hyprland.get_client(obj.get_string_member("lastwindow")); - - var list = filter_clients(); - if (_clients.length() != list.length()) { - _clients = list.copy(); - notify_property("clients"); - } - } - - public void focus() { - Hyprland.get_default().dispatch("workspace", id.to_string()); - } - - public void move_to(Monitor m) { - Hyprland.get_default().dispatch("moveworkspacetomonitor", id.to_string() + " " + m.id.to_string()); - } -} -} diff --git a/hyprland/version b/hyprland/version deleted file mode 100644 index 6e8bf73..0000000 --- a/hyprland/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/lib/apps/application.vala b/lib/apps/application.vala new file mode 100644 index 0000000..5748fc6 --- /dev/null +++ b/lib/apps/application.vala @@ -0,0 +1,118 @@ +namespace AstalApps { +public class Application : Object { + public DesktopAppInfo app { get; construct set; } + public int frequency { get; set; default = 0; } + public string name { get { return app.get_name(); } } + public string entry { get { return app.get_id(); } } + public string description { get { return app.get_description(); } } + public string wm_class { get { return app.get_startup_wm_class(); } } + public string executable { owned get { return app.get_string("Exec"); } } + public string icon_name { owned get { return app.get_string("Icon"); } } + + internal Application(string id, int? frequency = 0) { + Object(app: new DesktopAppInfo(id)); + this.frequency = frequency; + } + + public string get_key(string key) { + return app.get_string(key); + } + + public bool launch() { + try { + var s = app.launch(null, null); + ++frequency; + return s; + } catch (Error err) { + critical(err.message); + return false; + } + } + + public Score fuzzy_match(string term) { + var score = Score(); + if (name != null) + score.name = levenshtein(term, name); + if (entry != null) + score.entry = levenshtein(term, entry); + if (executable != null) + score.executable = levenshtein(term, executable); + if (description != null) + score.description = levenshtein(term, description); + + return score; + } + + public Score exact_match(string term) { + var score = Score(); + if (name != null) + score.name = name.down().contains(term.down()) ? 1 : 0; + if (entry != null) + score.entry = entry.down().contains(term.down()) ? 1 : 0; + if (executable != null) + score.executable = executable.down().contains(term.down()) ? 1 : 0; + if (description != null) + score.description = description.down().contains(term.down()) ? 1 : 0; + + return score; + } + + internal Json.Node to_json() { + return new Json.Builder() + .begin_object() + .set_member_name("name").add_string_value(name) + .set_member_name("entry").add_string_value(entry) + .set_member_name("executable").add_string_value(executable) + .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) + .end_object() + .get_root(); + } +} + +int min3(int a, int b, int c) { + return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); +} + +double levenshtein(string s1, string s2) { + int len1 = s1.length; + int len2 = s2.length; + + int[, ] d = new int[len1 + 1, len2 + 1]; + + for (int i = 0; i <= len1; i++) { + d[i, 0] = i; + } + for (int j = 0; j <= len2; j++) { + d[0, j] = j; + } + + for (int i = 1; i <= len1; i++) { + for (int j = 1; j <= len2; j++) { + int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1; + d[i, j] = min3( + d[i - 1, j] + 1, // deletion + d[i, j - 1] + 1, // insertion + d[i - 1, j - 1] + cost // substitution + ); + } + } + + var distance = d[len1, len2]; + int max_len = len1 > len2 ? len1 : len2; + + if (max_len == 0) { + return 1.0; + } + + return 1.0 - ((double)distance / max_len); +} + +public struct Score { + double name; + double entry; + double executable; + double description; +} +} diff --git a/lib/apps/apps.vala b/lib/apps/apps.vala new file mode 100644 index 0000000..2a0d507 --- /dev/null +++ b/lib/apps/apps.vala @@ -0,0 +1,172 @@ +namespace AstalApps { +public class Apps : Object { + private string cache_directory; + private string cache_file; + private List _list; + private HashTable frequents { get; private set; } + + public bool show_hidden { get; set; } + public List list { owned get { return _list.copy(); } } + + public double min_score { get; set; default = 0.5; } + + public double name_multiplier { get; set; default = 2; } + public double entry_multiplier { get; set; default = 1; } + public double executable_multiplier { get; set; default = 1; } + public double description_multiplier { get; set; default = 0.5; } + + public bool include_name { get; set; default = true; } + public bool include_entry { get; set; default = false; } + public bool include_executable { get; set; default = false; } + public bool include_description { get; set; default = false; } + + construct { + cache_directory = Environment.get_user_cache_dir() + "/astal"; + cache_file = cache_directory + "/apps-frequents.json"; + frequents = new HashTable(str_hash, str_equal); + + AppInfoMonitor.get().changed.connect(() => { + reload(); + }); + + if (FileUtils.test(cache_file, FileTest.EXISTS)) { + try { + uint8[] content; + File.new_for_path(cache_file).load_contents(null, out content, null); + + var parser = new Json.Parser(); + parser.load_from_data((string)content); + var obj = parser.get_root().get_object(); + foreach (var member in obj.get_members()) { + var v = obj.get_member(member).get_value().get_int64(); + frequents.set(member, (int)v); + } + } catch (Error err) { + critical("cannot read cache: %s\n", err.message); + } + } + + reload(); + } + + private double score (string search, Application a, bool exact) { + var am = exact ? a.exact_match(search) : a.fuzzy_match(search); + double r = 0; + + if (include_name) + r += am.name * name_multiplier; + if (include_entry) + r += am.entry * entry_multiplier; + if (include_executable) + r += am.executable * executable_multiplier; + if (include_description) + r += am.description * description_multiplier; + + return r; + } + + public List query(string? search = "", bool exact = false) { + if (search == null) + search = ""; + + var arr = list.copy(); + + // empty search, sort by frequency + if (search == "") { + arr.sort_with_data((a, b) => { + return (int)b.frequency - (int)a.frequency; + }); + + return arr; + } + + // single character, sort by frequency and exact match + if (search.length == 1) { + foreach (var app in list) { + if (score(search, app, true) == 0) + arr.remove(app); + } + + arr.sort_with_data((a, b) => { + return (int)b.frequency - (int)a.frequency; + }); + + return arr; + } + + // filter + foreach (var app in list) { + if (score(search, app, exact) < min_score) + arr.remove(app); + } + + // sort by score, frequency + arr.sort_with_data((a, b) => { + var s1 = score(search, a, exact); + var s2 = score(search, b, exact); + + if (s1 == s2) + return (int)b.frequency - (int)a.frequency; + + return s1 < s2 ? 1 : -1; + }); + + return arr; + } + + public List fuzzy_query(string? search = "") { + return query(search, false); + } + + public List exact_query(string? search = "") { + return query(search, true); + } + + public void reload() { + var arr = AppInfo.get_all(); + + _list = new List(); + foreach (var app in arr) { + if (!show_hidden && !app.should_show()) + continue; + + var a = new Application( + app.get_id(), + frequents.get(app.get_id()) + ); + a.notify.connect((pspec) => { + if (pspec.name != "frequency") + return; + + var f = frequents.get(app.get_id()); + frequents.set(app.get_id(), ++f); + + _list.sort((a, b) => { + return (int)a.frequency - (int)b.frequency; + }); + cache(); + }); + _list.append(a); + } + + cache(); + } + + private void cache() { + var json = new Json.Builder().begin_object(); + foreach (string key in frequents.get_keys()) + json.set_member_name(key).add_int_value(frequents.get(key)); + + try { + if (!FileUtils.test(cache_directory, FileTest.EXISTS)) + File.new_for_path(cache_directory).make_directory_with_parents(null); + + var generator = new Json.Generator(); + generator.set_root(json.end_object().get_root()); + FileUtils.set_contents_full(cache_file, generator.to_data(null)); + } catch (Error err) { + critical("cannot cache frequents: %s", err.message); + } + } +} +} diff --git a/lib/apps/cli.vala b/lib/apps/cli.vala new file mode 100644 index 0000000..d926c87 --- /dev/null +++ b/lib/apps/cli.vala @@ -0,0 +1,66 @@ +static bool help; +static bool version; +static string search; +static string launch; +static bool json; + +const OptionEntry[] options = { + { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, + { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, + { "search", 's', OptionFlags.NONE, OptionArg.STRING, ref search, null, null }, + { "launch", 'l', OptionFlags.NONE, OptionArg.STRING, ref launch, null, null }, + { "json", 'j', OptionFlags.NONE, OptionArg.NONE, ref json, null, null }, + { null }, +}; + +int main(string[] argv) { + try { + var opts = new OptionContext(); + opts.add_main_entries(options, null); + opts.set_help_enabled(false); + opts.set_ignore_unknown_options(false); + opts.parse(ref argv); + } catch (OptionError err) { + printerr (err.message); + return 1; + } + + if (help) { + print("Usage:\n"); + print(" %s [flags]\n\n", argv[0]); + print("Flags:\n"); + print(" -h, --help Print this help and exit\n"); + print(" -v, --version Print version number and exit\n"); + print(" -s, --search Sort by a search term\n"); + print(" -l, --launch Launch an application\n"); + print(" -j, --json Print list in json format\n"); + return 0; + } + + if (version) { + print(AstalApps.VERSION); + return 0; + } + + var apps = new AstalApps.Apps(); + + if (launch != null) { + apps.query(launch).first().data.launch(); + return 0; + } + + if (json) { + var b = new Json.Builder().begin_array(); + foreach (var app in apps.query(search)) + b.add_value(app.to_json()); + + var generator = new Json.Generator(); + generator.set_root(b.end_array().get_root()); + stdout.printf(generator.to_data(null)); + } else { + foreach (var app in apps.query(search)) + stdout.printf("%s\n", app.entry); + } + + return 0; +} diff --git a/lib/apps/config.vala.in b/lib/apps/config.vala.in new file mode 100644 index 0000000..b3a9f49 --- /dev/null +++ b/lib/apps/config.vala.in @@ -0,0 +1,6 @@ +namespace AstalApps { + public const int MAJOR_VERSION = @MAJOR_VERSION@; + public const int MINOR_VERSION = @MINOR_VERSION@; + public const int MICRO_VERSION = @MICRO_VERSION@; + public const string VERSION = "@VERSION@"; +} diff --git a/lib/apps/meson.build b/lib/apps/meson.build new file mode 100644 index 0000000..fb87e22 --- /dev/null +++ b/lib/apps/meson.build @@ -0,0 +1,95 @@ +project( + 'astal-apps', + 'vala', + 'c', + version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), + meson_version: '>= 0.62.0', + default_options: [ + 'warning_level=2', + 'werror=false', + 'c_std=gnu11', + ], +) + +assert( + get_option('lib') or get_option('cli'), + 'Either lib or cli option must be set to true.', +) + +version_split = meson.project_version().split('.') +api_version = version_split[0] + '.' + version_split[1] +gir = 'AstalApps-' + api_version + '.gir' +typelib = 'AstalApps-' + api_version + '.typelib' + +config = configure_file( + input: 'config.vala.in', + output: 'config.vala', + configuration: { + 'VERSION': meson.project_version(), + 'MAJOR_VERSION': version_split[0], + 'MINOR_VERSION': version_split[1], + 'MICRO_VERSION': version_split[2], + }, +) + +deps = [ + dependency('glib-2.0'), + dependency('gobject-2.0'), + dependency('gio-unix-2.0'), + dependency('json-glib-1.0'), +] + +sources = [ + config, + 'apps.vala', + 'application.vala', + 'cli.vala', +] + +if get_option('lib') + lib = library( + meson.project_name(), + sources, + dependencies: deps, + vala_header: meson.project_name() + '.h', + vala_vapi: meson.project_name() + '-' + api_version + '.vapi', + vala_gir: gir, + version: meson.project_version(), + install: true, + install_dir: [true, true, true, true], + ) + + import('pkgconfig').generate( + lib, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: deps, + install_dir: get_option('libdir') / 'pkgconfig', + ) + + custom_target( + typelib, + command: [ + find_program('g-ir-compiler'), + '--output', '@OUTPUT@', + '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', + meson.current_build_dir() / gir, + ], + input: lib, + output: typelib, + depends: lib, + install: true, + install_dir: get_option('libdir') / 'girepository-1.0', + ) +endif + +if get_option('cli') + executable( + meson.project_name(), + ['cli.vala', sources], + dependencies: deps, + install: true, + ) +endif diff --git a/lib/apps/meson_options.txt b/lib/apps/meson_options.txt new file mode 100644 index 0000000..f110242 --- /dev/null +++ b/lib/apps/meson_options.txt @@ -0,0 +1,11 @@ +option( + 'lib', + type: 'boolean', + value: true, +) + +option( + 'cli', + type: 'boolean', + value: true, +) diff --git a/lib/apps/version b/lib/apps/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/apps/version @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/auth/include/astal-auth.h b/lib/auth/include/astal-auth.h new file mode 100644 index 0000000..a3073ff --- /dev/null +++ b/lib/auth/include/astal-auth.h @@ -0,0 +1,32 @@ +#ifndef ASTAL_AUTH_PAM_H +#define ASTAL_AUTH_PAM_H + +#include +#include + +G_BEGIN_DECLS + +#define ASTAL_AUTH_TYPE_PAM (astal_auth_pam_get_type()) + +G_DECLARE_FINAL_TYPE(AstalAuthPam, astal_auth_pam, ASTAL_AUTH, PAM, GObject) + +void astal_auth_pam_set_username(AstalAuthPam *self, const gchar *username); + +const gchar *astal_auth_pam_get_username(AstalAuthPam *self); + +void astal_auth_pam_set_service(AstalAuthPam *self, const gchar *service); + +const gchar *astal_auth_pam_get_service(AstalAuthPam *self); + +gboolean astal_auth_pam_start_authenticate(AstalAuthPam *self); + +void astal_auth_pam_supply_secret(AstalAuthPam *self, const gchar *secret); + +gboolean astal_auth_pam_authenticate(const gchar *password, GAsyncReadyCallback result_callback, + gpointer user_data); + +gssize astal_auth_pam_authenticate_finish(GAsyncResult *res, GError **error); + +G_END_DECLS + +#endif // !ASTAL_AUTH_PAM_H diff --git a/lib/auth/include/meson.build b/lib/auth/include/meson.build new file mode 100644 index 0000000..0575998 --- /dev/null +++ b/lib/auth/include/meson.build @@ -0,0 +1,4 @@ +astal_auth_inc = include_directories('.') +astal_auth_headers = files('astal-auth.h') + +install_headers('astal-auth.h') diff --git a/lib/auth/meson.build b/lib/auth/meson.build new file mode 100644 index 0000000..768e0f0 --- /dev/null +++ b/lib/auth/meson.build @@ -0,0 +1,23 @@ +project( + 'astal_auth', + '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'], language: 'c') + +version_split = meson.project_version().split('.') +lib_so_version = version_split[0] + '.' + version_split[1] + +pkg_config = import('pkgconfig') +gnome = import('gnome') + +subdir('include') +subdir('src') + +install_data('pam/astal-auth', install_dir: get_option('sysconfdir') / 'pam.d') diff --git a/lib/auth/meson_options.txt b/lib/auth/meson_options.txt new file mode 100644 index 0000000..a39d755 --- /dev/null +++ b/lib/auth/meson_options.txt @@ -0,0 +1,12 @@ +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/auth/pam/astal-auth b/lib/auth/pam/astal-auth new file mode 100644 index 0000000..41f79d7 --- /dev/null +++ b/lib/auth/pam/astal-auth @@ -0,0 +1,5 @@ +# PAM configuration file for the astal-auth library. +# By default, it only includes the 'login' +# configuration file (see /etc/pam.d/login) + +auth include login diff --git a/lib/auth/src/astal-auth.c b/lib/auth/src/astal-auth.c new file mode 100644 index 0000000..1ac2bd7 --- /dev/null +++ b/lib/auth/src/astal-auth.c @@ -0,0 +1,153 @@ +#include "astal-auth.h" + +#include +#include +#include + +GMainLoop *loop; + +static void cleanup_and_quit(AstalAuthPam *pam, int status) { + g_object_unref(pam); + g_main_loop_quit(loop); + exit(status); +} + +static char *read_secret(const char *msg, gboolean echo) { + struct termios oldt, newt; + char *password = NULL; + size_t size = 0; + ssize_t len; + + if (tcgetattr(STDIN_FILENO, &oldt) != 0) { + return NULL; + } + newt = oldt; + if (echo) { + newt.c_lflag |= ECHO; + } else { + newt.c_lflag &= ~(ECHO); + } + if (tcsetattr(STDIN_FILENO, TCSANOW, &newt) != 0) { + return NULL; + } + g_print("%s", msg); + if ((len = getline(&password, &size, stdin)) == -1) { + g_free(password); + return NULL; + } + + if (password[len - 1] == '\n') { + password[len - 1] = '\0'; + } + + printf("\n"); + + if (tcsetattr(STDIN_FILENO, TCSANOW, &oldt) != 0) { + return NULL; + } + + return password; +} + +static void authenticate(AstalAuthPam *pam) { + static int attempts = 0; + if (attempts >= 3) { + g_print("%d failed attempts.\n", attempts); + cleanup_and_quit(pam, EXIT_FAILURE); + } + if (!astal_auth_pam_start_authenticate(pam)) { + g_print("could not start authentication process\n"); + cleanup_and_quit(pam, EXIT_FAILURE); + } + attempts++; +} + +static void on_visible(AstalAuthPam *pam, const gchar *data) { + char *secret = read_secret(data, TRUE); + if (secret == NULL) cleanup_and_quit(pam, EXIT_FAILURE); + astal_auth_pam_supply_secret(pam, secret); + g_free(secret); +} + +static void on_hidden(AstalAuthPam *pam, const gchar *data, gchar *secret) { + if (!secret) secret = read_secret(data, FALSE); + if (secret == NULL) cleanup_and_quit(pam, EXIT_FAILURE); + astal_auth_pam_supply_secret(pam, secret); + g_free(secret); +} + +static void on_info(AstalAuthPam *pam, const gchar *data) { + g_print("info: %s\n", data); + astal_auth_pam_supply_secret(pam, NULL); +} + +static void on_error(AstalAuthPam *pam, const gchar *data) { + g_print("error: %s\n", data); + astal_auth_pam_supply_secret(pam, NULL); +} + +static void on_success(AstalAuthPam *pam) { + g_print("Authentication successful\n"); + cleanup_and_quit(pam, EXIT_SUCCESS); +} + +static void on_fail(AstalAuthPam *pam, const gchar *data, gboolean retry) { + g_print("%s\n", data); + if (retry) + authenticate(pam); + else + cleanup_and_quit(pam, EXIT_FAILURE); +} + +int main(int argc, char **argv) { + char *password = NULL; + char *username = NULL; + char *service = NULL; + + int opt; + const char *optstring = "p:u:s:"; + + static struct option long_options[] = {{"password", required_argument, NULL, 'p'}, + {"username", required_argument, NULL, 'u'}, + {"service", required_argument, NULL, 's'}, + {NULL, 0, NULL, 0}}; + + while ((opt = getopt_long(argc, argv, optstring, long_options, NULL)) != -1) { + switch (opt) { + case 'p': + password = optarg; + break; + case 'u': + username = optarg; + break; + case 's': + service = optarg; + break; + default: + g_print("Usage: %s [-p password] [-u username] [-s service]\n", argv[0]); + exit(EXIT_FAILURE); + } + } + + loop = g_main_loop_new(NULL, FALSE); + + AstalAuthPam *pam = g_object_new(ASTAL_AUTH_TYPE_PAM, NULL); + + if (username) astal_auth_pam_set_username(pam, username); + if (service) astal_auth_pam_set_service(pam, service); + if (password) { + g_signal_connect(pam, "fail", G_CALLBACK(on_fail), (void *)FALSE); + } else { + g_signal_connect(pam, "auth-prompt-visible", G_CALLBACK(on_visible), NULL); + g_signal_connect(pam, "auth-info", G_CALLBACK(on_info), NULL); + g_signal_connect(pam, "auth-error", G_CALLBACK(on_error), NULL); + g_signal_connect(pam, "fail", G_CALLBACK(on_fail), (void *)TRUE); + } + + g_signal_connect(pam, "auth-prompt-hidden", G_CALLBACK(on_hidden), g_strdup(password)); + g_signal_connect(pam, "success", G_CALLBACK(on_success), NULL); + + authenticate(pam); + + g_main_loop_run(loop); +} diff --git a/lib/auth/src/meson.build b/lib/auth/src/meson.build new file mode 100644 index 0000000..e187740 --- /dev/null +++ b/lib/auth/src/meson.build @@ -0,0 +1,59 @@ +srcs = files( + 'pam.c', +) + +deps = [dependency('gobject-2.0'), dependency('gio-2.0'), dependency('pam')] + +astal_auth_lib = library( + 'astal-auth', + sources: srcs, + include_directories: astal_auth_inc, + dependencies: deps, + version: meson.project_version(), + install: true, +) + +libastal_auth = declare_dependency(link_with: astal_auth_lib, include_directories: astal_auth_inc) + +executable( + 'astal-auth', + files('astal-auth.c'), + dependencies: [dependency('gobject-2.0'), libastal_auth], + install: true, +) + +pkg_config_name = 'astal-auth-' + lib_so_version + +if get_option('introspection') + gir = gnome.generate_gir( + astal_auth_lib, + sources: srcs + astal_auth_headers, + nsversion: '0.1', + namespace: 'AstalAuth', + symbol_prefix: 'astal_auth', + identifier_prefix: 'AstalAuth', + includes: ['GObject-2.0', 'Gio-2.0'], + header: 'astal-auth.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-auth', + version: meson.project_version(), + libraries: [astal_auth_lib], + filebase: pkg_config_name, + subdirs: 'astal', + description: 'astal authentication module', + url: 'https://github.com/astal-sh/auth', +) diff --git a/lib/auth/src/pam.c b/lib/auth/src/pam.c new file mode 100644 index 0000000..d0afec4 --- /dev/null +++ b/lib/auth/src/pam.c @@ -0,0 +1,524 @@ +#include +#include +#include + +#include "astal-auth.h" + +struct _AstalAuthPam { + GObject parent_instance; + + gchar *username; + gchar *service; +}; + +typedef struct { + GTask *task; + GMainContext *context; + GMutex data_mutex; + GCond data_cond; + + gchar *secret; + gboolean secret_set; +} AstalAuthPamPrivate; + +typedef struct { + AstalAuthPam *pam; + guint signal_id; + gchar *msg; +} AstalAuthPamSignalEmitData; + +static void astal_auth_pam_signal_emit_data_free(AstalAuthPamSignalEmitData *data) { + g_free(data->msg); + g_free(data); +} + +typedef enum { + ASTAL_AUTH_PAM_SIGNAL_PROMPT_VISIBLE, + ASTAL_AUTH_PAM_SIGNAL_PROMPT_HIDDEN, + ASTAL_AUTH_PAM_SIGNAL_INFO, + ASTAL_AUTH_PAM_SIGNAL_ERROR, + ASTAL_AUTH_PAM_SIGNAL_SUCCESS, + ASTAL_AUTH_PAM_SIGNAL_FAIL, + ASTAL_AUTH_PAM_N_SIGNALS +} AstalAuthPamSignals; + +typedef enum { + ASTAL_AUTH_PAM_PROP_USERNAME = 1, + ASTAL_AUTH_PAM_PROP_SERVICE, + ASTAL_AUTH_PAM_N_PROPERTIES +} AstalAuthPamProperties; + +static guint astal_auth_pam_signals[ASTAL_AUTH_PAM_N_SIGNALS] = { + 0, +}; +static GParamSpec *astal_auth_pam_properties[ASTAL_AUTH_PAM_N_PROPERTIES] = { + NULL, +}; + +G_DEFINE_TYPE_WITH_PRIVATE(AstalAuthPam, astal_auth_pam, G_TYPE_OBJECT); + +/** + * + * AstalAuthPam + * + * For simple authentication using only a password, using the [func@AstalAuth.Pam.authenticate] + * method is recommended. Look at the simple examples for how to use it. + * + * There is also a way to get access to the pam conversation, to allow for a more complex + * authentication process, like using multiple factor authentication. Generally it can be used like + * this: + * + * 1. create the Pam object. + * 2. set username and service if so required. It has sane defaults, so in most cases you can skip + * this. + * 3. connect to the signals. + * After an `auth-*` signal is emitted, it has to be responded with exactly one + * [method@AstalAuth.Pam.supply_secret] call. The secret is a string containing the user input. For + * [auth-info][signal@AstalAuth.Pam::auth-info:] and [auth-error][signal@AstalAuth.Pam::auth-error:] + * it should be `NULL`. Not connecting those signals, is equivalent to calling + * [method@AstalAuth.Pam.supply_secret] with `NULL` immediately after the signal is emitted. + * 4. start authentication process using [method@AstalAuth.Pam.start_authenticate]. + * 5. it is possible to reuse the same Pam object for multiple sequential authentication attempts. + * Just call [method@AstalAuth.Pam.start_authenticate] again after the `success` or `fail` signal + * was emitted. + * + */ + +/** + * astal_auth_pam_set_username + * @self: a AstalAuthPam object + * @username: the new username + * + * Sets the username to be used for authentication. This must be set to + * before calling start_authenticate. + * Changing it afterwards has no effect on the authentication process. + * + * Defaults to the owner of the process. + * + */ +void astal_auth_pam_set_username(AstalAuthPam *self, const gchar *username) { + g_return_if_fail(ASTAL_AUTH_IS_PAM(self)); + g_return_if_fail(username != NULL); + + g_free(self->username); + self->username = g_strdup(username); + g_object_notify(G_OBJECT(self), "username"); +} + +/** + * astal_auth_pam_supply_secret + * @self: a AstalAuthPam Object + * @secret: (nullable): the secret to be provided to pam. Can be NULL. + * + * provides pam with a secret. This method must be called exactly once after a + * auth-* signal is emitted. + */ +void astal_auth_pam_supply_secret(AstalAuthPam *self, const gchar *secret) { + g_return_if_fail(ASTAL_AUTH_IS_PAM(self)); + AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); + + g_mutex_lock(&priv->data_mutex); + g_free(priv->secret); + priv->secret = g_strdup(secret); + priv->secret_set = TRUE; + g_cond_signal(&priv->data_cond); + g_mutex_unlock(&priv->data_mutex); +} + +/** + * astal_auth_pam_set_service + * @self: a AstalAuthPam object + * @service: the pam service used for authentication + * + * Sets the service to be used for authentication. This must be set to + * before calling start_authenticate. + * Changing it afterwards has no effect on the authentication process. + * + * Defaults to `astal-auth`. + * + */ +void astal_auth_pam_set_service(AstalAuthPam *self, const gchar *service) { + g_return_if_fail(ASTAL_AUTH_IS_PAM(self)); + g_return_if_fail(service != NULL); + + g_free(self->service); + self->service = g_strdup(service); + g_object_notify(G_OBJECT(self), "service"); +} + +/** + * astal_auth_pam_get_username + * @self: a AstalAuthPam object + * + * Fetches the username from AsalAuthPam object. + * + * Returns: the username of the AsalAuthPam object. This string is + * owned by the object and must not be modified or freed. + */ + +const gchar *astal_auth_pam_get_username(AstalAuthPam *self) { + g_return_val_if_fail(ASTAL_AUTH_IS_PAM(self), NULL); + return self->username; +} + +/** + * astal_auth_pam_get_service + * @self: a AstalAuthPam + * + * Fetches the service from AsalAuthPam object. + * + * Returns: the service of the AsalAuthPam object. This string is + * owned by the object and must not be modified or freed. + */ +const gchar *astal_auth_pam_get_service(AstalAuthPam *self) { + g_return_val_if_fail(ASTAL_AUTH_IS_PAM(self), NULL); + return self->service; +} + +static void astal_auth_pam_set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *pspec) { + AstalAuthPam *self = ASTAL_AUTH_PAM(object); + + switch (property_id) { + case ASTAL_AUTH_PAM_PROP_USERNAME: + astal_auth_pam_set_username(self, g_value_get_string(value)); + break; + case ASTAL_AUTH_PAM_PROP_SERVICE: + astal_auth_pam_set_service(self, g_value_get_string(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_auth_pam_get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *pspec) { + AstalAuthPam *self = ASTAL_AUTH_PAM(object); + + switch (property_id) { + case ASTAL_AUTH_PAM_PROP_USERNAME: + g_value_set_string(value, self->username); + break; + case ASTAL_AUTH_PAM_PROP_SERVICE: + g_value_set_string(value, self->service); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_auth_pam_callback(GObject *object, GAsyncResult *res, gpointer user_data) { + AstalAuthPam *self = ASTAL_AUTH_PAM(object); + AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); + + GTask *task = g_steal_pointer(&priv->task); + + GError *error = NULL; + g_task_propagate_int(task, &error); + + if (error == NULL) { + g_signal_emit(self, astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_SUCCESS], 0); + } else { + g_signal_emit(self, astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_FAIL], 0, error->message); + g_error_free(error); + } + g_object_unref(task); +} + +static gboolean astal_auth_pam_emit_signal_in_context(gpointer user_data) { + AstalAuthPamSignalEmitData *data = user_data; + g_signal_emit(data->pam, data->signal_id, 0, data->msg); + return G_SOURCE_REMOVE; +} + +static void astal_auth_pam_emit_signal(AstalAuthPam *pam, guint signal, const gchar *msg) { + GSource *emit_source; + AstalAuthPamSignalEmitData *data; + + data = g_new0(AstalAuthPamSignalEmitData, 1); + data->pam = pam; + data->signal_id = astal_auth_pam_signals[signal]; + data->msg = g_strdup(msg); + + emit_source = g_idle_source_new(); + g_source_set_callback(emit_source, astal_auth_pam_emit_signal_in_context, data, + (GDestroyNotify)astal_auth_pam_signal_emit_data_free); + g_source_set_priority(emit_source, G_PRIORITY_DEFAULT); + g_source_attach(emit_source, + ((AstalAuthPamPrivate *)astal_auth_pam_get_instance_private(pam))->context); + g_source_unref(emit_source); +} + +int astal_auth_pam_handle_conversation(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr) { + AstalAuthPam *self = appdata_ptr; + AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); + + struct pam_response *replies = NULL; + if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) { + return PAM_CONV_ERR; + } + replies = (struct pam_response *)calloc(num_msg, sizeof(struct pam_response)); + if (replies == NULL) { + return PAM_BUF_ERR; + } + for (int i = 0; i < num_msg; ++i) { + guint signal; + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + signal = ASTAL_AUTH_PAM_SIGNAL_PROMPT_HIDDEN; + break; + case PAM_PROMPT_ECHO_ON: + signal = ASTAL_AUTH_PAM_SIGNAL_PROMPT_VISIBLE; + break; + case PAM_ERROR_MSG: + signal = ASTAL_AUTH_PAM_SIGNAL_ERROR; + ; + break; + case PAM_TEXT_INFO: + signal = ASTAL_AUTH_PAM_SIGNAL_INFO; + break; + default: + g_free(replies); + return PAM_CONV_ERR; + break; + } + guint signal_id = astal_auth_pam_signals[signal]; + if (g_signal_has_handler_pending(self, signal_id, 0, FALSE)) { + astal_auth_pam_emit_signal(self, signal, msg[i]->msg); + g_mutex_lock(&priv->data_mutex); + while (!priv->secret_set) { + g_cond_wait(&priv->data_cond, &priv->data_mutex); + } + replies[i].resp_retcode = 0; + replies[i].resp = g_strdup(priv->secret); + g_free(priv->secret); + priv->secret = NULL; + priv->secret_set = FALSE; + g_mutex_unlock(&priv->data_mutex); + } + } + *resp = replies; + return PAM_SUCCESS; +} + +static void astal_auth_pam_thread(GTask *task, gpointer object, gpointer task_data, + GCancellable *cancellable) { + AstalAuthPam *self = g_task_get_source_object(task); + + pam_handle_t *pamh = NULL; + const struct pam_conv conv = { + .conv = astal_auth_pam_handle_conversation, + .appdata_ptr = self, + }; + + int retval; + retval = pam_start(self->service, self->username, &conv, &pamh); + if (retval == PAM_SUCCESS) { + retval = pam_authenticate(pamh, 0); + pam_end(pamh, retval); + } + if (retval != PAM_SUCCESS) { + g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_FAILED, "%s", + pam_strerror(pamh, retval)); + } else { + g_task_return_int(task, retval); + } +} + +gboolean astal_auth_pam_start_authenticate_with_callback(AstalAuthPam *self, + GAsyncReadyCallback result_callback, + gpointer user_data) { + g_return_val_if_fail(ASTAL_AUTH_IS_PAM(self), FALSE); + AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); + g_return_val_if_fail(priv->task == NULL, FALSE); + + priv->task = g_task_new(self, NULL, result_callback, user_data); + g_task_set_priority(priv->task, 0); + g_task_set_name(priv->task, "[AstalAuth] authenticate"); + g_task_run_in_thread(priv->task, astal_auth_pam_thread); + + return TRUE; +} + +/** + * astal_auth_pam_start_authenticate: + * @self: a AstalAuthPam Object + * + * starts a new authentication process using the PAM (Pluggable Authentication Modules) system. + * Note that this will cancel an already running authentication process + * associated with this AstalAuthPam object. + */ +gboolean astal_auth_pam_start_authenticate(AstalAuthPam *self) { + return astal_auth_pam_start_authenticate_with_callback( + self, (GAsyncReadyCallback)astal_auth_pam_callback, NULL); +} + +static void astal_auth_pam_on_hidden(AstalAuthPam *pam, const gchar *msg, gchar *password) { + astal_auth_pam_supply_secret(pam, password); + g_free(password); +} + +/** + * astal_auth_pam_authenticate: + * @password: the password to be authenticated + * @result_callback: (scope async) (closure user_data): a GAsyncReadyCallback + * to call when the request is satisfied + * @user_data: the data to pass to callback function + * + * Requests authentication of the provided password using the PAM (Pluggable Authentication Modules) + * system. + */ +gboolean astal_auth_pam_authenticate(const gchar *password, GAsyncReadyCallback result_callback, + gpointer user_data) { + AstalAuthPam *pam = g_object_new(ASTAL_AUTH_TYPE_PAM, NULL); + g_signal_connect(pam, "auth-prompt-hidden", G_CALLBACK(astal_auth_pam_on_hidden), + (void *)g_strdup(password)); + + gboolean started = + astal_auth_pam_start_authenticate_with_callback(pam, result_callback, user_data); + g_object_unref(pam); + return started; +} + +gssize astal_auth_pam_authenticate_finish(GAsyncResult *res, GError **error) { + return g_task_propagate_int(G_TASK(res), error); +} + +static void astal_auth_pam_init(AstalAuthPam *self) { + AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); + + priv->secret = NULL; + + g_cond_init(&priv->data_cond); + g_mutex_init(&priv->data_mutex); + + priv->context = g_main_context_get_thread_default(); +} + +static void astal_auth_pam_finalize(GObject *gobject) { + AstalAuthPam *self = ASTAL_AUTH_PAM(gobject); + AstalAuthPamPrivate *priv = astal_auth_pam_get_instance_private(self); + + g_free(self->username); + g_free(self->service); + + g_free(priv->secret); + + g_cond_clear(&priv->data_cond); + g_mutex_clear(&priv->data_mutex); + + G_OBJECT_CLASS(astal_auth_pam_parent_class)->finalize(gobject); +} + +static void astal_auth_pam_class_init(AstalAuthPamClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + + object_class->get_property = astal_auth_pam_get_property; + object_class->set_property = astal_auth_pam_set_property; + + object_class->finalize = astal_auth_pam_finalize; + + struct passwd *passwd = getpwuid(getuid()); + + /** + * AstalAuthPam:username: + * + * The username used for authentication. + * Changing the value of this property has no affect on an already started authentication + * process. + * + * Defaults to the user that owns this process. + */ + astal_auth_pam_properties[ASTAL_AUTH_PAM_PROP_USERNAME] = + g_param_spec_string("username", "username", "username used for authentication", + passwd->pw_name, G_PARAM_CONSTRUCT | G_PARAM_READWRITE); + /** + * AstalAuthPam:service: + * + * The pam service used for authentication. + * Changing the value of this property has no affect on an already started authentication + * process. + * + * Defaults to the astal-auth pam service. + */ + astal_auth_pam_properties[ASTAL_AUTH_PAM_PROP_SERVICE] = + g_param_spec_string("service", "service", "the pam service to use", "astal-auth", + G_PARAM_CONSTRUCT | G_PARAM_READWRITE); + + g_object_class_install_properties(object_class, ASTAL_AUTH_PAM_N_PROPERTIES, + astal_auth_pam_properties); + /** + * AstalAuthPam::auth-prompt-visible: + * @pam: the object which received the signal. + * @msg: the prompt to be shown to the user + * + * This signal is emitted when user input is required. The input should be visible + * when entered (e.g., for One-Time Passwords (OTP)). + * + * This signal has to be matched with exaclty one supply_secret call. + */ + astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_PROMPT_VISIBLE] = + g_signal_new("auth-prompt-visible", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); + /** + * AstalAuthPam::auth-prompt-hidden: + * @pam: the object which received the signal. + * @msg: the prompt to be shown to the user + * + * This signal is emitted when user input is required. The input should be hidden + * when entered (e.g., for passwords). + * + * This signal has to be matched with exaclty one supply_secret call. + */ + astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_PROMPT_HIDDEN] = + g_signal_new("auth-prompt-hidden", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); + /** + * AstalAuthPam::auth-info: + * @pam: the object which received the signal. + * @msg: the info mssage to be shown to the user + * + * This signal is emitted when the user should receive an information (e.g., tell the user to + * touch a security key, or the remaining time pam has been locked after multiple failed + * attempts) + * + * This signal has to be matched with exaclty one supply_secret call. + */ + astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_INFO] = + g_signal_new("auth-info", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_STRING); + /** + * AstalAuthPam::auth-error: + * @pam: the object which received the signal. + * @msg: the error message + * + * This signal is emitted when an authentication error has occured. + * + * This signal has to be matched with exaclty one supply_secret call. + */ + astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_ERROR] = + g_signal_new("auth-error", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, G_TYPE_STRING); + /** + * AstalAuthPam::success: + * @pam: the object which received the signal. + * + * This signal is emitted after successful authentication + */ + astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_SUCCESS] = + g_signal_new("success", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + /** + * AstalAuthPam::fail: + * @pam: the object which received the signal. + * @msg: the authentication failure message + * + * This signal is emitted when authentication failed. + */ + astal_auth_pam_signals[ASTAL_AUTH_PAM_SIGNAL_FAIL] = + g_signal_new("fail", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_STRING); +} diff --git a/lib/auth/version b/lib/auth/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/auth/version @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/battery/cli.vala b/lib/battery/cli.vala new file mode 100644 index 0000000..710edec --- /dev/null +++ b/lib/battery/cli.vala @@ -0,0 +1,74 @@ +static bool help; +static bool version; +static bool monitor; + +const OptionEntry[] options = { + { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, + { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, + { "monitor", 'm', OptionFlags.NONE, OptionArg.NONE, ref monitor, null, null }, + { null }, +}; + +int main(string[] argv) { + try { + var opts = new OptionContext(); + opts.add_main_entries(options, null); + opts.set_help_enabled(false); + opts.set_ignore_unknown_options(false); + opts.parse(ref argv); + } catch (OptionError err) { + printerr (err.message); + return 1; + } + + if (help) { + print("Usage:\n"); + print(" %s [flags]\n\n", argv[0]); + print("Flags:\n"); + print(" -h, --help Print this help and exit\n"); + print(" -v, --version Print version number and exit\n"); + print(" -m, --monitor Monitor property changes\n"); + return 0; + } + + if (version) { + print(AstalBattery.VERSION); + return 0; + } + + var battery = AstalBattery.get_default(); + print("%s\n", to_json(battery)); + + if (monitor) { + battery.notify.connect((prop) => { + if (prop.get_name() == "percentage" + || prop.get_name() == "state" + || prop.get_name() == "icon-name" + || prop.get_name() == "time-to-full" + || prop.get_name() == "time-to-empty" + ) { + print("%s\n", to_json(battery)); + } + }); + new GLib.MainLoop(null, false).run(); + } + + return 0; +} + +private string to_json(AstalBattery.Device device) { + string s = "unknown"; + if (device.state == AstalBattery.State.CHARGING) + s = "charging"; + if (device.state == AstalBattery.State.DISCHARGING) + s = "discharging"; + if (device.state == AstalBattery.State.FULLY_CHARGED) + s = "fully_charged"; + + var p = device.percentage; + var i = device.icon_name; + var r = device.state == AstalBattery.State.CHARGING + ? device.time_to_full : device.time_to_empty; + + return "{ \"percentage\": %f, \"state\": \"%s\", \"icon_name\": \"%s\", \"time_remaining\": %f }".printf(p, s, i, r); +} diff --git a/lib/battery/config.vala.in b/lib/battery/config.vala.in new file mode 100644 index 0000000..6e7f77e --- /dev/null +++ b/lib/battery/config.vala.in @@ -0,0 +1,6 @@ +namespace AstalBattery { + public const int MAJOR_VERSION = @MAJOR_VERSION@; + public const int MINOR_VERSION = @MINOR_VERSION@; + public const int MICRO_VERSION = @MICRO_VERSION@; + public const string VERSION = "@VERSION@"; +} diff --git a/lib/battery/device.vala b/lib/battery/device.vala new file mode 100644 index 0000000..eab3770 --- /dev/null +++ b/lib/battery/device.vala @@ -0,0 +1,296 @@ +namespace AstalBattery { +public Device get_default() { + return Device.get_default(); +} + +public class Device : Object { + private static Device display_device; + public static Device? get_default() { + if (display_device != null) + return display_device; + + try { + display_device = new Device("/org/freedesktop/UPower/devices/DisplayDevice"); + + return display_device; + } catch (Error error) { + critical(error.message); + } + return null; + } + + private IUPowerDevice proxy; + + public Device(string path) throws Error { + proxy = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.UPower", path); + proxy.g_properties_changed.connect(sync); + sync(); + } + + public Type device_type { get; private set; } + public string native_path { owned get; private set; } + public string vendor { owned get; private set; } + public string model { owned get; private set; } + public string serial { owned get; private set; } + public uint64 update_time { get; private set; } + public bool power_supply { get; private set; } + public bool has_history { get; private set; } + public bool has_statistics { get; private set; } + public bool online { get; private set; } + public double energy { get; private set; } + public double energy_empty { get; private set; } + public double energy_full { get; private set; } + public double energy_full_design { get; private set; } + public double energy_rate { get; private set; } + public double voltage { get; private set; } + public int charge_cycles { get; private set; } + public double luminosity { get; private set; } + public int64 time_to_empty { get; private set; } + public int64 time_to_full { get; private set;} + public double percentage { get; private set; } + public double temperature { get; private set; } + public bool is_present { get; private set; } + public State state { get; private set; } + public bool is_rechargable { get; private set; } + public double capacity { get; private set; } + public Technology technology { get; private set; } + public WarningLevel warning_level { get; private set; } + public BatteryLevel battery_level { get; private set; } + public string icon_name { owned get; private set; } + + public bool charging { get; private set; } + public bool is_battery { get; private set; } + public string battery_icon_name { get; private set; } + public string device_type_name { get; private set; } + public string device_type_icon { get; private set; } + + private unowned string get_battery_icon() { + if (percentage <= 0) + return "battery-good"; + + if (percentage < 0.10) + return "battery-empty"; + + if (percentage < 0.37) + return "battery-caution"; + + if (percentage < 0.62) + return "battery-low"; + + if (percentage < 0.87) + return "battery-good"; + + return "battery-full"; + } + + + public void sync() { + device_type = (Type)proxy.Type; + native_path = proxy.native_path; + vendor = proxy.vendor; + model = proxy.model; + serial = proxy.serial; + update_time = proxy.update_time; + power_supply = proxy.power_supply; + has_history = proxy.has_history; + has_statistics = proxy.has_statistics; + online = proxy.online; + energy = proxy.energy; + energy_empty = proxy.energy_empty; + energy_full = proxy.energy_full; + energy_full_design = proxy.energy_full_design; + energy_rate = proxy.energy_rate; + voltage = proxy.voltage; + charge_cycles = proxy.charge_cycles; + luminosity = proxy.luminosity; + time_to_empty = proxy.time_to_empty; + time_to_full = proxy.time_to_full; + percentage = proxy.percentage / 100; + temperature = proxy.temperature; + is_present = proxy.is_present; + state = (State)proxy.state; + is_rechargable = proxy.is_rechargable; + capacity = proxy.capacity; + technology = (Technology)proxy.technology; + warning_level = (WarningLevel)proxy.warning_level; + battery_level = (BatteryLevel)proxy.battery_level; + icon_name = proxy.icon_name; + + charging = state == State.FULLY_CHARGED || state == State.CHARGING; + is_battery = device_type != Type.UNKNOWN && device_type != Type.LINE_POWER; + + if (!is_battery) + battery_icon_name = "preferences-system-power"; + else if (percentage == 1.0 && charging) + battery_icon_name = "battery-full-charged"; + else + battery_icon_name = charging ? get_battery_icon() + "-charging" : get_battery_icon(); + + device_type_name = device_type.get_name(); + device_type_icon = device_type.get_icon_name(); + } +} + +[CCode (type_signature = "u")] +public enum State { + UNKNOWN = 0, + CHARGING, + DISCHARGING, + EMPTY, + FULLY_CHARGED, + PENDING_CHARGE, + PENDING_DISCHARGE, +} + +[CCode (type_signature = "u")] +public enum Technology { + UNKNOWN = 0, + LITHIUM_ION, + LITHIUM_POLYMER, + LITHIUM_IRON_PHOSPHATE, + LEAD_ACID, + NICKEL_CADMIUM, + NICKEL_METAL_HYDRIDE, +} + +[CCode (type_signature = "u")] +public enum WarningLevel { + UNKNOWN = 0, + NONE, + DISCHARGING, + LOW, + CRITICIAL, + ACTION, +} + +[CCode (type_signature = "u")] +public enum BatteryLevel { + UNKNOWN = 0, + NONE, + LOW, + CRITICIAL, + NORMAL, + HIGH, + FULL, +} + +[CCode (type_signature = "u")] +public enum Type { + UNKNOWN = 0, + LINE_POWER, + BATTERY, + UPS, + MONITOR, + MOUSE, + KEYBOARD, + PDA, + PHONE, + MEDIA_PLAYER, + TABLET, + COMPUTER, + GAMING_INPUT, + PEN, + TOUCHPAD, + MODEM, + NETWORK, + HEADSET, + SPEAKERS, + HEADPHONES, + VIDEO, + OTHER_AUDIO, + REMOVE_CONTROL, + PRINTER, + SCANNER, + CAMERA, + WEARABLE, + TOY, + BLUETOOTH_GENERIC; + + // TODO: add more icon names + public string? get_icon_name () { + switch (this) { + case UPS: + return "uninterruptible-power-supply"; + case MOUSE: + return "input-mouse"; + case KEYBOARD: + return "input-keyboard"; + case PDA: + case PHONE: + return "phone"; + case MEDIA_PLAYER: + return "multimedia-player"; + case TABLET: + case PEN: + return "input-tablet"; + case GAMING_INPUT: + return "input-gaming"; + default: + return null; + } + } + + public unowned string? get_name () { + switch (this) { + case LINE_POWER: + return "Plugged In"; + case BATTERY: + return "Battery"; + case UPS: + return "UPS"; + case MONITOR: + return "Display"; + case MOUSE: + return "Mouse"; + case KEYBOARD: + return "Keyboard"; + case PDA: + return "PDA"; + case PHONE: + return "Phone"; + case MEDIA_PLAYER: + return "Media Player"; + case TABLET: + return "Tablet"; + case COMPUTER: + return "Computer"; + case GAMING_INPUT: + return "Controller"; + case PEN: + return "Pen"; + case TOUCHPAD: + return "Touchpad"; + case MODEM: + return "Modem"; + case NETWORK: + return "Network"; + case HEADSET: + return "Headset"; + case SPEAKERS: + return "Speakers"; + case HEADPHONES: + return "Headphones"; + case VIDEO: + return "Video"; + case OTHER_AUDIO: + return "Other Audio"; + case REMOVE_CONTROL: + return "Remove Control"; + case PRINTER: + return "Printer"; + case SCANNER: + return "Scanner"; + case CAMERA: + return "Camera"; + case WEARABLE: + return "Wearable"; + case TOY: + return "Toy"; + case BLUETOOTH_GENERIC: + return "Bluetooth Generic"; + default: + return "Unknown"; + } + } +} +} diff --git a/lib/battery/ifaces.vala b/lib/battery/ifaces.vala new file mode 100644 index 0000000..e6eb849 --- /dev/null +++ b/lib/battery/ifaces.vala @@ -0,0 +1,65 @@ +namespace AstalBattery { +[DBus (name = "org.freedesktop.UPower")] +interface IUPower : DBusProxy { + public abstract string[] enumerate_devices() throws Error; + public abstract string get_display_device() throws Error; + public abstract string get_critical_action() throws Error; + + public signal void device_added(string object_path); + public signal void device_removed(string object_path); + + public abstract string daemon_version { owned get; } + public abstract bool on_battery { get; } + public abstract bool lid_is_closed { get; } + public abstract bool lis_is_present { get; } +} + +[DBus (name = "org.freedesktop.UPower.Device")] +public interface IUPowerDevice : DBusProxy { + public abstract HistoryDataPoint[] get_history (string type, uint32 timespan, uint32 resolution) throws GLib.Error; + public abstract StatisticsDataPoint[] get_statistics (string type) throws GLib.Error; + public abstract void refresh () throws GLib.Error; + + public abstract uint Type { get; } + public abstract string native_path { owned get; } + public abstract string vendor { owned get; } + public abstract string model { owned get; } + public abstract string serial { owned get; } + public abstract uint64 update_time { get; } + public abstract bool power_supply { get; } + public abstract bool has_history { get; } + public abstract bool has_statistics { get; } + public abstract bool online { get; } + public abstract double energy { get; } + public abstract double energy_empty { get; } + public abstract double energy_full { get; } + public abstract double energy_full_design { get; } + public abstract double energy_rate { get; } + public abstract double voltage { get; } + public abstract int32 charge_cycles { get; } + public abstract double luminosity { get; } + public abstract int64 time_to_empty { get; } + public abstract int64 time_to_full { get; } + public abstract double percentage { get; } + public abstract double temperature { get; } + public abstract bool is_present { get; } + public abstract uint state { get; } + public abstract bool is_rechargable { get; } + public abstract double capacity { get; } + public abstract uint technology { get; } + public abstract uint32 warning_level { get; } + public abstract uint32 battery_level { get; } + public abstract string icon_name { owned get; } +} + +public struct HistoryDataPoint { + uint32 time; + double value; + uint32 state; +} + +public struct StatisticsDataPoint { + double value; + double accuracy; +} +} diff --git a/lib/battery/meson.build b/lib/battery/meson.build new file mode 100644 index 0000000..bfb8255 --- /dev/null +++ b/lib/battery/meson.build @@ -0,0 +1,94 @@ +project( + 'astal-battery', + 'vala', + 'c', + version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), + meson_version: '>= 0.62.0', + default_options: [ + 'warning_level=2', + 'werror=false', + 'c_std=gnu11', + ], +) + +assert( + get_option('lib') or get_option('cli'), + 'Either lib or cli option must be set to true.', +) + +version_split = meson.project_version().split('.') +api_version = version_split[0] + '.' + version_split[1] +gir = 'AstalBattery-' + api_version + '.gir' +typelib = 'AstalBattery-' + api_version + '.typelib' + +config = configure_file( + input: 'config.vala.in', + output: 'config.vala', + configuration: { + 'VERSION': meson.project_version(), + 'MAJOR_VERSION': version_split[0], + 'MINOR_VERSION': version_split[1], + 'MICRO_VERSION': version_split[2], + }, +) + +deps = [ + dependency('glib-2.0'), + dependency('gio-2.0'), + dependency('gobject-2.0'), +] + +sources = [ + config, + 'ifaces.vala', + 'device.vala', + 'upower.vala', +] + +if get_option('lib') + lib = library( + meson.project_name(), + sources, + dependencies: deps, + vala_header: meson.project_name() + '.h', + vala_vapi: meson.project_name() + '-' + api_version + '.vapi', + vala_gir: gir, + version: meson.project_version(), + install: true, + install_dir: [true, true, true, true], + ) + + import('pkgconfig').generate( + lib, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: deps, + install_dir: get_option('libdir') / 'pkgconfig', + ) + + custom_target( + typelib, + command: [ + find_program('g-ir-compiler'), + '--output', '@OUTPUT@', + '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', + meson.current_build_dir() / gir, + ], + input: lib, + output: typelib, + depends: lib, + install: true, + install_dir: get_option('libdir') / 'girepository-1.0', + ) +endif + +if get_option('cli') + executable( + meson.project_name(), + ['cli.vala', sources], + dependencies: deps, + install: true, + ) +endif diff --git a/lib/battery/meson_options.txt b/lib/battery/meson_options.txt new file mode 100644 index 0000000..f110242 --- /dev/null +++ b/lib/battery/meson_options.txt @@ -0,0 +1,11 @@ +option( + 'lib', + type: 'boolean', + value: true, +) + +option( + 'cli', + type: 'boolean', + value: true, +) diff --git a/lib/battery/upower.vala b/lib/battery/upower.vala new file mode 100644 index 0000000..9c18ffd --- /dev/null +++ b/lib/battery/upower.vala @@ -0,0 +1,58 @@ +namespace AstalBattery { +public class UPower : Object { + private IUPower proxy; + private HashTable _devices = + new HashTable(str_hash, str_equal); + + public List devices { + owned get { return _devices.get_values(); } + } + + public signal void device_added(Device device); + public signal void device_removed(Device device); + + public Device display_device { owned get { return Device.get_default(); }} + + public string daemon_version { owned get { return proxy.daemon_version; } } + public bool on_battery { get { return proxy.on_battery; } } + public bool lid_is_closed { get { return proxy.lid_is_closed; } } + public bool lis_is_present { get { return proxy.lid_is_closed; } } + + public string critical_action { + owned get { + try { + return proxy.get_critical_action(); + } catch (Error error) { + critical(error.message); + return ""; + } + } + } + + construct { + try { + proxy = Bus.get_proxy_sync( + BusType.SYSTEM, + "org.freedesktop.UPower", + "/org/freedesktop/UPower" + ); + + foreach (var path in proxy.enumerate_devices()) + _devices.set(path, new Device(path)); + + proxy.device_added.connect((path) => { + _devices.set(path, new Device(path)); + notify_property("devices"); + }); + + proxy.device_removed.connect((path) => { + device_removed(_devices.get(path)); + _devices.remove(path); + notify_property("devices"); + }); + } catch (Error error) { + critical(error.message); + } + } +} +} diff --git a/lib/battery/version b/lib/battery/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/battery/version @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/bluetooth/adapter.vala b/lib/bluetooth/adapter.vala new file mode 100644 index 0000000..0c9d00e --- /dev/null +++ b/lib/bluetooth/adapter.vala @@ -0,0 +1,89 @@ +namespace AstalBluetooth { +[DBus (name = "org.bluez.Adapter1")] +internal interface IAdapter : DBusProxy { + public abstract void remove_device(ObjectPath device) throws Error; + public abstract void start_discovery() throws Error; + public abstract void stop_discovery() throws Error; + + public abstract string[] uuids { owned get; } + public abstract bool discoverable { get; set; } + public abstract bool discovering { get; } + public abstract bool pairable { get; set; } + public abstract bool powered { get; set; } + public abstract string address { owned get; } + public abstract string alias { owned get; set; } + public abstract string modalias { owned get; } + public abstract string name { owned get; } + public abstract uint class { get; } + public abstract uint discoverable_timeout { get; set; } + public abstract uint pairable_timeout { get; set; } +} + +public class Adapter : Object { + private IAdapter proxy; + public string object_path { owned get; construct set; } + + internal Adapter(IAdapter proxy) { + this.proxy = proxy; + this.object_path = proxy.g_object_path; + proxy.g_properties_changed.connect((props) => { + var map = (HashTable)props; + foreach (var key in map.get_keys()) { + var prop = kebab_case(key); + if (get_class().find_property(prop) != null) { + notify_property(prop); + } + } + }); + } + + public string[] uuids { owned get { return proxy.uuids; } } + public bool discovering { get { return proxy.discovering; } } + public string modalias { owned get { return proxy.modalias; } } + public string name { owned get { return proxy.name; } } + public uint class { get { return proxy.class; } } + public string address { owned get { return proxy.address; } } + + public bool discoverable { + get { return proxy.discoverable; } + set { proxy.discoverable = value; } + } + + public bool pairable { + get { return proxy.pairable; } + set { proxy.pairable = value; } + } + + public bool powered { + get { return proxy.powered; } + set { proxy.powered = value; } + } + + public string alias { + owned get { return proxy.alias; } + set { proxy.alias = value; } + } + + public uint discoverable_timeout { + get { return proxy.discoverable_timeout; } + set { proxy.discoverable_timeout = value; } + } + + public uint pairable_timeout { + get { return proxy.pairable_timeout; } + set { proxy.pairable_timeout = value; } + } + + public void remove_device(Device device) { + try { proxy.remove_device((ObjectPath)device.object_path); } catch (Error err) { critical(err.message); } + } + + public void start_discovery() { + try { proxy.start_discovery(); } catch (Error err) { critical(err.message); } + } + + public void stop_discovery() { + try { proxy.stop_discovery(); } catch (Error err) { critical(err.message); } + } +} +} diff --git a/lib/bluetooth/bluetooth.vala b/lib/bluetooth/bluetooth.vala new file mode 100644 index 0000000..ce086ba --- /dev/null +++ b/lib/bluetooth/bluetooth.vala @@ -0,0 +1,181 @@ +namespace AstalBluetooth { +public Bluetooth get_default() { + return Bluetooth.get_default(); +} + +public class Bluetooth : Object { + private static Bluetooth _instance; + + public static Bluetooth get_default() { + if (_instance == null) + _instance = new Bluetooth(); + + return _instance; + } + + private DBusObjectManagerClient manager; + + private HashTable _adapters = + new HashTable(str_hash, str_equal); + + private HashTable _devices = + new HashTable(str_hash, str_equal); + + public signal void device_added (Device device) { + notify_property("devices"); + } + + public signal void device_removed (Device device) { + notify_property("devices"); + } + + public signal void adapter_added (Adapter adapter) { + notify_property("adapters"); + } + + public signal void adapter_removed (Adapter adapter) { + notify_property("adapters"); + } + + public bool is_powered { get; private set; default = false; } + public bool is_connected { get; private set; default = false; } + public Adapter? adapter { get { return adapters.nth_data(0); } } + + public List adapters { + owned get { return _adapters.get_values(); } + } + + public List devices { + owned get { return _devices.get_values(); } + } + + construct { + try { + manager = new DBusObjectManagerClient.for_bus_sync( + BusType.SYSTEM, + DBusObjectManagerClientFlags.NONE, + "org.bluez", + "/", + manager_proxy_get_type, + null + ); + + foreach (var object in manager.get_objects()) { + foreach (var iface in object.get_interfaces()) { + on_interface_added(object, iface); + } + } + + manager.interface_added.connect(on_interface_added); + manager.interface_removed.connect(on_interface_removed); + + manager.object_added.connect((object) => { + foreach (var iface in object.get_interfaces()) { + on_interface_added(object, iface); + } + }); + + manager.object_removed.connect((object) => { + foreach (var iface in object.get_interfaces()) { + on_interface_removed(object, iface); + } + }); + } catch (Error err) { + critical(err.message); + } + } + + public void toggle() { + adapter.powered = !adapter.powered; + } + + [CCode (cname="astal_bluetooth_idevice_proxy_get_type")] + extern static GLib.Type get_idevice_proxy_type(); + + [CCode (cname="astal_bluetooth_iadapter_proxy_get_type")] + extern static GLib.Type get_iadapter_proxy_type(); + + private Type manager_proxy_get_type(DBusObjectManagerClient _, string object_path, string? interface_name) { + if (interface_name == null) + return typeof(DBusObjectProxy); + + switch (interface_name) { + case "org.bluez.Device1": + return get_idevice_proxy_type(); + case "org.bluez.Adapter1": + return get_iadapter_proxy_type(); + default: + return typeof(DBusProxy); + } + } + + private void on_interface_added(DBusObject object, DBusInterface iface) { + if (iface is IDevice) { + var device = new Device((IDevice)iface); + _devices.set(device.object_path, device); + device_added(device); + device.notify.connect(sync); + sync(); + } + + if (iface is IAdapter) { + var adapter = new Adapter((IAdapter)iface); + _adapters.set(adapter.object_path, adapter); + adapter_added(adapter); + adapter.notify.connect(sync); + sync(); + } + } + + private void on_interface_removed (DBusObject object, DBusInterface iface) { + if (iface is IDevice) { + unowned var device = (IDevice)iface; + device_removed(_devices.get(device.g_object_path)); + _devices.remove(device.g_object_path); + } + + if (iface is IAdapter) { + unowned var adapter = (IAdapter)iface; + adapter_removed(_adapters.get(adapter.g_object_path)); + _adapters.remove(adapter.g_object_path); + } + + sync(); + } + + private void sync() { + var powered = get_powered(); + var connected = get_connected(); + + if (powered != is_powered || connected != is_connected) { + if (powered != is_powered) { + is_powered = powered; + } + + if (connected != is_connected) { + is_connected = connected; + } + } + } + + private bool get_powered() { + foreach (var adapter in adapters) { + if (adapter.powered) { + return true; + } + } + + return false; + } + + private bool get_connected() { + foreach (var device in devices) { + if (device.connected) { + return true; + } + } + + return false; + } +} +} diff --git a/lib/bluetooth/config.vala.in b/lib/bluetooth/config.vala.in new file mode 100644 index 0000000..9fce720 --- /dev/null +++ b/lib/bluetooth/config.vala.in @@ -0,0 +1,6 @@ +namespace AstalBluetooth { + public const int MAJOR_VERSION = @MAJOR_VERSION@; + public const int MINOR_VERSION = @MINOR_VERSION@; + public const int MICRO_VERSION = @MICRO_VERSION@; + public const string VERSION = "@VERSION@"; +} diff --git a/lib/bluetooth/device.vala b/lib/bluetooth/device.vala new file mode 100644 index 0000000..8fe086f --- /dev/null +++ b/lib/bluetooth/device.vala @@ -0,0 +1,106 @@ +namespace AstalBluetooth { +[DBus (name = "org.bluez.Device1")] +internal interface IDevice : DBusProxy { + public abstract void cancel_pairing() throws Error; + public abstract async void connect() throws Error; + public abstract void connect_profile(string uuid) throws Error; + public abstract async void disconnect() throws Error; + public abstract void disconnect_profile(string uuid) throws Error; + public abstract void pair() throws Error; + + public abstract string[] uuids { owned get; } + public abstract bool blocked { get; set; } + public abstract bool connected { get; } + public abstract bool legacy_pairing { get; } + public abstract bool paired { get; } + public abstract bool trusted { get; set; } + public abstract int16 rssi { get; } + public abstract ObjectPath adapter { owned get; } + public abstract string address { owned get; } + public abstract string alias { owned get; set; } + public abstract string icon { owned get; } + public abstract string modalias { owned get; } + public abstract string name { owned get; } + public abstract uint16 appearance { get; } + public abstract uint32 class { get; } +} + +public class Device : Object { + private IDevice proxy; + public string object_path { owned get; construct set; } + + internal Device(IDevice proxy) { + this.proxy = proxy; + this.object_path = proxy.g_object_path; + proxy.g_properties_changed.connect((props) => { + var map = (HashTable)props; + foreach (var key in map.get_keys()) { + var prop = kebab_case(key); + if (get_class().find_property(prop) != null) { + notify_property(prop); + } + } + }); + } + + public string[] uuids { owned get { return proxy.uuids; } } + public bool connected { get { return proxy.connected; } } + public bool legacy_pairing { get { return proxy.legacy_pairing; } } + public bool paired { get { return proxy.paired; } } + public int16 rssi { get { return proxy.rssi; } } + public ObjectPath adapter { owned get { return proxy.adapter; } } + public string address { owned get { return proxy.address; } } + public string icon { owned get { return proxy.icon; } } + public string modalias { owned get { return proxy.modalias; } } + public string name { owned get { return proxy.name; } } + public uint16 appearance { get { return proxy.appearance; } } + public uint32 class { get { return proxy.class; } } + public bool connecting { get; private set; } + + public bool blocked { + get { return proxy.blocked; } + set { proxy.blocked = value; } + } + + public bool trusted { + get { return proxy.trusted; } + set { proxy.trusted = value; } + } + + public string alias { + owned get { return proxy.alias; } + set { proxy.alias = value; } + } + + public void cancel_pairing() { + try { proxy.cancel_pairing(); } catch (Error err) { critical(err.message); } + } + + public async void connect_device() { + try { + connecting = true; + yield proxy.connect(); + } catch (Error err) { + critical(err.message); + } finally { + connecting = false; + } + } + + public async void disconnect_device() { + try { yield proxy.disconnect(); } catch (Error err) { critical(err.message); } + } + + public void connect_profile(string uuid) { + try { proxy.connect_profile(uuid); } catch (Error err) { critical(err.message); } + } + + public void disconnect_profile(string uuid) { + try { proxy.disconnect_profile(uuid); } catch (Error err) { critical(err.message); } + } + + public void pair() { + try { proxy.pair(); } catch (Error err) { critical(err.message); } + } +} +} diff --git a/lib/bluetooth/meson.build b/lib/bluetooth/meson.build new file mode 100644 index 0000000..934d380 --- /dev/null +++ b/lib/bluetooth/meson.build @@ -0,0 +1,79 @@ +project( + 'astal-bluetooth', + 'vala', + 'c', + version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), + meson_version: '>= 0.62.0', + default_options: [ + 'warning_level=2', + 'werror=false', + 'c_std=gnu11', + ], +) + +version_split = meson.project_version().split('.') +api_version = version_split[0] + '.' + version_split[1] +gir = 'AstalBluetooth-' + api_version + '.gir' +typelib = 'AstalBluetooth-' + api_version + '.typelib' + +config = configure_file( + input: 'config.vala.in', + output: 'config.vala', + configuration: { + 'VERSION': meson.project_version(), + 'MAJOR_VERSION': version_split[0], + 'MINOR_VERSION': version_split[1], + 'MICRO_VERSION': version_split[2], + }, +) + +deps = [ + dependency('glib-2.0'), + dependency('gobject-2.0'), + dependency('gio-2.0'), +] + +sources = [ + config, + 'utils.vala', + 'device.vala', + 'adapter.vala', + 'bluetooth.vala', +] + +lib = library( + meson.project_name(), + sources, + dependencies: deps, + vala_header: meson.project_name() + '.h', + vala_vapi: meson.project_name() + '-' + api_version + '.vapi', + vala_gir: gir, + version: meson.project_version(), + install: true, + install_dir: [true, true, true, true], +) + +import('pkgconfig').generate( + lib, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: deps, + install_dir: get_option('libdir') / 'pkgconfig', +) + +custom_target( + typelib, + command: [ + find_program('g-ir-compiler'), + '--output', '@OUTPUT@', + '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', + meson.current_build_dir() / gir, + ], + input: lib, + output: typelib, + depends: lib, + install: true, + install_dir: get_option('libdir') / 'girepository-1.0', +) diff --git a/lib/bluetooth/utils.vala b/lib/bluetooth/utils.vala new file mode 100644 index 0000000..5dcaff6 --- /dev/null +++ b/lib/bluetooth/utils.vala @@ -0,0 +1,21 @@ +namespace AstalBluetooth { +internal string kebab_case(string pascal_case) { + StringBuilder kebab_case = new StringBuilder(); + + for (int i = 0; i < pascal_case.length; i++) { + char c = pascal_case[i]; + + if (c >= 'A' && c <= 'Z') { + if (i != 0) { + kebab_case.append_c('-'); + } + + kebab_case.append_c((char)(c + 32)); + } else { + kebab_case.append_c(c); + } + } + + return kebab_case.str; +} +} diff --git a/lib/bluetooth/version b/lib/bluetooth/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/bluetooth/version @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/hyprland/cli.vala b/lib/hyprland/cli.vala new file mode 100644 index 0000000..a68d63b --- /dev/null +++ b/lib/hyprland/cli.vala @@ -0,0 +1,42 @@ +static bool help; +static bool version; + +const OptionEntry[] options = { + { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, + { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, + { null }, +}; + +int main(string[] argv) { + try { + var opts = new OptionContext(); + opts.add_main_entries(options, null); + opts.set_help_enabled(false); + opts.set_ignore_unknown_options(false); + opts.parse(ref argv); + } catch (OptionError err) { + printerr (err.message); + return 1; + } + + if (help) { + print("Usage:\n"); + print(" %s [flags]\n\n", argv[0]); + print("Flags:\n"); + print(" -h, --help Print this help and exit\n"); + print(" -v, --version Print version number and exit\n"); + return 0; + } + + if (version) { + print(AstalHyprland.VERSION); + return 0; + } + + AstalHyprland.Hyprland.get_default().event.connect((event, args) => { + print("{ event: \"%s\", payload: \"%s\" }\n", event, args); + }); + + new MainLoop(null, false).run(); + return 0; +} diff --git a/lib/hyprland/client.vala b/lib/hyprland/client.vala new file mode 100644 index 0000000..6456667 --- /dev/null +++ b/lib/hyprland/client.vala @@ -0,0 +1,75 @@ +namespace AstalHyprland { +public class Client : Object { + public signal void removed (); + public signal void moved_to (Workspace workspace); + + public string address { get; private set; } + public bool mapped { get; private set; } + public bool hidden { get; private set; } + public int x { get; private set; } + public int y { get; private set; } + public int width { get; private set; } + public int height { get; private set; } + public Workspace workspace { get; private set; } + public bool floating { get; private set; } + public Monitor monitor { get; private set; } + public string class { get; private set; } + public string title { get; private set; } + public string initial_class { get; private set; } + public string initial_title { get; private set; } + public uint pid { get; private set; } + public bool xwayland { get; private set; } + public bool pinned { get; private set; } + public bool fullscreen { get; private set; } + public int fullscreen_mode { get; private set; } + public bool fake_fullscreen { get; private set; } + // TODO: public Group[] grouped { get; private set; } + // TODO: public Tag[] tags { get; private set; } + public string swallowing { get; private set; } + public int focus_history_id { get; private set; } + + internal void sync(Json.Object obj) { + var hyprland = Hyprland.get_default(); + + address = obj.get_string_member("address").replace("0x", ""); + mapped = obj.get_boolean_member("mapped"); + hidden = obj.get_boolean_member("hidden"); + floating = obj.get_boolean_member("floating"); + class = obj.get_string_member("class"); + title = obj.get_string_member("title"); + initial_title = obj.get_string_member("initialTitle"); + initial_class = obj.get_string_member("initialClass"); + pid = (uint)obj.get_int_member("pid"); + xwayland = obj.get_boolean_member("xwayland"); + pinned = obj.get_boolean_member("pinned"); + fullscreen = obj.get_boolean_member("fullscreen"); + fullscreen_mode = (int)obj.get_int_member("fullscreenMode"); // is this used? + fake_fullscreen = obj.get_boolean_member("fakeFullscreen"); + swallowing = obj.get_string_member("swallowing"); + focus_history_id = (int)obj.get_int_member("focusHistoryID"); + x = (int)obj.get_array_member("at").get_int_element(0); + y = (int)obj.get_array_member("at").get_int_element(1); + width = (int)obj.get_array_member("size").get_int_element(0); + height = (int)obj.get_array_member("size").get_int_element(1); + + workspace = hyprland.get_workspace((int)obj.get_object_member("workspace").get_int_member("id")); + monitor = hyprland.get_monitor((int)obj.get_int_member("monitor")); + } + + public void kill() { + Hyprland.get_default().dispatch("closewindow", "address:" + "0x" + address); + } + + public void focus() { + Hyprland.get_default().dispatch("focuswindow", "address:" + "0x" + address); + } + + public void move_to(Workspace ws) { + Hyprland.get_default().dispatch("movetoworkspacesilent", ws.id.to_string() + ",address:" + "0x" + address); + } + + public void toggle_floating() { + Hyprland.get_default().dispatch("togglefloating", "address:" + "0x" + address); + } +} +} diff --git a/lib/hyprland/config.vala.in b/lib/hyprland/config.vala.in new file mode 100644 index 0000000..65993b2 --- /dev/null +++ b/lib/hyprland/config.vala.in @@ -0,0 +1,6 @@ +namespace AstalHyprland { + public const int MAJOR_VERSION = @MAJOR_VERSION@; + public const int MINOR_VERSION = @MINOR_VERSION@; + public const int MICRO_VERSION = @MICRO_VERSION@; + public const string VERSION = "@VERSION@"; +} diff --git a/lib/hyprland/hyprland.vala b/lib/hyprland/hyprland.vala new file mode 100644 index 0000000..5359d2e --- /dev/null +++ b/lib/hyprland/hyprland.vala @@ -0,0 +1,451 @@ +namespace AstalHyprland { +public Hyprland get_default() { + return Hyprland.get_default(); +} + +public class Hyprland : Object { + private static string HIS = GLib.Environment.get_variable("HYPRLAND_INSTANCE_SIGNATURE"); + private static string RUN_DIR = GLib.Environment.get_user_runtime_dir(); + + private static Hyprland _instance; + public static Hyprland? get_default() { + if (_instance != null) + return _instance; + + var HIS = GLib.Environment.get_variable("HYPRLAND_INSTANCE_SIGNATURE"); + if (HIS == null) { + critical("Hyprland is not running"); + return null; + } + + var h = new Hyprland(); + _instance = h; + + h.socket2 = h.connection("socket2"); + h.watch_socket(new DataInputStream(h.socket2.input_stream)); + try { + h.init(); + } catch (Error err) { + critical("could not initialize: %s", err.message); + return null; + } + + return _instance; + } + + // monitors, workspaces, clients + private HashTable _monitors = + new HashTable((i) => i, (a, b) => a == b); + + private HashTable _workspaces = + new HashTable((i) => i, (a, b) => a == b); + + private HashTable _clients = + new HashTable(str_hash, str_equal); + + public List monitors { owned get { return _monitors.get_values(); } } + public List workspaces { owned get { return _workspaces.get_values(); } } + public List clients { owned get { return _clients.get_values(); } } + + public Monitor get_monitor(int id) { return _monitors.get(id); } + public Workspace get_workspace(int id) { return _workspaces.get(id); } + public Client? get_client(string address) { + if (address == "" || address == null) + return null; + + if (address.substring(0, 2) == "0x") + return _clients.get(address.substring(2, -1)); + + return _clients.get(address); + } + + public Monitor? get_monitor_by_name(string name) { + foreach (var mon in monitors) { + if (mon.name == name) + return mon; + } + return null; + } + + public Workspace? get_workspace_by_name(string name) { + foreach (var ws in workspaces) { + if (ws.name == name) + return ws; + } + return null; + } + + public Workspace focused_workspace { get; private set; } + public Monitor focused_monitor { get; private set; } + public Client focused_client { get; private set; } + + // other props + public List binds { + owned get { + var list = new List(); + try { + var arr = Json.from_string(message("j/binds")).get_array(); + foreach (var b in arr.get_elements()) + list.append(new Bind.from_json(b.get_object())); + } catch (Error err) { + critical(err.message); + } + return list; + } + } + + public Position cursor_position { + owned get { + return new Position.cursorpos(message("cursorpos")); + } + } + + // signals + public signal void event (string event, string args); + + // TODO: nag vaxry for fullscreenv2 + // public signal void fullscreen (bool fullscreen); + public signal void minimize (Client client, bool minimize); + public signal void floating (Client client, bool floating); + public signal void urgent (Client client); + public signal void client_moved (Client client, Workspace ws); + + public signal void submap (string name); + public signal void keyboard_layout (string keyboard, string layout); + public signal void config_reloaded (); + + // state + public signal void client_added (Client client); + public signal void client_removed (string address); + public signal void workspace_added (Workspace workspace); + public signal void workspace_removed (int id); + public signal void monitor_added (Monitor monitor); + public signal void monitor_removed (int id); + + private SocketConnection socket2; + + private SocketConnection? connection(string socket) { + var path = RUN_DIR + "/hypr/" + HIS + "/." + socket + ".sock"; + try { + return new SocketClient().connect(new UnixSocketAddress(path), null); + } catch (Error err) { + critical(err.message); + return null; + } + } + + private void watch_socket(DataInputStream stream) { + stream.read_line_async.begin(Priority.DEFAULT, null, (_, res) => { + try { + var line = stream.read_line_async.end(res); + handle_event.begin(line, (_, res) => { + try { + handle_event.end(res); + } catch (Error err) { + critical(err.message); + } + }); + watch_socket(stream); + } catch (Error err) { + critical(err.message); + } + }); + } + + private void write_socket( + string message, + out SocketConnection conn, + out DataInputStream stream + ) throws Error { + conn = connection("socket"); + conn.output_stream.write(message.data, null); + stream = new DataInputStream(conn.input_stream); + } + + public string message(string message) { + SocketConnection conn; + DataInputStream stream; + try { + write_socket(message, out conn, out stream); + return stream.read_upto("\x04", -1, null, null); + } catch (Error err) { + critical(err.message); + } finally { + try { + if (conn != null) + conn.close(null); + } catch (Error err) { + critical(err.message); + } + } + return ""; + } + + public async string message_async(string message) { + SocketConnection conn; + DataInputStream stream; + try { + write_socket(message, out conn, out stream); + return yield stream.read_upto_async("\x04", -1, Priority.DEFAULT, null, null); + } catch (Error err) { + critical(err.message); + } finally { + try { + conn.close(null); + } catch (Error err) { + critical(err.message); + } + } + return ""; + } + + public void dispatch(string dispatcher, string args) { + var msg = "dispatch " + dispatcher + " " + args; + message_async.begin(msg, (_, res) => { + var err = message_async.end(res); + if (err != "ok") + critical("dispatch error: %s", err); + }); + } + + public void move_cursor(int x, int y) { + dispatch("movecursor", x.to_string() + " " + y.to_string()); + + } + + // TODO: nag vaxry to make socket events and hyprctl more consistent + private void init() throws Error { + var mons = Json.from_string(message("j/monitors")).get_array(); + var wrkspcs = Json.from_string(message("j/workspaces")).get_array(); + var clnts = Json.from_string(message("j/clients")).get_array(); + + // create + foreach (var mon in mons.get_elements()) { + var id = (int)mon.get_object().get_member("id").get_int(); + var m = new Monitor(); + _monitors.insert(id, m); + + if (mon.get_object().get_member("focused").get_boolean()) + focused_monitor = m; + } + foreach (var wrkpsc in wrkspcs.get_elements()) { + var id = (int)wrkpsc.get_object().get_member("id").get_int(); + _workspaces.set(id, new Workspace()); + } + foreach (var clnt in clnts.get_elements()) { + var addr = clnt.get_object().get_member("address").get_string(); + _clients.set(addr.replace("0x", ""), new Client()); + } + + // init + foreach (var c in clnts.get_elements()) { + var addr = c.get_object().get_member("address").get_string(); + get_client(addr).sync(c.get_object()); + } + foreach (var ws in wrkspcs.get_elements()) { + var id = (int)ws.get_object().get_member("id").get_int(); + get_workspace(id).sync(ws.get_object()); + } + foreach (var mon in mons.get_elements()) { + var id = (int)mon.get_object().get_member("id").get_int(); + get_monitor(id).sync(mon.get_object()); + } + + // focused + focused_workspace = get_workspace((int)Json.from_string(message("j/activeworkspace")) + .get_object().get_member("id").get_int()); + + focused_client = get_client(Json.from_string(message("j/activewindow")) + .get_object().get_member("address").get_string()); + } + + ~Hyprland() { + if (socket2 != null) { + try { + socket2.close(null); + } catch (Error err) { + critical(err.message); + } + } + } + + public async void sync_monitors() throws Error { + var str = yield message_async("j/monitors"); + var arr = Json.from_string(str).get_array(); + foreach (var obj in arr.get_elements()) { + var id = (int)obj.get_object().get_int_member("id"); + var m = get_monitor(id); + if (m != null) + m.sync(obj.get_object()); + } + } + + public async void sync_workspaces() throws Error { + var str = yield message_async("j/workspaces"); + var arr = Json.from_string(str).get_array(); + foreach (var obj in arr.get_elements()) { + var id = (int)obj.get_object().get_int_member("id"); + var ws = get_workspace(id); + if (ws != null) + ws.sync(obj.get_object()); + + } + } + + public async void sync_clients() throws Error { + var str = yield message_async("j/clients"); + var arr = Json.from_string(str).get_array(); + foreach (var obj in arr.get_elements()) { + var addr = obj.get_object().get_string_member("address"); + var c = get_client(addr); + if (c != null) + c.sync(obj.get_object()); + } + } + + private async void handle_event(string line) throws Error { + var args = line.split(">>"); + + switch (args[0]) { + case "workspacev2": + focused_workspace = get_workspace(int.parse(args[1])); + break; + + case "focusedmon": + var argv = args[1].split(",", 2); + focused_monitor = get_monitor_by_name(argv[0]); + focused_workspace = get_workspace_by_name(argv[1]); + break; + + case "activewindowv2": + focused_client = get_client(args[1]); + break; + + // TODO: nag vaxry for fullscreenv2 that passes address + case "fullscreen": + yield sync_clients(); + break; + + case "monitorremoved": + var id = get_monitor_by_name(args[1]).id; + _monitors.get(id).removed(); + _monitors.remove(id); + monitor_removed(id); + notify_property("monitors"); + break; + + case "monitoraddedv2": + var id = int.parse(args[1].split(",", 2)[0]); + var mon = new Monitor(); + _monitors.insert(id, mon); + yield sync_monitors(); + monitor_added(mon); + notify_property("monitors"); + break; + + case "createworkspacev2": + var id = int.parse(args[1].split(",", 2)[0]); + var ws = new Workspace(); + _workspaces.insert(id, ws); + yield sync_workspaces(); + workspace_added(ws); + notify_property("workspaces"); + break; + + case "destroyworkspacev2": + var id = int.parse(args[1].split(",", 2)[0]); + _workspaces.get(id).removed(); + _workspaces.remove(id); + workspace_removed(id); + notify_property("workspaces"); + break; + + case "moveworkspacev2": + yield sync_workspaces(); + yield sync_monitors(); + break; + + case "renameworkspace": + yield sync_workspaces(); + break; + + case "activespecial": + yield sync_monitors(); + yield sync_workspaces(); + break; + + case "activelayout": + var argv = args[1].split(","); + keyboard_layout(argv[0], argv[1]); + break; + + case "openwindow": + var addr = args[1].split(",")[0]; + var client = new Client(); + _clients.insert(addr, client); + yield sync_clients(); + yield sync_workspaces(); + client_added(client); + notify_property("clients"); + break; + + case "closewindow": + _clients.get(args[1]).removed(); + _clients.remove(args[1]); + client_removed(args[1]); + yield sync_workspaces(); + notify_property("clients"); + break; + + case "movewindowv2": + yield sync_clients(); + yield sync_workspaces(); + var argv = args[1].split(","); + client_moved(get_client(argv[0]), get_workspace(int.parse(argv[1]))); + get_client(argv[0]).moved_to(get_workspace(int.parse(argv[1]))); + break; + + case "submap": + submap(args[1]); + break; + + case "changefloatingmode": + var argv = args[1].split(","); + yield sync_clients(); + floating(get_client(argv[0]), argv[1] == "0"); + break; + + case "urgent": + urgent(get_client(args[1])); + break; + + case "minimize": + var argv = args[1].split(","); + yield sync_clients(); + minimize(get_client(argv[0]), argv[1] == "0"); + break; + + case "windowtitle": + yield sync_clients(); + break; + + // TODO: + case "togglegroup": + case "moveintogroup": + case "moveoutofgroup": + case "ignoregrouplock": + case "lockgroups": + break; + + case "configreloaded": + config_reloaded(); + break; + + default: + break; + } + + event(args[0], args[1]); + } +} +} diff --git a/lib/hyprland/meson.build b/lib/hyprland/meson.build new file mode 100644 index 0000000..7112ee1 --- /dev/null +++ b/lib/hyprland/meson.build @@ -0,0 +1,99 @@ +project( + 'astal-hyprland', + 'vala', + 'c', + version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), + meson_version: '>= 0.62.0', + default_options: [ + 'warning_level=2', + 'werror=false', + 'c_std=gnu11', + ], +) + +assert( + get_option('lib') or get_option('cli'), + 'Either lib or cli option must be set to true.', +) + +version_split = meson.project_version().split('.') +api_version = version_split[0] + '.' + version_split[1] +gir = 'AstalHyprland-' + api_version + '.gir' +typelib = 'AstalHyprland-' + api_version + '.typelib' + +config = configure_file( + input: 'config.vala.in', + output: 'config.vala', + configuration: { + 'VERSION': meson.project_version(), + 'MAJOR_VERSION': version_split[0], + 'MINOR_VERSION': version_split[1], + 'MICRO_VERSION': version_split[2], + }, +) + +deps = [ + dependency('glib-2.0'), + dependency('gobject-2.0'), + dependency('gio-2.0'), + dependency('gio-unix-2.0'), + dependency('json-glib-1.0'), +] + +sources = [ + config, + 'client.vala', + 'cli.vala', + 'hyprland.vala', + 'monitor.vala', + 'structs.vala', + 'workspace.vala', +] + +if get_option('lib') + lib = library( + meson.project_name(), + sources, + dependencies: deps, + vala_header: meson.project_name() + '.h', + vala_vapi: meson.project_name() + '-' + api_version + '.vapi', + vala_gir: gir, + version: meson.project_version(), + install: true, + install_dir: [true, true, true, true], + ) + + import('pkgconfig').generate( + lib, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: deps, + install_dir: get_option('libdir') / 'pkgconfig', + ) + + custom_target( + typelib, + command: [ + find_program('g-ir-compiler'), + '--output', '@OUTPUT@', + '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', + meson.current_build_dir() / gir, + ], + input: lib, + output: typelib, + depends: lib, + install: true, + install_dir: get_option('libdir') / 'girepository-1.0', + ) +endif + +if get_option('cli') + executable( + meson.project_name(), + ['cli.vala', sources], + dependencies: deps, + install: true, + ) +endif diff --git a/lib/hyprland/meson_options.txt b/lib/hyprland/meson_options.txt new file mode 100644 index 0000000..f110242 --- /dev/null +++ b/lib/hyprland/meson_options.txt @@ -0,0 +1,11 @@ +option( + 'lib', + type: 'boolean', + value: true, +) + +option( + 'cli', + type: 'boolean', + value: true, +) diff --git a/lib/hyprland/monitor.vala b/lib/hyprland/monitor.vala new file mode 100644 index 0000000..d7b8028 --- /dev/null +++ b/lib/hyprland/monitor.vala @@ -0,0 +1,71 @@ +namespace AstalHyprland { +public class Monitor : Object { + public signal void removed (); + + public int id { get; private set; } + public string name { get; private set; } + public string description { get; private set; } + public string make { get; private set; } + public string model { get; private set; } + public string serial { get; private set; } + public int width { get; private set; } + public int height { get; private set; } + public double refresh_rate { get; private set; } + public int x { get; private set; } + public int y { get; private set; } + public Workspace active_workspace { get; private set; } + public Workspace special_workspace { get; private set; } + public int reserved_top { get; private set; } + public int reserved_bottom { get; private set; } + public int reserved_left { get; private set; } + public int reserved_right { get; private set; } + public double scale { get; private set; } + public bool focused { get; private set; } + public bool dpms_status { get; private set; } + public bool vrr { get; private set; } + public bool actively_tearing { get; private set; } + public bool disabled { get; private set; } + public string current_format { get; private set; } + public Array available_modes { get; private set; } + + internal void sync(Json.Object obj) { + var hyprland = Hyprland.get_default(); + + id = (int)obj.get_int_member("id"); + name = obj.get_string_member("name"); + description = obj.get_string_member("description"); + make = obj.get_string_member("make"); + model = obj.get_string_member("model"); + serial = obj.get_string_member("serial"); + width = (int)obj.get_int_member("width"); + height = (int)obj.get_int_member("height"); + refresh_rate = obj.get_double_member("refreshRate"); + x = (int)obj.get_int_member("x"); + y = (int)obj.get_int_member("y"); + scale = obj.get_double_member("scale"); + focused = obj.get_boolean_member("focused"); + dpms_status = obj.get_boolean_member("dpmsStatus"); + vrr = obj.get_boolean_member("vrr"); + actively_tearing = obj.get_boolean_member("activelyTearing"); + disabled = obj.get_boolean_member("disabled"); + current_format = obj.get_string_member("currentFormat"); + + var r = obj.get_array_member("reserved"); + reserved_top = (int)r.get_int_element(0); + reserved_bottom = (int)r.get_int_element(1); + reserved_left = (int)r.get_int_element(2); + reserved_right = (int)r.get_int_element(3); + + var modes = new Array(); + foreach (var mode in obj.get_array_member("availableModes").get_elements()) + modes.append_val(mode.get_string()); + + active_workspace = hyprland.get_workspace((int)obj.get_object_member("activeWorkspace").get_int_member("id")); + special_workspace = hyprland.get_workspace((int)obj.get_object_member("specialWorkspace").get_int_member("id")); + } + + public void focus() { + Hyprland.get_default().dispatch("focusmonitor", id.to_string()); + } +} +} diff --git a/lib/hyprland/structs.vala b/lib/hyprland/structs.vala new file mode 100644 index 0000000..25f70c3 --- /dev/null +++ b/lib/hyprland/structs.vala @@ -0,0 +1,42 @@ +namespace AstalHyprland { +public class Bind : Object { + public bool locked { get; construct set; } + public bool mouse { get; construct set; } + public bool release { get; construct set; } + public bool repeat { get; construct set; } + public bool non_consuming { get; construct set; } + public int64 modmask { get; construct set; } + public string submap { get; construct set; } + public string key { get; construct set; } + public int64 keycode { get; construct set; } + public bool catch_all { get; construct set; } + public string dispatcher { get; construct set; } + public string arg { get; construct set; } + + internal Bind.from_json(Json.Object obj) { + locked = obj.get_boolean_member("locked"); + mouse = obj.get_boolean_member("mouse"); + release = obj.get_boolean_member("release"); + repeat = obj.get_boolean_member("repeat"); + non_consuming = obj.get_boolean_member("non_consuming"); + modmask = obj.get_int_member("modmask"); + submap = obj.get_string_member("submap"); + key = obj.get_string_member("key"); + keycode = obj.get_int_member("keycode"); + catch_all = obj.get_boolean_member("catch_all"); + dispatcher = obj.get_string_member("dispatcher"); + arg = obj.get_string_member("arg"); + } +} + +public class Position : Object { + public int x { get; construct set; } + public int y { get; construct set; } + + internal Position.cursorpos(string pos) { + var xy = pos.split(","); + x = int.parse(xy[0].strip()); + y = int.parse(xy[1].strip()); + } +} +} diff --git a/lib/hyprland/version b/lib/hyprland/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/hyprland/version @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/hyprland/workspace.vala b/lib/hyprland/workspace.vala new file mode 100644 index 0000000..075f86f --- /dev/null +++ b/lib/hyprland/workspace.vala @@ -0,0 +1,57 @@ +namespace AstalHyprland { +public class Workspace : Object { + public signal void removed (); + + public List _clients = new List(); + + public int id { get; private set; } + public string name { get; private set; } + public Monitor monitor { get; private set; } + public List clients { owned get { return _clients.copy(); } } + public bool has_fullscreen { get; private set; } + public Client last_client { get; private set; } + + public Workspace.dummy(int id, Monitor? monitor) { + this.id = id; + this.name = id.to_string(); + this.monitor = monitor; + } + + internal List filter_clients() { + var hyprland = Hyprland.get_default(); + var list = new List(); + foreach (var client in hyprland.clients) { + if (client.workspace == this) { + list.append(client); + } + } + + return list; + } + + internal void sync(Json.Object obj) { + var hyprland = Hyprland.get_default(); + + id = (int)obj.get_int_member("id"); + name = obj.get_string_member("name"); + has_fullscreen = obj.get_boolean_member("hasfullscreen"); + + monitor = hyprland.get_monitor((int)obj.get_int_member("monitorID")); + last_client = hyprland.get_client(obj.get_string_member("lastwindow")); + + var list = filter_clients(); + if (_clients.length() != list.length()) { + _clients = list.copy(); + notify_property("clients"); + } + } + + public void focus() { + Hyprland.get_default().dispatch("workspace", id.to_string()); + } + + public void move_to(Monitor m) { + Hyprland.get_default().dispatch("moveworkspacetomonitor", id.to_string() + " " + m.id.to_string()); + } +} +} diff --git a/lib/mpris/cli.vala b/lib/mpris/cli.vala new file mode 100644 index 0000000..b71def9 --- /dev/null +++ b/lib/mpris/cli.vala @@ -0,0 +1,331 @@ +namespace AstalMpris { +static bool help; +static bool version; +static bool list; +static bool raw; +[CCode (array_length = false, array_null_terminated = true)] +static string[] players; + +const OptionEntry[] options = { + { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, + { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, + { "player", 'p', OptionFlags.NONE, OptionArg.STRING_ARRAY, ref players, null, null }, + { "list", 'l', OptionFlags.NONE, OptionArg.NONE, ref list, null, null }, + { "raw", 'r', OptionFlags.NONE, OptionArg.NONE, ref raw, null, null }, + { null }, +}; + +int main(string[] argv) { + try { + var opts = new OptionContext(); + opts.add_main_entries(options, null); + opts.set_help_enabled(false); + opts.set_ignore_unknown_options(false); + opts.parse(ref argv); + } catch (OptionError err) { + printerr(err.message); + return 1; + } + + if (help) { + print("Usage:\n"); + print(" %s [flags] [command]\n\n", argv[0]); + print("Flags:\n"); + print(" -h, --help Print this help and exit\n"); + print(" -v, --version Print version number and exit\n"); + print(" -l, --list List available players\n"); + print(" -p, --player Operate on given player\n"); + print(" -r, --raw Print single line json info\n"); + print("\nCommands:\n"); + print(" info Print info about player\n"); + print(" monitor Monitor changes\n"); + print(" play Play track\n"); + print(" pause Pause track\n"); + print(" play-pause Play if paused, Pause if playing\n"); + print(" stop Stop player\n"); + print(" next Play next track\n"); + print(" previous Play previous track\n"); + print(" quit Quit player\n"); + print(" raise Ask compositor to raise the player\n"); + print(" position [OFFSET][+/-/%] Set position of player\n"); + print(" volume [LEVEL][+/-/%] Set volume of player\n"); + print(" loop [STATUS] One of: \"None\", \"Track\", \"Playlist\"\n"); + print(" shuffle [STATUS] One of: \"On\", \"Off\", \"Toggle\"\n"); + return 0; + } + + if (version) { + print(VERSION); + return 0; + } + + var mpris = new Mpris(); + var mpris_players = new List(); + + if (list) { + foreach (var p in mpris.players) + print("%s\n", p.bus_name.replace(Mpris.PREFIX, "")); + + return 0; + } + + if (players.length > 0) { + foreach (var name in players) + mpris_players.append(new Player(name)); + } else { + foreach (var p in mpris.players) + mpris_players.append(p); + } + + var cmd = argv[1]; + var arg = argv[2]; + + switch (cmd) { + case "monitor": + return do_monitor(mpris); + + case "info": + print_players(mpris_players.copy()); + break; + + case "play": + foreach (var player in mpris_players) + player.play(); + break; + + case "pause": + foreach (var player in mpris_players) + player.pause(); + break; + + case "play-pause": + foreach (var player in mpris_players) + player.play_pause(); + break; + + case "stop": + foreach (var player in mpris_players) + player.stop(); + break; + + case "next": + foreach (var player in mpris_players) + player.next(); + break; + + case "previous": + foreach (var player in mpris_players) + player.previous(); + break; + + case "raise": + foreach (var player in mpris_players) + player.raise(); + break; + + case "quit": + foreach (var player in mpris_players) + player.quit(); + break; + + case "position": + foreach (var player in mpris_players) { + if (do_position(player, arg) != 0) + return 1; + } + break; + + case "volume": + foreach (var player in mpris_players) { + if (do_volume(player, arg) != 0) + return 1; + } + break; + + case "loop": + foreach (var player in mpris_players) { + if (do_loop(player, arg) != 0) + return 1; + } + break; + + case "shuffle": + foreach (var player in mpris_players) { + if (do_shuffle(player, arg) != 0) + return 1; + } + break; + + case "open": + if (arg == null) { + stderr.printf("missing open arg"); + return 1; + } + + foreach (var player in mpris_players) + player.open_uri(arg); + break; + + default: + if (cmd == null) + stderr.printf("missing command\n"); + else + stderr.printf(@"unknown command \"$cmd\"\n"); + return 1; + } + + return 0; +} + +Json.Node to_json(Player p) { + var uris = new Json.Builder().begin_array(); + foreach (var uri in p.supported_uri_schemas) + uris.add_string_value(uri); + + uris.end_array(); + + return new Json.Builder().begin_object() + .set_member_name("bus_name").add_string_value(p.bus_name) + .set_member_name("available").add_boolean_value(p.available) + .set_member_name("identity").add_string_value(p.identity) + .set_member_name("entry").add_string_value(p.entry) + .set_member_name("supported_uri_schemas").add_value(uris.get_root()) + .set_member_name("loop_status").add_string_value(p.loop_status.to_string()) + .set_member_name("shuffle_status").add_string_value(p.shuffle_status.to_string()) + .set_member_name("rate").add_double_value(p.rate) + .set_member_name("volume").add_double_value(p.volume) + .set_member_name("position").add_double_value(p.position) + .set_member_name("cover_art").add_string_value(p.cover_art) + .set_member_name("metadata").add_value(Json.gvariant_serialize( + p.metadata != null ? p.metadata : new HashTable(str_hash, str_equal))) + .end_object() + .get_root(); +} + +void print_players(List players) { + var json = new Json.Builder().begin_array(); + + foreach (var p in players) + json.add_value(to_json(p)); + + stdout.printf("%s\n", Json.to_string(json.end_array().get_root(), !raw)); + stdout.flush(); +} + +int do_monitor(Mpris mpris) { + print_players(mpris.players); + foreach (var player in mpris.players) { + player.notify.connect(() => print_players(mpris.players)); + } + + mpris.player_added.connect((player) => { + player.notify.connect(() => print_players(mpris.players)); + }); + + mpris.player_closed.connect(() => { + print_players(mpris.players); + }); + + new MainLoop(null, false).run(); + return 0; +} + +int do_position(Player player, string? arg) { + if (arg == null) { + stderr.printf("missing position argument\n"); + return 1; + } + + else if (arg.has_suffix("%")) { + var percent = double.parse(arg.slice(0, -1)) / 100; + player.position = player.length * percent; + } + + else if (arg.has_suffix("-")) { + player.position += double.parse(arg.slice(0, -1)) * -1; + } + + else if (arg.has_suffix("+")) { + player.position += double.parse(arg.slice(0, -1)); + } + + else { + player.position = double.parse(arg); + } + + return 0; +} + +int do_volume(Player player, string? arg) { + if (arg == null) { + stderr.printf("missing volume argument\n"); + return 1; + } + + else if (arg.has_suffix("%")) { + player.volume = double.parse(arg.slice(0, -1)) / 100; + } + + else if (arg.has_suffix("-")) { + player.volume += (double.parse(arg.slice(0, -1)) * -1) / 100; + } + + else if (arg.has_suffix("+")) { + player.volume += double.parse(arg.slice(0, -1)) / 100; + } + + else { + player.volume = double.parse(arg); + } + + return 0; +} + +int do_loop(Player player, string? arg) { + if (arg == null) { + player.loop(); + return 0; + } + + switch (arg) { + case "None": + player.loop_status = Loop.NONE; + break; + case "Track": + player.loop_status = Loop.TRACK; + break; + case "Playlist": + player.loop_status = Loop.PLAYLIST; + break; + default: + stderr.printf(@"unknown shuffle status \"$arg\""); + return 1; + } + + return 0; +} + +int do_shuffle(Player player, string? arg) { + if (arg == null) { + player.shuffle(); + return 1; + } + + switch (arg) { + case "On": + player.shuffle_status = Shuffle.ON; + break; + case "Off": + player.shuffle_status = Shuffle.OFF; + break; + case "Toggle": + player.shuffle(); + break; + default: + stderr.printf(@"unknown shuffle status \"$arg\""); + return 1; + } + + return 0; +} +} diff --git a/lib/mpris/config.vala.in b/lib/mpris/config.vala.in new file mode 100644 index 0000000..767c4bd --- /dev/null +++ b/lib/mpris/config.vala.in @@ -0,0 +1,6 @@ +namespace AstalMpris { + public const int MAJOR_VERSION = @MAJOR_VERSION@; + public const int MINOR_VERSION = @MINOR_VERSION@; + public const int MICRO_VERSION = @MICRO_VERSION@; + public const string VERSION = "@VERSION@"; +} diff --git a/lib/mpris/ifaces.vala b/lib/mpris/ifaces.vala new file mode 100644 index 0000000..4a9d715 --- /dev/null +++ b/lib/mpris/ifaces.vala @@ -0,0 +1,60 @@ +namespace AstalMpris { +[DBus (name="org.freedesktop.DBus")] +internal interface DBusImpl : DBusProxy { + public abstract string[] list_names () throws GLib.Error; + public signal void name_owner_changed (string name, string old_owner, string new_owner); +} + +[DBus (name="org.freedesktop.DBus.Properties")] +internal interface PropsIface : DBusProxy { + public abstract HashTable get_all (string iface); +} + +[DBus (name="org.mpris.MediaPlayer2")] +internal interface IMpris : PropsIface { + public abstract void raise () throws GLib.Error; + public abstract void quit () throws GLib.Error; + + public abstract bool can_quit { get; } + public abstract bool fullscreen { get; set; } + public abstract bool can_set_fullscreen { get; } + public abstract bool can_raise { get; } + public abstract bool has_track_list { get; } + public abstract string identity { owned get; } + public abstract string desktop_entry { owned get; } + public abstract string[] supported_uri_schemas { owned get; } + public abstract string[] supported_mime_types { owned get; } +} + +[DBus (name="org.mpris.MediaPlayer2.Player")] +internal interface IPlayer : IMpris { + public abstract void next () throws GLib.Error; + public abstract void previous () throws GLib.Error; + public abstract void pause () throws GLib.Error; + public abstract void play_pause () throws GLib.Error; + public abstract void stop () throws GLib.Error; + public abstract void play () throws GLib.Error; + public abstract void seek (int64 offset) throws GLib.Error; + public abstract void set_position (ObjectPath track_id, int64 position) throws GLib.Error; + public abstract void open_uri (string uri) throws GLib.Error; + + public signal void seeked (int64 position); + + public abstract string playback_status { owned get; } + public abstract string loop_status { owned get; set; } + public abstract double rate { get; set; } + public abstract bool shuffle { get; set; } + public abstract HashTable metadata { owned get; } + public abstract double volume { get; set; } + public abstract int64 position { get; } + public abstract double minimum_rate { get; set; } + public abstract double maximum_rate { get; set; } + + public abstract bool can_go_next { get; } + public abstract bool can_go_previous { get; } + public abstract bool can_play { get; } + public abstract bool can_pause { get; } + public abstract bool can_seek { get; } + public abstract bool can_control { get; } +} +} diff --git a/lib/mpris/meson.build b/lib/mpris/meson.build new file mode 100644 index 0000000..c9a5c53 --- /dev/null +++ b/lib/mpris/meson.build @@ -0,0 +1,94 @@ +project( + 'astal-mpris', + 'vala', + 'c', + version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), + meson_version: '>= 0.62.0', + default_options: [ + 'warning_level=2', + 'werror=false', + 'c_std=gnu11', + ], +) + +assert( + get_option('lib') or get_option('cli'), + 'Either lib or cli option must be set to true.', +) + +version_split = meson.project_version().split('.') +api_version = version_split[0] + '.' + version_split[1] +gir = 'AstalMpris-' + api_version + '.gir' +typelib = 'AstalMpris-' + api_version + '.typelib' + +config = configure_file( + input: 'config.vala.in', + output: 'config.vala', + configuration: { + 'VERSION': meson.project_version(), + 'MAJOR_VERSION': version_split[0], + 'MINOR_VERSION': version_split[1], + 'MICRO_VERSION': version_split[2], + }, +) + +deps = [ + dependency('glib-2.0'), + dependency('gobject-2.0'), + dependency('json-glib-1.0'), +] + +sources = [ + config, + 'ifaces.vala', + 'player.vala', + 'mpris.vala', +] + +if get_option('lib') + lib = library( + meson.project_name(), + sources, + dependencies: deps, + vala_header: meson.project_name() + '.h', + vala_vapi: meson.project_name() + '-' + api_version + '.vapi', + vala_gir: gir, + version: meson.project_version(), + install: true, + install_dir: [true, true, true, true], + ) + + import('pkgconfig').generate( + lib, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: deps, + install_dir: get_option('libdir') / 'pkgconfig', + ) + + custom_target( + typelib, + command: [ + find_program('g-ir-compiler'), + '--output', '@OUTPUT@', + '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', + meson.current_build_dir() / gir, + ], + input: lib, + output: typelib, + depends: lib, + install: true, + install_dir: get_option('libdir') / 'girepository-1.0', + ) +endif + +if get_option('cli') + executable( + meson.project_name(), + ['cli.vala', sources], + dependencies: deps, + install: true, + ) +endif diff --git a/lib/mpris/meson_options.txt b/lib/mpris/meson_options.txt new file mode 100644 index 0000000..f110242 --- /dev/null +++ b/lib/mpris/meson_options.txt @@ -0,0 +1,11 @@ +option( + 'lib', + type: 'boolean', + value: true, +) + +option( + 'cli', + type: 'boolean', + value: true, +) diff --git a/lib/mpris/mpris.vala b/lib/mpris/mpris.vala new file mode 100644 index 0000000..0e55a2e --- /dev/null +++ b/lib/mpris/mpris.vala @@ -0,0 +1,66 @@ +namespace AstalMpris { +public Mpris get_default() { + return Mpris.get_default(); +} + +public class Mpris : Object { + internal static string PREFIX = "org.mpris.MediaPlayer2."; + + private static Mpris instance; + public static Mpris get_default() { + if (instance == null) + instance = new Mpris(); + + return instance; + } + + private DBusImpl proxy; + + private HashTable _players = + new HashTable (str_hash, str_equal); + + public List players { owned get { return _players.get_values(); } } + + public signal void player_added (Player player); + public signal void player_closed (Player player); + + construct { + try { + proxy = Bus.get_proxy_sync( + BusType.SESSION, + "org.freedesktop.DBus", + "/org/freedesktop/DBus" + ); + + foreach (var busname in proxy.list_names()) { + if (busname.has_prefix(Mpris.PREFIX)) + add_player(busname); + } + + proxy.name_owner_changed.connect((name, old_owner, new_owner) => { + if (!name.has_prefix(Mpris.PREFIX)) + return; + + if (new_owner != "" && old_owner == "") + add_player(name); + }); + } catch (Error error) { + critical(error.message); + } + } + + private void add_player(string busname) { + var p = new Player(busname); + _players.set(busname, p); + + p.closed.connect(() => { + player_closed(p); + _players.remove(busname); + notify_property("players"); + }); + + player_added(p); + notify_property("players"); + } +} +} diff --git a/lib/mpris/player.vala b/lib/mpris/player.vala new file mode 100644 index 0000000..ed146f6 --- /dev/null +++ b/lib/mpris/player.vala @@ -0,0 +1,467 @@ +namespace AstalMpris { +public class Player : Object { + private static string COVER_CACHE = Environment.get_user_cache_dir() + "/astal/mpris"; + + private IPlayer proxy; + + public signal void appeared () { available = true; } + public signal void closed () { available = false; } + + // identifiers + public string bus_name { owned get; construct set; } + public bool available { get; private set; } + + // periodically notify position + private uint pollid; + + // mpris + public void raise() { + try { proxy.raise(); } catch (Error error) { critical(error.message); } + } + + public void quit() { + try { proxy.quit(); } catch (Error error) { critical(error.message); } + } + + public bool can_quit { get; private set; } + public bool fullscreen { get; private set; } + public bool can_set_fullscreen { get; private set; } + public bool can_raise { get; private set; } + public bool has_track_list { get; private set; } + public string identity { owned get; private set; } + public string entry { owned get; private set; } + public string[] supported_uri_schemas { owned get; private set; } + public string[] supported_mime_types { owned get; private set; } + + public void toggle_fullscreen() { + if (!can_set_fullscreen) + critical("can not set fullscreen on " + bus_name); + + proxy.fullscreen = !fullscreen; + } + + // player + public void next() { + try { proxy.next(); } catch (Error error) { critical(error.message); } + } + + public void previous() { + try { proxy.previous(); } catch (Error error) { critical(error.message); } + } + + public void pause() { + try { proxy.pause(); } catch (Error error) { critical(error.message); } + } + + public void play_pause() { + try { proxy.play_pause(); } catch (Error error) { critical(error.message); } + } + + public void stop() { + try { proxy.stop(); } catch (Error error) { critical(error.message); } + } + + public void play() { + try { proxy.play(); } catch (Error error) { critical(error.message); } + } + + public void open_uri(string uri) { + try { proxy.open_uri(uri); } catch (Error error) { critical(error.message); } + } + + public void loop() { + switch (loop_status) { + case Loop.NONE: + loop_status = Loop.TRACK; + break; + case Loop.TRACK: + loop_status = Loop.PLAYLIST; + break; + case Loop.PLAYLIST: + loop_status = Loop.NONE; + break; + default: + break; + } + } + + public void shuffle() { + shuffle_status = shuffle_status == Shuffle.ON + ? Shuffle.OFF + : Shuffle.ON; + } + + public signal void seeked (int64 position); + + public double _get_position() { + try { + var reply = proxy.call_sync( + "org.freedesktop.DBus.Properties.Get", + new Variant("(ss)", + "org.mpris.MediaPlayer2.Player", + "Position" + ), + DBusCallFlags.NONE, + -1, + null + ); + + var body = reply.get_child_value(0); + if (body.classify() == Variant.Class.STRING) { + return -1; // Position not supported + } + + return (double)body.get_variant().get_int64() / 1000000; + } catch (Error err) { + return -1; + } + } + + private void _set_position(double pos) { + try { + proxy.set_position((ObjectPath)trackid, (int64)(pos * 1000000)); + } catch (Error error) { + critical(error.message); + } + } + + private Loop _loop_status = Loop.UNSUPPORTED; + private double _rate; + private Shuffle _shuffle_status = Shuffle.UNSUPPORTED; + private double _volume = -1; + + public Loop loop_status { + get { return _loop_status; } + set { proxy.loop_status = value.to_string(); } + } + + public double rate { + get { return _rate; } + set { proxy.rate = value; } + } + + public Shuffle shuffle_status { + get { return _shuffle_status; } + set { proxy.shuffle = value == Shuffle.ON; } + } + + public double volume { + get { return _volume; } + set { proxy.volume = value; } + } + + public double position { + get { return _get_position(); } + set { _set_position(value); } + } + + public PlaybackStatus playback_status { get; private set; } + public double minimum_rate { get; private set; } + public double maximum_rate { get; private set; } + public bool can_go_next { get; private set; } + public bool can_go_previous { get; private set; } + public bool can_play { get; private set; } + public bool can_pause { get; private set; } + public bool can_seek { get; private set; } + public bool can_control { get; private set; } + + // metadata + [CCode (notify = false)] + public HashTable metadata { owned get; private set; } + + public string trackid { owned get; private set; } + public double length { get; private set; } + public string art_url { owned get; private set; } + + public string album { owned get; private set; } + public string album_artist { owned get; private set; } + public string artist { owned get; private set; } + public string lyrics { owned get; private set; } + public string title { owned get; private set; } + public string composer { owned get; private set; } + public string comments { owned get; private set; } + + // cached cover art + public string cover_art { owned get; private set; } + + public Player(string name) { + Object(bus_name: name.has_prefix("org.mpris.MediaPlayer2.") + ? name : "org.mpris.MediaPlayer2." + name); + } + + private void sync() { + // mpris + can_quit = proxy.can_quit; + fullscreen = proxy.fullscreen; + can_set_fullscreen = proxy.can_set_fullscreen; + can_raise = proxy.can_raise; + has_track_list = proxy.has_track_list; + identity = proxy.identity; + entry = proxy.desktop_entry; + supported_uri_schemas = proxy.supported_uri_schemas; + supported_mime_types = proxy.supported_mime_types; + + if (position >= 0) + notify_property("position"); + + // LoopStatus and Shuffle are optional props + var props = proxy.get_all("org.mpris.MediaPlayer2.Player"); + + // player + if (props != null && props.get("LoopStatus") != null) { + if (loop_status != Loop.from_string(proxy.loop_status)) { + _loop_status = Loop.from_string(proxy.loop_status); + notify_property("loop-status"); + } + } + + if (rate != proxy.rate) { + _rate = proxy.rate; + notify_property("rate"); + } + + if (props != null && props.get("Shuffle") != null) { + if (shuffle_status != Shuffle.from_bool(proxy.shuffle)) { + _shuffle_status = Shuffle.from_bool(proxy.shuffle); + notify_property("shuffle-status"); + } + } + + if (volume != proxy.volume) { + _volume = proxy.volume; + notify_property("volume"); + } + + playback_status = PlaybackStatus.from_string(proxy.playback_status); + minimum_rate = proxy.minimum_rate; + maximum_rate = proxy.maximum_rate; + can_go_next = proxy.can_go_next; + can_go_previous = proxy.can_go_previous; + can_play = proxy.can_play; + can_pause = proxy.can_pause; + can_seek = proxy.can_seek; + can_control = proxy.can_control; + + // metadata + metadata = proxy.metadata; + if (metadata != null) { + if (metadata.get("mpris:length") != null) + length = (double)metadata.get("mpris:length").get_uint64() / 1000000; + else + length = -1; + + trackid = get_str("mpris:trackid"); + art_url = get_str("mpris:artUrl"); + album = get_str("xesam:album"); + lyrics = get_str("xesam:asText"); + title = get_str("xesam:title"); + album_artist = join_strv("xesam:albumArtist", ", "); + artist = join_strv("xesam:artist", ", "); + comments = join_strv("xesam:comments", "\n"); + composer = join_strv("xesam:composer", ", "); + cache_cover.begin((_, res) => cache_cover.end(res)); + notify_property("metadata"); + } + } + + private async void cache_cover() { + if (art_url == null || art_url == "") + return; + + var file = File.new_for_uri(art_url); + if (file.get_path() != null) { + cover_art = file.get_path(); + return; + } + + var path = COVER_CACHE + "/" + Checksum.compute_for_string(ChecksumType.SHA1, art_url, -1); + if (FileUtils.test(path, FileTest.EXISTS)) { + cover_art = path; + return; + } + + try { + if (!FileUtils.test(COVER_CACHE, FileTest.IS_DIR)) + File.new_for_path(COVER_CACHE).make_directory_with_parents(null); + + file.copy_async.begin( + File.new_for_path(path), + FileCopyFlags.OVERWRITE, + Priority.DEFAULT, + null, + null, + (_, res) => { + try { + file.copy_async.end(res); + cover_art = path; + } catch (Error err) { + critical("Failed to cache cover art with url \"%s\": %s", art_url, err.message); + } + } + ); + } catch (Error err) { + critical(err.message); + } + } + + public Variant? get_meta(string key) { + return metadata.lookup(key); + } + + private string get_str(string key) { + if (metadata.get(key) == null) + return ""; + + var str = metadata.get(key).get_string(null); + return str == null ? "" : str; + } + + private string? join_strv(string key, string sep) { + if (metadata.get(key) == null) + return null; + + var arr = metadata.get(key).get_strv(); + if (arr.length == 0) + return null; + + var builder = new StringBuilder(); + for (var i = 0; i < arr.length; ++i) { + builder.append(arr[i]); + if (i + 1 < arr.length) + builder.append(sep); + } + + return builder.str; + } + + construct { + try { + try_proxy(); + sync(); + } catch (Error error) { + critical(error.message); + } + } + + public void try_proxy() throws Error { + if (proxy != null) + return; + + proxy = Bus.get_proxy_sync( + BusType.SESSION, + bus_name, + "/org/mpris/MediaPlayer2" + ); + + if (proxy.g_name_owner != null) + appeared(); + + proxy.notify["g-name-owner"].connect(() => { + if (proxy.g_name_owner != null) + appeared(); + else + 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() { + Source.remove(pollid); + } +} + +public enum PlaybackStatus { + PLAYING, + PAUSED, + STOPPED; + + public static PlaybackStatus from_string(string? str) { + switch (str) { + case "Playing": + return PLAYING; + case "Paused": + return PAUSED; + case "Stopped": + default: + return STOPPED; + } + } + + public string to_string() { + switch (this) { + case PLAYING: + return "Playing"; + case PAUSED: + return "Paused"; + case STOPPED: + default: + return "Stopped"; + } + } +} + +public enum Loop { + UNSUPPORTED, + NONE, + TRACK, + PLAYLIST; + + public static Loop from_string(string? str) { + switch (str) { + case "None": + return NONE; + case "Track": + return TRACK; + case "Playlist": + return PLAYLIST; + default: + return UNSUPPORTED; + } + } + + public string? to_string() { + switch (this) { + case NONE: + return "None"; + case TRACK: + return "Track"; + case PLAYLIST: + return "Playlist"; + default: + return "Unsupported"; + } + } +} + +public enum Shuffle { + UNSUPPORTED, + ON, + OFF; + + public static Shuffle from_bool(bool b) { + return b ? Shuffle.ON : Shuffle.OFF; + } + + public string? to_string() { + switch (this) { + case OFF: + return "Off"; + case ON: + return "On"; + default: + return "Unsupported"; + } + } +} +} diff --git a/lib/mpris/version b/lib/mpris/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/mpris/version @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/network/accesspoint.vala b/lib/network/accesspoint.vala new file mode 100644 index 0000000..3c51018 --- /dev/null +++ b/lib/network/accesspoint.vala @@ -0,0 +1,49 @@ +public class AstalNetwork.AccessPoint : Object { + private Wifi wifi; + private NM.AccessPoint ap; + + public uint bandwidth { get { return ap.bandwidth; } } + public string bssid { owned get { return ap.bssid; } } + public uint frequency { get { return ap.frequency; } } + public int last_seen { get { return ap.last_seen; } } + public uint max_bitrate { get { return ap.max_bitrate; } } + public uint8 strength { get { return ap.strength; } } + public string icon_name { get; private set; } + public NM.80211Mode mode { get { return ap.mode; } } + public NM.80211ApFlags flags { get { return ap.flags; } } + public NM.80211ApSecurityFlags rsn_flags { get { return ap.rsn_flags; } } + public NM.80211ApSecurityFlags wpa_flags { get { return ap.wpa_flags; } } + + public string? ssid { + owned get { + if (ap.ssid == null) + return null; + + return (string)NM.Utils.ssid_to_utf8(ap.ssid.get_data()); + } + } + + internal AccessPoint(Wifi wifi, NM.AccessPoint ap) { + this.wifi = wifi; + this.ap = ap; + ap.notify.connect((pspec) => { + if (get_class().find_property(pspec.name) != null) + notify_property(pspec.name); + if (pspec.name == "strength") + icon_name = _icon(); + }); + icon_name = _icon(); + } + + private string _icon() { + if (strength >= 80) return Wifi.ICON_EXCELLENT; + if (strength >= 60) return Wifi.ICON_GOOD; + if (strength >= 40) return Wifi.ICON_OK; + if (strength >= 20) return Wifi.ICON_WEAK; + return Wifi.ICON_NONE; + } + + // TODO: connect to ap + // public signal void auth(); + // public void try_connect(string? password) { } +} diff --git a/lib/network/config.vala.in b/lib/network/config.vala.in new file mode 100644 index 0000000..dbec0f3 --- /dev/null +++ b/lib/network/config.vala.in @@ -0,0 +1,6 @@ +namespace AstalNetwork { + public const int MAJOR_VERSION = @MAJOR_VERSION@; + public const int MINOR_VERSION = @MINOR_VERSION@; + public const int MICRO_VERSION = @MICRO_VERSION@; + public const string VERSION = "@VERSION@"; +} diff --git a/lib/network/meson.build b/lib/network/meson.build new file mode 100644 index 0000000..17ea358 --- /dev/null +++ b/lib/network/meson.build @@ -0,0 +1,80 @@ +project( + 'astal-network', + 'vala', + 'c', + version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), + meson_version: '>= 0.62.0', + default_options: [ + 'warning_level=2', + 'werror=false', + 'c_std=gnu11', + ], +) + +version_split = meson.project_version().split('.') +api_version = version_split[0] + '.' + version_split[1] +gir = 'AstalNetwork-' + api_version + '.gir' +typelib = 'AstalNetwork-' + api_version + '.typelib' + +config = configure_file( + input: 'config.vala.in', + output: 'config.vala', + configuration: { + 'VERSION': meson.project_version(), + 'MAJOR_VERSION': version_split[0], + 'MINOR_VERSION': version_split[1], + 'MICRO_VERSION': version_split[2], + }, +) + +deps = [ + dependency('glib-2.0'), + dependency('gobject-2.0'), + dependency('libnm'), +] + +sources = [ + config, + 'network.vala', + 'wifi.vala', + 'wired.vala', + 'wired.vala', + 'accesspoint.vala', +] + +lib = library( + meson.project_name(), + sources, + dependencies: deps, + vala_header: meson.project_name() + '.h', + vala_vapi: meson.project_name() + '-' + api_version + '.vapi', + vala_gir: gir, + version: meson.project_version(), + install: true, + install_dir: [true, true, true, true], +) + +import('pkgconfig').generate( + lib, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: deps, + install_dir: get_option('libdir') / 'pkgconfig', +) + +custom_target( + typelib, + command: [ + find_program('g-ir-compiler'), + '--output', '@OUTPUT@', + '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', + meson.current_build_dir() / gir, + ], + input: lib, + output: typelib, + depends: lib, + install: true, + install_dir: get_option('libdir') / 'girepository-1.0', +) diff --git a/lib/network/network.vala b/lib/network/network.vala new file mode 100644 index 0000000..7c8e466 --- /dev/null +++ b/lib/network/network.vala @@ -0,0 +1,206 @@ +namespace AstalNetwork { + public Network get_default() { + return Network.get_default(); + } +} + +public class AstalNetwork.Network : Object { + private static Network instance; + public static Network get_default() { + if (instance == null) + instance = new Network(); + + return instance; + } + + public NM.Client client { get; private set; } + + public Wifi? wifi { get; private set; } + public Wired? wired { get; private set; } + public Primary primary { get; private set; } + + public Connectivity connectivity { + get { return (Connectivity)client.connectivity; } + } + + public State state { + get { return (State)client.state; } + } + + construct { + try { + client = new NM.Client(); + var wifi_device = (NM.DeviceWifi)get_device(NM.DeviceType.WIFI); + if (wifi_device != null) + wifi = new Wifi(wifi_device); + + var ethernet = (NM.DeviceEthernet)get_device(NM.DeviceType.ETHERNET); + if (ethernet != null) + wired = new Wired(ethernet); + + sync(); + client.notify["primary-connection"].connect(sync); + client.notify["activating-connection"].connect(sync); + + client.notify["state"].connect(() => notify_property("state")); + client.notify["connectivity"].connect(() => notify_property("connectivity")); + } catch (Error err) { + critical(err.message); + } + } + + private NM.Device get_device(NM.DeviceType t) { + var valid = new GenericArray(); + foreach (var device in client.get_devices()) { + if (device.device_type == t) + valid.add(device); + } + + foreach (var device in valid) { + if (device.active_connection != null) + return device; + } + + return valid.get(0); + } + + private void sync() { + var ac = client.get_primary_connection(); + + if (ac == null) + ac = client.get_activating_connection(); + + if (ac != null) + primary = Primary.from_connection_type(ac.type); + else + primary = Primary.UNKNOWN; + } +} + +public enum AstalNetwork.Primary { + UNKNOWN, + WIRED, + WIFI; + + public string to_string() { + switch (this) { + case WIFI: return "wifi"; + case WIRED: return "wired"; + default: return "unknown"; + } + } + + public static Primary from_connection_type(string type) { + switch (type) { + case "802-11-wireless": return Primary.WIFI; + case "802-3-ethernet": return Primary.WIRED; + default: return Primary.UNKNOWN; + } + } +} + +// alias for NM.State +public enum AstalNetwork.State { + UNKNOWN, + ASLEEP, + DISCONNECTED, + DISCONNECTING, + CONNECTING, + CONNECTED_LOCAL, + CONNECTED_SITE, + CONNECTED_GLOBAL; + + public string to_string() { + switch (this) { + case ASLEEP: return "asleep"; + case DISCONNECTED: return "disconnected"; + case DISCONNECTING: return "disconnecting"; + case CONNECTING: return "connecting"; + case CONNECTED_LOCAL: return "connected_local"; + case CONNECTED_SITE: return "connected_site"; + case CONNECTED_GLOBAL: return "connected_global"; + default: return "unknown"; + } + } +} + + +// alias for NM.ConnectivityState +public enum AstalNetwork.Connectivity { + UNKNOWN, + NONE, + PORTAL, + LIMITED, + FULL; + + public string to_string() { + switch (this) { + case NONE: return "none"; + case PORTAL: return "portal"; + case LIMITED: return "limited"; + case FULL: return "full"; + default: return "unknown"; + } + } +} + +// alias for NM.DeviceState +public enum AstalNetwork.DeviceState { + UNKNOWN, + UNMANAGED, + UNAVAILABLE, + DISCONNECTED, + PREPARE, + CONFIG, + NEED_AUTH, + IP_CONFIG, + IP_CHECK, + SECONDARIES, + ACTIVATED, + DEACTIVATING, + FAILED; + + public string to_string() { + switch (this) { + case UNMANAGED: return "unmanaged"; + case UNAVAILABLE: return "unavailable"; + case DISCONNECTED: return "disconnected"; + case PREPARE: return "prepare"; + case CONFIG: return "config"; + case NEED_AUTH: return "need_auth"; + case IP_CONFIG: return "ip_config"; + case IP_CHECK: return "ip_check"; + case SECONDARIES: return "secondaries"; + case ACTIVATED: return "activated"; + case DEACTIVATING: return "deactivating"; + case FAILED: return "failed"; + default: return "unknown"; + } + + } +} + +public enum AstalNetwork.Internet { + CONNECTED, + CONNECTING, + DISCONNECTED; + + public static Internet from_device(NM.Device device) { + if (device == null || device.active_connection == null) + return DISCONNECTED; + + switch (device.active_connection.state) { + case NM.ActiveConnectionState.ACTIVATED: return CONNECTED; + case NM.ActiveConnectionState.ACTIVATING: return CONNECTING; + default: return DISCONNECTED; + } + } + + public string to_string() { + switch (this) { + case CONNECTED: return "connected"; + case CONNECTING: return "connecting"; + default: return "disconnected"; + } + } +} diff --git a/lib/network/version b/lib/network/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/network/version @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/network/vpn.vala b/lib/network/vpn.vala new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/network/vpn.vala @@ -0,0 +1 @@ + diff --git a/lib/network/wifi.vala b/lib/network/wifi.vala new file mode 100644 index 0000000..c9e9881 --- /dev/null +++ b/lib/network/wifi.vala @@ -0,0 +1,182 @@ +public class AstalNetwork.Wifi : Object { + internal const string ICON_EXCELLENT = "network-wireless-signal-excellent-symbolic"; + internal const string ICON_OK = "network-wireless-signal-ok-symbolic"; + internal const string ICON_GOOD = "network-wireless-signal-good-symbolic"; + internal const string ICON_WEAK = "network-wireless-signal-weak-symbolic"; + internal const string ICON_NONE = "network-wireless-signal-none-symbolic"; + internal const string ICON_ACQUIRING = "network-wireless-acquiring-symbolic"; + internal const string ICON_CONNECTED = "network-wireless-connected-symbolic"; + internal const string ICON_DISABLED = "network-wireless-disabled-symbolic"; + internal const string ICON_OFFLINE = "network-wireless-offline-symbolic"; + internal const string ICON_NO_ROUTE = "network-wireless-no-route-symbolic"; + internal const string ICON_HOTSPOT = "network-wireless-hotspot-symbolic"; + + private HashTable _access_points = + new HashTable(str_hash, str_equal); + + public NM.DeviceWifi device { get; construct set; } + + public NM.ActiveConnection? active_connection { get; private set; } + private ulong connection_handler = 0; + + public AccessPoint? active_access_point { get; private set; } + private ulong ap_handler = 0; + + public List access_points { + owned get { return _access_points.get_values(); } + } + + public bool enabled { + get { return device.client.wireless_enabled; } + set { device.client.wireless_enabled = value; } + } + + public Internet internet { get; private set; } + public uint bandwidth { get; private set; } + public string ssid { get; private set; } + public uint8 strength { get; private set; } + public uint frequency { get; private set; } + public DeviceState state { get; private set; } + public string icon_name { get; private set; } + public bool is_hotspot { get; private set; } + public bool scanning { get; private set; } + + internal Wifi(NM.DeviceWifi device) { + this.device = device; + + foreach (var ap in device.access_points) + _access_points.set(ap.bssid, new AccessPoint(this, ap)); + + device.access_point_added.connect((access_point) => { + var ap = (NM.AccessPoint)access_point; + _access_points.set(ap.bssid, new AccessPoint(this, ap)); + notify_property("access-points"); + }); + + device.access_point_removed.connect((access_point) => { + var ap = (NM.AccessPoint)access_point; + _access_points.remove(ap.bssid); + notify_property("access-points"); + }); + + on_active_connection(); + device.notify["active-connection"].connect(on_active_connection); + + on_active_access_point(); + device.notify["active-access-point"].connect(on_active_access_point); + + state = (DeviceState)device.state; + device.client.notify["wireless-enabled"].connect(() => notify_property("enabled")); + device.state_changed.connect((n, o, r) => { + state_changed(n, o, r); + state = (DeviceState)n; + }); + + device.notify.connect(() => { icon_name = _icon(); }); + device.client.notify.connect(() => { icon_name = _icon(); }); + icon_name = _icon(); + } + + public signal void state_changed( + DeviceState new_state, + DeviceState old_state, + NM.DeviceStateReason reaseon + ); + + public void scan() { + scanning = true; + var last_scan = device.last_scan; + device.request_scan_async.begin(null, (_, res) => { + try { + device.request_scan_async.end(res); + Timeout.add(1000, () => { + if (device.last_scan == last_scan) + return Source.CONTINUE; + + scanning = false; + return Source.REMOVE; + }, Priority.DEFAULT); + } catch (Error err) { + critical(err.message); + } + }); + } + + private void on_active_connection() { + if (connection_handler > 0 && active_connection != null) { + active_connection.disconnect(connection_handler); + connection_handler = 0; + active_connection = null; + } + + active_connection = device.active_connection; + is_hotspot = _hotspot(); + if (active_connection != null) { + connection_handler = active_connection.notify["state"].connect(() => { + internet = Internet.from_device(device); + }); + } + } + + private void on_active_access_point_notify() { + bandwidth = active_access_point.bandwidth; + frequency = active_access_point.frequency; + strength = active_access_point.strength; + ssid = active_access_point.ssid; + } + + private void on_active_access_point() { + if (ap_handler > 0 && active_access_point != null) { + active_access_point.disconnect(ap_handler); + ap_handler = 0; + active_access_point = null; + } + + var ap = device.active_access_point; + if (ap != null) { + active_access_point = _access_points.get(ap.bssid); + on_active_access_point_notify(); + ap_handler = active_access_point.notify.connect(on_active_access_point_notify); + } + } + + private string _icon() { + if (!enabled) return ICON_DISABLED; + + var full = device.client.connectivity == NM.ConnectivityState.FULL; + + if (internet == Internet.CONNECTED) { + if (is_hotspot) return ICON_HOTSPOT; + if (!full) return ICON_NO_ROUTE; + if (active_access_point == null) return ICON_CONNECTED; + + if (strength >= 80) return ICON_EXCELLENT; + if (strength >= 60) return ICON_GOOD; + if (strength >= 40) return ICON_OK; + if (strength >= 20) return ICON_WEAK; + + return ICON_NONE; + } + + if (internet == Internet.CONNECTING) { + return ICON_ACQUIRING; + } + + return ICON_OFFLINE; + } + + private bool _hotspot() { + if (device.active_connection == null) + return false; + + var conn = device.active_connection.connection; + if (conn == null) + return false; + + var ip4config = conn.get_setting_ip4_config(); + if (ip4config == null) + return false; + + return ip4config.method == NM.SettingIP4Config.METHOD_SHARED; + } +} diff --git a/lib/network/wired.vala b/lib/network/wired.vala new file mode 100644 index 0000000..68cf460 --- /dev/null +++ b/lib/network/wired.vala @@ -0,0 +1,73 @@ +public class AstalNetwork.Wired : Object { + private const string ICON_CONNECTED = "network-wired-symbolic"; + private const string ICON_DISCONNECTED = "network-wired-disconnected-symbolic"; + private const string ICON_ACQUIRING = "network-wired-acquiring-symbolic"; + private const string ICON_NO_ROUTE = "network-wired-no-route-symbolic"; + + public NM.DeviceEthernet device { get; construct set; } + + public NM.ActiveConnection connection; + private ulong connection_handler = 0; + + internal Wired(NM.DeviceEthernet device) { + this.device = device; + + speed = device.speed; + state = (DeviceState)device.state; + icon_name = _icon(); + + device.notify.connect((pspec) => { + if (pspec.name == "speed") { + speed = device.speed; + } + if (pspec.name == "state") { + state = (DeviceState)device.state; + } + if (pspec.name == "active-connection") { + on_active_connection(); + } + icon_name = _icon(); + }); + + device.client.notify.connect(() => { icon_name = _icon(); }); + + on_active_connection(); + icon_name = _icon(); + } + + private void on_active_connection() { + if (connection_handler > 0 && connection != null) { + connection.disconnect(connection_handler); + connection_handler = 0; + connection = null; + } + + connection = device.active_connection; + if (connection != null) { + connection_handler = connection.notify["state"].connect(() => { + internet = Internet.from_device(device); + }); + } + } + + public uint speed { get; private set; } + public Internet internet { get; private set; } + public DeviceState state { get; private set; } + public string icon_name { get; private set; } + + private string _icon() { + var full = device.client.connectivity == NM.ConnectivityState.FULL; + + if (internet == Internet.CONNECTING) { + return ICON_ACQUIRING; + } + + if (internet == Internet.CONNECTED) { + if (!full) return ICON_NO_ROUTE; + + return ICON_CONNECTED; + } + + return ICON_DISCONNECTED; + } +} diff --git a/lib/notifd/cli.vala b/lib/notifd/cli.vala new file mode 100644 index 0000000..afce774 --- /dev/null +++ b/lib/notifd/cli.vala @@ -0,0 +1,115 @@ +static bool help; +static bool version; +static bool daemonize; +static bool list; +static string invoke; +static int close_n; +static int get_n; +static bool toggle_dnd; + +const OptionEntry[] options = { + { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, + { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, + { "daemonize", 'd', OptionFlags.NONE, OptionArg.NONE, ref daemonize, null, null }, + { "list", 'l', OptionFlags.NONE, OptionArg.NONE, ref list, null, null }, + { "invoke", 'i', OptionFlags.NONE, OptionArg.STRING, ref invoke, null, null }, + { "close", 'c', OptionFlags.NONE, OptionArg.INT, ref close_n, null, null }, + { "get", 'g', OptionFlags.NONE, OptionArg.INT, ref get_n, null, null }, + { "toggle-dnd", 't', OptionFlags.NONE, OptionArg.NONE, ref toggle_dnd, null, null }, + { null }, +}; + +int main(string[] argv) { + try { + var opts = new OptionContext(); + opts.add_main_entries(options, null); + opts.set_help_enabled(false); + opts.set_ignore_unknown_options(false); + opts.parse(ref argv); + } catch (OptionError err) { + printerr (err.message); + return 1; + } + + if (help) { + print("Cli client for astal-notifd\n\n"); + print("Usage:\n"); + print(" %s [flags]\n\n", argv[0]); + print("Flags:\n"); + print(" -h, --help Print this help and exit\n"); + print(" -v, --version Print version number and exit\n"); + print(" -l, --list Print every notification and exit\n"); + print(" -d, --daemonize Watch for new notifications\n"); + print(" -i, --invoke Invoke a notification action\n"); + print(" -c, --close Close a notification by its id\n"); + print(" -g, --get Print a notification by its id\n"); + print(" -t, --toggle-dnd Toggle do not disturb\n"); + return 0; + } + + var notifd = new AstalNotifd.Notifd(); + + if (version) { + print(AstalNotifd.VERSION); + return 0; + } + + if (list) { + var state = Environment.get_user_state_dir() + "/astal/notifd/notifications.json"; + if (FileUtils.test(state, FileTest.EXISTS)) { + try { + uint8[] json; + File.new_for_path(state).load_contents(null, out json, null); + + var obj = Json.from_string((string)json); + + var list = obj.get_object().get_member("notifications"); + stdout.printf("%s\n", Json.to_string(list, true)); + return 0; + } catch (Error err) { + stderr.printf("failed to load cache: %s", err.message); + } + } + stdout.printf("[]\n"); + return 0; + } + + if (toggle_dnd) { + notifd.dont_disturb = !notifd.dont_disturb; + return 0; + } + + if (daemonize) { + notifd.notified.connect((id) => { + stdout.printf("%s\n", notifd.get_notification_json(id)); + stdout.flush(); + }); + new MainLoop().run(); + } + + if (invoke != null) { + if (!invoke.contains(":")) { + stderr.printf("invoke format needs to be :"); + return 1; + } + + var split = invoke.split(":"); + var n_id = int.parse(split[0]); + var a_id = split[1]; + + notifd.get_notification(n_id).invoke(a_id); + } + + if (close_n > 0) { + notifd.get_notification(close_n).dismiss(); + } + + if (get_n > 0) { + stdout.printf("%s", notifd.get_notification(get_n).to_json_string()); + } + + if (!daemonize && invoke == null && close_n == 0 && get_n == 0) + return 1; + + return 0; +} diff --git a/lib/notifd/config.vala.in b/lib/notifd/config.vala.in new file mode 100644 index 0000000..752c754 --- /dev/null +++ b/lib/notifd/config.vala.in @@ -0,0 +1,6 @@ +namespace AstalNotifd { + public const int MAJOR_VERSION = @MAJOR_VERSION@; + public const int MINOR_VERSION = @MINOR_VERSION@; + public const int MICRO_VERSION = @MICRO_VERSION@; + public const string VERSION = "@VERSION@"; +} diff --git a/lib/notifd/daemon.vala b/lib/notifd/daemon.vala new file mode 100644 index 0000000..b8fb598 --- /dev/null +++ b/lib/notifd/daemon.vala @@ -0,0 +1,255 @@ +[DBus (name = "org.freedesktop.Notifications")] +internal class AstalNotifd.Daemon : Object { + public static string name = "notifd"; + public static string vendor = "astal"; + public static string version = "0.1"; + + private string state_file; + private string state_directory; + private string cache_directory; + + private uint n_id = 1; + private HashTable notifs = + new HashTable((i) => i, (a, b) => a == b); + + private bool _ignore_timeout; + public bool ignore_timeout { + get { return _ignore_timeout; } + set { + _ignore_timeout = value; + write_state(); + } + } + + private bool _dont_disturb; + public bool dont_disturb { + get { return _dont_disturb; } + set { + _dont_disturb = value; + write_state(); + } + } + + public signal void notified(uint id, bool replaced); + public signal void resolved(uint id, ClosedReason reason); + public signal void action_invoked(uint id, string action); + public signal void prop_changed(string prop); + + // emitting an event from proxy doesn't seem to work + public void emit_resolved(uint id, ClosedReason reason) { resolved(id, reason); } + public void emit_action_invoked(uint id, string action) { action_invoked(id, action); } + + construct { + cache_directory = Environment.get_user_cache_dir() + "/astal/notifd"; + state_directory = Environment.get_user_state_dir() + "/astal/notifd"; + state_file = state_directory + "/notifications.json"; + + if (FileUtils.test(state_file, FileTest.EXISTS)) { + try { + uint8[] json; + File.new_for_path(state_file).load_contents(null, out json, null); + + var obj = Json.from_string((string)json); + + var list = obj.get_object().get_array_member("notifications"); + for (var i = 0; i < list.get_length(); ++i) { + add_notification(new Notification.from_json(list.get_object_element(i))); + } + n_id = list.get_length() + 1; + + _dont_disturb = obj.get_object().get_boolean_member("dont_disturb"); + _ignore_timeout = obj.get_object().get_boolean_member("ignore_timeout"); + } catch (Error err) { + warning("failed to load cache: %s", err.message); + } + } + + notify.connect((prop) => prop_changed(prop.name)); + + notified.connect(() => { + notify_property("notifications"); + }); + + resolved.connect((id, reason) => { + notifs.get(id).resolved(reason); + notifs.remove(id); + write_state(); + notify_property("notifications"); + notification_closed(id, reason); + }); + } + + public uint[] notification_ids() throws DBusError, IOError { + var keys = notifs.get_keys(); + uint[] id = new uint[keys.length()]; + for (var i = 0; i < keys.length(); ++i) + id[i] = keys.nth_data(i); + return id; + } + + [DBus (visible = false)] + public List notifications { + owned get { return notifs.get_values(); } + } + + [DBus (visible = false)] + public Notification get_notification(uint id) { + return notifs.get(id); + } + + public string get_notification_json(uint id) throws DBusError, IOError { + return notifs.get(id).to_json_string(); + } + + [DBus (name = "Notify")] + public uint Notify( + string app_name, + uint replaces_id, + string app_icon, + string summary, + string body, + string[] actions, + HashTable hints, + int expire_timeout + ) throws DBusError, IOError { + if (hints.get("image-data") != null) { + var file = cache_image(hints.get("image-data"), app_name); + if (file != null) { + hints.set("image-path", new Variant.string(file)); + hints.remove("image-data"); + } + } + + // deprecated hints + hints.remove("image_data"); + hints.remove("icon_data"); + + var id = replaces_id > 0 ? replaces_id : n_id++; + + var replaced = add_notification(new Notification( + app_name, id, app_icon, summary, body, actions, hints, expire_timeout + )); + + if (!ignore_timeout && expire_timeout > 0) { + Timeout.add(expire_timeout, () => { + resolved(id, ClosedReason.EXPIRED); + return Source.REMOVE; + }, Priority.DEFAULT); + } + + notified(id, replaced); + + write_state(); + return id; + } + + private bool add_notification(Notification n) { + n.dismissed.connect(() => resolved(n.id, ClosedReason.DISMISSED_BY_USER)); + n.invoked.connect((action) => action_invoked(n.id, action)); + var replaced = notifs.contains(n.id); + notifs.set(n.id, n); + return replaced; + } + + private void write_state() { + var list = new Json.Builder().begin_array(); + foreach (var n in notifications) { + list.add_value(n.to_json()); + } + list.end_array(); + + var obj = new Json.Builder() + .begin_object() + .set_member_name("notifications").add_value(list.get_root()) + .set_member_name("ignore_timeout").add_boolean_value(ignore_timeout) + .set_member_name("dont_disturb").add_boolean_value(dont_disturb) + .end_object(); + + try { + if (!FileUtils.test(state_directory, FileTest.EXISTS)) + File.new_for_path(state_directory).make_directory_with_parents(null); + + FileUtils.set_contents_full(state_file, Json.to_string(obj.get_root(), false)); + } catch (Error err) { + warning("failed to cache notifications: %s", err.message); + } + } + + public signal void notification_closed(uint id, uint reason); + public signal void activation_token(uint id, string token); + + public void close_notification(uint id) throws DBusError, IOError { + resolved(id, ClosedReason.CLOSED); + } + + public void get_server_information( + out string name, + out string vendor, + out string version, + out string spec_version + ) throws DBusError, IOError { + name = Daemon.name; + vendor = Daemon.vendor; + version = Daemon.version; + spec_version = "1.2"; + } + + public string[] get_capabilities() throws DBusError, IOError { + return {"action-icons", "actions", "body", "icon-static", "persistence", "sound"}; + } + + private string? cache_image(Variant image, string app_name) { + int w = image.get_child_value(0).get_int32(); + int h = image.get_child_value(1).get_int32(); + int rs = image.get_child_value(2).get_int32(); + bool alpha = image.get_child_value(3).get_boolean(); + int bps = image.get_child_value(4).get_int32(); + Bytes data = image.get_child_value(6).get_data_as_bytes(); + + if (bps != 8) { + warning("Can not cache image from %s. %s", app_name, + "Currently only RGB images with 8 bits per sample are supported."); + return null; + } + + var pixbuf = new Gdk.Pixbuf.from_bytes( + data, Gdk.Colorspace.RGB, alpha, bps, w, h, rs); + + if (pixbuf == null) + return null; + + var file_name = cache_directory + "/" + data.hash().to_string("%u.png"); + + try { + if (!FileUtils.test(cache_directory, FileTest.EXISTS)) + File.new_for_path(cache_directory).make_directory_with_parents(null); + + var output_stream = File.new_for_path(file_name) + .replace(null, false, FileCreateFlags.NONE, null); + + pixbuf.save_to_streamv(output_stream, "png", null, null, null); + output_stream.close(null); + } catch (Error err) { + warning("could not cache image %s", err.message); + return null; + } + + return file_name; + } + + internal Daemon register(DBusConnection conn) { + try { + conn.register_object("/org/freedesktop/Notifications", this); + } catch (Error err) { + critical(err.message); + } + return this; + } +} + +public enum AstalNotifd.ClosedReason { + EXPIRED = 1, + DISMISSED_BY_USER = 2, + CLOSED = 3, + UNDEFINED = 4, +} diff --git a/lib/notifd/meson.build b/lib/notifd/meson.build new file mode 100644 index 0000000..6bea022 --- /dev/null +++ b/lib/notifd/meson.build @@ -0,0 +1,97 @@ +project( + 'astal-notifd', + 'vala', + 'c', + version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), + meson_version: '>= 0.62.0', + default_options: [ + 'warning_level=2', + 'werror=false', + 'c_std=gnu11', + ], +) + +assert( + get_option('lib') or get_option('cli'), + 'Either lib or cli option must be set to true.', +) + +version_split = meson.project_version().split('.') +api_version = version_split[0] + '.' + version_split[1] +gir = 'AstalNotifd-' + api_version + '.gir' +typelib = 'AstalNotifd-' + api_version + '.typelib' + +config = configure_file( + input: 'config.vala.in', + output: 'config.vala', + configuration: { + 'VERSION': meson.project_version(), + 'MAJOR_VERSION': version_split[0], + 'MINOR_VERSION': version_split[1], + 'MICRO_VERSION': version_split[2], + }, +) + +deps = [ + dependency('glib-2.0'), + dependency('gobject-2.0'), + dependency('gio-2.0'), + dependency('json-glib-1.0'), + dependency('gdk-pixbuf-2.0'), +] + +sources = [ + config, + 'daemon.vala', + 'notifd.vala', + 'notification.vala', + 'proxy.vala', +] + +if get_option('lib') + lib = library( + meson.project_name(), + sources, + dependencies: deps, + vala_header: meson.project_name() + '.h', + vala_vapi: meson.project_name() + '-' + api_version + '.vapi', + vala_gir: gir, + version: meson.project_version(), + install: true, + install_dir: [true, true, true, true], + ) + + import('pkgconfig').generate( + lib, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: deps, + install_dir: get_option('libdir') / 'pkgconfig', + ) + + custom_target( + typelib, + command: [ + find_program('g-ir-compiler'), + '--output', '@OUTPUT@', + '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', + meson.current_build_dir() / gir, + ], + input: lib, + output: typelib, + depends: lib, + install: true, + install_dir: get_option('libdir') / 'girepository-1.0', + ) +endif + +if get_option('cli') + executable( + meson.project_name(), + ['cli.vala', sources], + dependencies: deps, + install: true, + ) +endif diff --git a/lib/notifd/meson_options.txt b/lib/notifd/meson_options.txt new file mode 100644 index 0000000..f110242 --- /dev/null +++ b/lib/notifd/meson_options.txt @@ -0,0 +1,11 @@ +option( + 'lib', + type: 'boolean', + value: true, +) + +option( + 'cli', + type: 'boolean', + value: true, +) diff --git a/lib/notifd/notifd.vala b/lib/notifd/notifd.vala new file mode 100644 index 0000000..c962862 --- /dev/null +++ b/lib/notifd/notifd.vala @@ -0,0 +1,140 @@ +namespace AstalNotifd { + public Notifd get_default() { + return Notifd.get_default(); + } +} + +public class AstalNotifd.Notifd : Object { + private static Notifd _instance; + public static Notifd get_default() { + if (_instance == null) + _instance = new Notifd(); + + return _instance; + } + + private Daemon daemon; + private DaemonProxy proxy; + + public signal void active(ActiveType type); + + public bool ignore_timeout { + get { + return proxy != null ? proxy.ignore_timeout : daemon.ignore_timeout; + } + set { + if (proxy != null) + proxy.ignore_timeout = value; + else + daemon.ignore_timeout = value; + } + } + + public bool dont_disturb { + get { + return proxy != null ? proxy.dont_disturb : daemon.dont_disturb; + } + set { + if (proxy != null) + proxy.dont_disturb = value; + else + daemon.dont_disturb = value; + } + } + + public List notifications { + owned get { return proxy != null ? proxy.notifications : daemon.notifications; } + } + + public uint[] notification_ids() throws Error { + return proxy != null ? proxy.notification_ids() : daemon.notification_ids(); + } + + public Notification get_notification(uint id) { + return proxy != null ? proxy.get_notification(id) : daemon.get_notification(id); + } + + public string get_notification_json(uint id) { + return get_notification(id).to_json_string(); + } + + public signal void notified(uint id, bool replaced); + public signal void resolved(uint id, ClosedReason reason); + + construct { + // hack to make it synchronous + MainLoop? loop = null; + + if (!MainContext.default().is_owner()) { + loop = new MainLoop(); + } + + bool done = false; + + Bus.own_name( + BusType.SESSION, + "org.freedesktop.Notifications", + BusNameOwnerFlags.NONE, + acquire_daemon, + on_daemon_acquired, + make_proxy + ); + + active.connect(() => { + done = true; + if (loop != null && loop.is_running()) { + loop.quit(); + } + }); + + if (loop != null) { + loop.run(); + } else { + while (!done) { + MainContext.default().iteration(false); + } + } + } + + private void acquire_daemon(DBusConnection conn) { + daemon = new Daemon().register(conn); + } + + private void on_daemon_acquired() { + if (proxy != null) { + proxy.stop(); + proxy = null; + } + daemon.notified.connect((id, replaced) => notified(id, replaced)); + daemon.resolved.connect((id, reason) => resolved(id, reason)); + daemon.notify.connect((prop) => { + if (get_class().find_property(prop.name) != null) { + notify_property(prop.name); + } + }); + active(ActiveType.DAEMON); + } + + private void make_proxy() { + proxy = new DaemonProxy(); + + if (proxy.start()) { + active(ActiveType.PROXY); + } else { + return; + } + + proxy.notified.connect((id, replaced) => notified(id, replaced)); + proxy.resolved.connect((id, reason) => resolved(id, reason)); + proxy.notify.connect((prop) => { + if (get_class().find_property(prop.name) != null) { + notify_property(prop.name); + } + }); + } +} + +public enum AstalNotifd.ActiveType { + DAEMON, + PROXY, +} diff --git a/lib/notifd/notification.vala b/lib/notifd/notification.vala new file mode 100644 index 0000000..0b4af06 --- /dev/null +++ b/lib/notifd/notification.vala @@ -0,0 +1,160 @@ +public enum AstalNotifd.Urgency { + LOW = 0, + NORMAL = 1, + CRITICAL = 2, +} + +public struct AstalNotifd.Action { + public string id; + public string label; +} + +public class AstalNotifd.Notification : Object { + private List _actions; + private HashTable hints; + + public int64 time { construct set; get; } + public string app_name { construct set; get; } + public string app_icon { construct set; get; } + public string summary { construct set; get; } + public string body { construct set; get; } + public uint id { construct set; get; } + public int expire_timeout { construct set; get; } + public List actions { get { return _actions; } } + + public string image { get { return get_str_hint("image-path"); } } + public bool action_icons { get { return get_bool_hint("action-icons"); } } + public string category { get { return get_str_hint("category"); } } + public string desktop_entry { get { return get_str_hint("desktop-entry"); } } + public bool resident { get { return get_bool_hint("resident"); } } + public string sound_file { get { return get_str_hint("sound-file"); } } + public string sound_name { get { return get_str_hint("sound-name"); } } + public bool suppress_sound { get { return get_bool_hint("suppress-sound"); } } + public bool transient { get { return get_bool_hint("transient"); } } + public int x { get { return get_int_hint("x"); } } + public int y { get { return get_int_hint("y"); } } + public Urgency urgency { get { return get_int_hint("urgency"); } } + + internal Notification( + string app_name, + uint id, + string app_icon, + string summary, + string body, + string[] actions, + HashTable hints, + int expire_timeout + ) { + Object( + app_name: app_name, + id: id, + app_icon: app_icon, + summary: summary, + body: body, + expire_timeout: expire_timeout, + time: new DateTime.now_local().to_unix() + ); + + this.hints = hints; + _actions = new List(); + for (var i = 0; i < actions.length; i += 2) { + _actions.append(Action() { + id = actions[i], + label = actions[i + 1] + }); + } + } + + public Variant? get_hint(string hint) { + return hints.contains(hint) ? hints.get(hint) : null; + } + + public unowned string get_str_hint(string hint) { + return hints.contains(hint) ? hints.get(hint).get_string() : null; + } + + public bool get_bool_hint(string hint) { + return hints.contains(hint) ? hints.get(hint).get_boolean() : false; + } + + public int get_int_hint(string hint) { + return hints.contains(hint) ? hints.get(hint).get_int32() : 0; + } + + public signal void resolved(ClosedReason reason); + public signal void dismissed(); + public signal void invoked(string action); + + public void dismiss() { dismissed(); } + public void invoke(string action) { invoked(action); } + + internal Notification.from_json(Json.Object root) throws GLib.Error { + foreach (var key in root.get_members()) { + var node = root.get_member(key); + switch (key) { + case "id": id = (uint)node.get_int(); break; + case "time": time = node.get_int(); break; + case "expire_timeout": expire_timeout = (int)node.get_int(); break; + case "app_name": app_name = node.get_string(); break; + case "app_icon": app_icon = node.get_string(); break; + case "summary": summary = node.get_string(); break; + case "body": body = node.get_string(); break; + case "hints": + hints = new HashTable(str_hash, str_equal); + var obj = node.get_object(); + foreach (var hint in obj.get_members()) { + hints.set(hint, Json.gvariant_deserialize(obj.get_member(hint), null)); + } + break; + case "actions": + _actions = new List(); + for (var i = 0; i < node.get_array().get_length(); ++i) { + var o = node.get_array().get_object_element(i); + _actions.append(Action() { + id = o.get_member("id").get_string(), + label = o.get_member("label").get_string() + }); + } + break; + default: break; + } + } + } + + internal static Notification from_json_string(string json) throws GLib.Error { + var parser = new Json.Parser(); + parser.load_from_data(json); + return new Notification.from_json(parser.get_root().get_object()); + } + + public string to_json_string() { + var generator = new Json.Generator(); + generator.set_root(to_json()); + return generator.to_data(null); + } + + internal Json.Node to_json() { + var acts = new Json.Builder().begin_array(); + foreach (var action in actions) { + acts.begin_object() + .set_member_name("id").add_string_value(action.id) + .set_member_name("label").add_string_value(action.label) + .end_object(); + } + acts.end_array(); + + return new Json.Builder() + .begin_object() + .set_member_name("id").add_int_value(id) + .set_member_name("time").add_int_value(time) + .set_member_name("expire_timeout").add_int_value(expire_timeout) + .set_member_name("app_name").add_string_value(app_name) + .set_member_name("app_icon").add_string_value(app_icon) + .set_member_name("summary").add_string_value(summary) + .set_member_name("body").add_string_value(body) + .set_member_name("actions").add_value(acts.get_root()) + .set_member_name("hints").add_value(Json.gvariant_serialize(hints)) + .end_object() + .get_root(); + } +} diff --git a/lib/notifd/proxy.vala b/lib/notifd/proxy.vala new file mode 100644 index 0000000..bedb8b9 --- /dev/null +++ b/lib/notifd/proxy.vala @@ -0,0 +1,129 @@ +[DBus (name = "org.freedesktop.Notifications")] +internal interface AstalNotifd.IDaemon : DBusProxy { + public abstract bool ignore_timeout { get; set; } + public abstract bool dont_disturb { get; set; } + + public abstract uint[] notification_ids() throws DBusError, IOError; + public abstract string get_notification_json(uint id) throws DBusError, IOError; + + public signal void notified(uint id, bool replaced); + public signal void resolved(uint id, ClosedReason reason); + public signal void prop_changed(string prop); + + public abstract void emit_resolved(uint id, ClosedReason reason); + public abstract void emit_action_invoked(uint id, string action); +} + +internal class AstalNotifd.DaemonProxy : Object { + private HashTable notifs = + new HashTable((i) => i, (a, b) => a == b); + + public List notifications { + owned get { return notifs.get_values(); } + } + + public bool ignore_timeout { + get { return proxy.ignore_timeout; } + set { proxy.ignore_timeout = value; } + } + + public bool dont_disturb { + get { return proxy.dont_disturb; } + 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); + } + + public signal void notified(uint id, bool replaced); + public signal void resolved(uint id, ClosedReason reason); + + private IDaemon proxy; + private List ids = new List(); + + public void stop() { + if (ids.length() > 0) { + foreach (var id in ids) + SignalHandler.disconnect(proxy, id); + } + } + + public bool start() { + try { + var bus = Bus.get_sync(BusType.SESSION, null); + var variant = bus.call_sync( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "GetServerInformation", + null, + null, + DBusCallFlags.NONE, + -1, + null); + + var name = variant.get_child_value(0).get_string(); + var vendor = variant.get_child_value(1).get_string(); + var version = variant.get_child_value(2).get_string(); + + var running = name == Daemon.name + && vendor == Daemon.vendor + && version == Daemon.version; + + if (running) { + setup_proxy(); + return true; + } else { + critical("cannot get proxy: %s is already running", name); + } + } catch (Error err) { + critical("cannot get proxy: %s", err.message); + } + return false; + } + + private void setup_proxy() throws Error { + proxy = Bus.get_proxy_sync( + BusType.SESSION, + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications" + ); + + foreach (var id in proxy.notification_ids()) + add_notification(id); + + ids.append(proxy.prop_changed.connect((prop) => { + if (prop == "ignore-timeout" || prop == "dont-disturb") + notify_property(prop); + })); + + ids.append(proxy.notified.connect((id, replaced) => { + add_notification(id); + notified(id, replaced); + notify_property("notifications"); + })); + + ids.append(proxy.resolved.connect((id, reason) => { + notifs.remove(id); + resolved(id, reason); + notify_property("notifications"); + })); + } + + private void add_notification(uint id) { + try { + var n = Notification.from_json_string(proxy.get_notification_json(id)); + proxy.resolved.connect((id, reason) => n.resolved(reason)); + n.dismissed.connect(() => proxy.emit_resolved(id, ClosedReason.DISMISSED_BY_USER)); + n.invoked.connect((action) => proxy.emit_action_invoked(id, action)); + notifs.set(id, n); + } catch (Error err) { + critical(err.message); + } + } +} diff --git a/lib/notifd/signals.md b/lib/notifd/signals.md new file mode 100644 index 0000000..cdc6688 --- /dev/null +++ b/lib/notifd/signals.md @@ -0,0 +1,35 @@ +# Signals + +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()` +* invoked(action) - by user with `.invoke()` + +## Deamon + +non-spec, used by user + +* notified(id, replaced) - by outside through dbus with `.Notify()` +* resolved(id, reason) - by `Notification.dismiss()` or outside with `.CloseNotification` + +spec, not used by user + +* notification_closed(id, reason) - sideeffect of `resolved` +* action_invoked(id, action) - by `Notification.invoke()` + +## Proxy + +mirrors Daemon + +* notified(id, replaced) +* resolved(id, reason) + +creates `Notification` objects through daemon's json strings +and hooks them up to call daemon's signals and vice versa + +## Notifd + +acts as a bridge between Proxy/Daemon, everything else is internal only diff --git a/lib/notifd/version b/lib/notifd/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/notifd/version @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/powerprofiles/cli.vala b/lib/powerprofiles/cli.vala new file mode 100644 index 0000000..7be01d2 --- /dev/null +++ b/lib/powerprofiles/cli.vala @@ -0,0 +1,80 @@ +static bool help; +static bool version; +static bool daemonize; +static bool list; +static string set; + +const OptionEntry[] options = { + { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, + { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, + { "daemonize", 'd', OptionFlags.NONE, OptionArg.NONE, ref daemonize, null, null }, + { "list", 'l', OptionFlags.NONE, OptionArg.NONE, ref list, null, null }, + { "set", 's', OptionFlags.NONE, OptionArg.STRING, ref set, null, null }, + { null }, +}; + +int main(string[] argv) { + try { + var opts = new OptionContext(); + opts.add_main_entries(options, null); + opts.set_help_enabled(false); + opts.set_ignore_unknown_options(false); + opts.parse(ref argv); + } catch (OptionError err) { + printerr (err.message); + return 1; + } + + if (help) { + print("Usage:\n"); + print(" %s [flags]\n\n", argv[0]); + print("Flags:\n"); + print(" -h, --help Print this help and exit\n"); + print(" -v, --version Print version number and exit\n"); + print(" -d, --daemonize Monitor for changes\n"); + print(" -l, --list List available profiles\n"); + return 0; + } + + if (version) { + print(AstalPowerProfiles.VERSION); + return 0; + } + + var profiles = AstalPowerProfiles.get_default(); + if (set != null) { + profiles.active_profile = set; + } + + else if (list) { + foreach (var p in profiles.profiles) { + print("%s\n", p.profile); + } + return 0; + } + + if (daemonize) { + var loop = new MainLoop(); + + stdout.printf("%s\n", profiles.to_json_string()); + stdout.flush(); + + profiles.notify.connect(() => { + stdout.printf("%s\n", profiles.to_json_string()); + stdout.flush(); + }); + + profiles.profile_released.connect(() => { + stdout.printf("%s\n", profiles.to_json_string()); + stdout.flush(); + }); + + loop.run(); + } + + if (set == null && !daemonize) { + stdout.printf("%s\n", profiles.to_json_string()); + } + + return 0; +} diff --git a/lib/powerprofiles/config.vala.in b/lib/powerprofiles/config.vala.in new file mode 100644 index 0000000..79034f1 --- /dev/null +++ b/lib/powerprofiles/config.vala.in @@ -0,0 +1,6 @@ +namespace AstalPowerProfiles { + public const int MAJOR_VERSION = @MAJOR_VERSION@; + public const int MINOR_VERSION = @MINOR_VERSION@; + public const int MICRO_VERSION = @MICRO_VERSION@; + public const string VERSION = "@VERSION@"; +} diff --git a/lib/powerprofiles/meson.build b/lib/powerprofiles/meson.build new file mode 100644 index 0000000..d0fe78f --- /dev/null +++ b/lib/powerprofiles/meson.build @@ -0,0 +1,93 @@ +project( + 'astal-power-profiles', + 'vala', + 'c', + version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), + meson_version: '>= 0.62.0', + default_options: [ + 'warning_level=2', + 'werror=false', + 'c_std=gnu11', + ], +) + +assert( + get_option('lib') or get_option('cli'), + 'Either lib or cli option must be set to true.', +) + +version_split = meson.project_version().split('.') +api_version = version_split[0] + '.' + version_split[1] +gir = 'AstalPowerProfiles-' + api_version + '.gir' +typelib = 'AstalPowerProfiles-' + api_version + '.typelib' + +config = configure_file( + input: 'config.vala.in', + output: 'config.vala', + configuration: { + 'VERSION': meson.project_version(), + 'MAJOR_VERSION': version_split[0], + 'MINOR_VERSION': version_split[1], + 'MICRO_VERSION': version_split[2], + }, +) + +deps = [ + dependency('glib-2.0'), + dependency('gobject-2.0'), + dependency('gio-2.0'), + dependency('json-glib-1.0'), +] + +sources = [ + config, + 'power-profiles.vala', +] + +if get_option('lib') + lib = library( + meson.project_name(), + sources, + dependencies: deps, + vala_header: meson.project_name() + '.h', + vala_vapi: meson.project_name() + '-' + api_version + '.vapi', + vala_gir: gir, + version: meson.project_version(), + install: true, + install_dir: [true, true, true, true], + ) + + import('pkgconfig').generate( + lib, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: deps, + install_dir: get_option('libdir') / 'pkgconfig', + ) + + custom_target( + typelib, + command: [ + find_program('g-ir-compiler'), + '--output', '@OUTPUT@', + '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', + meson.current_build_dir() / gir, + ], + input: lib, + output: typelib, + depends: lib, + install: true, + install_dir: get_option('libdir') / 'girepository-1.0', + ) +endif + +if get_option('cli') + executable( + meson.project_name(), + ['cli.vala', sources], + dependencies: deps, + install: true, + ) +endif diff --git a/lib/powerprofiles/meson_options.txt b/lib/powerprofiles/meson_options.txt new file mode 100644 index 0000000..f110242 --- /dev/null +++ b/lib/powerprofiles/meson_options.txt @@ -0,0 +1,11 @@ +option( + 'lib', + type: 'boolean', + value: true, +) + +option( + 'cli', + type: 'boolean', + value: true, +) diff --git a/lib/powerprofiles/power-profiles.vala b/lib/powerprofiles/power-profiles.vala new file mode 100644 index 0000000..ab98505 --- /dev/null +++ b/lib/powerprofiles/power-profiles.vala @@ -0,0 +1,205 @@ +namespace AstalPowerProfiles { +[DBus (name = "org.freedesktop.UPower.PowerProfiles")] +private interface IPowerProfiles : DBusProxy { + public abstract string[] actions { owned get; } + public abstract string active_profile { owned get; set; } + public abstract HashTable[] active_profile_holds { owned get; } + public abstract string performance_degraded { owned get; } + public abstract string performance_inhibited { owned get; } + public abstract HashTable[] profiles { owned get; } + public abstract string version { owned get; } + + public signal void profile_released (uint cookie); + + public abstract uint hold_profile(string profile, string reason, string application_id) throws Error; + public abstract void release_profile(uint cookie) throws Error; +} + +public PowerProfiles get_default() { + return PowerProfiles.get_default(); +} + +public class PowerProfiles : Object { + private static PowerProfiles instance; + public static PowerProfiles get_default() { + if (instance == null) + instance = new PowerProfiles(); + + return instance; + } + + private IPowerProfiles proxy; + + construct { + try { + proxy = Bus.get_proxy_sync( + GLib.BusType.SYSTEM, + "org.freedesktop.UPower.PowerProfiles", + "/org/freedesktop/UPower/PowerProfiles" + ); + + proxy.profile_released.connect((cookie) => profile_released(cookie)); + proxy.g_properties_changed.connect((props) => { + var map = (HashTable)props; + foreach (var key in map.get_keys()) { + notify_property(kebab_case(key)); + if (key == "ActiveProfile") + notify_property("icon-name"); + } + }); + } catch (Error error){ + critical(error.message); + } + } + + public string active_profile { + owned get { return proxy.active_profile; } + set { proxy.active_profile = value; } + } + + public string icon_name { + owned get { return @"power-profile-$active_profile-symbolic"; } + } + + public string[] actions { + owned get { return proxy.actions.copy(); } + } + + public Hold[] active_profile_holds { + owned get { + Hold[] holds = new Hold[proxy.active_profile_holds.length]; + for (var i = 0; i < proxy.active_profile_holds.length; ++i) { + var hold = proxy.active_profile_holds[i]; + holds[i] = Hold() { + application_id = hold.get("ApplicationId").get_string(), + profile = hold.get("Profile").get_string(), + reason = hold.get("Reason").get_string() + }; + } + return holds; + } + } + + public string performance_degraded { + owned get { return proxy.performance_degraded; } + } + + public string performance_inhibited { + owned get { return proxy.performance_degraded; } + } + + public Profile[] profiles { + owned get { + Profile[] profs = new Profile[proxy.profiles.length]; + for (var i = 0; i < proxy.profiles.length; ++i) { + var prof = proxy.profiles[i]; + profs[i] = Profile() { + profile = prof.get("Profile").get_string(), + cpu_driver = prof.get("CpuDriver").get_string(), + platform_driver = prof.get("PlatformDriver").get_string(), + driver = prof.get("Driver").get_string() + }; + } + return profs; + } + } + + public string version { + owned get { return proxy.version; } + } + + public signal void profile_released (uint cookie); + + public int hold_profile(string profile, string reason, string application_id) { + try { + return (int)proxy.hold_profile(profile, reason, application_id); + } catch (Error error) { + critical(error.message); + return -1; + } + } + + public void release_profile(uint cookie) { + try { + proxy.release_profile(cookie); + } catch (Error error) { + critical(error.message); + } + } + + public string to_json_string() { + var acts = new Json.Builder().begin_array(); + foreach (var action in actions) { + acts.add_string_value(action); + } + + var active_holds = new Json.Builder().begin_array(); + foreach (var action in active_profile_holds) { + active_holds.add_value(new Json.Builder() + .begin_object() + .set_member_name("application_id").add_string_value(action.application_id) + .set_member_name("profile").add_string_value(action.profile) + .set_member_name("reason").add_string_value(action.reason) + .end_object() + .get_root()); + } + + var profs = new Json.Builder().begin_array(); + foreach (var prof in profiles) { + profs.add_value(new Json.Builder() + .begin_object() + .set_member_name("profie").add_string_value(prof.profile) + .set_member_name("driver").add_string_value(prof.driver) + .set_member_name("cpu_driver").add_string_value(prof.cpu_driver) + .set_member_name("platform_driver").add_string_value(prof.platform_driver) + .end_object() + .get_root()); + } + + return Json.to_string(new Json.Builder() + .begin_object() + .set_member_name("active_profile").add_string_value(active_profile) + .set_member_name("icon_name").add_string_value(icon_name) + .set_member_name("performance_degraded").add_string_value(performance_degraded) + .set_member_name("performance_inhibited").add_string_value(performance_inhibited) + .set_member_name("actions").add_value(acts.end_array().get_root()) + .set_member_name("active_profile_holds").add_value(active_holds.end_array().get_root()) + .set_member_name("profiles").add_value(profs.end_array().get_root()) + .end_object() + .get_root(), false); + } +} + +public struct Profile { + public string profile; + public string cpu_driver; + public string platform_driver; + public string driver; +} + +public struct Hold { + public string application_id; + public string profile; + public string reason; +} + +private string kebab_case(string pascal_case) { + StringBuilder kebab_case = new StringBuilder(); + + for (int i = 0; i < pascal_case.length; i++) { + char c = pascal_case[i]; + + if (c >= 'A' && c <= 'Z') { + if (i != 0) { + kebab_case.append_c('-'); + } + + kebab_case.append_c((char)(c + 32)); + } else { + kebab_case.append_c(c); + } + } + + return kebab_case.str; +} +} diff --git a/lib/powerprofiles/version b/lib/powerprofiles/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/powerprofiles/version @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/river/include/astal-river.h b/lib/river/include/astal-river.h new file mode 100644 index 0000000..6bedd94 --- /dev/null +++ b/lib/river/include/astal-river.h @@ -0,0 +1,59 @@ +#ifndef ASTAL_RIVER_H +#define ASTAL_RIVER_H + +#include + +G_BEGIN_DECLS + +#define ASTAL_RIVER_TYPE_OUTPUT (astal_river_output_get_type()) + +G_DECLARE_FINAL_TYPE(AstalRiverOutput, astal_river_output, ASTAL_RIVER, OUTPUT, GObject) + +guint astal_river_output_get_id(AstalRiverOutput *self); + +gchar *astal_river_output_get_name(AstalRiverOutput *self); + +gchar *astal_river_output_get_layout_name(AstalRiverOutput *self); + +gchar *astal_river_output_get_focused_view(AstalRiverOutput *self); + +guint astal_river_output_get_focused_tags(AstalRiverOutput *self); + +guint astal_river_output_get_urgent_tags(AstalRiverOutput *self); + +guint astal_river_output_get_occupied_tags(AstalRiverOutput *self); + +#define ASTAL_RIVER_TYPE_RIVER (astal_river_river_get_type()) + +G_DECLARE_FINAL_TYPE(AstalRiverRiver, astal_river_river, ASTAL_RIVER, RIVER, GObject) + +AstalRiverRiver *astal_river_river_new(); + +AstalRiverRiver *astal_river_river_get_default(); +AstalRiverRiver *astal_river_get_default(); + +GList *astal_river_river_get_outputs(AstalRiverRiver *self); + +AstalRiverOutput *astal_river_river_get_output(AstalRiverRiver *self, gchar *name); + +gchar *astal_river_river_get_focused_view(AstalRiverRiver *self); + +gchar *astal_river_river_get_focused_output(AstalRiverRiver *self); + +gchar *astal_river_river_get_mode(AstalRiverRiver *self); + +/** + * AstalRiverCommandCallback: + * @success: a #gboolean indicating whether the command was executed successfully + * @msg: a string containing the result of the command + * + * A callback function that is called after a river command is run. + */ +typedef void (*AstalRiverCommandCallback)(gboolean success, const gchar *msg); + +void astal_river_river_run_command_async(AstalRiverRiver *self, gint length, const gchar **cmd, + AstalRiverCommandCallback callback); + +G_END_DECLS + +#endif // !ASTAL_RIVER_H diff --git a/lib/river/include/meson.build b/lib/river/include/meson.build new file mode 100644 index 0000000..4b08a89 --- /dev/null +++ b/lib/river/include/meson.build @@ -0,0 +1,6 @@ +astal_river_inc = include_directories('.') +astal_river_headers = files( + 'astal-river.h', +) + +install_headers('astal-river.h') diff --git a/lib/river/include/river-private.h b/lib/river/include/river-private.h new file mode 100644 index 0000000..14cd1c5 --- /dev/null +++ b/lib/river/include/river-private.h @@ -0,0 +1,20 @@ +#ifndef ASTAL_RIVER_OUTPUT_PRIVATE_H +#define ASTAL_RIVER_OUTPUT_PRIVATE_H + +#include + +#include "astal-river.h" +#include "river-status-unstable-v1-client.h" + +G_BEGIN_DECLS + +AstalRiverOutput *astal_river_output_new(guint id, struct wl_output *wl_output, + struct zriver_status_manager_v1 *status_manager, + struct wl_display *wl_display); + +struct wl_output *astal_river_output_get_wl_output(AstalRiverOutput *self); +void astal_river_output_set_focused_view(AstalRiverOutput *self, const gchar *focused_view); + +G_END_DECLS + +#endif // !ASTAL_RIVER_OUTPUT_PRIVATE_H diff --git a/lib/river/include/wayland-source.h b/lib/river/include/wayland-source.h new file mode 100644 index 0000000..b219589 --- /dev/null +++ b/lib/river/include/wayland-source.h @@ -0,0 +1,16 @@ +#ifndef __WAYLAND_SOURCE_H__ +#define __WAYLAND_SOURCE_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _WLSource WLSource; + +WLSource* wl_source_new(); +void wl_source_free(WLSource* self); +struct wl_display* wl_source_get_display(WLSource* source); + +G_END_DECLS + +#endif /* __WAYLAND_SOURCE_H__ */ diff --git a/lib/river/meson.build b/lib/river/meson.build new file mode 100644 index 0000000..aa5e23b --- /dev/null +++ b/lib/river/meson.build @@ -0,0 +1,18 @@ +project( + 'astal_river', + 'c', + version: '0.1.0', + 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') + +subdir('protocols') +subdir('include') +subdir('src') diff --git a/lib/river/meson_options.txt b/lib/river/meson_options.txt new file mode 100644 index 0000000..97aa4e7 --- /dev/null +++ b/lib/river/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/river/protocols/meson.build b/lib/river/protocols/meson.build new file mode 100644 index 0000000..ddd825f --- /dev/null +++ b/lib/river/protocols/meson.build @@ -0,0 +1,23 @@ +wayland_scanner = find_program('wayland-scanner') + +protocols = ['river-status-unstable-v1.xml', 'river-control-unstable-v1.xml'] + +gen_client_header = generator( + wayland_scanner, + output: ['@BASENAME@-client.h'], + arguments: ['-c', 'client-header', '@INPUT@', '@BUILD_DIR@/@BASENAME@-client.h'], +) + +gen_private_code = generator( + wayland_scanner, + output: ['@BASENAME@.c'], + arguments: ['-c', 'private-code', '@INPUT@', '@BUILD_DIR@/@BASENAME@.c'], +) + +client_protocol_srcs = [] + +foreach protocol : protocols + client_header = gen_client_header.process(protocol) + code = gen_private_code.process(protocol) + client_protocol_srcs += [client_header, code] +endforeach diff --git a/lib/river/protocols/river-control-unstable-v1.xml b/lib/river/protocols/river-control-unstable-v1.xml new file mode 100644 index 0000000..c431901 --- /dev/null +++ b/lib/river/protocols/river-control-unstable-v1.xml @@ -0,0 +1,86 @@ + + + + Copyright 2020 The River Developers + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + + + This interface allows clients to run compositor commands and receive a + success/failure response with output or a failure message respectively. + + Each command is built up in a series of add_argument requests and + executed with a run_command request. The first argument is the command + to be run. + + A complete list of commands should be made available in the man page of + the compositor. + + + + + This request indicates that the client will not use the + river_control object any more. Objects that have been created + through this instance are not affected. + + + + + + Arguments are stored by the server in the order they were sent until + the run_command request is made. + + + + + + + Execute the command built up using the add_argument request for the + given seat. + + + + + + + + + This object is created by the run_command request. Exactly one of the + success or failure events will be sent. This object will be destroyed + by the compositor after one of the events is sent. + + + + + Sent when the command has been successfully received and executed by + the compositor. Some commands may produce output, in which case the + output argument will be a non-empty string. + + + + + + + Sent when the command could not be carried out. This could be due to + sending a non-existent command, no command, not enough arguments, too + many arguments, invalid arguments, etc. + + + + + + diff --git a/lib/river/protocols/river-status-unstable-v1.xml b/lib/river/protocols/river-status-unstable-v1.xml new file mode 100644 index 0000000..09c52c1 --- /dev/null +++ b/lib/river/protocols/river-status-unstable-v1.xml @@ -0,0 +1,149 @@ + + + + Copyright 2020 The River Developers + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + + + A global factory for objects that receive status information specific + to river. It could be used to implement, for example, a status bar. + + + + + This request indicates that the client will not use the + river_status_manager object any more. Objects that have been created + through this instance are not affected. + + + + + + This creates a new river_output_status object for the given wl_output. + + + + + + + + This creates a new river_seat_status object for the given wl_seat. + + + + + + + + + This interface allows clients to receive information about the current + windowing state of an output. + + + + + This request indicates that the client will not use the + river_output_status object any more. + + + + + + Sent once binding the interface and again whenever the tag focus of + the output changes. + + + + + + + Sent once on binding the interface and again whenever the tag state + of the output changes. + + + + + + + Sent once on binding the interface and again whenever the set of + tags with at least one urgent view changes. + + + + + + + Sent once on binding the interface should a layout name exist and again + whenever the name changes. + + + + + + + Sent when the current layout name has been removed without a new one + being set, for example when the active layout generator disconnects. + + + + + + + This interface allows clients to receive information about the current + focus of a seat. Note that (un)focused_output events will only be sent + if the client has bound the relevant wl_output globals. + + + + + This request indicates that the client will not use the + river_seat_status object any more. + + + + + + Sent on binding the interface and again whenever an output gains focus. + + + + + + + Sent whenever an output loses focus. + + + + + + + Sent once on binding the interface and again whenever the focused + view or a property thereof changes. The title may be an empty string + if no view is focused or the focused view did not set a title. + + + + + + + Sent once on binding the interface and again whenever a new mode + is entered (e.g. with riverctl enter-mode foobar). + + + + + + diff --git a/lib/river/src/astal-river.c b/lib/river/src/astal-river.c new file mode 100644 index 0000000..37f34d5 --- /dev/null +++ b/lib/river/src/astal-river.c @@ -0,0 +1,51 @@ +#include +#include +#include + +#include "gio/gio.h" +#include "astal-river.h" + +GMainLoop* loop; + +void print_json(AstalRiverRiver* river) { + JsonNode* json = json_gobject_serialize(G_OBJECT(river)); + + gchar* json_str = json_to_string(json, FALSE); + g_print("%s\n", json_str); + g_free(json); +} + +int main(int argc, char** argv) { + gboolean daemon = FALSE; + + int opt; + const char* optstring = "d"; + + static struct option long_options[] = {{"daemon", no_argument, NULL, 'd'}, {NULL, 0, NULL, 0}}; + + while ((opt = getopt_long(argc, argv, optstring, long_options, NULL)) != -1) { + switch (opt) { + case 'd': + daemon = TRUE; + break; + default: + g_print("Usage: %s [-d]\n", argv[0]); + exit(EXIT_FAILURE); + } + } + + GError* error = NULL; + AstalRiverRiver* river = g_initable_new(ASTAL_RIVER_TYPE_RIVER, NULL, &error, NULL); + if (error) { + g_critical("%s\n", error->message); + exit(EXIT_FAILURE); + } + if (daemon) { + loop = g_main_loop_new(NULL, FALSE); + g_signal_connect(river, "changed", G_CALLBACK(print_json), NULL); + g_main_loop_run(loop); + } else { + print_json(river); + g_object_unref(river); + } +} diff --git a/lib/river/src/meson.build b/lib/river/src/meson.build new file mode 100644 index 0000000..b7ce20d --- /dev/null +++ b/lib/river/src/meson.build @@ -0,0 +1,71 @@ +srcs = files( + 'river-output.c', + 'river.c', + 'wayland-source.c', +) + +deps = [ + dependency('gobject-2.0'), + dependency('gio-2.0'), + dependency('wayland-client'), + dependency('json-glib-1.0'), +] + +astal_river_lib = library( + 'astal-river', + sources: srcs + client_protocol_srcs, + include_directories: astal_river_inc, + dependencies: deps, + version: meson.project_version(), + install: true, +) + +libastal_river = declare_dependency(link_with: astal_river_lib, include_directories: astal_river_inc) + +executable( + 'astal-river', + files('astal-river.c'), + dependencies: [ + dependency('gobject-2.0'), + dependency('gio-2.0'), + dependency('json-glib-1.0'), + libastal_river, + ], + install: true, +) + +pkg_config_name = 'astal-river-' + lib_so_version + +if get_option('introspection') + gir = gnome.generate_gir( + astal_river_lib, + sources: srcs + astal_river_headers, + nsversion: '0.1', + namespace: 'AstalRiver', + symbol_prefix: 'astal_river', + identifier_prefix: 'AstalRiver', + includes: ['GObject-2.0', 'Gio-2.0'], + header: 'astal-river.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-river', + version: meson.project_version(), + libraries: [astal_river_lib], + filebase: pkg_config_name, + subdirs: 'astal', + description: 'astal riverentication module', + url: 'https://github.com/astal-sh/river', +) diff --git a/lib/river/src/river-output.c b/lib/river/src/river-output.c new file mode 100644 index 0000000..6317db1 --- /dev/null +++ b/lib/river/src/river-output.c @@ -0,0 +1,343 @@ +#include + +#include "river-private.h" +#include "river-status-unstable-v1-client.h" + +struct _AstalRiverOutput { + GObject parent_instance; + guint focused_tags; + guint occupied_tags; + guint urgent_tags; + gchar* layout_name; + gchar* focused_view; + guint id; + gchar* name; +}; + +typedef struct { + struct zriver_status_manager_v1* river_status_manager; + struct zriver_output_status_v1* river_output_status; + struct wl_display* wl_display; + struct wl_output* wl_output; +} AstalRiverOutputPrivate; + +G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalRiverOutput, astal_river_output, G_TYPE_OBJECT); + +typedef enum { + ASTAL_RIVER_OUTPUT_PROP_FOCUSED_TAGS = 1, + ASTAL_RIVER_OUTPUT_PROP_OCCUPIED_TAGS, + ASTAL_RIVER_OUTPUT_PROP_URGENT_TAGS, + ASTAL_RIVER_OUTPUT_PROP_LAYOUT_NAME, + ASTAL_RIVER_OUTPUT_PROP_NAME, + ASTAL_RIVER_OUTPUT_PROP_FOCUSED_VIEW, + ASTAL_RIVER_OUTPUT_PROP_ID, + ASTAL_RIVER_OUTPUT_N_PROPERTIES +} AstalRiverOutputProperties; + +typedef enum { + ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED, + ASTAL_RIVER_OUTPUT_N_SIGNALS +} AstalRiverOutputSignals; + +static guint astal_river_output_signals[ASTAL_RIVER_OUTPUT_N_SIGNALS] = { + 0, +}; + +static GParamSpec* astal_river_output_properties[ASTAL_RIVER_OUTPUT_N_PROPERTIES] = { + NULL, +}; + +/** + * astal_river_output_get_nid + * @self: the AstalRiverOutput object + * + * Returns: the id of the underlying wl_output object + */ +guint astal_river_output_get_id(AstalRiverOutput* self) { return self->id; } + +/** + * astal_river_output_get_name + * @self: the AstalRiverOutput object + * + * Returns: (transfer none) (nullable): the name of the output + */ +gchar* astal_river_output_get_name(AstalRiverOutput* self) { return self->name; } + +/** + * astal_river_output_get_layout_name + * @self: the AstalRiverOutput object + * + * Returns: (transfer none) (nullable): the currently used layout name of the output + */ +gchar* astal_river_output_get_layout_name(AstalRiverOutput* self) { return self->layout_name; } + +/** + * astal_river_output_get_focused_view + * @self: the AstalRiverOutput object + * + * Returns: (transfer none) (nullable): the focused view on the output + */ +gchar* astal_river_output_get_focused_view(AstalRiverOutput* self) { return self->focused_view; } + +void astal_river_output_set_focused_view(AstalRiverOutput* self, const gchar* focused_view) { + g_free(self->focused_view); + self->focused_view = g_strdup(focused_view); + g_object_notify(G_OBJECT(self), "focused-view"); + g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); +} + +/** + * astal_river_output_get_focused_tags + * @self: the AstalRiverOutput object + * + * Returns: the focused tags of the output + */ +guint astal_river_output_get_focused_tags(AstalRiverOutput* self) { return self->focused_tags; } + +/** + * astal_river_output_get_urgent_tags + * @self: the AstalRiverOutput object + * + * Returns: the urgent tags of the output + */ +guint astal_river_output_get_urgent_tags(AstalRiverOutput* self) { return self->urgent_tags; } + +/** + * astal_river_output_get_occupied_tags + * @self: the AstalRiverOutput object + * + * Returns: the occupied tags of the output + */ +guint astal_river_output_get_occupied_tags(AstalRiverOutput* self) { return self->occupied_tags; } + +struct wl_output* astal_river_output_get_wl_output(AstalRiverOutput* self) { + AstalRiverOutputPrivate* priv = astal_river_output_get_instance_private(self); + return priv->wl_output; +} + +static void astal_river_output_get_property(GObject* object, guint property_id, GValue* value, + GParamSpec* pspec) { + AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(object); + + switch (property_id) { + case ASTAL_RIVER_OUTPUT_PROP_FOCUSED_TAGS: + g_value_set_uint(value, self->focused_tags); + break; + case ASTAL_RIVER_OUTPUT_PROP_OCCUPIED_TAGS: + g_value_set_uint(value, self->occupied_tags); + break; + case ASTAL_RIVER_OUTPUT_PROP_URGENT_TAGS: + g_value_set_uint(value, self->urgent_tags); + break; + case ASTAL_RIVER_OUTPUT_PROP_ID: + g_value_set_uint(value, self->id); + break; + case ASTAL_RIVER_OUTPUT_PROP_NAME: + g_value_set_string(value, self->name); + break; + case ASTAL_RIVER_OUTPUT_PROP_LAYOUT_NAME: + g_value_set_string(value, self->layout_name); + break; + case ASTAL_RIVER_OUTPUT_PROP_FOCUSED_VIEW: + g_value_set_string(value, self->focused_view); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_river_output_set_property(GObject* object, guint property_id, const GValue* value, + GParamSpec* pspec) { + AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(object); + + switch (property_id) { + case ASTAL_RIVER_OUTPUT_PROP_ID: + self->id = g_value_get_uint(value); + g_object_notify(G_OBJECT(self), "id"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } + g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); +} + +static void noop() {} + +static void astal_river_handle_focused_tags(void* data, struct zriver_output_status_v1* status, + uint32_t tags) { + AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); + self->focused_tags = tags; + g_object_notify(G_OBJECT(self), "focused-tags"); + g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); +} + +static void astal_river_handle_urgent_tags(void* data, struct zriver_output_status_v1* status, + uint32_t tags) { + AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); + self->urgent_tags = tags; + g_object_notify(G_OBJECT(self), "urgent-tags"); + g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); +} + +static void astal_river_handle_occupied_tags(void* data, struct zriver_output_status_v1* status, + struct wl_array* view_tags) { + AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); + guint tags = 0; + guint* view; + wl_array_for_each(view, view_tags) { tags |= *view; } + + self->occupied_tags = tags; + g_object_notify(G_OBJECT(self), "occupied-tags"); + g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); +} + +static void astal_river_handle_layout_name(void* data, struct zriver_output_status_v1* status, + const char* name) { + AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); + g_free(self->layout_name); + self->layout_name = g_strdup(name); + g_object_notify(G_OBJECT(self), "layout-name"); + g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); +} + +static void astal_river_handle_layout_name_clear(void* data, + struct zriver_output_status_v1* status) { + AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); + g_free(self->layout_name); + self->layout_name = NULL; + g_object_notify(G_OBJECT(self), "layout-name"); + g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); +} + +static void astal_river_wl_output_handle_name(void* data, struct wl_output* output, + const char* name) { + AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); + g_free(self->name); + self->name = g_strdup(name); + g_object_notify(G_OBJECT(self), "name"); + g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); +} + +static const struct zriver_output_status_v1_listener output_status_listener = { + .focused_tags = astal_river_handle_focused_tags, + .view_tags = astal_river_handle_occupied_tags, + .urgent_tags = astal_river_handle_urgent_tags, + .layout_name = astal_river_handle_layout_name, + .layout_name_clear = astal_river_handle_layout_name_clear, +}; + +static const struct wl_output_listener wl_output_listener = { + .name = astal_river_wl_output_handle_name, + .geometry = noop, + .mode = noop, + .scale = noop, + .description = noop, + .done = noop, +}; + +static void astal_river_output_finalize(GObject* object) { + AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(object); + AstalRiverOutputPrivate* priv = astal_river_output_get_instance_private(self); + + zriver_output_status_v1_destroy(priv->river_output_status); + wl_output_destroy(priv->wl_output); + + wl_display_roundtrip(priv->wl_display); + + g_free(self->layout_name); + g_free(self->name); + + G_OBJECT_CLASS(astal_river_output_parent_class)->finalize(object); +} + +static void astal_river_output_init(AstalRiverOutput* self) {} + +AstalRiverOutput* astal_river_output_new(guint id, struct wl_output* wl_output, + struct zriver_status_manager_v1* status_manager, + struct wl_display* wl_display) { + AstalRiverOutput* self = g_object_new(ASTAL_RIVER_TYPE_OUTPUT, NULL); + AstalRiverOutputPrivate* priv = astal_river_output_get_instance_private(self); + + self->id = id; + priv->wl_display = wl_display; + priv->wl_output = wl_output; + priv->river_status_manager = status_manager; + + priv->river_output_status = + zriver_status_manager_v1_get_river_output_status(priv->river_status_manager, wl_output); + + zriver_output_status_v1_add_listener(priv->river_output_status, &output_status_listener, self); + + wl_output_add_listener(wl_output, &wl_output_listener, self); + + wl_display_roundtrip(priv->wl_display); + + return self; +} + +static void astal_river_output_class_init(AstalRiverOutputClass* class) { + GObjectClass* object_class = G_OBJECT_CLASS(class); + object_class->get_property = astal_river_output_get_property; + object_class->set_property = astal_river_output_set_property; + object_class->finalize = astal_river_output_finalize; + /** + * AstalRiverOutput:focused-tags: + * + * The currently focused tags + */ + astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_FOCUSED_TAGS] = g_param_spec_uint( + "focused-tags", "focused-tags", "currently focused tags", 0, INT_MAX, 0, G_PARAM_READABLE); + /** + * AstalRiverOutput:occupied-tags: + * + * The currently occupied tags + */ + astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_OCCUPIED_TAGS] = + g_param_spec_uint("occupied-tags", "occupied-tags", "currently occupied tags", 0, INT_MAX, + 0, G_PARAM_READABLE); + /** + * AstalRiverOutput:urgent-tags: + * + * The currently tags marked as urgent + */ + astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_URGENT_TAGS] = g_param_spec_uint( + "urgent-tags", "urgent-tags", "currently urgent tags", 0, INT_MAX, 0, G_PARAM_READABLE); + /** + * AstalRiverOutput:id: + * + * The id of the underlying wl_output object + */ + astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_ID] = + g_param_spec_uint("id", "id", "id of the output object", 0, INT_MAX, 0, G_PARAM_READABLE); + /** + * AstalRiverOutput:layout-name: + * + * The name of active layout + */ + astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_LAYOUT_NAME] = g_param_spec_string( + "layout-name", "layout-name", "name of the current layout", NULL, G_PARAM_READABLE); + /** + * AstalRiverOutput:name: + * + * The name of this output + */ + astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_NAME] = + g_param_spec_string("name", "name", "name of the output", NULL, G_PARAM_READABLE); + /** + * AstalRiverOutput:focused-view: + * + * The name of currently focused view + */ + astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_FOCUSED_VIEW] = + g_param_spec_string("focused-view", "focused-view", + "name of last focused view on this output", NULL, G_PARAM_READABLE); + + g_object_class_install_properties(object_class, ASTAL_RIVER_OUTPUT_N_PROPERTIES, + astal_river_output_properties); + + astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED] = + g_signal_new("changed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); +} diff --git a/lib/river/src/river.c b/lib/river/src/river.c new file mode 100644 index 0000000..a735898 --- /dev/null +++ b/lib/river/src/river.c @@ -0,0 +1,504 @@ +#include +#include +#include +#include + +#include "river-control-unstable-v1-client.h" +#include "river-private.h" +#include "river-status-unstable-v1-client.h" +#include "wayland-source.h" + +struct _AstalRiverRiver { + GObject parent_instance; + GList* outputs; + gchar* focused_output; + gchar* focused_view; + gchar* mode; +}; + +typedef struct { + GHashTable* signal_ids; + gboolean init; + struct wl_registry* wl_registry; + struct wl_seat* seat; + struct wl_display* display; + WLSource* wl_source; + struct zriver_status_manager_v1* river_status_manager; + struct zriver_control_v1* river_control; + struct zriver_seat_status_v1* river_seat_status; +} AstalRiverRiverPrivate; + +static JsonSerializableIface* serializable_iface = NULL; +static void astal_river_river_initable_iface_init(GInitableIface* iface); +static void astal_river_river_json_serializable_iface_init(JsonSerializableIface* g_iface); + +G_DEFINE_TYPE_WITH_CODE(AstalRiverRiver, astal_river_river, G_TYPE_OBJECT, + G_ADD_PRIVATE(AstalRiverRiver) G_IMPLEMENT_INTERFACE( + G_TYPE_INITABLE, astal_river_river_initable_iface_init) + G_IMPLEMENT_INTERFACE(JSON_TYPE_SERIALIZABLE, + astal_river_river_json_serializable_iface_init)) + +typedef enum { + ASTAL_RIVER_RIVER_PROP_FOCUSED_OUTPUT = 1, + ASTAL_RIVER_RIVER_PROP_FOCUSED_VIEW, + ASTAL_RIVER_RIVER_PROP_MODE, + ASTAL_RIVER_RIVER_PROP_OUTPUTS, + ASTAL_RIVER_RIVER_N_PROPERTIES +} AstalRiverRiverProperties; + +typedef enum { + ASTAL_RIVER_RIVER_SIGNAL_CHANGED, + ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_ADDED, + ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_REMOVED, + ASTAL_RIVER_RIVER_N_SIGNALS +} AstalRiverRiverSignals; + +static guint astal_river_river_signals[ASTAL_RIVER_RIVER_N_SIGNALS] = { + 0, +}; +static GParamSpec* astal_river_river_properties[ASTAL_RIVER_RIVER_N_PROPERTIES] = { + NULL, +}; + +static void reemit_changed(AstalRiverOutput* output, AstalRiverRiver* self) { + g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); +} + +static AstalRiverOutput* find_output_by_id(AstalRiverRiver* self, uint32_t id) { + GList* output = self->outputs; + while (output != NULL) { + AstalRiverOutput* river_output = output->data; + if (astal_river_output_get_id(river_output) == id) return river_output; + output = output->next; + } + return NULL; +} + +static AstalRiverOutput* find_output_by_output(AstalRiverRiver* self, struct wl_output* wl_output) { + GList* output = self->outputs; + while (output != NULL) { + AstalRiverOutput* river_output = output->data; + if (astal_river_output_get_wl_output(river_output) == wl_output) return river_output; + output = output->next; + } + return NULL; +} + +static AstalRiverOutput* find_output_by_name(AstalRiverRiver* self, gchar* name) { + GList* output = self->outputs; + while (output != NULL) { + AstalRiverOutput* river_output = output->data; + if (strcmp(astal_river_output_get_name(river_output), name) == 0) return river_output; + output = output->next; + } + return NULL; +} + +/** + * astal_river_river_get_outputs + * @self: the AstalRiverRiver object + * + * Returns: (transfer none) (element-type AstalRiver.Output): a list of all outputs + * + */ +GList* astal_river_river_get_outputs(AstalRiverRiver* self) { return self->outputs; } + +/** + * astal_river_river_get_output + * @self: the AstalRiverRiver object + * @name: the name of the output + * + * Returns: (transfer none) (nullable): the output with the given name or null + */ +AstalRiverOutput* astal_river_river_get_output(AstalRiverRiver* self, gchar* name) { + return find_output_by_name(self, name); +} + +/** + * astal_river_river_get_focused_view + * @self: the AstalRiverOutput object + * + * Returns: (transfer none) (nullable): the currently focused view + */ +gchar* astal_river_river_get_focused_view(AstalRiverRiver* self) { return self->focused_view; } + +/** + * astal_river_river_get_focused_output + * @self: the AstalRiverOutput object + * + * Returns: (transfer none) (nullable): the name of the currently focused output + */ +gchar* astal_river_river_get_focused_output(AstalRiverRiver* self) { return self->focused_output; } + +/** + * astal_river_river_get_mode + * @self: the AstalRiverOutput object + * + * Returns: (transfer none) (nullable): the currently active mode + */ +gchar* astal_river_river_get_mode(AstalRiverRiver* self) { return self->mode; } + +static void astal_river_river_get_property(GObject* object, guint property_id, GValue* value, + GParamSpec* pspec) { + AstalRiverRiver* self = ASTAL_RIVER_RIVER(object); + + switch (property_id) { + case ASTAL_RIVER_RIVER_PROP_MODE: + g_value_set_string(value, self->mode); + break; + case ASTAL_RIVER_RIVER_PROP_FOCUSED_VIEW: + g_value_set_string(value, self->focused_view); + break; + case ASTAL_RIVER_RIVER_PROP_FOCUSED_OUTPUT: + g_value_set_string(value, self->focused_output); + break; + case ASTAL_RIVER_RIVER_PROP_OUTPUTS: + g_value_set_pointer(value, self->outputs); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static JsonNode* astal_river_river_serialize_property(JsonSerializable* serializable, + const gchar* name, const GValue* value, + GParamSpec* pspec) { + JsonNode* retval = NULL; + if (strcmp(name, "outputs") == 0) { + JsonArray* outputs = json_array_new(); + retval = json_node_new(JSON_NODE_ARRAY); + GList* output = g_value_get_pointer(value); + while (output != NULL) { + AstalRiverOutput* river_output = output->data; + json_array_add_element(outputs, json_gobject_serialize(G_OBJECT(river_output))); + output = output->next; + } + json_node_take_array(retval, outputs); + } else + retval = serializable_iface->serialize_property(serializable, name, value, pspec); + + return retval; +} + +static void noop() {} + +static void river_seat_status_handle_focused_output(void* data, + struct zriver_seat_status_v1* seat_status, + struct wl_output* focused_output) { + AstalRiverRiver* self = ASTAL_RIVER_RIVER(data); + g_free(self->focused_output); + AstalRiverOutput* output = find_output_by_output(self, focused_output); + if (output == NULL) return; + self->focused_output = g_strdup(astal_river_output_get_name(output)); + g_object_notify(G_OBJECT(self), "focused-output"); + g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); +} + +static void river_seat_status_handle_focused_view(void* data, + struct zriver_seat_status_v1* seat_status, + const char* focused_view) { + AstalRiverRiver* self = ASTAL_RIVER_RIVER(data); + g_free(self->focused_view); + self->focused_view = g_strdup(focused_view); + AstalRiverOutput* output = find_output_by_name(self, self->focused_output); + astal_river_output_set_focused_view(output, focused_view); + g_object_notify(G_OBJECT(self), "focused-view"); + g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); +} + +static void river_seat_status_handle_mode(void* data, struct zriver_seat_status_v1* seat_status, + const char* mode) { + AstalRiverRiver* self = ASTAL_RIVER_RIVER(data); + g_free(self->mode); + self->mode = g_strdup(mode); + g_object_notify(G_OBJECT(self), "mode"); + g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); +} + +static const struct zriver_seat_status_v1_listener river_seat_status_listener = { + .focused_output = river_seat_status_handle_focused_output, + .unfocused_output = noop, + .focused_view = river_seat_status_handle_focused_view, + .mode = river_seat_status_handle_mode, +}; + +static void global_registry_handler(void* data, struct wl_registry* registry, uint32_t id, + const char* interface, uint32_t version) { + AstalRiverRiver* self = ASTAL_RIVER_RIVER(data); + AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); + if (strcmp(interface, wl_output_interface.name) == 0) { + if (priv->river_status_manager == NULL) return; + struct wl_output* wl_out = wl_registry_bind(registry, id, &wl_output_interface, 4); + AstalRiverOutput* output = + astal_river_output_new(id, wl_out, priv->river_status_manager, priv->display); + + self->outputs = g_list_append(self->outputs, output); + g_object_notify(G_OBJECT(self), "outputs"); + g_signal_emit(G_OBJECT(self), + astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_ADDED], 0, + astal_river_output_get_name(output)); + g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); + + guint signal_id = g_signal_connect(output, "changed", G_CALLBACK(reemit_changed), self); + g_hash_table_insert(priv->signal_ids, GUINT_TO_POINTER(id), GUINT_TO_POINTER(signal_id)); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + priv->seat = wl_registry_bind(registry, id, &wl_seat_interface, 4); + } else if (strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { + priv->river_status_manager = + wl_registry_bind(registry, id, &zriver_status_manager_v1_interface, 4); + } else if (strcmp(interface, zriver_control_v1_interface.name) == 0) { + priv->river_control = wl_registry_bind(registry, id, &zriver_control_v1_interface, 1); + } +} + +static void astal_river_river_callback_success(void* data, struct zriver_command_callback_v1* cb, + const char* msg) { + AstalRiverCommandCallback callback = (AstalRiverCommandCallback)(data); + callback(TRUE, msg); +} + +static void astal_river_river_callback_failure(void* data, struct zriver_command_callback_v1* cb, + const char* msg) { + AstalRiverCommandCallback callback = (AstalRiverCommandCallback)(data); + callback(FALSE, msg); +} + +const struct zriver_command_callback_v1_listener cb_listener = { + .success = astal_river_river_callback_success, .failure = astal_river_river_callback_failure}; + +/** + * astal_river_river_run_command_async: + * @self: the AstalRiverRiver object + * @length: the length of the cmd array + * @cmd: (array length=length): the command to execute + * @callback: (scope async) (nullable): the callback to invoke. + * + * Calls the given callback with the provided parameters. + */ +void astal_river_river_run_command_async(AstalRiverRiver* self, gint length, const gchar** cmd, + AstalRiverCommandCallback callback) { + AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); + + for (gint i = 0; i < length; ++i) { + zriver_control_v1_add_argument(priv->river_control, cmd[i]); + } + + struct zriver_command_callback_v1* cb = + zriver_control_v1_run_command(priv->river_control, priv->seat); + if (callback != NULL) zriver_command_callback_v1_add_listener(cb, &cb_listener, callback); +} + +static void global_registry_remover(void* data, struct wl_registry* registry, uint32_t id) { + AstalRiverRiver* self = ASTAL_RIVER_RIVER(data); + AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); + AstalRiverOutput* output = find_output_by_id(self, id); + if (output != NULL) { + guint signal_id = + GPOINTER_TO_UINT(g_hash_table_lookup(priv->signal_ids, GUINT_TO_POINTER(id))); + g_hash_table_remove(priv->signal_ids, GUINT_TO_POINTER(id)); + g_signal_handler_disconnect(output, signal_id); + g_signal_emit(G_OBJECT(self), + astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_ADDED], 0, + astal_river_output_get_name(output)); + self->outputs = g_list_remove(self->outputs, output); + g_object_notify(G_OBJECT(self), "outputs"); + g_object_unref(output); + return; + } + g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); +} + +static const struct wl_registry_listener registry_listener = {global_registry_handler, + global_registry_remover}; + +static void astal_river_river_json_serializable_iface_init(JsonSerializableIface* iface) { + iface->serialize_property = astal_river_river_serialize_property; + serializable_iface = g_type_default_interface_peek(JSON_TYPE_SERIALIZABLE); +} + +static gboolean astal_river_river_initable_init(GInitable* initable, GCancellable* cancellable, + GError** error) { + AstalRiverRiver* self = ASTAL_RIVER_RIVER(initable); + AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); + + if (priv->init) return TRUE; + + priv->wl_source = wl_source_new(NULL, NULL); + + if (priv->wl_source == NULL) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can not connect to wayland display"); + return FALSE; + } + + priv->display = wl_source_get_display(priv->wl_source); + + priv->wl_registry = wl_display_get_registry(priv->display); + wl_registry_add_listener(priv->wl_registry, ®istry_listener, self); + + wl_display_roundtrip(priv->display); + + if (priv->river_status_manager == NULL) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can not connect river status protocol"); + return FALSE; + } + + priv->river_seat_status = + zriver_status_manager_v1_get_river_seat_status(priv->river_status_manager, priv->seat); + zriver_seat_status_v1_add_listener(priv->river_seat_status, &river_seat_status_listener, self); + + wl_display_roundtrip(priv->display); + + priv->init = TRUE; + return TRUE; +} + +static void astal_river_river_constructed(GObject* object) { + astal_river_river_initable_init(G_INITABLE(object), NULL, NULL); +} + +static void astal_river_river_initable_iface_init(GInitableIface* iface) { + iface->init = astal_river_river_initable_init; +} + +static void astal_river_river_init(AstalRiverRiver* self) { + AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); + self->outputs = NULL; + priv->init = FALSE; + priv->seat = NULL; + priv->display = NULL; + priv->river_status_manager = NULL; + priv->signal_ids = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +/** + * astal_river_river_new + * + * creates a new River object. It is recommended to use the get_default() method + * instead of this method. + * + * Returns: (nullable): a newly created connection to river + */ +AstalRiverRiver* astal_river_river_new() { + return g_initable_new(ASTAL_RIVER_TYPE_RIVER, NULL, NULL, NULL); +} + +static void disconnect_signal(gpointer key, gpointer value, gpointer user_data) { + AstalRiverRiver* self = ASTAL_RIVER_RIVER(user_data); + + AstalRiverOutput* output = find_output_by_id(self, GPOINTER_TO_UINT(key)); + g_signal_handler_disconnect(output, GPOINTER_TO_UINT(value)); +} + +/** + * astal_river_river_get_default + * + * Returns: (nullable) (transfer none): gets the default River object. + */ +AstalRiverRiver* astal_river_river_get_default() { + static AstalRiverRiver* self = NULL; + + if (self == NULL) self = astal_river_river_new(); + + return self; +} + +/** + * astal_river_get_default + * + * Returns: (nullable) (transfer none): gets the default River object. + */ +AstalRiverRiver* astal_river_get_default() { return astal_river_river_get_default(); } + +static void astal_river_river_finalize(GObject* object) { + AstalRiverRiver* self = ASTAL_RIVER_RIVER(object); + AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); + + g_hash_table_foreach(priv->signal_ids, disconnect_signal, self); + g_hash_table_destroy(priv->signal_ids); + + if (priv->display != NULL) wl_display_roundtrip(priv->display); + + g_clear_list(&self->outputs, g_object_unref); + self->outputs = NULL; + + if (priv->wl_registry != NULL) wl_registry_destroy(priv->wl_registry); + if (priv->river_status_manager != NULL) + zriver_status_manager_v1_destroy(priv->river_status_manager); + if (priv->river_seat_status != NULL) zriver_seat_status_v1_destroy(priv->river_seat_status); + if (priv->seat != NULL) wl_seat_destroy(priv->seat); + if (priv->display != NULL) wl_display_flush(priv->display); + + if (priv->wl_source != NULL) wl_source_free(priv->wl_source); + + g_free(self->focused_view); + g_free(self->focused_output); + g_free(self->mode); + + G_OBJECT_CLASS(astal_river_river_parent_class)->finalize(object); +} + +static void astal_river_river_class_init(AstalRiverRiverClass* class) { + GObjectClass* object_class = G_OBJECT_CLASS(class); + object_class->get_property = astal_river_river_get_property; + object_class->finalize = astal_river_river_finalize; + object_class->constructed = astal_river_river_constructed; + + /** + * AstalRiverRiver:mode: + * + * The currently active mode + */ + astal_river_river_properties[ASTAL_RIVER_RIVER_PROP_MODE] = + g_param_spec_string("mode", "mode", "currently active mode", NULL, G_PARAM_READABLE); + /** + * AstalRiverRiver:focused-view: + * + * The name of the currently focused view + */ + astal_river_river_properties[ASTAL_RIVER_RIVER_PROP_FOCUSED_VIEW] = g_param_spec_string( + "focused-view", "focused-view", "currently focused view", NULL, G_PARAM_READABLE); + /** + * AstalRiverRiver:focused-output: + * + * The name of the currently focused output + */ + astal_river_river_properties[ASTAL_RIVER_RIVER_PROP_FOCUSED_OUTPUT] = g_param_spec_string( + "focused-output", "focused-output", "currently focused-output", NULL, G_PARAM_READABLE); + /** + * AstalRiverRiver:outputs: (type GList(AstalRiverOutput)) + * + * A list of AstalRiverOutput objects + */ + astal_river_river_properties[ASTAL_RIVER_RIVER_PROP_OUTPUTS] = + g_param_spec_pointer("outputs", "outputs", "a list of all outputs", G_PARAM_READABLE); + + g_object_class_install_properties(object_class, ASTAL_RIVER_RIVER_N_PROPERTIES, + astal_river_river_properties); + /** + * AstalRiverRiver::output-added: + * @river: the object which received the signal. + * @output: the name of the added output + * + * This signal is emitted when a new output was connected + */ + astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_ADDED] = + g_signal_new("output-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, G_TYPE_STRING); + /** + * AstalRiverRiver::output-removed: + * @river: the object which received the signal. + * @output: the name of the removed output + * + * This signal is emitted when a new output was disconnected + */ + astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_REMOVED] = + g_signal_new("output-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, G_TYPE_STRING); + + astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED] = + g_signal_new("changed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); +} diff --git a/lib/river/src/wayland-source.c b/lib/river/src/wayland-source.c new file mode 100644 index 0000000..875c32c --- /dev/null +++ b/lib/river/src/wayland-source.c @@ -0,0 +1,104 @@ + +#include "wayland-source.h" + +#include +#include +#include + +struct _WLSource { + GSource source; + struct wl_display *display; + gpointer fd; + int error; +}; + +static gboolean wl_source_prepare(GSource *source, gint *timeout) { + WLSource *self = (WLSource *)source; + + *timeout = 0; + if (wl_display_prepare_read(self->display) != 0) + return TRUE; + else if (wl_display_flush(self->display) < 0) { + self->error = errno; + return TRUE; + } + *timeout = -1; + return FALSE; +} + +static gboolean wl_source_check(GSource *source) { + WLSource *self = (WLSource *)source; + + if (self->error > 0) return TRUE; + + GIOCondition revents; + revents = g_source_query_unix_fd(source, self->fd); + + if (revents & G_IO_IN) { + if (wl_display_read_events(self->display) < 0) self->error = errno; + } else + wl_display_cancel_read(self->display); + + return revents > 0; +} + +static gboolean wl_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { + WLSource *self = (WLSource *)source; + GIOCondition revents; + + revents = g_source_query_unix_fd(source, self->fd); + if ((self->error > 0) || (revents & (G_IO_ERR | G_IO_HUP))) { + errno = self->error; + self->error = 0; + if (callback != NULL) return callback(user_data); + return G_SOURCE_REMOVE; + } + + if (wl_display_dispatch_pending(self->display) < 0) { + if (callback != NULL) return callback(user_data); + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +static void wl_source_finalize(GSource *source) { + WLSource *self = (WLSource *)source; + wl_display_disconnect(self->display); +} + +static GSourceFuncs wl_source_funcs = { + .prepare = wl_source_prepare, + .check = wl_source_check, + .dispatch = wl_source_dispatch, + .finalize = wl_source_finalize, +}; + +WLSource *wl_source_new() { + struct wl_display *display; + WLSource *self; + GSource *source; + + display = wl_display_connect(NULL); + if (display == NULL) return NULL; + + source = g_source_new(&wl_source_funcs, sizeof(WLSource)); + self = (WLSource *)source; + self->display = display; + + self->fd = g_source_add_unix_fd(source, wl_display_get_fd(self->display), + G_IO_IN | G_IO_ERR | G_IO_HUP); + + g_source_attach(source, NULL); + + return self; +} + +void wl_source_free(WLSource *self) { + GSource *source = (GSource *)self; + g_return_if_fail(source != NULL); + g_source_destroy(source); + g_source_unref(source); +} + +struct wl_display *wl_source_get_display(WLSource *self) { return self->display; } diff --git a/lib/river/version b/lib/river/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/river/version @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/tray/cli.vala b/lib/tray/cli.vala new file mode 100644 index 0000000..3147fb5 --- /dev/null +++ b/lib/tray/cli.vala @@ -0,0 +1,54 @@ +static bool version; +static bool daemonize; + +const OptionEntry[] options = { + { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, "Print version number", null }, + { "daemonize", 'd', OptionFlags.NONE, OptionArg.NONE, ref daemonize, "Monitor the systemtray", null }, + { null }, +}; + +int main(string[] argv) { + try { + var opts = new OptionContext(); + opts.add_main_entries(options, null); + opts.set_help_enabled(true); + opts.set_ignore_unknown_options(false); + opts.parse(ref argv); + } catch (OptionError err) { + printerr (err.message); + return 1; + } + + if (version) { + print(AstalTray.VERSION); + return 0; + } + + if (daemonize) { + var loop = new MainLoop(); + var tray = new AstalTray.Tray(); + + tray.item_added.connect((id) => { + AstalTray.TrayItem item = tray.get_item(id); + + stdout.printf("{\"event\":\"item_added\",\"id\":\"%s\",\"item\":%s}\n", + id, item.to_json_string()); + stdout.flush(); + + item.changed.connect(() => { + stdout.printf("{\"event\":\"item_changed\",\"id\":\"%s\",\"item\":%s}\n", + id, item.to_json_string()); + stdout.flush(); + }); + }); + + tray.item_removed.connect((id) => { + stdout.printf("{\"event\":\"item_removed\",\"id\":\"%s\"}\n", id); + stdout.flush(); + }); + + loop.run(); + } + + return 0; +} diff --git a/lib/tray/config.vala.in b/lib/tray/config.vala.in new file mode 100644 index 0000000..8ef8498 --- /dev/null +++ b/lib/tray/config.vala.in @@ -0,0 +1,6 @@ +namespace AstalTray { + public const int MAJOR_VERSION = @MAJOR_VERSION@; + public const int MINOR_VERSION = @MINOR_VERSION@; + public const int MICRO_VERSION = @MICRO_VERSION@; + public const string VERSION = "@VERSION@"; +} diff --git a/lib/tray/meson.build b/lib/tray/meson.build new file mode 100644 index 0000000..421f33d --- /dev/null +++ b/lib/tray/meson.build @@ -0,0 +1,118 @@ +project( + 'astal-tray', + 'vala', + 'c', + version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), + meson_version: '>= 0.62.0', + default_options: [ + 'warning_level=2', + 'werror=false', + 'c_std=gnu11', + ], +) + +assert( + get_option('lib') or get_option('cli'), + 'Either lib or cli option must be set to true.', +) + +version_split = meson.project_version().split('.') +api_version = version_split[0] + '.' + version_split[1] +gir = 'AstalTray-' + api_version + '.gir' +typelib = 'AstalTray-' + api_version + '.typelib' + +config = configure_file( + input: 'config.vala.in', + output: 'config.vala', + configuration: { + 'VERSION': meson.project_version(), + 'MAJOR_VERSION': version_split[0], + 'MINOR_VERSION': version_split[1], + 'MICRO_VERSION': version_split[2], + }, +) + +deps = [ + dependency('glib-2.0'), + dependency('gobject-2.0'), + dependency('gio-2.0'), + dependency('json-glib-1.0'), + dependency('gdk-pixbuf-2.0'), + dependency('gtk+-3.0'), +] + +dbusmenu_cflags = run_command( + find_program('pkg-config', required: true), + '--cflags', 'dbusmenu-gtk3-0.4', + 'gobject-introspection-1.0', + 'gobject-2.0', + 'glib-2.0', + capture: true, + check: true, +).stdout().strip() + +dbusmenu_libs = run_command( + find_program('pkg-config', required: true), + '--libs', 'dbusmenu-gtk3-0.4', + 'gobject-introspection-1.0', + 'gobject-2.0', + 'glib-2.0', + capture: true, + check: true, +).stdout().strip() + +sources = [config, 'tray.vala', 'watcher.vala', 'trayItem.vala'] + +if get_option('lib') + lib = library( + meson.project_name(), + sources, + dependencies: deps, + vala_header: meson.project_name() + '.h', + vala_vapi: meson.project_name() + '-' + api_version + '.vapi', + vala_gir: gir, + vala_args: ['--pkg', 'DbusmenuGtk3-0.4', '--pkg', 'Dbusmenu-0.4'], + version: meson.project_version(), + c_args: dbusmenu_cflags.split(' '), + link_args: dbusmenu_libs.split(' '), + install: true, + install_dir: [true, true, true, true], + ) + + import('pkgconfig').generate( + lib, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: deps, + install_dir: get_option('libdir') / 'pkgconfig', + ) + + custom_target( + typelib, + command: [ + find_program('g-ir-compiler'), + '--output', '@OUTPUT@', + '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', + meson.current_build_dir() / gir, + ], + input: lib, + output: typelib, + depends: lib, + install: true, + install_dir: get_option('libdir') / 'girepository-1.0', + ) +endif + +if get_option('cli') + executable( + meson.project_name(), + ['cli.vala', sources], + dependencies: deps, + vala_args: ['--pkg', 'DbusmenuGtk3-0.4', '--pkg', 'Dbusmenu-0.4'], + c_args: dbusmenu_cflags.split(' '), + link_args: dbusmenu_libs.split(' '), + install: true, + ) +endif diff --git a/lib/tray/meson_options.txt b/lib/tray/meson_options.txt new file mode 100644 index 0000000..f110242 --- /dev/null +++ b/lib/tray/meson_options.txt @@ -0,0 +1,11 @@ +option( + 'lib', + type: 'boolean', + value: true, +) + +option( + 'cli', + type: 'boolean', + value: true, +) diff --git a/lib/tray/tray.vala b/lib/tray/tray.vala new file mode 100644 index 0000000..09b0643 --- /dev/null +++ b/lib/tray/tray.vala @@ -0,0 +1,135 @@ +namespace AstalTray { +[DBus (name="org.kde.StatusNotifierWatcher")] +internal interface IWatcher : Object { + public abstract string[] RegisteredStatusNotifierItems { owned get; } + public abstract int ProtocolVersion { owned get; } + + public abstract void RegisterStatusNotifierItem(string service, BusName sender) throws DBusError, IOError; + public abstract void RegisterStatusNotifierHost(string service) throws DBusError, IOError; + + public signal void StatusNotifierItemRegistered(string service); + public signal void StatusNotifierItemUnregistered(string service); + public signal void StatusNotifierHostRegistered(); + public signal void StatusNotifierHostUnregistered(); +} + +public Tray get_default() { + return Tray.get_default(); +} + +public class Tray : Object { + private static Tray? instance; + public static unowned Tray get_default() { + if (instance == null) + instance = new Tray(); + + return instance; + } + + private StatusNotifierWatcher watcher; + private IWatcher proxy; + + private HashTable _items = + new HashTable(str_hash, str_equal); + + public List items { owned get { return _items.get_values(); }} + + public signal void item_added(string service) { + notify_property("items"); + } + + public signal void item_removed(string service) { + notify_property("items"); + } + + construct { + try { + Bus.own_name( + BusType.SESSION, + "org.kde.StatusNotifierWatcher", + BusNameOwnerFlags.NONE, + start_watcher, + () => { + if (proxy != null) { + proxy = null; + } + }, + start_host + ); + } catch (Error err) { + critical(err.message); + } + + } + + private void start_watcher(DBusConnection conn) { + try { + watcher = new StatusNotifierWatcher(); + conn.register_object("/StatusNotifierWatcher", watcher); + watcher.StatusNotifierItemRegistered.connect(on_item_register); + watcher.StatusNotifierItemUnregistered.connect(on_item_unregister); + } catch (Error err) { + critical(err.message); + } + } + + private void start_host() { + if (proxy != null) + return; + + try { + proxy = Bus.get_proxy_sync(BusType.SESSION, + "org.kde.StatusNotifierWatcher", + "/StatusNotifierWatcher"); + + proxy.StatusNotifierItemRegistered.connect(on_item_register); + proxy.StatusNotifierItemUnregistered.connect(on_item_unregister); + + proxy.notify["g-name-owner"].connect(() => { + _items.foreach((service, _) => { + item_removed(service); + }); + + _items.remove_all(); + + if(proxy != null) { + foreach (string item in proxy.RegisteredStatusNotifierItems) { + on_item_register(item); + } + } else { + foreach (string item in watcher.RegisteredStatusNotifierItems) { + on_item_register(item); + } + } + }); + + foreach (string item in proxy.RegisteredStatusNotifierItems) { + on_item_register(item); + } + } catch (Error err) { + critical("cannot get proxy: %s", err.message); + } + } + + private void on_item_register(string service) { + if (_items.contains(service)) + return; + + var parts = service.split("/", 2); + TrayItem item = new TrayItem(parts[0], "/" + parts[1]); + item.ready.connect(() => { + _items.set(service, item); + item_added(service); + }); + } + + private void on_item_unregister(string service) { + _items.remove(service); + item_removed(service); + } + + public TrayItem get_item(string service) { + return _items.get(service); + } +} +} diff --git a/lib/tray/trayItem.vala b/lib/tray/trayItem.vala new file mode 100644 index 0000000..b6b9da0 --- /dev/null +++ b/lib/tray/trayItem.vala @@ -0,0 +1,363 @@ +using DbusmenuGtk; + +namespace AstalTray { +public struct Pixmap { + int width; + int height; + uint8[] bytes; +} + +public struct Tooltip { + string icon_name; + Pixmap[] icon; + string title; + string description; +} + +[DBus (use_string_marshalling = true)] +public enum Category { + [DBus (value = "ApplicationStatus"), Description (nick = "ApplicationStatus")] + APPLICATION, + + [DBus (value = "Communications"), Description (nick = "Communications")] + COMMUNICATIONS, + + [DBus (value = "SystemServices"), Description (nick = "SystemServices")] + SYSTEM, + + [DBus (value = "Hardware"), Description (nick = "Hardware")] + HARDWARE; + + public string to_nick () { + var enumc = (EnumClass)typeof (Category).class_ref(); + unowned var eval = enumc.get_value(this); + return eval.value_nick; + } +} + + +[DBus (use_string_marshalling = true)] +public enum Status { + [DBus (value = "Passive"), Description (nick = "Passive")] + PASSIVE, + + [DBus (value = "Active"), Description (nick = "Active")] + ACTIVE, + + [DBus (value = "NeedsAttention"), Description (nick = "NeedsAttention")] + NEEDS_ATTENTION; + + public string to_nick () { + var enumc = (EnumClass)typeof (Status).class_ref(); + unowned var eval = enumc.get_value(this); + return eval.value_nick; + } +} + +[DBus (name="org.kde.StatusNotifierItem")] +internal interface IItem : DBusProxy { + public abstract string Title { owned get; } + public abstract Category Category { owned get; } + public abstract Status Status { owned get; } + public abstract Tooltip? ToolTip { owned get; } + public abstract string Id { owned get; } + public abstract string? IconThemePath { owned get; } + public abstract bool ItemIsMenu { owned get; } + public abstract ObjectPath? Menu { owned get; } + public abstract string IconName { owned get; } + public abstract Pixmap[] IconPixmap { owned get; } + public abstract string AttentionIconName { owned get; } + public abstract Pixmap[] AttentionIconPixmap { owned get; } + public abstract string OverlayIconName { owned get; } + public abstract Pixmap[] OverlayIconPixmap { owned get; } + + public abstract void ContexMenu(int x, int y) throws DBusError, IOError; + public abstract void Activate(int x, int y) throws DBusError, IOError; + public abstract void SecondaryActivate(int x, int y) throws DBusError, IOError; + public abstract void Scroll(int delta, string orientation) throws DBusError, IOError; + + public signal void NewTitle(); + public signal void NewIcon(); + public signal void NewAttentionIcon(); + public signal void NewOverlayIcon(); + public signal void NewToolTip(); + public signal void NewStatus(string status); +} + +public class TrayItem : Object { + private IItem proxy; + private List connection_ids; + + public string title { owned get { return proxy.Title; } } + public Category category { get { return proxy.Category; } } + public Status status { get { return proxy.Status; } } + public Tooltip? tooltip { owned get { return proxy.ToolTip; } } + + public string tooltip_markup { + owned get { + if (proxy.ToolTip == null) + return ""; + + var tt = proxy.ToolTip.title; + if (proxy.ToolTip.description != "") + tt += "\n" + proxy.ToolTip.description; + + return tt; + } + } + + public string id { owned get { return proxy.Id ;} } + public string icon_theme_path { owned get { return proxy.IconThemePath ;} } + public bool is_menu { get { return proxy.ItemIsMenu ;} } + + public string icon_name { + owned get { + return proxy.Status == Status.NEEDS_ATTENTION + ? proxy.AttentionIconName + : proxy.IconName; + } + } + + public Gdk.Pixbuf icon_pixbuf { owned get { return _get_icon_pixbuf(); } } + + public GLib.Icon gicon { get; private set; } + + public string item_id { get; private set; } + + public signal void changed(); + public signal void ready(); + + public TrayItem(string service, string path) { + connection_ids = new List(); + item_id = service + path; + setup_proxy.begin(service, path, (_, res) => setup_proxy.end(res)); + } + + private async void setup_proxy(string service, string path) { + try { + proxy = yield Bus.get_proxy( + BusType.SESSION, + service, + path); + + connection_ids.append(proxy.NewStatus.connect(refresh_all_properties)); + connection_ids.append(proxy.NewToolTip.connect(refresh_all_properties)); + connection_ids.append(proxy.NewTitle.connect(refresh_all_properties)); + connection_ids.append(proxy.NewIcon.connect(refresh_all_properties)); + + proxy.notify["g-name-owner"].connect(() => { + if (proxy.g_name_owner == null) { + foreach (var id in connection_ids) + SignalHandler.disconnect(proxy, id); + } + }); + + update_gicon(); + + ready(); + } catch (Error err) { + critical(err.message); + } + } + + private void _notify() { + string[] props = { "category", "id", "title", "status", "is-menu", "tooltip-markup", "icon-name", "icon-pixbuf" }; + + foreach (string prop in props) + notify_property(prop); + + changed(); + } + + private void update_gicon() { + if(icon_name != null && icon_name != "") { + if(icon_theme_path != null && icon_theme_path != "") { + + Gtk.IconTheme icon_theme = new Gtk.IconTheme(); + string[] paths = {icon_theme_path}; + icon_theme.set_search_path(paths); + + int size = icon_theme.get_icon_sizes(icon_name)[0]; + Gtk.IconInfo icon_info = icon_theme.lookup_icon( + icon_name, size, Gtk.IconLookupFlags.FORCE_SIZE); + + if (icon_info != null) + gicon = new GLib.FileIcon(GLib.File.new_for_path(icon_info.get_filename())); + } else { + gicon = new GLib.ThemedIcon(icon_name); + } + } + else { + Pixmap[] pixmaps = proxy.Status == Status.NEEDS_ATTENTION + ? proxy.AttentionIconPixmap + : proxy.IconPixmap; + gicon = pixmap_to_pixbuf(pixmaps); + } + } + + + private void refresh_all_properties() { + proxy.g_connection.call.begin( + proxy.g_name, + proxy.g_object_path, + "org.freedesktop.DBus.Properties", + "GetAll", + new Variant("(s)", proxy.g_interface_name), + new VariantType("(a{sv})"), + DBusCallFlags.NONE, + -1, + null, + (_, result) => { + try { + Variant parameters = proxy.g_connection.call.end(result); + VariantIter prop_iter; + parameters.get("(a{sv})", out prop_iter); + + string prop_key; + Variant prop_value; + + while (prop_iter.next ("{sv}", out prop_key, out prop_value)) { + proxy.set_cached_property(prop_key, prop_value); + } + + update_gicon(); + + _notify(); + } catch(Error e) { + //silently ignore + } + } + ); + } + + public void activate(int x, int y) { + try { + proxy.Activate(x, y); + } catch (Error e) { + if(e.domain != DBusError.quark() || e.code != DBusError.UNKNOWN_METHOD) + warning(e.message); + } + } + + public void secondary_activate(int x, int y) { + try { + proxy.SecondaryActivate(x, y); + } catch (Error e) { + if(e.domain != DBusError.quark() || e.code != DBusError.UNKNOWN_METHOD) + warning(e.message); + } + } + + public void scroll(int delta, string orientation) { + try { + proxy.Scroll(delta, orientation); + } catch (Error e) { + if(e.domain != DBusError.quark() || e.code != DBusError.UNKNOWN_METHOD) + warning("%s\n", e.message); + } + } + + + public DbusmenuGtk.Menu? create_menu() { + if (proxy.Menu == null) + return null; + + return new DbusmenuGtk.Menu( + proxy.get_name_owner(), + proxy.Menu); + } + + public Gdk.Pixbuf? _get_icon_pixbuf() { + Pixmap[] pixmaps = proxy.Status == Status.NEEDS_ATTENTION + ? proxy.AttentionIconPixmap + : proxy.IconPixmap; + + + string icon_name = proxy.Status == Status.NEEDS_ATTENTION + ? proxy.AttentionIconName + : proxy.IconName; + + Gdk.Pixbuf pixbuf = null; + + if (icon_name != null && proxy.IconThemePath != null) + pixbuf = load_from_theme(icon_name, proxy.IconThemePath); + + if (pixbuf == null) + pixbuf = pixmap_to_pixbuf(pixmaps); + + return pixbuf; + } + + private Gdk.Pixbuf? load_from_theme(string icon_name, string theme_path) { + if (theme_path == "" || theme_path == null) + return null; + + if (icon_name == "" || icon_name == null) + return null; + + Gtk.IconTheme icon_theme = new Gtk.IconTheme(); + string[] paths = {theme_path}; + icon_theme.set_search_path(paths); + + int size = icon_theme.get_icon_sizes(icon_name)[0]; + Gtk.IconInfo icon_info = icon_theme.lookup_icon( + icon_name, size, Gtk.IconLookupFlags.FORCE_SIZE); + + if (icon_info != null) + return icon_info.load_icon(); + + return null; + } + + private Gdk.Pixbuf? pixmap_to_pixbuf(Pixmap[] pixmaps) { + if (pixmaps == null || pixmaps.length == 0) + return null; + + Pixmap pixmap = pixmaps[0]; + uint8[] image_data = pixmap.bytes.copy(); + + for (int i = 0; i < pixmap.width * pixmap.height * 4; i += 4) { + uint8 alpha = image_data[i]; + image_data[i] = image_data[i + 1]; + image_data[i + 1] = image_data[i + 2]; + image_data[i + 2] = image_data[i + 3]; + image_data[i + 3] = alpha; + } + + return new Gdk.Pixbuf.from_bytes( + new Bytes(image_data), + Gdk.Colorspace.RGB, + true, + 8, + (int)pixmap.width, + (int)pixmap.height, + (int)(pixmap.width * 4) + ); + } + + public string to_json_string() { + var generator = new Json.Generator(); + generator.set_root(to_json()); + return generator.to_data(null); + } + + internal Json.Node to_json() { + return new Json.Builder() + .begin_object() + .set_member_name("item_id").add_string_value(item_id) + .set_member_name("id").add_string_value(id) + .set_member_name("bus_name").add_string_value(proxy.g_name) + .set_member_name("object_path").add_string_value(proxy.g_object_path) + .set_member_name("title").add_string_value(title) + .set_member_name("status").add_string_value(status.to_nick()) + .set_member_name("category").add_string_value(category.to_nick()) + .set_member_name("tooltip").add_string_value(tooltip_markup) + .set_member_name("icon_theme_path").add_string_value(proxy.IconThemePath) + .set_member_name("icon_name").add_string_value(icon_name) + .set_member_name("menu_path").add_string_value(proxy.Menu) + .set_member_name("is_menu").add_boolean_value(is_menu) + .end_object() + .get_root(); + } +} +} diff --git a/lib/tray/version b/lib/tray/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/tray/version @@ -0,0 +1 @@ +0.1.0 diff --git a/lib/tray/watcher.vala b/lib/tray/watcher.vala new file mode 100644 index 0000000..974cd02 --- /dev/null +++ b/lib/tray/watcher.vala @@ -0,0 +1,59 @@ +namespace AstalTray { +[DBus (name="org.kde.StatusNotifierWatcher")] +internal class StatusNotifierWatcher : Object { + private HashTable _items = + new HashTable(str_hash, str_equal); + + public string[] RegisteredStatusNotifierItems { owned get { return _items.get_values_as_ptr_array().data; } } + public bool IsStatusNotifierHostRegistered { get; default = true; } + public int ProtocolVersion { get; default = 0; } + + public signal void StatusNotifierItemRegistered(string service); + public signal void StatusNotifierItemUnregistered(string service); + public signal void StatusNotifierHostRegistered(); + public signal void StatusNotifierHostUnregistered(); + + public void RegisterStatusNotifierItem(string service, BusName sender) throws DBusError, IOError { + string busName; + string path; + if (service[0] == '/') { + path = service; + busName = sender; + } else { + busName = service; + path = "/StatusNotifierItem"; + } + + Bus.get_sync(BusType.SESSION).signal_subscribe( + null, + "org.freedesktop.DBus", + "NameOwnerChanged", + null, + null, + DBusSignalFlags.NONE, + (connection, sender_name, path, interface_name, signal_name, parameters) => { + string name = null; + string new_owner = null; + string old_owner = null; + parameters.get("(sss)", &name, &old_owner, &new_owner); + if (new_owner == "" && _items.contains(old_owner)) { + string full_path = _items.take(old_owner); + StatusNotifierItemUnregistered(full_path); + } + } + ); + + _items.set(busName, busName+path); + StatusNotifierItemRegistered(busName+path); + } + + public void RegisterStatusNotifierHost(string service) throws DBusError, IOError { + /* NOTE: + usually the watcher should keep track of registered host + but some tray applications do net register their trayitem properly + when hosts register/deregister. This is fixed by setting isHostRegistered + always to true, this also make host handling logic unneccessary. + */ + } +} +} diff --git a/lib/wireplumber/flake.nix b/lib/wireplumber/flake.nix new file mode 100644 index 0000000..96ffc6f --- /dev/null +++ b/lib/wireplumber/flake.nix @@ -0,0 +1,54 @@ +{ + description = "Wrapper library for WirePlumber"; + + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + + outputs = { + self, + nixpkgs, + }: let + version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); + system = "x86_64-linux"; + pkgs = import nixpkgs {inherit system;}; + + nativeBuildInputs = with pkgs; [ + gobject-introspection + meson + pkg-config + ninja + vala + ]; + + buildInputs = with pkgs; [ + glib + wireplumber + # json-glib + ]; + in { + packages.${system} = rec { + default = wireplumber; + wireplumber = pkgs.stdenv.mkDerivation { + inherit nativeBuildInputs buildInputs; + pname = "astal-wireplumber"; + version = version; + src = ./.; + outputs = ["out" "dev"]; + }; + }; + + devShells.${system} = { + default = pkgs.mkShell { + inherit nativeBuildInputs buildInputs; + }; + wireplumber = pkgs.mkShell { + inherit nativeBuildInputs; + buildInputs = + buildInputs + ++ [ + self.packages.${system}.default + pkgs.gjs + ]; + }; + }; + }; +} diff --git a/lib/wireplumber/include/astal-wp.h b/lib/wireplumber/include/astal-wp.h new file mode 100644 index 0000000..6c48211 --- /dev/null +++ b/lib/wireplumber/include/astal-wp.h @@ -0,0 +1,4 @@ + +#include "astal/wireplumber/audio.h" +#include "astal/wireplumber/endpoint.h" +#include "astal/wireplumber/wp.h" diff --git a/lib/wireplumber/include/astal/wireplumber/audio.h b/lib/wireplumber/include/astal/wireplumber/audio.h new file mode 100644 index 0000000..c1176e2 --- /dev/null +++ b/lib/wireplumber/include/astal/wireplumber/audio.h @@ -0,0 +1,33 @@ +#ifndef ASTAL_WIREPLUMBER_AUDIO_H +#define ASTAL_WIREPLUMBER_AUDIO_H + +#include + +#include "device.h" +#include "endpoint.h" + +G_BEGIN_DECLS + +#define ASTAL_WP_TYPE_AUDIO (astal_wp_audio_get_type()) + +G_DECLARE_FINAL_TYPE(AstalWpAudio, astal_wp_audio, ASTAL_WP, AUDIO, GObject) + +AstalWpEndpoint *astal_wp_audio_get_speaker(AstalWpAudio *self, guint id); +AstalWpEndpoint *astal_wp_audio_get_microphone(AstalWpAudio *self, guint id); +AstalWpEndpoint *astal_wp_audio_get_recorder(AstalWpAudio *self, guint id); +AstalWpEndpoint *astal_wp_audio_get_stream(AstalWpAudio *self, guint id); +AstalWpEndpoint *astal_wp_audio_get_endpoint(AstalWpAudio *self, guint id); +AstalWpDevice *astal_wp_audio_get_device(AstalWpAudio *self, guint id); + +AstalWpEndpoint *astal_wp_audio_get_default_speaker(AstalWpAudio *self); +AstalWpEndpoint *astal_wp_audio_get_default_microphone(AstalWpAudio *self); + +GList *astal_wp_audio_get_microphones(AstalWpAudio *self); +GList *astal_wp_audio_get_speakers(AstalWpAudio *self); +GList *astal_wp_audio_get_recorders(AstalWpAudio *self); +GList *astal_wp_audio_get_streams(AstalWpAudio *self); +GList *astal_wp_audio_get_devices(AstalWpAudio *self); + +G_END_DECLS + +#endif // !ASTAL_WIREPLUMBER_AUDIO_H diff --git a/lib/wireplumber/include/astal/wireplumber/device.h b/lib/wireplumber/include/astal/wireplumber/device.h new file mode 100644 index 0000000..9f633e3 --- /dev/null +++ b/lib/wireplumber/include/astal/wireplumber/device.h @@ -0,0 +1,29 @@ +#ifndef ASTAL_WP_DEVICE_H +#define ASTAL_WP_DEVICE_H + +#include + +#include "profile.h" + +G_BEGIN_DECLS + +#define ASTAL_WP_TYPE_DEVICE (astal_wp_device_get_type()) + +G_DECLARE_FINAL_TYPE(AstalWpDevice, astal_wp_device, ASTAL_WP, DEVICE, GObject) + +#define ASTAL_WP_TYPE_DEVICE_TYPE (astal_wp_device_type_get_type()) + +typedef enum { ASTAL_WP_DEVICE_TYPE_AUDIO, ASTAL_WP_DEVICE_TYPE_VIDEO } AstalWpDeviceType; + +guint astal_wp_device_get_id(AstalWpDevice *self); +const gchar *astal_wp_device_get_description(AstalWpDevice *self); +const gchar *astal_wp_device_get_icon(AstalWpDevice *self); +AstalWpProfile *astal_wp_device_get_profile(AstalWpDevice *self, gint id); +GList *astal_wp_device_get_profiles(AstalWpDevice *self); +void astal_wp_device_set_active_profile(AstalWpDevice *self, int profile_id); +gint astal_wp_device_get_active_profile(AstalWpDevice *self); +AstalWpDeviceType astal_wp_device_get_device_type(AstalWpDevice *self); + +G_END_DECLS + +#endif // !ASTAL_WP_DEVICE_H diff --git a/lib/wireplumber/include/astal/wireplumber/endpoint.h b/lib/wireplumber/include/astal/wireplumber/endpoint.h new file mode 100644 index 0000000..6ef0329 --- /dev/null +++ b/lib/wireplumber/include/astal/wireplumber/endpoint.h @@ -0,0 +1,43 @@ +#ifndef ASTAL_WP_ENDPOINT_H +#define ASTAL_WP_ENDPOINT_H + +#include + +G_BEGIN_DECLS + +#define ASTAL_WP_TYPE_ENDPOINT (astal_wp_endpoint_get_type()) + +G_DECLARE_FINAL_TYPE(AstalWpEndpoint, astal_wp_endpoint, ASTAL_WP, ENDPOINT, GObject) + +#define ASTAL_WP_TYPE_MEDIA_CLASS (astal_wp_media_class_get_type()) + +typedef enum { + ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE, + ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER, + ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER, + ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM, + ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE, + ASTAL_WP_MEDIA_CLASS_VIDEO_SINK, + ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER, + ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM, +} AstalWpMediaClass; + +void astal_wp_endpoint_set_volume(AstalWpEndpoint *self, gdouble volume); +void astal_wp_endpoint_set_mute(AstalWpEndpoint *self, gboolean mute); +gboolean astal_wp_endpoint_get_is_default(AstalWpEndpoint *self); +void astal_wp_endpoint_set_is_default(AstalWpEndpoint *self, gboolean is_default); +gboolean astal_wp_endpoint_get_lock_channels(AstalWpEndpoint *self); +void astal_wp_endpoint_set_lock_channels(AstalWpEndpoint *self, gboolean lock_channels); + +AstalWpMediaClass astal_wp_endpoint_get_media_class(AstalWpEndpoint *self); +guint astal_wp_endpoint_get_id(AstalWpEndpoint *self); +gboolean astal_wp_endpoint_get_mute(AstalWpEndpoint *self); +gdouble astal_wp_endpoint_get_volume(AstalWpEndpoint *self); +const gchar *astal_wp_endpoint_get_description(AstalWpEndpoint *self); +const gchar *astal_wp_endpoint_get_name(AstalWpEndpoint *self); +const gchar *astal_wp_endpoint_get_icon(AstalWpEndpoint *self); +const gchar *astal_wp_endpoint_get_volume_icon(AstalWpEndpoint *self); + +G_END_DECLS + +#endif // !ASTAL_WP_ENDPOINT_H diff --git a/lib/wireplumber/include/astal/wireplumber/meson.build b/lib/wireplumber/include/astal/wireplumber/meson.build new file mode 100644 index 0000000..c805ea2 --- /dev/null +++ b/lib/wireplumber/include/astal/wireplumber/meson.build @@ -0,0 +1,10 @@ +astal_wireplumber_subheaders = files( + 'audio.h', + 'device.h', + 'endpoint.h', + 'profile.h', + 'video.h', + 'wp.h', +) + +install_headers(astal_wireplumber_subheaders, subdir: 'astal/wireplumber') diff --git a/lib/wireplumber/include/astal/wireplumber/profile.h b/lib/wireplumber/include/astal/wireplumber/profile.h new file mode 100644 index 0000000..2ec768e --- /dev/null +++ b/lib/wireplumber/include/astal/wireplumber/profile.h @@ -0,0 +1,17 @@ +#ifndef ASTAL_WP_PROFILE_H +#define ASTAL_WP_PROFILE_H + +#include + +G_BEGIN_DECLS + +#define ASTAL_WP_TYPE_PROFILE (astal_wp_profile_get_type()) + +G_DECLARE_FINAL_TYPE(AstalWpProfile, astal_wp_profile, ASTAL_WP, PROFILE, GObject) + +gint astal_wp_profile_get_index(AstalWpProfile *self); +const gchar *astal_wp_profile_get_description(AstalWpProfile *self); + +G_END_DECLS + +#endif // !ASTAL_WP_PROFILE_H diff --git a/lib/wireplumber/include/astal/wireplumber/video.h b/lib/wireplumber/include/astal/wireplumber/video.h new file mode 100644 index 0000000..3c4ae74 --- /dev/null +++ b/lib/wireplumber/include/astal/wireplumber/video.h @@ -0,0 +1,29 @@ +#ifndef ASTAL_WIREPLUMBER_VIDEO_H +#define ASTAL_WIREPLUMBER_VIDEO_H + +#include + +#include "device.h" +#include "endpoint.h" + +G_BEGIN_DECLS + +#define ASTAL_WP_TYPE_VIDEO (astal_wp_video_get_type()) + +G_DECLARE_FINAL_TYPE(AstalWpVideo, astal_wp_video, ASTAL_WP, VIDEO, GObject) + +AstalWpEndpoint *astal_wp_video_get_source(AstalWpVideo *self, guint id); +AstalWpEndpoint *astal_wp_video_get_sink(AstalWpVideo *self, guint id); +AstalWpEndpoint *astal_wp_video_get_recorder(AstalWpVideo *self, guint id); +AstalWpEndpoint *astal_wp_video_get_stream(AstalWpVideo *self, guint id); +AstalWpDevice *astal_wp_video_get_device(AstalWpVideo *self, guint id); + +GList *astal_wp_video_get_sources(AstalWpVideo *self); +GList *astal_wp_video_get_sinks(AstalWpVideo *self); +GList *astal_wp_video_get_recorders(AstalWpVideo *self); +GList *astal_wp_video_get_streams(AstalWpVideo *self); +GList *astal_wp_video_get_devices(AstalWpVideo *self); + +G_END_DECLS + +#endif // !ASTAL_WIREPLUMBER_VIDEO_H diff --git a/lib/wireplumber/include/astal/wireplumber/wp.h b/lib/wireplumber/include/astal/wireplumber/wp.h new file mode 100644 index 0000000..1ff341c --- /dev/null +++ b/lib/wireplumber/include/astal/wireplumber/wp.h @@ -0,0 +1,47 @@ +#ifndef ASTAL_WIREPLUMBER_H +#define ASTAL_WIREPLUMBER_H + +#include + +#include "audio.h" +#include "device.h" +#include "endpoint.h" +#include "video.h" + +G_BEGIN_DECLS + +#define ASTAL_WP_TYPE_SCALE (astal_wp_scale_get_type()) + +typedef enum { + ASTAL_WP_SCALE_LINEAR, + ASTAL_WP_SCALE_CUBIC, +} AstalWpScale; + +#define ASTAL_WP_TYPE_WP (astal_wp_wp_get_type()) + +G_DECLARE_FINAL_TYPE(AstalWpWp, astal_wp_wp, ASTAL_WP, WP, GObject) + +AstalWpWp* astal_wp_wp_get_default(); +AstalWpWp* astal_wp_get_default_wp(); + +AstalWpAudio* astal_wp_wp_get_audio(AstalWpWp* self); +AstalWpVideo* astal_wp_wp_get_video(AstalWpWp* self); + +AstalWpEndpoint* astal_wp_wp_get_endpoint(AstalWpWp* self, guint id); +GList* astal_wp_wp_get_endpoints(AstalWpWp* self); + +AstalWpDevice* astal_wp_wp_get_device(AstalWpWp* self, guint id); +GList* astal_wp_wp_get_devices(AstalWpWp* self); + +AstalWpEndpoint* astal_wp_wp_get_default_speaker(AstalWpWp* self); +AstalWpEndpoint* astal_wp_wp_get_default_microphone(AstalWpWp* self); + +AstalWpScale astal_wp_wp_get_scale(AstalWpWp* self); +void astal_wp_wp_set_scale(AstalWpWp* self, AstalWpScale scale); + +AstalWpVideo* astal_wp_video_new(AstalWpWp* wp); +AstalWpAudio* astal_wp_audio_new(AstalWpWp* wp); + +G_END_DECLS + +#endif // !ASTAL_WIREPLUMBER_H diff --git a/lib/wireplumber/include/meson.build b/lib/wireplumber/include/meson.build new file mode 100644 index 0000000..afe00eb --- /dev/null +++ b/lib/wireplumber/include/meson.build @@ -0,0 +1,8 @@ +astal_wireplumber_inc = include_directories('.', 'astal/wireplumber', 'private') +astal_wireplumber_headers = files( + 'astal-wp.h', +) + +install_headers(astal_wireplumber_headers) + +subdir('astal/wireplumber') diff --git a/lib/wireplumber/include/private/device-private.h b/lib/wireplumber/include/private/device-private.h new file mode 100644 index 0000000..e98a7f7 --- /dev/null +++ b/lib/wireplumber/include/private/device-private.h @@ -0,0 +1,15 @@ +#ifndef ASTAL_WP_DEVICE_PRIVATE_H +#define ASTAL_WP_DEVICE_PRIVATE_H + +#include +#include + +#include "device.h" + +G_BEGIN_DECLS + +AstalWpDevice *astal_wp_device_create(WpDevice *device); + +G_END_DECLS + +#endif // !ASTAL_WP_DEVICE_PRIATE_H diff --git a/lib/wireplumber/include/private/endpoint-private.h b/lib/wireplumber/include/private/endpoint-private.h new file mode 100644 index 0000000..7431c78 --- /dev/null +++ b/lib/wireplumber/include/private/endpoint-private.h @@ -0,0 +1,22 @@ +#ifndef ASTAL_WP_ENDPOINT_PRIV_H +#define ASTAL_WP_ENDPOINT_PRIV_H + +#include +#include + +#include "endpoint.h" +#include "wp.h" + +G_BEGIN_DECLS + +AstalWpEndpoint *astal_wp_endpoint_create(WpNode *node, WpPlugin *mixer, WpPlugin *defaults, + AstalWpWp *wp); +AstalWpEndpoint *astal_wp_endpoint_init_as_default(AstalWpEndpoint *self, WpPlugin *mixer, + WpPlugin *defaults, AstalWpMediaClass type, + AstalWpWp *wp); +void astal_wp_endpoint_update_default(AstalWpEndpoint *self, gboolean is_default); +void astal_wp_endpoint_update_volume(AstalWpEndpoint *self); + +G_END_DECLS + +#endif // !ASTAL_WP_ENDPOINT_PRIV_H diff --git a/lib/wireplumber/meson.build b/lib/wireplumber/meson.build new file mode 100644 index 0000000..0d2141f --- /dev/null +++ b/lib/wireplumber/meson.build @@ -0,0 +1,17 @@ +project( + 'astal_wireplumber', + 'c', + version: '0.1.0', + 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') + +subdir('include') +subdir('src') diff --git a/lib/wireplumber/meson_options.txt b/lib/wireplumber/meson_options.txt new file mode 100644 index 0000000..97aa4e7 --- /dev/null +++ b/lib/wireplumber/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/wireplumber/src/audio.c b/lib/wireplumber/src/audio.c new file mode 100644 index 0000000..15582d7 --- /dev/null +++ b/lib/wireplumber/src/audio.c @@ -0,0 +1,503 @@ +#include "audio.h" + +#include + +#include "device.h" +#include "endpoint.h" +#include "glib-object.h" +#include "wp.h" + +struct _AstalWpAudio { + GObject parent_instance; +}; + +typedef struct { + AstalWpWp *wp; +} AstalWpAudioPrivate; + +G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpAudio, astal_wp_audio, G_TYPE_OBJECT); + +typedef enum { + ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_ADDED, + ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_REMOVED, + ASTAL_WP_AUDIO_SIGNAL_SPEAKER_ADDED, + ASTAL_WP_AUDIO_SIGNAL_SPEAKER_REMOVED, + ASTAL_WP_AUDIO_SIGNAL_STREAM_ADDED, + ASTAL_WP_AUDIO_SIGNAL_STREAM_REMOVED, + ASTAL_WP_AUDIO_SIGNAL_RECORDER_ADDED, + ASTAL_WP_AUDIO_SIGNAL_RECORDER_REMOVED, + ASTAL_WP_AUDIO_SIGNAL_DEVICE_ADDED, + ASTAL_WP_AUDIO_SIGNAL_DEVICE_REMOVED, + ASTAL_WP_AUDIO_N_SIGNALS +} AstalWpWpSignals; + +static guint astal_wp_audio_signals[ASTAL_WP_AUDIO_N_SIGNALS] = { + 0, +}; + +typedef enum { + ASTAL_WP_AUDIO_PROP_MICROPHONES = 1, + ASTAL_WP_AUDIO_PROP_SPEAKERS, + ASTAL_WP_AUDIO_PROP_STREAMS, + ASTAL_WP_AUDIO_PROP_RECORDERS, + ASTAL_WP_AUDIO_PROP_DEVICES, + ASTAL_WP_AUDIO_PROP_DEFAULT_SPEAKER, + ASTAL_WP_AUDIO_PROP_DEFAULT_MICROPHONE, + ASTAL_WP_AUDIO_N_PROPERTIES, +} AstalWpAudioProperties; + +static GParamSpec *astal_wp_audio_properties[ASTAL_WP_AUDIO_N_PROPERTIES] = { + NULL, +}; + +/** + * astal_wp_audio_get_speaker: + * @self: the AstalWpAudio object + * @id: the id of the endpoint + * + * gets the speaker with the given id + * + * Returns: (transfer none) (nullable) + */ +AstalWpEndpoint *astal_wp_audio_get_speaker(AstalWpAudio *self, guint id) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + + AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); + if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER) + return endpoint; + return NULL; +} + +/** + * astal_wp_audio_get_microphone: + * @self: the AstalWpAudio object + * @id: the id of the endpoint + * + * gets the microphone with the given id + * + * Returns: (transfer none) (nullable) + */ +AstalWpEndpoint *astal_wp_audio_get_microphone(AstalWpAudio *self, guint id) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + + AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); + if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE) + return endpoint; + return NULL; +} + +/** + * astal_wp_audio_get_recorder: + * @self: the AstalWpAudio object + * @id: the id of the endpoint + * + * gets the recorder with the given id + * + * Returns: (transfer none) (nullable) + */ +AstalWpEndpoint *astal_wp_audio_get_recorder(AstalWpAudio *self, guint id) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + + AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); + if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER) + return endpoint; + return NULL; +} + +/** + * astal_wp_audio_get_stream: + * @self: the AstalWpAudio object + * @id: the id of the endpoint + * + * gets the stream with the given id + * + * Returns: (transfer none) (nullable) + */ +AstalWpEndpoint *astal_wp_audio_get_stream(AstalWpAudio *self, guint id) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + + AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); + if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM) + return endpoint; + return NULL; +} + +/** + * astal_wp_audio_get_device: + * @self: the AstalWpAudio object + * @id: the id of the device + * + * gets the device with the given id + * + * Returns: (transfer none) (nullable) + */ +AstalWpDevice *astal_wp_audio_get_device(AstalWpAudio *self, guint id) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + + return astal_wp_wp_get_device(priv->wp, id); +} + +/** + * astal_wp_audio_get_microphones: + * @self: the AstalWpAudio object + * + * a GList containing the microphones + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) + */ +GList *astal_wp_audio_get_microphones(AstalWpAudio *self) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + GList *eps = astal_wp_wp_get_endpoints(priv->wp); + GList *mics = NULL; + + for (GList *l = eps; l != NULL; l = l->next) { + if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE) { + mics = g_list_append(mics, l->data); + } + } + g_list_free(eps); + return mics; +} + +/** + * astal_wp_audio_get_speakers: + * @self: the AstalWpAudio object + * + * a GList containing the speakers + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) + */ +GList *astal_wp_audio_get_speakers(AstalWpAudio *self) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + GList *eps = astal_wp_wp_get_endpoints(priv->wp); + GList *speakers = NULL; + + for (GList *l = eps; l != NULL; l = l->next) { + if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER) { + speakers = g_list_append(speakers, l->data); + } + } + g_list_free(eps); + return speakers; +} + +/** + * astal_wp_audio_get_recorders: + * @self: the AstalWpAudio object + * + * a GList containing the recorders + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) + */ +GList *astal_wp_audio_get_recorders(AstalWpAudio *self) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + GList *eps = astal_wp_wp_get_endpoints(priv->wp); + GList *recorders = NULL; + + for (GList *l = eps; l != NULL; l = l->next) { + if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER) { + recorders = g_list_append(recorders, l->data); + } + } + g_list_free(eps); + return recorders; +} + +/** + * astal_wp_audio_get_streams: + * @self: the AstalWpAudio object + * + * a GList containing the streams + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) + */ +GList *astal_wp_audio_get_streams(AstalWpAudio *self) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + GList *eps = astal_wp_wp_get_endpoints(priv->wp); + GList *streams = NULL; + + for (GList *l = eps; l != NULL; l = l->next) { + if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM) { + streams = g_list_append(streams, l->data); + } + } + g_list_free(eps); + return streams; +} + +/** + * astal_wp_audio_get_devices: + * @self: the AstalWpAudio object + * + * a GList containing the devices + * + * Returns: (transfer container) (nullable) (type GList(AstalWpDevice)) + */ +GList *astal_wp_audio_get_devices(AstalWpAudio *self) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + GList *eps = astal_wp_wp_get_devices(priv->wp); + GList *list = NULL; + + for (GList *l = eps; l != NULL; l = l->next) { + if (astal_wp_device_get_device_type(l->data) == ASTAL_WP_DEVICE_TYPE_AUDIO) { + list = g_list_append(list, l->data); + } + } + g_list_free(eps); + return list; +} + +/** + * astal_wp_audio_get_endpoint: + * @self: the AstalWpAudio object + * @id: the id of the endpoint + * + * the endpoint with the given id + * + * Returns: (transfer none) (nullable) + */ +AstalWpEndpoint *astal_wp_audio_get_endpoint(AstalWpAudio *self, guint id) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + + AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); + return endpoint; +} + +/** + * astal_wp_audio_get_default_speaker + * + * gets the default speaker object + * + * Returns: (nullable) (transfer none) + */ +AstalWpEndpoint *astal_wp_audio_get_default_speaker(AstalWpAudio *self) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + return astal_wp_wp_get_default_speaker(priv->wp); +} + +/** + * astal_wp_audio_get_default_microphone + * + * gets the default microphone object + * + * Returns: (nullable) (transfer none) + */ +AstalWpEndpoint *astal_wp_audio_get_default_microphone(AstalWpAudio *self) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + return astal_wp_wp_get_default_microphone(priv->wp); +} + +static void astal_wp_audio_get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *pspec) { + AstalWpAudio *self = ASTAL_WP_AUDIO(object); + + switch (property_id) { + case ASTAL_WP_AUDIO_PROP_MICROPHONES: + g_value_set_pointer(value, astal_wp_audio_get_microphones(self)); + break; + case ASTAL_WP_AUDIO_PROP_SPEAKERS: + g_value_set_pointer(value, astal_wp_audio_get_speakers(self)); + break; + case ASTAL_WP_AUDIO_PROP_STREAMS: + g_value_set_pointer(value, astal_wp_audio_get_streams(self)); + break; + case ASTAL_WP_AUDIO_PROP_RECORDERS: + g_value_set_pointer(value, astal_wp_audio_get_recorders(self)); + break; + case ASTAL_WP_AUDIO_PROP_DEFAULT_SPEAKER: + g_value_set_object(value, astal_wp_audio_get_default_speaker(self)); + break; + case ASTAL_WP_AUDIO_PROP_DEVICES: + g_value_set_pointer(value, astal_wp_audio_get_devices(self)); + break; + case ASTAL_WP_AUDIO_PROP_DEFAULT_MICROPHONE: + g_value_set_object(value, astal_wp_audio_get_default_microphone(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_wp_audio_device_added(AstalWpAudio *self, gpointer object) { + AstalWpDevice *device = ASTAL_WP_DEVICE(object); + if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_AUDIO) { + g_signal_emit_by_name(self, "device-added", device); + g_object_notify(G_OBJECT(self), "devices"); + } +} + +static void astal_wp_audio_device_removed(AstalWpAudio *self, gpointer object) { + AstalWpDevice *device = ASTAL_WP_DEVICE(object); + if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_AUDIO) { + g_signal_emit_by_name(self, "device-removed", device); + g_object_notify(G_OBJECT(self), "devices"); + } +} + +static void astal_wp_audio_object_added(AstalWpAudio *self, gpointer object) { + AstalWpEndpoint *endpoint = ASTAL_WP_ENDPOINT(object); + switch (astal_wp_endpoint_get_media_class(endpoint)) { + case ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE: + g_signal_emit_by_name(self, "microphone-added", endpoint); + g_object_notify(G_OBJECT(self), "microphones"); + break; + case ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER: + g_signal_emit_by_name(self, "speaker-added", endpoint); + g_object_notify(G_OBJECT(self), "speakers"); + break; + case ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM: + g_signal_emit_by_name(self, "stream-added", endpoint); + g_object_notify(G_OBJECT(self), "streams"); + break; + case ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER: + g_signal_emit_by_name(self, "recorder-added", endpoint); + g_object_notify(G_OBJECT(self), "recorders"); + break; + default: + break; + } +} + +static void astal_wp_audio_object_removed(AstalWpAudio *self, gpointer object) { + AstalWpEndpoint *endpoint = ASTAL_WP_ENDPOINT(object); + switch (astal_wp_endpoint_get_media_class(endpoint)) { + case ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE: + g_signal_emit_by_name(self, "microphone-removed", endpoint); + g_object_notify(G_OBJECT(self), "microphones"); + break; + case ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER: + g_signal_emit_by_name(self, "speaker-removed", endpoint); + g_object_notify(G_OBJECT(self), "speakers"); + break; + case ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM: + g_signal_emit_by_name(self, "stream-removed", endpoint); + g_object_notify(G_OBJECT(self), "streams"); + break; + case ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER: + g_signal_emit_by_name(self, "recorder-removed", endpoint); + g_object_notify(G_OBJECT(self), "recorders"); + break; + default: + break; + } +} + +AstalWpAudio *astal_wp_audio_new(AstalWpWp *wp) { + AstalWpAudio *self = g_object_new(ASTAL_WP_TYPE_AUDIO, NULL); + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + priv->wp = g_object_ref(wp); + + g_signal_connect_swapped(priv->wp, "endpoint-added", G_CALLBACK(astal_wp_audio_object_added), + self); + g_signal_connect_swapped(priv->wp, "endpoint-removed", + G_CALLBACK(astal_wp_audio_object_removed), self); + g_signal_connect_swapped(priv->wp, "device-added", G_CALLBACK(astal_wp_audio_device_added), + self); + g_signal_connect_swapped(priv->wp, "device-removed", G_CALLBACK(astal_wp_audio_device_removed), + self); + + return self; +} + +static void astal_wp_audio_dispose(GObject *object) { + AstalWpAudio *self = ASTAL_WP_AUDIO(object); + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + g_clear_object(&priv->wp); +} + +static void astal_wp_audio_init(AstalWpAudio *self) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); +} + +static void astal_wp_audio_class_init(AstalWpAudioClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + object_class->get_property = astal_wp_audio_get_property; + object_class->dispose = astal_wp_audio_dispose; + + /** + * AstalWpAudio:microphones: (type GList(AstalWpEndpoint)) (transfer container) + * + * A list of AstalWpEndpoint objects + */ + astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_MICROPHONES] = + g_param_spec_pointer("microphones", "microphones", "microphones", G_PARAM_READABLE); + /** + * AstalWpAudio:speakers: (type GList(AstalWpEndpoint)) (transfer container) + * + * A list of AstalWpEndpoint objects + */ + astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_SPEAKERS] = + g_param_spec_pointer("speakers", "speakers", "speakers", G_PARAM_READABLE); + /** + * AstalWpAudio:recorders: (type GList(AstalWpEndpoint)) (transfer container) + * + * A list of AstalWpEndpoint objects + */ + astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_RECORDERS] = + g_param_spec_pointer("recorders", "recorders", "recorders", G_PARAM_READABLE); + /** + * AstalWpAudio:streams: (type GList(AstalWpEndpoint)) (transfer container) + * + * A list of AstalWpEndpoint objects + */ + astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_STREAMS] = + g_param_spec_pointer("streams", "streams", "streams", G_PARAM_READABLE); + /** + * AstalWpAudio:devices: (type GList(AstalWpDevice)) (transfer container) + * + * A list of AstalWpEndpoint objects + */ + astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_DEVICES] = + g_param_spec_pointer("devices", "devices", "devices", G_PARAM_READABLE); + /** + * AstalWpAudio:default-speaker: + * + * The AstalWndpoint object representing the default speaker + */ + astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_DEFAULT_SPEAKER] = + g_param_spec_object("default-speaker", "default-speaker", "default-speaker", + ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); + /** + * AstalWpAudio:default-microphone: + * + * The AstalWndpoint object representing the default speaker + */ + astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_DEFAULT_MICROPHONE] = + g_param_spec_object("default-microphone", "default-microphone", "default-microphone", + ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); + + g_object_class_install_properties(object_class, ASTAL_WP_AUDIO_N_PROPERTIES, + astal_wp_audio_properties); + + astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_ADDED] = + g_signal_new("microphone-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_REMOVED] = + g_signal_new("microphone-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_SPEAKER_ADDED] = + g_signal_new("speaker-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_SPEAKER_REMOVED] = + g_signal_new("speaker-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_STREAM_ADDED] = + g_signal_new("stream-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_STREAM_REMOVED] = + g_signal_new("stream-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_RECORDER_ADDED] = + g_signal_new("recorder-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_RECORDER_REMOVED] = + g_signal_new("recorder-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_DEVICE_ADDED] = + g_signal_new("device-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); + astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_REMOVED] = + g_signal_new("device-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); +} diff --git a/lib/wireplumber/src/device.c b/lib/wireplumber/src/device.c new file mode 100644 index 0000000..af0760c --- /dev/null +++ b/lib/wireplumber/src/device.c @@ -0,0 +1,371 @@ +#include + +#include "device-private.h" +#include "profile.h" + +struct _AstalWpDevice { + GObject parent_instance; + + guint id; + gchar *description; + gchar *icon; + gint active_profile; + AstalWpDeviceType type; +}; + +typedef struct { + WpDevice *device; + GHashTable *profiles; +} AstalWpDevicePrivate; + +G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpDevice, astal_wp_device, G_TYPE_OBJECT); + +G_DEFINE_ENUM_TYPE(AstalWpDeviceType, astal_wp_device_type, + G_DEFINE_ENUM_VALUE(ASTAL_WP_DEVICE_TYPE_AUDIO, "Audio/Device"), + G_DEFINE_ENUM_VALUE(ASTAL_WP_DEVICE_TYPE_VIDEO, "Video/Device")); + +typedef enum { + ASTAL_WP_DEVICE_PROP_ID = 1, + ASTAL_WP_DEVICE_PROP_DESCRIPTION, + ASTAL_WP_DEVICE_PROP_ICON, + ASTAL_WP_DEVICE_PROP_PROFILES, + ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE, + ASTAL_WP_DEVICE_PROP_DEVICE_TYPE, + ASTAL_WP_DEVICE_N_PROPERTIES, +} AstalWpDeviceProperties; + +static GParamSpec *astal_wp_device_properties[ASTAL_WP_DEVICE_N_PROPERTIES] = { + NULL, +}; + +/** + * astal_wp_device_get_id + * @self: the AstalWpDevice object + * + * gets the id of this device + * + */ +guint astal_wp_device_get_id(AstalWpDevice *self) { return self->id; } + +/** + * astal_wp_device_get_description + * @self: the AstalWpDevice object + * + * gets the description of this device + * + */ +const gchar *astal_wp_device_get_description(AstalWpDevice *self) { return self->description; } + +/** + * astal_wp_device_get_icon + * @self: the AstalWpDevice object + * + * gets the icon of this device + * + */ +const gchar *astal_wp_device_get_icon(AstalWpDevice *self) { + g_return_val_if_fail(self != NULL, "audio-card-symbolic"); + return self->icon; +} + +/** + * astal_wp_device_get_device_type + * @self: the AstalWpDevice object + * + * gets the type of this device + * + */ +AstalWpDeviceType astal_wp_device_get_device_type(AstalWpDevice *self) { return self->type; } + +/** + * astal_wp_device_get_active_profile + * @self: the AstalWpDevice object + * + * gets the currently active profile of this device + * + */ +gint astal_wp_device_get_active_profile(AstalWpDevice *self) { return self->active_profile; } + +/** + * astal_wp_device_set_active_profile + * @self: the AstalWpDevice object + * @profile_id: the id of the profile + * + * sets the profile for this device + * + */ +void astal_wp_device_set_active_profile(AstalWpDevice *self, int profile_id) { + AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); + + WpSpaPodBuilder *builder = + wp_spa_pod_builder_new_object("Spa:Pod:Object:Param:Profile", "Profile"); + wp_spa_pod_builder_add_property(builder, "index"); + wp_spa_pod_builder_add_int(builder, profile_id); + WpSpaPod *pod = wp_spa_pod_builder_end(builder); + wp_pipewire_object_set_param(WP_PIPEWIRE_OBJECT(priv->device), "Profile", 0, pod); + + wp_spa_pod_builder_unref(builder); +} + +/** + * astal_wp_device_get_profile: + * @self: the AstalWpDevice object + * @id: the id of the profile + * + * gets the profile with the given id + * + * Returns: (transfer none) (nullable) + */ +AstalWpProfile *astal_wp_device_get_profile(AstalWpDevice *self, gint id) { + AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); + + return g_hash_table_lookup(priv->profiles, GINT_TO_POINTER(id)); +} + +/** + * astal_wp_device_get_profiles: + * @self: the AstalWpDevice object + * + * gets a GList containing the profiles + * + * Returns: (transfer container) (nullable) (type GList(AstalWpProfile)) + */ +GList *astal_wp_device_get_profiles(AstalWpDevice *self) { + AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); + return g_hash_table_get_values(priv->profiles); +} + +static void astal_wp_device_get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *pspec) { + AstalWpDevice *self = ASTAL_WP_DEVICE(object); + + switch (property_id) { + case ASTAL_WP_DEVICE_PROP_ID: + g_value_set_uint(value, self->id); + break; + case ASTAL_WP_DEVICE_PROP_DESCRIPTION: + g_value_set_string(value, self->description); + break; + case ASTAL_WP_DEVICE_PROP_ICON: + g_value_set_string(value, self->icon); + break; + case ASTAL_WP_DEVICE_PROP_PROFILES: + g_value_set_pointer(value, astal_wp_device_get_profiles(self)); + break; + case ASTAL_WP_DEVICE_PROP_DEVICE_TYPE: + g_value_set_enum(value, astal_wp_device_get_device_type(self)); + break; + case ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE: + g_value_set_int(value, self->active_profile); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_wp_device_set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *pspec) { + AstalWpDevice *self = ASTAL_WP_DEVICE(object); + + switch (property_id) { + case ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE: + astal_wp_device_set_active_profile(self, g_value_get_int(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_wp_device_update_profiles(AstalWpDevice *self) { + AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); + g_hash_table_remove_all(priv->profiles); + + WpIterator *iter = + wp_pipewire_object_enum_params_sync(WP_PIPEWIRE_OBJECT(priv->device), "EnumProfile", NULL); + if (iter == NULL) return; + GValue profile = G_VALUE_INIT; + while (wp_iterator_next(iter, &profile)) { + WpSpaPod *pod = g_value_get_boxed(&profile); + + gint index; + gchar *description; + wp_spa_pod_get_object(pod, NULL, "index", "i", &index, "description", "s", &description, + NULL); + + g_hash_table_insert( + priv->profiles, GINT_TO_POINTER(index), + g_object_new(ASTAL_WP_TYPE_PROFILE, "index", index, "description", description, NULL)); + g_value_unset(&profile); + } + wp_iterator_unref(iter); + + g_object_notify(G_OBJECT(self), "profiles"); +} + +static void astal_wp_device_update_active_profile(AstalWpDevice *self) { + AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); + + WpIterator *iter = + wp_pipewire_object_enum_params_sync(WP_PIPEWIRE_OBJECT(priv->device), "Profile", NULL); + if (iter == NULL) return; + GValue profile = G_VALUE_INIT; + while (wp_iterator_next(iter, &profile)) { + WpSpaPod *pod = g_value_get_boxed(&profile); + + gint index; + gchar *description; + wp_spa_pod_get_object(pod, NULL, "index", "i", &index, "description", "s", &description, + NULL); + + g_hash_table_insert( + priv->profiles, GINT_TO_POINTER(index), + g_object_new(ASTAL_WP_TYPE_PROFILE, "index", index, "description", description, NULL)); + + self->active_profile = index; + g_value_unset(&profile); + } + wp_iterator_unref(iter); + + g_object_notify(G_OBJECT(self), "active-profile-id"); +} + +static void astal_wp_device_params_changed(AstalWpDevice *self, const gchar *prop) { + if (g_strcmp0(prop, "EnumProfile") == 0) { + astal_wp_device_update_profiles(self); + } else if (g_strcmp0(prop, "Profile") == 0) { + astal_wp_device_update_active_profile(self); + } +} + +static void astal_wp_device_update_properties(AstalWpDevice *self) { + AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); + if (priv->device == NULL) return; + self->id = wp_proxy_get_bound_id(WP_PROXY(priv->device)); + const gchar *description = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "device.description"); + if (description == NULL) { + description = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "device.name"); + } + if (description == NULL) { + description = "unknown"; + } + g_free(self->description); + self->description = g_strdup(description); + + const gchar *icon = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "device.icon-name"); + if (icon == NULL) { + icon = "audio-card-symbolic"; + } + g_free(self->icon); + self->icon = g_strdup(icon); + + const gchar *type = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "media.class"); + GEnumClass *enum_class = g_type_class_ref(ASTAL_WP_TYPE_DEVICE_TYPE); + if (g_enum_get_value_by_nick(enum_class, type) != NULL) + self->type = g_enum_get_value_by_nick(enum_class, type)->value; + g_type_class_unref(enum_class); + + astal_wp_device_update_profiles(self); + astal_wp_device_update_active_profile(self); + + g_object_notify(G_OBJECT(self), "id"); + g_object_notify(G_OBJECT(self), "device-type"); + g_object_notify(G_OBJECT(self), "icon"); + g_object_notify(G_OBJECT(self), "description"); +} + +AstalWpDevice *astal_wp_device_create(WpDevice *device) { + AstalWpDevice *self = g_object_new(ASTAL_WP_TYPE_DEVICE, NULL); + AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); + + priv->device = g_object_ref(device); + + g_signal_connect_swapped(priv->device, "params-changed", + G_CALLBACK(astal_wp_device_params_changed), self); + + astal_wp_device_update_properties(self); + return self; +} + +static void astal_wp_device_init(AstalWpDevice *self) { + AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); + priv->device = NULL; + + priv->profiles = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref); + + self->description = NULL; + self->icon = NULL; +} + +static void astal_wp_device_dispose(GObject *object) { + AstalWpDevice *self = ASTAL_WP_DEVICE(object); + AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); + + g_clear_object(&priv->device); +} + +static void astal_wp_device_finalize(GObject *object) { + AstalWpDevice *self = ASTAL_WP_DEVICE(object); + g_free(self->description); + g_free(self->icon); +} + +static void astal_wp_device_class_init(AstalWpDeviceClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + object_class->dispose = astal_wp_device_dispose; + object_class->finalize = astal_wp_device_finalize; + object_class->get_property = astal_wp_device_get_property; + object_class->set_property = astal_wp_device_set_property; + /** + * AstalWpDevice:id + * + * The id of this device. + */ + astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_ID] = + g_param_spec_uint("id", "id", "id", 0, UINT_MAX, 0, G_PARAM_READABLE); + /** + * AstalWpDevice:description + * + * The description of this device. + */ + astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_DESCRIPTION] = + g_param_spec_string("description", "description", "description", NULL, G_PARAM_READABLE); + /** + * AstalWpDevice:icon + * + * The icon name for this device. + */ + astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_ICON] = + g_param_spec_string("icon", "icon", "icon", NULL, G_PARAM_READABLE); + /** + * AstalWpDevice:device-type: (type AstalWpDeviceType) + * + * The type of this device + */ + astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_DEVICE_TYPE] = + g_param_spec_enum("device-type", "device-type", "device-type", ASTAL_WP_TYPE_DEVICE_TYPE, 1, + G_PARAM_READABLE); + /** + * AstalWpDevice:profiles: (type GList(AstalWpProfile)) (transfer container) + * + * A list of available profiles + */ + astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_PROFILES] = + g_param_spec_pointer("profiles", "profiles", "profiles", G_PARAM_READABLE); + /** + * AstalWpDevice:active-profile-id + * + * The id of the currently active profile. + */ + astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE] = + g_param_spec_int("active-profile-id", "active-profile-id", "active-profile-id", G_MININT, + G_MAXINT, 0, G_PARAM_READWRITE); + + g_object_class_install_properties(object_class, ASTAL_WP_DEVICE_N_PROPERTIES, + astal_wp_device_properties); +} diff --git a/lib/wireplumber/src/endpoint.c b/lib/wireplumber/src/endpoint.c new file mode 100644 index 0000000..13979d1 --- /dev/null +++ b/lib/wireplumber/src/endpoint.c @@ -0,0 +1,554 @@ +#include "endpoint.h" + +#include + +#include "device.h" +#include "endpoint-private.h" +#include "glib.h" +#include "wp.h" + +struct _AstalWpEndpoint { + GObject parent_instance; + + guint id; + gdouble volume; + gboolean mute; + gchar *description; + gchar *name; + AstalWpMediaClass type; + gboolean is_default; + gboolean lock_channels; + + gchar *icon; +}; + +typedef struct { + WpNode *node; + WpPlugin *mixer; + WpPlugin *defaults; + AstalWpWp *wp; + + gboolean is_default_node; + AstalWpMediaClass media_class; + + gulong default_signal_handler_id; + gulong mixer_signal_handler_id; + +} AstalWpEndpointPrivate; + +G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpEndpoint, astal_wp_endpoint, G_TYPE_OBJECT); + +G_DEFINE_ENUM_TYPE(AstalWpMediaClass, astal_wp_media_class, + G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE, "Audio/Source"), + G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER, "Audio/Sink"), + G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER, "Stream/Input/Audio"), + G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM, "Stream/Output/Audio"), + G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE, "Video/Source"), + G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_SINK, "Video/Sink"), + G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER, "Stream/Input/Video"), + G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM, "Stream/Output/Video")); + +typedef enum { + ASTAL_WP_ENDPOINT_PROP_ID = 1, + ASTAL_WP_ENDPOINT_PROP_VOLUME, + ASTAL_WP_ENDPOINT_PROP_MUTE, + ASTAL_WP_ENDPOINT_PROP_DESCRIPTION, + ASTAL_WP_ENDPOINT_PROP_NAME, + ASTAL_WP_ENDPOINT_PROP_MEDIA_CLASS, + ASTAL_WP_ENDPOINT_PROP_DEFAULT, + ASTAL_WP_ENDPOINT_PROP_ICON, + ASTAL_WP_ENDPOINT_PROP_VOLUME_ICON, + ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS, + ASTAL_WP_ENDPOINT_N_PROPERTIES, +} AstalWpEndpointProperties; + +static GParamSpec *astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_N_PROPERTIES] = { + NULL, +}; + +void astal_wp_endpoint_update_volume(AstalWpEndpoint *self) { + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + + gdouble volume = 0; + gboolean mute; + GVariant *variant = NULL; + GVariantIter *channels = NULL; + + g_signal_emit_by_name(priv->mixer, "get-volume", self->id, &variant); + + if (variant == NULL) return; + + g_variant_lookup(variant, "volume", "d", &volume); + g_variant_lookup(variant, "mute", "b", &mute); + g_variant_lookup(variant, "channelVolumes", "a{sv}", &channels); + + if (channels != NULL) { + const gchar *key; + const gchar *channel_str; + gdouble channel_volume; + GVariant *varvol; + + while (g_variant_iter_loop(channels, "{&sv}", &key, &varvol)) { + g_variant_lookup(varvol, "volume", "d", &channel_volume); + g_variant_lookup(varvol, "channel", "&s", &channel_str); + if (channel_volume > volume) volume = channel_volume; + } + } + + if (mute != self->mute) { + self->mute = mute; + g_object_notify(G_OBJECT(self), "mute"); + } + + if (volume != self->volume) { + self->volume = volume; + g_object_notify(G_OBJECT(self), "volume"); + } + + g_object_notify(G_OBJECT(self), "volume-icon"); +} + +/** + * astal_wp_endpoint_set_volume: + * @self: the AstalWpEndpoint object + * @volume: The new volume level to set. + * + * Sets the volume level for this endpoint. The volume is clamped to be between + * 0 and 1.5. + */ +void astal_wp_endpoint_set_volume(AstalWpEndpoint *self, gdouble volume) { + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + + gboolean ret; + if (volume >= 1.5) volume = 1.5; + if (volume <= 0) volume = 0; + + gboolean mute; + GVariant *variant = NULL; + GVariantIter *channels = NULL; + + g_auto(GVariantBuilder) vol_b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); + g_signal_emit_by_name(priv->mixer, "get-volume", self->id, &variant); + + if (variant == NULL) return; + + g_variant_lookup(variant, "mute", "b", &mute); + g_variant_lookup(variant, "channelVolumes", "a{sv}", &channels); + + if (channels != NULL && !self->lock_channels) { + g_auto(GVariantBuilder) channel_volumes_b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); + + const gchar *key; + const gchar *channel_str; + gdouble channel_volume; + GVariant *varvol; + + while (g_variant_iter_loop(channels, "{&sv}", &key, &varvol)) { + g_auto(GVariantBuilder) channel_b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); + g_variant_lookup(varvol, "volume", "d", &channel_volume); + g_variant_lookup(varvol, "channel", "&s", &channel_str); + gdouble vol = self->volume == 0 ? volume : channel_volume * volume / self->volume; + g_variant_builder_add(&channel_b, "{sv}", "volume", g_variant_new_double(vol)); + g_variant_builder_add(&channel_volumes_b, "{sv}", key, + g_variant_builder_end(&channel_b)); + } + + g_variant_builder_add(&vol_b, "{sv}", "channelVolumes", + g_variant_builder_end(&channel_volumes_b)); + } else { + GVariant *volume_variant = g_variant_new_double(volume); + g_variant_builder_add(&vol_b, "{sv}", "volume", volume_variant); + } + + g_signal_emit_by_name(priv->mixer, "set-volume", self->id, g_variant_builder_end(&vol_b), &ret); +} + +/** + * astal_wp_endpoint_set_mute: + * @self: the AstalWpEndpoint instance. + * @mute: A boolean indicating whether to mute the endpoint. + * + * Sets the mute status for the endpoint. + */ +void astal_wp_endpoint_set_mute(AstalWpEndpoint *self, gboolean mute) { + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + + gboolean ret; + GVariant *variant = NULL; + GVariantBuilder b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&b, "{sv}", "mute", g_variant_new_boolean(mute)); + variant = g_variant_builder_end(&b); + + g_signal_emit_by_name(priv->mixer, "set-volume", self->id, variant, &ret); +} + +/** + * astal_wp_endpoint_get_media_class: + * @self: the AstalWpEndpoint instance. + * + * gets the media class of the endpoint. + */ +AstalWpMediaClass astal_wp_endpoint_get_media_class(AstalWpEndpoint *self) { return self->type; } + +/** + * astal_wp_endpoint_get_id: + * @self: the AstalWpEndpoint instance. + * + * gets the id of the endpoint. + */ +guint astal_wp_endpoint_get_id(AstalWpEndpoint *self) { return self->id; } + +/** + * astal_wp_endpoint_get_mute: + * @self: the AstalWpEndpoint instance. + * + * gets the mute status of the endpoint. + */ +gboolean astal_wp_endpoint_get_mute(AstalWpEndpoint *self) { return self->mute; } + +/** + * astal_wp_endpoint_get_volume: + * @self: the AstalWpEndpoint instance. + * + * gets the volume + */ +gdouble astal_wp_endpoint_get_volume(AstalWpEndpoint *self) { return self->volume; } + +const gchar *astal_wp_endpoint_get_description(AstalWpEndpoint *self) { return self->description; } + +const gchar *astal_wp_endpoint_get_name(AstalWpEndpoint *self) { return self->name; } + +const gchar *astal_wp_endpoint_get_icon(AstalWpEndpoint *self) { return self->icon; } + +gboolean astal_wp_endpoint_get_is_default(AstalWpEndpoint *self) { return self->is_default; } + +void astal_wp_endpoint_set_is_default(AstalWpEndpoint *self, gboolean is_default) { + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + + if (!is_default) return; + gboolean ret; + const gchar *name = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.name"); + const gchar *media_class = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.class"); + g_signal_emit_by_name(priv->defaults, "set-default-configured-node-name", media_class, name, + &ret); +} + +gboolean astal_wp_endpoint_get_lock_channels(AstalWpEndpoint *self) { return self->lock_channels; } + +void astal_wp_endpoint_set_lock_channels(AstalWpEndpoint *self, gboolean lock_channels) { + self->lock_channels = lock_channels; + astal_wp_endpoint_set_volume(self, self->volume); +} + +const gchar *astal_wp_endpoint_get_volume_icon(AstalWpEndpoint *self) { + if (self->type == ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE) { + if (self->mute) return "microphone-sensitivity-muted-symbolic"; + if (self->volume <= 0.33) return "microphone-sensitivity-low-symbolic"; + if (self->volume <= 0.66) return "microphone-sensitivity-medium-symbolic"; + return "microphone-sensitivity-high-symbolic"; + + } else { + if (self->mute) return "audio-volume-muted-symbolic"; + if (self->volume <= 0.33) return "audio-volume-low-symbolic"; + if (self->volume <= 0.66) return "audio-volume-medium-symbolic"; + if (self->volume <= 1) return "audio-volume-high-symbolic"; + return "audio-volume-overamplified-symbolic"; + } +} + +static void astal_wp_endpoint_get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *pspec) { + AstalWpEndpoint *self = ASTAL_WP_ENDPOINT(object); + + switch (property_id) { + case ASTAL_WP_ENDPOINT_PROP_ID: + g_value_set_uint(value, self->id); + break; + case ASTAL_WP_ENDPOINT_PROP_MUTE: + g_value_set_boolean(value, self->mute); + break; + case ASTAL_WP_ENDPOINT_PROP_VOLUME: + g_value_set_double(value, self->volume); + break; + case ASTAL_WP_ENDPOINT_PROP_DESCRIPTION: + g_value_set_string(value, self->description); + break; + case ASTAL_WP_ENDPOINT_PROP_NAME: + g_value_set_string(value, self->name); + break; + case ASTAL_WP_ENDPOINT_PROP_ICON: + g_value_set_string(value, self->icon); + break; + case ASTAL_WP_ENDPOINT_PROP_VOLUME_ICON: + g_value_set_string(value, astal_wp_endpoint_get_volume_icon(self)); + break; + case ASTAL_WP_ENDPOINT_PROP_MEDIA_CLASS: + g_value_set_enum(value, self->type); + break; + case ASTAL_WP_ENDPOINT_PROP_DEFAULT: + g_value_set_boolean(value, self->is_default); + break; + case ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS: + g_value_set_boolean(value, self->lock_channels); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_wp_endpoint_set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *pspec) { + AstalWpEndpoint *self = ASTAL_WP_ENDPOINT(object); + + switch (property_id) { + case ASTAL_WP_ENDPOINT_PROP_MUTE: + astal_wp_endpoint_set_mute(self, g_value_get_boolean(value)); + break; + case ASTAL_WP_ENDPOINT_PROP_VOLUME: + astal_wp_endpoint_set_volume(self, g_value_get_double(value)); + break; + case ASTAL_WP_ENDPOINT_PROP_DEFAULT: + astal_wp_endpoint_set_is_default(self, g_value_get_boolean(value)); + break; + case ASTAL_WP_ENDPOINT_PROP_ICON: + g_free(self->icon); + self->icon = g_strdup(g_value_get_string(value)); + break; + case ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS: + astal_wp_endpoint_set_lock_channels(self, g_value_get_boolean(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_wp_endpoint_update_properties(AstalWpEndpoint *self) { + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + if (priv->node == NULL) return; + self->id = wp_proxy_get_bound_id(WP_PROXY(priv->node)); + astal_wp_endpoint_update_volume(self); + + const gchar *description = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.description"); + if (description == NULL) { + description = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.nick"); + } + if (description == NULL) { + description = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.name"); + } + g_free(self->description); + self->description = g_strdup(description); + + const gchar *name = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.name"); + g_free(self->name); + self->name = g_strdup(name); + + const gchar *type = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.class"); + GEnumClass *enum_class = g_type_class_ref(ASTAL_WP_TYPE_MEDIA_CLASS); + if (g_enum_get_value_by_nick(enum_class, type) != NULL) + self->type = g_enum_get_value_by_nick(enum_class, type)->value; + g_type_class_unref(enum_class); + + const gchar *icon = NULL; + switch (self->type) { + case ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER: + case ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE: + const gchar *dev = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "device.id"); + guint device_id = g_ascii_strtoull(dev, NULL, 10); + AstalWpDevice *device = astal_wp_wp_get_device(priv->wp, device_id); + icon = astal_wp_device_get_icon(device); + if (icon == NULL) { + icon = self->type == ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER + ? "audio-card-symbolic" + : "audio-input-microphone-symbolic"; + } + break; + case ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM: + case ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER: + icon = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.icon-name"); + if (icon == NULL) + icon = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), + "window.icon-name"); + if (icon == NULL) + icon = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), + "application.icon-name"); + if (icon == NULL) icon = "application-x-executable-symbolic"; + break; + default: + icon = "audio-card-symbolic"; + } + g_free(self->icon); + self->icon = g_strdup(icon); + + g_object_notify(G_OBJECT(self), "id"); + g_object_notify(G_OBJECT(self), "description"); + g_object_notify(G_OBJECT(self), "name"); + g_object_notify(G_OBJECT(self), "icon"); + g_object_notify(G_OBJECT(self), "media-class"); +} + +static void astal_wp_endpoint_default_changed_as_default(AstalWpEndpoint *self) { + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + + GEnumClass *enum_class = g_type_class_ref(ASTAL_WP_TYPE_MEDIA_CLASS); + const gchar *media_class = g_enum_get_value(enum_class, priv->media_class)->value_nick; + guint defaultId; + g_signal_emit_by_name(priv->defaults, "get-default-node", media_class, &defaultId); + g_type_class_unref(enum_class); + + if (defaultId != self->id) { + if (priv->node != NULL) g_object_unref(priv->node); + AstalWpEndpoint *default_endpoint = astal_wp_wp_get_endpoint(priv->wp, defaultId); + if (default_endpoint != NULL && + astal_wp_endpoint_get_media_class(default_endpoint) == priv->media_class) { + AstalWpEndpointPrivate *default_endpoint_priv = + astal_wp_endpoint_get_instance_private(default_endpoint); + priv->node = g_object_ref(default_endpoint_priv->node); + astal_wp_endpoint_update_properties(self); + } + } +} + +static void astal_wp_endpoint_default_changed(AstalWpEndpoint *self) { + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + + guint defaultId; + const gchar *media_class = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.class"); + g_signal_emit_by_name(priv->defaults, "get-default-node", media_class, &defaultId); + + if (self->is_default && defaultId != self->id) { + self->is_default = FALSE; + g_object_notify(G_OBJECT(self), "is-default"); + } else if (!self->is_default && defaultId == self->id) { + self->is_default = TRUE; + g_object_notify(G_OBJECT(self), "is-default"); + } +} + +static void astal_wp_endpoint_mixer_changed(AstalWpEndpoint *self, guint node_id) { + if (self->id != node_id) return; + astal_wp_endpoint_update_volume(self); +} + +AstalWpEndpoint *astal_wp_endpoint_init_as_default(AstalWpEndpoint *self, WpPlugin *mixer, + WpPlugin *defaults, AstalWpMediaClass type, + AstalWpWp *wp) { + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + + priv->mixer = g_object_ref(mixer); + priv->defaults = g_object_ref(defaults); + + priv->media_class = type; + priv->is_default_node = TRUE; + self->is_default = TRUE; + priv->wp = g_object_ref(wp); + + priv->default_signal_handler_id = g_signal_connect_swapped( + priv->defaults, "changed", G_CALLBACK(astal_wp_endpoint_default_changed_as_default), self); + priv->mixer_signal_handler_id = g_signal_connect_swapped( + priv->mixer, "changed", G_CALLBACK(astal_wp_endpoint_mixer_changed), self); + + astal_wp_endpoint_default_changed_as_default(self); + astal_wp_endpoint_update_properties(self); + return self; +} + +AstalWpEndpoint *astal_wp_endpoint_create(WpNode *node, WpPlugin *mixer, WpPlugin *defaults, + AstalWpWp *wp) { + AstalWpEndpoint *self = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL); + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + + priv->mixer = g_object_ref(mixer); + priv->defaults = g_object_ref(defaults); + priv->node = g_object_ref(node); + priv->is_default_node = FALSE; + priv->wp = g_object_ref(wp); + + priv->default_signal_handler_id = g_signal_connect_swapped( + priv->defaults, "changed", G_CALLBACK(astal_wp_endpoint_default_changed), self); + priv->mixer_signal_handler_id = g_signal_connect_swapped( + priv->mixer, "changed", G_CALLBACK(astal_wp_endpoint_mixer_changed), self); + + astal_wp_endpoint_update_properties(self); + astal_wp_endpoint_default_changed(self); + return self; +} + +static void astal_wp_endpoint_init(AstalWpEndpoint *self) { + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + priv->node = NULL; + priv->mixer = NULL; + priv->defaults = NULL; + priv->wp = NULL; + + self->volume = 0; + self->mute = TRUE; + self->description = NULL; + self->name = NULL; +} + +static void astal_wp_endpoint_dispose(GObject *object) { + AstalWpEndpoint *self = ASTAL_WP_ENDPOINT(object); + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + + g_signal_handler_disconnect(priv->defaults, priv->default_signal_handler_id); + g_signal_handler_disconnect(priv->mixer, priv->mixer_signal_handler_id); + + g_clear_object(&priv->node); + g_clear_object(&priv->mixer); + g_clear_object(&priv->defaults); + g_clear_object(&priv->wp); +} + +static void astal_wp_endpoint_finalize(GObject *object) { + AstalWpEndpoint *self = ASTAL_WP_ENDPOINT(object); + g_free(self->description); + g_free(self->name); +} + +static void astal_wp_endpoint_class_init(AstalWpEndpointClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + object_class->dispose = astal_wp_endpoint_dispose; + object_class->finalize = astal_wp_endpoint_finalize; + object_class->get_property = astal_wp_endpoint_get_property; + object_class->set_property = astal_wp_endpoint_set_property; + + astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_ID] = + g_param_spec_uint("id", "id", "id", 0, UINT_MAX, 0, G_PARAM_READABLE); + astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_VOLUME] = + g_param_spec_double("volume", "volume", "volume", 0, G_MAXFLOAT, 0, G_PARAM_READWRITE); + astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_MUTE] = + g_param_spec_boolean("mute", "mute", "mute", TRUE, G_PARAM_READWRITE); + astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_DESCRIPTION] = + g_param_spec_string("description", "description", "description", NULL, G_PARAM_READABLE); + astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_NAME] = + g_param_spec_string("name", "name", "name", NULL, G_PARAM_READABLE); + astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_ICON] = g_param_spec_string( + "icon", "icon", "icon", "audio-card-symbolic", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_VOLUME_ICON] = g_param_spec_string( + "volume-icon", "volume-icon", "volume-icon", "audio-volume-muted", G_PARAM_READABLE); + /** + * AstalWpEndpoint:media-class: (type AstalWpMediaClass) + * + * The media class of this endpoint + */ + astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_MEDIA_CLASS] = + g_param_spec_enum("media-class", "media-class", "media-class", ASTAL_WP_TYPE_MEDIA_CLASS, 1, + G_PARAM_READABLE); + astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_DEFAULT] = + g_param_spec_boolean("is_default", "is_default", "is_default", FALSE, G_PARAM_READWRITE); + astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS] = g_param_spec_boolean( + "lock_channels", "lock_channels", "lock channels", FALSE, G_PARAM_READWRITE); + + g_object_class_install_properties(object_class, ASTAL_WP_ENDPOINT_N_PROPERTIES, + astal_wp_endpoint_properties); +} diff --git a/lib/wireplumber/src/meson.build b/lib/wireplumber/src/meson.build new file mode 100644 index 0000000..8b69c41 --- /dev/null +++ b/lib/wireplumber/src/meson.build @@ -0,0 +1,72 @@ +srcs = files( + 'audio.c', + 'device.c', + 'endpoint.c', + 'profile.c', + 'video.c', + 'wireplumber.c', +) + +deps = [ + dependency('gobject-2.0'), + dependency('gio-2.0'), + dependency('wireplumber-0.5'), +] + +astal_wireplumber_lib = library( + 'astal-wireplumber', + sources: srcs, + include_directories: astal_wireplumber_inc, + dependencies: deps, + version: meson.project_version(), + install: true, +) + +libastal_wireplumber = declare_dependency(link_with: astal_wireplumber_lib, include_directories: astal_wireplumber_inc) + +# astal_wireplumber_executable = executable( +# 'astal-wireplumber', +# files('astal-wireplumber.c'), +# dependencies : [ +# dependency('gobject-2.0'), +# dependency('gio-2.0'), +# dependency('json-glib-1.0'), +# libastal_wireplumber +# ], +# install : true) + +pkg_config_name = 'astal-wireplumber-' + lib_so_version + +if get_option('introspection') + gir = gnome.generate_gir( + astal_wireplumber_lib, + sources: srcs + astal_wireplumber_headers + astal_wireplumber_subheaders, + nsversion: '0.1', + namespace: 'AstalWp', + symbol_prefix: 'astal_wp', + identifier_prefix: 'AstalWp', + includes: ['GObject-2.0', 'Gio-2.0'], + header: 'astal-wp.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-wireplumber', + version: meson.project_version(), + libraries: [astal_wireplumber_lib], + filebase: pkg_config_name, + subdirs: 'astal', + description: 'astal wireplumber module', + url: 'https://github.com/astal-sh/wireplumber', +) diff --git a/lib/wireplumber/src/profile.c b/lib/wireplumber/src/profile.c new file mode 100644 index 0000000..291dc7f --- /dev/null +++ b/lib/wireplumber/src/profile.c @@ -0,0 +1,84 @@ +#include "profile.h" + +#include + +struct _AstalWpProfile { + GObject parent_instance; + + gint index; + gchar *description; +}; + +G_DEFINE_FINAL_TYPE(AstalWpProfile, astal_wp_profile, G_TYPE_OBJECT); + +typedef enum { + ASTAL_WP_PROFILE_PROP_INDEX = 1, + ASTAL_WP_PROFILE_PROP_DESCRIPTION, + ASTAL_WP_PROFILE_N_PROPERTIES, +} AstalWpProfileProperties; + +static GParamSpec *astal_wp_profile_properties[ASTAL_WP_PROFILE_N_PROPERTIES] = { + NULL, +}; + +gint astal_wp_profile_get_index(AstalWpProfile *self) { return self->index; } + +const gchar *astal_wp_profile_get_description(AstalWpProfile *self) { return self->description; } + +static void astal_wp_profile_get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *pspec) { + AstalWpProfile *self = ASTAL_WP_PROFILE(object); + + switch (property_id) { + case ASTAL_WP_PROFILE_PROP_INDEX: + g_value_set_int(value, self->index); + break; + case ASTAL_WP_PROFILE_PROP_DESCRIPTION: + g_value_set_string(value, self->description); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_wp_profile_set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *pspec) { + AstalWpProfile *self = ASTAL_WP_PROFILE(object); + + switch (property_id) { + case ASTAL_WP_PROFILE_PROP_INDEX: + self->index = g_value_get_int(value); + break; + case ASTAL_WP_PROFILE_PROP_DESCRIPTION: + g_free(self->description); + self->description = g_strdup(g_value_get_string(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_wp_profile_init(AstalWpProfile *self) { self->description = NULL; } + +static void astal_wp_profile_finalize(GObject *object) { + AstalWpProfile *self = ASTAL_WP_PROFILE(object); + g_free(self->description); +} + +static void astal_wp_profile_class_init(AstalWpProfileClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + object_class->finalize = astal_wp_profile_finalize; + object_class->get_property = astal_wp_profile_get_property; + object_class->set_property = astal_wp_profile_set_property; + + astal_wp_profile_properties[ASTAL_WP_PROFILE_PROP_DESCRIPTION] = + g_param_spec_string("description", "description", "description", NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + astal_wp_profile_properties[ASTAL_WP_PROFILE_PROP_INDEX] = + g_param_spec_int("index", "index", "index", G_MININT, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_properties(object_class, ASTAL_WP_PROFILE_N_PROPERTIES, + astal_wp_profile_properties); +} diff --git a/lib/wireplumber/src/video.c b/lib/wireplumber/src/video.c new file mode 100644 index 0000000..00cdd82 --- /dev/null +++ b/lib/wireplumber/src/video.c @@ -0,0 +1,428 @@ +#include "video.h" + +#include + +#include "device.h" +#include "endpoint.h" +#include "wp.h" + +struct _AstalWpVideo { + GObject parent_instance; +}; + +typedef struct { + AstalWpWp *wp; +} AstalWpVideoPrivate; + +G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpVideo, astal_wp_video, G_TYPE_OBJECT); + +typedef enum { + ASTAL_WP_VIDEO_SIGNAL_SOURCE_ADDED, + ASTAL_WP_VIDEO_SIGNAL_SOURCE_REMOVED, + ASTAL_WP_VIDEO_SIGNAL_SINK_ADDED, + ASTAL_WP_VIDEO_SIGNAL_SINK_REMOVED, + ASTAL_WP_VIDEO_SIGNAL_STREAM_ADDED, + ASTAL_WP_VIDEO_SIGNAL_STREAM_REMOVED, + ASTAL_WP_VIDEO_SIGNAL_RECORDER_ADDED, + ASTAL_WP_VIDEO_SIGNAL_RECORDER_REMOVED, + ASTAL_WP_VIDEO_SIGNAL_DEVICE_ADDED, + ASTAL_WP_VIDEO_SIGNAL_DEVICE_REMOVED, + ASTAL_WP_VIDEO_N_SIGNALS +} AstalWpWpSignals; + +static guint astal_wp_video_signals[ASTAL_WP_VIDEO_N_SIGNALS] = { + 0, +}; + +typedef enum { + ASTAL_WP_VIDEO_PROP_SOURCE = 1, + ASTAL_WP_VIDEO_PROP_SINK, + ASTAL_WP_VIDEO_PROP_STREAMS, + ASTAL_WP_VIDEO_PROP_RECORDERS, + ASTAL_WP_VIDEO_PROP_DEVICES, + ASTAL_WP_VIDEO_N_PROPERTIES, +} AstalWpVideoProperties; + +static GParamSpec *astal_wp_video_properties[ASTAL_WP_VIDEO_N_PROPERTIES] = { + NULL, +}; + +/** + * astal_wp_video_get_source: + * @self: the AstalWpVideo object + * @id: the id of the endpoint + * + * Returns: (transfer none) (nullable): the source with the given id + */ +AstalWpEndpoint *astal_wp_video_get_speaker(AstalWpVideo *self, guint id) { + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + + AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); + if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE) + return endpoint; + return NULL; +} + +/** + * astal_wp_video_get_sink: + * @self: the AstalWpVideo object + * @id: the id of the endpoint + * + * Returns: (transfer none) (nullable): the sink with the given id + */ +AstalWpEndpoint *astal_wp_video_get_sink(AstalWpVideo *self, guint id) { + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + + AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); + if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_VIDEO_SINK) + return endpoint; + return NULL; +} + +/** + * astal_wp_video_get_stream: + * @self: the AstalWpVideo object + * @id: the id of the endpoint + * + * Returns: (transfer none) (nullable): the stream with the given id + */ +AstalWpEndpoint *astal_wp_video_get_stream(AstalWpVideo *self, guint id) { + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + + AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); + if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM) + return endpoint; + return NULL; +} + +/** + * astal_wp_video_get_recorder: + * @self: the AstalWpVideo object + * @id: the id of the endpoint + * + * Returns: (transfer none) (nullable): the recorder with the given id + */ +AstalWpEndpoint *astal_wp_video_get_recorder(AstalWpVideo *self, guint id) { + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + + AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); + if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER) + return endpoint; + return NULL; +} + +/** + * astal_wp_video_get_device: + * @self: the AstalWpVideo object + * @id: the id of the device + * + * Returns: (transfer none) (nullable): the device with the given id + */ +AstalWpDevice *astal_wp_video_get_device(AstalWpVideo *self, guint id) { + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + + AstalWpDevice *device = astal_wp_wp_get_device(priv->wp, id); + if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_VIDEO) return device; + return NULL; +} + +/** + * astal_wp_video_get_sources: + * @self: the AstalWpVideo object + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the + * video sources + */ +GList *astal_wp_video_get_sources(AstalWpVideo *self) { + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + GList *eps = astal_wp_wp_get_endpoints(priv->wp); + GList *list = NULL; + + for (GList *l = eps; l != NULL; l = l->next) { + if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE) { + list = g_list_append(list, l->data); + } + } + g_list_free(eps); + return list; +} + +/** + * astal_wp_video_get_sinks + * @self: the AstalWpVideo object + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the + * video sinks + */ +GList *astal_wp_video_get_sinks(AstalWpVideo *self) { + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + GList *eps = astal_wp_wp_get_endpoints(priv->wp); + GList *list = NULL; + + for (GList *l = eps; l != NULL; l = l->next) { + if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_SINK) { + list = g_list_append(list, l->data); + } + } + g_list_free(eps); + return list; +} + +/** + * astal_wp_video_get_recorders: + * @self: the AstalWpVideo object + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the + * video recorders + */ +GList *astal_wp_video_get_recorders(AstalWpVideo *self) { + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + GList *eps = astal_wp_wp_get_endpoints(priv->wp); + GList *list = NULL; + + for (GList *l = eps; l != NULL; l = l->next) { + if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER) { + list = g_list_append(list, l->data); + } + } + g_list_free(eps); + return list; +} + +/** + * astal_wp_video_get_streams: + * @self: the AstalWpVideo object + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the + * video streams + */ +GList *astal_wp_video_get_streams(AstalWpVideo *self) { + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + GList *eps = astal_wp_wp_get_endpoints(priv->wp); + GList *list = NULL; + + for (GList *l = eps; l != NULL; l = l->next) { + if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM) { + list = g_list_append(list, l->data); + } + } + g_list_free(eps); + return list; +} + +/** + * astal_wp_video_get_devices: + * @self: the AstalWpAudio object + * + * Returns: (transfer container) (nullable) (type GList(AstalWpVideo)): a GList containing the + * devices + */ +GList *astal_wp_video_get_devices(AstalWpVideo *self) { + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + GList *eps = astal_wp_wp_get_devices(priv->wp); + GList *list = NULL; + + for (GList *l = eps; l != NULL; l = l->next) { + if (astal_wp_device_get_device_type(l->data) == ASTAL_WP_DEVICE_TYPE_VIDEO) { + list = g_list_append(list, l->data); + } + } + g_list_free(eps); + return list; +} + +static void astal_wp_video_get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *pspec) { + AstalWpVideo *self = ASTAL_WP_VIDEO(object); + + switch (property_id) { + case ASTAL_WP_VIDEO_PROP_SOURCE: + g_value_set_pointer(value, astal_wp_video_get_sources(self)); + break; + case ASTAL_WP_VIDEO_PROP_SINK: + g_value_set_pointer(value, astal_wp_video_get_sinks(self)); + break; + case ASTAL_WP_VIDEO_PROP_RECORDERS: + g_value_set_pointer(value, astal_wp_video_get_recorders(self)); + break; + case ASTAL_WP_VIDEO_PROP_STREAMS: + g_value_set_pointer(value, astal_wp_video_get_streams(self)); + break; + case ASTAL_WP_VIDEO_PROP_DEVICES: + g_value_set_pointer(value, astal_wp_video_get_devices(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +void astal_wp_video_device_added(AstalWpVideo *self, gpointer object) { + AstalWpDevice *device = ASTAL_WP_DEVICE(object); + if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_VIDEO) { + g_signal_emit_by_name(self, "device-added", device); + g_object_notify(G_OBJECT(self), "devices"); + } +} + +static void astal_wp_video_device_removed(AstalWpVideo *self, gpointer object) { + AstalWpDevice *device = ASTAL_WP_DEVICE(object); + if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_VIDEO) { + g_signal_emit_by_name(self, "device-removed", device); + g_object_notify(G_OBJECT(self), "devices"); + } +} + +static void astal_wp_video_object_added(AstalWpVideo *self, gpointer object) { + AstalWpEndpoint *endpoint = ASTAL_WP_ENDPOINT(object); + switch (astal_wp_endpoint_get_media_class(endpoint)) { + case ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE: + g_signal_emit_by_name(self, "source-added", endpoint); + g_object_notify(G_OBJECT(self), "sources"); + break; + case ASTAL_WP_MEDIA_CLASS_VIDEO_SINK: + g_signal_emit_by_name(self, "sink-added", endpoint); + g_object_notify(G_OBJECT(self), "sinks"); + break; + case ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM: + g_signal_emit_by_name(self, "stream-added", endpoint); + g_object_notify(G_OBJECT(self), "streams"); + break; + case ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER: + g_signal_emit_by_name(self, "recorder-added", endpoint); + g_object_notify(G_OBJECT(self), "recorders"); + break; + default: + break; + } +} + +static void astal_wp_video_object_removed(AstalWpAudio *self, gpointer object) { + AstalWpEndpoint *endpoint = ASTAL_WP_ENDPOINT(object); + switch (astal_wp_endpoint_get_media_class(endpoint)) { + case ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE: + g_signal_emit_by_name(self, "source-removed", endpoint); + g_object_notify(G_OBJECT(self), "sources"); + break; + case ASTAL_WP_MEDIA_CLASS_VIDEO_SINK: + g_signal_emit_by_name(self, "sink-removed", endpoint); + g_object_notify(G_OBJECT(self), "sinks"); + break; + case ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM: + g_signal_emit_by_name(self, "stream-removed", endpoint); + g_object_notify(G_OBJECT(self), "streams"); + break; + case ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER: + g_signal_emit_by_name(self, "recorder-removed", endpoint); + g_object_notify(G_OBJECT(self), "recorders"); + break; + default: + break; + } +} + +AstalWpVideo *astal_wp_video_new(AstalWpWp *wp) { + AstalWpVideo *self = g_object_new(ASTAL_WP_TYPE_VIDEO, NULL); + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + priv->wp = g_object_ref(wp); + g_signal_connect_swapped(priv->wp, "endpoint-added", G_CALLBACK(astal_wp_video_object_added), + self); + g_signal_connect_swapped(priv->wp, "endpoint-removed", + G_CALLBACK(astal_wp_video_object_removed), self); + g_signal_connect_swapped(priv->wp, "device-added", G_CALLBACK(astal_wp_video_device_added), + self); + g_signal_connect_swapped(priv->wp, "device-removed", G_CALLBACK(astal_wp_video_device_removed), + self); + + return self; +} + +static void astal_wp_video_dispose(GObject *object) { + AstalWpVideo *self = ASTAL_WP_VIDEO(object); + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); + g_clear_object(&priv->wp); +} + +static void astal_wp_video_init(AstalWpVideo *self) { + AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); +} + +static void astal_wp_video_class_init(AstalWpVideoClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + object_class->get_property = astal_wp_video_get_property; + object_class->dispose = astal_wp_video_dispose; + + /** + * AstalWpVideo:sources: (type GList(AstalWpEndpoint)) (transfer container) + * + * A list of AstalWpEndpoint objects + */ + astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_SOURCE] = + g_param_spec_pointer("sources", "sources", "sources", G_PARAM_READABLE); + + /** + * AstalWpVideo:sinks: (type GList(AstalWpEndpoint)) (transfer container) + * + * A list of AstalWpEndpoint objects + */ + astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_SINK] = + g_param_spec_pointer("sinks", "sinks", "sinks", G_PARAM_READABLE); + + /** + * AstalWpVideo:recorder: (type GList(AstalWpEndpoint)) (transfer container) + * + * A list of AstalWpEndpoint objects + */ + astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_RECORDERS] = + g_param_spec_pointer("recorders", "recorders", "recorders", G_PARAM_READABLE); + + /** + * AstalWpVideo:streams: (type GList(AstalWpEndpoint)) (transfer container) + * + * A list of AstalWpEndpoint objects + */ + astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_STREAMS] = + g_param_spec_pointer("streams", "streams", "streams", G_PARAM_READABLE); + + /** + * AstalWpVideo:devices: (type GList(AstalWpEndpoint)) (transfer container) + * + * A list of AstalWpEndpoint objects + */ + astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_DEVICES] = + g_param_spec_pointer("devices", "devices", "devices", G_PARAM_READABLE); + + g_object_class_install_properties(object_class, ASTAL_WP_VIDEO_N_PROPERTIES, + astal_wp_video_properties); + + astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SOURCE_ADDED] = + g_signal_new("source-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SOURCE_REMOVED] = + g_signal_new("source-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SINK_ADDED] = + g_signal_new("sink-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SINK_REMOVED] = + g_signal_new("sink-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_STREAM_ADDED] = + g_signal_new("stream-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SOURCE_REMOVED] = + g_signal_new("stream-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_RECORDER_ADDED] = + g_signal_new("recorder-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_RECORDER_REMOVED] = + g_signal_new("recorder-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_DEVICE_ADDED] = + g_signal_new("device-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); + astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_DEVICE_REMOVED] = + g_signal_new("device-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); +} diff --git a/lib/wireplumber/src/wireplumber.c b/lib/wireplumber/src/wireplumber.c new file mode 100644 index 0000000..f1fa516 --- /dev/null +++ b/lib/wireplumber/src/wireplumber.c @@ -0,0 +1,503 @@ +#include + +#include "audio.h" +#include "device-private.h" +#include "endpoint-private.h" +#include "glib-object.h" +#include "glib.h" +#include "video.h" +#include "wp.h" + +struct _AstalWpWp { + GObject parent_instance; + + AstalWpEndpoint *default_speaker; + AstalWpEndpoint *default_microphone; + + AstalWpAudio *audio; + AstalWpVideo *video; + + AstalWpScale scale; +}; + +typedef struct { + WpCore *core; + WpObjectManager *obj_manager; + + WpPlugin *mixer; + WpPlugin *defaults; + gint pending_plugins; + + GHashTable *endpoints; + GHashTable *devices; +} AstalWpWpPrivate; + +G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpWp, astal_wp_wp, G_TYPE_OBJECT); + +G_DEFINE_ENUM_TYPE(AstalWpScale, astal_wp_scale, + G_DEFINE_ENUM_VALUE(ASTAL_WP_SCALE_LINEAR, "linear"), + G_DEFINE_ENUM_VALUE(ASTAL_WP_SCALE_CUBIC, "cubic")); + +typedef enum { + ASTAL_WP_WP_SIGNAL_ENDPOINT_ADDED, + ASTAL_WP_WP_SIGNAL_ENDPOINT_REMOVED, + ASTAL_WP_WP_SIGNAL_DEVICE_ADDED, + ASTAL_WP_WP_SIGNAL_DEVICE_REMOVED, + ASTAL_WP_WP_N_SIGNALS +} AstalWpWpSignals; + +static guint astal_wp_wp_signals[ASTAL_WP_WP_N_SIGNALS] = { + 0, +}; + +typedef enum { + ASTAL_WP_WP_PROP_AUDIO = 1, + ASTAL_WP_WP_PROP_VIDEO, + ASTAL_WP_WP_PROP_ENDPOINTS, + ASTAL_WP_WP_PROP_DEVICES, + ASTAL_WP_WP_PROP_DEFAULT_SPEAKER, + ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE, + ASTAL_WP_WP_PROP_SCALE, + ASTAL_WP_WP_N_PROPERTIES, +} AstalWpWpProperties; + +static GParamSpec *astal_wp_wp_properties[ASTAL_WP_WP_N_PROPERTIES] = { + NULL, +}; + +/** + * astal_wp_wp_get_endpoint: + * @self: the AstalWpWp object + * @id: the id of the endpoint + * + * Returns: (transfer none) (nullable): the endpoint with the given id + */ +AstalWpEndpoint *astal_wp_wp_get_endpoint(AstalWpWp *self, guint id) { + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + + AstalWpEndpoint *endpoint = g_hash_table_lookup(priv->endpoints, GUINT_TO_POINTER(id)); + return endpoint; +} + +/** + * astal_wp_wp_get_endpoints: + * @self: the AstalWpWp object + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the + * endpoints + */ +GList *astal_wp_wp_get_endpoints(AstalWpWp *self) { + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + return g_hash_table_get_values(priv->endpoints); +} + +/** + * astal_wp_wp_get_device: + * @self: the AstalWpWp object + * @id: the id of the device + * + * Returns: (transfer none) (nullable): the device with the given id + */ +AstalWpDevice *astal_wp_wp_get_device(AstalWpWp *self, guint id) { + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + + AstalWpDevice *device = g_hash_table_lookup(priv->devices, GUINT_TO_POINTER(id)); + return device; +} + +/** + * astal_wp_wp_get_devices: + * @self: the AstalWpWp object + * + * Returns: (transfer container) (nullable) (type GList(AstalWpDevice)): a GList containing the + * devices + */ +GList *astal_wp_wp_get_devices(AstalWpWp *self) { + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + return g_hash_table_get_values(priv->devices); +} + +/** + * astal_wp_wp_get_audio + * + * Returns: (nullable) (transfer none): gets the audio object + */ +AstalWpAudio *astal_wp_wp_get_audio(AstalWpWp *self) { return self->audio; } + +/** + * astal_wp_wp_get_video + * + * Returns: (nullable) (transfer none): gets the video object + */ +AstalWpVideo *astal_wp_wp_get_video(AstalWpWp *self) { return self->video; } + +/** + * astal_wp_wp_get_default_speaker + * + * Returns: (nullable) (transfer none): gets the default speaker object + */ +AstalWpEndpoint *astal_wp_wp_get_default_speaker(AstalWpWp *self) { return self->default_speaker; } + +/** + * astal_wp_wp_get_default_microphone + * + * Returns: (nullable) (transfer none): gets the default microphone object + */ +AstalWpEndpoint *astal_wp_wp_get_default_microphone(AstalWpWp *self) { + return self->default_microphone; +} + +AstalWpScale astal_wp_wp_get_scale(AstalWpWp *self) { return self->scale; } + +void astal_wp_wp_set_scale(AstalWpWp *self, AstalWpScale scale) { + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + self->scale = scale; + + if (priv->mixer == NULL) return; + + g_object_set(priv->mixer, "scale", self->scale, NULL); + + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init(&iter, priv->endpoints); + while (g_hash_table_iter_next(&iter, &key, &value)) { + AstalWpEndpoint *ep = ASTAL_WP_ENDPOINT(value); + astal_wp_endpoint_update_volume(ep); + } + + astal_wp_endpoint_update_volume(self->default_speaker); + astal_wp_endpoint_update_volume(self->default_microphone); +} + +static void astal_wp_wp_get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *pspec) { + AstalWpWp *self = ASTAL_WP_WP(object); + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + + switch (property_id) { + case ASTAL_WP_WP_PROP_AUDIO: + g_value_set_object(value, astal_wp_wp_get_audio(self)); + break; + case ASTAL_WP_WP_PROP_VIDEO: + g_value_set_object(value, astal_wp_wp_get_video(self)); + break; + case ASTAL_WP_WP_PROP_ENDPOINTS: + g_value_set_pointer(value, g_hash_table_get_values(priv->endpoints)); + break; + case ASTAL_WP_WP_PROP_DEVICES: + g_value_set_pointer(value, g_hash_table_get_values(priv->devices)); + break; + case ASTAL_WP_WP_PROP_DEFAULT_SPEAKER: + g_value_set_object(value, self->default_speaker); + break; + case ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE: + g_value_set_object(value, self->default_microphone); + break; + case ASTAL_WP_WP_PROP_SCALE: + g_value_set_enum(value, self->scale); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_wp_wp_set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *pspec) { + AstalWpWp *self = ASTAL_WP_WP(object); + + switch (property_id) { + case ASTAL_WP_WP_PROP_SCALE: + astal_wp_wp_set_scale(self, g_value_get_enum(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void astal_wp_wp_object_added(AstalWpWp *self, gpointer object) { + // print pipewire properties + // WpIterator *iter = wp_pipewire_object_new_properties_iterator(WP_PIPEWIRE_OBJECT(object)); + // GValue item = G_VALUE_INIT; + // const gchar *key, *value; + // + // g_print("\n\n"); + // while (wp_iterator_next (iter, &item)) { + // WpPropertiesItem *pi = g_value_get_boxed (&item); + // key = wp_properties_item_get_key (pi); + // value = wp_properties_item_get_value (pi); + // g_print("%s: %s\n", key, value); + // g_value_unset(&item); + // } + + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + + if (WP_IS_NODE(object)) { + WpNode *node = WP_NODE(object); + AstalWpEndpoint *endpoint = + astal_wp_endpoint_create(node, priv->mixer, priv->defaults, self); + + g_hash_table_insert(priv->endpoints, + GUINT_TO_POINTER(wp_proxy_get_bound_id(WP_PROXY(node))), endpoint); + + g_signal_emit_by_name(self, "endpoint-added", endpoint); + g_object_notify(G_OBJECT(self), "endpoints"); + } else if (WP_IS_DEVICE(object)) { + WpDevice *node = WP_DEVICE(object); + AstalWpDevice *device = astal_wp_device_create(node); + g_hash_table_insert(priv->devices, GUINT_TO_POINTER(wp_proxy_get_bound_id(WP_PROXY(node))), + device); + g_signal_emit_by_name(self, "device-added", device); + g_object_notify(G_OBJECT(self), "devices"); + } +} + +static void astal_wp_wp_object_removed(AstalWpWp *self, gpointer object) { + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + + if (WP_IS_NODE(object)) { + guint id = wp_proxy_get_bound_id(WP_PROXY(object)); + AstalWpEndpoint *endpoint = + g_object_ref(g_hash_table_lookup(priv->endpoints, GUINT_TO_POINTER(id))); + + g_hash_table_remove(priv->endpoints, GUINT_TO_POINTER(id)); + + g_signal_emit_by_name(self, "endpoint-removed", endpoint); + g_object_notify(G_OBJECT(self), "endpoints"); + g_object_unref(endpoint); + } else if (WP_IS_DEVICE(object)) { + guint id = wp_proxy_get_bound_id(WP_PROXY(object)); + AstalWpDevice *device = + g_object_ref(g_hash_table_lookup(priv->devices, GUINT_TO_POINTER(id))); + g_hash_table_remove(priv->devices, GUINT_TO_POINTER(id)); + + g_signal_emit_by_name(self, "device-removed", device); + g_object_notify(G_OBJECT(self), "devices"); + g_object_unref(device); + } +} + +static void astal_wp_wp_objm_installed(AstalWpWp *self) { + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + + astal_wp_endpoint_init_as_default(self->default_speaker, priv->mixer, priv->defaults, + ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER, self); + astal_wp_endpoint_init_as_default(self->default_microphone, priv->mixer, priv->defaults, + ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE, self); +} + +static void astal_wp_wp_plugin_activated(WpObject *obj, GAsyncResult *result, AstalWpWp *self) { + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + + GError *error = NULL; + wp_object_activate_finish(obj, result, &error); + if (error) { + g_critical("Failed to activate component: %s\n", error->message); + return; + } + + if (--priv->pending_plugins == 0) { + priv->defaults = wp_plugin_find(priv->core, "default-nodes-api"); + priv->mixer = wp_plugin_find(priv->core, "mixer-api"); + g_object_set(priv->mixer, "scale", self->scale, NULL); + + g_signal_connect_swapped(priv->obj_manager, "object-added", + G_CALLBACK(astal_wp_wp_object_added), self); + g_signal_connect_swapped(priv->obj_manager, "object-removed", + G_CALLBACK(astal_wp_wp_object_removed), self); + + wp_core_install_object_manager(priv->core, priv->obj_manager); + } +} + +static void astal_wp_wp_plugin_loaded(WpObject *obj, GAsyncResult *result, AstalWpWp *self) { + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + + GError *error = NULL; + wp_core_load_component_finish(priv->core, result, &error); + if (error) { + g_critical("Failed to load component: %s\n", error->message); + return; + } + + wp_object_activate(obj, WP_PLUGIN_FEATURE_ENABLED, NULL, + (GAsyncReadyCallback)astal_wp_wp_plugin_activated, self); +} + +/** + * astal_wp_wp_get_default + * + * Returns: (nullable) (transfer none): gets the default wireplumber object. + */ +AstalWpWp *astal_wp_wp_get_default() { + static AstalWpWp *self = NULL; + + if (self == NULL) self = g_object_new(ASTAL_WP_TYPE_WP, NULL); + + return self; +} + +/** + * astal_wp_get_default_wp + * + * Returns: (nullable) (transfer none): gets the default wireplumber object. + */ +AstalWpWp *astal_wp_get_default_wp() { return astal_wp_wp_get_default(); } + +static void astal_wp_wp_dispose(GObject *object) { + AstalWpWp *self = ASTAL_WP_WP(object); + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + + g_clear_object(&self->video); + g_clear_object(&self->audio); + + wp_core_disconnect(priv->core); + g_clear_object(&self->default_speaker); + g_clear_object(&self->default_microphone); + g_clear_object(&priv->mixer); + g_clear_object(&priv->defaults); + g_clear_object(&priv->obj_manager); + g_clear_object(&priv->core); + + if (priv->endpoints != NULL) { + g_hash_table_destroy(priv->endpoints); + priv->endpoints = NULL; + } +} + +static void astal_wp_wp_finalize(GObject *object) { + AstalWpWp *self = ASTAL_WP_WP(object); + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); +} + +static void astal_wp_wp_init(AstalWpWp *self) { + AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + + priv->endpoints = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref); + priv->devices = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref); + + wp_init(7); + priv->core = wp_core_new(NULL, NULL, NULL); + + if (!wp_core_connect(priv->core)) { + g_critical("could not connect to PipeWire\n"); + return; + } + + priv->obj_manager = wp_object_manager_new(); + wp_object_manager_request_object_features(priv->obj_manager, WP_TYPE_NODE, + WP_OBJECT_FEATURES_ALL); + wp_object_manager_request_object_features(priv->obj_manager, WP_TYPE_GLOBAL_PROXY, + WP_OBJECT_FEATURES_ALL); + + wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, + "media.class", "=s", "Audio/Sink", NULL); + wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, + "media.class", "=s", "Audio/Source", NULL); + wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, + "media.class", "=s", "Stream/Output/Audio", NULL); + wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, + "media.class", "=s", "Stream/Input/Audio", NULL); + wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_DEVICE, + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "media.class", "=s", + "Audio/Device", NULL); + + wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, + "media.class", "=s", "Video/Sink", NULL); + wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, + "media.class", "=s", "Video/Source", NULL); + wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, + "media.class", "=s", "Stream/Output/Video", NULL); + wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, + "media.class", "=s", "Stream/Input/Video", NULL); + wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_DEVICE, + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "media.class", "=s", + "Video/Device", NULL); + // wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_CLIENT, NULL); + + g_signal_connect_swapped(priv->obj_manager, "installed", (GCallback)astal_wp_wp_objm_installed, + self); + + self->default_speaker = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL); + self->default_microphone = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL); + + self->audio = astal_wp_audio_new(self); + self->video = astal_wp_video_new(self); + + priv->pending_plugins = 2; + wp_core_load_component(priv->core, "libwireplumber-module-default-nodes-api", "module", NULL, + "default-nodes-api", NULL, + (GAsyncReadyCallback)astal_wp_wp_plugin_loaded, self); + wp_core_load_component(priv->core, "libwireplumber-module-mixer-api", "module", NULL, + "mixer-api", NULL, (GAsyncReadyCallback)astal_wp_wp_plugin_loaded, self); +} + +static void astal_wp_wp_class_init(AstalWpWpClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + object_class->finalize = astal_wp_wp_finalize; + object_class->dispose = astal_wp_wp_dispose; + object_class->get_property = astal_wp_wp_get_property; + object_class->set_property = astal_wp_wp_set_property; + + astal_wp_wp_properties[ASTAL_WP_WP_PROP_AUDIO] = + g_param_spec_object("audio", "audio", "audio", ASTAL_WP_TYPE_AUDIO, G_PARAM_READABLE); + astal_wp_wp_properties[ASTAL_WP_WP_PROP_VIDEO] = + g_param_spec_object("video", "video", "video", ASTAL_WP_TYPE_VIDEO, G_PARAM_READABLE); + /** + * AstalWpWp:scale: (type AstalWpScale) + * + * The scale used for the volume + */ + astal_wp_wp_properties[ASTAL_WP_WP_PROP_SCALE] = + g_param_spec_enum("scale", "scale", "scale", ASTAL_WP_TYPE_SCALE, ASTAL_WP_SCALE_CUBIC, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + /** + * AstalWpWp:endpoints: (type GList(AstalWpEndpoint)) (transfer container) + * + * A list of AstalWpEndpoint objects + */ + astal_wp_wp_properties[ASTAL_WP_WP_PROP_ENDPOINTS] = + g_param_spec_pointer("endpoints", "endpoints", "endpoints", G_PARAM_READABLE); + /** + * AstalWpWp:devices: (type GList(AstalWpDevice)) (transfer container) + * + * A list of AstalWpDevice objects + */ + astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEVICES] = + g_param_spec_pointer("devices", "devices", "devices", G_PARAM_READABLE); + /** + * AstalWpWp:default-speaker: + * + * The AstalWndpoint object representing the default speaker + */ + astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEFAULT_SPEAKER] = + g_param_spec_object("default-speaker", "default-speaker", "default-speaker", + ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); + /** + * AstalWpWp:default-microphone: + * + * The AstalWndpoint object representing the default speaker + */ + astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE] = + g_param_spec_object("default-microphone", "default-microphone", "default-microphone", + ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); + + g_object_class_install_properties(object_class, ASTAL_WP_WP_N_PROPERTIES, + astal_wp_wp_properties); + + astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_ENDPOINT_ADDED] = + g_signal_new("endpoint-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_ENDPOINT_REMOVED] = + g_signal_new("endpoint-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); + astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_DEVICE_ADDED] = + g_signal_new("device-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); + astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_DEVICE_REMOVED] = + g_signal_new("device-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); +} diff --git a/lib/wireplumber/version b/lib/wireplumber/version new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/lib/wireplumber/version @@ -0,0 +1 @@ +0.1.0 diff --git a/mpris/.gitignore b/mpris/.gitignore deleted file mode 100644 index f047207..0000000 --- a/mpris/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -build/ -result -.cache/ -test.sh -tmp/ diff --git a/mpris/LICENSE b/mpris/LICENSE deleted file mode 100644 index 67cd97b..0000000 --- a/mpris/LICENSE +++ /dev/null @@ -1,503 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - diff --git a/mpris/README.md b/mpris/README.md deleted file mode 100644 index b05f3a5..0000000 --- a/mpris/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# astal-mpris - -Library and cli tool for controlling media players - -## TODO - -- docs diff --git a/mpris/flake.lock b/mpris/flake.lock deleted file mode 100644 index 0821cb0..0000000 --- a/mpris/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1720768451, - "narHash": "sha256-EYekUHJE2gxeo2pM/zM9Wlqw1Uw2XTJXOSAO79ksc4Y=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/mpris/flake.nix b/mpris/flake.nix deleted file mode 100644 index fbacb73..0000000 --- a/mpris/flake.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ - description = "Library and cli tool for controlling media players"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - - outputs = { self, nixpkgs }: let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs { inherit system; }; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - ]; - - buildInputs = with pkgs; [ - gvfs - glib - json-glib - ]; - in { - packages.${system} = rec { - default = mpris; - mpris = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-mpris"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - mpris = pkgs.mkShell { - inherit nativeBuildInputs; - buildInputs = buildInputs ++ [ - self.packages.${system}.default - pkgs.gjs - ]; - }; - }; - }; -} diff --git a/mpris/meson.build b/mpris/meson.build deleted file mode 100644 index d1ab98f..0000000 --- a/mpris/meson.build +++ /dev/null @@ -1,19 +0,0 @@ -project( - 'astal-mpris', - 'vala', - 'c', - version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), - meson_version: '>= 0.62.0', - default_options: [ - 'warning_level=2', - 'werror=false', - 'c_std=gnu11', - ], -) - -assert( - get_option('lib') or get_option('cli'), - 'Either lib or cli option must be set to true.', -) - -subdir('src') diff --git a/mpris/meson_options.txt b/mpris/meson_options.txt deleted file mode 100644 index f110242..0000000 --- a/mpris/meson_options.txt +++ /dev/null @@ -1,11 +0,0 @@ -option( - 'lib', - type: 'boolean', - value: true, -) - -option( - 'cli', - type: 'boolean', - value: true, -) diff --git a/mpris/src/cli.vala b/mpris/src/cli.vala deleted file mode 100644 index b71def9..0000000 --- a/mpris/src/cli.vala +++ /dev/null @@ -1,331 +0,0 @@ -namespace AstalMpris { -static bool help; -static bool version; -static bool list; -static bool raw; -[CCode (array_length = false, array_null_terminated = true)] -static string[] players; - -const OptionEntry[] options = { - { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, - { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, - { "player", 'p', OptionFlags.NONE, OptionArg.STRING_ARRAY, ref players, null, null }, - { "list", 'l', OptionFlags.NONE, OptionArg.NONE, ref list, null, null }, - { "raw", 'r', OptionFlags.NONE, OptionArg.NONE, ref raw, null, null }, - { null }, -}; - -int main(string[] argv) { - try { - var opts = new OptionContext(); - opts.add_main_entries(options, null); - opts.set_help_enabled(false); - opts.set_ignore_unknown_options(false); - opts.parse(ref argv); - } catch (OptionError err) { - printerr(err.message); - return 1; - } - - if (help) { - print("Usage:\n"); - print(" %s [flags] [command]\n\n", argv[0]); - print("Flags:\n"); - print(" -h, --help Print this help and exit\n"); - print(" -v, --version Print version number and exit\n"); - print(" -l, --list List available players\n"); - print(" -p, --player Operate on given player\n"); - print(" -r, --raw Print single line json info\n"); - print("\nCommands:\n"); - print(" info Print info about player\n"); - print(" monitor Monitor changes\n"); - print(" play Play track\n"); - print(" pause Pause track\n"); - print(" play-pause Play if paused, Pause if playing\n"); - print(" stop Stop player\n"); - print(" next Play next track\n"); - print(" previous Play previous track\n"); - print(" quit Quit player\n"); - print(" raise Ask compositor to raise the player\n"); - print(" position [OFFSET][+/-/%] Set position of player\n"); - print(" volume [LEVEL][+/-/%] Set volume of player\n"); - print(" loop [STATUS] One of: \"None\", \"Track\", \"Playlist\"\n"); - print(" shuffle [STATUS] One of: \"On\", \"Off\", \"Toggle\"\n"); - return 0; - } - - if (version) { - print(VERSION); - return 0; - } - - var mpris = new Mpris(); - var mpris_players = new List(); - - if (list) { - foreach (var p in mpris.players) - print("%s\n", p.bus_name.replace(Mpris.PREFIX, "")); - - return 0; - } - - if (players.length > 0) { - foreach (var name in players) - mpris_players.append(new Player(name)); - } else { - foreach (var p in mpris.players) - mpris_players.append(p); - } - - var cmd = argv[1]; - var arg = argv[2]; - - switch (cmd) { - case "monitor": - return do_monitor(mpris); - - case "info": - print_players(mpris_players.copy()); - break; - - case "play": - foreach (var player in mpris_players) - player.play(); - break; - - case "pause": - foreach (var player in mpris_players) - player.pause(); - break; - - case "play-pause": - foreach (var player in mpris_players) - player.play_pause(); - break; - - case "stop": - foreach (var player in mpris_players) - player.stop(); - break; - - case "next": - foreach (var player in mpris_players) - player.next(); - break; - - case "previous": - foreach (var player in mpris_players) - player.previous(); - break; - - case "raise": - foreach (var player in mpris_players) - player.raise(); - break; - - case "quit": - foreach (var player in mpris_players) - player.quit(); - break; - - case "position": - foreach (var player in mpris_players) { - if (do_position(player, arg) != 0) - return 1; - } - break; - - case "volume": - foreach (var player in mpris_players) { - if (do_volume(player, arg) != 0) - return 1; - } - break; - - case "loop": - foreach (var player in mpris_players) { - if (do_loop(player, arg) != 0) - return 1; - } - break; - - case "shuffle": - foreach (var player in mpris_players) { - if (do_shuffle(player, arg) != 0) - return 1; - } - break; - - case "open": - if (arg == null) { - stderr.printf("missing open arg"); - return 1; - } - - foreach (var player in mpris_players) - player.open_uri(arg); - break; - - default: - if (cmd == null) - stderr.printf("missing command\n"); - else - stderr.printf(@"unknown command \"$cmd\"\n"); - return 1; - } - - return 0; -} - -Json.Node to_json(Player p) { - var uris = new Json.Builder().begin_array(); - foreach (var uri in p.supported_uri_schemas) - uris.add_string_value(uri); - - uris.end_array(); - - return new Json.Builder().begin_object() - .set_member_name("bus_name").add_string_value(p.bus_name) - .set_member_name("available").add_boolean_value(p.available) - .set_member_name("identity").add_string_value(p.identity) - .set_member_name("entry").add_string_value(p.entry) - .set_member_name("supported_uri_schemas").add_value(uris.get_root()) - .set_member_name("loop_status").add_string_value(p.loop_status.to_string()) - .set_member_name("shuffle_status").add_string_value(p.shuffle_status.to_string()) - .set_member_name("rate").add_double_value(p.rate) - .set_member_name("volume").add_double_value(p.volume) - .set_member_name("position").add_double_value(p.position) - .set_member_name("cover_art").add_string_value(p.cover_art) - .set_member_name("metadata").add_value(Json.gvariant_serialize( - p.metadata != null ? p.metadata : new HashTable(str_hash, str_equal))) - .end_object() - .get_root(); -} - -void print_players(List players) { - var json = new Json.Builder().begin_array(); - - foreach (var p in players) - json.add_value(to_json(p)); - - stdout.printf("%s\n", Json.to_string(json.end_array().get_root(), !raw)); - stdout.flush(); -} - -int do_monitor(Mpris mpris) { - print_players(mpris.players); - foreach (var player in mpris.players) { - player.notify.connect(() => print_players(mpris.players)); - } - - mpris.player_added.connect((player) => { - player.notify.connect(() => print_players(mpris.players)); - }); - - mpris.player_closed.connect(() => { - print_players(mpris.players); - }); - - new MainLoop(null, false).run(); - return 0; -} - -int do_position(Player player, string? arg) { - if (arg == null) { - stderr.printf("missing position argument\n"); - return 1; - } - - else if (arg.has_suffix("%")) { - var percent = double.parse(arg.slice(0, -1)) / 100; - player.position = player.length * percent; - } - - else if (arg.has_suffix("-")) { - player.position += double.parse(arg.slice(0, -1)) * -1; - } - - else if (arg.has_suffix("+")) { - player.position += double.parse(arg.slice(0, -1)); - } - - else { - player.position = double.parse(arg); - } - - return 0; -} - -int do_volume(Player player, string? arg) { - if (arg == null) { - stderr.printf("missing volume argument\n"); - return 1; - } - - else if (arg.has_suffix("%")) { - player.volume = double.parse(arg.slice(0, -1)) / 100; - } - - else if (arg.has_suffix("-")) { - player.volume += (double.parse(arg.slice(0, -1)) * -1) / 100; - } - - else if (arg.has_suffix("+")) { - player.volume += double.parse(arg.slice(0, -1)) / 100; - } - - else { - player.volume = double.parse(arg); - } - - return 0; -} - -int do_loop(Player player, string? arg) { - if (arg == null) { - player.loop(); - return 0; - } - - switch (arg) { - case "None": - player.loop_status = Loop.NONE; - break; - case "Track": - player.loop_status = Loop.TRACK; - break; - case "Playlist": - player.loop_status = Loop.PLAYLIST; - break; - default: - stderr.printf(@"unknown shuffle status \"$arg\""); - return 1; - } - - return 0; -} - -int do_shuffle(Player player, string? arg) { - if (arg == null) { - player.shuffle(); - return 1; - } - - switch (arg) { - case "On": - player.shuffle_status = Shuffle.ON; - break; - case "Off": - player.shuffle_status = Shuffle.OFF; - break; - case "Toggle": - player.shuffle(); - break; - default: - stderr.printf(@"unknown shuffle status \"$arg\""); - return 1; - } - - return 0; -} -} diff --git a/mpris/src/config.vala.in b/mpris/src/config.vala.in deleted file mode 100644 index 767c4bd..0000000 --- a/mpris/src/config.vala.in +++ /dev/null @@ -1,6 +0,0 @@ -namespace AstalMpris { - public const int MAJOR_VERSION = @MAJOR_VERSION@; - public const int MINOR_VERSION = @MINOR_VERSION@; - public const int MICRO_VERSION = @MICRO_VERSION@; - public const string VERSION = "@VERSION@"; -} diff --git a/mpris/src/ifaces.vala b/mpris/src/ifaces.vala deleted file mode 100644 index 4a9d715..0000000 --- a/mpris/src/ifaces.vala +++ /dev/null @@ -1,60 +0,0 @@ -namespace AstalMpris { -[DBus (name="org.freedesktop.DBus")] -internal interface DBusImpl : DBusProxy { - public abstract string[] list_names () throws GLib.Error; - public signal void name_owner_changed (string name, string old_owner, string new_owner); -} - -[DBus (name="org.freedesktop.DBus.Properties")] -internal interface PropsIface : DBusProxy { - public abstract HashTable get_all (string iface); -} - -[DBus (name="org.mpris.MediaPlayer2")] -internal interface IMpris : PropsIface { - public abstract void raise () throws GLib.Error; - public abstract void quit () throws GLib.Error; - - public abstract bool can_quit { get; } - public abstract bool fullscreen { get; set; } - public abstract bool can_set_fullscreen { get; } - public abstract bool can_raise { get; } - public abstract bool has_track_list { get; } - public abstract string identity { owned get; } - public abstract string desktop_entry { owned get; } - public abstract string[] supported_uri_schemas { owned get; } - public abstract string[] supported_mime_types { owned get; } -} - -[DBus (name="org.mpris.MediaPlayer2.Player")] -internal interface IPlayer : IMpris { - public abstract void next () throws GLib.Error; - public abstract void previous () throws GLib.Error; - public abstract void pause () throws GLib.Error; - public abstract void play_pause () throws GLib.Error; - public abstract void stop () throws GLib.Error; - public abstract void play () throws GLib.Error; - public abstract void seek (int64 offset) throws GLib.Error; - public abstract void set_position (ObjectPath track_id, int64 position) throws GLib.Error; - public abstract void open_uri (string uri) throws GLib.Error; - - public signal void seeked (int64 position); - - public abstract string playback_status { owned get; } - public abstract string loop_status { owned get; set; } - public abstract double rate { get; set; } - public abstract bool shuffle { get; set; } - public abstract HashTable metadata { owned get; } - public abstract double volume { get; set; } - public abstract int64 position { get; } - public abstract double minimum_rate { get; set; } - public abstract double maximum_rate { get; set; } - - public abstract bool can_go_next { get; } - public abstract bool can_go_previous { get; } - public abstract bool can_play { get; } - public abstract bool can_pause { get; } - public abstract bool can_seek { get; } - public abstract bool can_control { get; } -} -} diff --git a/mpris/src/meson.build b/mpris/src/meson.build deleted file mode 100644 index 9b3c51f..0000000 --- a/mpris/src/meson.build +++ /dev/null @@ -1,77 +0,0 @@ -version_split = meson.project_version().split('.') -api_version = version_split[0] + '.' + version_split[1] -gir = 'AstalMpris-' + api_version + '.gir' -typelib = 'AstalMpris-' + api_version + '.typelib' -so = 'lib' + meson.project_name() + '.so.' + meson.project_version() - -config = configure_file( - input: 'config.vala.in', - output: 'config.vala', - configuration: { - 'VERSION': meson.project_version(), - 'MAJOR_VERSION': version_split[0], - 'MINOR_VERSION': version_split[1], - 'MICRO_VERSION': version_split[2], - }, -) - -deps = [ - dependency('glib-2.0'), - dependency('gobject-2.0'), - dependency('json-glib-1.0'), -] - -sources = [ - config, - 'ifaces.vala', - 'player.vala', - 'mpris.vala', -] - -if get_option('lib') - lib = library( - meson.project_name(), - sources, - dependencies: deps, - vala_header: meson.project_name() + '.h', - vala_vapi: meson.project_name() + '-' + api_version + '.vapi', - vala_gir: gir, - version: meson.project_version(), - install: true, - install_dir: [true, true, true, true], - ) - - import('pkgconfig').generate( - lib, - name: meson.project_name(), - filebase: meson.project_name() + '-' + api_version, - version: meson.project_version(), - subdirs: meson.project_name(), - requires: deps, - install_dir: get_option('libdir') / 'pkgconfig', - ) - - custom_target( - typelib, - command: [ - find_program('g-ir-compiler'), - '--output', '@OUTPUT@', - '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', - meson.current_build_dir() / gir, - ], - input: lib, - output: typelib, - depends: lib, - install: true, - install_dir: get_option('libdir') / 'girepository-1.0', - ) -endif - -if get_option('cli') - executable( - meson.project_name(), - ['cli.vala', sources], - dependencies: deps, - install: true, - ) -endif diff --git a/mpris/src/mpris.vala b/mpris/src/mpris.vala deleted file mode 100644 index 0e55a2e..0000000 --- a/mpris/src/mpris.vala +++ /dev/null @@ -1,66 +0,0 @@ -namespace AstalMpris { -public Mpris get_default() { - return Mpris.get_default(); -} - -public class Mpris : Object { - internal static string PREFIX = "org.mpris.MediaPlayer2."; - - private static Mpris instance; - public static Mpris get_default() { - if (instance == null) - instance = new Mpris(); - - return instance; - } - - private DBusImpl proxy; - - private HashTable _players = - new HashTable (str_hash, str_equal); - - public List players { owned get { return _players.get_values(); } } - - public signal void player_added (Player player); - public signal void player_closed (Player player); - - construct { - try { - proxy = Bus.get_proxy_sync( - BusType.SESSION, - "org.freedesktop.DBus", - "/org/freedesktop/DBus" - ); - - foreach (var busname in proxy.list_names()) { - if (busname.has_prefix(Mpris.PREFIX)) - add_player(busname); - } - - proxy.name_owner_changed.connect((name, old_owner, new_owner) => { - if (!name.has_prefix(Mpris.PREFIX)) - return; - - if (new_owner != "" && old_owner == "") - add_player(name); - }); - } catch (Error error) { - critical(error.message); - } - } - - private void add_player(string busname) { - var p = new Player(busname); - _players.set(busname, p); - - p.closed.connect(() => { - player_closed(p); - _players.remove(busname); - notify_property("players"); - }); - - player_added(p); - notify_property("players"); - } -} -} diff --git a/mpris/src/player.vala b/mpris/src/player.vala deleted file mode 100644 index ed146f6..0000000 --- a/mpris/src/player.vala +++ /dev/null @@ -1,467 +0,0 @@ -namespace AstalMpris { -public class Player : Object { - private static string COVER_CACHE = Environment.get_user_cache_dir() + "/astal/mpris"; - - private IPlayer proxy; - - public signal void appeared () { available = true; } - public signal void closed () { available = false; } - - // identifiers - public string bus_name { owned get; construct set; } - public bool available { get; private set; } - - // periodically notify position - private uint pollid; - - // mpris - public void raise() { - try { proxy.raise(); } catch (Error error) { critical(error.message); } - } - - public void quit() { - try { proxy.quit(); } catch (Error error) { critical(error.message); } - } - - public bool can_quit { get; private set; } - public bool fullscreen { get; private set; } - public bool can_set_fullscreen { get; private set; } - public bool can_raise { get; private set; } - public bool has_track_list { get; private set; } - public string identity { owned get; private set; } - public string entry { owned get; private set; } - public string[] supported_uri_schemas { owned get; private set; } - public string[] supported_mime_types { owned get; private set; } - - public void toggle_fullscreen() { - if (!can_set_fullscreen) - critical("can not set fullscreen on " + bus_name); - - proxy.fullscreen = !fullscreen; - } - - // player - public void next() { - try { proxy.next(); } catch (Error error) { critical(error.message); } - } - - public void previous() { - try { proxy.previous(); } catch (Error error) { critical(error.message); } - } - - public void pause() { - try { proxy.pause(); } catch (Error error) { critical(error.message); } - } - - public void play_pause() { - try { proxy.play_pause(); } catch (Error error) { critical(error.message); } - } - - public void stop() { - try { proxy.stop(); } catch (Error error) { critical(error.message); } - } - - public void play() { - try { proxy.play(); } catch (Error error) { critical(error.message); } - } - - public void open_uri(string uri) { - try { proxy.open_uri(uri); } catch (Error error) { critical(error.message); } - } - - public void loop() { - switch (loop_status) { - case Loop.NONE: - loop_status = Loop.TRACK; - break; - case Loop.TRACK: - loop_status = Loop.PLAYLIST; - break; - case Loop.PLAYLIST: - loop_status = Loop.NONE; - break; - default: - break; - } - } - - public void shuffle() { - shuffle_status = shuffle_status == Shuffle.ON - ? Shuffle.OFF - : Shuffle.ON; - } - - public signal void seeked (int64 position); - - public double _get_position() { - try { - var reply = proxy.call_sync( - "org.freedesktop.DBus.Properties.Get", - new Variant("(ss)", - "org.mpris.MediaPlayer2.Player", - "Position" - ), - DBusCallFlags.NONE, - -1, - null - ); - - var body = reply.get_child_value(0); - if (body.classify() == Variant.Class.STRING) { - return -1; // Position not supported - } - - return (double)body.get_variant().get_int64() / 1000000; - } catch (Error err) { - return -1; - } - } - - private void _set_position(double pos) { - try { - proxy.set_position((ObjectPath)trackid, (int64)(pos * 1000000)); - } catch (Error error) { - critical(error.message); - } - } - - private Loop _loop_status = Loop.UNSUPPORTED; - private double _rate; - private Shuffle _shuffle_status = Shuffle.UNSUPPORTED; - private double _volume = -1; - - public Loop loop_status { - get { return _loop_status; } - set { proxy.loop_status = value.to_string(); } - } - - public double rate { - get { return _rate; } - set { proxy.rate = value; } - } - - public Shuffle shuffle_status { - get { return _shuffle_status; } - set { proxy.shuffle = value == Shuffle.ON; } - } - - public double volume { - get { return _volume; } - set { proxy.volume = value; } - } - - public double position { - get { return _get_position(); } - set { _set_position(value); } - } - - public PlaybackStatus playback_status { get; private set; } - public double minimum_rate { get; private set; } - public double maximum_rate { get; private set; } - public bool can_go_next { get; private set; } - public bool can_go_previous { get; private set; } - public bool can_play { get; private set; } - public bool can_pause { get; private set; } - public bool can_seek { get; private set; } - public bool can_control { get; private set; } - - // metadata - [CCode (notify = false)] - public HashTable metadata { owned get; private set; } - - public string trackid { owned get; private set; } - public double length { get; private set; } - public string art_url { owned get; private set; } - - public string album { owned get; private set; } - public string album_artist { owned get; private set; } - public string artist { owned get; private set; } - public string lyrics { owned get; private set; } - public string title { owned get; private set; } - public string composer { owned get; private set; } - public string comments { owned get; private set; } - - // cached cover art - public string cover_art { owned get; private set; } - - public Player(string name) { - Object(bus_name: name.has_prefix("org.mpris.MediaPlayer2.") - ? name : "org.mpris.MediaPlayer2." + name); - } - - private void sync() { - // mpris - can_quit = proxy.can_quit; - fullscreen = proxy.fullscreen; - can_set_fullscreen = proxy.can_set_fullscreen; - can_raise = proxy.can_raise; - has_track_list = proxy.has_track_list; - identity = proxy.identity; - entry = proxy.desktop_entry; - supported_uri_schemas = proxy.supported_uri_schemas; - supported_mime_types = proxy.supported_mime_types; - - if (position >= 0) - notify_property("position"); - - // LoopStatus and Shuffle are optional props - var props = proxy.get_all("org.mpris.MediaPlayer2.Player"); - - // player - if (props != null && props.get("LoopStatus") != null) { - if (loop_status != Loop.from_string(proxy.loop_status)) { - _loop_status = Loop.from_string(proxy.loop_status); - notify_property("loop-status"); - } - } - - if (rate != proxy.rate) { - _rate = proxy.rate; - notify_property("rate"); - } - - if (props != null && props.get("Shuffle") != null) { - if (shuffle_status != Shuffle.from_bool(proxy.shuffle)) { - _shuffle_status = Shuffle.from_bool(proxy.shuffle); - notify_property("shuffle-status"); - } - } - - if (volume != proxy.volume) { - _volume = proxy.volume; - notify_property("volume"); - } - - playback_status = PlaybackStatus.from_string(proxy.playback_status); - minimum_rate = proxy.minimum_rate; - maximum_rate = proxy.maximum_rate; - can_go_next = proxy.can_go_next; - can_go_previous = proxy.can_go_previous; - can_play = proxy.can_play; - can_pause = proxy.can_pause; - can_seek = proxy.can_seek; - can_control = proxy.can_control; - - // metadata - metadata = proxy.metadata; - if (metadata != null) { - if (metadata.get("mpris:length") != null) - length = (double)metadata.get("mpris:length").get_uint64() / 1000000; - else - length = -1; - - trackid = get_str("mpris:trackid"); - art_url = get_str("mpris:artUrl"); - album = get_str("xesam:album"); - lyrics = get_str("xesam:asText"); - title = get_str("xesam:title"); - album_artist = join_strv("xesam:albumArtist", ", "); - artist = join_strv("xesam:artist", ", "); - comments = join_strv("xesam:comments", "\n"); - composer = join_strv("xesam:composer", ", "); - cache_cover.begin((_, res) => cache_cover.end(res)); - notify_property("metadata"); - } - } - - private async void cache_cover() { - if (art_url == null || art_url == "") - return; - - var file = File.new_for_uri(art_url); - if (file.get_path() != null) { - cover_art = file.get_path(); - return; - } - - var path = COVER_CACHE + "/" + Checksum.compute_for_string(ChecksumType.SHA1, art_url, -1); - if (FileUtils.test(path, FileTest.EXISTS)) { - cover_art = path; - return; - } - - try { - if (!FileUtils.test(COVER_CACHE, FileTest.IS_DIR)) - File.new_for_path(COVER_CACHE).make_directory_with_parents(null); - - file.copy_async.begin( - File.new_for_path(path), - FileCopyFlags.OVERWRITE, - Priority.DEFAULT, - null, - null, - (_, res) => { - try { - file.copy_async.end(res); - cover_art = path; - } catch (Error err) { - critical("Failed to cache cover art with url \"%s\": %s", art_url, err.message); - } - } - ); - } catch (Error err) { - critical(err.message); - } - } - - public Variant? get_meta(string key) { - return metadata.lookup(key); - } - - private string get_str(string key) { - if (metadata.get(key) == null) - return ""; - - var str = metadata.get(key).get_string(null); - return str == null ? "" : str; - } - - private string? join_strv(string key, string sep) { - if (metadata.get(key) == null) - return null; - - var arr = metadata.get(key).get_strv(); - if (arr.length == 0) - return null; - - var builder = new StringBuilder(); - for (var i = 0; i < arr.length; ++i) { - builder.append(arr[i]); - if (i + 1 < arr.length) - builder.append(sep); - } - - return builder.str; - } - - construct { - try { - try_proxy(); - sync(); - } catch (Error error) { - critical(error.message); - } - } - - public void try_proxy() throws Error { - if (proxy != null) - return; - - proxy = Bus.get_proxy_sync( - BusType.SESSION, - bus_name, - "/org/mpris/MediaPlayer2" - ); - - if (proxy.g_name_owner != null) - appeared(); - - proxy.notify["g-name-owner"].connect(() => { - if (proxy.g_name_owner != null) - appeared(); - else - 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() { - Source.remove(pollid); - } -} - -public enum PlaybackStatus { - PLAYING, - PAUSED, - STOPPED; - - public static PlaybackStatus from_string(string? str) { - switch (str) { - case "Playing": - return PLAYING; - case "Paused": - return PAUSED; - case "Stopped": - default: - return STOPPED; - } - } - - public string to_string() { - switch (this) { - case PLAYING: - return "Playing"; - case PAUSED: - return "Paused"; - case STOPPED: - default: - return "Stopped"; - } - } -} - -public enum Loop { - UNSUPPORTED, - NONE, - TRACK, - PLAYLIST; - - public static Loop from_string(string? str) { - switch (str) { - case "None": - return NONE; - case "Track": - return TRACK; - case "Playlist": - return PLAYLIST; - default: - return UNSUPPORTED; - } - } - - public string? to_string() { - switch (this) { - case NONE: - return "None"; - case TRACK: - return "Track"; - case PLAYLIST: - return "Playlist"; - default: - return "Unsupported"; - } - } -} - -public enum Shuffle { - UNSUPPORTED, - ON, - OFF; - - public static Shuffle from_bool(bool b) { - return b ? Shuffle.ON : Shuffle.OFF; - } - - public string? to_string() { - switch (this) { - case OFF: - return "Off"; - case ON: - return "On"; - default: - return "Unsupported"; - } - } -} -} diff --git a/mpris/version b/mpris/version deleted file mode 100644 index 6e8bf73..0000000 --- a/mpris/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/network/.gitignore b/network/.gitignore deleted file mode 100644 index f047207..0000000 --- a/network/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -build/ -result -.cache/ -test.sh -tmp/ diff --git a/network/LICENSE b/network/LICENSE deleted file mode 100644 index 67cd97b..0000000 --- a/network/LICENSE +++ /dev/null @@ -1,503 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - diff --git a/network/README.md b/network/README.md deleted file mode 100644 index 71151e5..0000000 --- a/network/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# network - -NetworkManager wrapper library - -## TODO - -- docs diff --git a/network/flake.lock b/network/flake.lock deleted file mode 100644 index 13f566b..0000000 --- a/network/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1716137900, - "narHash": "sha256-sowPU+tLQv8GlqtVtsXioTKeaQvlMz/pefcdwg8MvfM=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "6c0b7a92c30122196a761b440ac0d46d3d9954f1", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/network/flake.nix b/network/flake.nix deleted file mode 100644 index f8cf3dc..0000000 --- a/network/flake.nix +++ /dev/null @@ -1,53 +0,0 @@ -{ - description = "NetworkManager wrapper library"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - - outputs = { - self, - nixpkgs, - }: let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs {inherit system;}; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - ]; - - buildInputs = with pkgs; [ - glib - networkmanager - ]; - in { - packages.${system} = rec { - default = network; - network = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-network"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - network = pkgs.mkShell { - inherit nativeBuildInputs; - buildInputs = - buildInputs - ++ [ - self.packages.${system}.default - pkgs.gjs - ]; - }; - }; - }; -} diff --git a/network/meson.build b/network/meson.build deleted file mode 100644 index a9dc9c3..0000000 --- a/network/meson.build +++ /dev/null @@ -1,14 +0,0 @@ -project( - 'astal-network', - 'vala', - 'c', - version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), - meson_version: '>= 0.62.0', - default_options: [ - 'warning_level=2', - 'werror=false', - 'c_std=gnu11', - ], -) - -subdir('src') diff --git a/network/src/accesspoint.vala b/network/src/accesspoint.vala deleted file mode 100644 index 3c51018..0000000 --- a/network/src/accesspoint.vala +++ /dev/null @@ -1,49 +0,0 @@ -public class AstalNetwork.AccessPoint : Object { - private Wifi wifi; - private NM.AccessPoint ap; - - public uint bandwidth { get { return ap.bandwidth; } } - public string bssid { owned get { return ap.bssid; } } - public uint frequency { get { return ap.frequency; } } - public int last_seen { get { return ap.last_seen; } } - public uint max_bitrate { get { return ap.max_bitrate; } } - public uint8 strength { get { return ap.strength; } } - public string icon_name { get; private set; } - public NM.80211Mode mode { get { return ap.mode; } } - public NM.80211ApFlags flags { get { return ap.flags; } } - public NM.80211ApSecurityFlags rsn_flags { get { return ap.rsn_flags; } } - public NM.80211ApSecurityFlags wpa_flags { get { return ap.wpa_flags; } } - - public string? ssid { - owned get { - if (ap.ssid == null) - return null; - - return (string)NM.Utils.ssid_to_utf8(ap.ssid.get_data()); - } - } - - internal AccessPoint(Wifi wifi, NM.AccessPoint ap) { - this.wifi = wifi; - this.ap = ap; - ap.notify.connect((pspec) => { - if (get_class().find_property(pspec.name) != null) - notify_property(pspec.name); - if (pspec.name == "strength") - icon_name = _icon(); - }); - icon_name = _icon(); - } - - private string _icon() { - if (strength >= 80) return Wifi.ICON_EXCELLENT; - if (strength >= 60) return Wifi.ICON_GOOD; - if (strength >= 40) return Wifi.ICON_OK; - if (strength >= 20) return Wifi.ICON_WEAK; - return Wifi.ICON_NONE; - } - - // TODO: connect to ap - // public signal void auth(); - // public void try_connect(string? password) { } -} diff --git a/network/src/config.vala.in b/network/src/config.vala.in deleted file mode 100644 index dbec0f3..0000000 --- a/network/src/config.vala.in +++ /dev/null @@ -1,6 +0,0 @@ -namespace AstalNetwork { - public const int MAJOR_VERSION = @MAJOR_VERSION@; - public const int MINOR_VERSION = @MINOR_VERSION@; - public const int MICRO_VERSION = @MICRO_VERSION@; - public const string VERSION = "@VERSION@"; -} diff --git a/network/src/meson.build b/network/src/meson.build deleted file mode 100644 index 3294ae3..0000000 --- a/network/src/meson.build +++ /dev/null @@ -1,67 +0,0 @@ -version_split = meson.project_version().split('.') -api_version = version_split[0] + '.' + version_split[1] -gir = 'AstalNetwork-' + api_version + '.gir' -typelib = 'AstalNetwork-' + api_version + '.typelib' - -config = configure_file( - input: 'config.vala.in', - output: 'config.vala', - configuration: { - 'VERSION': meson.project_version(), - 'MAJOR_VERSION': version_split[0], - 'MINOR_VERSION': version_split[1], - 'MICRO_VERSION': version_split[2], - }, -) - -deps = [ - dependency('glib-2.0'), - dependency('gobject-2.0'), - dependency('libnm') -] - -sources = [ - config, - 'network.vala', - 'wifi.vala', - 'wired.vala', - 'wired.vala', - 'accesspoint.vala', -] - -lib = library( - meson.project_name(), - sources, - dependencies: deps, - vala_header: meson.project_name() + '.h', - vala_vapi: meson.project_name() + '-' + api_version + '.vapi', - vala_gir: gir, - version: meson.project_version(), - install: true, - install_dir: [true, true, true, true], -) - -import('pkgconfig').generate( - lib, - name: meson.project_name(), - filebase: meson.project_name() + '-' + api_version, - version: meson.project_version(), - subdirs: meson.project_name(), - requires: deps, - install_dir: get_option('libdir') / 'pkgconfig', -) - -custom_target( - typelib, - command: [ - find_program('g-ir-compiler'), - '--output', '@OUTPUT@', - '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', - meson.current_build_dir() / gir, - ], - input: lib, - output: typelib, - depends: lib, - install: true, - install_dir: get_option('libdir') / 'girepository-1.0', -) diff --git a/network/src/network.vala b/network/src/network.vala deleted file mode 100644 index 7c8e466..0000000 --- a/network/src/network.vala +++ /dev/null @@ -1,206 +0,0 @@ -namespace AstalNetwork { - public Network get_default() { - return Network.get_default(); - } -} - -public class AstalNetwork.Network : Object { - private static Network instance; - public static Network get_default() { - if (instance == null) - instance = new Network(); - - return instance; - } - - public NM.Client client { get; private set; } - - public Wifi? wifi { get; private set; } - public Wired? wired { get; private set; } - public Primary primary { get; private set; } - - public Connectivity connectivity { - get { return (Connectivity)client.connectivity; } - } - - public State state { - get { return (State)client.state; } - } - - construct { - try { - client = new NM.Client(); - var wifi_device = (NM.DeviceWifi)get_device(NM.DeviceType.WIFI); - if (wifi_device != null) - wifi = new Wifi(wifi_device); - - var ethernet = (NM.DeviceEthernet)get_device(NM.DeviceType.ETHERNET); - if (ethernet != null) - wired = new Wired(ethernet); - - sync(); - client.notify["primary-connection"].connect(sync); - client.notify["activating-connection"].connect(sync); - - client.notify["state"].connect(() => notify_property("state")); - client.notify["connectivity"].connect(() => notify_property("connectivity")); - } catch (Error err) { - critical(err.message); - } - } - - private NM.Device get_device(NM.DeviceType t) { - var valid = new GenericArray(); - foreach (var device in client.get_devices()) { - if (device.device_type == t) - valid.add(device); - } - - foreach (var device in valid) { - if (device.active_connection != null) - return device; - } - - return valid.get(0); - } - - private void sync() { - var ac = client.get_primary_connection(); - - if (ac == null) - ac = client.get_activating_connection(); - - if (ac != null) - primary = Primary.from_connection_type(ac.type); - else - primary = Primary.UNKNOWN; - } -} - -public enum AstalNetwork.Primary { - UNKNOWN, - WIRED, - WIFI; - - public string to_string() { - switch (this) { - case WIFI: return "wifi"; - case WIRED: return "wired"; - default: return "unknown"; - } - } - - public static Primary from_connection_type(string type) { - switch (type) { - case "802-11-wireless": return Primary.WIFI; - case "802-3-ethernet": return Primary.WIRED; - default: return Primary.UNKNOWN; - } - } -} - -// alias for NM.State -public enum AstalNetwork.State { - UNKNOWN, - ASLEEP, - DISCONNECTED, - DISCONNECTING, - CONNECTING, - CONNECTED_LOCAL, - CONNECTED_SITE, - CONNECTED_GLOBAL; - - public string to_string() { - switch (this) { - case ASLEEP: return "asleep"; - case DISCONNECTED: return "disconnected"; - case DISCONNECTING: return "disconnecting"; - case CONNECTING: return "connecting"; - case CONNECTED_LOCAL: return "connected_local"; - case CONNECTED_SITE: return "connected_site"; - case CONNECTED_GLOBAL: return "connected_global"; - default: return "unknown"; - } - } -} - - -// alias for NM.ConnectivityState -public enum AstalNetwork.Connectivity { - UNKNOWN, - NONE, - PORTAL, - LIMITED, - FULL; - - public string to_string() { - switch (this) { - case NONE: return "none"; - case PORTAL: return "portal"; - case LIMITED: return "limited"; - case FULL: return "full"; - default: return "unknown"; - } - } -} - -// alias for NM.DeviceState -public enum AstalNetwork.DeviceState { - UNKNOWN, - UNMANAGED, - UNAVAILABLE, - DISCONNECTED, - PREPARE, - CONFIG, - NEED_AUTH, - IP_CONFIG, - IP_CHECK, - SECONDARIES, - ACTIVATED, - DEACTIVATING, - FAILED; - - public string to_string() { - switch (this) { - case UNMANAGED: return "unmanaged"; - case UNAVAILABLE: return "unavailable"; - case DISCONNECTED: return "disconnected"; - case PREPARE: return "prepare"; - case CONFIG: return "config"; - case NEED_AUTH: return "need_auth"; - case IP_CONFIG: return "ip_config"; - case IP_CHECK: return "ip_check"; - case SECONDARIES: return "secondaries"; - case ACTIVATED: return "activated"; - case DEACTIVATING: return "deactivating"; - case FAILED: return "failed"; - default: return "unknown"; - } - - } -} - -public enum AstalNetwork.Internet { - CONNECTED, - CONNECTING, - DISCONNECTED; - - public static Internet from_device(NM.Device device) { - if (device == null || device.active_connection == null) - return DISCONNECTED; - - switch (device.active_connection.state) { - case NM.ActiveConnectionState.ACTIVATED: return CONNECTED; - case NM.ActiveConnectionState.ACTIVATING: return CONNECTING; - default: return DISCONNECTED; - } - } - - public string to_string() { - switch (this) { - case CONNECTED: return "connected"; - case CONNECTING: return "connecting"; - default: return "disconnected"; - } - } -} diff --git a/network/src/vpn.vala b/network/src/vpn.vala deleted file mode 100644 index 8b13789..0000000 --- a/network/src/vpn.vala +++ /dev/null @@ -1 +0,0 @@ - diff --git a/network/src/wifi.vala b/network/src/wifi.vala deleted file mode 100644 index c9e9881..0000000 --- a/network/src/wifi.vala +++ /dev/null @@ -1,182 +0,0 @@ -public class AstalNetwork.Wifi : Object { - internal const string ICON_EXCELLENT = "network-wireless-signal-excellent-symbolic"; - internal const string ICON_OK = "network-wireless-signal-ok-symbolic"; - internal const string ICON_GOOD = "network-wireless-signal-good-symbolic"; - internal const string ICON_WEAK = "network-wireless-signal-weak-symbolic"; - internal const string ICON_NONE = "network-wireless-signal-none-symbolic"; - internal const string ICON_ACQUIRING = "network-wireless-acquiring-symbolic"; - internal const string ICON_CONNECTED = "network-wireless-connected-symbolic"; - internal const string ICON_DISABLED = "network-wireless-disabled-symbolic"; - internal const string ICON_OFFLINE = "network-wireless-offline-symbolic"; - internal const string ICON_NO_ROUTE = "network-wireless-no-route-symbolic"; - internal const string ICON_HOTSPOT = "network-wireless-hotspot-symbolic"; - - private HashTable _access_points = - new HashTable(str_hash, str_equal); - - public NM.DeviceWifi device { get; construct set; } - - public NM.ActiveConnection? active_connection { get; private set; } - private ulong connection_handler = 0; - - public AccessPoint? active_access_point { get; private set; } - private ulong ap_handler = 0; - - public List access_points { - owned get { return _access_points.get_values(); } - } - - public bool enabled { - get { return device.client.wireless_enabled; } - set { device.client.wireless_enabled = value; } - } - - public Internet internet { get; private set; } - public uint bandwidth { get; private set; } - public string ssid { get; private set; } - public uint8 strength { get; private set; } - public uint frequency { get; private set; } - public DeviceState state { get; private set; } - public string icon_name { get; private set; } - public bool is_hotspot { get; private set; } - public bool scanning { get; private set; } - - internal Wifi(NM.DeviceWifi device) { - this.device = device; - - foreach (var ap in device.access_points) - _access_points.set(ap.bssid, new AccessPoint(this, ap)); - - device.access_point_added.connect((access_point) => { - var ap = (NM.AccessPoint)access_point; - _access_points.set(ap.bssid, new AccessPoint(this, ap)); - notify_property("access-points"); - }); - - device.access_point_removed.connect((access_point) => { - var ap = (NM.AccessPoint)access_point; - _access_points.remove(ap.bssid); - notify_property("access-points"); - }); - - on_active_connection(); - device.notify["active-connection"].connect(on_active_connection); - - on_active_access_point(); - device.notify["active-access-point"].connect(on_active_access_point); - - state = (DeviceState)device.state; - device.client.notify["wireless-enabled"].connect(() => notify_property("enabled")); - device.state_changed.connect((n, o, r) => { - state_changed(n, o, r); - state = (DeviceState)n; - }); - - device.notify.connect(() => { icon_name = _icon(); }); - device.client.notify.connect(() => { icon_name = _icon(); }); - icon_name = _icon(); - } - - public signal void state_changed( - DeviceState new_state, - DeviceState old_state, - NM.DeviceStateReason reaseon - ); - - public void scan() { - scanning = true; - var last_scan = device.last_scan; - device.request_scan_async.begin(null, (_, res) => { - try { - device.request_scan_async.end(res); - Timeout.add(1000, () => { - if (device.last_scan == last_scan) - return Source.CONTINUE; - - scanning = false; - return Source.REMOVE; - }, Priority.DEFAULT); - } catch (Error err) { - critical(err.message); - } - }); - } - - private void on_active_connection() { - if (connection_handler > 0 && active_connection != null) { - active_connection.disconnect(connection_handler); - connection_handler = 0; - active_connection = null; - } - - active_connection = device.active_connection; - is_hotspot = _hotspot(); - if (active_connection != null) { - connection_handler = active_connection.notify["state"].connect(() => { - internet = Internet.from_device(device); - }); - } - } - - private void on_active_access_point_notify() { - bandwidth = active_access_point.bandwidth; - frequency = active_access_point.frequency; - strength = active_access_point.strength; - ssid = active_access_point.ssid; - } - - private void on_active_access_point() { - if (ap_handler > 0 && active_access_point != null) { - active_access_point.disconnect(ap_handler); - ap_handler = 0; - active_access_point = null; - } - - var ap = device.active_access_point; - if (ap != null) { - active_access_point = _access_points.get(ap.bssid); - on_active_access_point_notify(); - ap_handler = active_access_point.notify.connect(on_active_access_point_notify); - } - } - - private string _icon() { - if (!enabled) return ICON_DISABLED; - - var full = device.client.connectivity == NM.ConnectivityState.FULL; - - if (internet == Internet.CONNECTED) { - if (is_hotspot) return ICON_HOTSPOT; - if (!full) return ICON_NO_ROUTE; - if (active_access_point == null) return ICON_CONNECTED; - - if (strength >= 80) return ICON_EXCELLENT; - if (strength >= 60) return ICON_GOOD; - if (strength >= 40) return ICON_OK; - if (strength >= 20) return ICON_WEAK; - - return ICON_NONE; - } - - if (internet == Internet.CONNECTING) { - return ICON_ACQUIRING; - } - - return ICON_OFFLINE; - } - - private bool _hotspot() { - if (device.active_connection == null) - return false; - - var conn = device.active_connection.connection; - if (conn == null) - return false; - - var ip4config = conn.get_setting_ip4_config(); - if (ip4config == null) - return false; - - return ip4config.method == NM.SettingIP4Config.METHOD_SHARED; - } -} diff --git a/network/src/wired.vala b/network/src/wired.vala deleted file mode 100644 index 68cf460..0000000 --- a/network/src/wired.vala +++ /dev/null @@ -1,73 +0,0 @@ -public class AstalNetwork.Wired : Object { - private const string ICON_CONNECTED = "network-wired-symbolic"; - private const string ICON_DISCONNECTED = "network-wired-disconnected-symbolic"; - private const string ICON_ACQUIRING = "network-wired-acquiring-symbolic"; - private const string ICON_NO_ROUTE = "network-wired-no-route-symbolic"; - - public NM.DeviceEthernet device { get; construct set; } - - public NM.ActiveConnection connection; - private ulong connection_handler = 0; - - internal Wired(NM.DeviceEthernet device) { - this.device = device; - - speed = device.speed; - state = (DeviceState)device.state; - icon_name = _icon(); - - device.notify.connect((pspec) => { - if (pspec.name == "speed") { - speed = device.speed; - } - if (pspec.name == "state") { - state = (DeviceState)device.state; - } - if (pspec.name == "active-connection") { - on_active_connection(); - } - icon_name = _icon(); - }); - - device.client.notify.connect(() => { icon_name = _icon(); }); - - on_active_connection(); - icon_name = _icon(); - } - - private void on_active_connection() { - if (connection_handler > 0 && connection != null) { - connection.disconnect(connection_handler); - connection_handler = 0; - connection = null; - } - - connection = device.active_connection; - if (connection != null) { - connection_handler = connection.notify["state"].connect(() => { - internet = Internet.from_device(device); - }); - } - } - - public uint speed { get; private set; } - public Internet internet { get; private set; } - public DeviceState state { get; private set; } - public string icon_name { get; private set; } - - private string _icon() { - var full = device.client.connectivity == NM.ConnectivityState.FULL; - - if (internet == Internet.CONNECTING) { - return ICON_ACQUIRING; - } - - if (internet == Internet.CONNECTED) { - if (!full) return ICON_NO_ROUTE; - - return ICON_CONNECTED; - } - - return ICON_DISCONNECTED; - } -} diff --git a/network/version b/network/version deleted file mode 100644 index 6e8bf73..0000000 --- a/network/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/notifd/LICENSE b/notifd/LICENSE deleted file mode 100644 index 67cd97b..0000000 --- a/notifd/LICENSE +++ /dev/null @@ -1,503 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - diff --git a/notifd/README.md b/notifd/README.md deleted file mode 100644 index da93a6a..0000000 --- a/notifd/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# notifd - -A notification daemon library and cli tool - -## TODO - -- docs diff --git a/notifd/flake.lock b/notifd/flake.lock deleted file mode 100644 index 13f566b..0000000 --- a/notifd/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1716137900, - "narHash": "sha256-sowPU+tLQv8GlqtVtsXioTKeaQvlMz/pefcdwg8MvfM=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "6c0b7a92c30122196a761b440ac0d46d3d9954f1", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/notifd/flake.nix b/notifd/flake.nix deleted file mode 100644 index b4e7a45..0000000 --- a/notifd/flake.nix +++ /dev/null @@ -1,54 +0,0 @@ -{ - description = "Notification daemon library and cli tool"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - - outputs = { - self, - nixpkgs, - }: let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs {inherit system;}; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - ]; - - buildInputs = with pkgs; [ - glib - gdk-pixbuf - json-glib - ]; - in { - packages.${system} = rec { - default = notifd; - notifd = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-notifd"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - notifd = pkgs.mkShell { - inherit nativeBuildInputs; - buildInputs = - buildInputs - ++ [ - self.packages.${system}.default - pkgs.gjs - ]; - }; - }; - }; -} diff --git a/notifd/meson.build b/notifd/meson.build deleted file mode 100644 index 8fabdb4..0000000 --- a/notifd/meson.build +++ /dev/null @@ -1,19 +0,0 @@ -project( - 'astal-notifd', - 'vala', - 'c', - version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), - meson_version: '>= 0.62.0', - default_options: [ - 'warning_level=2', - 'werror=false', - 'c_std=gnu11', - ], -) - -assert( - get_option('lib') or get_option('cli'), - 'Either lib or cli option must be set to true.', -) - -subdir('src') diff --git a/notifd/meson_options.txt b/notifd/meson_options.txt deleted file mode 100644 index f110242..0000000 --- a/notifd/meson_options.txt +++ /dev/null @@ -1,11 +0,0 @@ -option( - 'lib', - type: 'boolean', - value: true, -) - -option( - 'cli', - type: 'boolean', - value: true, -) diff --git a/notifd/src/cli.vala b/notifd/src/cli.vala deleted file mode 100644 index afce774..0000000 --- a/notifd/src/cli.vala +++ /dev/null @@ -1,115 +0,0 @@ -static bool help; -static bool version; -static bool daemonize; -static bool list; -static string invoke; -static int close_n; -static int get_n; -static bool toggle_dnd; - -const OptionEntry[] options = { - { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, - { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, - { "daemonize", 'd', OptionFlags.NONE, OptionArg.NONE, ref daemonize, null, null }, - { "list", 'l', OptionFlags.NONE, OptionArg.NONE, ref list, null, null }, - { "invoke", 'i', OptionFlags.NONE, OptionArg.STRING, ref invoke, null, null }, - { "close", 'c', OptionFlags.NONE, OptionArg.INT, ref close_n, null, null }, - { "get", 'g', OptionFlags.NONE, OptionArg.INT, ref get_n, null, null }, - { "toggle-dnd", 't', OptionFlags.NONE, OptionArg.NONE, ref toggle_dnd, null, null }, - { null }, -}; - -int main(string[] argv) { - try { - var opts = new OptionContext(); - opts.add_main_entries(options, null); - opts.set_help_enabled(false); - opts.set_ignore_unknown_options(false); - opts.parse(ref argv); - } catch (OptionError err) { - printerr (err.message); - return 1; - } - - if (help) { - print("Cli client for astal-notifd\n\n"); - print("Usage:\n"); - print(" %s [flags]\n\n", argv[0]); - print("Flags:\n"); - print(" -h, --help Print this help and exit\n"); - print(" -v, --version Print version number and exit\n"); - print(" -l, --list Print every notification and exit\n"); - print(" -d, --daemonize Watch for new notifications\n"); - print(" -i, --invoke Invoke a notification action\n"); - print(" -c, --close Close a notification by its id\n"); - print(" -g, --get Print a notification by its id\n"); - print(" -t, --toggle-dnd Toggle do not disturb\n"); - return 0; - } - - var notifd = new AstalNotifd.Notifd(); - - if (version) { - print(AstalNotifd.VERSION); - return 0; - } - - if (list) { - var state = Environment.get_user_state_dir() + "/astal/notifd/notifications.json"; - if (FileUtils.test(state, FileTest.EXISTS)) { - try { - uint8[] json; - File.new_for_path(state).load_contents(null, out json, null); - - var obj = Json.from_string((string)json); - - var list = obj.get_object().get_member("notifications"); - stdout.printf("%s\n", Json.to_string(list, true)); - return 0; - } catch (Error err) { - stderr.printf("failed to load cache: %s", err.message); - } - } - stdout.printf("[]\n"); - return 0; - } - - if (toggle_dnd) { - notifd.dont_disturb = !notifd.dont_disturb; - return 0; - } - - if (daemonize) { - notifd.notified.connect((id) => { - stdout.printf("%s\n", notifd.get_notification_json(id)); - stdout.flush(); - }); - new MainLoop().run(); - } - - if (invoke != null) { - if (!invoke.contains(":")) { - stderr.printf("invoke format needs to be :"); - return 1; - } - - var split = invoke.split(":"); - var n_id = int.parse(split[0]); - var a_id = split[1]; - - notifd.get_notification(n_id).invoke(a_id); - } - - if (close_n > 0) { - notifd.get_notification(close_n).dismiss(); - } - - if (get_n > 0) { - stdout.printf("%s", notifd.get_notification(get_n).to_json_string()); - } - - if (!daemonize && invoke == null && close_n == 0 && get_n == 0) - return 1; - - return 0; -} diff --git a/notifd/src/config.vala.in b/notifd/src/config.vala.in deleted file mode 100644 index 752c754..0000000 --- a/notifd/src/config.vala.in +++ /dev/null @@ -1,6 +0,0 @@ -namespace AstalNotifd { - public const int MAJOR_VERSION = @MAJOR_VERSION@; - public const int MINOR_VERSION = @MINOR_VERSION@; - public const int MICRO_VERSION = @MICRO_VERSION@; - public const string VERSION = "@VERSION@"; -} diff --git a/notifd/src/daemon.vala b/notifd/src/daemon.vala deleted file mode 100644 index b8fb598..0000000 --- a/notifd/src/daemon.vala +++ /dev/null @@ -1,255 +0,0 @@ -[DBus (name = "org.freedesktop.Notifications")] -internal class AstalNotifd.Daemon : Object { - public static string name = "notifd"; - public static string vendor = "astal"; - public static string version = "0.1"; - - private string state_file; - private string state_directory; - private string cache_directory; - - private uint n_id = 1; - private HashTable notifs = - new HashTable((i) => i, (a, b) => a == b); - - private bool _ignore_timeout; - public bool ignore_timeout { - get { return _ignore_timeout; } - set { - _ignore_timeout = value; - write_state(); - } - } - - private bool _dont_disturb; - public bool dont_disturb { - get { return _dont_disturb; } - set { - _dont_disturb = value; - write_state(); - } - } - - public signal void notified(uint id, bool replaced); - public signal void resolved(uint id, ClosedReason reason); - public signal void action_invoked(uint id, string action); - public signal void prop_changed(string prop); - - // emitting an event from proxy doesn't seem to work - public void emit_resolved(uint id, ClosedReason reason) { resolved(id, reason); } - public void emit_action_invoked(uint id, string action) { action_invoked(id, action); } - - construct { - cache_directory = Environment.get_user_cache_dir() + "/astal/notifd"; - state_directory = Environment.get_user_state_dir() + "/astal/notifd"; - state_file = state_directory + "/notifications.json"; - - if (FileUtils.test(state_file, FileTest.EXISTS)) { - try { - uint8[] json; - File.new_for_path(state_file).load_contents(null, out json, null); - - var obj = Json.from_string((string)json); - - var list = obj.get_object().get_array_member("notifications"); - for (var i = 0; i < list.get_length(); ++i) { - add_notification(new Notification.from_json(list.get_object_element(i))); - } - n_id = list.get_length() + 1; - - _dont_disturb = obj.get_object().get_boolean_member("dont_disturb"); - _ignore_timeout = obj.get_object().get_boolean_member("ignore_timeout"); - } catch (Error err) { - warning("failed to load cache: %s", err.message); - } - } - - notify.connect((prop) => prop_changed(prop.name)); - - notified.connect(() => { - notify_property("notifications"); - }); - - resolved.connect((id, reason) => { - notifs.get(id).resolved(reason); - notifs.remove(id); - write_state(); - notify_property("notifications"); - notification_closed(id, reason); - }); - } - - public uint[] notification_ids() throws DBusError, IOError { - var keys = notifs.get_keys(); - uint[] id = new uint[keys.length()]; - for (var i = 0; i < keys.length(); ++i) - id[i] = keys.nth_data(i); - return id; - } - - [DBus (visible = false)] - public List notifications { - owned get { return notifs.get_values(); } - } - - [DBus (visible = false)] - public Notification get_notification(uint id) { - return notifs.get(id); - } - - public string get_notification_json(uint id) throws DBusError, IOError { - return notifs.get(id).to_json_string(); - } - - [DBus (name = "Notify")] - public uint Notify( - string app_name, - uint replaces_id, - string app_icon, - string summary, - string body, - string[] actions, - HashTable hints, - int expire_timeout - ) throws DBusError, IOError { - if (hints.get("image-data") != null) { - var file = cache_image(hints.get("image-data"), app_name); - if (file != null) { - hints.set("image-path", new Variant.string(file)); - hints.remove("image-data"); - } - } - - // deprecated hints - hints.remove("image_data"); - hints.remove("icon_data"); - - var id = replaces_id > 0 ? replaces_id : n_id++; - - var replaced = add_notification(new Notification( - app_name, id, app_icon, summary, body, actions, hints, expire_timeout - )); - - if (!ignore_timeout && expire_timeout > 0) { - Timeout.add(expire_timeout, () => { - resolved(id, ClosedReason.EXPIRED); - return Source.REMOVE; - }, Priority.DEFAULT); - } - - notified(id, replaced); - - write_state(); - return id; - } - - private bool add_notification(Notification n) { - n.dismissed.connect(() => resolved(n.id, ClosedReason.DISMISSED_BY_USER)); - n.invoked.connect((action) => action_invoked(n.id, action)); - var replaced = notifs.contains(n.id); - notifs.set(n.id, n); - return replaced; - } - - private void write_state() { - var list = new Json.Builder().begin_array(); - foreach (var n in notifications) { - list.add_value(n.to_json()); - } - list.end_array(); - - var obj = new Json.Builder() - .begin_object() - .set_member_name("notifications").add_value(list.get_root()) - .set_member_name("ignore_timeout").add_boolean_value(ignore_timeout) - .set_member_name("dont_disturb").add_boolean_value(dont_disturb) - .end_object(); - - try { - if (!FileUtils.test(state_directory, FileTest.EXISTS)) - File.new_for_path(state_directory).make_directory_with_parents(null); - - FileUtils.set_contents_full(state_file, Json.to_string(obj.get_root(), false)); - } catch (Error err) { - warning("failed to cache notifications: %s", err.message); - } - } - - public signal void notification_closed(uint id, uint reason); - public signal void activation_token(uint id, string token); - - public void close_notification(uint id) throws DBusError, IOError { - resolved(id, ClosedReason.CLOSED); - } - - public void get_server_information( - out string name, - out string vendor, - out string version, - out string spec_version - ) throws DBusError, IOError { - name = Daemon.name; - vendor = Daemon.vendor; - version = Daemon.version; - spec_version = "1.2"; - } - - public string[] get_capabilities() throws DBusError, IOError { - return {"action-icons", "actions", "body", "icon-static", "persistence", "sound"}; - } - - private string? cache_image(Variant image, string app_name) { - int w = image.get_child_value(0).get_int32(); - int h = image.get_child_value(1).get_int32(); - int rs = image.get_child_value(2).get_int32(); - bool alpha = image.get_child_value(3).get_boolean(); - int bps = image.get_child_value(4).get_int32(); - Bytes data = image.get_child_value(6).get_data_as_bytes(); - - if (bps != 8) { - warning("Can not cache image from %s. %s", app_name, - "Currently only RGB images with 8 bits per sample are supported."); - return null; - } - - var pixbuf = new Gdk.Pixbuf.from_bytes( - data, Gdk.Colorspace.RGB, alpha, bps, w, h, rs); - - if (pixbuf == null) - return null; - - var file_name = cache_directory + "/" + data.hash().to_string("%u.png"); - - try { - if (!FileUtils.test(cache_directory, FileTest.EXISTS)) - File.new_for_path(cache_directory).make_directory_with_parents(null); - - var output_stream = File.new_for_path(file_name) - .replace(null, false, FileCreateFlags.NONE, null); - - pixbuf.save_to_streamv(output_stream, "png", null, null, null); - output_stream.close(null); - } catch (Error err) { - warning("could not cache image %s", err.message); - return null; - } - - return file_name; - } - - internal Daemon register(DBusConnection conn) { - try { - conn.register_object("/org/freedesktop/Notifications", this); - } catch (Error err) { - critical(err.message); - } - return this; - } -} - -public enum AstalNotifd.ClosedReason { - EXPIRED = 1, - DISMISSED_BY_USER = 2, - CLOSED = 3, - UNDEFINED = 4, -} diff --git a/notifd/src/meson.build b/notifd/src/meson.build deleted file mode 100644 index d3efa36..0000000 --- a/notifd/src/meson.build +++ /dev/null @@ -1,79 +0,0 @@ -version_split = meson.project_version().split('.') -api_version = version_split[0] + '.' + version_split[1] -gir = 'AstalNotifd-' + api_version + '.gir' -typelib = 'AstalNotifd-' + api_version + '.typelib' - -config = configure_file( - input: 'config.vala.in', - output: 'config.vala', - configuration: { - 'VERSION': meson.project_version(), - 'MAJOR_VERSION': version_split[0], - 'MINOR_VERSION': version_split[1], - 'MICRO_VERSION': version_split[2], - }, -) - -deps = [ - dependency('glib-2.0'), - dependency('gobject-2.0'), - dependency('gio-2.0'), - dependency('json-glib-1.0'), - dependency('gdk-pixbuf-2.0'), -] - -sources = [ - config, - 'daemon.vala', - 'notifd.vala', - 'notification.vala', - 'proxy.vala', -] - -if get_option('lib') - lib = library( - meson.project_name(), - sources, - dependencies: deps, - vala_header: meson.project_name() + '.h', - vala_vapi: meson.project_name() + '-' + api_version + '.vapi', - vala_gir: gir, - version: meson.project_version(), - install: true, - install_dir: [true, true, true, true], - ) - - import('pkgconfig').generate( - lib, - name: meson.project_name(), - filebase: meson.project_name() + '-' + api_version, - version: meson.project_version(), - subdirs: meson.project_name(), - requires: deps, - install_dir: get_option('libdir') / 'pkgconfig', - ) - - custom_target( - typelib, - command: [ - find_program('g-ir-compiler'), - '--output', '@OUTPUT@', - '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', - meson.current_build_dir() / gir, - ], - input: lib, - output: typelib, - depends: lib, - install: true, - install_dir: get_option('libdir') / 'girepository-1.0', - ) -endif - -if get_option('cli') - executable( - meson.project_name(), - ['cli.vala', sources], - dependencies: deps, - install: true, - ) -endif diff --git a/notifd/src/notifd.vala b/notifd/src/notifd.vala deleted file mode 100644 index c962862..0000000 --- a/notifd/src/notifd.vala +++ /dev/null @@ -1,140 +0,0 @@ -namespace AstalNotifd { - public Notifd get_default() { - return Notifd.get_default(); - } -} - -public class AstalNotifd.Notifd : Object { - private static Notifd _instance; - public static Notifd get_default() { - if (_instance == null) - _instance = new Notifd(); - - return _instance; - } - - private Daemon daemon; - private DaemonProxy proxy; - - public signal void active(ActiveType type); - - public bool ignore_timeout { - get { - return proxy != null ? proxy.ignore_timeout : daemon.ignore_timeout; - } - set { - if (proxy != null) - proxy.ignore_timeout = value; - else - daemon.ignore_timeout = value; - } - } - - public bool dont_disturb { - get { - return proxy != null ? proxy.dont_disturb : daemon.dont_disturb; - } - set { - if (proxy != null) - proxy.dont_disturb = value; - else - daemon.dont_disturb = value; - } - } - - public List notifications { - owned get { return proxy != null ? proxy.notifications : daemon.notifications; } - } - - public uint[] notification_ids() throws Error { - return proxy != null ? proxy.notification_ids() : daemon.notification_ids(); - } - - public Notification get_notification(uint id) { - return proxy != null ? proxy.get_notification(id) : daemon.get_notification(id); - } - - public string get_notification_json(uint id) { - return get_notification(id).to_json_string(); - } - - public signal void notified(uint id, bool replaced); - public signal void resolved(uint id, ClosedReason reason); - - construct { - // hack to make it synchronous - MainLoop? loop = null; - - if (!MainContext.default().is_owner()) { - loop = new MainLoop(); - } - - bool done = false; - - Bus.own_name( - BusType.SESSION, - "org.freedesktop.Notifications", - BusNameOwnerFlags.NONE, - acquire_daemon, - on_daemon_acquired, - make_proxy - ); - - active.connect(() => { - done = true; - if (loop != null && loop.is_running()) { - loop.quit(); - } - }); - - if (loop != null) { - loop.run(); - } else { - while (!done) { - MainContext.default().iteration(false); - } - } - } - - private void acquire_daemon(DBusConnection conn) { - daemon = new Daemon().register(conn); - } - - private void on_daemon_acquired() { - if (proxy != null) { - proxy.stop(); - proxy = null; - } - daemon.notified.connect((id, replaced) => notified(id, replaced)); - daemon.resolved.connect((id, reason) => resolved(id, reason)); - daemon.notify.connect((prop) => { - if (get_class().find_property(prop.name) != null) { - notify_property(prop.name); - } - }); - active(ActiveType.DAEMON); - } - - private void make_proxy() { - proxy = new DaemonProxy(); - - if (proxy.start()) { - active(ActiveType.PROXY); - } else { - return; - } - - proxy.notified.connect((id, replaced) => notified(id, replaced)); - proxy.resolved.connect((id, reason) => resolved(id, reason)); - proxy.notify.connect((prop) => { - if (get_class().find_property(prop.name) != null) { - notify_property(prop.name); - } - }); - } -} - -public enum AstalNotifd.ActiveType { - DAEMON, - PROXY, -} diff --git a/notifd/src/notification.vala b/notifd/src/notification.vala deleted file mode 100644 index 0b4af06..0000000 --- a/notifd/src/notification.vala +++ /dev/null @@ -1,160 +0,0 @@ -public enum AstalNotifd.Urgency { - LOW = 0, - NORMAL = 1, - CRITICAL = 2, -} - -public struct AstalNotifd.Action { - public string id; - public string label; -} - -public class AstalNotifd.Notification : Object { - private List _actions; - private HashTable hints; - - public int64 time { construct set; get; } - public string app_name { construct set; get; } - public string app_icon { construct set; get; } - public string summary { construct set; get; } - public string body { construct set; get; } - public uint id { construct set; get; } - public int expire_timeout { construct set; get; } - public List actions { get { return _actions; } } - - public string image { get { return get_str_hint("image-path"); } } - public bool action_icons { get { return get_bool_hint("action-icons"); } } - public string category { get { return get_str_hint("category"); } } - public string desktop_entry { get { return get_str_hint("desktop-entry"); } } - public bool resident { get { return get_bool_hint("resident"); } } - public string sound_file { get { return get_str_hint("sound-file"); } } - public string sound_name { get { return get_str_hint("sound-name"); } } - public bool suppress_sound { get { return get_bool_hint("suppress-sound"); } } - public bool transient { get { return get_bool_hint("transient"); } } - public int x { get { return get_int_hint("x"); } } - public int y { get { return get_int_hint("y"); } } - public Urgency urgency { get { return get_int_hint("urgency"); } } - - internal Notification( - string app_name, - uint id, - string app_icon, - string summary, - string body, - string[] actions, - HashTable hints, - int expire_timeout - ) { - Object( - app_name: app_name, - id: id, - app_icon: app_icon, - summary: summary, - body: body, - expire_timeout: expire_timeout, - time: new DateTime.now_local().to_unix() - ); - - this.hints = hints; - _actions = new List(); - for (var i = 0; i < actions.length; i += 2) { - _actions.append(Action() { - id = actions[i], - label = actions[i + 1] - }); - } - } - - public Variant? get_hint(string hint) { - return hints.contains(hint) ? hints.get(hint) : null; - } - - public unowned string get_str_hint(string hint) { - return hints.contains(hint) ? hints.get(hint).get_string() : null; - } - - public bool get_bool_hint(string hint) { - return hints.contains(hint) ? hints.get(hint).get_boolean() : false; - } - - public int get_int_hint(string hint) { - return hints.contains(hint) ? hints.get(hint).get_int32() : 0; - } - - public signal void resolved(ClosedReason reason); - public signal void dismissed(); - public signal void invoked(string action); - - public void dismiss() { dismissed(); } - public void invoke(string action) { invoked(action); } - - internal Notification.from_json(Json.Object root) throws GLib.Error { - foreach (var key in root.get_members()) { - var node = root.get_member(key); - switch (key) { - case "id": id = (uint)node.get_int(); break; - case "time": time = node.get_int(); break; - case "expire_timeout": expire_timeout = (int)node.get_int(); break; - case "app_name": app_name = node.get_string(); break; - case "app_icon": app_icon = node.get_string(); break; - case "summary": summary = node.get_string(); break; - case "body": body = node.get_string(); break; - case "hints": - hints = new HashTable(str_hash, str_equal); - var obj = node.get_object(); - foreach (var hint in obj.get_members()) { - hints.set(hint, Json.gvariant_deserialize(obj.get_member(hint), null)); - } - break; - case "actions": - _actions = new List(); - for (var i = 0; i < node.get_array().get_length(); ++i) { - var o = node.get_array().get_object_element(i); - _actions.append(Action() { - id = o.get_member("id").get_string(), - label = o.get_member("label").get_string() - }); - } - break; - default: break; - } - } - } - - internal static Notification from_json_string(string json) throws GLib.Error { - var parser = new Json.Parser(); - parser.load_from_data(json); - return new Notification.from_json(parser.get_root().get_object()); - } - - public string to_json_string() { - var generator = new Json.Generator(); - generator.set_root(to_json()); - return generator.to_data(null); - } - - internal Json.Node to_json() { - var acts = new Json.Builder().begin_array(); - foreach (var action in actions) { - acts.begin_object() - .set_member_name("id").add_string_value(action.id) - .set_member_name("label").add_string_value(action.label) - .end_object(); - } - acts.end_array(); - - return new Json.Builder() - .begin_object() - .set_member_name("id").add_int_value(id) - .set_member_name("time").add_int_value(time) - .set_member_name("expire_timeout").add_int_value(expire_timeout) - .set_member_name("app_name").add_string_value(app_name) - .set_member_name("app_icon").add_string_value(app_icon) - .set_member_name("summary").add_string_value(summary) - .set_member_name("body").add_string_value(body) - .set_member_name("actions").add_value(acts.get_root()) - .set_member_name("hints").add_value(Json.gvariant_serialize(hints)) - .end_object() - .get_root(); - } -} diff --git a/notifd/src/proxy.vala b/notifd/src/proxy.vala deleted file mode 100644 index bedb8b9..0000000 --- a/notifd/src/proxy.vala +++ /dev/null @@ -1,129 +0,0 @@ -[DBus (name = "org.freedesktop.Notifications")] -internal interface AstalNotifd.IDaemon : DBusProxy { - public abstract bool ignore_timeout { get; set; } - public abstract bool dont_disturb { get; set; } - - public abstract uint[] notification_ids() throws DBusError, IOError; - public abstract string get_notification_json(uint id) throws DBusError, IOError; - - public signal void notified(uint id, bool replaced); - public signal void resolved(uint id, ClosedReason reason); - public signal void prop_changed(string prop); - - public abstract void emit_resolved(uint id, ClosedReason reason); - public abstract void emit_action_invoked(uint id, string action); -} - -internal class AstalNotifd.DaemonProxy : Object { - private HashTable notifs = - new HashTable((i) => i, (a, b) => a == b); - - public List notifications { - owned get { return notifs.get_values(); } - } - - public bool ignore_timeout { - get { return proxy.ignore_timeout; } - set { proxy.ignore_timeout = value; } - } - - public bool dont_disturb { - get { return proxy.dont_disturb; } - 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); - } - - public signal void notified(uint id, bool replaced); - public signal void resolved(uint id, ClosedReason reason); - - private IDaemon proxy; - private List ids = new List(); - - public void stop() { - if (ids.length() > 0) { - foreach (var id in ids) - SignalHandler.disconnect(proxy, id); - } - } - - public bool start() { - try { - var bus = Bus.get_sync(BusType.SESSION, null); - var variant = bus.call_sync( - "org.freedesktop.Notifications", - "/org/freedesktop/Notifications", - "org.freedesktop.Notifications", - "GetServerInformation", - null, - null, - DBusCallFlags.NONE, - -1, - null); - - var name = variant.get_child_value(0).get_string(); - var vendor = variant.get_child_value(1).get_string(); - var version = variant.get_child_value(2).get_string(); - - var running = name == Daemon.name - && vendor == Daemon.vendor - && version == Daemon.version; - - if (running) { - setup_proxy(); - return true; - } else { - critical("cannot get proxy: %s is already running", name); - } - } catch (Error err) { - critical("cannot get proxy: %s", err.message); - } - return false; - } - - private void setup_proxy() throws Error { - proxy = Bus.get_proxy_sync( - BusType.SESSION, - "org.freedesktop.Notifications", - "/org/freedesktop/Notifications" - ); - - foreach (var id in proxy.notification_ids()) - add_notification(id); - - ids.append(proxy.prop_changed.connect((prop) => { - if (prop == "ignore-timeout" || prop == "dont-disturb") - notify_property(prop); - })); - - ids.append(proxy.notified.connect((id, replaced) => { - add_notification(id); - notified(id, replaced); - notify_property("notifications"); - })); - - ids.append(proxy.resolved.connect((id, reason) => { - notifs.remove(id); - resolved(id, reason); - notify_property("notifications"); - })); - } - - private void add_notification(uint id) { - try { - var n = Notification.from_json_string(proxy.get_notification_json(id)); - proxy.resolved.connect((id, reason) => n.resolved(reason)); - n.dismissed.connect(() => proxy.emit_resolved(id, ClosedReason.DISMISSED_BY_USER)); - n.invoked.connect((action) => proxy.emit_action_invoked(id, action)); - notifs.set(id, n); - } catch (Error err) { - critical(err.message); - } - } -} diff --git a/notifd/src/signals.md b/notifd/src/signals.md deleted file mode 100644 index cdc6688..0000000 --- a/notifd/src/signals.md +++ /dev/null @@ -1,35 +0,0 @@ -# Signals - -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()` -* invoked(action) - by user with `.invoke()` - -## Deamon - -non-spec, used by user - -* notified(id, replaced) - by outside through dbus with `.Notify()` -* resolved(id, reason) - by `Notification.dismiss()` or outside with `.CloseNotification` - -spec, not used by user - -* notification_closed(id, reason) - sideeffect of `resolved` -* action_invoked(id, action) - by `Notification.invoke()` - -## Proxy - -mirrors Daemon - -* notified(id, replaced) -* resolved(id, reason) - -creates `Notification` objects through daemon's json strings -and hooks them up to call daemon's signals and vice versa - -## Notifd - -acts as a bridge between Proxy/Daemon, everything else is internal only diff --git a/notifd/version b/notifd/version deleted file mode 100644 index 6e8bf73..0000000 --- a/notifd/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/powerprofiles/.gitignore b/powerprofiles/.gitignore deleted file mode 100644 index f047207..0000000 --- a/powerprofiles/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -build/ -result -.cache/ -test.sh -tmp/ diff --git a/powerprofiles/LICENSE b/powerprofiles/LICENSE deleted file mode 100644 index 67cd97b..0000000 --- a/powerprofiles/LICENSE +++ /dev/null @@ -1,503 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - diff --git a/powerprofiles/README.md b/powerprofiles/README.md deleted file mode 100644 index 3e0a1ca..0000000 --- a/powerprofiles/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# powerprofiles - -Library and cli to control upowerd powerprofiles - -## TODO - -- docs diff --git a/powerprofiles/flake.lock b/powerprofiles/flake.lock deleted file mode 100644 index 13f566b..0000000 --- a/powerprofiles/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1716137900, - "narHash": "sha256-sowPU+tLQv8GlqtVtsXioTKeaQvlMz/pefcdwg8MvfM=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "6c0b7a92c30122196a761b440ac0d46d3d9954f1", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/powerprofiles/flake.nix b/powerprofiles/flake.nix deleted file mode 100644 index 9c2c68b..0000000 --- a/powerprofiles/flake.nix +++ /dev/null @@ -1,53 +0,0 @@ -{ - description = "Library and cli to control upowerd powerprofiles"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - - outputs = { - self, - nixpkgs, - }: let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs {inherit system;}; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - ]; - - buildInputs = with pkgs; [ - glib - json-glib - ]; - in { - packages.${system} = rec { - default = power-profiles; - power-profiles = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-power-profiles"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - power-profiles = pkgs.mkShell { - inherit nativeBuildInputs; - buildInputs = - buildInputs - ++ [ - self.packages.${system}.default - pkgs.gjs - ]; - }; - }; - }; -} diff --git a/powerprofiles/meson.build b/powerprofiles/meson.build deleted file mode 100644 index 0e0ad2d..0000000 --- a/powerprofiles/meson.build +++ /dev/null @@ -1,19 +0,0 @@ -project( - 'astal-power-profiles', - 'vala', - 'c', - version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), - meson_version: '>= 0.62.0', - default_options: [ - 'warning_level=2', - 'werror=false', - 'c_std=gnu11', - ], -) - -assert( - get_option('lib') or get_option('cli'), - 'Either lib or cli option must be set to true.', -) - -subdir('src') diff --git a/powerprofiles/meson_options.txt b/powerprofiles/meson_options.txt deleted file mode 100644 index f110242..0000000 --- a/powerprofiles/meson_options.txt +++ /dev/null @@ -1,11 +0,0 @@ -option( - 'lib', - type: 'boolean', - value: true, -) - -option( - 'cli', - type: 'boolean', - value: true, -) diff --git a/powerprofiles/src/cli.vala b/powerprofiles/src/cli.vala deleted file mode 100644 index 7be01d2..0000000 --- a/powerprofiles/src/cli.vala +++ /dev/null @@ -1,80 +0,0 @@ -static bool help; -static bool version; -static bool daemonize; -static bool list; -static string set; - -const OptionEntry[] options = { - { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, - { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, - { "daemonize", 'd', OptionFlags.NONE, OptionArg.NONE, ref daemonize, null, null }, - { "list", 'l', OptionFlags.NONE, OptionArg.NONE, ref list, null, null }, - { "set", 's', OptionFlags.NONE, OptionArg.STRING, ref set, null, null }, - { null }, -}; - -int main(string[] argv) { - try { - var opts = new OptionContext(); - opts.add_main_entries(options, null); - opts.set_help_enabled(false); - opts.set_ignore_unknown_options(false); - opts.parse(ref argv); - } catch (OptionError err) { - printerr (err.message); - return 1; - } - - if (help) { - print("Usage:\n"); - print(" %s [flags]\n\n", argv[0]); - print("Flags:\n"); - print(" -h, --help Print this help and exit\n"); - print(" -v, --version Print version number and exit\n"); - print(" -d, --daemonize Monitor for changes\n"); - print(" -l, --list List available profiles\n"); - return 0; - } - - if (version) { - print(AstalPowerProfiles.VERSION); - return 0; - } - - var profiles = AstalPowerProfiles.get_default(); - if (set != null) { - profiles.active_profile = set; - } - - else if (list) { - foreach (var p in profiles.profiles) { - print("%s\n", p.profile); - } - return 0; - } - - if (daemonize) { - var loop = new MainLoop(); - - stdout.printf("%s\n", profiles.to_json_string()); - stdout.flush(); - - profiles.notify.connect(() => { - stdout.printf("%s\n", profiles.to_json_string()); - stdout.flush(); - }); - - profiles.profile_released.connect(() => { - stdout.printf("%s\n", profiles.to_json_string()); - stdout.flush(); - }); - - loop.run(); - } - - if (set == null && !daemonize) { - stdout.printf("%s\n", profiles.to_json_string()); - } - - return 0; -} diff --git a/powerprofiles/src/config.vala.in b/powerprofiles/src/config.vala.in deleted file mode 100644 index 79034f1..0000000 --- a/powerprofiles/src/config.vala.in +++ /dev/null @@ -1,6 +0,0 @@ -namespace AstalPowerProfiles { - public const int MAJOR_VERSION = @MAJOR_VERSION@; - public const int MINOR_VERSION = @MINOR_VERSION@; - public const int MICRO_VERSION = @MICRO_VERSION@; - public const string VERSION = "@VERSION@"; -} diff --git a/powerprofiles/src/meson.build b/powerprofiles/src/meson.build deleted file mode 100644 index fdb8580..0000000 --- a/powerprofiles/src/meson.build +++ /dev/null @@ -1,75 +0,0 @@ -version_split = meson.project_version().split('.') -api_version = version_split[0] + '.' + version_split[1] -gir = 'AstalPowerProfiles-' + api_version + '.gir' -typelib = 'AstalPowerProfiles-' + api_version + '.typelib' - -config = configure_file( - input: 'config.vala.in', - output: 'config.vala', - configuration: { - 'VERSION': meson.project_version(), - 'MAJOR_VERSION': version_split[0], - 'MINOR_VERSION': version_split[1], - 'MICRO_VERSION': version_split[2], - }, -) - -deps = [ - dependency('glib-2.0'), - dependency('gobject-2.0'), - dependency('gio-2.0'), - dependency('json-glib-1.0'), -] - -sources = [ - config, - 'power-profiles.vala', -] - -if get_option('lib') - lib = library( - meson.project_name(), - sources, - dependencies: deps, - vala_header: meson.project_name() + '.h', - vala_vapi: meson.project_name() + '-' + api_version + '.vapi', - vala_gir: gir, - version: meson.project_version(), - install: true, - install_dir: [true, true, true, true], - ) - - import('pkgconfig').generate( - lib, - name: meson.project_name(), - filebase: meson.project_name() + '-' + api_version, - version: meson.project_version(), - subdirs: meson.project_name(), - requires: deps, - install_dir: get_option('libdir') / 'pkgconfig', - ) - - custom_target( - typelib, - command: [ - find_program('g-ir-compiler'), - '--output', '@OUTPUT@', - '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', - meson.current_build_dir() / gir, - ], - input: lib, - output: typelib, - depends: lib, - install: true, - install_dir: get_option('libdir') / 'girepository-1.0', - ) -endif - -if get_option('cli') - executable( - meson.project_name(), - ['cli.vala', sources], - dependencies: deps, - install: true, - ) -endif diff --git a/powerprofiles/src/power-profiles.vala b/powerprofiles/src/power-profiles.vala deleted file mode 100644 index ab98505..0000000 --- a/powerprofiles/src/power-profiles.vala +++ /dev/null @@ -1,205 +0,0 @@ -namespace AstalPowerProfiles { -[DBus (name = "org.freedesktop.UPower.PowerProfiles")] -private interface IPowerProfiles : DBusProxy { - public abstract string[] actions { owned get; } - public abstract string active_profile { owned get; set; } - public abstract HashTable[] active_profile_holds { owned get; } - public abstract string performance_degraded { owned get; } - public abstract string performance_inhibited { owned get; } - public abstract HashTable[] profiles { owned get; } - public abstract string version { owned get; } - - public signal void profile_released (uint cookie); - - public abstract uint hold_profile(string profile, string reason, string application_id) throws Error; - public abstract void release_profile(uint cookie) throws Error; -} - -public PowerProfiles get_default() { - return PowerProfiles.get_default(); -} - -public class PowerProfiles : Object { - private static PowerProfiles instance; - public static PowerProfiles get_default() { - if (instance == null) - instance = new PowerProfiles(); - - return instance; - } - - private IPowerProfiles proxy; - - construct { - try { - proxy = Bus.get_proxy_sync( - GLib.BusType.SYSTEM, - "org.freedesktop.UPower.PowerProfiles", - "/org/freedesktop/UPower/PowerProfiles" - ); - - proxy.profile_released.connect((cookie) => profile_released(cookie)); - proxy.g_properties_changed.connect((props) => { - var map = (HashTable)props; - foreach (var key in map.get_keys()) { - notify_property(kebab_case(key)); - if (key == "ActiveProfile") - notify_property("icon-name"); - } - }); - } catch (Error error){ - critical(error.message); - } - } - - public string active_profile { - owned get { return proxy.active_profile; } - set { proxy.active_profile = value; } - } - - public string icon_name { - owned get { return @"power-profile-$active_profile-symbolic"; } - } - - public string[] actions { - owned get { return proxy.actions.copy(); } - } - - public Hold[] active_profile_holds { - owned get { - Hold[] holds = new Hold[proxy.active_profile_holds.length]; - for (var i = 0; i < proxy.active_profile_holds.length; ++i) { - var hold = proxy.active_profile_holds[i]; - holds[i] = Hold() { - application_id = hold.get("ApplicationId").get_string(), - profile = hold.get("Profile").get_string(), - reason = hold.get("Reason").get_string() - }; - } - return holds; - } - } - - public string performance_degraded { - owned get { return proxy.performance_degraded; } - } - - public string performance_inhibited { - owned get { return proxy.performance_degraded; } - } - - public Profile[] profiles { - owned get { - Profile[] profs = new Profile[proxy.profiles.length]; - for (var i = 0; i < proxy.profiles.length; ++i) { - var prof = proxy.profiles[i]; - profs[i] = Profile() { - profile = prof.get("Profile").get_string(), - cpu_driver = prof.get("CpuDriver").get_string(), - platform_driver = prof.get("PlatformDriver").get_string(), - driver = prof.get("Driver").get_string() - }; - } - return profs; - } - } - - public string version { - owned get { return proxy.version; } - } - - public signal void profile_released (uint cookie); - - public int hold_profile(string profile, string reason, string application_id) { - try { - return (int)proxy.hold_profile(profile, reason, application_id); - } catch (Error error) { - critical(error.message); - return -1; - } - } - - public void release_profile(uint cookie) { - try { - proxy.release_profile(cookie); - } catch (Error error) { - critical(error.message); - } - } - - public string to_json_string() { - var acts = new Json.Builder().begin_array(); - foreach (var action in actions) { - acts.add_string_value(action); - } - - var active_holds = new Json.Builder().begin_array(); - foreach (var action in active_profile_holds) { - active_holds.add_value(new Json.Builder() - .begin_object() - .set_member_name("application_id").add_string_value(action.application_id) - .set_member_name("profile").add_string_value(action.profile) - .set_member_name("reason").add_string_value(action.reason) - .end_object() - .get_root()); - } - - var profs = new Json.Builder().begin_array(); - foreach (var prof in profiles) { - profs.add_value(new Json.Builder() - .begin_object() - .set_member_name("profie").add_string_value(prof.profile) - .set_member_name("driver").add_string_value(prof.driver) - .set_member_name("cpu_driver").add_string_value(prof.cpu_driver) - .set_member_name("platform_driver").add_string_value(prof.platform_driver) - .end_object() - .get_root()); - } - - return Json.to_string(new Json.Builder() - .begin_object() - .set_member_name("active_profile").add_string_value(active_profile) - .set_member_name("icon_name").add_string_value(icon_name) - .set_member_name("performance_degraded").add_string_value(performance_degraded) - .set_member_name("performance_inhibited").add_string_value(performance_inhibited) - .set_member_name("actions").add_value(acts.end_array().get_root()) - .set_member_name("active_profile_holds").add_value(active_holds.end_array().get_root()) - .set_member_name("profiles").add_value(profs.end_array().get_root()) - .end_object() - .get_root(), false); - } -} - -public struct Profile { - public string profile; - public string cpu_driver; - public string platform_driver; - public string driver; -} - -public struct Hold { - public string application_id; - public string profile; - public string reason; -} - -private string kebab_case(string pascal_case) { - StringBuilder kebab_case = new StringBuilder(); - - for (int i = 0; i < pascal_case.length; i++) { - char c = pascal_case[i]; - - if (c >= 'A' && c <= 'Z') { - if (i != 0) { - kebab_case.append_c('-'); - } - - kebab_case.append_c((char)(c + 32)); - } else { - kebab_case.append_c(c); - } - } - - return kebab_case.str; -} -} diff --git a/powerprofiles/version b/powerprofiles/version deleted file mode 100644 index 6e8bf73..0000000 --- a/powerprofiles/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/river/.gitignore b/river/.gitignore deleted file mode 100644 index 6bf41b5..0000000 --- a/river/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -result/ -.cache/ diff --git a/river/LICENSE b/river/LICENSE deleted file mode 100644 index 4ef65b3..0000000 --- a/river/LICENSE +++ /dev/null @@ -1,504 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - - diff --git a/river/README.md b/river/README.md deleted file mode 100644 index 3285628..0000000 --- a/river/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# astal-river - -a library and cli tool for getting status information of the river wayland compositor. - -## Build from source -### Dependencies - -- meson -- glib -- gobject-introspection -- json-glib -- libwayland - -```sh -# Clone the repository -git clone https://github.com/astal-sh/river -cd river - -# Setup and build -meson setup build -meson compile -C build - -# Install -meson install -C build -``` - -## Todo -- docs - - diff --git a/river/flake.lock b/river/flake.lock deleted file mode 100644 index ed5e17d..0000000 --- a/river/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1720031269, - "narHash": "sha256-rwz8NJZV+387rnWpTYcXaRNvzUSnnF9aHONoJIYmiUQ=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "9f4128e00b0ae8ec65918efeba59db998750ead6", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/river/flake.nix b/river/flake.nix deleted file mode 100644 index 766263f..0000000 --- a/river/flake.nix +++ /dev/null @@ -1,54 +0,0 @@ -{ - description = "Library and cli tool for querying river"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - - outputs = { - self, - nixpkgs, - }: let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs {inherit system;}; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - wayland - ]; - - buildInputs = with pkgs; [ - glib - json-glib - ]; - in { - packages.${system} = rec { - default = river; - river = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-river"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - river = pkgs.mkShell { - inherit nativeBuildInputs; - buildInputs = - buildInputs - ++ [ - pkgs.gjs - self.packages.${system}.default - ]; - }; - }; - }; -} diff --git a/river/include/astal-river.h b/river/include/astal-river.h deleted file mode 100644 index 6bedd94..0000000 --- a/river/include/astal-river.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef ASTAL_RIVER_H -#define ASTAL_RIVER_H - -#include - -G_BEGIN_DECLS - -#define ASTAL_RIVER_TYPE_OUTPUT (astal_river_output_get_type()) - -G_DECLARE_FINAL_TYPE(AstalRiverOutput, astal_river_output, ASTAL_RIVER, OUTPUT, GObject) - -guint astal_river_output_get_id(AstalRiverOutput *self); - -gchar *astal_river_output_get_name(AstalRiverOutput *self); - -gchar *astal_river_output_get_layout_name(AstalRiverOutput *self); - -gchar *astal_river_output_get_focused_view(AstalRiverOutput *self); - -guint astal_river_output_get_focused_tags(AstalRiverOutput *self); - -guint astal_river_output_get_urgent_tags(AstalRiverOutput *self); - -guint astal_river_output_get_occupied_tags(AstalRiverOutput *self); - -#define ASTAL_RIVER_TYPE_RIVER (astal_river_river_get_type()) - -G_DECLARE_FINAL_TYPE(AstalRiverRiver, astal_river_river, ASTAL_RIVER, RIVER, GObject) - -AstalRiverRiver *astal_river_river_new(); - -AstalRiverRiver *astal_river_river_get_default(); -AstalRiverRiver *astal_river_get_default(); - -GList *astal_river_river_get_outputs(AstalRiverRiver *self); - -AstalRiverOutput *astal_river_river_get_output(AstalRiverRiver *self, gchar *name); - -gchar *astal_river_river_get_focused_view(AstalRiverRiver *self); - -gchar *astal_river_river_get_focused_output(AstalRiverRiver *self); - -gchar *astal_river_river_get_mode(AstalRiverRiver *self); - -/** - * AstalRiverCommandCallback: - * @success: a #gboolean indicating whether the command was executed successfully - * @msg: a string containing the result of the command - * - * A callback function that is called after a river command is run. - */ -typedef void (*AstalRiverCommandCallback)(gboolean success, const gchar *msg); - -void astal_river_river_run_command_async(AstalRiverRiver *self, gint length, const gchar **cmd, - AstalRiverCommandCallback callback); - -G_END_DECLS - -#endif // !ASTAL_RIVER_H diff --git a/river/include/meson.build b/river/include/meson.build deleted file mode 100644 index 28a51f6..0000000 --- a/river/include/meson.build +++ /dev/null @@ -1,6 +0,0 @@ -astal_river_inc = include_directories('.') -astal_river_headers = files( - 'astal-river.h', -) - -install_headers('astal-river.h') diff --git a/river/include/river-private.h b/river/include/river-private.h deleted file mode 100644 index 14cd1c5..0000000 --- a/river/include/river-private.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef ASTAL_RIVER_OUTPUT_PRIVATE_H -#define ASTAL_RIVER_OUTPUT_PRIVATE_H - -#include - -#include "astal-river.h" -#include "river-status-unstable-v1-client.h" - -G_BEGIN_DECLS - -AstalRiverOutput *astal_river_output_new(guint id, struct wl_output *wl_output, - struct zriver_status_manager_v1 *status_manager, - struct wl_display *wl_display); - -struct wl_output *astal_river_output_get_wl_output(AstalRiverOutput *self); -void astal_river_output_set_focused_view(AstalRiverOutput *self, const gchar *focused_view); - -G_END_DECLS - -#endif // !ASTAL_RIVER_OUTPUT_PRIVATE_H diff --git a/river/include/wayland-source.h b/river/include/wayland-source.h deleted file mode 100644 index b219589..0000000 --- a/river/include/wayland-source.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef __WAYLAND_SOURCE_H__ -#define __WAYLAND_SOURCE_H__ - -#include - -G_BEGIN_DECLS - -typedef struct _WLSource WLSource; - -WLSource* wl_source_new(); -void wl_source_free(WLSource* self); -struct wl_display* wl_source_get_display(WLSource* source); - -G_END_DECLS - -#endif /* __WAYLAND_SOURCE_H__ */ diff --git a/river/meson.build b/river/meson.build deleted file mode 100644 index 0cee54d..0000000 --- a/river/meson.build +++ /dev/null @@ -1,23 +0,0 @@ -project('astal_river', - 'c', - version : '0.1.0', - 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') - -subdir('protocols') -subdir('include') -subdir('src') diff --git a/river/meson_options.txt b/river/meson_options.txt deleted file mode 100644 index d585854..0000000 --- a/river/meson_options.txt +++ /dev/null @@ -1,2 +0,0 @@ -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/river/protocols/meson.build b/river/protocols/meson.build deleted file mode 100644 index 69f01bb..0000000 --- a/river/protocols/meson.build +++ /dev/null @@ -1,23 +0,0 @@ - -wayland_scanner = find_program('wayland-scanner') - -protocols = [ - 'river-status-unstable-v1.xml', - 'river-control-unstable-v1.xml' -] - -gen_client_header = generator(wayland_scanner, - output: ['@BASENAME@-client.h'], - arguments: ['-c', 'client-header', '@INPUT@', '@BUILD_DIR@/@BASENAME@-client.h']) - -gen_private_code = generator(wayland_scanner, - output: ['@BASENAME@.c'], - arguments: ['-c', 'private-code', '@INPUT@', '@BUILD_DIR@/@BASENAME@.c']) - -client_protocol_srcs = [] - -foreach protocol : protocols - client_header = gen_client_header.process(protocol) - code = gen_private_code.process(protocol) - client_protocol_srcs += [client_header, code] -endforeach diff --git a/river/protocols/river-control-unstable-v1.xml b/river/protocols/river-control-unstable-v1.xml deleted file mode 100644 index c431901..0000000 --- a/river/protocols/river-control-unstable-v1.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - Copyright 2020 The River Developers - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - - - This interface allows clients to run compositor commands and receive a - success/failure response with output or a failure message respectively. - - Each command is built up in a series of add_argument requests and - executed with a run_command request. The first argument is the command - to be run. - - A complete list of commands should be made available in the man page of - the compositor. - - - - - This request indicates that the client will not use the - river_control object any more. Objects that have been created - through this instance are not affected. - - - - - - Arguments are stored by the server in the order they were sent until - the run_command request is made. - - - - - - - Execute the command built up using the add_argument request for the - given seat. - - - - - - - - - This object is created by the run_command request. Exactly one of the - success or failure events will be sent. This object will be destroyed - by the compositor after one of the events is sent. - - - - - Sent when the command has been successfully received and executed by - the compositor. Some commands may produce output, in which case the - output argument will be a non-empty string. - - - - - - - Sent when the command could not be carried out. This could be due to - sending a non-existent command, no command, not enough arguments, too - many arguments, invalid arguments, etc. - - - - - - diff --git a/river/protocols/river-status-unstable-v1.xml b/river/protocols/river-status-unstable-v1.xml deleted file mode 100644 index 09c52c1..0000000 --- a/river/protocols/river-status-unstable-v1.xml +++ /dev/null @@ -1,149 +0,0 @@ - - - - Copyright 2020 The River Developers - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - - - A global factory for objects that receive status information specific - to river. It could be used to implement, for example, a status bar. - - - - - This request indicates that the client will not use the - river_status_manager object any more. Objects that have been created - through this instance are not affected. - - - - - - This creates a new river_output_status object for the given wl_output. - - - - - - - - This creates a new river_seat_status object for the given wl_seat. - - - - - - - - - This interface allows clients to receive information about the current - windowing state of an output. - - - - - This request indicates that the client will not use the - river_output_status object any more. - - - - - - Sent once binding the interface and again whenever the tag focus of - the output changes. - - - - - - - Sent once on binding the interface and again whenever the tag state - of the output changes. - - - - - - - Sent once on binding the interface and again whenever the set of - tags with at least one urgent view changes. - - - - - - - Sent once on binding the interface should a layout name exist and again - whenever the name changes. - - - - - - - Sent when the current layout name has been removed without a new one - being set, for example when the active layout generator disconnects. - - - - - - - This interface allows clients to receive information about the current - focus of a seat. Note that (un)focused_output events will only be sent - if the client has bound the relevant wl_output globals. - - - - - This request indicates that the client will not use the - river_seat_status object any more. - - - - - - Sent on binding the interface and again whenever an output gains focus. - - - - - - - Sent whenever an output loses focus. - - - - - - - Sent once on binding the interface and again whenever the focused - view or a property thereof changes. The title may be an empty string - if no view is focused or the focused view did not set a title. - - - - - - - Sent once on binding the interface and again whenever a new mode - is entered (e.g. with riverctl enter-mode foobar). - - - - - - diff --git a/river/src/astal-river.c b/river/src/astal-river.c deleted file mode 100644 index 37f34d5..0000000 --- a/river/src/astal-river.c +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include -#include - -#include "gio/gio.h" -#include "astal-river.h" - -GMainLoop* loop; - -void print_json(AstalRiverRiver* river) { - JsonNode* json = json_gobject_serialize(G_OBJECT(river)); - - gchar* json_str = json_to_string(json, FALSE); - g_print("%s\n", json_str); - g_free(json); -} - -int main(int argc, char** argv) { - gboolean daemon = FALSE; - - int opt; - const char* optstring = "d"; - - static struct option long_options[] = {{"daemon", no_argument, NULL, 'd'}, {NULL, 0, NULL, 0}}; - - while ((opt = getopt_long(argc, argv, optstring, long_options, NULL)) != -1) { - switch (opt) { - case 'd': - daemon = TRUE; - break; - default: - g_print("Usage: %s [-d]\n", argv[0]); - exit(EXIT_FAILURE); - } - } - - GError* error = NULL; - AstalRiverRiver* river = g_initable_new(ASTAL_RIVER_TYPE_RIVER, NULL, &error, NULL); - if (error) { - g_critical("%s\n", error->message); - exit(EXIT_FAILURE); - } - if (daemon) { - loop = g_main_loop_new(NULL, FALSE); - g_signal_connect(river, "changed", G_CALLBACK(print_json), NULL); - g_main_loop_run(loop); - } else { - print_json(river); - g_object_unref(river); - } -} diff --git a/river/src/meson.build b/river/src/meson.build deleted file mode 100644 index 647cc87..0000000 --- a/river/src/meson.build +++ /dev/null @@ -1,70 +0,0 @@ -srcs = files( - 'river.c', - 'river-output.c', - 'wayland-source.c', -) - -deps = [ - dependency('gobject-2.0'), - dependency('gio-2.0'), - dependency('wayland-client'), - dependency('json-glib-1.0'), -] - -astal_river_lib = library( - 'astal-river', - sources : srcs + client_protocol_srcs, - include_directories : astal_river_inc, - dependencies : deps, - version : meson.project_version(), - install : true -) - -libastal_river = declare_dependency( - link_with : astal_river_lib, - include_directories : astal_river_inc) - -astal_river_executable = executable( - 'astal-river', - files('astal-river.c'), - dependencies : [ - dependency('gobject-2.0'), - dependency('gio-2.0'), - dependency('json-glib-1.0'), - libastal_river - ], - install : true) - -pkg_config_name = 'astal-river-' + lib_so_version - -if get_option('introspection') - gir = gnome.generate_gir( - astal_river_lib, - sources : srcs + astal_river_headers, - nsversion : '0.1', - namespace : 'AstalRiver', - symbol_prefix : 'astal_river', - identifier_prefix : 'AstalRiver', - includes : ['GObject-2.0', 'Gio-2.0'], - header : 'astal-river.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-river', - version : meson.project_version(), - libraries : [astal_river_lib], - filebase : pkg_config_name, - subdirs : 'astal', - description : 'astal riverentication module', - url : 'https://github.com/astal-sh/river') diff --git a/river/src/river-output.c b/river/src/river-output.c deleted file mode 100644 index 6317db1..0000000 --- a/river/src/river-output.c +++ /dev/null @@ -1,343 +0,0 @@ -#include - -#include "river-private.h" -#include "river-status-unstable-v1-client.h" - -struct _AstalRiverOutput { - GObject parent_instance; - guint focused_tags; - guint occupied_tags; - guint urgent_tags; - gchar* layout_name; - gchar* focused_view; - guint id; - gchar* name; -}; - -typedef struct { - struct zriver_status_manager_v1* river_status_manager; - struct zriver_output_status_v1* river_output_status; - struct wl_display* wl_display; - struct wl_output* wl_output; -} AstalRiverOutputPrivate; - -G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalRiverOutput, astal_river_output, G_TYPE_OBJECT); - -typedef enum { - ASTAL_RIVER_OUTPUT_PROP_FOCUSED_TAGS = 1, - ASTAL_RIVER_OUTPUT_PROP_OCCUPIED_TAGS, - ASTAL_RIVER_OUTPUT_PROP_URGENT_TAGS, - ASTAL_RIVER_OUTPUT_PROP_LAYOUT_NAME, - ASTAL_RIVER_OUTPUT_PROP_NAME, - ASTAL_RIVER_OUTPUT_PROP_FOCUSED_VIEW, - ASTAL_RIVER_OUTPUT_PROP_ID, - ASTAL_RIVER_OUTPUT_N_PROPERTIES -} AstalRiverOutputProperties; - -typedef enum { - ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED, - ASTAL_RIVER_OUTPUT_N_SIGNALS -} AstalRiverOutputSignals; - -static guint astal_river_output_signals[ASTAL_RIVER_OUTPUT_N_SIGNALS] = { - 0, -}; - -static GParamSpec* astal_river_output_properties[ASTAL_RIVER_OUTPUT_N_PROPERTIES] = { - NULL, -}; - -/** - * astal_river_output_get_nid - * @self: the AstalRiverOutput object - * - * Returns: the id of the underlying wl_output object - */ -guint astal_river_output_get_id(AstalRiverOutput* self) { return self->id; } - -/** - * astal_river_output_get_name - * @self: the AstalRiverOutput object - * - * Returns: (transfer none) (nullable): the name of the output - */ -gchar* astal_river_output_get_name(AstalRiverOutput* self) { return self->name; } - -/** - * astal_river_output_get_layout_name - * @self: the AstalRiverOutput object - * - * Returns: (transfer none) (nullable): the currently used layout name of the output - */ -gchar* astal_river_output_get_layout_name(AstalRiverOutput* self) { return self->layout_name; } - -/** - * astal_river_output_get_focused_view - * @self: the AstalRiverOutput object - * - * Returns: (transfer none) (nullable): the focused view on the output - */ -gchar* astal_river_output_get_focused_view(AstalRiverOutput* self) { return self->focused_view; } - -void astal_river_output_set_focused_view(AstalRiverOutput* self, const gchar* focused_view) { - g_free(self->focused_view); - self->focused_view = g_strdup(focused_view); - g_object_notify(G_OBJECT(self), "focused-view"); - g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); -} - -/** - * astal_river_output_get_focused_tags - * @self: the AstalRiverOutput object - * - * Returns: the focused tags of the output - */ -guint astal_river_output_get_focused_tags(AstalRiverOutput* self) { return self->focused_tags; } - -/** - * astal_river_output_get_urgent_tags - * @self: the AstalRiverOutput object - * - * Returns: the urgent tags of the output - */ -guint astal_river_output_get_urgent_tags(AstalRiverOutput* self) { return self->urgent_tags; } - -/** - * astal_river_output_get_occupied_tags - * @self: the AstalRiverOutput object - * - * Returns: the occupied tags of the output - */ -guint astal_river_output_get_occupied_tags(AstalRiverOutput* self) { return self->occupied_tags; } - -struct wl_output* astal_river_output_get_wl_output(AstalRiverOutput* self) { - AstalRiverOutputPrivate* priv = astal_river_output_get_instance_private(self); - return priv->wl_output; -} - -static void astal_river_output_get_property(GObject* object, guint property_id, GValue* value, - GParamSpec* pspec) { - AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(object); - - switch (property_id) { - case ASTAL_RIVER_OUTPUT_PROP_FOCUSED_TAGS: - g_value_set_uint(value, self->focused_tags); - break; - case ASTAL_RIVER_OUTPUT_PROP_OCCUPIED_TAGS: - g_value_set_uint(value, self->occupied_tags); - break; - case ASTAL_RIVER_OUTPUT_PROP_URGENT_TAGS: - g_value_set_uint(value, self->urgent_tags); - break; - case ASTAL_RIVER_OUTPUT_PROP_ID: - g_value_set_uint(value, self->id); - break; - case ASTAL_RIVER_OUTPUT_PROP_NAME: - g_value_set_string(value, self->name); - break; - case ASTAL_RIVER_OUTPUT_PROP_LAYOUT_NAME: - g_value_set_string(value, self->layout_name); - break; - case ASTAL_RIVER_OUTPUT_PROP_FOCUSED_VIEW: - g_value_set_string(value, self->focused_view); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_river_output_set_property(GObject* object, guint property_id, const GValue* value, - GParamSpec* pspec) { - AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(object); - - switch (property_id) { - case ASTAL_RIVER_OUTPUT_PROP_ID: - self->id = g_value_get_uint(value); - g_object_notify(G_OBJECT(self), "id"); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } - g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); -} - -static void noop() {} - -static void astal_river_handle_focused_tags(void* data, struct zriver_output_status_v1* status, - uint32_t tags) { - AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); - self->focused_tags = tags; - g_object_notify(G_OBJECT(self), "focused-tags"); - g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); -} - -static void astal_river_handle_urgent_tags(void* data, struct zriver_output_status_v1* status, - uint32_t tags) { - AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); - self->urgent_tags = tags; - g_object_notify(G_OBJECT(self), "urgent-tags"); - g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); -} - -static void astal_river_handle_occupied_tags(void* data, struct zriver_output_status_v1* status, - struct wl_array* view_tags) { - AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); - guint tags = 0; - guint* view; - wl_array_for_each(view, view_tags) { tags |= *view; } - - self->occupied_tags = tags; - g_object_notify(G_OBJECT(self), "occupied-tags"); - g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); -} - -static void astal_river_handle_layout_name(void* data, struct zriver_output_status_v1* status, - const char* name) { - AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); - g_free(self->layout_name); - self->layout_name = g_strdup(name); - g_object_notify(G_OBJECT(self), "layout-name"); - g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); -} - -static void astal_river_handle_layout_name_clear(void* data, - struct zriver_output_status_v1* status) { - AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); - g_free(self->layout_name); - self->layout_name = NULL; - g_object_notify(G_OBJECT(self), "layout-name"); - g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); -} - -static void astal_river_wl_output_handle_name(void* data, struct wl_output* output, - const char* name) { - AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(data); - g_free(self->name); - self->name = g_strdup(name); - g_object_notify(G_OBJECT(self), "name"); - g_signal_emit(self, astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED], 0); -} - -static const struct zriver_output_status_v1_listener output_status_listener = { - .focused_tags = astal_river_handle_focused_tags, - .view_tags = astal_river_handle_occupied_tags, - .urgent_tags = astal_river_handle_urgent_tags, - .layout_name = astal_river_handle_layout_name, - .layout_name_clear = astal_river_handle_layout_name_clear, -}; - -static const struct wl_output_listener wl_output_listener = { - .name = astal_river_wl_output_handle_name, - .geometry = noop, - .mode = noop, - .scale = noop, - .description = noop, - .done = noop, -}; - -static void astal_river_output_finalize(GObject* object) { - AstalRiverOutput* self = ASTAL_RIVER_OUTPUT(object); - AstalRiverOutputPrivate* priv = astal_river_output_get_instance_private(self); - - zriver_output_status_v1_destroy(priv->river_output_status); - wl_output_destroy(priv->wl_output); - - wl_display_roundtrip(priv->wl_display); - - g_free(self->layout_name); - g_free(self->name); - - G_OBJECT_CLASS(astal_river_output_parent_class)->finalize(object); -} - -static void astal_river_output_init(AstalRiverOutput* self) {} - -AstalRiverOutput* astal_river_output_new(guint id, struct wl_output* wl_output, - struct zriver_status_manager_v1* status_manager, - struct wl_display* wl_display) { - AstalRiverOutput* self = g_object_new(ASTAL_RIVER_TYPE_OUTPUT, NULL); - AstalRiverOutputPrivate* priv = astal_river_output_get_instance_private(self); - - self->id = id; - priv->wl_display = wl_display; - priv->wl_output = wl_output; - priv->river_status_manager = status_manager; - - priv->river_output_status = - zriver_status_manager_v1_get_river_output_status(priv->river_status_manager, wl_output); - - zriver_output_status_v1_add_listener(priv->river_output_status, &output_status_listener, self); - - wl_output_add_listener(wl_output, &wl_output_listener, self); - - wl_display_roundtrip(priv->wl_display); - - return self; -} - -static void astal_river_output_class_init(AstalRiverOutputClass* class) { - GObjectClass* object_class = G_OBJECT_CLASS(class); - object_class->get_property = astal_river_output_get_property; - object_class->set_property = astal_river_output_set_property; - object_class->finalize = astal_river_output_finalize; - /** - * AstalRiverOutput:focused-tags: - * - * The currently focused tags - */ - astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_FOCUSED_TAGS] = g_param_spec_uint( - "focused-tags", "focused-tags", "currently focused tags", 0, INT_MAX, 0, G_PARAM_READABLE); - /** - * AstalRiverOutput:occupied-tags: - * - * The currently occupied tags - */ - astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_OCCUPIED_TAGS] = - g_param_spec_uint("occupied-tags", "occupied-tags", "currently occupied tags", 0, INT_MAX, - 0, G_PARAM_READABLE); - /** - * AstalRiverOutput:urgent-tags: - * - * The currently tags marked as urgent - */ - astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_URGENT_TAGS] = g_param_spec_uint( - "urgent-tags", "urgent-tags", "currently urgent tags", 0, INT_MAX, 0, G_PARAM_READABLE); - /** - * AstalRiverOutput:id: - * - * The id of the underlying wl_output object - */ - astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_ID] = - g_param_spec_uint("id", "id", "id of the output object", 0, INT_MAX, 0, G_PARAM_READABLE); - /** - * AstalRiverOutput:layout-name: - * - * The name of active layout - */ - astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_LAYOUT_NAME] = g_param_spec_string( - "layout-name", "layout-name", "name of the current layout", NULL, G_PARAM_READABLE); - /** - * AstalRiverOutput:name: - * - * The name of this output - */ - astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_NAME] = - g_param_spec_string("name", "name", "name of the output", NULL, G_PARAM_READABLE); - /** - * AstalRiverOutput:focused-view: - * - * The name of currently focused view - */ - astal_river_output_properties[ASTAL_RIVER_OUTPUT_PROP_FOCUSED_VIEW] = - g_param_spec_string("focused-view", "focused-view", - "name of last focused view on this output", NULL, G_PARAM_READABLE); - - g_object_class_install_properties(object_class, ASTAL_RIVER_OUTPUT_N_PROPERTIES, - astal_river_output_properties); - - astal_river_output_signals[ASTAL_RIVER_OUTPUT_SIGNAL_CHANGED] = - g_signal_new("changed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, - G_TYPE_NONE, 0); -} diff --git a/river/src/river.c b/river/src/river.c deleted file mode 100644 index a735898..0000000 --- a/river/src/river.c +++ /dev/null @@ -1,504 +0,0 @@ -#include -#include -#include -#include - -#include "river-control-unstable-v1-client.h" -#include "river-private.h" -#include "river-status-unstable-v1-client.h" -#include "wayland-source.h" - -struct _AstalRiverRiver { - GObject parent_instance; - GList* outputs; - gchar* focused_output; - gchar* focused_view; - gchar* mode; -}; - -typedef struct { - GHashTable* signal_ids; - gboolean init; - struct wl_registry* wl_registry; - struct wl_seat* seat; - struct wl_display* display; - WLSource* wl_source; - struct zriver_status_manager_v1* river_status_manager; - struct zriver_control_v1* river_control; - struct zriver_seat_status_v1* river_seat_status; -} AstalRiverRiverPrivate; - -static JsonSerializableIface* serializable_iface = NULL; -static void astal_river_river_initable_iface_init(GInitableIface* iface); -static void astal_river_river_json_serializable_iface_init(JsonSerializableIface* g_iface); - -G_DEFINE_TYPE_WITH_CODE(AstalRiverRiver, astal_river_river, G_TYPE_OBJECT, - G_ADD_PRIVATE(AstalRiverRiver) G_IMPLEMENT_INTERFACE( - G_TYPE_INITABLE, astal_river_river_initable_iface_init) - G_IMPLEMENT_INTERFACE(JSON_TYPE_SERIALIZABLE, - astal_river_river_json_serializable_iface_init)) - -typedef enum { - ASTAL_RIVER_RIVER_PROP_FOCUSED_OUTPUT = 1, - ASTAL_RIVER_RIVER_PROP_FOCUSED_VIEW, - ASTAL_RIVER_RIVER_PROP_MODE, - ASTAL_RIVER_RIVER_PROP_OUTPUTS, - ASTAL_RIVER_RIVER_N_PROPERTIES -} AstalRiverRiverProperties; - -typedef enum { - ASTAL_RIVER_RIVER_SIGNAL_CHANGED, - ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_ADDED, - ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_REMOVED, - ASTAL_RIVER_RIVER_N_SIGNALS -} AstalRiverRiverSignals; - -static guint astal_river_river_signals[ASTAL_RIVER_RIVER_N_SIGNALS] = { - 0, -}; -static GParamSpec* astal_river_river_properties[ASTAL_RIVER_RIVER_N_PROPERTIES] = { - NULL, -}; - -static void reemit_changed(AstalRiverOutput* output, AstalRiverRiver* self) { - g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); -} - -static AstalRiverOutput* find_output_by_id(AstalRiverRiver* self, uint32_t id) { - GList* output = self->outputs; - while (output != NULL) { - AstalRiverOutput* river_output = output->data; - if (astal_river_output_get_id(river_output) == id) return river_output; - output = output->next; - } - return NULL; -} - -static AstalRiverOutput* find_output_by_output(AstalRiverRiver* self, struct wl_output* wl_output) { - GList* output = self->outputs; - while (output != NULL) { - AstalRiverOutput* river_output = output->data; - if (astal_river_output_get_wl_output(river_output) == wl_output) return river_output; - output = output->next; - } - return NULL; -} - -static AstalRiverOutput* find_output_by_name(AstalRiverRiver* self, gchar* name) { - GList* output = self->outputs; - while (output != NULL) { - AstalRiverOutput* river_output = output->data; - if (strcmp(astal_river_output_get_name(river_output), name) == 0) return river_output; - output = output->next; - } - return NULL; -} - -/** - * astal_river_river_get_outputs - * @self: the AstalRiverRiver object - * - * Returns: (transfer none) (element-type AstalRiver.Output): a list of all outputs - * - */ -GList* astal_river_river_get_outputs(AstalRiverRiver* self) { return self->outputs; } - -/** - * astal_river_river_get_output - * @self: the AstalRiverRiver object - * @name: the name of the output - * - * Returns: (transfer none) (nullable): the output with the given name or null - */ -AstalRiverOutput* astal_river_river_get_output(AstalRiverRiver* self, gchar* name) { - return find_output_by_name(self, name); -} - -/** - * astal_river_river_get_focused_view - * @self: the AstalRiverOutput object - * - * Returns: (transfer none) (nullable): the currently focused view - */ -gchar* astal_river_river_get_focused_view(AstalRiverRiver* self) { return self->focused_view; } - -/** - * astal_river_river_get_focused_output - * @self: the AstalRiverOutput object - * - * Returns: (transfer none) (nullable): the name of the currently focused output - */ -gchar* astal_river_river_get_focused_output(AstalRiverRiver* self) { return self->focused_output; } - -/** - * astal_river_river_get_mode - * @self: the AstalRiverOutput object - * - * Returns: (transfer none) (nullable): the currently active mode - */ -gchar* astal_river_river_get_mode(AstalRiverRiver* self) { return self->mode; } - -static void astal_river_river_get_property(GObject* object, guint property_id, GValue* value, - GParamSpec* pspec) { - AstalRiverRiver* self = ASTAL_RIVER_RIVER(object); - - switch (property_id) { - case ASTAL_RIVER_RIVER_PROP_MODE: - g_value_set_string(value, self->mode); - break; - case ASTAL_RIVER_RIVER_PROP_FOCUSED_VIEW: - g_value_set_string(value, self->focused_view); - break; - case ASTAL_RIVER_RIVER_PROP_FOCUSED_OUTPUT: - g_value_set_string(value, self->focused_output); - break; - case ASTAL_RIVER_RIVER_PROP_OUTPUTS: - g_value_set_pointer(value, self->outputs); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static JsonNode* astal_river_river_serialize_property(JsonSerializable* serializable, - const gchar* name, const GValue* value, - GParamSpec* pspec) { - JsonNode* retval = NULL; - if (strcmp(name, "outputs") == 0) { - JsonArray* outputs = json_array_new(); - retval = json_node_new(JSON_NODE_ARRAY); - GList* output = g_value_get_pointer(value); - while (output != NULL) { - AstalRiverOutput* river_output = output->data; - json_array_add_element(outputs, json_gobject_serialize(G_OBJECT(river_output))); - output = output->next; - } - json_node_take_array(retval, outputs); - } else - retval = serializable_iface->serialize_property(serializable, name, value, pspec); - - return retval; -} - -static void noop() {} - -static void river_seat_status_handle_focused_output(void* data, - struct zriver_seat_status_v1* seat_status, - struct wl_output* focused_output) { - AstalRiverRiver* self = ASTAL_RIVER_RIVER(data); - g_free(self->focused_output); - AstalRiverOutput* output = find_output_by_output(self, focused_output); - if (output == NULL) return; - self->focused_output = g_strdup(astal_river_output_get_name(output)); - g_object_notify(G_OBJECT(self), "focused-output"); - g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); -} - -static void river_seat_status_handle_focused_view(void* data, - struct zriver_seat_status_v1* seat_status, - const char* focused_view) { - AstalRiverRiver* self = ASTAL_RIVER_RIVER(data); - g_free(self->focused_view); - self->focused_view = g_strdup(focused_view); - AstalRiverOutput* output = find_output_by_name(self, self->focused_output); - astal_river_output_set_focused_view(output, focused_view); - g_object_notify(G_OBJECT(self), "focused-view"); - g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); -} - -static void river_seat_status_handle_mode(void* data, struct zriver_seat_status_v1* seat_status, - const char* mode) { - AstalRiverRiver* self = ASTAL_RIVER_RIVER(data); - g_free(self->mode); - self->mode = g_strdup(mode); - g_object_notify(G_OBJECT(self), "mode"); - g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); -} - -static const struct zriver_seat_status_v1_listener river_seat_status_listener = { - .focused_output = river_seat_status_handle_focused_output, - .unfocused_output = noop, - .focused_view = river_seat_status_handle_focused_view, - .mode = river_seat_status_handle_mode, -}; - -static void global_registry_handler(void* data, struct wl_registry* registry, uint32_t id, - const char* interface, uint32_t version) { - AstalRiverRiver* self = ASTAL_RIVER_RIVER(data); - AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); - if (strcmp(interface, wl_output_interface.name) == 0) { - if (priv->river_status_manager == NULL) return; - struct wl_output* wl_out = wl_registry_bind(registry, id, &wl_output_interface, 4); - AstalRiverOutput* output = - astal_river_output_new(id, wl_out, priv->river_status_manager, priv->display); - - self->outputs = g_list_append(self->outputs, output); - g_object_notify(G_OBJECT(self), "outputs"); - g_signal_emit(G_OBJECT(self), - astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_ADDED], 0, - astal_river_output_get_name(output)); - g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); - - guint signal_id = g_signal_connect(output, "changed", G_CALLBACK(reemit_changed), self); - g_hash_table_insert(priv->signal_ids, GUINT_TO_POINTER(id), GUINT_TO_POINTER(signal_id)); - } else if (strcmp(interface, wl_seat_interface.name) == 0) { - priv->seat = wl_registry_bind(registry, id, &wl_seat_interface, 4); - } else if (strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { - priv->river_status_manager = - wl_registry_bind(registry, id, &zriver_status_manager_v1_interface, 4); - } else if (strcmp(interface, zriver_control_v1_interface.name) == 0) { - priv->river_control = wl_registry_bind(registry, id, &zriver_control_v1_interface, 1); - } -} - -static void astal_river_river_callback_success(void* data, struct zriver_command_callback_v1* cb, - const char* msg) { - AstalRiverCommandCallback callback = (AstalRiverCommandCallback)(data); - callback(TRUE, msg); -} - -static void astal_river_river_callback_failure(void* data, struct zriver_command_callback_v1* cb, - const char* msg) { - AstalRiverCommandCallback callback = (AstalRiverCommandCallback)(data); - callback(FALSE, msg); -} - -const struct zriver_command_callback_v1_listener cb_listener = { - .success = astal_river_river_callback_success, .failure = astal_river_river_callback_failure}; - -/** - * astal_river_river_run_command_async: - * @self: the AstalRiverRiver object - * @length: the length of the cmd array - * @cmd: (array length=length): the command to execute - * @callback: (scope async) (nullable): the callback to invoke. - * - * Calls the given callback with the provided parameters. - */ -void astal_river_river_run_command_async(AstalRiverRiver* self, gint length, const gchar** cmd, - AstalRiverCommandCallback callback) { - AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); - - for (gint i = 0; i < length; ++i) { - zriver_control_v1_add_argument(priv->river_control, cmd[i]); - } - - struct zriver_command_callback_v1* cb = - zriver_control_v1_run_command(priv->river_control, priv->seat); - if (callback != NULL) zriver_command_callback_v1_add_listener(cb, &cb_listener, callback); -} - -static void global_registry_remover(void* data, struct wl_registry* registry, uint32_t id) { - AstalRiverRiver* self = ASTAL_RIVER_RIVER(data); - AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); - AstalRiverOutput* output = find_output_by_id(self, id); - if (output != NULL) { - guint signal_id = - GPOINTER_TO_UINT(g_hash_table_lookup(priv->signal_ids, GUINT_TO_POINTER(id))); - g_hash_table_remove(priv->signal_ids, GUINT_TO_POINTER(id)); - g_signal_handler_disconnect(output, signal_id); - g_signal_emit(G_OBJECT(self), - astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_ADDED], 0, - astal_river_output_get_name(output)); - self->outputs = g_list_remove(self->outputs, output); - g_object_notify(G_OBJECT(self), "outputs"); - g_object_unref(output); - return; - } - g_signal_emit(self, astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED], 0); -} - -static const struct wl_registry_listener registry_listener = {global_registry_handler, - global_registry_remover}; - -static void astal_river_river_json_serializable_iface_init(JsonSerializableIface* iface) { - iface->serialize_property = astal_river_river_serialize_property; - serializable_iface = g_type_default_interface_peek(JSON_TYPE_SERIALIZABLE); -} - -static gboolean astal_river_river_initable_init(GInitable* initable, GCancellable* cancellable, - GError** error) { - AstalRiverRiver* self = ASTAL_RIVER_RIVER(initable); - AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); - - if (priv->init) return TRUE; - - priv->wl_source = wl_source_new(NULL, NULL); - - if (priv->wl_source == NULL) { - g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Can not connect to wayland display"); - return FALSE; - } - - priv->display = wl_source_get_display(priv->wl_source); - - priv->wl_registry = wl_display_get_registry(priv->display); - wl_registry_add_listener(priv->wl_registry, ®istry_listener, self); - - wl_display_roundtrip(priv->display); - - if (priv->river_status_manager == NULL) { - g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Can not connect river status protocol"); - return FALSE; - } - - priv->river_seat_status = - zriver_status_manager_v1_get_river_seat_status(priv->river_status_manager, priv->seat); - zriver_seat_status_v1_add_listener(priv->river_seat_status, &river_seat_status_listener, self); - - wl_display_roundtrip(priv->display); - - priv->init = TRUE; - return TRUE; -} - -static void astal_river_river_constructed(GObject* object) { - astal_river_river_initable_init(G_INITABLE(object), NULL, NULL); -} - -static void astal_river_river_initable_iface_init(GInitableIface* iface) { - iface->init = astal_river_river_initable_init; -} - -static void astal_river_river_init(AstalRiverRiver* self) { - AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); - self->outputs = NULL; - priv->init = FALSE; - priv->seat = NULL; - priv->display = NULL; - priv->river_status_manager = NULL; - priv->signal_ids = g_hash_table_new(g_direct_hash, g_direct_equal); -} - -/** - * astal_river_river_new - * - * creates a new River object. It is recommended to use the get_default() method - * instead of this method. - * - * Returns: (nullable): a newly created connection to river - */ -AstalRiverRiver* astal_river_river_new() { - return g_initable_new(ASTAL_RIVER_TYPE_RIVER, NULL, NULL, NULL); -} - -static void disconnect_signal(gpointer key, gpointer value, gpointer user_data) { - AstalRiverRiver* self = ASTAL_RIVER_RIVER(user_data); - - AstalRiverOutput* output = find_output_by_id(self, GPOINTER_TO_UINT(key)); - g_signal_handler_disconnect(output, GPOINTER_TO_UINT(value)); -} - -/** - * astal_river_river_get_default - * - * Returns: (nullable) (transfer none): gets the default River object. - */ -AstalRiverRiver* astal_river_river_get_default() { - static AstalRiverRiver* self = NULL; - - if (self == NULL) self = astal_river_river_new(); - - return self; -} - -/** - * astal_river_get_default - * - * Returns: (nullable) (transfer none): gets the default River object. - */ -AstalRiverRiver* astal_river_get_default() { return astal_river_river_get_default(); } - -static void astal_river_river_finalize(GObject* object) { - AstalRiverRiver* self = ASTAL_RIVER_RIVER(object); - AstalRiverRiverPrivate* priv = astal_river_river_get_instance_private(self); - - g_hash_table_foreach(priv->signal_ids, disconnect_signal, self); - g_hash_table_destroy(priv->signal_ids); - - if (priv->display != NULL) wl_display_roundtrip(priv->display); - - g_clear_list(&self->outputs, g_object_unref); - self->outputs = NULL; - - if (priv->wl_registry != NULL) wl_registry_destroy(priv->wl_registry); - if (priv->river_status_manager != NULL) - zriver_status_manager_v1_destroy(priv->river_status_manager); - if (priv->river_seat_status != NULL) zriver_seat_status_v1_destroy(priv->river_seat_status); - if (priv->seat != NULL) wl_seat_destroy(priv->seat); - if (priv->display != NULL) wl_display_flush(priv->display); - - if (priv->wl_source != NULL) wl_source_free(priv->wl_source); - - g_free(self->focused_view); - g_free(self->focused_output); - g_free(self->mode); - - G_OBJECT_CLASS(astal_river_river_parent_class)->finalize(object); -} - -static void astal_river_river_class_init(AstalRiverRiverClass* class) { - GObjectClass* object_class = G_OBJECT_CLASS(class); - object_class->get_property = astal_river_river_get_property; - object_class->finalize = astal_river_river_finalize; - object_class->constructed = astal_river_river_constructed; - - /** - * AstalRiverRiver:mode: - * - * The currently active mode - */ - astal_river_river_properties[ASTAL_RIVER_RIVER_PROP_MODE] = - g_param_spec_string("mode", "mode", "currently active mode", NULL, G_PARAM_READABLE); - /** - * AstalRiverRiver:focused-view: - * - * The name of the currently focused view - */ - astal_river_river_properties[ASTAL_RIVER_RIVER_PROP_FOCUSED_VIEW] = g_param_spec_string( - "focused-view", "focused-view", "currently focused view", NULL, G_PARAM_READABLE); - /** - * AstalRiverRiver:focused-output: - * - * The name of the currently focused output - */ - astal_river_river_properties[ASTAL_RIVER_RIVER_PROP_FOCUSED_OUTPUT] = g_param_spec_string( - "focused-output", "focused-output", "currently focused-output", NULL, G_PARAM_READABLE); - /** - * AstalRiverRiver:outputs: (type GList(AstalRiverOutput)) - * - * A list of AstalRiverOutput objects - */ - astal_river_river_properties[ASTAL_RIVER_RIVER_PROP_OUTPUTS] = - g_param_spec_pointer("outputs", "outputs", "a list of all outputs", G_PARAM_READABLE); - - g_object_class_install_properties(object_class, ASTAL_RIVER_RIVER_N_PROPERTIES, - astal_river_river_properties); - /** - * AstalRiverRiver::output-added: - * @river: the object which received the signal. - * @output: the name of the added output - * - * This signal is emitted when a new output was connected - */ - astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_ADDED] = - g_signal_new("output-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, G_TYPE_STRING); - /** - * AstalRiverRiver::output-removed: - * @river: the object which received the signal. - * @output: the name of the removed output - * - * This signal is emitted when a new output was disconnected - */ - astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_OUTPUT_REMOVED] = - g_signal_new("output-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, G_TYPE_STRING); - - astal_river_river_signals[ASTAL_RIVER_RIVER_SIGNAL_CHANGED] = - g_signal_new("changed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, - G_TYPE_NONE, 0); -} diff --git a/river/src/wayland-source.c b/river/src/wayland-source.c deleted file mode 100644 index 875c32c..0000000 --- a/river/src/wayland-source.c +++ /dev/null @@ -1,104 +0,0 @@ - -#include "wayland-source.h" - -#include -#include -#include - -struct _WLSource { - GSource source; - struct wl_display *display; - gpointer fd; - int error; -}; - -static gboolean wl_source_prepare(GSource *source, gint *timeout) { - WLSource *self = (WLSource *)source; - - *timeout = 0; - if (wl_display_prepare_read(self->display) != 0) - return TRUE; - else if (wl_display_flush(self->display) < 0) { - self->error = errno; - return TRUE; - } - *timeout = -1; - return FALSE; -} - -static gboolean wl_source_check(GSource *source) { - WLSource *self = (WLSource *)source; - - if (self->error > 0) return TRUE; - - GIOCondition revents; - revents = g_source_query_unix_fd(source, self->fd); - - if (revents & G_IO_IN) { - if (wl_display_read_events(self->display) < 0) self->error = errno; - } else - wl_display_cancel_read(self->display); - - return revents > 0; -} - -static gboolean wl_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { - WLSource *self = (WLSource *)source; - GIOCondition revents; - - revents = g_source_query_unix_fd(source, self->fd); - if ((self->error > 0) || (revents & (G_IO_ERR | G_IO_HUP))) { - errno = self->error; - self->error = 0; - if (callback != NULL) return callback(user_data); - return G_SOURCE_REMOVE; - } - - if (wl_display_dispatch_pending(self->display) < 0) { - if (callback != NULL) return callback(user_data); - return G_SOURCE_REMOVE; - } - - return G_SOURCE_CONTINUE; -} - -static void wl_source_finalize(GSource *source) { - WLSource *self = (WLSource *)source; - wl_display_disconnect(self->display); -} - -static GSourceFuncs wl_source_funcs = { - .prepare = wl_source_prepare, - .check = wl_source_check, - .dispatch = wl_source_dispatch, - .finalize = wl_source_finalize, -}; - -WLSource *wl_source_new() { - struct wl_display *display; - WLSource *self; - GSource *source; - - display = wl_display_connect(NULL); - if (display == NULL) return NULL; - - source = g_source_new(&wl_source_funcs, sizeof(WLSource)); - self = (WLSource *)source; - self->display = display; - - self->fd = g_source_add_unix_fd(source, wl_display_get_fd(self->display), - G_IO_IN | G_IO_ERR | G_IO_HUP); - - g_source_attach(source, NULL); - - return self; -} - -void wl_source_free(WLSource *self) { - GSource *source = (GSource *)self; - g_return_if_fail(source != NULL); - g_source_destroy(source); - g_source_unref(source); -} - -struct wl_display *wl_source_get_display(WLSource *self) { return self->display; } diff --git a/river/version b/river/version deleted file mode 100644 index 6e8bf73..0000000 --- a/river/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/tray/LICENSE b/tray/LICENSE deleted file mode 100644 index 67cd97b..0000000 --- a/tray/LICENSE +++ /dev/null @@ -1,503 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - diff --git a/tray/README.md b/tray/README.md deleted file mode 100644 index c436746..0000000 --- a/tray/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# libastal-tray -a library for managing the systemtray by implementing the StatusNotifierItem dbus protocol. - -## Build from source -### Dependencies - -- meson -- glib -- gdk-pixbuf -- gtk3 -- gobject-introspection -- dbusemenu-gtk3 -- vala - -```sh -# Clone the repository -git clone https://github.com/astal-sh/tray -cd tray - -# Setup and build -meson setup build -meson compile -C build - -# Install -meson install -C build -``` - -## Todo -- docs -- cli tool diff --git a/tray/flake.lock b/tray/flake.lock deleted file mode 100644 index 8d2ab6e..0000000 --- a/tray/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1719254875, - "narHash": "sha256-ECni+IkwXjusHsm9Sexdtq8weAq/yUyt1TWIemXt3Ko=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "2893f56de08021cffd9b6b6dfc70fd9ccd51eb60", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/tray/flake.nix b/tray/flake.nix deleted file mode 100644 index c78cbaa..0000000 --- a/tray/flake.nix +++ /dev/null @@ -1,56 +0,0 @@ -{ - description = "Library and cli for the StatusNotifierItem protocol"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - - outputs = { - self, - nixpkgs, - }: let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs {inherit system;}; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - ]; - - buildInputs = with pkgs; [ - gtk3 - glib - gdk-pixbuf - libdbusmenu-gtk3 - json-glib - ]; - in { - packages.${system} = rec { - default = tray; - tray = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-tray"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - tray = pkgs.mkShell { - inherit nativeBuildInputs; - buildInputs = - buildInputs - ++ [ - self.packages.${system}.default - pkgs.gjs - ]; - }; - }; - }; -} diff --git a/tray/meson.build b/tray/meson.build deleted file mode 100644 index 4a52d56..0000000 --- a/tray/meson.build +++ /dev/null @@ -1,19 +0,0 @@ -project( - 'astal-tray', - 'vala', - 'c', - version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), - meson_version: '>= 0.62.0', - default_options: [ - 'warning_level=2', - 'werror=false', - 'c_std=gnu11', - ], -) - -assert( - get_option('lib') or get_option('cli'), - 'Either lib or cli option must be set to true.', -) - -subdir('src') diff --git a/tray/meson_options.txt b/tray/meson_options.txt deleted file mode 100644 index f110242..0000000 --- a/tray/meson_options.txt +++ /dev/null @@ -1,11 +0,0 @@ -option( - 'lib', - type: 'boolean', - value: true, -) - -option( - 'cli', - type: 'boolean', - value: true, -) diff --git a/tray/src/cli.vala b/tray/src/cli.vala deleted file mode 100644 index 3147fb5..0000000 --- a/tray/src/cli.vala +++ /dev/null @@ -1,54 +0,0 @@ -static bool version; -static bool daemonize; - -const OptionEntry[] options = { - { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, "Print version number", null }, - { "daemonize", 'd', OptionFlags.NONE, OptionArg.NONE, ref daemonize, "Monitor the systemtray", null }, - { null }, -}; - -int main(string[] argv) { - try { - var opts = new OptionContext(); - opts.add_main_entries(options, null); - opts.set_help_enabled(true); - opts.set_ignore_unknown_options(false); - opts.parse(ref argv); - } catch (OptionError err) { - printerr (err.message); - return 1; - } - - if (version) { - print(AstalTray.VERSION); - return 0; - } - - if (daemonize) { - var loop = new MainLoop(); - var tray = new AstalTray.Tray(); - - tray.item_added.connect((id) => { - AstalTray.TrayItem item = tray.get_item(id); - - stdout.printf("{\"event\":\"item_added\",\"id\":\"%s\",\"item\":%s}\n", - id, item.to_json_string()); - stdout.flush(); - - item.changed.connect(() => { - stdout.printf("{\"event\":\"item_changed\",\"id\":\"%s\",\"item\":%s}\n", - id, item.to_json_string()); - stdout.flush(); - }); - }); - - tray.item_removed.connect((id) => { - stdout.printf("{\"event\":\"item_removed\",\"id\":\"%s\"}\n", id); - stdout.flush(); - }); - - loop.run(); - } - - return 0; -} diff --git a/tray/src/config.vala.in b/tray/src/config.vala.in deleted file mode 100644 index 8ef8498..0000000 --- a/tray/src/config.vala.in +++ /dev/null @@ -1,6 +0,0 @@ -namespace AstalTray { - public const int MAJOR_VERSION = @MAJOR_VERSION@; - public const int MINOR_VERSION = @MINOR_VERSION@; - public const int MICRO_VERSION = @MICRO_VERSION@; - public const string VERSION = "@VERSION@"; -} diff --git a/tray/src/meson.build b/tray/src/meson.build deleted file mode 100644 index b2229b0..0000000 --- a/tray/src/meson.build +++ /dev/null @@ -1,100 +0,0 @@ -version_split = meson.project_version().split('.') -api_version = version_split[0] + '.' + version_split[1] -gir = 'AstalTray-' + api_version + '.gir' -typelib = 'AstalTray-' + api_version + '.typelib' - -config = configure_file( - input: 'config.vala.in', - output: 'config.vala', - configuration: { - 'VERSION': meson.project_version(), - 'MAJOR_VERSION': version_split[0], - 'MINOR_VERSION': version_split[1], - 'MICRO_VERSION': version_split[2], - }, -) - -deps = [ - dependency('glib-2.0'), - dependency('gobject-2.0'), - dependency('gio-2.0'), - dependency('json-glib-1.0'), - dependency('gdk-pixbuf-2.0'), - dependency('gtk+-3.0'), -] - -dbusmenu_cflags = run_command( - find_program('pkg-config', required: true), - '--cflags', 'dbusmenu-gtk3-0.4', - 'gobject-introspection-1.0', - 'gobject-2.0', - 'glib-2.0', - capture: true, - check: true, -).stdout().strip() - -dbusmenu_libs = run_command( - find_program('pkg-config', required: true), - '--libs', 'dbusmenu-gtk3-0.4', - 'gobject-introspection-1.0', - 'gobject-2.0', - 'glib-2.0', - capture: true, - check: true, -).stdout().strip() - -sources = [config, 'tray.vala', 'watcher.vala', 'trayItem.vala'] - -if get_option('lib') - lib = library( - meson.project_name(), - sources, - dependencies: deps, - vala_header: meson.project_name() + '.h', - vala_vapi: meson.project_name() + '-' + api_version + '.vapi', - vala_gir: gir, - vala_args: ['--pkg', 'DbusmenuGtk3-0.4', '--pkg', 'Dbusmenu-0.4'], - version: meson.project_version(), - c_args: dbusmenu_cflags.split(' '), - link_args: dbusmenu_libs.split(' '), - install: true, - install_dir: [true, true, true, true], - ) - - import('pkgconfig').generate( - lib, - name: meson.project_name(), - filebase: meson.project_name() + '-' + api_version, - version: meson.project_version(), - subdirs: meson.project_name(), - requires: deps, - install_dir: get_option('libdir') / 'pkgconfig', - ) - - custom_target( - typelib, - command: [ - find_program('g-ir-compiler'), - '--output', '@OUTPUT@', - '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', - meson.current_build_dir() / gir, - ], - input: lib, - output: typelib, - depends: lib, - install: true, - install_dir: get_option('libdir') / 'girepository-1.0', - ) -endif - -if get_option('cli') - executable( - meson.project_name(), - ['cli.vala', sources], - dependencies: deps, - vala_args: ['--pkg', 'DbusmenuGtk3-0.4', '--pkg', 'Dbusmenu-0.4'], - c_args: dbusmenu_cflags.split(' '), - link_args: dbusmenu_libs.split(' '), - install: true, - ) -endif diff --git a/tray/src/tray.vala b/tray/src/tray.vala deleted file mode 100644 index 09b0643..0000000 --- a/tray/src/tray.vala +++ /dev/null @@ -1,135 +0,0 @@ -namespace AstalTray { -[DBus (name="org.kde.StatusNotifierWatcher")] -internal interface IWatcher : Object { - public abstract string[] RegisteredStatusNotifierItems { owned get; } - public abstract int ProtocolVersion { owned get; } - - public abstract void RegisterStatusNotifierItem(string service, BusName sender) throws DBusError, IOError; - public abstract void RegisterStatusNotifierHost(string service) throws DBusError, IOError; - - public signal void StatusNotifierItemRegistered(string service); - public signal void StatusNotifierItemUnregistered(string service); - public signal void StatusNotifierHostRegistered(); - public signal void StatusNotifierHostUnregistered(); -} - -public Tray get_default() { - return Tray.get_default(); -} - -public class Tray : Object { - private static Tray? instance; - public static unowned Tray get_default() { - if (instance == null) - instance = new Tray(); - - return instance; - } - - private StatusNotifierWatcher watcher; - private IWatcher proxy; - - private HashTable _items = - new HashTable(str_hash, str_equal); - - public List items { owned get { return _items.get_values(); }} - - public signal void item_added(string service) { - notify_property("items"); - } - - public signal void item_removed(string service) { - notify_property("items"); - } - - construct { - try { - Bus.own_name( - BusType.SESSION, - "org.kde.StatusNotifierWatcher", - BusNameOwnerFlags.NONE, - start_watcher, - () => { - if (proxy != null) { - proxy = null; - } - }, - start_host - ); - } catch (Error err) { - critical(err.message); - } - - } - - private void start_watcher(DBusConnection conn) { - try { - watcher = new StatusNotifierWatcher(); - conn.register_object("/StatusNotifierWatcher", watcher); - watcher.StatusNotifierItemRegistered.connect(on_item_register); - watcher.StatusNotifierItemUnregistered.connect(on_item_unregister); - } catch (Error err) { - critical(err.message); - } - } - - private void start_host() { - if (proxy != null) - return; - - try { - proxy = Bus.get_proxy_sync(BusType.SESSION, - "org.kde.StatusNotifierWatcher", - "/StatusNotifierWatcher"); - - proxy.StatusNotifierItemRegistered.connect(on_item_register); - proxy.StatusNotifierItemUnregistered.connect(on_item_unregister); - - proxy.notify["g-name-owner"].connect(() => { - _items.foreach((service, _) => { - item_removed(service); - }); - - _items.remove_all(); - - if(proxy != null) { - foreach (string item in proxy.RegisteredStatusNotifierItems) { - on_item_register(item); - } - } else { - foreach (string item in watcher.RegisteredStatusNotifierItems) { - on_item_register(item); - } - } - }); - - foreach (string item in proxy.RegisteredStatusNotifierItems) { - on_item_register(item); - } - } catch (Error err) { - critical("cannot get proxy: %s", err.message); - } - } - - private void on_item_register(string service) { - if (_items.contains(service)) - return; - - var parts = service.split("/", 2); - TrayItem item = new TrayItem(parts[0], "/" + parts[1]); - item.ready.connect(() => { - _items.set(service, item); - item_added(service); - }); - } - - private void on_item_unregister(string service) { - _items.remove(service); - item_removed(service); - } - - public TrayItem get_item(string service) { - return _items.get(service); - } -} -} diff --git a/tray/src/trayItem.vala b/tray/src/trayItem.vala deleted file mode 100644 index b6b9da0..0000000 --- a/tray/src/trayItem.vala +++ /dev/null @@ -1,363 +0,0 @@ -using DbusmenuGtk; - -namespace AstalTray { -public struct Pixmap { - int width; - int height; - uint8[] bytes; -} - -public struct Tooltip { - string icon_name; - Pixmap[] icon; - string title; - string description; -} - -[DBus (use_string_marshalling = true)] -public enum Category { - [DBus (value = "ApplicationStatus"), Description (nick = "ApplicationStatus")] - APPLICATION, - - [DBus (value = "Communications"), Description (nick = "Communications")] - COMMUNICATIONS, - - [DBus (value = "SystemServices"), Description (nick = "SystemServices")] - SYSTEM, - - [DBus (value = "Hardware"), Description (nick = "Hardware")] - HARDWARE; - - public string to_nick () { - var enumc = (EnumClass)typeof (Category).class_ref(); - unowned var eval = enumc.get_value(this); - return eval.value_nick; - } -} - - -[DBus (use_string_marshalling = true)] -public enum Status { - [DBus (value = "Passive"), Description (nick = "Passive")] - PASSIVE, - - [DBus (value = "Active"), Description (nick = "Active")] - ACTIVE, - - [DBus (value = "NeedsAttention"), Description (nick = "NeedsAttention")] - NEEDS_ATTENTION; - - public string to_nick () { - var enumc = (EnumClass)typeof (Status).class_ref(); - unowned var eval = enumc.get_value(this); - return eval.value_nick; - } -} - -[DBus (name="org.kde.StatusNotifierItem")] -internal interface IItem : DBusProxy { - public abstract string Title { owned get; } - public abstract Category Category { owned get; } - public abstract Status Status { owned get; } - public abstract Tooltip? ToolTip { owned get; } - public abstract string Id { owned get; } - public abstract string? IconThemePath { owned get; } - public abstract bool ItemIsMenu { owned get; } - public abstract ObjectPath? Menu { owned get; } - public abstract string IconName { owned get; } - public abstract Pixmap[] IconPixmap { owned get; } - public abstract string AttentionIconName { owned get; } - public abstract Pixmap[] AttentionIconPixmap { owned get; } - public abstract string OverlayIconName { owned get; } - public abstract Pixmap[] OverlayIconPixmap { owned get; } - - public abstract void ContexMenu(int x, int y) throws DBusError, IOError; - public abstract void Activate(int x, int y) throws DBusError, IOError; - public abstract void SecondaryActivate(int x, int y) throws DBusError, IOError; - public abstract void Scroll(int delta, string orientation) throws DBusError, IOError; - - public signal void NewTitle(); - public signal void NewIcon(); - public signal void NewAttentionIcon(); - public signal void NewOverlayIcon(); - public signal void NewToolTip(); - public signal void NewStatus(string status); -} - -public class TrayItem : Object { - private IItem proxy; - private List connection_ids; - - public string title { owned get { return proxy.Title; } } - public Category category { get { return proxy.Category; } } - public Status status { get { return proxy.Status; } } - public Tooltip? tooltip { owned get { return proxy.ToolTip; } } - - public string tooltip_markup { - owned get { - if (proxy.ToolTip == null) - return ""; - - var tt = proxy.ToolTip.title; - if (proxy.ToolTip.description != "") - tt += "\n" + proxy.ToolTip.description; - - return tt; - } - } - - public string id { owned get { return proxy.Id ;} } - public string icon_theme_path { owned get { return proxy.IconThemePath ;} } - public bool is_menu { get { return proxy.ItemIsMenu ;} } - - public string icon_name { - owned get { - return proxy.Status == Status.NEEDS_ATTENTION - ? proxy.AttentionIconName - : proxy.IconName; - } - } - - public Gdk.Pixbuf icon_pixbuf { owned get { return _get_icon_pixbuf(); } } - - public GLib.Icon gicon { get; private set; } - - public string item_id { get; private set; } - - public signal void changed(); - public signal void ready(); - - public TrayItem(string service, string path) { - connection_ids = new List(); - item_id = service + path; - setup_proxy.begin(service, path, (_, res) => setup_proxy.end(res)); - } - - private async void setup_proxy(string service, string path) { - try { - proxy = yield Bus.get_proxy( - BusType.SESSION, - service, - path); - - connection_ids.append(proxy.NewStatus.connect(refresh_all_properties)); - connection_ids.append(proxy.NewToolTip.connect(refresh_all_properties)); - connection_ids.append(proxy.NewTitle.connect(refresh_all_properties)); - connection_ids.append(proxy.NewIcon.connect(refresh_all_properties)); - - proxy.notify["g-name-owner"].connect(() => { - if (proxy.g_name_owner == null) { - foreach (var id in connection_ids) - SignalHandler.disconnect(proxy, id); - } - }); - - update_gicon(); - - ready(); - } catch (Error err) { - critical(err.message); - } - } - - private void _notify() { - string[] props = { "category", "id", "title", "status", "is-menu", "tooltip-markup", "icon-name", "icon-pixbuf" }; - - foreach (string prop in props) - notify_property(prop); - - changed(); - } - - private void update_gicon() { - if(icon_name != null && icon_name != "") { - if(icon_theme_path != null && icon_theme_path != "") { - - Gtk.IconTheme icon_theme = new Gtk.IconTheme(); - string[] paths = {icon_theme_path}; - icon_theme.set_search_path(paths); - - int size = icon_theme.get_icon_sizes(icon_name)[0]; - Gtk.IconInfo icon_info = icon_theme.lookup_icon( - icon_name, size, Gtk.IconLookupFlags.FORCE_SIZE); - - if (icon_info != null) - gicon = new GLib.FileIcon(GLib.File.new_for_path(icon_info.get_filename())); - } else { - gicon = new GLib.ThemedIcon(icon_name); - } - } - else { - Pixmap[] pixmaps = proxy.Status == Status.NEEDS_ATTENTION - ? proxy.AttentionIconPixmap - : proxy.IconPixmap; - gicon = pixmap_to_pixbuf(pixmaps); - } - } - - - private void refresh_all_properties() { - proxy.g_connection.call.begin( - proxy.g_name, - proxy.g_object_path, - "org.freedesktop.DBus.Properties", - "GetAll", - new Variant("(s)", proxy.g_interface_name), - new VariantType("(a{sv})"), - DBusCallFlags.NONE, - -1, - null, - (_, result) => { - try { - Variant parameters = proxy.g_connection.call.end(result); - VariantIter prop_iter; - parameters.get("(a{sv})", out prop_iter); - - string prop_key; - Variant prop_value; - - while (prop_iter.next ("{sv}", out prop_key, out prop_value)) { - proxy.set_cached_property(prop_key, prop_value); - } - - update_gicon(); - - _notify(); - } catch(Error e) { - //silently ignore - } - } - ); - } - - public void activate(int x, int y) { - try { - proxy.Activate(x, y); - } catch (Error e) { - if(e.domain != DBusError.quark() || e.code != DBusError.UNKNOWN_METHOD) - warning(e.message); - } - } - - public void secondary_activate(int x, int y) { - try { - proxy.SecondaryActivate(x, y); - } catch (Error e) { - if(e.domain != DBusError.quark() || e.code != DBusError.UNKNOWN_METHOD) - warning(e.message); - } - } - - public void scroll(int delta, string orientation) { - try { - proxy.Scroll(delta, orientation); - } catch (Error e) { - if(e.domain != DBusError.quark() || e.code != DBusError.UNKNOWN_METHOD) - warning("%s\n", e.message); - } - } - - - public DbusmenuGtk.Menu? create_menu() { - if (proxy.Menu == null) - return null; - - return new DbusmenuGtk.Menu( - proxy.get_name_owner(), - proxy.Menu); - } - - public Gdk.Pixbuf? _get_icon_pixbuf() { - Pixmap[] pixmaps = proxy.Status == Status.NEEDS_ATTENTION - ? proxy.AttentionIconPixmap - : proxy.IconPixmap; - - - string icon_name = proxy.Status == Status.NEEDS_ATTENTION - ? proxy.AttentionIconName - : proxy.IconName; - - Gdk.Pixbuf pixbuf = null; - - if (icon_name != null && proxy.IconThemePath != null) - pixbuf = load_from_theme(icon_name, proxy.IconThemePath); - - if (pixbuf == null) - pixbuf = pixmap_to_pixbuf(pixmaps); - - return pixbuf; - } - - private Gdk.Pixbuf? load_from_theme(string icon_name, string theme_path) { - if (theme_path == "" || theme_path == null) - return null; - - if (icon_name == "" || icon_name == null) - return null; - - Gtk.IconTheme icon_theme = new Gtk.IconTheme(); - string[] paths = {theme_path}; - icon_theme.set_search_path(paths); - - int size = icon_theme.get_icon_sizes(icon_name)[0]; - Gtk.IconInfo icon_info = icon_theme.lookup_icon( - icon_name, size, Gtk.IconLookupFlags.FORCE_SIZE); - - if (icon_info != null) - return icon_info.load_icon(); - - return null; - } - - private Gdk.Pixbuf? pixmap_to_pixbuf(Pixmap[] pixmaps) { - if (pixmaps == null || pixmaps.length == 0) - return null; - - Pixmap pixmap = pixmaps[0]; - uint8[] image_data = pixmap.bytes.copy(); - - for (int i = 0; i < pixmap.width * pixmap.height * 4; i += 4) { - uint8 alpha = image_data[i]; - image_data[i] = image_data[i + 1]; - image_data[i + 1] = image_data[i + 2]; - image_data[i + 2] = image_data[i + 3]; - image_data[i + 3] = alpha; - } - - return new Gdk.Pixbuf.from_bytes( - new Bytes(image_data), - Gdk.Colorspace.RGB, - true, - 8, - (int)pixmap.width, - (int)pixmap.height, - (int)(pixmap.width * 4) - ); - } - - public string to_json_string() { - var generator = new Json.Generator(); - generator.set_root(to_json()); - return generator.to_data(null); - } - - internal Json.Node to_json() { - return new Json.Builder() - .begin_object() - .set_member_name("item_id").add_string_value(item_id) - .set_member_name("id").add_string_value(id) - .set_member_name("bus_name").add_string_value(proxy.g_name) - .set_member_name("object_path").add_string_value(proxy.g_object_path) - .set_member_name("title").add_string_value(title) - .set_member_name("status").add_string_value(status.to_nick()) - .set_member_name("category").add_string_value(category.to_nick()) - .set_member_name("tooltip").add_string_value(tooltip_markup) - .set_member_name("icon_theme_path").add_string_value(proxy.IconThemePath) - .set_member_name("icon_name").add_string_value(icon_name) - .set_member_name("menu_path").add_string_value(proxy.Menu) - .set_member_name("is_menu").add_boolean_value(is_menu) - .end_object() - .get_root(); - } -} -} diff --git a/tray/src/watcher.vala b/tray/src/watcher.vala deleted file mode 100644 index 974cd02..0000000 --- a/tray/src/watcher.vala +++ /dev/null @@ -1,59 +0,0 @@ -namespace AstalTray { -[DBus (name="org.kde.StatusNotifierWatcher")] -internal class StatusNotifierWatcher : Object { - private HashTable _items = - new HashTable(str_hash, str_equal); - - public string[] RegisteredStatusNotifierItems { owned get { return _items.get_values_as_ptr_array().data; } } - public bool IsStatusNotifierHostRegistered { get; default = true; } - public int ProtocolVersion { get; default = 0; } - - public signal void StatusNotifierItemRegistered(string service); - public signal void StatusNotifierItemUnregistered(string service); - public signal void StatusNotifierHostRegistered(); - public signal void StatusNotifierHostUnregistered(); - - public void RegisterStatusNotifierItem(string service, BusName sender) throws DBusError, IOError { - string busName; - string path; - if (service[0] == '/') { - path = service; - busName = sender; - } else { - busName = service; - path = "/StatusNotifierItem"; - } - - Bus.get_sync(BusType.SESSION).signal_subscribe( - null, - "org.freedesktop.DBus", - "NameOwnerChanged", - null, - null, - DBusSignalFlags.NONE, - (connection, sender_name, path, interface_name, signal_name, parameters) => { - string name = null; - string new_owner = null; - string old_owner = null; - parameters.get("(sss)", &name, &old_owner, &new_owner); - if (new_owner == "" && _items.contains(old_owner)) { - string full_path = _items.take(old_owner); - StatusNotifierItemUnregistered(full_path); - } - } - ); - - _items.set(busName, busName+path); - StatusNotifierItemRegistered(busName+path); - } - - public void RegisterStatusNotifierHost(string service) throws DBusError, IOError { - /* NOTE: - usually the watcher should keep track of registered host - but some tray applications do net register their trayitem properly - when hosts register/deregister. This is fixed by setting isHostRegistered - always to true, this also make host handling logic unneccessary. - */ - } -} -} diff --git a/tray/version b/tray/version deleted file mode 100644 index 6e8bf73..0000000 --- a/tray/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/wireplumber/.gitignore b/wireplumber/.gitignore deleted file mode 100644 index 6bf41b5..0000000 --- a/wireplumber/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -result/ -.cache/ diff --git a/wireplumber/LICENSE b/wireplumber/LICENSE deleted file mode 100644 index 67cd97b..0000000 --- a/wireplumber/LICENSE +++ /dev/null @@ -1,503 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - diff --git a/wireplumber/README.md b/wireplumber/README.md deleted file mode 100644 index cfbb6db..0000000 --- a/wireplumber/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# astal-wireplumber - -A libwireplumber wrapper. - -## Build from source -### Dependencies - -- meson -- libwireplumber -- glib -- gobject-introspection -- vala (only required for the vapi option) - -### Meson options - -* `-Dintrospection` (default: `true`): build GObject Introspection data (needed for language bindings) -* `-Dvapi` (default: `true`): build VAPI data (required to make this lib usable in vala). Requires `-Dintrospection=true` - -### build instructions - -```sh -# Clone the repository -git clone https://github.com/astal-sh/wireplumber -cd wireplumber - -# Setup and build -meson setup build -meson compile -C build - -# Install -meson install -C build -``` - - diff --git a/wireplumber/flake.lock b/wireplumber/flake.lock deleted file mode 100644 index f1034b0..0000000 --- a/wireplumber/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1720957393, - "narHash": "sha256-oedh2RwpjEa+TNxhg5Je9Ch6d3W1NKi7DbRO1ziHemA=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "693bc46d169f5af9c992095736e82c3488bf7dbb", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/wireplumber/flake.nix b/wireplumber/flake.nix deleted file mode 100644 index 96ffc6f..0000000 --- a/wireplumber/flake.nix +++ /dev/null @@ -1,54 +0,0 @@ -{ - description = "Wrapper library for WirePlumber"; - - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - - outputs = { - self, - nixpkgs, - }: let - version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs {inherit system;}; - - nativeBuildInputs = with pkgs; [ - gobject-introspection - meson - pkg-config - ninja - vala - ]; - - buildInputs = with pkgs; [ - glib - wireplumber - # json-glib - ]; - in { - packages.${system} = rec { - default = wireplumber; - wireplumber = pkgs.stdenv.mkDerivation { - inherit nativeBuildInputs buildInputs; - pname = "astal-wireplumber"; - version = version; - src = ./.; - outputs = ["out" "dev"]; - }; - }; - - devShells.${system} = { - default = pkgs.mkShell { - inherit nativeBuildInputs buildInputs; - }; - wireplumber = pkgs.mkShell { - inherit nativeBuildInputs; - buildInputs = - buildInputs - ++ [ - self.packages.${system}.default - pkgs.gjs - ]; - }; - }; - }; -} diff --git a/wireplumber/include/astal-wp.h b/wireplumber/include/astal-wp.h deleted file mode 100644 index 6c48211..0000000 --- a/wireplumber/include/astal-wp.h +++ /dev/null @@ -1,4 +0,0 @@ - -#include "astal/wireplumber/audio.h" -#include "astal/wireplumber/endpoint.h" -#include "astal/wireplumber/wp.h" diff --git a/wireplumber/include/astal/wireplumber/audio.h b/wireplumber/include/astal/wireplumber/audio.h deleted file mode 100644 index c1176e2..0000000 --- a/wireplumber/include/astal/wireplumber/audio.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ASTAL_WIREPLUMBER_AUDIO_H -#define ASTAL_WIREPLUMBER_AUDIO_H - -#include - -#include "device.h" -#include "endpoint.h" - -G_BEGIN_DECLS - -#define ASTAL_WP_TYPE_AUDIO (astal_wp_audio_get_type()) - -G_DECLARE_FINAL_TYPE(AstalWpAudio, astal_wp_audio, ASTAL_WP, AUDIO, GObject) - -AstalWpEndpoint *astal_wp_audio_get_speaker(AstalWpAudio *self, guint id); -AstalWpEndpoint *astal_wp_audio_get_microphone(AstalWpAudio *self, guint id); -AstalWpEndpoint *astal_wp_audio_get_recorder(AstalWpAudio *self, guint id); -AstalWpEndpoint *astal_wp_audio_get_stream(AstalWpAudio *self, guint id); -AstalWpEndpoint *astal_wp_audio_get_endpoint(AstalWpAudio *self, guint id); -AstalWpDevice *astal_wp_audio_get_device(AstalWpAudio *self, guint id); - -AstalWpEndpoint *astal_wp_audio_get_default_speaker(AstalWpAudio *self); -AstalWpEndpoint *astal_wp_audio_get_default_microphone(AstalWpAudio *self); - -GList *astal_wp_audio_get_microphones(AstalWpAudio *self); -GList *astal_wp_audio_get_speakers(AstalWpAudio *self); -GList *astal_wp_audio_get_recorders(AstalWpAudio *self); -GList *astal_wp_audio_get_streams(AstalWpAudio *self); -GList *astal_wp_audio_get_devices(AstalWpAudio *self); - -G_END_DECLS - -#endif // !ASTAL_WIREPLUMBER_AUDIO_H diff --git a/wireplumber/include/astal/wireplumber/device.h b/wireplumber/include/astal/wireplumber/device.h deleted file mode 100644 index 9f633e3..0000000 --- a/wireplumber/include/astal/wireplumber/device.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ASTAL_WP_DEVICE_H -#define ASTAL_WP_DEVICE_H - -#include - -#include "profile.h" - -G_BEGIN_DECLS - -#define ASTAL_WP_TYPE_DEVICE (astal_wp_device_get_type()) - -G_DECLARE_FINAL_TYPE(AstalWpDevice, astal_wp_device, ASTAL_WP, DEVICE, GObject) - -#define ASTAL_WP_TYPE_DEVICE_TYPE (astal_wp_device_type_get_type()) - -typedef enum { ASTAL_WP_DEVICE_TYPE_AUDIO, ASTAL_WP_DEVICE_TYPE_VIDEO } AstalWpDeviceType; - -guint astal_wp_device_get_id(AstalWpDevice *self); -const gchar *astal_wp_device_get_description(AstalWpDevice *self); -const gchar *astal_wp_device_get_icon(AstalWpDevice *self); -AstalWpProfile *astal_wp_device_get_profile(AstalWpDevice *self, gint id); -GList *astal_wp_device_get_profiles(AstalWpDevice *self); -void astal_wp_device_set_active_profile(AstalWpDevice *self, int profile_id); -gint astal_wp_device_get_active_profile(AstalWpDevice *self); -AstalWpDeviceType astal_wp_device_get_device_type(AstalWpDevice *self); - -G_END_DECLS - -#endif // !ASTAL_WP_DEVICE_H diff --git a/wireplumber/include/astal/wireplumber/endpoint.h b/wireplumber/include/astal/wireplumber/endpoint.h deleted file mode 100644 index 6ef0329..0000000 --- a/wireplumber/include/astal/wireplumber/endpoint.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef ASTAL_WP_ENDPOINT_H -#define ASTAL_WP_ENDPOINT_H - -#include - -G_BEGIN_DECLS - -#define ASTAL_WP_TYPE_ENDPOINT (astal_wp_endpoint_get_type()) - -G_DECLARE_FINAL_TYPE(AstalWpEndpoint, astal_wp_endpoint, ASTAL_WP, ENDPOINT, GObject) - -#define ASTAL_WP_TYPE_MEDIA_CLASS (astal_wp_media_class_get_type()) - -typedef enum { - ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE, - ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER, - ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER, - ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM, - ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE, - ASTAL_WP_MEDIA_CLASS_VIDEO_SINK, - ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER, - ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM, -} AstalWpMediaClass; - -void astal_wp_endpoint_set_volume(AstalWpEndpoint *self, gdouble volume); -void astal_wp_endpoint_set_mute(AstalWpEndpoint *self, gboolean mute); -gboolean astal_wp_endpoint_get_is_default(AstalWpEndpoint *self); -void astal_wp_endpoint_set_is_default(AstalWpEndpoint *self, gboolean is_default); -gboolean astal_wp_endpoint_get_lock_channels(AstalWpEndpoint *self); -void astal_wp_endpoint_set_lock_channels(AstalWpEndpoint *self, gboolean lock_channels); - -AstalWpMediaClass astal_wp_endpoint_get_media_class(AstalWpEndpoint *self); -guint astal_wp_endpoint_get_id(AstalWpEndpoint *self); -gboolean astal_wp_endpoint_get_mute(AstalWpEndpoint *self); -gdouble astal_wp_endpoint_get_volume(AstalWpEndpoint *self); -const gchar *astal_wp_endpoint_get_description(AstalWpEndpoint *self); -const gchar *astal_wp_endpoint_get_name(AstalWpEndpoint *self); -const gchar *astal_wp_endpoint_get_icon(AstalWpEndpoint *self); -const gchar *astal_wp_endpoint_get_volume_icon(AstalWpEndpoint *self); - -G_END_DECLS - -#endif // !ASTAL_WP_ENDPOINT_H diff --git a/wireplumber/include/astal/wireplumber/meson.build b/wireplumber/include/astal/wireplumber/meson.build deleted file mode 100644 index d02563c..0000000 --- a/wireplumber/include/astal/wireplumber/meson.build +++ /dev/null @@ -1,10 +0,0 @@ -astal_wireplumber_subheaders = files( - 'wp.h', - 'endpoint.h', - 'device.h', - 'video.h', - 'audio.h', - 'profile.h', -) - -install_headers(astal_wireplumber_subheaders, subdir : 'astal/wireplumber') diff --git a/wireplumber/include/astal/wireplumber/profile.h b/wireplumber/include/astal/wireplumber/profile.h deleted file mode 100644 index 2ec768e..0000000 --- a/wireplumber/include/astal/wireplumber/profile.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef ASTAL_WP_PROFILE_H -#define ASTAL_WP_PROFILE_H - -#include - -G_BEGIN_DECLS - -#define ASTAL_WP_TYPE_PROFILE (astal_wp_profile_get_type()) - -G_DECLARE_FINAL_TYPE(AstalWpProfile, astal_wp_profile, ASTAL_WP, PROFILE, GObject) - -gint astal_wp_profile_get_index(AstalWpProfile *self); -const gchar *astal_wp_profile_get_description(AstalWpProfile *self); - -G_END_DECLS - -#endif // !ASTAL_WP_PROFILE_H diff --git a/wireplumber/include/astal/wireplumber/video.h b/wireplumber/include/astal/wireplumber/video.h deleted file mode 100644 index 3c4ae74..0000000 --- a/wireplumber/include/astal/wireplumber/video.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ASTAL_WIREPLUMBER_VIDEO_H -#define ASTAL_WIREPLUMBER_VIDEO_H - -#include - -#include "device.h" -#include "endpoint.h" - -G_BEGIN_DECLS - -#define ASTAL_WP_TYPE_VIDEO (astal_wp_video_get_type()) - -G_DECLARE_FINAL_TYPE(AstalWpVideo, astal_wp_video, ASTAL_WP, VIDEO, GObject) - -AstalWpEndpoint *astal_wp_video_get_source(AstalWpVideo *self, guint id); -AstalWpEndpoint *astal_wp_video_get_sink(AstalWpVideo *self, guint id); -AstalWpEndpoint *astal_wp_video_get_recorder(AstalWpVideo *self, guint id); -AstalWpEndpoint *astal_wp_video_get_stream(AstalWpVideo *self, guint id); -AstalWpDevice *astal_wp_video_get_device(AstalWpVideo *self, guint id); - -GList *astal_wp_video_get_sources(AstalWpVideo *self); -GList *astal_wp_video_get_sinks(AstalWpVideo *self); -GList *astal_wp_video_get_recorders(AstalWpVideo *self); -GList *astal_wp_video_get_streams(AstalWpVideo *self); -GList *astal_wp_video_get_devices(AstalWpVideo *self); - -G_END_DECLS - -#endif // !ASTAL_WIREPLUMBER_VIDEO_H diff --git a/wireplumber/include/astal/wireplumber/wp.h b/wireplumber/include/astal/wireplumber/wp.h deleted file mode 100644 index 1ff341c..0000000 --- a/wireplumber/include/astal/wireplumber/wp.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef ASTAL_WIREPLUMBER_H -#define ASTAL_WIREPLUMBER_H - -#include - -#include "audio.h" -#include "device.h" -#include "endpoint.h" -#include "video.h" - -G_BEGIN_DECLS - -#define ASTAL_WP_TYPE_SCALE (astal_wp_scale_get_type()) - -typedef enum { - ASTAL_WP_SCALE_LINEAR, - ASTAL_WP_SCALE_CUBIC, -} AstalWpScale; - -#define ASTAL_WP_TYPE_WP (astal_wp_wp_get_type()) - -G_DECLARE_FINAL_TYPE(AstalWpWp, astal_wp_wp, ASTAL_WP, WP, GObject) - -AstalWpWp* astal_wp_wp_get_default(); -AstalWpWp* astal_wp_get_default_wp(); - -AstalWpAudio* astal_wp_wp_get_audio(AstalWpWp* self); -AstalWpVideo* astal_wp_wp_get_video(AstalWpWp* self); - -AstalWpEndpoint* astal_wp_wp_get_endpoint(AstalWpWp* self, guint id); -GList* astal_wp_wp_get_endpoints(AstalWpWp* self); - -AstalWpDevice* astal_wp_wp_get_device(AstalWpWp* self, guint id); -GList* astal_wp_wp_get_devices(AstalWpWp* self); - -AstalWpEndpoint* astal_wp_wp_get_default_speaker(AstalWpWp* self); -AstalWpEndpoint* astal_wp_wp_get_default_microphone(AstalWpWp* self); - -AstalWpScale astal_wp_wp_get_scale(AstalWpWp* self); -void astal_wp_wp_set_scale(AstalWpWp* self, AstalWpScale scale); - -AstalWpVideo* astal_wp_video_new(AstalWpWp* wp); -AstalWpAudio* astal_wp_audio_new(AstalWpWp* wp); - -G_END_DECLS - -#endif // !ASTAL_WIREPLUMBER_H diff --git a/wireplumber/include/meson.build b/wireplumber/include/meson.build deleted file mode 100644 index 441097c..0000000 --- a/wireplumber/include/meson.build +++ /dev/null @@ -1,8 +0,0 @@ -astal_wireplumber_inc = include_directories('.', 'astal/wireplumber', 'private') -astal_wireplumber_headers = files( - 'astal-wp.h', -) - -install_headers(astal_wireplumber_headers) - -subdir('astal/wireplumber') diff --git a/wireplumber/include/private/device-private.h b/wireplumber/include/private/device-private.h deleted file mode 100644 index e98a7f7..0000000 --- a/wireplumber/include/private/device-private.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef ASTAL_WP_DEVICE_PRIVATE_H -#define ASTAL_WP_DEVICE_PRIVATE_H - -#include -#include - -#include "device.h" - -G_BEGIN_DECLS - -AstalWpDevice *astal_wp_device_create(WpDevice *device); - -G_END_DECLS - -#endif // !ASTAL_WP_DEVICE_PRIATE_H diff --git a/wireplumber/include/private/endpoint-private.h b/wireplumber/include/private/endpoint-private.h deleted file mode 100644 index 7431c78..0000000 --- a/wireplumber/include/private/endpoint-private.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef ASTAL_WP_ENDPOINT_PRIV_H -#define ASTAL_WP_ENDPOINT_PRIV_H - -#include -#include - -#include "endpoint.h" -#include "wp.h" - -G_BEGIN_DECLS - -AstalWpEndpoint *astal_wp_endpoint_create(WpNode *node, WpPlugin *mixer, WpPlugin *defaults, - AstalWpWp *wp); -AstalWpEndpoint *astal_wp_endpoint_init_as_default(AstalWpEndpoint *self, WpPlugin *mixer, - WpPlugin *defaults, AstalWpMediaClass type, - AstalWpWp *wp); -void astal_wp_endpoint_update_default(AstalWpEndpoint *self, gboolean is_default); -void astal_wp_endpoint_update_volume(AstalWpEndpoint *self); - -G_END_DECLS - -#endif // !ASTAL_WP_ENDPOINT_PRIV_H diff --git a/wireplumber/meson.build b/wireplumber/meson.build deleted file mode 100644 index b295b31..0000000 --- a/wireplumber/meson.build +++ /dev/null @@ -1,22 +0,0 @@ -project('astal_wireplumber', - 'c', - version : '0.1.0', - 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') - -subdir('include') -subdir('src') diff --git a/wireplumber/meson_options.txt b/wireplumber/meson_options.txt deleted file mode 100644 index d585854..0000000 --- a/wireplumber/meson_options.txt +++ /dev/null @@ -1,2 +0,0 @@ -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/wireplumber/src/audio.c b/wireplumber/src/audio.c deleted file mode 100644 index 15582d7..0000000 --- a/wireplumber/src/audio.c +++ /dev/null @@ -1,503 +0,0 @@ -#include "audio.h" - -#include - -#include "device.h" -#include "endpoint.h" -#include "glib-object.h" -#include "wp.h" - -struct _AstalWpAudio { - GObject parent_instance; -}; - -typedef struct { - AstalWpWp *wp; -} AstalWpAudioPrivate; - -G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpAudio, astal_wp_audio, G_TYPE_OBJECT); - -typedef enum { - ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_ADDED, - ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_REMOVED, - ASTAL_WP_AUDIO_SIGNAL_SPEAKER_ADDED, - ASTAL_WP_AUDIO_SIGNAL_SPEAKER_REMOVED, - ASTAL_WP_AUDIO_SIGNAL_STREAM_ADDED, - ASTAL_WP_AUDIO_SIGNAL_STREAM_REMOVED, - ASTAL_WP_AUDIO_SIGNAL_RECORDER_ADDED, - ASTAL_WP_AUDIO_SIGNAL_RECORDER_REMOVED, - ASTAL_WP_AUDIO_SIGNAL_DEVICE_ADDED, - ASTAL_WP_AUDIO_SIGNAL_DEVICE_REMOVED, - ASTAL_WP_AUDIO_N_SIGNALS -} AstalWpWpSignals; - -static guint astal_wp_audio_signals[ASTAL_WP_AUDIO_N_SIGNALS] = { - 0, -}; - -typedef enum { - ASTAL_WP_AUDIO_PROP_MICROPHONES = 1, - ASTAL_WP_AUDIO_PROP_SPEAKERS, - ASTAL_WP_AUDIO_PROP_STREAMS, - ASTAL_WP_AUDIO_PROP_RECORDERS, - ASTAL_WP_AUDIO_PROP_DEVICES, - ASTAL_WP_AUDIO_PROP_DEFAULT_SPEAKER, - ASTAL_WP_AUDIO_PROP_DEFAULT_MICROPHONE, - ASTAL_WP_AUDIO_N_PROPERTIES, -} AstalWpAudioProperties; - -static GParamSpec *astal_wp_audio_properties[ASTAL_WP_AUDIO_N_PROPERTIES] = { - NULL, -}; - -/** - * astal_wp_audio_get_speaker: - * @self: the AstalWpAudio object - * @id: the id of the endpoint - * - * gets the speaker with the given id - * - * Returns: (transfer none) (nullable) - */ -AstalWpEndpoint *astal_wp_audio_get_speaker(AstalWpAudio *self, guint id) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - - AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); - if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER) - return endpoint; - return NULL; -} - -/** - * astal_wp_audio_get_microphone: - * @self: the AstalWpAudio object - * @id: the id of the endpoint - * - * gets the microphone with the given id - * - * Returns: (transfer none) (nullable) - */ -AstalWpEndpoint *astal_wp_audio_get_microphone(AstalWpAudio *self, guint id) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - - AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); - if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE) - return endpoint; - return NULL; -} - -/** - * astal_wp_audio_get_recorder: - * @self: the AstalWpAudio object - * @id: the id of the endpoint - * - * gets the recorder with the given id - * - * Returns: (transfer none) (nullable) - */ -AstalWpEndpoint *astal_wp_audio_get_recorder(AstalWpAudio *self, guint id) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - - AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); - if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER) - return endpoint; - return NULL; -} - -/** - * astal_wp_audio_get_stream: - * @self: the AstalWpAudio object - * @id: the id of the endpoint - * - * gets the stream with the given id - * - * Returns: (transfer none) (nullable) - */ -AstalWpEndpoint *astal_wp_audio_get_stream(AstalWpAudio *self, guint id) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - - AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); - if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM) - return endpoint; - return NULL; -} - -/** - * astal_wp_audio_get_device: - * @self: the AstalWpAudio object - * @id: the id of the device - * - * gets the device with the given id - * - * Returns: (transfer none) (nullable) - */ -AstalWpDevice *astal_wp_audio_get_device(AstalWpAudio *self, guint id) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - - return astal_wp_wp_get_device(priv->wp, id); -} - -/** - * astal_wp_audio_get_microphones: - * @self: the AstalWpAudio object - * - * a GList containing the microphones - * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) - */ -GList *astal_wp_audio_get_microphones(AstalWpAudio *self) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - GList *eps = astal_wp_wp_get_endpoints(priv->wp); - GList *mics = NULL; - - for (GList *l = eps; l != NULL; l = l->next) { - if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE) { - mics = g_list_append(mics, l->data); - } - } - g_list_free(eps); - return mics; -} - -/** - * astal_wp_audio_get_speakers: - * @self: the AstalWpAudio object - * - * a GList containing the speakers - * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) - */ -GList *astal_wp_audio_get_speakers(AstalWpAudio *self) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - GList *eps = astal_wp_wp_get_endpoints(priv->wp); - GList *speakers = NULL; - - for (GList *l = eps; l != NULL; l = l->next) { - if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER) { - speakers = g_list_append(speakers, l->data); - } - } - g_list_free(eps); - return speakers; -} - -/** - * astal_wp_audio_get_recorders: - * @self: the AstalWpAudio object - * - * a GList containing the recorders - * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) - */ -GList *astal_wp_audio_get_recorders(AstalWpAudio *self) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - GList *eps = astal_wp_wp_get_endpoints(priv->wp); - GList *recorders = NULL; - - for (GList *l = eps; l != NULL; l = l->next) { - if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER) { - recorders = g_list_append(recorders, l->data); - } - } - g_list_free(eps); - return recorders; -} - -/** - * astal_wp_audio_get_streams: - * @self: the AstalWpAudio object - * - * a GList containing the streams - * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) - */ -GList *astal_wp_audio_get_streams(AstalWpAudio *self) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - GList *eps = astal_wp_wp_get_endpoints(priv->wp); - GList *streams = NULL; - - for (GList *l = eps; l != NULL; l = l->next) { - if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM) { - streams = g_list_append(streams, l->data); - } - } - g_list_free(eps); - return streams; -} - -/** - * astal_wp_audio_get_devices: - * @self: the AstalWpAudio object - * - * a GList containing the devices - * - * Returns: (transfer container) (nullable) (type GList(AstalWpDevice)) - */ -GList *astal_wp_audio_get_devices(AstalWpAudio *self) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - GList *eps = astal_wp_wp_get_devices(priv->wp); - GList *list = NULL; - - for (GList *l = eps; l != NULL; l = l->next) { - if (astal_wp_device_get_device_type(l->data) == ASTAL_WP_DEVICE_TYPE_AUDIO) { - list = g_list_append(list, l->data); - } - } - g_list_free(eps); - return list; -} - -/** - * astal_wp_audio_get_endpoint: - * @self: the AstalWpAudio object - * @id: the id of the endpoint - * - * the endpoint with the given id - * - * Returns: (transfer none) (nullable) - */ -AstalWpEndpoint *astal_wp_audio_get_endpoint(AstalWpAudio *self, guint id) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - - AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); - return endpoint; -} - -/** - * astal_wp_audio_get_default_speaker - * - * gets the default speaker object - * - * Returns: (nullable) (transfer none) - */ -AstalWpEndpoint *astal_wp_audio_get_default_speaker(AstalWpAudio *self) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - return astal_wp_wp_get_default_speaker(priv->wp); -} - -/** - * astal_wp_audio_get_default_microphone - * - * gets the default microphone object - * - * Returns: (nullable) (transfer none) - */ -AstalWpEndpoint *astal_wp_audio_get_default_microphone(AstalWpAudio *self) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - return astal_wp_wp_get_default_microphone(priv->wp); -} - -static void astal_wp_audio_get_property(GObject *object, guint property_id, GValue *value, - GParamSpec *pspec) { - AstalWpAudio *self = ASTAL_WP_AUDIO(object); - - switch (property_id) { - case ASTAL_WP_AUDIO_PROP_MICROPHONES: - g_value_set_pointer(value, astal_wp_audio_get_microphones(self)); - break; - case ASTAL_WP_AUDIO_PROP_SPEAKERS: - g_value_set_pointer(value, astal_wp_audio_get_speakers(self)); - break; - case ASTAL_WP_AUDIO_PROP_STREAMS: - g_value_set_pointer(value, astal_wp_audio_get_streams(self)); - break; - case ASTAL_WP_AUDIO_PROP_RECORDERS: - g_value_set_pointer(value, astal_wp_audio_get_recorders(self)); - break; - case ASTAL_WP_AUDIO_PROP_DEFAULT_SPEAKER: - g_value_set_object(value, astal_wp_audio_get_default_speaker(self)); - break; - case ASTAL_WP_AUDIO_PROP_DEVICES: - g_value_set_pointer(value, astal_wp_audio_get_devices(self)); - break; - case ASTAL_WP_AUDIO_PROP_DEFAULT_MICROPHONE: - g_value_set_object(value, astal_wp_audio_get_default_microphone(self)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_wp_audio_device_added(AstalWpAudio *self, gpointer object) { - AstalWpDevice *device = ASTAL_WP_DEVICE(object); - if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_AUDIO) { - g_signal_emit_by_name(self, "device-added", device); - g_object_notify(G_OBJECT(self), "devices"); - } -} - -static void astal_wp_audio_device_removed(AstalWpAudio *self, gpointer object) { - AstalWpDevice *device = ASTAL_WP_DEVICE(object); - if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_AUDIO) { - g_signal_emit_by_name(self, "device-removed", device); - g_object_notify(G_OBJECT(self), "devices"); - } -} - -static void astal_wp_audio_object_added(AstalWpAudio *self, gpointer object) { - AstalWpEndpoint *endpoint = ASTAL_WP_ENDPOINT(object); - switch (astal_wp_endpoint_get_media_class(endpoint)) { - case ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE: - g_signal_emit_by_name(self, "microphone-added", endpoint); - g_object_notify(G_OBJECT(self), "microphones"); - break; - case ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER: - g_signal_emit_by_name(self, "speaker-added", endpoint); - g_object_notify(G_OBJECT(self), "speakers"); - break; - case ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM: - g_signal_emit_by_name(self, "stream-added", endpoint); - g_object_notify(G_OBJECT(self), "streams"); - break; - case ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER: - g_signal_emit_by_name(self, "recorder-added", endpoint); - g_object_notify(G_OBJECT(self), "recorders"); - break; - default: - break; - } -} - -static void astal_wp_audio_object_removed(AstalWpAudio *self, gpointer object) { - AstalWpEndpoint *endpoint = ASTAL_WP_ENDPOINT(object); - switch (astal_wp_endpoint_get_media_class(endpoint)) { - case ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE: - g_signal_emit_by_name(self, "microphone-removed", endpoint); - g_object_notify(G_OBJECT(self), "microphones"); - break; - case ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER: - g_signal_emit_by_name(self, "speaker-removed", endpoint); - g_object_notify(G_OBJECT(self), "speakers"); - break; - case ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM: - g_signal_emit_by_name(self, "stream-removed", endpoint); - g_object_notify(G_OBJECT(self), "streams"); - break; - case ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER: - g_signal_emit_by_name(self, "recorder-removed", endpoint); - g_object_notify(G_OBJECT(self), "recorders"); - break; - default: - break; - } -} - -AstalWpAudio *astal_wp_audio_new(AstalWpWp *wp) { - AstalWpAudio *self = g_object_new(ASTAL_WP_TYPE_AUDIO, NULL); - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - priv->wp = g_object_ref(wp); - - g_signal_connect_swapped(priv->wp, "endpoint-added", G_CALLBACK(astal_wp_audio_object_added), - self); - g_signal_connect_swapped(priv->wp, "endpoint-removed", - G_CALLBACK(astal_wp_audio_object_removed), self); - g_signal_connect_swapped(priv->wp, "device-added", G_CALLBACK(astal_wp_audio_device_added), - self); - g_signal_connect_swapped(priv->wp, "device-removed", G_CALLBACK(astal_wp_audio_device_removed), - self); - - return self; -} - -static void astal_wp_audio_dispose(GObject *object) { - AstalWpAudio *self = ASTAL_WP_AUDIO(object); - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); - g_clear_object(&priv->wp); -} - -static void astal_wp_audio_init(AstalWpAudio *self) { - AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); -} - -static void astal_wp_audio_class_init(AstalWpAudioClass *class) { - GObjectClass *object_class = G_OBJECT_CLASS(class); - object_class->get_property = astal_wp_audio_get_property; - object_class->dispose = astal_wp_audio_dispose; - - /** - * AstalWpAudio:microphones: (type GList(AstalWpEndpoint)) (transfer container) - * - * A list of AstalWpEndpoint objects - */ - astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_MICROPHONES] = - g_param_spec_pointer("microphones", "microphones", "microphones", G_PARAM_READABLE); - /** - * AstalWpAudio:speakers: (type GList(AstalWpEndpoint)) (transfer container) - * - * A list of AstalWpEndpoint objects - */ - astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_SPEAKERS] = - g_param_spec_pointer("speakers", "speakers", "speakers", G_PARAM_READABLE); - /** - * AstalWpAudio:recorders: (type GList(AstalWpEndpoint)) (transfer container) - * - * A list of AstalWpEndpoint objects - */ - astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_RECORDERS] = - g_param_spec_pointer("recorders", "recorders", "recorders", G_PARAM_READABLE); - /** - * AstalWpAudio:streams: (type GList(AstalWpEndpoint)) (transfer container) - * - * A list of AstalWpEndpoint objects - */ - astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_STREAMS] = - g_param_spec_pointer("streams", "streams", "streams", G_PARAM_READABLE); - /** - * AstalWpAudio:devices: (type GList(AstalWpDevice)) (transfer container) - * - * A list of AstalWpEndpoint objects - */ - astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_DEVICES] = - g_param_spec_pointer("devices", "devices", "devices", G_PARAM_READABLE); - /** - * AstalWpAudio:default-speaker: - * - * The AstalWndpoint object representing the default speaker - */ - astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_DEFAULT_SPEAKER] = - g_param_spec_object("default-speaker", "default-speaker", "default-speaker", - ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); - /** - * AstalWpAudio:default-microphone: - * - * The AstalWndpoint object representing the default speaker - */ - astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_DEFAULT_MICROPHONE] = - g_param_spec_object("default-microphone", "default-microphone", "default-microphone", - ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); - - g_object_class_install_properties(object_class, ASTAL_WP_AUDIO_N_PROPERTIES, - astal_wp_audio_properties); - - astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_ADDED] = - g_signal_new("microphone-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, - NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_REMOVED] = - g_signal_new("microphone-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, - NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_SPEAKER_ADDED] = - g_signal_new("speaker-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_SPEAKER_REMOVED] = - g_signal_new("speaker-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_STREAM_ADDED] = - g_signal_new("stream-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_STREAM_REMOVED] = - g_signal_new("stream-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_RECORDER_ADDED] = - g_signal_new("recorder-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_RECORDER_REMOVED] = - g_signal_new("recorder-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, - NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_DEVICE_ADDED] = - g_signal_new("device-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); - astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_REMOVED] = - g_signal_new("device-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); -} diff --git a/wireplumber/src/device.c b/wireplumber/src/device.c deleted file mode 100644 index af0760c..0000000 --- a/wireplumber/src/device.c +++ /dev/null @@ -1,371 +0,0 @@ -#include - -#include "device-private.h" -#include "profile.h" - -struct _AstalWpDevice { - GObject parent_instance; - - guint id; - gchar *description; - gchar *icon; - gint active_profile; - AstalWpDeviceType type; -}; - -typedef struct { - WpDevice *device; - GHashTable *profiles; -} AstalWpDevicePrivate; - -G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpDevice, astal_wp_device, G_TYPE_OBJECT); - -G_DEFINE_ENUM_TYPE(AstalWpDeviceType, astal_wp_device_type, - G_DEFINE_ENUM_VALUE(ASTAL_WP_DEVICE_TYPE_AUDIO, "Audio/Device"), - G_DEFINE_ENUM_VALUE(ASTAL_WP_DEVICE_TYPE_VIDEO, "Video/Device")); - -typedef enum { - ASTAL_WP_DEVICE_PROP_ID = 1, - ASTAL_WP_DEVICE_PROP_DESCRIPTION, - ASTAL_WP_DEVICE_PROP_ICON, - ASTAL_WP_DEVICE_PROP_PROFILES, - ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE, - ASTAL_WP_DEVICE_PROP_DEVICE_TYPE, - ASTAL_WP_DEVICE_N_PROPERTIES, -} AstalWpDeviceProperties; - -static GParamSpec *astal_wp_device_properties[ASTAL_WP_DEVICE_N_PROPERTIES] = { - NULL, -}; - -/** - * astal_wp_device_get_id - * @self: the AstalWpDevice object - * - * gets the id of this device - * - */ -guint astal_wp_device_get_id(AstalWpDevice *self) { return self->id; } - -/** - * astal_wp_device_get_description - * @self: the AstalWpDevice object - * - * gets the description of this device - * - */ -const gchar *astal_wp_device_get_description(AstalWpDevice *self) { return self->description; } - -/** - * astal_wp_device_get_icon - * @self: the AstalWpDevice object - * - * gets the icon of this device - * - */ -const gchar *astal_wp_device_get_icon(AstalWpDevice *self) { - g_return_val_if_fail(self != NULL, "audio-card-symbolic"); - return self->icon; -} - -/** - * astal_wp_device_get_device_type - * @self: the AstalWpDevice object - * - * gets the type of this device - * - */ -AstalWpDeviceType astal_wp_device_get_device_type(AstalWpDevice *self) { return self->type; } - -/** - * astal_wp_device_get_active_profile - * @self: the AstalWpDevice object - * - * gets the currently active profile of this device - * - */ -gint astal_wp_device_get_active_profile(AstalWpDevice *self) { return self->active_profile; } - -/** - * astal_wp_device_set_active_profile - * @self: the AstalWpDevice object - * @profile_id: the id of the profile - * - * sets the profile for this device - * - */ -void astal_wp_device_set_active_profile(AstalWpDevice *self, int profile_id) { - AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); - - WpSpaPodBuilder *builder = - wp_spa_pod_builder_new_object("Spa:Pod:Object:Param:Profile", "Profile"); - wp_spa_pod_builder_add_property(builder, "index"); - wp_spa_pod_builder_add_int(builder, profile_id); - WpSpaPod *pod = wp_spa_pod_builder_end(builder); - wp_pipewire_object_set_param(WP_PIPEWIRE_OBJECT(priv->device), "Profile", 0, pod); - - wp_spa_pod_builder_unref(builder); -} - -/** - * astal_wp_device_get_profile: - * @self: the AstalWpDevice object - * @id: the id of the profile - * - * gets the profile with the given id - * - * Returns: (transfer none) (nullable) - */ -AstalWpProfile *astal_wp_device_get_profile(AstalWpDevice *self, gint id) { - AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); - - return g_hash_table_lookup(priv->profiles, GINT_TO_POINTER(id)); -} - -/** - * astal_wp_device_get_profiles: - * @self: the AstalWpDevice object - * - * gets a GList containing the profiles - * - * Returns: (transfer container) (nullable) (type GList(AstalWpProfile)) - */ -GList *astal_wp_device_get_profiles(AstalWpDevice *self) { - AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); - return g_hash_table_get_values(priv->profiles); -} - -static void astal_wp_device_get_property(GObject *object, guint property_id, GValue *value, - GParamSpec *pspec) { - AstalWpDevice *self = ASTAL_WP_DEVICE(object); - - switch (property_id) { - case ASTAL_WP_DEVICE_PROP_ID: - g_value_set_uint(value, self->id); - break; - case ASTAL_WP_DEVICE_PROP_DESCRIPTION: - g_value_set_string(value, self->description); - break; - case ASTAL_WP_DEVICE_PROP_ICON: - g_value_set_string(value, self->icon); - break; - case ASTAL_WP_DEVICE_PROP_PROFILES: - g_value_set_pointer(value, astal_wp_device_get_profiles(self)); - break; - case ASTAL_WP_DEVICE_PROP_DEVICE_TYPE: - g_value_set_enum(value, astal_wp_device_get_device_type(self)); - break; - case ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE: - g_value_set_int(value, self->active_profile); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_wp_device_set_property(GObject *object, guint property_id, const GValue *value, - GParamSpec *pspec) { - AstalWpDevice *self = ASTAL_WP_DEVICE(object); - - switch (property_id) { - case ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE: - astal_wp_device_set_active_profile(self, g_value_get_int(value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_wp_device_update_profiles(AstalWpDevice *self) { - AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); - g_hash_table_remove_all(priv->profiles); - - WpIterator *iter = - wp_pipewire_object_enum_params_sync(WP_PIPEWIRE_OBJECT(priv->device), "EnumProfile", NULL); - if (iter == NULL) return; - GValue profile = G_VALUE_INIT; - while (wp_iterator_next(iter, &profile)) { - WpSpaPod *pod = g_value_get_boxed(&profile); - - gint index; - gchar *description; - wp_spa_pod_get_object(pod, NULL, "index", "i", &index, "description", "s", &description, - NULL); - - g_hash_table_insert( - priv->profiles, GINT_TO_POINTER(index), - g_object_new(ASTAL_WP_TYPE_PROFILE, "index", index, "description", description, NULL)); - g_value_unset(&profile); - } - wp_iterator_unref(iter); - - g_object_notify(G_OBJECT(self), "profiles"); -} - -static void astal_wp_device_update_active_profile(AstalWpDevice *self) { - AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); - - WpIterator *iter = - wp_pipewire_object_enum_params_sync(WP_PIPEWIRE_OBJECT(priv->device), "Profile", NULL); - if (iter == NULL) return; - GValue profile = G_VALUE_INIT; - while (wp_iterator_next(iter, &profile)) { - WpSpaPod *pod = g_value_get_boxed(&profile); - - gint index; - gchar *description; - wp_spa_pod_get_object(pod, NULL, "index", "i", &index, "description", "s", &description, - NULL); - - g_hash_table_insert( - priv->profiles, GINT_TO_POINTER(index), - g_object_new(ASTAL_WP_TYPE_PROFILE, "index", index, "description", description, NULL)); - - self->active_profile = index; - g_value_unset(&profile); - } - wp_iterator_unref(iter); - - g_object_notify(G_OBJECT(self), "active-profile-id"); -} - -static void astal_wp_device_params_changed(AstalWpDevice *self, const gchar *prop) { - if (g_strcmp0(prop, "EnumProfile") == 0) { - astal_wp_device_update_profiles(self); - } else if (g_strcmp0(prop, "Profile") == 0) { - astal_wp_device_update_active_profile(self); - } -} - -static void astal_wp_device_update_properties(AstalWpDevice *self) { - AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); - if (priv->device == NULL) return; - self->id = wp_proxy_get_bound_id(WP_PROXY(priv->device)); - const gchar *description = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "device.description"); - if (description == NULL) { - description = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "device.name"); - } - if (description == NULL) { - description = "unknown"; - } - g_free(self->description); - self->description = g_strdup(description); - - const gchar *icon = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "device.icon-name"); - if (icon == NULL) { - icon = "audio-card-symbolic"; - } - g_free(self->icon); - self->icon = g_strdup(icon); - - const gchar *type = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "media.class"); - GEnumClass *enum_class = g_type_class_ref(ASTAL_WP_TYPE_DEVICE_TYPE); - if (g_enum_get_value_by_nick(enum_class, type) != NULL) - self->type = g_enum_get_value_by_nick(enum_class, type)->value; - g_type_class_unref(enum_class); - - astal_wp_device_update_profiles(self); - astal_wp_device_update_active_profile(self); - - g_object_notify(G_OBJECT(self), "id"); - g_object_notify(G_OBJECT(self), "device-type"); - g_object_notify(G_OBJECT(self), "icon"); - g_object_notify(G_OBJECT(self), "description"); -} - -AstalWpDevice *astal_wp_device_create(WpDevice *device) { - AstalWpDevice *self = g_object_new(ASTAL_WP_TYPE_DEVICE, NULL); - AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); - - priv->device = g_object_ref(device); - - g_signal_connect_swapped(priv->device, "params-changed", - G_CALLBACK(astal_wp_device_params_changed), self); - - astal_wp_device_update_properties(self); - return self; -} - -static void astal_wp_device_init(AstalWpDevice *self) { - AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); - priv->device = NULL; - - priv->profiles = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref); - - self->description = NULL; - self->icon = NULL; -} - -static void astal_wp_device_dispose(GObject *object) { - AstalWpDevice *self = ASTAL_WP_DEVICE(object); - AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); - - g_clear_object(&priv->device); -} - -static void astal_wp_device_finalize(GObject *object) { - AstalWpDevice *self = ASTAL_WP_DEVICE(object); - g_free(self->description); - g_free(self->icon); -} - -static void astal_wp_device_class_init(AstalWpDeviceClass *class) { - GObjectClass *object_class = G_OBJECT_CLASS(class); - object_class->dispose = astal_wp_device_dispose; - object_class->finalize = astal_wp_device_finalize; - object_class->get_property = astal_wp_device_get_property; - object_class->set_property = astal_wp_device_set_property; - /** - * AstalWpDevice:id - * - * The id of this device. - */ - astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_ID] = - g_param_spec_uint("id", "id", "id", 0, UINT_MAX, 0, G_PARAM_READABLE); - /** - * AstalWpDevice:description - * - * The description of this device. - */ - astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_DESCRIPTION] = - g_param_spec_string("description", "description", "description", NULL, G_PARAM_READABLE); - /** - * AstalWpDevice:icon - * - * The icon name for this device. - */ - astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_ICON] = - g_param_spec_string("icon", "icon", "icon", NULL, G_PARAM_READABLE); - /** - * AstalWpDevice:device-type: (type AstalWpDeviceType) - * - * The type of this device - */ - astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_DEVICE_TYPE] = - g_param_spec_enum("device-type", "device-type", "device-type", ASTAL_WP_TYPE_DEVICE_TYPE, 1, - G_PARAM_READABLE); - /** - * AstalWpDevice:profiles: (type GList(AstalWpProfile)) (transfer container) - * - * A list of available profiles - */ - astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_PROFILES] = - g_param_spec_pointer("profiles", "profiles", "profiles", G_PARAM_READABLE); - /** - * AstalWpDevice:active-profile-id - * - * The id of the currently active profile. - */ - astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE] = - g_param_spec_int("active-profile-id", "active-profile-id", "active-profile-id", G_MININT, - G_MAXINT, 0, G_PARAM_READWRITE); - - g_object_class_install_properties(object_class, ASTAL_WP_DEVICE_N_PROPERTIES, - astal_wp_device_properties); -} diff --git a/wireplumber/src/endpoint.c b/wireplumber/src/endpoint.c deleted file mode 100644 index 13979d1..0000000 --- a/wireplumber/src/endpoint.c +++ /dev/null @@ -1,554 +0,0 @@ -#include "endpoint.h" - -#include - -#include "device.h" -#include "endpoint-private.h" -#include "glib.h" -#include "wp.h" - -struct _AstalWpEndpoint { - GObject parent_instance; - - guint id; - gdouble volume; - gboolean mute; - gchar *description; - gchar *name; - AstalWpMediaClass type; - gboolean is_default; - gboolean lock_channels; - - gchar *icon; -}; - -typedef struct { - WpNode *node; - WpPlugin *mixer; - WpPlugin *defaults; - AstalWpWp *wp; - - gboolean is_default_node; - AstalWpMediaClass media_class; - - gulong default_signal_handler_id; - gulong mixer_signal_handler_id; - -} AstalWpEndpointPrivate; - -G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpEndpoint, astal_wp_endpoint, G_TYPE_OBJECT); - -G_DEFINE_ENUM_TYPE(AstalWpMediaClass, astal_wp_media_class, - G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE, "Audio/Source"), - G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER, "Audio/Sink"), - G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER, "Stream/Input/Audio"), - G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM, "Stream/Output/Audio"), - G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE, "Video/Source"), - G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_SINK, "Video/Sink"), - G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER, "Stream/Input/Video"), - G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM, "Stream/Output/Video")); - -typedef enum { - ASTAL_WP_ENDPOINT_PROP_ID = 1, - ASTAL_WP_ENDPOINT_PROP_VOLUME, - ASTAL_WP_ENDPOINT_PROP_MUTE, - ASTAL_WP_ENDPOINT_PROP_DESCRIPTION, - ASTAL_WP_ENDPOINT_PROP_NAME, - ASTAL_WP_ENDPOINT_PROP_MEDIA_CLASS, - ASTAL_WP_ENDPOINT_PROP_DEFAULT, - ASTAL_WP_ENDPOINT_PROP_ICON, - ASTAL_WP_ENDPOINT_PROP_VOLUME_ICON, - ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS, - ASTAL_WP_ENDPOINT_N_PROPERTIES, -} AstalWpEndpointProperties; - -static GParamSpec *astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_N_PROPERTIES] = { - NULL, -}; - -void astal_wp_endpoint_update_volume(AstalWpEndpoint *self) { - AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - - gdouble volume = 0; - gboolean mute; - GVariant *variant = NULL; - GVariantIter *channels = NULL; - - g_signal_emit_by_name(priv->mixer, "get-volume", self->id, &variant); - - if (variant == NULL) return; - - g_variant_lookup(variant, "volume", "d", &volume); - g_variant_lookup(variant, "mute", "b", &mute); - g_variant_lookup(variant, "channelVolumes", "a{sv}", &channels); - - if (channels != NULL) { - const gchar *key; - const gchar *channel_str; - gdouble channel_volume; - GVariant *varvol; - - while (g_variant_iter_loop(channels, "{&sv}", &key, &varvol)) { - g_variant_lookup(varvol, "volume", "d", &channel_volume); - g_variant_lookup(varvol, "channel", "&s", &channel_str); - if (channel_volume > volume) volume = channel_volume; - } - } - - if (mute != self->mute) { - self->mute = mute; - g_object_notify(G_OBJECT(self), "mute"); - } - - if (volume != self->volume) { - self->volume = volume; - g_object_notify(G_OBJECT(self), "volume"); - } - - g_object_notify(G_OBJECT(self), "volume-icon"); -} - -/** - * astal_wp_endpoint_set_volume: - * @self: the AstalWpEndpoint object - * @volume: The new volume level to set. - * - * Sets the volume level for this endpoint. The volume is clamped to be between - * 0 and 1.5. - */ -void astal_wp_endpoint_set_volume(AstalWpEndpoint *self, gdouble volume) { - AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - - gboolean ret; - if (volume >= 1.5) volume = 1.5; - if (volume <= 0) volume = 0; - - gboolean mute; - GVariant *variant = NULL; - GVariantIter *channels = NULL; - - g_auto(GVariantBuilder) vol_b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); - g_signal_emit_by_name(priv->mixer, "get-volume", self->id, &variant); - - if (variant == NULL) return; - - g_variant_lookup(variant, "mute", "b", &mute); - g_variant_lookup(variant, "channelVolumes", "a{sv}", &channels); - - if (channels != NULL && !self->lock_channels) { - g_auto(GVariantBuilder) channel_volumes_b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); - - const gchar *key; - const gchar *channel_str; - gdouble channel_volume; - GVariant *varvol; - - while (g_variant_iter_loop(channels, "{&sv}", &key, &varvol)) { - g_auto(GVariantBuilder) channel_b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); - g_variant_lookup(varvol, "volume", "d", &channel_volume); - g_variant_lookup(varvol, "channel", "&s", &channel_str); - gdouble vol = self->volume == 0 ? volume : channel_volume * volume / self->volume; - g_variant_builder_add(&channel_b, "{sv}", "volume", g_variant_new_double(vol)); - g_variant_builder_add(&channel_volumes_b, "{sv}", key, - g_variant_builder_end(&channel_b)); - } - - g_variant_builder_add(&vol_b, "{sv}", "channelVolumes", - g_variant_builder_end(&channel_volumes_b)); - } else { - GVariant *volume_variant = g_variant_new_double(volume); - g_variant_builder_add(&vol_b, "{sv}", "volume", volume_variant); - } - - g_signal_emit_by_name(priv->mixer, "set-volume", self->id, g_variant_builder_end(&vol_b), &ret); -} - -/** - * astal_wp_endpoint_set_mute: - * @self: the AstalWpEndpoint instance. - * @mute: A boolean indicating whether to mute the endpoint. - * - * Sets the mute status for the endpoint. - */ -void astal_wp_endpoint_set_mute(AstalWpEndpoint *self, gboolean mute) { - AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - - gboolean ret; - GVariant *variant = NULL; - GVariantBuilder b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); - g_variant_builder_add(&b, "{sv}", "mute", g_variant_new_boolean(mute)); - variant = g_variant_builder_end(&b); - - g_signal_emit_by_name(priv->mixer, "set-volume", self->id, variant, &ret); -} - -/** - * astal_wp_endpoint_get_media_class: - * @self: the AstalWpEndpoint instance. - * - * gets the media class of the endpoint. - */ -AstalWpMediaClass astal_wp_endpoint_get_media_class(AstalWpEndpoint *self) { return self->type; } - -/** - * astal_wp_endpoint_get_id: - * @self: the AstalWpEndpoint instance. - * - * gets the id of the endpoint. - */ -guint astal_wp_endpoint_get_id(AstalWpEndpoint *self) { return self->id; } - -/** - * astal_wp_endpoint_get_mute: - * @self: the AstalWpEndpoint instance. - * - * gets the mute status of the endpoint. - */ -gboolean astal_wp_endpoint_get_mute(AstalWpEndpoint *self) { return self->mute; } - -/** - * astal_wp_endpoint_get_volume: - * @self: the AstalWpEndpoint instance. - * - * gets the volume - */ -gdouble astal_wp_endpoint_get_volume(AstalWpEndpoint *self) { return self->volume; } - -const gchar *astal_wp_endpoint_get_description(AstalWpEndpoint *self) { return self->description; } - -const gchar *astal_wp_endpoint_get_name(AstalWpEndpoint *self) { return self->name; } - -const gchar *astal_wp_endpoint_get_icon(AstalWpEndpoint *self) { return self->icon; } - -gboolean astal_wp_endpoint_get_is_default(AstalWpEndpoint *self) { return self->is_default; } - -void astal_wp_endpoint_set_is_default(AstalWpEndpoint *self, gboolean is_default) { - AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - - if (!is_default) return; - gboolean ret; - const gchar *name = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.name"); - const gchar *media_class = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.class"); - g_signal_emit_by_name(priv->defaults, "set-default-configured-node-name", media_class, name, - &ret); -} - -gboolean astal_wp_endpoint_get_lock_channels(AstalWpEndpoint *self) { return self->lock_channels; } - -void astal_wp_endpoint_set_lock_channels(AstalWpEndpoint *self, gboolean lock_channels) { - self->lock_channels = lock_channels; - astal_wp_endpoint_set_volume(self, self->volume); -} - -const gchar *astal_wp_endpoint_get_volume_icon(AstalWpEndpoint *self) { - if (self->type == ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE) { - if (self->mute) return "microphone-sensitivity-muted-symbolic"; - if (self->volume <= 0.33) return "microphone-sensitivity-low-symbolic"; - if (self->volume <= 0.66) return "microphone-sensitivity-medium-symbolic"; - return "microphone-sensitivity-high-symbolic"; - - } else { - if (self->mute) return "audio-volume-muted-symbolic"; - if (self->volume <= 0.33) return "audio-volume-low-symbolic"; - if (self->volume <= 0.66) return "audio-volume-medium-symbolic"; - if (self->volume <= 1) return "audio-volume-high-symbolic"; - return "audio-volume-overamplified-symbolic"; - } -} - -static void astal_wp_endpoint_get_property(GObject *object, guint property_id, GValue *value, - GParamSpec *pspec) { - AstalWpEndpoint *self = ASTAL_WP_ENDPOINT(object); - - switch (property_id) { - case ASTAL_WP_ENDPOINT_PROP_ID: - g_value_set_uint(value, self->id); - break; - case ASTAL_WP_ENDPOINT_PROP_MUTE: - g_value_set_boolean(value, self->mute); - break; - case ASTAL_WP_ENDPOINT_PROP_VOLUME: - g_value_set_double(value, self->volume); - break; - case ASTAL_WP_ENDPOINT_PROP_DESCRIPTION: - g_value_set_string(value, self->description); - break; - case ASTAL_WP_ENDPOINT_PROP_NAME: - g_value_set_string(value, self->name); - break; - case ASTAL_WP_ENDPOINT_PROP_ICON: - g_value_set_string(value, self->icon); - break; - case ASTAL_WP_ENDPOINT_PROP_VOLUME_ICON: - g_value_set_string(value, astal_wp_endpoint_get_volume_icon(self)); - break; - case ASTAL_WP_ENDPOINT_PROP_MEDIA_CLASS: - g_value_set_enum(value, self->type); - break; - case ASTAL_WP_ENDPOINT_PROP_DEFAULT: - g_value_set_boolean(value, self->is_default); - break; - case ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS: - g_value_set_boolean(value, self->lock_channels); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_wp_endpoint_set_property(GObject *object, guint property_id, const GValue *value, - GParamSpec *pspec) { - AstalWpEndpoint *self = ASTAL_WP_ENDPOINT(object); - - switch (property_id) { - case ASTAL_WP_ENDPOINT_PROP_MUTE: - astal_wp_endpoint_set_mute(self, g_value_get_boolean(value)); - break; - case ASTAL_WP_ENDPOINT_PROP_VOLUME: - astal_wp_endpoint_set_volume(self, g_value_get_double(value)); - break; - case ASTAL_WP_ENDPOINT_PROP_DEFAULT: - astal_wp_endpoint_set_is_default(self, g_value_get_boolean(value)); - break; - case ASTAL_WP_ENDPOINT_PROP_ICON: - g_free(self->icon); - self->icon = g_strdup(g_value_get_string(value)); - break; - case ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS: - astal_wp_endpoint_set_lock_channels(self, g_value_get_boolean(value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_wp_endpoint_update_properties(AstalWpEndpoint *self) { - AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - if (priv->node == NULL) return; - self->id = wp_proxy_get_bound_id(WP_PROXY(priv->node)); - astal_wp_endpoint_update_volume(self); - - const gchar *description = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.description"); - if (description == NULL) { - description = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.nick"); - } - if (description == NULL) { - description = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.name"); - } - g_free(self->description); - self->description = g_strdup(description); - - const gchar *name = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.name"); - g_free(self->name); - self->name = g_strdup(name); - - const gchar *type = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.class"); - GEnumClass *enum_class = g_type_class_ref(ASTAL_WP_TYPE_MEDIA_CLASS); - if (g_enum_get_value_by_nick(enum_class, type) != NULL) - self->type = g_enum_get_value_by_nick(enum_class, type)->value; - g_type_class_unref(enum_class); - - const gchar *icon = NULL; - switch (self->type) { - case ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER: - case ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE: - const gchar *dev = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "device.id"); - guint device_id = g_ascii_strtoull(dev, NULL, 10); - AstalWpDevice *device = astal_wp_wp_get_device(priv->wp, device_id); - icon = astal_wp_device_get_icon(device); - if (icon == NULL) { - icon = self->type == ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER - ? "audio-card-symbolic" - : "audio-input-microphone-symbolic"; - } - break; - case ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM: - case ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER: - icon = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.icon-name"); - if (icon == NULL) - icon = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), - "window.icon-name"); - if (icon == NULL) - icon = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), - "application.icon-name"); - if (icon == NULL) icon = "application-x-executable-symbolic"; - break; - default: - icon = "audio-card-symbolic"; - } - g_free(self->icon); - self->icon = g_strdup(icon); - - g_object_notify(G_OBJECT(self), "id"); - g_object_notify(G_OBJECT(self), "description"); - g_object_notify(G_OBJECT(self), "name"); - g_object_notify(G_OBJECT(self), "icon"); - g_object_notify(G_OBJECT(self), "media-class"); -} - -static void astal_wp_endpoint_default_changed_as_default(AstalWpEndpoint *self) { - AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - - GEnumClass *enum_class = g_type_class_ref(ASTAL_WP_TYPE_MEDIA_CLASS); - const gchar *media_class = g_enum_get_value(enum_class, priv->media_class)->value_nick; - guint defaultId; - g_signal_emit_by_name(priv->defaults, "get-default-node", media_class, &defaultId); - g_type_class_unref(enum_class); - - if (defaultId != self->id) { - if (priv->node != NULL) g_object_unref(priv->node); - AstalWpEndpoint *default_endpoint = astal_wp_wp_get_endpoint(priv->wp, defaultId); - if (default_endpoint != NULL && - astal_wp_endpoint_get_media_class(default_endpoint) == priv->media_class) { - AstalWpEndpointPrivate *default_endpoint_priv = - astal_wp_endpoint_get_instance_private(default_endpoint); - priv->node = g_object_ref(default_endpoint_priv->node); - astal_wp_endpoint_update_properties(self); - } - } -} - -static void astal_wp_endpoint_default_changed(AstalWpEndpoint *self) { - AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - - guint defaultId; - const gchar *media_class = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.class"); - g_signal_emit_by_name(priv->defaults, "get-default-node", media_class, &defaultId); - - if (self->is_default && defaultId != self->id) { - self->is_default = FALSE; - g_object_notify(G_OBJECT(self), "is-default"); - } else if (!self->is_default && defaultId == self->id) { - self->is_default = TRUE; - g_object_notify(G_OBJECT(self), "is-default"); - } -} - -static void astal_wp_endpoint_mixer_changed(AstalWpEndpoint *self, guint node_id) { - if (self->id != node_id) return; - astal_wp_endpoint_update_volume(self); -} - -AstalWpEndpoint *astal_wp_endpoint_init_as_default(AstalWpEndpoint *self, WpPlugin *mixer, - WpPlugin *defaults, AstalWpMediaClass type, - AstalWpWp *wp) { - AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - - priv->mixer = g_object_ref(mixer); - priv->defaults = g_object_ref(defaults); - - priv->media_class = type; - priv->is_default_node = TRUE; - self->is_default = TRUE; - priv->wp = g_object_ref(wp); - - priv->default_signal_handler_id = g_signal_connect_swapped( - priv->defaults, "changed", G_CALLBACK(astal_wp_endpoint_default_changed_as_default), self); - priv->mixer_signal_handler_id = g_signal_connect_swapped( - priv->mixer, "changed", G_CALLBACK(astal_wp_endpoint_mixer_changed), self); - - astal_wp_endpoint_default_changed_as_default(self); - astal_wp_endpoint_update_properties(self); - return self; -} - -AstalWpEndpoint *astal_wp_endpoint_create(WpNode *node, WpPlugin *mixer, WpPlugin *defaults, - AstalWpWp *wp) { - AstalWpEndpoint *self = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL); - AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - - priv->mixer = g_object_ref(mixer); - priv->defaults = g_object_ref(defaults); - priv->node = g_object_ref(node); - priv->is_default_node = FALSE; - priv->wp = g_object_ref(wp); - - priv->default_signal_handler_id = g_signal_connect_swapped( - priv->defaults, "changed", G_CALLBACK(astal_wp_endpoint_default_changed), self); - priv->mixer_signal_handler_id = g_signal_connect_swapped( - priv->mixer, "changed", G_CALLBACK(astal_wp_endpoint_mixer_changed), self); - - astal_wp_endpoint_update_properties(self); - astal_wp_endpoint_default_changed(self); - return self; -} - -static void astal_wp_endpoint_init(AstalWpEndpoint *self) { - AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - priv->node = NULL; - priv->mixer = NULL; - priv->defaults = NULL; - priv->wp = NULL; - - self->volume = 0; - self->mute = TRUE; - self->description = NULL; - self->name = NULL; -} - -static void astal_wp_endpoint_dispose(GObject *object) { - AstalWpEndpoint *self = ASTAL_WP_ENDPOINT(object); - AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - - g_signal_handler_disconnect(priv->defaults, priv->default_signal_handler_id); - g_signal_handler_disconnect(priv->mixer, priv->mixer_signal_handler_id); - - g_clear_object(&priv->node); - g_clear_object(&priv->mixer); - g_clear_object(&priv->defaults); - g_clear_object(&priv->wp); -} - -static void astal_wp_endpoint_finalize(GObject *object) { - AstalWpEndpoint *self = ASTAL_WP_ENDPOINT(object); - g_free(self->description); - g_free(self->name); -} - -static void astal_wp_endpoint_class_init(AstalWpEndpointClass *class) { - GObjectClass *object_class = G_OBJECT_CLASS(class); - object_class->dispose = astal_wp_endpoint_dispose; - object_class->finalize = astal_wp_endpoint_finalize; - object_class->get_property = astal_wp_endpoint_get_property; - object_class->set_property = astal_wp_endpoint_set_property; - - astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_ID] = - g_param_spec_uint("id", "id", "id", 0, UINT_MAX, 0, G_PARAM_READABLE); - astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_VOLUME] = - g_param_spec_double("volume", "volume", "volume", 0, G_MAXFLOAT, 0, G_PARAM_READWRITE); - astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_MUTE] = - g_param_spec_boolean("mute", "mute", "mute", TRUE, G_PARAM_READWRITE); - astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_DESCRIPTION] = - g_param_spec_string("description", "description", "description", NULL, G_PARAM_READABLE); - astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_NAME] = - g_param_spec_string("name", "name", "name", NULL, G_PARAM_READABLE); - astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_ICON] = g_param_spec_string( - "icon", "icon", "icon", "audio-card-symbolic", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); - astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_VOLUME_ICON] = g_param_spec_string( - "volume-icon", "volume-icon", "volume-icon", "audio-volume-muted", G_PARAM_READABLE); - /** - * AstalWpEndpoint:media-class: (type AstalWpMediaClass) - * - * The media class of this endpoint - */ - astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_MEDIA_CLASS] = - g_param_spec_enum("media-class", "media-class", "media-class", ASTAL_WP_TYPE_MEDIA_CLASS, 1, - G_PARAM_READABLE); - astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_DEFAULT] = - g_param_spec_boolean("is_default", "is_default", "is_default", FALSE, G_PARAM_READWRITE); - astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS] = g_param_spec_boolean( - "lock_channels", "lock_channels", "lock channels", FALSE, G_PARAM_READWRITE); - - g_object_class_install_properties(object_class, ASTAL_WP_ENDPOINT_N_PROPERTIES, - astal_wp_endpoint_properties); -} diff --git a/wireplumber/src/meson.build b/wireplumber/src/meson.build deleted file mode 100644 index 87a5ae8..0000000 --- a/wireplumber/src/meson.build +++ /dev/null @@ -1,73 +0,0 @@ -srcs = files( - 'wireplumber.c', - 'endpoint.c', - 'device.c', - 'video.c', - 'profile.c', - 'audio.c', -) - -deps = [ - dependency('gobject-2.0'), - dependency('gio-2.0'), - dependency('wireplumber-0.5'), - # dependency('json-glib-1.0'), -] - -astal_wireplumber_lib = library( - 'astal-wireplumber', - sources : srcs, - include_directories : astal_wireplumber_inc, - dependencies : deps, - version : meson.project_version(), - install : true -) - -libastal_wireplumber = declare_dependency( - link_with : astal_wireplumber_lib, - include_directories : astal_wireplumber_inc) - -# astal_wireplumber_executable = executable( -# 'astal-wireplumber', -# files('astal-wireplumber.c'), -# dependencies : [ -# dependency('gobject-2.0'), -# dependency('gio-2.0'), -# dependency('json-glib-1.0'), -# libastal_wireplumber -# ], -# install : true) - -pkg_config_name = 'astal-wireplumber-' + lib_so_version - -if get_option('introspection') - gir = gnome.generate_gir( - astal_wireplumber_lib, - sources : srcs + astal_wireplumber_headers + astal_wireplumber_subheaders, - nsversion : '0.1', - namespace : 'AstalWp', - symbol_prefix : 'astal_wp', - identifier_prefix : 'AstalWp', - includes : ['GObject-2.0', 'Gio-2.0'], - header : 'astal-wp.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-wireplumber', - version : meson.project_version(), - libraries : [astal_wireplumber_lib], - filebase : pkg_config_name, - subdirs : 'astal', - description : 'astal wireplumber module', - url : 'https://github.com/astal-sh/wireplumber') diff --git a/wireplumber/src/profile.c b/wireplumber/src/profile.c deleted file mode 100644 index 291dc7f..0000000 --- a/wireplumber/src/profile.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "profile.h" - -#include - -struct _AstalWpProfile { - GObject parent_instance; - - gint index; - gchar *description; -}; - -G_DEFINE_FINAL_TYPE(AstalWpProfile, astal_wp_profile, G_TYPE_OBJECT); - -typedef enum { - ASTAL_WP_PROFILE_PROP_INDEX = 1, - ASTAL_WP_PROFILE_PROP_DESCRIPTION, - ASTAL_WP_PROFILE_N_PROPERTIES, -} AstalWpProfileProperties; - -static GParamSpec *astal_wp_profile_properties[ASTAL_WP_PROFILE_N_PROPERTIES] = { - NULL, -}; - -gint astal_wp_profile_get_index(AstalWpProfile *self) { return self->index; } - -const gchar *astal_wp_profile_get_description(AstalWpProfile *self) { return self->description; } - -static void astal_wp_profile_get_property(GObject *object, guint property_id, GValue *value, - GParamSpec *pspec) { - AstalWpProfile *self = ASTAL_WP_PROFILE(object); - - switch (property_id) { - case ASTAL_WP_PROFILE_PROP_INDEX: - g_value_set_int(value, self->index); - break; - case ASTAL_WP_PROFILE_PROP_DESCRIPTION: - g_value_set_string(value, self->description); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_wp_profile_set_property(GObject *object, guint property_id, const GValue *value, - GParamSpec *pspec) { - AstalWpProfile *self = ASTAL_WP_PROFILE(object); - - switch (property_id) { - case ASTAL_WP_PROFILE_PROP_INDEX: - self->index = g_value_get_int(value); - break; - case ASTAL_WP_PROFILE_PROP_DESCRIPTION: - g_free(self->description); - self->description = g_strdup(g_value_get_string(value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_wp_profile_init(AstalWpProfile *self) { self->description = NULL; } - -static void astal_wp_profile_finalize(GObject *object) { - AstalWpProfile *self = ASTAL_WP_PROFILE(object); - g_free(self->description); -} - -static void astal_wp_profile_class_init(AstalWpProfileClass *class) { - GObjectClass *object_class = G_OBJECT_CLASS(class); - object_class->finalize = astal_wp_profile_finalize; - object_class->get_property = astal_wp_profile_get_property; - object_class->set_property = astal_wp_profile_set_property; - - astal_wp_profile_properties[ASTAL_WP_PROFILE_PROP_DESCRIPTION] = - g_param_spec_string("description", "description", "description", NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); - astal_wp_profile_properties[ASTAL_WP_PROFILE_PROP_INDEX] = - g_param_spec_int("index", "index", "index", G_MININT, G_MAXINT, 0, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); - g_object_class_install_properties(object_class, ASTAL_WP_PROFILE_N_PROPERTIES, - astal_wp_profile_properties); -} diff --git a/wireplumber/src/video.c b/wireplumber/src/video.c deleted file mode 100644 index 00cdd82..0000000 --- a/wireplumber/src/video.c +++ /dev/null @@ -1,428 +0,0 @@ -#include "video.h" - -#include - -#include "device.h" -#include "endpoint.h" -#include "wp.h" - -struct _AstalWpVideo { - GObject parent_instance; -}; - -typedef struct { - AstalWpWp *wp; -} AstalWpVideoPrivate; - -G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpVideo, astal_wp_video, G_TYPE_OBJECT); - -typedef enum { - ASTAL_WP_VIDEO_SIGNAL_SOURCE_ADDED, - ASTAL_WP_VIDEO_SIGNAL_SOURCE_REMOVED, - ASTAL_WP_VIDEO_SIGNAL_SINK_ADDED, - ASTAL_WP_VIDEO_SIGNAL_SINK_REMOVED, - ASTAL_WP_VIDEO_SIGNAL_STREAM_ADDED, - ASTAL_WP_VIDEO_SIGNAL_STREAM_REMOVED, - ASTAL_WP_VIDEO_SIGNAL_RECORDER_ADDED, - ASTAL_WP_VIDEO_SIGNAL_RECORDER_REMOVED, - ASTAL_WP_VIDEO_SIGNAL_DEVICE_ADDED, - ASTAL_WP_VIDEO_SIGNAL_DEVICE_REMOVED, - ASTAL_WP_VIDEO_N_SIGNALS -} AstalWpWpSignals; - -static guint astal_wp_video_signals[ASTAL_WP_VIDEO_N_SIGNALS] = { - 0, -}; - -typedef enum { - ASTAL_WP_VIDEO_PROP_SOURCE = 1, - ASTAL_WP_VIDEO_PROP_SINK, - ASTAL_WP_VIDEO_PROP_STREAMS, - ASTAL_WP_VIDEO_PROP_RECORDERS, - ASTAL_WP_VIDEO_PROP_DEVICES, - ASTAL_WP_VIDEO_N_PROPERTIES, -} AstalWpVideoProperties; - -static GParamSpec *astal_wp_video_properties[ASTAL_WP_VIDEO_N_PROPERTIES] = { - NULL, -}; - -/** - * astal_wp_video_get_source: - * @self: the AstalWpVideo object - * @id: the id of the endpoint - * - * Returns: (transfer none) (nullable): the source with the given id - */ -AstalWpEndpoint *astal_wp_video_get_speaker(AstalWpVideo *self, guint id) { - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - - AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); - if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE) - return endpoint; - return NULL; -} - -/** - * astal_wp_video_get_sink: - * @self: the AstalWpVideo object - * @id: the id of the endpoint - * - * Returns: (transfer none) (nullable): the sink with the given id - */ -AstalWpEndpoint *astal_wp_video_get_sink(AstalWpVideo *self, guint id) { - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - - AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); - if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_VIDEO_SINK) - return endpoint; - return NULL; -} - -/** - * astal_wp_video_get_stream: - * @self: the AstalWpVideo object - * @id: the id of the endpoint - * - * Returns: (transfer none) (nullable): the stream with the given id - */ -AstalWpEndpoint *astal_wp_video_get_stream(AstalWpVideo *self, guint id) { - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - - AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); - if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM) - return endpoint; - return NULL; -} - -/** - * astal_wp_video_get_recorder: - * @self: the AstalWpVideo object - * @id: the id of the endpoint - * - * Returns: (transfer none) (nullable): the recorder with the given id - */ -AstalWpEndpoint *astal_wp_video_get_recorder(AstalWpVideo *self, guint id) { - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - - AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id); - if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER) - return endpoint; - return NULL; -} - -/** - * astal_wp_video_get_device: - * @self: the AstalWpVideo object - * @id: the id of the device - * - * Returns: (transfer none) (nullable): the device with the given id - */ -AstalWpDevice *astal_wp_video_get_device(AstalWpVideo *self, guint id) { - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - - AstalWpDevice *device = astal_wp_wp_get_device(priv->wp, id); - if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_VIDEO) return device; - return NULL; -} - -/** - * astal_wp_video_get_sources: - * @self: the AstalWpVideo object - * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the - * video sources - */ -GList *astal_wp_video_get_sources(AstalWpVideo *self) { - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - GList *eps = astal_wp_wp_get_endpoints(priv->wp); - GList *list = NULL; - - for (GList *l = eps; l != NULL; l = l->next) { - if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE) { - list = g_list_append(list, l->data); - } - } - g_list_free(eps); - return list; -} - -/** - * astal_wp_video_get_sinks - * @self: the AstalWpVideo object - * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the - * video sinks - */ -GList *astal_wp_video_get_sinks(AstalWpVideo *self) { - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - GList *eps = astal_wp_wp_get_endpoints(priv->wp); - GList *list = NULL; - - for (GList *l = eps; l != NULL; l = l->next) { - if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_SINK) { - list = g_list_append(list, l->data); - } - } - g_list_free(eps); - return list; -} - -/** - * astal_wp_video_get_recorders: - * @self: the AstalWpVideo object - * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the - * video recorders - */ -GList *astal_wp_video_get_recorders(AstalWpVideo *self) { - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - GList *eps = astal_wp_wp_get_endpoints(priv->wp); - GList *list = NULL; - - for (GList *l = eps; l != NULL; l = l->next) { - if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER) { - list = g_list_append(list, l->data); - } - } - g_list_free(eps); - return list; -} - -/** - * astal_wp_video_get_streams: - * @self: the AstalWpVideo object - * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the - * video streams - */ -GList *astal_wp_video_get_streams(AstalWpVideo *self) { - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - GList *eps = astal_wp_wp_get_endpoints(priv->wp); - GList *list = NULL; - - for (GList *l = eps; l != NULL; l = l->next) { - if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM) { - list = g_list_append(list, l->data); - } - } - g_list_free(eps); - return list; -} - -/** - * astal_wp_video_get_devices: - * @self: the AstalWpAudio object - * - * Returns: (transfer container) (nullable) (type GList(AstalWpVideo)): a GList containing the - * devices - */ -GList *astal_wp_video_get_devices(AstalWpVideo *self) { - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - GList *eps = astal_wp_wp_get_devices(priv->wp); - GList *list = NULL; - - for (GList *l = eps; l != NULL; l = l->next) { - if (astal_wp_device_get_device_type(l->data) == ASTAL_WP_DEVICE_TYPE_VIDEO) { - list = g_list_append(list, l->data); - } - } - g_list_free(eps); - return list; -} - -static void astal_wp_video_get_property(GObject *object, guint property_id, GValue *value, - GParamSpec *pspec) { - AstalWpVideo *self = ASTAL_WP_VIDEO(object); - - switch (property_id) { - case ASTAL_WP_VIDEO_PROP_SOURCE: - g_value_set_pointer(value, astal_wp_video_get_sources(self)); - break; - case ASTAL_WP_VIDEO_PROP_SINK: - g_value_set_pointer(value, astal_wp_video_get_sinks(self)); - break; - case ASTAL_WP_VIDEO_PROP_RECORDERS: - g_value_set_pointer(value, astal_wp_video_get_recorders(self)); - break; - case ASTAL_WP_VIDEO_PROP_STREAMS: - g_value_set_pointer(value, astal_wp_video_get_streams(self)); - break; - case ASTAL_WP_VIDEO_PROP_DEVICES: - g_value_set_pointer(value, astal_wp_video_get_devices(self)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -void astal_wp_video_device_added(AstalWpVideo *self, gpointer object) { - AstalWpDevice *device = ASTAL_WP_DEVICE(object); - if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_VIDEO) { - g_signal_emit_by_name(self, "device-added", device); - g_object_notify(G_OBJECT(self), "devices"); - } -} - -static void astal_wp_video_device_removed(AstalWpVideo *self, gpointer object) { - AstalWpDevice *device = ASTAL_WP_DEVICE(object); - if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_VIDEO) { - g_signal_emit_by_name(self, "device-removed", device); - g_object_notify(G_OBJECT(self), "devices"); - } -} - -static void astal_wp_video_object_added(AstalWpVideo *self, gpointer object) { - AstalWpEndpoint *endpoint = ASTAL_WP_ENDPOINT(object); - switch (astal_wp_endpoint_get_media_class(endpoint)) { - case ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE: - g_signal_emit_by_name(self, "source-added", endpoint); - g_object_notify(G_OBJECT(self), "sources"); - break; - case ASTAL_WP_MEDIA_CLASS_VIDEO_SINK: - g_signal_emit_by_name(self, "sink-added", endpoint); - g_object_notify(G_OBJECT(self), "sinks"); - break; - case ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM: - g_signal_emit_by_name(self, "stream-added", endpoint); - g_object_notify(G_OBJECT(self), "streams"); - break; - case ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER: - g_signal_emit_by_name(self, "recorder-added", endpoint); - g_object_notify(G_OBJECT(self), "recorders"); - break; - default: - break; - } -} - -static void astal_wp_video_object_removed(AstalWpAudio *self, gpointer object) { - AstalWpEndpoint *endpoint = ASTAL_WP_ENDPOINT(object); - switch (astal_wp_endpoint_get_media_class(endpoint)) { - case ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE: - g_signal_emit_by_name(self, "source-removed", endpoint); - g_object_notify(G_OBJECT(self), "sources"); - break; - case ASTAL_WP_MEDIA_CLASS_VIDEO_SINK: - g_signal_emit_by_name(self, "sink-removed", endpoint); - g_object_notify(G_OBJECT(self), "sinks"); - break; - case ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM: - g_signal_emit_by_name(self, "stream-removed", endpoint); - g_object_notify(G_OBJECT(self), "streams"); - break; - case ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER: - g_signal_emit_by_name(self, "recorder-removed", endpoint); - g_object_notify(G_OBJECT(self), "recorders"); - break; - default: - break; - } -} - -AstalWpVideo *astal_wp_video_new(AstalWpWp *wp) { - AstalWpVideo *self = g_object_new(ASTAL_WP_TYPE_VIDEO, NULL); - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - priv->wp = g_object_ref(wp); - g_signal_connect_swapped(priv->wp, "endpoint-added", G_CALLBACK(astal_wp_video_object_added), - self); - g_signal_connect_swapped(priv->wp, "endpoint-removed", - G_CALLBACK(astal_wp_video_object_removed), self); - g_signal_connect_swapped(priv->wp, "device-added", G_CALLBACK(astal_wp_video_device_added), - self); - g_signal_connect_swapped(priv->wp, "device-removed", G_CALLBACK(astal_wp_video_device_removed), - self); - - return self; -} - -static void astal_wp_video_dispose(GObject *object) { - AstalWpVideo *self = ASTAL_WP_VIDEO(object); - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); - g_clear_object(&priv->wp); -} - -static void astal_wp_video_init(AstalWpVideo *self) { - AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self); -} - -static void astal_wp_video_class_init(AstalWpVideoClass *class) { - GObjectClass *object_class = G_OBJECT_CLASS(class); - object_class->get_property = astal_wp_video_get_property; - object_class->dispose = astal_wp_video_dispose; - - /** - * AstalWpVideo:sources: (type GList(AstalWpEndpoint)) (transfer container) - * - * A list of AstalWpEndpoint objects - */ - astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_SOURCE] = - g_param_spec_pointer("sources", "sources", "sources", G_PARAM_READABLE); - - /** - * AstalWpVideo:sinks: (type GList(AstalWpEndpoint)) (transfer container) - * - * A list of AstalWpEndpoint objects - */ - astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_SINK] = - g_param_spec_pointer("sinks", "sinks", "sinks", G_PARAM_READABLE); - - /** - * AstalWpVideo:recorder: (type GList(AstalWpEndpoint)) (transfer container) - * - * A list of AstalWpEndpoint objects - */ - astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_RECORDERS] = - g_param_spec_pointer("recorders", "recorders", "recorders", G_PARAM_READABLE); - - /** - * AstalWpVideo:streams: (type GList(AstalWpEndpoint)) (transfer container) - * - * A list of AstalWpEndpoint objects - */ - astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_STREAMS] = - g_param_spec_pointer("streams", "streams", "streams", G_PARAM_READABLE); - - /** - * AstalWpVideo:devices: (type GList(AstalWpEndpoint)) (transfer container) - * - * A list of AstalWpEndpoint objects - */ - astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_DEVICES] = - g_param_spec_pointer("devices", "devices", "devices", G_PARAM_READABLE); - - g_object_class_install_properties(object_class, ASTAL_WP_VIDEO_N_PROPERTIES, - astal_wp_video_properties); - - astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SOURCE_ADDED] = - g_signal_new("source-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SOURCE_REMOVED] = - g_signal_new("source-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SINK_ADDED] = - g_signal_new("sink-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SINK_REMOVED] = - g_signal_new("sink-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_STREAM_ADDED] = - g_signal_new("stream-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SOURCE_REMOVED] = - g_signal_new("stream-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_RECORDER_ADDED] = - g_signal_new("recorder-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_RECORDER_REMOVED] = - g_signal_new("recorder-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, - NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_DEVICE_ADDED] = - g_signal_new("device-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); - astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_DEVICE_REMOVED] = - g_signal_new("device-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); -} diff --git a/wireplumber/src/wireplumber.c b/wireplumber/src/wireplumber.c deleted file mode 100644 index f1fa516..0000000 --- a/wireplumber/src/wireplumber.c +++ /dev/null @@ -1,503 +0,0 @@ -#include - -#include "audio.h" -#include "device-private.h" -#include "endpoint-private.h" -#include "glib-object.h" -#include "glib.h" -#include "video.h" -#include "wp.h" - -struct _AstalWpWp { - GObject parent_instance; - - AstalWpEndpoint *default_speaker; - AstalWpEndpoint *default_microphone; - - AstalWpAudio *audio; - AstalWpVideo *video; - - AstalWpScale scale; -}; - -typedef struct { - WpCore *core; - WpObjectManager *obj_manager; - - WpPlugin *mixer; - WpPlugin *defaults; - gint pending_plugins; - - GHashTable *endpoints; - GHashTable *devices; -} AstalWpWpPrivate; - -G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpWp, astal_wp_wp, G_TYPE_OBJECT); - -G_DEFINE_ENUM_TYPE(AstalWpScale, astal_wp_scale, - G_DEFINE_ENUM_VALUE(ASTAL_WP_SCALE_LINEAR, "linear"), - G_DEFINE_ENUM_VALUE(ASTAL_WP_SCALE_CUBIC, "cubic")); - -typedef enum { - ASTAL_WP_WP_SIGNAL_ENDPOINT_ADDED, - ASTAL_WP_WP_SIGNAL_ENDPOINT_REMOVED, - ASTAL_WP_WP_SIGNAL_DEVICE_ADDED, - ASTAL_WP_WP_SIGNAL_DEVICE_REMOVED, - ASTAL_WP_WP_N_SIGNALS -} AstalWpWpSignals; - -static guint astal_wp_wp_signals[ASTAL_WP_WP_N_SIGNALS] = { - 0, -}; - -typedef enum { - ASTAL_WP_WP_PROP_AUDIO = 1, - ASTAL_WP_WP_PROP_VIDEO, - ASTAL_WP_WP_PROP_ENDPOINTS, - ASTAL_WP_WP_PROP_DEVICES, - ASTAL_WP_WP_PROP_DEFAULT_SPEAKER, - ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE, - ASTAL_WP_WP_PROP_SCALE, - ASTAL_WP_WP_N_PROPERTIES, -} AstalWpWpProperties; - -static GParamSpec *astal_wp_wp_properties[ASTAL_WP_WP_N_PROPERTIES] = { - NULL, -}; - -/** - * astal_wp_wp_get_endpoint: - * @self: the AstalWpWp object - * @id: the id of the endpoint - * - * Returns: (transfer none) (nullable): the endpoint with the given id - */ -AstalWpEndpoint *astal_wp_wp_get_endpoint(AstalWpWp *self, guint id) { - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - - AstalWpEndpoint *endpoint = g_hash_table_lookup(priv->endpoints, GUINT_TO_POINTER(id)); - return endpoint; -} - -/** - * astal_wp_wp_get_endpoints: - * @self: the AstalWpWp object - * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the - * endpoints - */ -GList *astal_wp_wp_get_endpoints(AstalWpWp *self) { - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - return g_hash_table_get_values(priv->endpoints); -} - -/** - * astal_wp_wp_get_device: - * @self: the AstalWpWp object - * @id: the id of the device - * - * Returns: (transfer none) (nullable): the device with the given id - */ -AstalWpDevice *astal_wp_wp_get_device(AstalWpWp *self, guint id) { - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - - AstalWpDevice *device = g_hash_table_lookup(priv->devices, GUINT_TO_POINTER(id)); - return device; -} - -/** - * astal_wp_wp_get_devices: - * @self: the AstalWpWp object - * - * Returns: (transfer container) (nullable) (type GList(AstalWpDevice)): a GList containing the - * devices - */ -GList *astal_wp_wp_get_devices(AstalWpWp *self) { - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - return g_hash_table_get_values(priv->devices); -} - -/** - * astal_wp_wp_get_audio - * - * Returns: (nullable) (transfer none): gets the audio object - */ -AstalWpAudio *astal_wp_wp_get_audio(AstalWpWp *self) { return self->audio; } - -/** - * astal_wp_wp_get_video - * - * Returns: (nullable) (transfer none): gets the video object - */ -AstalWpVideo *astal_wp_wp_get_video(AstalWpWp *self) { return self->video; } - -/** - * astal_wp_wp_get_default_speaker - * - * Returns: (nullable) (transfer none): gets the default speaker object - */ -AstalWpEndpoint *astal_wp_wp_get_default_speaker(AstalWpWp *self) { return self->default_speaker; } - -/** - * astal_wp_wp_get_default_microphone - * - * Returns: (nullable) (transfer none): gets the default microphone object - */ -AstalWpEndpoint *astal_wp_wp_get_default_microphone(AstalWpWp *self) { - return self->default_microphone; -} - -AstalWpScale astal_wp_wp_get_scale(AstalWpWp *self) { return self->scale; } - -void astal_wp_wp_set_scale(AstalWpWp *self, AstalWpScale scale) { - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - self->scale = scale; - - if (priv->mixer == NULL) return; - - g_object_set(priv->mixer, "scale", self->scale, NULL); - - GHashTableIter iter; - gpointer key, value; - - g_hash_table_iter_init(&iter, priv->endpoints); - while (g_hash_table_iter_next(&iter, &key, &value)) { - AstalWpEndpoint *ep = ASTAL_WP_ENDPOINT(value); - astal_wp_endpoint_update_volume(ep); - } - - astal_wp_endpoint_update_volume(self->default_speaker); - astal_wp_endpoint_update_volume(self->default_microphone); -} - -static void astal_wp_wp_get_property(GObject *object, guint property_id, GValue *value, - GParamSpec *pspec) { - AstalWpWp *self = ASTAL_WP_WP(object); - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - - switch (property_id) { - case ASTAL_WP_WP_PROP_AUDIO: - g_value_set_object(value, astal_wp_wp_get_audio(self)); - break; - case ASTAL_WP_WP_PROP_VIDEO: - g_value_set_object(value, astal_wp_wp_get_video(self)); - break; - case ASTAL_WP_WP_PROP_ENDPOINTS: - g_value_set_pointer(value, g_hash_table_get_values(priv->endpoints)); - break; - case ASTAL_WP_WP_PROP_DEVICES: - g_value_set_pointer(value, g_hash_table_get_values(priv->devices)); - break; - case ASTAL_WP_WP_PROP_DEFAULT_SPEAKER: - g_value_set_object(value, self->default_speaker); - break; - case ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE: - g_value_set_object(value, self->default_microphone); - break; - case ASTAL_WP_WP_PROP_SCALE: - g_value_set_enum(value, self->scale); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_wp_wp_set_property(GObject *object, guint property_id, const GValue *value, - GParamSpec *pspec) { - AstalWpWp *self = ASTAL_WP_WP(object); - - switch (property_id) { - case ASTAL_WP_WP_PROP_SCALE: - astal_wp_wp_set_scale(self, g_value_get_enum(value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } -} - -static void astal_wp_wp_object_added(AstalWpWp *self, gpointer object) { - // print pipewire properties - // WpIterator *iter = wp_pipewire_object_new_properties_iterator(WP_PIPEWIRE_OBJECT(object)); - // GValue item = G_VALUE_INIT; - // const gchar *key, *value; - // - // g_print("\n\n"); - // while (wp_iterator_next (iter, &item)) { - // WpPropertiesItem *pi = g_value_get_boxed (&item); - // key = wp_properties_item_get_key (pi); - // value = wp_properties_item_get_value (pi); - // g_print("%s: %s\n", key, value); - // g_value_unset(&item); - // } - - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - - if (WP_IS_NODE(object)) { - WpNode *node = WP_NODE(object); - AstalWpEndpoint *endpoint = - astal_wp_endpoint_create(node, priv->mixer, priv->defaults, self); - - g_hash_table_insert(priv->endpoints, - GUINT_TO_POINTER(wp_proxy_get_bound_id(WP_PROXY(node))), endpoint); - - g_signal_emit_by_name(self, "endpoint-added", endpoint); - g_object_notify(G_OBJECT(self), "endpoints"); - } else if (WP_IS_DEVICE(object)) { - WpDevice *node = WP_DEVICE(object); - AstalWpDevice *device = astal_wp_device_create(node); - g_hash_table_insert(priv->devices, GUINT_TO_POINTER(wp_proxy_get_bound_id(WP_PROXY(node))), - device); - g_signal_emit_by_name(self, "device-added", device); - g_object_notify(G_OBJECT(self), "devices"); - } -} - -static void astal_wp_wp_object_removed(AstalWpWp *self, gpointer object) { - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - - if (WP_IS_NODE(object)) { - guint id = wp_proxy_get_bound_id(WP_PROXY(object)); - AstalWpEndpoint *endpoint = - g_object_ref(g_hash_table_lookup(priv->endpoints, GUINT_TO_POINTER(id))); - - g_hash_table_remove(priv->endpoints, GUINT_TO_POINTER(id)); - - g_signal_emit_by_name(self, "endpoint-removed", endpoint); - g_object_notify(G_OBJECT(self), "endpoints"); - g_object_unref(endpoint); - } else if (WP_IS_DEVICE(object)) { - guint id = wp_proxy_get_bound_id(WP_PROXY(object)); - AstalWpDevice *device = - g_object_ref(g_hash_table_lookup(priv->devices, GUINT_TO_POINTER(id))); - g_hash_table_remove(priv->devices, GUINT_TO_POINTER(id)); - - g_signal_emit_by_name(self, "device-removed", device); - g_object_notify(G_OBJECT(self), "devices"); - g_object_unref(device); - } -} - -static void astal_wp_wp_objm_installed(AstalWpWp *self) { - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - - astal_wp_endpoint_init_as_default(self->default_speaker, priv->mixer, priv->defaults, - ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER, self); - astal_wp_endpoint_init_as_default(self->default_microphone, priv->mixer, priv->defaults, - ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE, self); -} - -static void astal_wp_wp_plugin_activated(WpObject *obj, GAsyncResult *result, AstalWpWp *self) { - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - - GError *error = NULL; - wp_object_activate_finish(obj, result, &error); - if (error) { - g_critical("Failed to activate component: %s\n", error->message); - return; - } - - if (--priv->pending_plugins == 0) { - priv->defaults = wp_plugin_find(priv->core, "default-nodes-api"); - priv->mixer = wp_plugin_find(priv->core, "mixer-api"); - g_object_set(priv->mixer, "scale", self->scale, NULL); - - g_signal_connect_swapped(priv->obj_manager, "object-added", - G_CALLBACK(astal_wp_wp_object_added), self); - g_signal_connect_swapped(priv->obj_manager, "object-removed", - G_CALLBACK(astal_wp_wp_object_removed), self); - - wp_core_install_object_manager(priv->core, priv->obj_manager); - } -} - -static void astal_wp_wp_plugin_loaded(WpObject *obj, GAsyncResult *result, AstalWpWp *self) { - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - - GError *error = NULL; - wp_core_load_component_finish(priv->core, result, &error); - if (error) { - g_critical("Failed to load component: %s\n", error->message); - return; - } - - wp_object_activate(obj, WP_PLUGIN_FEATURE_ENABLED, NULL, - (GAsyncReadyCallback)astal_wp_wp_plugin_activated, self); -} - -/** - * astal_wp_wp_get_default - * - * Returns: (nullable) (transfer none): gets the default wireplumber object. - */ -AstalWpWp *astal_wp_wp_get_default() { - static AstalWpWp *self = NULL; - - if (self == NULL) self = g_object_new(ASTAL_WP_TYPE_WP, NULL); - - return self; -} - -/** - * astal_wp_get_default_wp - * - * Returns: (nullable) (transfer none): gets the default wireplumber object. - */ -AstalWpWp *astal_wp_get_default_wp() { return astal_wp_wp_get_default(); } - -static void astal_wp_wp_dispose(GObject *object) { - AstalWpWp *self = ASTAL_WP_WP(object); - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - - g_clear_object(&self->video); - g_clear_object(&self->audio); - - wp_core_disconnect(priv->core); - g_clear_object(&self->default_speaker); - g_clear_object(&self->default_microphone); - g_clear_object(&priv->mixer); - g_clear_object(&priv->defaults); - g_clear_object(&priv->obj_manager); - g_clear_object(&priv->core); - - if (priv->endpoints != NULL) { - g_hash_table_destroy(priv->endpoints); - priv->endpoints = NULL; - } -} - -static void astal_wp_wp_finalize(GObject *object) { - AstalWpWp *self = ASTAL_WP_WP(object); - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); -} - -static void astal_wp_wp_init(AstalWpWp *self) { - AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); - - priv->endpoints = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref); - priv->devices = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref); - - wp_init(7); - priv->core = wp_core_new(NULL, NULL, NULL); - - if (!wp_core_connect(priv->core)) { - g_critical("could not connect to PipeWire\n"); - return; - } - - priv->obj_manager = wp_object_manager_new(); - wp_object_manager_request_object_features(priv->obj_manager, WP_TYPE_NODE, - WP_OBJECT_FEATURES_ALL); - wp_object_manager_request_object_features(priv->obj_manager, WP_TYPE_GLOBAL_PROXY, - WP_OBJECT_FEATURES_ALL); - - wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, - "media.class", "=s", "Audio/Sink", NULL); - wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, - "media.class", "=s", "Audio/Source", NULL); - wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, - "media.class", "=s", "Stream/Output/Audio", NULL); - wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, - "media.class", "=s", "Stream/Input/Audio", NULL); - wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_DEVICE, - WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "media.class", "=s", - "Audio/Device", NULL); - - wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, - "media.class", "=s", "Video/Sink", NULL); - wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, - "media.class", "=s", "Video/Source", NULL); - wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, - "media.class", "=s", "Stream/Output/Video", NULL); - wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, - "media.class", "=s", "Stream/Input/Video", NULL); - wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_DEVICE, - WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "media.class", "=s", - "Video/Device", NULL); - // wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_CLIENT, NULL); - - g_signal_connect_swapped(priv->obj_manager, "installed", (GCallback)astal_wp_wp_objm_installed, - self); - - self->default_speaker = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL); - self->default_microphone = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL); - - self->audio = astal_wp_audio_new(self); - self->video = astal_wp_video_new(self); - - priv->pending_plugins = 2; - wp_core_load_component(priv->core, "libwireplumber-module-default-nodes-api", "module", NULL, - "default-nodes-api", NULL, - (GAsyncReadyCallback)astal_wp_wp_plugin_loaded, self); - wp_core_load_component(priv->core, "libwireplumber-module-mixer-api", "module", NULL, - "mixer-api", NULL, (GAsyncReadyCallback)astal_wp_wp_plugin_loaded, self); -} - -static void astal_wp_wp_class_init(AstalWpWpClass *class) { - GObjectClass *object_class = G_OBJECT_CLASS(class); - object_class->finalize = astal_wp_wp_finalize; - object_class->dispose = astal_wp_wp_dispose; - object_class->get_property = astal_wp_wp_get_property; - object_class->set_property = astal_wp_wp_set_property; - - astal_wp_wp_properties[ASTAL_WP_WP_PROP_AUDIO] = - g_param_spec_object("audio", "audio", "audio", ASTAL_WP_TYPE_AUDIO, G_PARAM_READABLE); - astal_wp_wp_properties[ASTAL_WP_WP_PROP_VIDEO] = - g_param_spec_object("video", "video", "video", ASTAL_WP_TYPE_VIDEO, G_PARAM_READABLE); - /** - * AstalWpWp:scale: (type AstalWpScale) - * - * The scale used for the volume - */ - astal_wp_wp_properties[ASTAL_WP_WP_PROP_SCALE] = - g_param_spec_enum("scale", "scale", "scale", ASTAL_WP_TYPE_SCALE, ASTAL_WP_SCALE_CUBIC, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT); - - /** - * AstalWpWp:endpoints: (type GList(AstalWpEndpoint)) (transfer container) - * - * A list of AstalWpEndpoint objects - */ - astal_wp_wp_properties[ASTAL_WP_WP_PROP_ENDPOINTS] = - g_param_spec_pointer("endpoints", "endpoints", "endpoints", G_PARAM_READABLE); - /** - * AstalWpWp:devices: (type GList(AstalWpDevice)) (transfer container) - * - * A list of AstalWpDevice objects - */ - astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEVICES] = - g_param_spec_pointer("devices", "devices", "devices", G_PARAM_READABLE); - /** - * AstalWpWp:default-speaker: - * - * The AstalWndpoint object representing the default speaker - */ - astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEFAULT_SPEAKER] = - g_param_spec_object("default-speaker", "default-speaker", "default-speaker", - ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); - /** - * AstalWpWp:default-microphone: - * - * The AstalWndpoint object representing the default speaker - */ - astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE] = - g_param_spec_object("default-microphone", "default-microphone", "default-microphone", - ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); - - g_object_class_install_properties(object_class, ASTAL_WP_WP_N_PROPERTIES, - astal_wp_wp_properties); - - astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_ENDPOINT_ADDED] = - g_signal_new("endpoint-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_ENDPOINT_REMOVED] = - g_signal_new("endpoint-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, - NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT); - astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_DEVICE_ADDED] = - g_signal_new("device-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); - astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_DEVICE_REMOVED] = - g_signal_new("device-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE); -} diff --git a/wireplumber/version b/wireplumber/version deleted file mode 100644 index 6e8bf73..0000000 --- a/wireplumber/version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 -- cgit v1.2.3