@ -9,7 +9,9 @@
# include <fstream>
# include <iostream>
# include <memory>
# include <QStyleFactory>
# include <thread>
# include "core/hle/service/am/applet_manager.h"
# include "core/loader/nca.h"
# include "core/loader/nro.h"
@ -20,6 +22,9 @@
# endif
# ifdef __unix__
# include <csignal>
# include <QtDBus/QDBusInterface>
# include <QtDBus/QDBusMessage>
# include <QtDBus/QtDBus>
# include <sys/socket.h>
# include "common/linux/gamemode.h"
# endif
@ -271,18 +276,6 @@ static void OverrideWindowsFont() {
}
# endif
bool GMainWindow : : CheckDarkMode ( ) {
# ifdef __unix__
const QPalette test_palette ( qApp - > palette ( ) ) ;
const QColor text_color = test_palette . color ( QPalette : : Active , QPalette : : Text ) ;
const QColor window_color = test_palette . color ( QPalette : : Active , QPalette : : Window ) ;
return ( text_color . value ( ) > window_color . value ( ) ) ;
# else
// TODO: Windows
return false ;
# endif // __unix__
}
GMainWindow : : GMainWindow ( std : : unique_ptr < QtConfig > config_ , bool has_broken_vulkan )
: ui { std : : make_unique < Ui : : MainWindow > ( ) } , system { std : : make_unique < Core : : System > ( ) } ,
input_subsystem { std : : make_shared < InputCommon : : InputSubsystem > ( ) } , config { std : : move ( config_ ) } ,
@ -303,8 +296,6 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
ui - > setupUi ( this ) ;
statusBar ( ) - > hide ( ) ;
// Check dark mode before a theme is loaded
os_dark_mode = CheckDarkMode ( ) ;
startup_icon_theme = QIcon : : themeName ( ) ;
// fallback can only be set once, colorful theme icons are okay on both light/dark
QIcon : : setFallbackThemeName ( QStringLiteral ( " colorful " ) ) ;
@ -329,6 +320,7 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
SetDefaultUIGeometry ( ) ;
RestoreUIState ( ) ;
UpdateUITheme ( ) ;
ConnectMenuEvents ( ) ;
ConnectWidgetEvents ( ) ;
@ -449,7 +441,10 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
SDL_EnableScreenSaver ( ) ;
# endif
# ifdef __unix__
SetupPrepareForSleep ( ) ;
ListenColorSchemeChange ( ) ;
# endif
QStringList args = QApplication : : arguments ( ) ;
@ -1647,8 +1642,8 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
}
}
void GMainWindow : : SetupPrepareForSleep ( ) {
# ifdef __unix__
void GMainWindow : : SetupPrepareForSleep ( ) {
auto bus = QDBusConnection : : systemBus ( ) ;
if ( bus . isConnected ( ) ) {
const bool success = bus . connect (
@ -1662,8 +1657,8 @@ void GMainWindow::SetupPrepareForSleep() {
} else {
LOG_WARNING ( Frontend , " QDBusConnection system bus is not connected " ) ;
}
# endif // __unix__
}
# endif // __unix__
void GMainWindow : : OnPrepareForSleep ( bool prepare_sleep ) {
if ( emu_thread = = nullptr ) {
@ -4799,9 +4794,106 @@ void GMainWindow::filterBarSetChecked(bool state) {
emit ( OnToggleFilterBar ( ) ) ;
}
void GMainWindow : : UpdateUITheme ( ) {
LOG_DEBUG ( Frontend , " Updating UI " ) ;
QString default_theme = QString : : fromStdString ( UISettings : : default_theme . data ( ) ) ;
QString current_theme = UISettings : : values . theme ;
if ( current_theme . isEmpty ( ) ) {
current_theme = default_theme ;
}
const bool current_dark_mode = CheckDarkMode ( ) ;
UpdateIcons ( current_theme ) ;
/* Find the stylesheet to load */
if ( TryLoadStylesheet ( current_theme ) ) {
return ;
}
// Reading new theme failed, loading default stylesheet
LOG_ERROR ( Frontend , " Unable to open style \" {} \" , fallback to the default theme " ,
current_theme . toStdString ( ) ) ;
if ( TryLoadStylesheet ( QStringLiteral ( " :/%1 " ) . arg ( default_theme ) ) ) {
return ;
}
// Reading default failed, loading empty stylesheet
LOG_ERROR ( Frontend , " Unable to set default style, stylesheet file not found " ) ;
qApp - > setStyleSheet ( { } ) ;
setStyleSheet ( { } ) ;
}
void GMainWindow : : UpdateIcons ( const QString & theme_path ) {
// Get the theme directory from its path
std : : size_t last_slash = theme_path . toStdString ( ) . find_last_of ( " / " ) ;
QString theme_dir = QString : : fromStdString ( theme_path . toStdString ( ) . substr ( last_slash + 1 ) ) ;
// Append _dark to the theme name to use dark variant icons
if ( CheckDarkMode ( ) ) {
LOG_DEBUG ( Frontend , " Using icons from: {} " , theme_dir . toStdString ( ) + " _dark " ) ;
QIcon : : setThemeName ( theme_dir + QStringLiteral ( " _dark " ) ) ;
} else {
LOG_DEBUG ( Frontend , " Using icons from: {} " , theme_dir . toStdString ( ) ) ;
QIcon : : setThemeName ( theme_dir ) ;
}
const QString theme_directory {
QString : : fromStdString ( Common : : FS : : GetSuyuPathString ( Common : : FS : : SuyuPath : : ThemesDir ) ) } ;
// Set path for default icons
// Use icon resources from application binary and current theme local subdirectory, if it exists
QStringList theme_paths ;
theme_paths < < QString : : fromStdString ( " :/icons " ) < < QStringLiteral ( " %1 " ) . arg ( theme_directory ) ;
QIcon : : setThemeSearchPaths ( theme_paths ) ;
// Change current directory, to allow user themes to add their own icons
QDir : : setCurrent ( QStringLiteral ( " %1/%2 " ) . arg ( theme_directory , UISettings : : values . theme ) ) ;
emit UpdateThemedIcons ( ) ;
}
bool GMainWindow : : TryLoadStylesheet ( const QString & theme_uri ) {
QString style_path ;
// Use themed stylesheet if it exists
if ( CheckDarkMode ( ) ) {
style_path = theme_uri + QStringLiteral ( " /dark.qss " ) ;
} else {
style_path = theme_uri + QStringLiteral ( " /light.qss " ) ;
}
if ( ! QFile : : exists ( style_path ) ) {
LOG_INFO ( Frontend , " Themed (light/dark) stylesheet could not be found, using default one " ) ;
// Use common stylesheet if themed one does not exist
style_path = theme_uri + QStringLiteral ( " /style.qss " ) ;
}
// Loading stylesheet
QFile style_file ( style_path ) ;
if ( style_file . open ( QFile : : ReadOnly | QFile : : Text ) ) {
// Update the color palette before applying the stylesheet
UpdateThemePalette ( ) ;
LOG_INFO ( Frontend , " Loading stylesheet in: {} " , theme_uri . toStdString ( ) ) ;
QTextStream ts_theme ( & style_file ) ;
qApp - > setStyleSheet ( ts_theme . readAll ( ) ) ;
setStyleSheet ( ts_theme . readAll ( ) ) ;
SetCustomStylesheet ( ) ;
return true ;
}
// Opening the file failed
return false ;
}
bool GMainWindow : : TryLoadStylesheet ( const std : : filesystem : : path & theme_path ) {
return TryLoadStylesheet ( QString : : fromStdString ( theme_path . string ( ) + " / " ) ) ;
}
static void AdjustLinkColor ( ) {
QPalette new_pal ( qApp - > palette ( ) ) ;
if ( UISettings : : IsDarkTheme ( ) ) {
if ( GMainWindow: : CheckDarkMod e( ) ) {
new_pal . setColor ( QPalette : : Link , QColor ( 0 , 190 , 255 , 255 ) ) ;
} else {
new_pal . setColor ( QPalette : : Link , QColor ( 0 , 140 , 200 , 255 ) ) ;
@ -4811,77 +4903,201 @@ static void AdjustLinkColor() {
}
}
void GMainWindow : : UpdateUITheme ( ) {
QString default_theme = QString : : fromStdString ( UISettings : : default_theme . data ( ) ) ;
QString current_theme = UISettings : : values . theme ;
if ( current_theme . isEmpty ( ) ) {
current_theme = default_theme ;
}
void GMainWindow : : UpdateThemePalette ( ) {
QPalette themePalette ( qApp - > palette ( ) ) ;
# ifdef _WIN32
QIcon : : setThemeName ( current_theme ) ;
AdjustLinkColor ( ) ;
# else
if ( current_theme = = QStringLiteral ( " default " ) | | current_theme = = QStringLiteral ( " colorful " ) ) {
LOG_INFO ( Frontend , " Theme is default or colorful: {} " , current_theme . toStdString ( ) ) ;
QIcon : : setThemeName ( current_theme = = QStringLiteral ( " colorful " ) ? current_theme
: startup_icon_theme ) ;
QIcon : : setThemeSearchPaths ( QStringList ( default_theme_paths ) ) ;
QColor dark ( 25 , 25 , 25 ) ;
QColor darkGray ( 100 , 100 , 100 ) ;
QColor gray ( 150 , 150 , 150 ) ;
QColor light ( 230 , 230 , 230 ) ;
// By default, revert fusion style set for Windows dark theme
QString style ;
if ( CheckDarkMode ( ) ) {
current_theme = QStringLiteral ( " default_dark " ) ;
// AlternateBase is kept at rgb(233, 231, 227) or rgb(245, 245, 245) on Windows dark
// palette, fix this. Sometimes, it even is rgb(0, 0, 0), but uses a very light gray for
// alternate rows, do not know why
if ( themePalette . alternateBase ( ) . color ( ) = = QColor ( 233 , 231 , 227 ) | |
themePalette . alternateBase ( ) . color ( ) = = QColor ( 245 , 245 , 245 ) | |
themePalette . alternateBase ( ) . color ( ) = = QColor ( 0 , 0 , 0 ) ) {
themePalette . setColor ( QPalette : : AlternateBase , dark ) ;
alternate_base_modified = true ;
}
// Use fusion theme, since its close to windowsvista, but works well with a dark palette
style = QStringLiteral ( " fusion " ) ;
} else {
LOG_INFO ( Frontend , " Theme is NOT default or colorful: {} " , current_theme . toStdString ( ) ) ;
QIcon : : setThemeName ( current_theme ) ;
// Use icon resources from application binary and current theme subdirectory if it exists
QStringList theme_paths ;
theme_paths < < QString : : fromStdString ( " :/icons " )
< < QStringLiteral ( " %1/%2/icons " )
. arg ( QString : : fromStdString (
Common : : FS : : GetYuzuPathString ( Common : : FS : : YuzuPath : : ThemesDir ) ) ,
current_theme ) ;
QIcon : : setThemeSearchPaths ( theme_paths ) ;
AdjustLinkColor ( ) ;
// Reset AlternateBase if it has been modified
if ( alternate_base_modified ) {
themePalette . setColor ( QPalette : : AlternateBase , QColor ( 245 , 245 , 245 ) ) ;
alternate_base_modified = false ;
}
// Reset Windows theme to the default
style = QStringLiteral ( " windowsvista " ) ;
}
LOG_DEBUG ( Frontend , " Using style: {} " , style . toStdString ( ) ) ;
qApp - > setStyle ( style ) ;
# else
if ( CheckDarkMode ( ) ) {
// Set Dark palette on non Windows platforms (that may not have a dark palette)
LOG_INFO ( Frontend , " Using custom dark palette " ) ;
themePalette . setColor ( QPalette : : Window , QColor ( 53 , 53 , 53 ) ) ;
themePalette . setColor ( QPalette : : WindowText , Qt : : white ) ;
themePalette . setColor ( QPalette : : Disabled , QPalette : : WindowText , QColor ( 127 , 127 , 127 ) ) ;
themePalette . setColor ( QPalette : : Base , QColor ( 42 , 42 , 42 ) ) ;
themePalette . setColor ( QPalette : : AlternateBase , QColor ( 66 , 66 , 66 ) ) ;
themePalette . setColor ( QPalette : : ToolTipBase , Qt : : white ) ;
themePalette . setColor ( QPalette : : ToolTipText , QColor ( 53 , 53 , 53 ) ) ;
themePalette . setColor ( QPalette : : Text , Qt : : white ) ;
themePalette . setColor ( QPalette : : Disabled , QPalette : : Text , QColor ( 127 , 127 , 127 ) ) ;
themePalette . setColor ( QPalette : : Dark , QColor ( 35 , 35 , 35 ) ) ;
themePalette . setColor ( QPalette : : Shadow , QColor ( 20 , 20 , 20 ) ) ;
themePalette . setColor ( QPalette : : Button , QColor ( 53 , 53 , 53 ) ) ;
themePalette . setColor ( QPalette : : ButtonText , Qt : : white ) ;
themePalette . setColor ( QPalette : : Disabled , QPalette : : ButtonText , QColor ( 127 , 127 , 127 ) ) ;
themePalette . setColor ( QPalette : : BrightText , Qt : : red ) ;
themePalette . setColor ( QPalette : : Link , QColor ( 42 , 130 , 218 ) ) ;
themePalette . setColor ( QPalette : : Highlight , QColor ( 42 , 130 , 218 ) ) ;
themePalette . setColor ( QPalette : : Disabled , QPalette : : Highlight , QColor ( 80 , 80 , 80 ) ) ;
themePalette . setColor ( QPalette : : HighlightedText , Qt : : white ) ;
themePalette . setColor ( QPalette : : Disabled , QPalette : : HighlightedText , QColor ( 127 , 127 , 127 ) ) ;
} else {
LOG_INFO ( Frontend , " Using standard palette " ) ;
// Reset light palette on non Windows platforms
themePalette = this - > style ( ) - > standardPalette ( ) ;
}
# endif
if ( current_theme ! = default_theme ) {
QString theme_uri { current_theme + QStringLiteral ( " style.qss " ) } ;
if ( tryLoadStylesheet ( theme_uri ) ) {
return ;
qApp - > setPalette ( themePalette ) ;
AdjustLinkColor ( ) ;
}
// Reading new theme failed, loading default stylesheet
LOG_ERROR ( Frontend , " Unable to open style \" {} \" , fallback to the default theme " ,
current_theme . toStdString ( ) ) ;
void GMainWindow : : SetCustomStylesheet ( ) {
setStyleSheet ( QStringLiteral ( " QStatusBar::item { border: none; } " ) ) ;
current_theme = default_theme ;
theme_uri = QStringLiteral ( " :%1/style.qss " ) . arg ( default_theme ) ;
if ( tryLoadStylesheet ( theme_uri ) ) {
return ;
// Set "dark" qss property value, that may be used in stylesheets
bool is_dark_mode = CheckDarkMode ( ) ;
if ( renderer_status_button ) {
renderer_status_button - > setProperty ( " dark " , is_dark_mode ) ;
}
if ( gpu_accuracy_button ) {
gpu_accuracy_button - > setProperty ( " dark " , is_dark_mode ) ;
}
# ifdef _WIN32
// Windows dark mode uses "fusion" style. Make it look like more "windowsvista" light style
if ( is_dark_mode ) {
/* the groove expands to the size of the slider by default. by giving it a height, it has a
fixed size */
/* handle is placed by default on the contents rect of the groove. Negative margin expands
it outside the groove */
setStyleSheet ( QStringLiteral ( " QSlider:horizontal{ height:30px; } \
QSlider : : sub - page : horizontal { background - color : palette ( highlight ) ; } \
QSlider : : add - page : horizontal { background - color : palette ( midlight ) ; } \
QSlider : : groove : horizontal { border - width : 1 px ; margin : 1 px 0 ; height : 2 px ; } \
QSlider : : handle : horizontal { border - width : 1 px ; border - style : solid ; border - color : palette ( dark ) ; \
width : 10 px ; margin : - 10 px 0 px ; } \
QSlider : : handle { background - color : palette ( button ) ; } \
QSlider : : handle : hover { background - color : palette ( highlight ) ; } " ));
}
# endif
}
// Reading default failed, loading empty stylesheet
LOG_ERROR ( Frontend , " Unable to set style \" {} \" , stylesheet file not found " ,
current_theme . toStdString ( ) ) ;
# ifdef __unix__
bool GMainWindow : : ListenColorSchemeChange ( ) {
auto bus = QDBusConnection : : sessionBus ( ) ;
if ( bus . isConnected ( ) ) {
const QString dbus_service = QStringLiteral ( " org.freedesktop.portal.Desktop " ) ;
const QString dbus_path = QStringLiteral ( " /org/freedesktop/portal/desktop " ) ;
const QString dbus_interface = QStringLiteral ( " org.freedesktop.portal.Settings " ) ;
const QString dbus_method = QStringLiteral ( " SettingChanged " ) ;
QStringList dbus_arguments ;
dbus_arguments < < QStringLiteral ( " org.freedesktop.appearance " )
< < QStringLiteral ( " color-scheme " ) ;
const QString dbus_signature = QStringLiteral ( " ssv " ) ;
qApp - > setStyleSheet ( { } ) ;
setStyleSheet ( { } ) ;
LOG_INFO ( Frontend , " Connected to DBus, listening for OS theme changes " ) ;
return bus . connect ( dbus_service , dbus_path , dbus_interface , dbus_method , dbus_arguments ,
dbus_signature , this , SLOT ( UpdateUITheme ( ) ) ) ;
}
}
bool GMainWindow : : tryLoadStylesheet ( const QString & theme_path ) {
QFile theme_file ( theme_path ) ;
if ( theme_file . open ( QFile : : ReadOnly | QFile : : Text ) ) {
LOG_INFO ( Frontend , " Loading style in: {} " , theme_path . toStdString ( ) ) ;
QTextStream ts ( & theme_file ) ;
qApp - > setStyleSheet ( ts . readAll ( ) ) ;
setStyleSheet ( ts . readAll ( ) ) ;
return true ;
}
// Opening the file failed
LOG_WARNING ( Frontend , " Unable to connect to DBus to listen for OS theme changes " ) ;
return false ;
}
# endif
bool GMainWindow : : CheckDarkMode ( ) {
const QPalette current_palette ( qApp - > palette ( ) ) ;
# ifdef __unix__
QProcess process ;
QStringList gdbus_arguments ;
// Using the freedesktop specifications for checking dark mode
LOG_INFO ( Frontend , " Retrieving theme from freedesktop color-scheme... " ) ;
gdbus_arguments < < QStringLiteral ( " --dest=org.freedesktop.portal.Desktop " )
< < QStringLiteral ( " --object-path /org/freedesktop/portal/desktop " )
< < QStringLiteral ( " --method org.freedesktop.portal.Settings.Read "
" org.freedesktop.appearance color-scheme " ) ;
process . start ( QStringLiteral ( " gdbus call --session " ) , gdbus_arguments ) ;
process . waitForFinished ( 1000 ) ;
QByteArray dbus_output = process . readAllStandardOutput ( ) ;
if ( ! dbus_output . isEmpty ( ) ) {
const int systemColorSchema = QString : : fromUtf8 ( dbus_output ) . trimmed ( ) . right ( 1 ) . toInt ( ) ;
return systemColorSchema = = 1 ;
}
// Try alternative for Gnome if the previous one failed
QStringList gsettings_arguments ;
gsettings_arguments < < QStringLiteral ( " get " )
< < QStringLiteral ( " org.gnome.desktop.interface " )
< < QStringLiteral ( " color-scheme " ) ;
LOG_DEBUG ( Frontend , " failed, retrieving theme from gsettings color-scheme... " ) ;
process . start ( QStringLiteral ( " gsettings " ) , gsettings_arguments ) ;
process . waitForFinished ( 1000 ) ;
QByteArray gsettings_output = process . readAllStandardOutput ( ) ;
// Try older gtk-theme method if the previous one failed
if ( gsettings_output . isEmpty ( ) ) {
LOG_INFO ( Frontend , " failed, retrieving theme from gtk-theme... " ) ;
gsettings_arguments . takeLast ( ) ;
gsettings_arguments < < QStringLiteral ( " gtk-theme " ) ;
process . start ( QStringLiteral ( " gsettings " ) , gsettings_arguments ) ;
process . waitForFinished ( 1000 ) ;
gsettings_output = process . readAllStandardOutput ( ) ;
}
// Interpret gsettings value if it succeeded
if ( ! gsettings_output . isEmpty ( ) ) {
QString systeme_theme = QString : : fromUtf8 ( gsettings_output ) ;
LOG_DEBUG ( Frontend , " Gsettings output: {} " , systeme_theme . toStdString ( ) ) ;
return systeme_theme . contains ( QStringLiteral ( " dark " ) , Qt : : CaseInsensitive ) ;
}
LOG_DEBUG ( Frontend , " failed, retrieving theme from palette " ) ;
# endif
// Use default method based on palette swap by OS.
// It is the only method on Windows with Qt 5.
// Windows needs QT_QPA_PLATFORM env variable set to windows:darkmode=2 to force palette change
return ( current_palette . color ( QPalette : : WindowText ) . lightness ( ) >
current_palette . color ( QPalette : : Window ) . lightness ( ) ) ;
}
void GMainWindow : : changeEvent ( QEvent * event ) {
// PaletteChange event appears to only reach so far into the GUI, explicitly asking to
// UpdateUITheme is a decent work around
if ( event - > type ( ) = = QEvent : : PaletteChange | |
event - > type ( ) = = QEvent : : ApplicationPaletteChange ) {
LOG_INFO ( Frontend ,
" Window color palette changed by event: {} (QEvent::PaletteChange is: {}) " ,
event - > type ( ) , QEvent : : PaletteChange ) ;
const QPalette test_palette ( qApp - > palette ( ) ) ;
// Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too
const QColor window_color = test_palette . color ( QPalette : : Active , QPalette : : Window ) ;
if ( last_window_color ! = window_color ) {
last_window_color = window_color ;
UpdateUITheme ( ) ;
}
} else QWidget : : changeEvent ( event ) ;
}
void GMainWindow : : LoadTranslation ( ) {
bool loaded ;
@ -4935,26 +5151,6 @@ void GMainWindow::SetGamemodeEnabled(bool state) {
}
# endif
void GMainWindow : : changeEvent ( QEvent * event ) {
# ifdef __unix__
// PaletteChange event appears to only reach so far into the GUI, explicitly asking to
// UpdateUITheme is a decent work around
if ( event - > type ( ) = = QEvent : : PaletteChange ) {
const QPalette test_palette ( qApp - > palette ( ) ) ;
const QString & current_theme = UISettings : : values . theme ;
// Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too
static QColor last_window_color ;
const QColor window_color = test_palette . color ( QPalette : : Active , QPalette : : Window ) ;
if ( last_window_color ! = window_color & & ( current_theme = = QStringLiteral ( " default " ) | |
current_theme = = QStringLiteral ( " colorful " ) ) ) {
UpdateUITheme ( ) ;
}
last_window_color = window_color ;
}
# endif // __unix__
QWidget : : changeEvent ( event ) ;
}
Service : : AM : : FrontendAppletParameters GMainWindow : : ApplicationAppletParameters ( ) {
return Service : : AM : : FrontendAppletParameters {
. applet_id = Service : : AM : : AppletId : : Application ,