3
0
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:
KramLololo
2026-08-05 16:16:20 +03:00
committed by GitHub
parent db95de4f5b
commit cab296c38b
9 changed files with 121 additions and 39 deletions

View File

@ -29,6 +29,7 @@ runs:
hyprcursor \
jq \
libc++ \
libcanberra \
libdisplay-info \
libdrm \
libei \

View File

@ -285,6 +285,7 @@ pkg_check_modules(deps
re2
muparser
lcms2
libcanberra
)
pkg_search_module(LUA REQUIRED IMPORTED_TARGET GLOBAL lua55 lua5.5 lua-55 lua-5.5 lua>=5.5 lua<5.6)

View File

@ -23,6 +23,7 @@
hyprwayland-scanner,
hyprwire,
lcms2,
libcanberra,
libGL,
libdrm,
libei,
@ -190,6 +191,7 @@ customStdenv.mkDerivation (finalAttrs: {
hyprutils
hyprwire
lcms2
libcanberra
libdrm
libgbm
libGL

View File

@ -114,6 +114,8 @@ CLuaEventHandler::CLuaEventHandler(lua_State* L) : m_lua(L) {
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.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.update_rules",
"window.move_to_workspace",
"window.bell",
"layer.opened",
"layer.closed",
"monitor.added",

View File

@ -525,6 +525,7 @@ std::vector<SP<IValue>> Values::getConfigValues() {
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: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:float_force_onscreen", "whether existing floating windows must remain fully/partially on-screen", 0),

View File

@ -90,6 +90,7 @@ namespace Event {
Event<PHLWINDOW> floating;
Event<PHLWINDOW> updateRules;
Event<PHLWINDOW, PHLWORKSPACE> moveToWorkspace;
Cancellable<PHLWINDOW> bell;
} window;
struct {

72
src/helpers/BellSound.cpp Normal file
View 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
View 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;
};

View File

@ -1,52 +1,29 @@
#include "XDGBell.hpp"
#include "core/Compositor.hpp"
#include "../desktop/view/Window.hpp"
#include "../helpers/BellSound.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 "../Compositor.hpp"
#include <format>
CXDGSystemBellManagerResource::CXDGSystemBellManagerResource(UP<CXdgSystemBellV1>&& resource) : m_resource(std::move(resource)) {
if UNLIKELY (!good())
return;
m_resource->setDestroy([this](CXdgSystemBellV1* r) { PROTO::xdgBell->destroyResource(this); });
m_resource->setOnDestroy([this](CXdgSystemBellV1* r) { PROTO::xdgBell->destroyResource(this); });
m_resource->setDestroy([this](CXdgSystemBellV1*) { PROTO::xdgBell->destroyResource(this); });
m_resource->setOnDestroy([this](CXdgSystemBellV1*) { PROTO::xdgBell->destroyResource(this); });
m_resource->setRing([](CXdgSystemBellV1* r, wl_resource* surface) {
if (!surface) {
IPC::Socket2::sock()->postEvent({
.event = "bell",
.data = "",
});
m_resource->setRing([](CXdgSystemBellV1*, wl_resource* surface) {
const auto WINDOW = Desktop::viewState()->query().surface(CWLSurfaceResource::fromResource(surface)).type(Desktop::View::VIEW_TYPE_WINDOW).runWindow();
Event::SCallbackInfo info;
Event::bus()->m_events.window.bell.emit(WINDOW, info);
if (info.cancelled)
return;
}
const auto SURFACE = CWLSurfaceResource::fromResource(surface);
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 = "",
});
IPC::Socket2::sock()->postEvent({.event = "bell", .data = WINDOW ? std::format("{:x}", rc<uintptr_t>(WINDOW.get())) : ""});
CBellSound::play();
});
}