Windows dark mode (#483)
* Add Dark Mode support for Windows * Only use dark mode if enabled * Actions: debug Windows build * Don't error if dark mode is unavailable * Revert Actions: debug Windows build * darkdetect: update vendor copy to 0.5.1 * Revert "Don't error if dark mode is unavailable" This reverts commit 5a9e871c12312c55355a9eb75df15c66bb4238ac. * Set up tmate * Stronger patching of darkdetect is needed * Revert "Set up tmate" This reverts commit 4012051f9328c52ab6d1297ede111ab48fad1f2d. * Fix EXE by using Windows darkdetect directly * Add try wrap to Windows darkmode check * darkdetect: update vendor copy to 0.5.2 * Revert to public darkdetect API Co-authored-by: Alberto Sottile <asottile@gmail.com> Co-authored-by: Alberto Sottile <alby128@gmail.com>
This commit is contained in:
parent
c0e8652c8c
commit
d60efabbad
@ -10,7 +10,7 @@ from configparser import SafeConfigParser, DEFAULTSECT
|
|||||||
from syncplay import constants, utils, version, milestone
|
from syncplay import constants, utils, version, milestone
|
||||||
from syncplay.messages import getMessage, setLanguage, isValidLanguage
|
from syncplay.messages import getMessage, setLanguage, isValidLanguage
|
||||||
from syncplay.players.playerFactory import PlayerFactory
|
from syncplay.players.playerFactory import PlayerFactory
|
||||||
from syncplay.utils import isMacOS
|
from syncplay.utils import isMacOS, isWindows
|
||||||
|
|
||||||
|
|
||||||
class InvalidConfigValue(Exception):
|
class InvalidConfigValue(Exception):
|
||||||
@ -514,13 +514,22 @@ class ConfigurationGetter(object):
|
|||||||
self._overrideConfigWithArgs(args)
|
self._overrideConfigWithArgs(args)
|
||||||
if not self._config['noGui']:
|
if not self._config['noGui']:
|
||||||
try:
|
try:
|
||||||
from syncplay.vendor.Qt import QtWidgets, IsPySide, IsPySide2
|
from syncplay.vendor.Qt import QtWidgets, IsPySide, IsPySide2, QtGui
|
||||||
from syncplay.vendor.Qt.QtCore import QCoreApplication
|
from syncplay.vendor.Qt.QtCore import QCoreApplication
|
||||||
from syncplay.vendor import qt5reactor
|
from syncplay.vendor import qt5reactor
|
||||||
if not (IsPySide2 or IsPySide):
|
if not (IsPySide2 or IsPySide):
|
||||||
raise ImportError
|
raise ImportError
|
||||||
if QCoreApplication.instance() is None:
|
if QCoreApplication.instance() is None:
|
||||||
self.app = QtWidgets.QApplication(sys.argv)
|
self.app = QtWidgets.QApplication(sys.argv)
|
||||||
|
if isWindows():
|
||||||
|
try:
|
||||||
|
from syncplay.vendor import darkdetect
|
||||||
|
isDarkMode = darkdetect.isDark()
|
||||||
|
except:
|
||||||
|
isDarkMode = False
|
||||||
|
if isDarkMode:
|
||||||
|
self.app.setStyle(QtWidgets.QStyleFactory.create("fusion"))
|
||||||
|
self.app.setPalette(self.getDarkPalette(QtGui))
|
||||||
qt5reactor.install()
|
qt5reactor.install()
|
||||||
if isMacOS():
|
if isMacOS():
|
||||||
import appnope
|
import appnope
|
||||||
@ -550,6 +559,29 @@ class ConfigurationGetter(object):
|
|||||||
setLanguage(self._config['language'])
|
setLanguage(self._config['language'])
|
||||||
return self._config
|
return self._config
|
||||||
|
|
||||||
|
def getDarkPalette(self, QtGui):
|
||||||
|
# Based on https://gist.github.com/lschmierer/443b8e21ad93e2a2d7eb#gistcomment-3503395
|
||||||
|
darkPalette = QtGui.QPalette()
|
||||||
|
darkPalette.setColor(QtGui.QPalette.Window, QtGui.QColor(53, 53, 53))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.WindowText, QtGui.QColor(255, 255, 255))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.Base, QtGui.QColor(35, 35, 35))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(53, 53, 53))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.ToolTipBase, QtGui.QColor(25, 25, 25))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.ToolTipText, QtGui.QColor(255, 255, 255))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.Text, QtGui.QColor(255, 255, 255))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.Button, QtGui.QColor(53, 53, 53))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.ButtonText, QtGui.QColor(255, 255, 255))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.BrightText, QtGui.QColor(255, 0, 0))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.Link, QtGui.QColor(42, 130, 218))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.Highlight, QtGui.QColor(42, 130, 218))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(35, 35, 35))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Button, QtGui.QColor(53, 53, 53))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, QtGui.QColor(128, 128, 128))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, QtGui.QColor(128, 128, 128))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text, QtGui.QColor(128, 128, 128))
|
||||||
|
darkPalette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Light, QtGui.QColor(53, 53, 53))
|
||||||
|
return darkPalette
|
||||||
|
|
||||||
def setConfigOption(self, option, value):
|
def setConfigOption(self, option, value):
|
||||||
path = self._getConfigurationFilePath()
|
path = self._getConfigurationFilePath()
|
||||||
backup = self._config.copy()
|
backup = self._config.copy()
|
||||||
|
|||||||
@ -40,7 +40,7 @@ if isMacOS() and IsPySide:
|
|||||||
from Cocoa import NSString, NSUTF8StringEncoding
|
from Cocoa import NSString, NSUTF8StringEncoding
|
||||||
lastCheckedForUpdates = None
|
lastCheckedForUpdates = None
|
||||||
from syncplay.vendor import darkdetect
|
from syncplay.vendor import darkdetect
|
||||||
if isMacOS():
|
if isMacOS() or isWindows():
|
||||||
isDarkMode = darkdetect.isDark()
|
isDarkMode = darkdetect.isDark()
|
||||||
else:
|
else:
|
||||||
isDarkMode = None
|
isDarkMode = None
|
||||||
|
|||||||
8
syncplay/vendor/darkdetect/__init__.py
vendored
8
syncplay/vendor/darkdetect/__init__.py
vendored
@ -4,7 +4,7 @@
|
|||||||
# Distributed under the terms of the 3-clause BSD License.
|
# Distributed under the terms of the 3-clause BSD License.
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
__version__ = '0.5.0'
|
__version__ = '0.5.2'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
@ -16,10 +16,10 @@ if sys.platform == "darwin":
|
|||||||
else:
|
else:
|
||||||
from ._mac_detect import *
|
from ._mac_detect import *
|
||||||
del V
|
del V
|
||||||
elif sys.platform == "win32" and platform.release() == "10":
|
elif sys.platform == "win32" and int(platform.release()) >= 10:
|
||||||
# Checks if running Windows 10 version 10.0.14393 (Anniversary Update) or higher. The getwindowsversion method returns a tuple.
|
# Checks if running Windows 10 version 10.0.14393 (Anniversary Update) OR HIGHER. The getwindowsversion method returns a tuple.
|
||||||
# The third item is the build number that we can use to check if the user has a new enough version of Windows.
|
# The third item is the build number that we can use to check if the user has a new enough version of Windows.
|
||||||
winver = int(sys.getwindowsversion()[2])
|
winver = int(platform.version().split('.')[2])
|
||||||
if winver >= 14393:
|
if winver >= 14393:
|
||||||
from ._windows_detect import *
|
from ._windows_detect import *
|
||||||
else:
|
else:
|
||||||
|
|||||||
14
syncplay/vendor/darkdetect/_mac_detect.py
vendored
14
syncplay/vendor/darkdetect/_mac_detect.py
vendored
@ -6,17 +6,15 @@
|
|||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import ctypes.util
|
import ctypes.util
|
||||||
import platform
|
|
||||||
|
|
||||||
from distutils.version import LooseVersion as V
|
try:
|
||||||
|
# macOS Big Sur+ use "a built-in dynamic linker cache of all system-provided libraries"
|
||||||
if V(platform.mac_ver()[0]) < V("10.16"):
|
|
||||||
appkit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('AppKit'))
|
|
||||||
objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc'))
|
|
||||||
else:
|
|
||||||
appkit = ctypes.cdll.LoadLibrary('AppKit.framework/AppKit')
|
appkit = ctypes.cdll.LoadLibrary('AppKit.framework/AppKit')
|
||||||
objc = ctypes.cdll.LoadLibrary('libobjc.dylib')
|
objc = ctypes.cdll.LoadLibrary('libobjc.dylib')
|
||||||
del V
|
except OSError:
|
||||||
|
# revert to full path for older OS versions and hardened programs
|
||||||
|
appkit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('AppKit'))
|
||||||
|
objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc'))
|
||||||
|
|
||||||
void_p = ctypes.c_void_p
|
void_p = ctypes.c_void_p
|
||||||
ull = ctypes.c_uint64
|
ull = ctypes.c_uint64
|
||||||
|
|||||||
@ -5,14 +5,20 @@ def theme():
|
|||||||
# Registry will return 0 if Windows is in Dark Mode and 1 if Windows is in Light Mode. This dictionary converts that output into the text that the program is expecting.
|
# Registry will return 0 if Windows is in Dark Mode and 1 if Windows is in Light Mode. This dictionary converts that output into the text that the program is expecting.
|
||||||
valueMeaning = {0: "Dark", 1: "Light"}
|
valueMeaning = {0: "Dark", 1: "Light"}
|
||||||
# In HKEY_CURRENT_USER, get the Personalisation Key.
|
# In HKEY_CURRENT_USER, get the Personalisation Key.
|
||||||
|
try:
|
||||||
key = getKey(hkey, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize")
|
key = getKey(hkey, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize")
|
||||||
# In the Personalisation Key, get the AppsUseLightTheme subkey. This returns a tuple.
|
# In the Personalisation Key, get the AppsUseLightTheme subkey. This returns a tuple.
|
||||||
# The first item in the tuple is the result we want (0 or 1 indicating Dark Mode or Light Mode); the other value is the type of subkey e.g. DWORD, QWORD, String, etc.
|
# The first item in the tuple is the result we want (0 or 1 indicating Dark Mode or Light Mode); the other value is the type of subkey e.g. DWORD, QWORD, String, etc.
|
||||||
subkey = getSubkeyValue(key, "AppsUseLightTheme")[0]
|
subkey = getSubkeyValue(key, "AppsUseLightTheme")[0]
|
||||||
|
except FileNotFoundError:
|
||||||
|
# some headless Windows instances (e.g. GitHub Actions or Docker images) do not have this key
|
||||||
|
return None
|
||||||
return valueMeaning[subkey]
|
return valueMeaning[subkey]
|
||||||
|
|
||||||
def isDark():
|
def isDark():
|
||||||
|
if theme() is not None:
|
||||||
return theme() == 'Dark'
|
return theme() == 'Dark'
|
||||||
|
|
||||||
def isLight():
|
def isLight():
|
||||||
|
if theme() is not None:
|
||||||
return theme() == 'Light'
|
return theme() == 'Light'
|
||||||
Loading…
x
Reference in New Issue
Block a user