mirror of
https://github.com/hyprwm/Hyprland.git
synced 2026-08-18 02:51:23 +00:00
protocols/bell: play a sound by default (#15502)
--------- Co-authored-by: thompty <tatthompson44@gmail.com>
This commit is contained in:
1
.github/actions/setup_base/action.yml
vendored
1
.github/actions/setup_base/action.yml
vendored
@ -29,6 +29,7 @@ runs:
|
|||||||
hyprcursor \
|
hyprcursor \
|
||||||
jq \
|
jq \
|
||||||
libc++ \
|
libc++ \
|
||||||
|
libcanberra \
|
||||||
libdisplay-info \
|
libdisplay-info \
|
||||||
libdrm \
|
libdrm \
|
||||||
libei \
|
libei \
|
||||||
|
|||||||
@ -285,6 +285,7 @@ pkg_check_modules(deps
|
|||||||
re2
|
re2
|
||||||
muparser
|
muparser
|
||||||
lcms2
|
lcms2
|
||||||
|
libcanberra
|
||||||
)
|
)
|
||||||
|
|
||||||
pkg_search_module(LUA REQUIRED IMPORTED_TARGET GLOBAL lua55 lua5.5 lua-55 lua-5.5 lua>=5.5 lua<5.6)
|
pkg_search_module(LUA REQUIRED IMPORTED_TARGET GLOBAL lua55 lua5.5 lua-55 lua-5.5 lua>=5.5 lua<5.6)
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
hyprwayland-scanner,
|
hyprwayland-scanner,
|
||||||
hyprwire,
|
hyprwire,
|
||||||
lcms2,
|
lcms2,
|
||||||
|
libcanberra,
|
||||||
libGL,
|
libGL,
|
||||||
libdrm,
|
libdrm,
|
||||||
libei,
|
libei,
|
||||||
@ -190,6 +191,7 @@ customStdenv.mkDerivation (finalAttrs: {
|
|||||||
hyprutils
|
hyprutils
|
||||||
hyprwire
|
hyprwire
|
||||||
lcms2
|
lcms2
|
||||||
|
libcanberra
|
||||||
libdrm
|
libdrm
|
||||||
libgbm
|
libgbm
|
||||||
libGL
|
libGL
|
||||||
|
|||||||
@ -114,6 +114,8 @@ CLuaEventHandler::CLuaEventHandler(lua_State* L) : m_lua(L) {
|
|||||||
CLuaWorkspace::push(L, ws);
|
CLuaWorkspace::push(L, ws);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
m_listeners.push_back(
|
||||||
|
bus()->m_events.window.bell.listen([this](PHLWINDOW w, Event::SCallbackInfo&) { dispatch("window.bell", 1, [&](lua_State* L) { CLuaWindow::push(L, w); }); }));
|
||||||
|
|
||||||
m_listeners.push_back(bus()->m_events.layer.opened.listen([this](PHLLS ls) { dispatch("layer.opened", 1, [&](lua_State* L) { CLuaLayerSurface::push(L, ls); }); }));
|
m_listeners.push_back(bus()->m_events.layer.opened.listen([this](PHLLS ls) { dispatch("layer.opened", 1, [&](lua_State* L) { CLuaLayerSurface::push(L, ls); }); }));
|
||||||
m_listeners.push_back(bus()->m_events.layer.closed.listen([this](PHLLS ls) { dispatch("layer.closed", 1, [&](lua_State* L) { CLuaLayerSurface::push(L, ls); }); }));
|
m_listeners.push_back(bus()->m_events.layer.closed.listen([this](PHLLS ls) { dispatch("layer.closed", 1, [&](lua_State* L) { CLuaLayerSurface::push(L, ls); }); }));
|
||||||
@ -278,6 +280,7 @@ std::unordered_set<std::string> CLuaEventHandler::knownEvents() {
|
|||||||
"window.fullscreen",
|
"window.fullscreen",
|
||||||
"window.update_rules",
|
"window.update_rules",
|
||||||
"window.move_to_workspace",
|
"window.move_to_workspace",
|
||||||
|
"window.bell",
|
||||||
"layer.opened",
|
"layer.opened",
|
||||||
"layer.closed",
|
"layer.closed",
|
||||||
"monitor.added",
|
"monitor.added",
|
||||||
|
|||||||
@ -525,6 +525,7 @@ std::vector<SP<IValue>> Values::getConfigValues() {
|
|||||||
MS<Bool>("misc:screencopy_force_8b", "forces 8 bit screencopy", true),
|
MS<Bool>("misc:screencopy_force_8b", "forces 8 bit screencopy", true),
|
||||||
MS<Bool>("misc:disable_scale_notification", "disables notification popup when a monitor fails to set a suitable scale", false),
|
MS<Bool>("misc:disable_scale_notification", "disables notification popup when a monitor fails to set a suitable scale", false),
|
||||||
MS<Bool>("misc:size_limits_tiled", "whether to apply minsize and maxsize rules to tiled windows", false),
|
MS<Bool>("misc:size_limits_tiled", "whether to apply minsize and maxsize rules to tiled windows", false),
|
||||||
|
MS<String>("misc:bell_sound", "path to custom wav/ogg system bell. `none` or an empty string mute it. `default` uses the system's current one.", "default"),
|
||||||
MS<Int>("misc:new_float_force_onscreen", "whether new floating windows must be placed fully/partially on-screen", 2),
|
MS<Int>("misc:new_float_force_onscreen", "whether new floating windows must be placed fully/partially on-screen", 2),
|
||||||
MS<Int>("misc:float_force_onscreen", "whether existing floating windows must remain fully/partially on-screen", 0),
|
MS<Int>("misc:float_force_onscreen", "whether existing floating windows must remain fully/partially on-screen", 0),
|
||||||
|
|
||||||
|
|||||||
@ -90,6 +90,7 @@ namespace Event {
|
|||||||
Event<PHLWINDOW> floating;
|
Event<PHLWINDOW> floating;
|
||||||
Event<PHLWINDOW> updateRules;
|
Event<PHLWINDOW> updateRules;
|
||||||
Event<PHLWINDOW, PHLWORKSPACE> moveToWorkspace;
|
Event<PHLWINDOW, PHLWORKSPACE> moveToWorkspace;
|
||||||
|
Cancellable<PHLWINDOW> bell;
|
||||||
} window;
|
} window;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|||||||
72
src/helpers/BellSound.cpp
Normal file
72
src/helpers/BellSound.cpp
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#include "BellSound.hpp"
|
||||||
|
#include <canberra.h>
|
||||||
|
#include "../config/ConfigValue.hpp"
|
||||||
|
#include "../config/ConfigManager.hpp"
|
||||||
|
#include "./MiscFunctions.hpp"
|
||||||
|
#include "../event/EventBus.hpp"
|
||||||
|
#include "../debug/log/Logger.hpp"
|
||||||
|
|
||||||
|
CBellSound::CBellSound() {
|
||||||
|
onNewConfig();
|
||||||
|
m_configListener = Event::bus()->m_events.config.reloaded.listen([this] { onNewConfig(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
CBellSound::~CBellSound() {
|
||||||
|
if (m_context) {
|
||||||
|
ca_context_destroy(m_context);
|
||||||
|
ca_proplist_destroy(m_sound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBellSound::onNewConfig() {
|
||||||
|
const auto VALUE = *CConfigValue<std::string>("misc:bell_sound");
|
||||||
|
|
||||||
|
if (VALUE == "default") {
|
||||||
|
m_muted = false;
|
||||||
|
initializeSoundContext();
|
||||||
|
ca_proplist_sets(m_sound, CA_PROP_EVENT_ID, "bell-window-system");
|
||||||
|
} else if (VALUE.empty() || VALUE == "none")
|
||||||
|
m_muted = true;
|
||||||
|
else {
|
||||||
|
m_muted = false;
|
||||||
|
initializeSoundContext();
|
||||||
|
|
||||||
|
const auto RESOLVEDPATH = absolutePath(VALUE, Config::mgr()->getMainConfigPath());
|
||||||
|
|
||||||
|
if (std::filesystem::exists(RESOLVEDPATH)) {
|
||||||
|
ca_proplist_sets(m_sound, CA_PROP_MEDIA_FILENAME, RESOLVEDPATH.c_str());
|
||||||
|
ca_proplist_set(m_sound, CA_PROP_EVENT_ID, nullptr, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ca_proplist_sets(m_sound, CA_PROP_EVENT_ID, "bell-window-system");
|
||||||
|
Log::logger->log(Log::WARN, "bell: resolved custom sound path '{}' doesn't exist, falling back to default", RESOLVEDPATH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBellSound::initializeSoundContext() {
|
||||||
|
if (m_context)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int result = ca_context_create(&m_context) || ca_proplist_create(&m_sound);
|
||||||
|
if UNLIKELY (result != CA_SUCCESS)
|
||||||
|
Log::logger->log(Log::ERR, "bell: failed to create canberra context, '{}'", ca_strerror(result));
|
||||||
|
|
||||||
|
ca_context_change_props(m_context, CA_PROP_APPLICATION_NAME, "Hyprland", CA_PROP_MEDIA_NAME, "System Bell", CA_PROP_EVENT_DESCRIPTION, "Wayland system bell",
|
||||||
|
CA_PROP_MEDIA_ROLE, "event", CA_PROP_MEDIA_ICON_NAME, "preferences-system-notifications", CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBellSound::play() {
|
||||||
|
static CBellSound instance;
|
||||||
|
|
||||||
|
if (instance.m_muted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int result = ca_context_play_full(instance.m_context, 0, instance.m_sound, nullptr, nullptr);
|
||||||
|
if UNLIKELY (result != CA_SUCCESS) {
|
||||||
|
if (result == CA_ERROR_CORRUPT)
|
||||||
|
Log::logger->log(Log::WARN, "bell: sound is not a wav/ogg file");
|
||||||
|
else
|
||||||
|
Log::logger->log(Log::WARN, "bell: failed to play sound, '{}'", ca_strerror(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/helpers/BellSound.hpp
Normal file
24
src/helpers/BellSound.hpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <hyprutils/signal/Listener.hpp>
|
||||||
|
|
||||||
|
struct ca_context;
|
||||||
|
struct ca_proplist;
|
||||||
|
|
||||||
|
class CBellSound {
|
||||||
|
public:
|
||||||
|
static void play();
|
||||||
|
|
||||||
|
private:
|
||||||
|
CBellSound();
|
||||||
|
~CBellSound();
|
||||||
|
|
||||||
|
void onNewConfig();
|
||||||
|
void initializeSoundContext();
|
||||||
|
|
||||||
|
bool m_muted = false;
|
||||||
|
ca_proplist* m_sound = nullptr;
|
||||||
|
ca_context* m_context = nullptr;
|
||||||
|
|
||||||
|
Hyprutils::Signal::CHyprSignalListener m_configListener;
|
||||||
|
};
|
||||||
@ -1,52 +1,29 @@
|
|||||||
#include "XDGBell.hpp"
|
#include "XDGBell.hpp"
|
||||||
#include "core/Compositor.hpp"
|
#include "../helpers/BellSound.hpp"
|
||||||
#include "../desktop/view/Window.hpp"
|
#include "./core/Compositor.hpp"
|
||||||
|
#include "../desktop/state/ViewState.hpp"
|
||||||
|
#include "../desktop/state/ViewQuery.hpp"
|
||||||
|
#include "../event/EventBus.hpp"
|
||||||
#include "../ipc/s2/S2.hpp"
|
#include "../ipc/s2/S2.hpp"
|
||||||
#include "../Compositor.hpp"
|
#include <format>
|
||||||
|
|
||||||
CXDGSystemBellManagerResource::CXDGSystemBellManagerResource(UP<CXdgSystemBellV1>&& resource) : m_resource(std::move(resource)) {
|
CXDGSystemBellManagerResource::CXDGSystemBellManagerResource(UP<CXdgSystemBellV1>&& resource) : m_resource(std::move(resource)) {
|
||||||
if UNLIKELY (!good())
|
if UNLIKELY (!good())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_resource->setDestroy([this](CXdgSystemBellV1* r) { PROTO::xdgBell->destroyResource(this); });
|
m_resource->setDestroy([this](CXdgSystemBellV1*) { PROTO::xdgBell->destroyResource(this); });
|
||||||
m_resource->setOnDestroy([this](CXdgSystemBellV1* r) { PROTO::xdgBell->destroyResource(this); });
|
m_resource->setOnDestroy([this](CXdgSystemBellV1*) { PROTO::xdgBell->destroyResource(this); });
|
||||||
|
|
||||||
m_resource->setRing([](CXdgSystemBellV1* r, wl_resource* surface) {
|
m_resource->setRing([](CXdgSystemBellV1*, wl_resource* surface) {
|
||||||
if (!surface) {
|
const auto WINDOW = Desktop::viewState()->query().surface(CWLSurfaceResource::fromResource(surface)).type(Desktop::View::VIEW_TYPE_WINDOW).runWindow();
|
||||||
IPC::Socket2::sock()->postEvent({
|
|
||||||
.event = "bell",
|
Event::SCallbackInfo info;
|
||||||
.data = "",
|
Event::bus()->m_events.window.bell.emit(WINDOW, info);
|
||||||
});
|
if (info.cancelled)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
const auto SURFACE = CWLSurfaceResource::fromResource(surface);
|
IPC::Socket2::sock()->postEvent({.event = "bell", .data = WINDOW ? std::format("{:x}", rc<uintptr_t>(WINDOW.get())) : ""});
|
||||||
|
CBellSound::play();
|
||||||
if (!SURFACE) {
|
|
||||||
IPC::Socket2::sock()->postEvent({
|
|
||||||
.event = "bell",
|
|
||||||
.data = "",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& w : Desktop::windowState()->windows()) {
|
|
||||||
if (!w->m_isMapped || w->m_isX11 || !w->m_xdgSurface || !w->wlSurface())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (w->wlSurface()->resource() == SURFACE) {
|
|
||||||
IPC::Socket2::sock()->postEvent({
|
|
||||||
.event = "bell",
|
|
||||||
.data = std::format("{:x}", rc<uintptr_t>(w.get())),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IPC::Socket2::sock()->postEvent({
|
|
||||||
.event = "bell",
|
|
||||||
.data = "",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user