Repository navigation
Expand file tree
/
Copy pathmain.py
More file actions
137 lines (106 loc) · 4.66 KB
/
Copy pathmain.py
File metadata and controls
137 lines (106 loc) · 4.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""GitHub Manager - application entry point."""
from __future__ import annotations
import ctypes
import sys
import traceback
from pathlib import Path
# Allow "python main.py" from anywhere by putting the package folder on sys.path.
sys.path.insert(0, str(Path(__file__).resolve().parent))
from PySide6.QtCore import Qt # noqa: E402
from PySide6.QtGui import QGuiApplication, QIcon # noqa: E402
from PySide6.QtWidgets import QApplication, QMessageBox # noqa: E402
from app.config import APP_NAME, APP_VERSION, ORG_NAME # noqa: E402
def _excepthook(exc_type, exc_value, exc_tb) -> None: # pragma: no cover
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_tb)
return
text = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
sys.stderr.write(text)
try:
QMessageBox.critical(
None,
"Something went wrong",
f"{exc_value}\n\nThe full traceback was written to the console.",
)
except Exception:
pass
def _system_is_dark() -> bool:
"""Whether the OS is in dark mode, or whether we could not tell.
Defaults to dark. A white mark is the one that survives being wrong more
often: Windows light taskbars are the minority and app icons are usually
shown over a wallpaper thumbnail anyway, while a white mark on a dark
surface is the combination the app itself is designed around.
"""
try:
from PySide6.QtCore import Qt
scheme = QGuiApplication.styleHints().colorScheme()
return scheme != Qt.ColorScheme.Light
except (AttributeError, ImportError, TypeError):
return True
def _make_icon() -> QIcon | None:
"""Render the app icon at runtime so no binary asset is required.
Rendered on an opaque plate rather than as a bare mark: the title bar and
the taskbar are the OS's to colour, and a mark that has to guess is a mark
that is invisible half the time. The plate is black on a light system and
white on a dark one, so the mark inside it always has contrast.
"""
try:
from app.ui.widgets import logo_plate_pixmap
dark = _system_is_dark()
icon = QIcon()
for size in (16, 24, 32, 48, 64, 128, 256):
# 2x so a HiDPI taskbar is not upscaling a bitmap it was just
# handed; Qt halves it back on the way out.
pixmap = logo_plate_pixmap(dark_plate=dark, pixels=size * 2)
pixmap.setDevicePixelRatio(2.0)
icon.addPixmap(pixmap)
return icon
except Exception:
return None
#: Must be a dotted, reverse-DNS-looking string and must never change between
#: releases. Windows keys its taskbar and icon caches on it, so a stable value is
#: what lets an upgraded executable replace its own cached icon, and an unstable
#: one leaves every past version's icon behind in the cache.
APP_USER_MODEL_ID = "mrlurix.GitHubManager.Desktop"
def _claim_taskbar_identity(app: QApplication) -> None:
"""Give the taskbar a stable identity for this app.
Without an AppUserModelID, Windows derives one from the executable's path.
A portable app is copied to wherever the user likes, so the same build
presents a different identity in each place, and the taskbar and the icon
cache accumulate one entry per location instead of one per application.
Called before any window exists, which is what the API requires. A failure
here is not worth interrupting startup for: the app runs, it just inherits
whatever identity Windows guesses.
"""
try:
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
APP_USER_MODEL_ID
)
except Exception:
# Present on every Windows this app targets, but an app that will not
# start over a missing taskbar nicety would be a worse trade.
pass
def main() -> int:
QApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
)
app = QApplication(sys.argv)
app.setApplicationName(APP_NAME)
app.setApplicationDisplayName(APP_NAME)
app.setApplicationVersion(APP_VERSION)
app.setOrganizationName(ORG_NAME)
app.setAttribute(Qt.ApplicationAttribute.AA_DontShowIconsInMenus, False)
_claim_taskbar_identity(app)
icon = _make_icon()
if icon is not None:
app.setWindowIcon(icon)
sys.excepthook = _excepthook
from app.ui.context import AppContext
from app.ui.main_window import MainWindow
ctx = AppContext()
window = MainWindow(ctx)
window.show()
# Portable mode: keep a data folder next to the executable.
return app.exec()
if __name__ == "__main__":
raise SystemExit(main())