Skip to content

Commit

Permalink
Add more code formatters, linters.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexhuszagh committed Sep 7, 2024
1 parent 3bd2674 commit 09443c7
Show file tree
Hide file tree
Showing 16 changed files with 516 additions and 494 deletions.
7 changes: 5 additions & 2 deletions example/advanced-dock.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@
Simple PyQt application using the advanced-docking-system.
'''

import shared
# pylint: disable=no-name-in-module

import sys

import shared

parser = shared.create_parser()
parser.add_argument(
'--use-internal',
Expand Down Expand Up @@ -114,7 +117,7 @@ def main():
# run
window.setWindowState(compat.WindowMaximized)
shared.set_stylesheet(args, app, compat)
return shared.exec_app(args, app, window, compat)
return shared.exec_app(args, app, window)


if __name__ == '__main__':
Expand Down
23 changes: 16 additions & 7 deletions example/branchless/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,44 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

'''
branchless
==========
Simple PyQt application without branches for our QTreeViews.
'''

import os
import sys

HOME = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, os.path.dirname(HOME))

import widgets
import shared
import shared # noqa # pylint: disable=wrong-import-position,import-error
import widgets # noqa # pylint: disable=wrong-import-position,import-error

parser = shared.create_parser()
args, unknown = shared.parse_args(parser)
QtCore, QtGui, QtWidgets = shared.import_qt(args)
compat = shared.get_compat_definitions(args)
ICON_MAP = shared.get_icon_map(args, compat)
ICON_MAP = shared.get_icon_map(compat)


def set_stylesheet(args, app, compat):
def set_stylesheet(app):
'''Set the application stylesheet.'''

if args.stylesheet != 'native':
resource_format = shared.get_resources(args)
qt_path = shared.get_stylesheet(resource_format)
ext_path = os.path.join(HOME, 'stylesheet.qss.in')
stylesheet = shared.read_qtext_file(qt_path, compat)
stylesheet += '\n' + open(ext_path, 'r').read()
with open(ext_path, 'r', encoding='utf-8') as file:
stylesheet += '\n' + file.read()
app.setStyleSheet(stylesheet)


def get_treeviews(parent, depth=1000):
'''Recursively get all tree views.'''
for child in parent.children():
if isinstance(child, QtWidgets.QTreeView):
yield child
Expand Down Expand Up @@ -93,8 +102,8 @@ def main():
for tree in get_treeviews(window):
tree.setObjectName("branchless")

set_stylesheet(args, app, compat)
return shared.exec_app(args, app, window, compat)
set_stylesheet(app)
return shared.exec_app(args, app, window)


if __name__ == '__main__':
Expand Down
117 changes: 64 additions & 53 deletions example/breeze_theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def from_string(value: str | None) -> 'Theme':
value = value.lower()
if value == 'dark':
return Theme.DARK
elif value == 'light':
if value == 'light':
return Theme.LIGHT
raise ValueError(f'Got an invalid theme value of "{value}".')

Expand All @@ -85,9 +85,9 @@ def to_string(self) -> str:
# NOTE: This is for Py3.10 and earlier support.
if self == Theme.DARK:
return 'Dark'
elif self == Theme.LIGHT:
if self == Theme.LIGHT:
return 'Light'
elif self == Theme.UNKNOWN:
if self == Theme.UNKNOWN:
return 'Unknown'
raise ValueError(f'Got an invalid theme value of "{self}".')

Expand Down Expand Up @@ -125,7 +125,7 @@ def _get_theme_windows() -> Theme:
# some headless Windows instances (e.g. GitHub Actions or Docker images) do not have this key
# this is also not present if the user has never set the value. however, more recent Windows
# installs will have this, starting at `10.0.10240.0`:
# https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/ui/apply-windows-themes#know-when-dark-mode-is-enabled
# https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/ui/apply-windows-themes#know-when-dark-mode-is-enabled # noqa # pylint: disable=line-too-long
#
# Note that the documentation is inverted: if the foreground is light, we are using DARK mode.
winver = sys.getwindowsversion()
Expand All @@ -144,15 +144,15 @@ def _get_theme_windows() -> Theme:

if use_light == 0:
return Theme.DARK
elif use_light == 1:
if use_light == 1:
return Theme.LIGHT
return Theme.UNKNOWN


def _listener_windows(callback: CallbackFn) -> None:
'''Register an event listener for dark/light theme changes.'''

import ctypes.wintypes # pyright: ignore[reportMissingImports]
import ctypes.wintypes # pyright: ignore[reportMissingImports] # pylint: disable=redefined-outer-name

global _advapi32

Expand All @@ -169,16 +169,16 @@ def _listener_windows(callback: CallbackFn) -> None:
ctypes.byref(hkey),
)

dwSize = ctypes.wintypes.DWORD(ctypes.sizeof(ctypes.wintypes.DWORD))
queryValueLast = ctypes.wintypes.DWORD()
queryValue = ctypes.wintypes.DWORD()
size = ctypes.wintypes.DWORD(ctypes.sizeof(ctypes.wintypes.DWORD))
query_last_value = ctypes.wintypes.DWORD()
query_value = ctypes.wintypes.DWORD()
advapi32.RegQueryValueExA(
hkey,
ctypes.wintypes.LPCSTR(b'AppsUseLightTheme'),
ctypes.wintypes.LPDWORD(),
ctypes.wintypes.LPDWORD(),
ctypes.cast(ctypes.byref(queryValueLast), ctypes.wintypes.LPBYTE),
ctypes.byref(dwSize),
ctypes.cast(ctypes.byref(query_last_value), ctypes.wintypes.LPBYTE),
ctypes.byref(size),
)

while True:
Expand All @@ -194,18 +194,18 @@ def _listener_windows(callback: CallbackFn) -> None:
ctypes.wintypes.LPCSTR(b'AppsUseLightTheme'),
ctypes.wintypes.LPDWORD(),
ctypes.wintypes.LPDWORD(),
ctypes.cast(ctypes.byref(queryValue), ctypes.wintypes.LPBYTE),
ctypes.byref(dwSize),
ctypes.cast(ctypes.byref(query_value), ctypes.wintypes.LPBYTE),
ctypes.byref(size),
)
if queryValueLast.value != queryValue.value:
queryValueLast.value = queryValue.value
callback(Theme.LIGHT if queryValue.value else Theme.DARK)
if query_last_value.value != query_value.value:
query_last_value.value = query_value.value
callback(Theme.LIGHT if query_value.value else Theme.DARK)


def _initialize_advapi32() -> 'ctypes.WinDLL':
'''Initialize our advapi32 library.'''

import ctypes.wintypes # pyright: ignore[reportMissingImports]
import ctypes.wintypes # pyright: ignore[reportMissingImports] # pylint: disable=redefined-outer-name

advapi32 = ctypes.windll.advapi32

Expand Down Expand Up @@ -277,7 +277,7 @@ def macos_supported_version() -> bool:
major = int(sysver.split('.')[0])
if major < 10:
return False
elif major >= 11:
if major >= 11:
return True

# have a macOS10 version
Expand Down Expand Up @@ -318,36 +318,45 @@ def _get_theme_macos_impl() -> ThemeFn:
objc = ctypes.cdll.LoadLibrary('libobjc.dylib')
except OSError:
# revert to full path for older OS versions and hardened programs
objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc'))
obc_name = ctypes.util.find_library('objc')
assert obc_name is not None
objc = ctypes.cdll.LoadLibrary(obc_name)

# See https://docs.python.org/3/library/ctypes.html#function-prototypes for arguments description
msg_prototype = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p)
msg = msg_prototype(('objc_msgSend', objc), ((1, '', None), (1, '', None), (1, '', None)))

auto_release_pool = _get_class(objc, 'NSAutoreleasePool')
pool = msg(auto_release_pool, _register_name(objc, 'alloc'))
pool = msg(pool, _register_name(objc, 'init'))

user_defaults = _get_class(objc, 'NSUserDefaults')
std_user_defaults = msg(user_defaults, _register_name(objc, 'standardUserDefaults'))

ns_string = _get_class(objc, 'NSString')
key = msg(ns_string, _register_name(objc, "stringWithUTF8String:"), _as_utf8('AppleInterfaceStyle'))
appearance_ns = msg(std_user_defaults, _register_name(objc, 'stringForKey:'), ctypes.c_void_p(key))
appearance_c = msg(appearance_ns, _register_name(objc, 'UTF8String'))

out = ctypes.string_at(appearance_c) if appearance_c is not None else None
msg(pool, _register_name(objc, 'release'))
return Theme.from_string(out.decode('utf-8')) if out is not None else Theme.LIGHT
def get_theme_func() -> Theme:
'''Get the theme with all our internal helpers.'''

pool = msg(auto_release_pool, _register_name(objc, 'alloc'))
pool = msg(pool, _register_name(objc, 'init'))
std_user_defaults = msg(user_defaults, _register_name(objc, 'standardUserDefaults'))

key = msg(ns_string, _register_name(objc, "stringWithUTF8String:"), _as_utf8('AppleInterfaceStyle'))
appearance_ns = msg(std_user_defaults, _register_name(objc, 'stringForKey:'), ctypes.c_void_p(key))
appearance_c = msg(appearance_ns, _register_name(objc, 'UTF8String'))

out = ctypes.string_at(appearance_c) if appearance_c is not None else None
msg(pool, _register_name(objc, 'release'))

return Theme.from_string(out.decode('utf-8')) if out is not None else Theme.LIGHT

return get_theme_func


def _listener_macos(callback: CallbackFn) -> None:
'''Register an event listener for dark/light theme changes.'''

try:
from Foundation import NSKeyValueObservingOptionNew as _ # noqa # pyright: ignore[reportMissingImports]
except (ImportError, ModuleNotFoundError):
raise RuntimeError('Missing the required Foundation modules: cannot listen.')
from Foundation import ( # noqa # pyright: ignore[reportMissingImports] # pylint: disable
NSKeyValueObservingOptionNew as _,
)
except (ImportError, ModuleNotFoundError) as error:
raise RuntimeError('Missing the required Foundation modules: cannot listen.') from error

# now need to register a child event
path = Path(__file__)
Expand All @@ -358,7 +367,7 @@ def _listener_macos(callback: CallbackFn) -> None:
universal_newlines=True,
cwd=path.parent,
) as process:
for line in process.stdout:
for line in typing.cast(str, process.stdout):
callback(Theme.from_string(line.strip()))


Expand All @@ -368,20 +377,25 @@ def _listen_child_macos() -> None:
# NOTE: We do this so we don't need imports at the global level.
try:
from Foundation import ( # pyright: ignore[reportMissingImports]
NSObject, NSKeyValueObservingOptionNew, NSKeyValueChangeNewKey, NSUserDefaults
NSKeyValueChangeNewKey,
NSKeyValueObservingOptionNew,
NSObject,
NSUserDefaults,
)
from PyObjCTools import AppHelper # pyright: ignore[reportMissingImports]
except ModuleNotFoundError:
raise RuntimeError('Missing the required Foundation modules: cannot listen.')
except ModuleNotFoundError as error:
raise RuntimeError('Missing the required Foundation modules: cannot listen.') from error

signal.signal(signal.SIGINT, signal.SIG_IGN)

class Observer(NSObject):
def observeValueForKeyPath_ofObject_change_context_(
self, path, object, changeDescription, context
'''Custom namespace key observer.'''
def observeValueForKeyPath_ofObject_change_context_( # pylint: disable=invalid-name
self, path, obj, changeDescription, context
):
'''Observe our key to detect the light/dark status.'''
_ = path
_ = object
_ = obj
_ = context
result = changeDescription[NSKeyValueChangeNewKey]
try:
Expand Down Expand Up @@ -431,31 +445,30 @@ def _listener_linux(callback: CallbackFn) -> None:
command = [gsettings, 'monitor', 'org.gnome.desktop.interface', schema]
# this has rhe same restrictions as above
with subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True) as process:
for line in process.stdout:
for line in typing.cast(str, process.stdout):
value = line.removeprefix(f"{schema}: '").removesuffix("'")
callback(Theme.DARK if '-dark' in value.lower() else Theme.LIGHT)


def _get_gsettings_schema() -> tuple[str, str]:
'''Get the schema to use when monitoring via gsettings.'''
# This follows the gsettings followed here:
# https://github.com/GNOME/gsettings-desktop-schemas/blob/master/schemas/org.gnome.desktop.interface.gschema.xml.in
# https://github.com/GNOME/gsettings-desktop-schemas/blob/master/schemas/org.gnome.desktop.interface.gschema.xml.in # noqa # pylint: disable=line-too-long

gsettings = _get_gsettings()
command = [gsettings, 'get', 'org.gnome.desktop.interface']
# using the freedesktop specifications for checking dark mode
# this will return something like `prefer-dark`, which is the true value.
# valid values are 'default', 'prefer-dark', 'prefer-light'.
process = subprocess.run(command + ['color-scheme'], capture_output=True)
process = subprocess.run(command + ['color-scheme'], capture_output=True, check=False)
if process.returncode == 0:
return ('color-scheme', process.stdout.decode('utf-8'))
elif b'No such key' not in process.stderr:
if b'No such key' not in process.stderr:
raise RuntimeError('Unable to get our color-scheme from our gsettings.')

# if not found then trying older gtk-theme method
# this relies on the theme not lying to you: if the theme is dark, it ends in `-dark`.
process = subprocess.run(command + ['gtk-theme'], capture_output=True)
process.check_returncode()
process = subprocess.run(command + ['gtk-theme'], capture_output=True, check=True)
return ('gtk-theme', process.stdout.decode('utf-8'))


Expand Down Expand Up @@ -517,19 +530,17 @@ def register_functions() -> tuple[ThemeFn, ListenerFn]:

if sys.platform == 'darwin' and macos_supported_version():
return (_get_theme_macos, _listener_macos)
elif sys.platform == 'win32' and platform.release().isdigit() and int(platform.release()) >= 10:
if sys.platform == 'win32' and platform.release().isdigit() and int(platform.release()) >= 10:
# 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.
winver = int(platform.version().split('.')[2])
if winver >= 14393:
return (_get_theme_windows, _listener_windows)
else:
return (_get_theme_dummy, _listener_dummy)
elif sys.platform == "linux":
return (_get_theme_linux, _listener_linux)
else:
return (_get_theme_dummy, _listener_dummy)
if sys.platform == "linux":
return (_get_theme_linux, _listener_linux)
return (_get_theme_dummy, _listener_dummy)


# register these callbacks once
Expand Down
Loading

0 comments on commit 09443c7

Please sign in to comment.