Merge branch 'master' into classic-channels
|
@ -299,6 +299,7 @@ For example, memory_db would be set by adding `memory_db = true` under the line
|
|||
| `save_state` | boolean | true | save the state of the gui (active channels, tabs, expanded channels) |
|
||||
| `alt_menu` | boolean | false | keep the menu hidden unless revealed with alt key |
|
||||
| `hide_to_tray` | boolean | false | hide abaddon to the system tray on window close |
|
||||
| `show_deleted_indicator` | boolean | true | show \[deleted\] indicator next to deleted messages instead of actually deleting the message |
|
||||
| `font_scale` | double | | scale font rendering. 1 is unchanged |
|
||||
|
||||
#### style
|
||||
|
@ -324,7 +325,13 @@ For example, memory_db would be set by adding `memory_db = true` under the line
|
|||
|
||||
| Setting | Type | Default | Description |
|
||||
|---------|--------|------------------------------------|------------------------------------------------------------|
|
||||
| vad | string | rnnoise if enabled, gate otherwise | Method used for voice activity detection. Changeable in UI |
|
||||
| `vad` | string | rnnoise if enabled, gate otherwise | Method used for voice activity detection. Changeable in UI |
|
||||
|
||||
#### windows
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
|---------------|---------|---------|-------------------------|
|
||||
| `hideconsole` | boolean | true | Hide console on startup |
|
||||
|
||||
### Environment variables
|
||||
|
||||
|
|
BIN
res/res/crunchyroll.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
res/res/e1e96d89e192de1997f73730db26e94f.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
res/res/ebay.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
res/res/instagram.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
res/res/paypal.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
res/res/playstation.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
res/res/riotgames.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
res/res/tiktok.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 5.1 KiB |
|
@ -419,13 +419,23 @@ void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) {
|
|||
if (m_opus_buffer == nullptr || !m_should_capture) return;
|
||||
|
||||
const double gain = m_capture_gain;
|
||||
// i have a suspicion i can cast the const away... but i wont
|
||||
|
||||
std::vector<int16_t> new_pcm(pcm, pcm + frames * 2);
|
||||
for (auto &val : new_pcm) {
|
||||
const int32_t unclamped = static_cast<int32_t>(val * gain);
|
||||
val = std::clamp(unclamped, INT16_MIN, INT16_MAX);
|
||||
}
|
||||
|
||||
if (m_mix_mono) {
|
||||
for (size_t i = 0; i < frames * 2; i += 2) {
|
||||
const int sample_L = new_pcm[i];
|
||||
const int sample_R = new_pcm[i + 1];
|
||||
const int16_t mixed = static_cast<int16_t>((sample_L + sample_R) / 2);
|
||||
new_pcm[i] = mixed;
|
||||
new_pcm[i + 1] = mixed;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCaptureVolume(new_pcm.data(), frames);
|
||||
|
||||
static std::array<float, 480> denoised_L;
|
||||
|
@ -629,6 +639,14 @@ bool AudioManager::GetSuppressNoise() const {
|
|||
}
|
||||
#endif
|
||||
|
||||
void AudioManager::SetMixMono(bool value) {
|
||||
m_mix_mono = value;
|
||||
}
|
||||
|
||||
bool AudioManager::GetMixMono() const {
|
||||
return m_mix_mono;
|
||||
}
|
||||
|
||||
AudioManager::type_signal_opus_packet AudioManager::signal_opus_packet() {
|
||||
return m_signal_opus_packet;
|
||||
}
|
||||
|
|
|
@ -87,6 +87,9 @@ public:
|
|||
bool GetSuppressNoise() const;
|
||||
#endif
|
||||
|
||||
void SetMixMono(bool value);
|
||||
bool GetMixMono() const;
|
||||
|
||||
private:
|
||||
void OnCapturedPCM(const int16_t *pcm, ma_uint32 frames);
|
||||
|
||||
|
@ -144,6 +147,7 @@ private:
|
|||
std::atomic<double> m_prob_threshold = 0.5;
|
||||
std::atomic<float> m_vad_prob = 0.0;
|
||||
std::atomic<bool> m_enable_noise_suppression = false;
|
||||
std::atomic<bool> m_mix_mono = false;
|
||||
|
||||
std::unordered_set<uint32_t> m_muted_ssrcs;
|
||||
std::unordered_map<uint32_t, double> m_volume_ssrc;
|
||||
|
|
|
@ -187,8 +187,15 @@ void ChatList::DeleteMessage(Snowflake id) {
|
|||
if (widget == m_id_to_widget.end()) return;
|
||||
|
||||
auto *x = dynamic_cast<ChatMessageItemContainer *>(widget->second);
|
||||
if (x != nullptr)
|
||||
x->UpdateAttributes();
|
||||
|
||||
if (x != nullptr) {
|
||||
if (Abaddon::Get().GetSettings().ShowDeletedIndicator) {
|
||||
x->UpdateAttributes();
|
||||
} else {
|
||||
RemoveMessageAndHeader(x);
|
||||
m_id_to_widget.erase(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatList::RefetchMessage(Snowflake id) {
|
||||
|
|
|
@ -1057,10 +1057,9 @@ void ChatMessageHeader::UpdateName() {
|
|||
const auto chan = discord.GetChannel(ChannelID);
|
||||
bool is_guild = chan.has_value() && chan->GuildID.has_value();
|
||||
if (is_guild) {
|
||||
const auto member = discord.GetMember(UserID, *chan->GuildID);
|
||||
const auto role_id = discord.GetMemberHoistedRole(*chan->GuildID, UserID, true);
|
||||
const auto role = discord.GetRole(role_id);
|
||||
const auto name = GetEscapedDisplayName(*user, member);
|
||||
const auto name = user->GetDisplayNameEscaped(*chan->GuildID);
|
||||
|
||||
std::string md;
|
||||
if (role.has_value())
|
||||
|
@ -1090,13 +1089,6 @@ void ChatMessageHeader::AttachUserMenuHandler(Gtk::Widget &widget) {
|
|||
});
|
||||
}
|
||||
|
||||
Glib::ustring ChatMessageHeader::GetEscapedDisplayName(const UserData &user, const std::optional<GuildMember> &member) {
|
||||
if (member.has_value() && !member->Nickname.empty())
|
||||
return Glib::Markup::escape_text(member->Nickname);
|
||||
else
|
||||
return Glib::Markup::escape_text(user.GetDisplayNameEscaped());
|
||||
}
|
||||
|
||||
bool ChatMessageHeader::on_author_button_press(GdkEventButton *ev) {
|
||||
if (ev->button == GDK_BUTTON_PRIMARY && (ev->state & GDK_SHIFT_MASK)) {
|
||||
m_signal_action_insert_mention.emit();
|
||||
|
|
|
@ -84,7 +84,6 @@ public:
|
|||
|
||||
protected:
|
||||
void AttachUserMenuHandler(Gtk::Widget &widget);
|
||||
static Glib::ustring GetEscapedDisplayName(const UserData &user, const std::optional<GuildMember> &member);
|
||||
|
||||
bool on_author_button_press(GdkEventButton *ev);
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ void SettingsManager::ReadSettings() {
|
|||
SMBOOL("gui", "unreads", Unreads);
|
||||
SMBOOL("gui", "alt_menu", AltMenu);
|
||||
SMBOOL("gui", "hide_to_tray", HideToTray);
|
||||
SMBOOL("gui", "show_deleted_indicator", ShowDeletedIndicator);
|
||||
SMFLT("gui", "font_scale", FontScale);
|
||||
SMINT("http", "concurrent", CacheHTTPConcurrency);
|
||||
SMSTR("http", "user_agent", UserAgent);
|
||||
|
@ -153,6 +154,7 @@ void SettingsManager::Close() {
|
|||
SMBOOL("gui", "unreads", Unreads);
|
||||
SMBOOL("gui", "alt_menu", AltMenu);
|
||||
SMBOOL("gui", "hide_to_tray", HideToTray);
|
||||
SMBOOL("gui", "show_deleted_indicator", ShowDeletedIndicator);
|
||||
SMFLT("gui", "font_scale", FontScale);
|
||||
SMINT("http", "concurrent", CacheHTTPConcurrency);
|
||||
SMSTR("http", "user_agent", UserAgent);
|
||||
|
|
|
@ -30,6 +30,7 @@ public:
|
|||
bool Unreads { true };
|
||||
bool AltMenu { false };
|
||||
bool HideToTray { false };
|
||||
bool ShowDeletedIndicator { true };
|
||||
double FontScale { -1.0 };
|
||||
|
||||
// [http]
|
||||
|
|
|
@ -29,10 +29,22 @@ std::optional<std::pair<std::string, std::string>> ParseCookie(const Glib::ustri
|
|||
}
|
||||
|
||||
std::optional<Glib::ustring> GetJavascriptFileFromAppPage(const Glib::ustring &contents) {
|
||||
auto regex = Glib::Regex::create(R"(app-mount.*(/assets/[\w\d]*.js).*/assets/[\w\d]*.js)");
|
||||
auto regex = Glib::Regex::create(R"(/assets/\w{20}.js)");
|
||||
std::vector<Glib::ustring> matches;
|
||||
|
||||
// regex->match_all doesnt work for some reason
|
||||
int start_position = 0;
|
||||
Glib::MatchInfo match;
|
||||
if (regex->match(contents, match)) {
|
||||
return match.fetch(1);
|
||||
while (regex->match(contents, start_position, match)) {
|
||||
const auto str = match.fetch(0);
|
||||
matches.push_back(str);
|
||||
int foo;
|
||||
match.fetch_pos(0, start_position, foo);
|
||||
start_position += str.size();
|
||||
}
|
||||
|
||||
if (matches.size() >= 6) {
|
||||
return matches[matches.size() - 6];
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -52,7 +64,7 @@ std::optional<uint32_t> GetBuildNumberFromJSURL(const Glib::ustring &url, const
|
|||
auto res = req.execute();
|
||||
if (res.error) return {};
|
||||
|
||||
auto regex = Glib::Regex::create(R"("buildNumber",null!==\(t="(\d+)\"\))");
|
||||
auto regex = Glib::Regex::create("buildNumber:\"(\\d+)\"");
|
||||
Glib::MatchInfo match;
|
||||
Glib::ustring string = res.text;
|
||||
if (regex->match(string, match)) {
|
||||
|
|
|
@ -1,6 +1,34 @@
|
|||
#include "userinfopane.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
static std::string GetConnectionURL(const ConnectionData &conn) {
|
||||
if (conn.Type == "github") {
|
||||
return "https://github.com/" + conn.Name;
|
||||
} else if (conn.Type == "steam") {
|
||||
return "https://steamcommunity.com/profiles/" + conn.ID;
|
||||
} else if (conn.Type == "twitch") {
|
||||
return "https://twitch.tv/" + conn.Name;
|
||||
} else if (conn.Type == "twitter") {
|
||||
return "https://twitter.com/i/user/" + conn.ID;
|
||||
} else if (conn.Type == "spotify") {
|
||||
return "https://open.spotify.com/user/" + conn.ID;
|
||||
} else if (conn.Type == "reddit") {
|
||||
return "https://reddit.com/u/" + conn.Name;
|
||||
} else if (conn.Type == "youtube") {
|
||||
return "https://www.youtube.com/channel/" + conn.ID;
|
||||
} else if (conn.Type == "facebook") {
|
||||
return "https://www.facebook.com/" + conn.ID;
|
||||
} else if (conn.Type == "ebay") {
|
||||
return "https://www.ebay.com/usr/" + conn.Name;
|
||||
} else if (conn.Type == "instagram") {
|
||||
return "https://www.instagram.com/" + conn.Name;
|
||||
} else if (conn.Type == "tiktok") {
|
||||
return "https://www.tiktok.com/@" + conn.Name;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
ConnectionItem::ConnectionItem(const ConnectionData &conn)
|
||||
: m_box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_name(conn.Name) {
|
||||
|
@ -8,23 +36,7 @@ ConnectionItem::ConnectionItem(const ConnectionData &conn)
|
|||
try {
|
||||
pixbuf = Gdk::Pixbuf::create_from_file(Abaddon::GetResPath("/" + conn.Type + ".png"), 32, 32);
|
||||
} catch (const Glib::Exception &e) {}
|
||||
std::string url;
|
||||
if (conn.Type == "github")
|
||||
url = "https://github.com/" + conn.Name;
|
||||
else if (conn.Type == "steam")
|
||||
url = "https://steamcommunity.com/profiles/" + conn.ID;
|
||||
else if (conn.Type == "twitch")
|
||||
url = "https://twitch.tv/" + conn.Name;
|
||||
else if (conn.Type == "twitter")
|
||||
url = "https://twitter.com/i/user/" + conn.ID;
|
||||
else if (conn.Type == "spotify")
|
||||
url = "https://open.spotify.com/user/" + conn.ID;
|
||||
else if (conn.Type == "reddit")
|
||||
url = "https://reddit.com/u/" + conn.Name;
|
||||
else if (conn.Type == "youtube")
|
||||
url = "https://www.youtube.com/channel/" + conn.ID;
|
||||
else if (conn.Type == "facebook")
|
||||
url = "https://www.facebook.com/" + conn.ID;
|
||||
std::string url = GetConnectionURL(conn);
|
||||
if (pixbuf) {
|
||||
m_image = Gtk::manage(new Gtk::Image(pixbuf));
|
||||
m_image->get_style_context()->add_class("profile-connection-image");
|
||||
|
@ -83,25 +95,33 @@ void ConnectionsContainer::SetConnections(const std::vector<ConnectionData> &con
|
|||
|
||||
static const std::unordered_set<std::string> supported_services = {
|
||||
"battlenet",
|
||||
"ebay",
|
||||
"epicgames",
|
||||
"facebook",
|
||||
"github",
|
||||
"instagram",
|
||||
"leagueoflegends",
|
||||
"paypal",
|
||||
"playstation",
|
||||
"reddit",
|
||||
"riotgames",
|
||||
"skype",
|
||||
"spotify",
|
||||
"steam",
|
||||
"tiktok",
|
||||
"twitch",
|
||||
"twitter",
|
||||
"xbox",
|
||||
"youtube",
|
||||
"facebook"
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < connections.size(); i++) {
|
||||
const auto &conn = connections[i];
|
||||
int i = 0;
|
||||
for (const auto &conn : connections) {
|
||||
if (supported_services.find(conn.Type) == supported_services.end()) continue;
|
||||
auto widget = Gtk::manage(new ConnectionItem(conn));
|
||||
widget->show();
|
||||
attach(*widget, static_cast<int>(i % 2), static_cast<int>(i / 2), 1, 1);
|
||||
attach(*widget, i % 2, i / 2, 1, 1);
|
||||
i++;
|
||||
}
|
||||
|
||||
set_halign(Gtk::ALIGN_FILL);
|
||||
|
|
|
@ -89,6 +89,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id)
|
|||
, m_mute("Mute")
|
||||
, m_deafen("Deafen")
|
||||
, m_noise_suppression("Suppress Noise")
|
||||
, m_mix_mono("Mix Mono")
|
||||
, m_channel_id(channel_id)
|
||||
, m_menu_view("View")
|
||||
, m_menu_view_settings("More _Settings", true) {
|
||||
|
@ -178,6 +179,11 @@ VoiceWindow::VoiceWindow(Snowflake channel_id)
|
|||
Abaddon::Get().GetAudio().SetSuppressNoise(m_noise_suppression.get_active());
|
||||
});
|
||||
|
||||
m_mix_mono.set_active(audio.GetMixMono());
|
||||
m_mix_mono.signal_toggled().connect([this]() {
|
||||
Abaddon::Get().GetAudio().SetMixMono(m_mix_mono.get_active());
|
||||
});
|
||||
|
||||
auto *playback_renderer = Gtk::make_managed<Gtk::CellRendererText>();
|
||||
m_playback_combo.set_valign(Gtk::ALIGN_END);
|
||||
m_playback_combo.set_hexpand(true);
|
||||
|
@ -223,6 +229,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id)
|
|||
m_controls.add(m_mute);
|
||||
m_controls.add(m_deafen);
|
||||
m_controls.add(m_noise_suppression);
|
||||
m_controls.add(m_mix_mono);
|
||||
m_main.add(m_menu_bar);
|
||||
m_main.add(m_controls);
|
||||
m_main.add(m_vad_value);
|
||||
|
|
|
@ -54,6 +54,7 @@ private:
|
|||
Gtk::Scale m_capture_gain;
|
||||
|
||||
Gtk::CheckButton m_noise_suppression;
|
||||
Gtk::CheckButton m_mix_mono;
|
||||
|
||||
Gtk::ComboBoxText m_vad_combo;
|
||||
Gtk::ComboBox m_playback_combo;
|
||||
|
|