diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 389a3becf..54c8c4f4b 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -54,7 +54,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:
 // This must be in alphabetical order according to action name as it must have the same order as
 // UISetting::values.shortcuts, which is alphabetically ordered.
 // clang-format off
-const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{
+const std::array<UISettings::Shortcut, 35> Config::default_hotkeys {{
      {QStringLiteral("Advance Frame"),            QStringLiteral("Main Window"), {QStringLiteral(""),       Qt::ApplicationShortcut}},
      {QStringLiteral("Audio Mute/Unmute"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
      {QStringLiteral("Audio Volume Down"),        QStringLiteral("Main Window"), {QStringLiteral(""),       Qt::WindowShortcut}},
@@ -71,6 +71,11 @@ const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{
      {QStringLiteral("Load Amiibo"),              QStringLiteral("Main Window"), {QStringLiteral("F2"),     Qt::WidgetWithChildrenShortcut}},
      {QStringLiteral("Load File"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
      {QStringLiteral("Load from Newest Slot"),    QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}},
+     {QStringLiteral("Multiplayer Browse Public Game Lobby"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), Qt::ApplicationShortcut}},
+     {QStringLiteral("Multiplayer Create Room"),              QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), Qt::ApplicationShortcut}},
+     {QStringLiteral("Multiplayer Direct Connect to Room"),   QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), Qt::ApplicationShortcut}},
+     {QStringLiteral("Multiplayer Leave Room"),               QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), Qt::ApplicationShortcut}},
+     {QStringLiteral("Multiplayer Show Current Room"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), Qt::ApplicationShortcut}},
      {QStringLiteral("Remove Amiibo"),            QStringLiteral("Main Window"), {QStringLiteral("F3"),     Qt::ApplicationShortcut}},
      {QStringLiteral("Restart Emulation"),        QStringLiteral("Main Window"), {QStringLiteral("F6"),     Qt::WindowShortcut}},
      {QStringLiteral("Rotate Screens Upright"),   QStringLiteral("Main Window"), {QStringLiteral("F8"),     Qt::WindowShortcut}},
@@ -557,6 +562,15 @@ void Config::ReadMultiplayerValues() {
     UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong();
     UISettings::values.room_description =
         ReadSetting(QStringLiteral("room_description"), QString{}).toString();
+    UISettings::values.multiplayer_filter_text =
+        ReadSetting(QStringLiteral("multiplayer_filter_text"), QString{}).toString();
+    UISettings::values.multiplayer_filter_games_owned =
+        ReadSetting(QStringLiteral("multiplayer_filter_games_owned"), false).toBool();
+    UISettings::values.multiplayer_filter_hide_empty =
+        ReadSetting(QStringLiteral("multiplayer_filter_hide_empty"), false).toBool();
+    UISettings::values.multiplayer_filter_hide_full =
+        ReadSetting(QStringLiteral("multiplayer_filter_hide_full"), false).toBool();
+
     // Read ban list back
     int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
     UISettings::values.ban_list.first.resize(size);
@@ -1074,6 +1088,15 @@ void Config::SaveMultiplayerValues() {
     WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0);
     WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description,
                  QString{});
+    WriteSetting(QStringLiteral("multiplayer_filter_text"),
+                 UISettings::values.multiplayer_filter_text, QString{});
+    WriteSetting(QStringLiteral("multiplayer_filter_games_owned"),
+                 UISettings::values.multiplayer_filter_games_owned, false);
+    WriteSetting(QStringLiteral("multiplayer_filter_hide_empty"),
+                 UISettings::values.multiplayer_filter_hide_empty, false);
+    WriteSetting(QStringLiteral("multiplayer_filter_hide_full"),
+                 UISettings::values.multiplayer_filter_hide_full, false);
+
     // Write ban list
     qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
     for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) {
diff --git a/src/citra_qt/configuration/config.h b/src/citra_qt/configuration/config.h
index 27be93711..521c6baf9 100644
--- a/src/citra_qt/configuration/config.h
+++ b/src/citra_qt/configuration/config.h
@@ -26,7 +26,7 @@ public:
 
     static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
     static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
-    static const std::array<UISettings::Shortcut, 30> default_hotkeys;
+    static const std::array<UISettings::Shortcut, 35> default_hotkeys;
 
 private:
     void Initialize(const std::string& config_name);
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 807da30e3..4f179a35c 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -647,6 +647,13 @@ void GMainWindow::InitializeHotkeys() {
     link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame"));
     link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot"));
     link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot"));
+    link_action_shortcut(ui->action_View_Lobby,
+                         QStringLiteral("Multiplayer Browse Public Game Lobby"));
+    link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room"));
+    link_action_shortcut(ui->action_Connect_To_Room,
+                         QStringLiteral("Multiplayer Direct Connect to Room"));
+    link_action_shortcut(ui->action_Show_Room, QStringLiteral("Multiplayer Show Current Room"));
+    link_action_shortcut(ui->action_Leave_Room, QStringLiteral("Multiplayer Leave Room"));
 
     const auto add_secondary_window_hotkey = [this](QKeySequence hotkey, const char* slot) {
         // This action will fire specifically when secondary_window is in focus
diff --git a/src/citra_qt/multiplayer/direct_connect.cpp b/src/citra_qt/multiplayer/direct_connect.cpp
index 936fd0435..bdc866a75 100644
--- a/src/citra_qt/multiplayer/direct_connect.cpp
+++ b/src/citra_qt/multiplayer/direct_connect.cpp
@@ -80,9 +80,8 @@ void DirectConnectWindow::Connect() {
     // Store settings
     UISettings::values.nickname = ui->nickname->text();
     UISettings::values.ip = ui->ip->text();
-    UISettings::values.port = (ui->port->isModified() && !ui->port->text().isEmpty())
-                                  ? ui->port->text()
-                                  : UISettings::values.port;
+    UISettings::values.port =
+        !ui->port->text().isEmpty() ? ui->port->text() : UISettings::values.port;
 
     // attempt to connect in a different thread
     QFuture<void> f = QtConcurrent::run([&] {
diff --git a/src/citra_qt/multiplayer/lobby.cpp b/src/citra_qt/multiplayer/lobby.cpp
index 7cd2ae7e1..00671b842 100644
--- a/src/citra_qt/multiplayer/lobby.cpp
+++ b/src/citra_qt/multiplayer/lobby.cpp
@@ -63,10 +63,10 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
 
     // UI Buttons
     connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby);
+    connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
     connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned);
     connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty);
     connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull);
-    connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
     connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom);
     connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom);
 
@@ -74,6 +74,12 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
     connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
             &Lobby::OnRefreshLobby);
 
+    // Load persistent filters after events are connected to make sure they apply
+    ui->search->setText(UISettings::values.multiplayer_filter_text);
+    ui->games_owned->setChecked(UISettings::values.multiplayer_filter_games_owned);
+    ui->hide_empty->setChecked(UISettings::values.multiplayer_filter_hide_empty);
+    ui->hide_full->setChecked(UISettings::values.multiplayer_filter_hide_full);
+
     // manually start a refresh when the window is opening
     // TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as
     // part of the constructor, but offload the refresh until after the window shown. perhaps emit a
@@ -180,6 +186,10 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
     UISettings::values.nickname = ui->nickname->text();
     UISettings::values.ip = proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
     UISettings::values.port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toString();
+    UISettings::values.multiplayer_filter_text = ui->search->text();
+    UISettings::values.multiplayer_filter_games_owned = ui->games_owned->isChecked();
+    UISettings::values.multiplayer_filter_hide_empty = ui->hide_empty->isChecked();
+    UISettings::values.multiplayer_filter_hide_full = ui->hide_full->isChecked();
 }
 
 void Lobby::ResetModel() {
diff --git a/src/citra_qt/multiplayer/lobby_p.h b/src/citra_qt/multiplayer/lobby_p.h
index 16db78eae..f1890fdef 100644
--- a/src/citra_qt/multiplayer/lobby_p.h
+++ b/src/citra_qt/multiplayer/lobby_p.h
@@ -188,12 +188,37 @@ public:
     }
 
     QVariant data(int role) const override {
-        if (role != Qt::DisplayRole) {
+        switch (role) {
+        case Qt::DisplayRole: {
+            auto members = data(MemberListRole).toList();
+            return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
+                                                 data(MaxPlayerRole).toString());
+        }
+        case Qt::ForegroundRole: {
+            auto members = data(MemberListRole).toList();
+            auto max_players = data(MaxPlayerRole).toInt();
+            const QColor room_full_color(255, 48, 32);
+            const QColor room_almost_full_color(255, 140, 32);
+            const QColor room_has_players_color(32, 160, 32);
+            const QColor room_empty_color(128, 128, 128);
+
+            if (members.size() >= max_players) {
+                return QBrush(room_full_color);
+            } else if (members.size() == (max_players - 1)) {
+                return QBrush(room_almost_full_color);
+            } else if (members.size() == 0) {
+                return QBrush(room_empty_color);
+            } else if (members.size() > 0 && members.size() < (max_players - 1)) {
+                return QBrush(room_has_players_color);
+            }
+
+            // FIXME: How to return a value that tells Qt not to modify the
+            // text color from the default (as if Qt::ForegroundRole wasn't overridden)?
+            return QBrush(nullptr);
+        }
+        default:
             return LobbyItem::data(role);
         }
-        auto members = data(MemberListRole).toList();
-        return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
-                                             data(MaxPlayerRole).toString());
     }
 
     bool operator<(const QStandardItem& other) const override {
diff --git a/src/citra_qt/uisettings.h b/src/citra_qt/uisettings.h
index 8e671f49f..6dedff0f0 100644
--- a/src/citra_qt/uisettings.h
+++ b/src/citra_qt/uisettings.h
@@ -138,6 +138,11 @@ struct Values {
     QString room_description;
     std::pair<std::vector<std::string>, std::vector<std::string>> ban_list;
 
+    QString multiplayer_filter_text;
+    bool multiplayer_filter_games_owned;
+    bool multiplayer_filter_hide_empty;
+    bool multiplayer_filter_hide_full;
+
     // logging
     Settings::Setting<bool> show_console{false, "showConsole"};
 };