summaryrefslogtreecommitdiff
path: root/src/windows
diff options
context:
space:
mode:
Diffstat (limited to 'src/windows')
-rw-r--r--src/windows/mainwindow.cpp13
-rw-r--r--src/windows/mainwindow.hpp8
-rw-r--r--src/windows/voicesettingswindow.cpp125
-rw-r--r--src/windows/voicesettingswindow.hpp25
-rw-r--r--src/windows/voicewindow.cpp263
-rw-r--r--src/windows/voicewindow.hpp85
6 files changed, 516 insertions, 3 deletions
diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp
index e43a297..73b78de 100644
--- a/src/windows/mainwindow.cpp
+++ b/src/windows/mainwindow.cpp
@@ -5,6 +5,7 @@ MainWindow::MainWindow()
, m_content_box(Gtk::ORIENTATION_HORIZONTAL)
, m_chan_content_paned(Gtk::ORIENTATION_HORIZONTAL)
, m_content_members_paned(Gtk::ORIENTATION_HORIZONTAL)
+ , m_left_pane(Gtk::ORIENTATION_VERTICAL)
, m_accels(Gtk::AccelGroup::create()) {
set_default_size(1200, 800);
get_style_context()->add_class("app-window");
@@ -50,12 +51,18 @@ MainWindow::MainWindow()
m_content_stack.set_visible_child("chat");
m_content_stack.show();
- m_chan_content_paned.pack1(m_channel_list);
+ m_voice_info.show();
+
+ m_left_pane.add(m_channel_list);
+ m_left_pane.add(m_voice_info);
+ m_left_pane.show();
+
+ m_chan_content_paned.pack1(m_left_pane);
m_chan_content_paned.pack2(m_content_members_paned);
m_chan_content_paned.child_property_shrink(m_content_members_paned) = true;
m_chan_content_paned.child_property_resize(m_content_members_paned) = true;
- m_chan_content_paned.child_property_shrink(m_channel_list) = true;
- m_chan_content_paned.child_property_resize(m_channel_list) = true;
+ m_chan_content_paned.child_property_shrink(m_left_pane) = true;
+ m_chan_content_paned.child_property_resize(m_left_pane) = true;
m_chan_content_paned.set_position(200);
m_chan_content_paned.show();
m_content_box.add(m_chan_content_paned);
diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp
index 6183779..5cad82f 100644
--- a/src/windows/mainwindow.hpp
+++ b/src/windows/mainwindow.hpp
@@ -3,6 +3,11 @@
#include "components/chatwindow.hpp"
#include "components/memberlist.hpp"
#include "components/friendslist.hpp"
+#include "components/voiceinfobox.hpp"
+#include <gtkmm/window.h>
+#include <gtkmm/stack.h>
+#include <gtkmm/menubar.h>
+#include <gtkmm/checkmenuitem.h>
class MainWindow : public Gtk::Window {
public:
@@ -52,6 +57,9 @@ private:
ChatWindow m_chat;
MemberList m_members;
FriendsList m_friends;
+ VoiceInfoBox m_voice_info;
+
+ Gtk::Box m_left_pane;
Gtk::Stack m_content_stack;
diff --git a/src/windows/voicesettingswindow.cpp b/src/windows/voicesettingswindow.cpp
new file mode 100644
index 0000000..c009cbf
--- /dev/null
+++ b/src/windows/voicesettingswindow.cpp
@@ -0,0 +1,125 @@
+#ifdef WITH_VOICE
+
+// clang-format off
+
+#include "voicesettingswindow.hpp"
+#include "abaddon.hpp"
+#include "audio/manager.hpp"
+#include <spdlog/spdlog.h>
+
+// clang-format on
+
+VoiceSettingsWindow::VoiceSettingsWindow()
+ : m_main(Gtk::ORIENTATION_VERTICAL) {
+ get_style_context()->add_class("app-window");
+ set_default_size(300, 300);
+
+ m_encoding_mode.append("Voice");
+ m_encoding_mode.append("Music");
+ m_encoding_mode.append("Restricted");
+ m_encoding_mode.set_tooltip_text(
+ "Sets the coding mode for the Opus encoder\n"
+ "Voice - Optimize for voice quality\n"
+ "Music - Optimize for non-voice signals incl. music\n"
+ "Restricted - Optimize for non-voice, low latency. Not recommended");
+
+ const auto mode = Abaddon::Get().GetAudio().GetEncodingApplication();
+ if (mode == OPUS_APPLICATION_VOIP) {
+ m_encoding_mode.set_active(0);
+ } else if (mode == OPUS_APPLICATION_AUDIO) {
+ m_encoding_mode.set_active(1);
+ } else if (mode == OPUS_APPLICATION_RESTRICTED_LOWDELAY) {
+ m_encoding_mode.set_active(2);
+ }
+
+ m_encoding_mode.signal_changed().connect([this]() {
+ const auto mode = m_encoding_mode.get_active_text();
+ auto &audio = Abaddon::Get().GetAudio();
+ spdlog::get("audio")->debug("Chose encoding mode: {}", mode.c_str());
+ if (mode == "Voice") {
+ audio.SetEncodingApplication(OPUS_APPLICATION_VOIP);
+ } else if (mode == "Music") {
+ spdlog::get("audio")->debug("music/audio");
+ audio.SetEncodingApplication(OPUS_APPLICATION_AUDIO);
+ } else if (mode == "Restricted") {
+ audio.SetEncodingApplication(OPUS_APPLICATION_RESTRICTED_LOWDELAY);
+ }
+ });
+
+ m_signal.append("Auto");
+ m_signal.append("Voice");
+ m_signal.append("Music");
+ m_signal.set_tooltip_text(
+ "Signal hint. Tells Opus what the current signal is\n"
+ "Auto - Let Opus figure it out\n"
+ "Voice - Tell Opus it's a voice signal\n"
+ "Music - Tell Opus it's a music signal");
+
+ const auto signal = Abaddon::Get().GetAudio().GetSignalHint();
+ if (signal == OPUS_AUTO) {
+ m_signal.set_active(0);
+ } else if (signal == OPUS_SIGNAL_VOICE) {
+ m_signal.set_active(1);
+ } else if (signal == OPUS_SIGNAL_MUSIC) {
+ m_signal.set_active(2);
+ }
+
+ m_signal.signal_changed().connect([this]() {
+ const auto signal = m_signal.get_active_text();
+ auto &audio = Abaddon::Get().GetAudio();
+ spdlog::get("audio")->debug("Chose signal hint: {}", signal.c_str());
+ if (signal == "Auto") {
+ audio.SetSignalHint(OPUS_AUTO);
+ } else if (signal == "Voice") {
+ audio.SetSignalHint(OPUS_SIGNAL_VOICE);
+ } else if (signal == "Music") {
+ audio.SetSignalHint(OPUS_SIGNAL_MUSIC);
+ }
+ });
+
+ // exponential scale for bitrate because higher bitrates dont sound much different
+ constexpr static auto MAX_BITRATE = 128000.0;
+ constexpr static auto MIN_BITRATE = 2400.0;
+ const auto bitrate_scale = [this](double value) -> double {
+ value /= 100.0;
+ return (MAX_BITRATE - MIN_BITRATE) * value * value * value + MIN_BITRATE;
+ };
+
+ const auto bitrate_scale_r = [this](double value) -> double {
+ return 100.0 * std::cbrt((value - MIN_BITRATE) / (MAX_BITRATE - MIN_BITRATE));
+ };
+
+ m_bitrate.set_range(0.0, 100.0);
+ m_bitrate.set_value_pos(Gtk::POS_TOP);
+ m_bitrate.set_value(bitrate_scale_r(Abaddon::Get().GetAudio().GetBitrate()));
+ m_bitrate.signal_format_value().connect([this, bitrate_scale](double value) {
+ const auto scaled = bitrate_scale(value);
+ if (value <= 99.9) {
+ return Glib::ustring(std::to_string(static_cast<int>(scaled)));
+ } else {
+ return Glib::ustring("MAX");
+ }
+ });
+ m_bitrate.signal_value_changed().connect([this, bitrate_scale]() {
+ const auto value = m_bitrate.get_value();
+ const auto scaled = bitrate_scale(value);
+ if (value <= 99.9) {
+ Abaddon::Get().GetAudio().SetBitrate(static_cast<int>(scaled));
+ } else {
+ Abaddon::Get().GetAudio().SetBitrate(OPUS_BITRATE_MAX);
+ }
+ });
+
+ m_main.add(m_encoding_mode);
+ m_main.add(m_signal);
+ m_main.add(m_bitrate);
+ add(m_main);
+ show_all_children();
+
+ // no need to bring in ManageHeapWindow, no user menu
+ signal_hide().connect([this]() {
+ delete this;
+ });
+}
+
+#endif
diff --git a/src/windows/voicesettingswindow.hpp b/src/windows/voicesettingswindow.hpp
new file mode 100644
index 0000000..cf6b477
--- /dev/null
+++ b/src/windows/voicesettingswindow.hpp
@@ -0,0 +1,25 @@
+#pragma once
+#ifdef WITH_VOICE
+
+// clang-format off
+
+#include <gtkmm/box.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/scale.h>
+#include <gtkmm/window.h>
+
+// clang-format on
+
+class VoiceSettingsWindow : public Gtk::Window {
+public:
+ VoiceSettingsWindow();
+
+ Gtk::Box m_main;
+ Gtk::ComboBoxText m_encoding_mode;
+ Gtk::ComboBoxText m_signal;
+ Gtk::Scale m_bitrate;
+
+private:
+};
+
+#endif
diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp
new file mode 100644
index 0000000..c82a0aa
--- /dev/null
+++ b/src/windows/voicewindow.cpp
@@ -0,0 +1,263 @@
+#ifdef WITH_VOICE
+
+// clang-format off
+
+#include "voicewindow.hpp"
+#include "components/lazyimage.hpp"
+#include "abaddon.hpp"
+#include "audio/manager.hpp"
+#include "voicesettingswindow.hpp"
+// clang-format on
+
+class VoiceWindowUserListEntry : public Gtk::ListBoxRow {
+public:
+ VoiceWindowUserListEntry(Snowflake id)
+ : m_main(Gtk::ORIENTATION_VERTICAL)
+ , m_horz(Gtk::ORIENTATION_HORIZONTAL)
+ , m_avatar(32, 32)
+ , m_mute("Mute") {
+ m_name.set_halign(Gtk::ALIGN_START);
+ m_name.set_hexpand(true);
+ m_mute.set_halign(Gtk::ALIGN_END);
+
+ m_volume.set_range(0.0, 200.0);
+ m_volume.set_value_pos(Gtk::POS_LEFT);
+ m_volume.set_value(100.0);
+ m_volume.signal_value_changed().connect([this]() {
+ m_signal_volume.emit(m_volume.get_value() * 0.01);
+ });
+
+ m_horz.add(m_avatar);
+ m_horz.add(m_name);
+ m_horz.add(m_mute);
+ m_main.add(m_horz);
+ m_main.add(m_volume);
+ m_main.add(m_meter);
+ add(m_main);
+ show_all_children();
+
+ auto &discord = Abaddon::Get().GetDiscordClient();
+ const auto user = discord.GetUser(id);
+ if (user.has_value()) {
+ m_name.set_text(user->Username);
+ m_avatar.SetURL(user->GetAvatarURL("png", "32"));
+ } else {
+ m_name.set_text("Unknown user");
+ }
+
+ m_mute.signal_toggled().connect([this]() {
+ m_signal_mute_cs.emit(m_mute.get_active());
+ });
+ }
+
+ void SetVolumeMeter(double frac) {
+ m_meter.SetVolume(frac);
+ }
+
+ void RestoreGain(double frac) {
+ m_volume.set_value(frac * 100.0);
+ }
+
+private:
+ Gtk::Box m_main;
+ Gtk::Box m_horz;
+ LazyImage m_avatar;
+ Gtk::Label m_name;
+ Gtk::CheckButton m_mute;
+ Gtk::Scale m_volume;
+ VolumeMeter m_meter;
+
+public:
+ using type_signal_mute_cs = sigc::signal<void(bool)>;
+ using type_signal_volume = sigc::signal<void(double)>;
+ type_signal_mute_cs signal_mute_cs() {
+ return m_signal_mute_cs;
+ }
+
+ type_signal_volume signal_volume() {
+ return m_signal_volume;
+ }
+
+private:
+ type_signal_mute_cs m_signal_mute_cs;
+ type_signal_volume m_signal_volume;
+};
+
+VoiceWindow::VoiceWindow(Snowflake channel_id)
+ : m_main(Gtk::ORIENTATION_VERTICAL)
+ , m_controls(Gtk::ORIENTATION_HORIZONTAL)
+ , m_mute("Mute")
+ , m_deafen("Deafen")
+ , m_channel_id(channel_id)
+ , m_menu_view("View")
+ , m_menu_view_settings("More _Settings", true) {
+ get_style_context()->add_class("app-window");
+
+ set_default_size(300, 300);
+
+ auto &discord = Abaddon::Get().GetDiscordClient();
+ auto &audio = Abaddon::Get().GetAudio();
+
+ SetUsers(discord.GetUsersInVoiceChannel(m_channel_id));
+
+ discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &VoiceWindow::OnUserDisconnect));
+ discord.signal_voice_user_connect().connect(sigc::mem_fun(*this, &VoiceWindow::OnUserConnect));
+
+ m_mute.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnMuteChanged));
+ m_deafen.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnDeafenChanged));
+
+ m_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
+ m_scroll.set_hexpand(true);
+ m_scroll.set_vexpand(true);
+
+ m_capture_volume.SetShowTick(true);
+
+ m_capture_gate.set_range(0.0, 100.0);
+ m_capture_gate.set_value_pos(Gtk::POS_LEFT);
+ m_capture_gate.set_value(audio.GetCaptureGate() * 100.0);
+ m_capture_gate.signal_value_changed().connect([this]() {
+ const double val = m_capture_gate.get_value() * 0.01;
+ m_signal_gate.emit(val);
+ m_capture_volume.SetTick(val);
+ });
+
+ m_capture_gain.set_range(0.0, 200.0);
+ m_capture_gain.set_value_pos(Gtk::POS_LEFT);
+ m_capture_gain.set_value(audio.GetCaptureGain() * 100.0);
+ m_capture_gain.signal_value_changed().connect([this]() {
+ const double val = m_capture_gain.get_value();
+ m_signal_gain.emit(val / 100.0);
+ });
+
+ auto *playback_renderer = Gtk::make_managed<Gtk::CellRendererText>();
+ m_playback_combo.set_valign(Gtk::ALIGN_END);
+ m_playback_combo.set_hexpand(true);
+ m_playback_combo.set_halign(Gtk::ALIGN_FILL);
+ m_playback_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetPlaybackDeviceModel());
+ m_playback_combo.set_active(Abaddon::Get().GetAudio().GetDevices().GetActivePlaybackDevice());
+ m_playback_combo.pack_start(*playback_renderer);
+ m_playback_combo.add_attribute(*playback_renderer, "text", 0);
+ m_playback_combo.signal_changed().connect([this]() {
+ Abaddon::Get().GetAudio().SetPlaybackDevice(m_playback_combo.get_active());
+ });
+
+ auto *capture_renderer = Gtk::make_managed<Gtk::CellRendererText>();
+ m_capture_combo.set_valign(Gtk::ALIGN_END);
+ m_capture_combo.set_hexpand(true);
+ m_capture_combo.set_halign(Gtk::ALIGN_FILL);
+ m_capture_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetCaptureDeviceModel());
+ m_capture_combo.set_active(Abaddon::Get().GetAudio().GetDevices().GetActiveCaptureDevice());
+ m_capture_combo.pack_start(*capture_renderer);
+ m_capture_combo.add_attribute(*capture_renderer, "text", 0);
+ m_capture_combo.signal_changed().connect([this]() {
+ Abaddon::Get().GetAudio().SetCaptureDevice(m_capture_combo.get_active());
+ });
+
+ m_menu_bar.append(m_menu_view);
+ m_menu_view.set_submenu(m_menu_view_sub);
+ m_menu_view_sub.append(m_menu_view_settings);
+ m_menu_view_settings.signal_activate().connect([this]() {
+ auto *window = new VoiceSettingsWindow;
+ window->show();
+ });
+
+ m_scroll.add(m_user_list);
+ m_controls.add(m_mute);
+ m_controls.add(m_deafen);
+ m_main.add(m_menu_bar);
+ m_main.add(m_controls);
+ m_main.add(m_capture_volume);
+ m_main.add(m_capture_gate);
+ m_main.add(m_capture_gain);
+ m_main.add(m_scroll);
+ m_main.add(m_playback_combo);
+ m_main.add(m_capture_combo);
+ add(m_main);
+ show_all_children();
+
+ Glib::signal_timeout().connect(sigc::mem_fun(*this, &VoiceWindow::UpdateVoiceMeters), 40);
+}
+
+void VoiceWindow::SetUsers(const std::unordered_set<Snowflake> &user_ids) {
+ const auto me = Abaddon::Get().GetDiscordClient().GetUserData().ID;
+ for (auto id : user_ids) {
+ if (id == me) continue;
+ m_user_list.add(*CreateRow(id));
+ }
+}
+
+Gtk::ListBoxRow *VoiceWindow::CreateRow(Snowflake id) {
+ auto *row = Gtk::make_managed<VoiceWindowUserListEntry>(id);
+ m_rows[id] = row;
+ auto &vc = Abaddon::Get().GetDiscordClient().GetVoiceClient();
+ row->RestoreGain(vc.GetUserVolume(id));
+ row->signal_mute_cs().connect([this, id](bool is_muted) {
+ m_signal_mute_user_cs.emit(id, is_muted);
+ });
+ row->signal_volume().connect([this, id](double volume) {
+ m_signal_user_volume_changed.emit(id, volume);
+ });
+ row->show_all();
+ return row;
+}
+
+void VoiceWindow::OnMuteChanged() {
+ m_signal_mute.emit(m_mute.get_active());
+}
+
+void VoiceWindow::OnDeafenChanged() {
+ m_signal_deafen.emit(m_deafen.get_active());
+}
+
+bool VoiceWindow::UpdateVoiceMeters() {
+ m_capture_volume.SetVolume(Abaddon::Get().GetAudio().GetCaptureVolumeLevel());
+ for (auto [id, row] : m_rows) {
+ const auto ssrc = Abaddon::Get().GetDiscordClient().GetSSRCOfUser(id);
+ if (ssrc.has_value()) {
+ row->SetVolumeMeter(Abaddon::Get().GetAudio().GetSSRCVolumeLevel(*ssrc));
+ }
+ }
+ return true;
+}
+
+void VoiceWindow::OnUserConnect(Snowflake user_id, Snowflake to_channel_id) {
+ if (m_channel_id == to_channel_id) {
+ if (auto it = m_rows.find(user_id); it == m_rows.end()) {
+ m_user_list.add(*CreateRow(user_id));
+ }
+ }
+}
+
+void VoiceWindow::OnUserDisconnect(Snowflake user_id, Snowflake from_channel_id) {
+ if (m_channel_id == from_channel_id) {
+ if (auto it = m_rows.find(user_id); it != m_rows.end()) {
+ delete it->second;
+ m_rows.erase(it);
+ }
+ }
+}
+
+VoiceWindow::type_signal_mute VoiceWindow::signal_mute() {
+ return m_signal_mute;
+}
+
+VoiceWindow::type_signal_deafen VoiceWindow::signal_deafen() {
+ return m_signal_deafen;
+}
+
+VoiceWindow::type_signal_gate VoiceWindow::signal_gate() {
+ return m_signal_gate;
+}
+
+VoiceWindow::type_signal_gate VoiceWindow::signal_gain() {
+ return m_signal_gain;
+}
+
+VoiceWindow::type_signal_mute_user_cs VoiceWindow::signal_mute_user_cs() {
+ return m_signal_mute_user_cs;
+}
+
+VoiceWindow::type_signal_user_volume_changed VoiceWindow::signal_user_volume_changed() {
+ return m_signal_user_volume_changed;
+}
+#endif
diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp
new file mode 100644
index 0000000..805d94a
--- /dev/null
+++ b/src/windows/voicewindow.hpp
@@ -0,0 +1,85 @@
+#pragma once
+#ifdef WITH_VOICE
+// clang-format off
+
+#include "components/volumemeter.hpp"
+#include "discord/snowflake.hpp"
+#include <gtkmm/box.h>
+#include <gtkmm/checkbutton.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/listbox.h>
+#include <gtkmm/menubar.h>
+#include <gtkmm/progressbar.h>
+#include <gtkmm/scale.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/window.h>
+#include <unordered_set>
+// clang-format on
+
+class VoiceWindowUserListEntry;
+class VoiceWindow : public Gtk::Window {
+public:
+ VoiceWindow(Snowflake channel_id);
+
+private:
+ void SetUsers(const std::unordered_set<Snowflake> &user_ids);
+
+ Gtk::ListBoxRow *CreateRow(Snowflake id);
+
+ void OnUserConnect(Snowflake user_id, Snowflake to_channel_id);
+ void OnUserDisconnect(Snowflake user_id, Snowflake from_channel_id);
+
+ void OnMuteChanged();
+ void OnDeafenChanged();
+
+ bool UpdateVoiceMeters();
+
+ Gtk::Box m_main;
+ Gtk::Box m_controls;
+
+ Gtk::CheckButton m_mute;
+ Gtk::CheckButton m_deafen;
+
+ Gtk::ScrolledWindow m_scroll;
+ Gtk::ListBox m_user_list;
+
+ VolumeMeter m_capture_volume;
+ Gtk::Scale m_capture_gate;
+ Gtk::Scale m_capture_gain;
+
+ Gtk::ComboBox m_playback_combo;
+ Gtk::ComboBox m_capture_combo;
+
+ Snowflake m_channel_id;
+
+ std::unordered_map<Snowflake, VoiceWindowUserListEntry *> m_rows;
+
+ Gtk::MenuBar m_menu_bar;
+ Gtk::MenuItem m_menu_view;
+ Gtk::Menu m_menu_view_sub;
+ Gtk::MenuItem m_menu_view_settings;
+
+public:
+ using type_signal_mute = sigc::signal<void(bool)>;
+ using type_signal_deafen = sigc::signal<void(bool)>;
+ using type_signal_gate = sigc::signal<void(double)>;
+ using type_signal_gain = sigc::signal<void(double)>;
+ using type_signal_mute_user_cs = sigc::signal<void(Snowflake, bool)>;
+ using type_signal_user_volume_changed = sigc::signal<void(Snowflake, double)>;
+
+ type_signal_mute signal_mute();
+ type_signal_deafen signal_deafen();
+ type_signal_gate signal_gate();
+ type_signal_gain signal_gain();
+ type_signal_mute_user_cs signal_mute_user_cs();
+ type_signal_user_volume_changed signal_user_volume_changed();
+
+private:
+ type_signal_mute m_signal_mute;
+ type_signal_deafen m_signal_deafen;
+ type_signal_gate m_signal_gate;
+ type_signal_gain m_signal_gain;
+ type_signal_mute_user_cs m_signal_mute_user_cs;
+ type_signal_user_volume_changed m_signal_user_volume_changed;
+};
+#endif