support specifying gfx driver in profile folder

We need to set the OpenGL mode prior to Qt initialisation, but
want to fetch the current driver from the profile manager - and
the profile manager required Qt to already be set up.

Work around this by moving away from QStandardPaths in favour of
a pure Python module. The profile manager now does early setup
using winpaths, and we defer most of the setup until Qt has been
initialised.

Also we install a message handler to catch OpenGL initialisation
errors, and automatically switch to the next driver so users
don't need to manually change the driver.

The --hwaccel option has been removed, as it is no longer necessary.
This commit is contained in:
Damien Elmes 2018-08-08 23:48:25 +10:00
parent 91983ce21f
commit f2b5c8a862
2 changed files with 87 additions and 36 deletions

View file

@ -224,10 +224,38 @@ def parseArgs(argv):
parser.add_option("-b", "--base", help="path to base folder") parser.add_option("-b", "--base", help="path to base folder")
parser.add_option("-p", "--profile", help="profile name to load") parser.add_option("-p", "--profile", help="profile name to load")
parser.add_option("-l", "--lang", help="interface language (en, de, etc)") parser.add_option("-l", "--lang", help="interface language (en, de, etc)")
if not isMac:
parser.add_option("--hwaccel", action="store_true", help="enable hardware acceleration")
return parser.parse_args(argv[1:]) return parser.parse_args(argv[1:])
def setupGL(pm):
if isMac:
return
mode = pm.glMode()
# work around pyqt loading wrong GL library
if isLin:
import ctypes
ctypes.CDLL('libGL.so.1', ctypes.RTLD_GLOBAL)
# catch opengl errors
def msgHandler(type, ctx, msg):
if "Failed to create OpenGL context" in msg:
QMessageBox.critical(None, "Error", "Error loading '%s' graphics driver. Please start Anki again to try next driver." % mode)
pm.nextGlMode()
return
else:
print("qt:", msg)
qInstallMessageHandler(msgHandler)
print("Hardware acceleration set to", mode)
if mode == "auto":
return
elif isLin:
os.environ["QT_XCB_FORCE_SOFTWARE_OPENGL"] = "1"
else:
os.environ["QT_OPENGL"] = mode
def run(): def run():
try: try:
_run() _run()
@ -256,17 +284,12 @@ def _run(argv=None, exec=True):
opts.base = opts.base or "" opts.base = opts.base or ""
opts.profile = opts.profile or "" opts.profile = opts.profile or ""
if not isMac and not opts.hwaccel: # profile manager
print("Hardware acceleration disabled.") from aqt.profiles import ProfileManager
if isWin: pm = ProfileManager(opts.base)
os.environ["QT_OPENGL"] = "software"
else:
os.environ["QT_XCB_FORCE_SOFTWARE_OPENGL"] = "1"
# work around pyqt loading wrong GL library # gl workarounds
if isLin: setupGL(pm)
import ctypes
ctypes.CDLL('libGL.so.1', ctypes.RTLD_GLOBAL)
# opt in to full hidpi support? # opt in to full hidpi support?
if not os.environ.get("ANKI_NOHIGHDPI"): if not os.environ.get("ANKI_NOHIGHDPI"):
@ -293,9 +316,10 @@ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \
environment points to a valid, writable folder.""") environment points to a valid, writable folder.""")
return return
# profile manager pm.setupMeta()
from aqt.profiles import ProfileManager
pm = ProfileManager(opts.base, opts.profile) if opts.profile:
pm.openProfile(opts.profile)
# i18n # i18n
setupLang(pm, app, opts.lang) setupLang(pm, app, opts.lang)

View file

@ -61,14 +61,18 @@ profileConf = dict(
class ProfileManager: class ProfileManager:
def __init__(self, base=None, profile=None): def __init__(self, base=None):
self.name = None self.name = None
self.db = None self.db = None
# instantiate base folder # instantiate base folder
self._setBaseFolder(base) self._setBaseFolder(base)
def setupMeta(self):
# load metadata # load metadata
self.firstRun = self._loadMeta() self.firstRun = self._loadMeta()
# did the user request a profile to start up with?
# profile load on startup
def openProfile(self, profile):
if profile: if profile:
if profile not in self.profiles(): if profile not in self.profiles():
QMessageBox.critical(None, "Error", "Requested profile does not exist.") QMessageBox.critical(None, "Error", "Requested profile does not exist.")
@ -101,27 +105,18 @@ a flash drive.""" % self.base)
if isMac: if isMac:
return os.path.expanduser("~/Documents/Anki") return os.path.expanduser("~/Documents/Anki")
elif isWin: elif isWin:
loc = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation) from aqt.winpaths import get_personal
return os.path.join(loc, "Anki") return os.path.join(get_personal(), "Anki")
else: else:
p = os.path.expanduser("~/Anki") p = os.path.expanduser("~/Anki")
if os.path.isdir(p): if os.path.isdir(p):
return p return p
else:
loc = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
if loc[:-1] == QStandardPaths.writableLocation(
QStandardPaths.HomeLocation):
# occasionally "documentsLocation" will return the home
# folder because the Documents folder isn't configured
# properly; fall back to an English path
return os.path.expanduser("~/Documents/Anki") return os.path.expanduser("~/Documents/Anki")
else:
return os.path.join(loc, "Anki")
def maybeMigrateFolder(self): def maybeMigrateFolder(self):
oldBase = self._oldFolderLocation() oldBase = self._oldFolderLocation()
if not os.path.exists(self.base) and os.path.isdir(oldBase): if oldBase and not os.path.exists(self.base) and os.path.isdir(oldBase):
shutil.move(oldBase, self.base) shutil.move(oldBase, self.base)
# Profile load/save # Profile load/save
@ -271,12 +266,8 @@ and no other programs are accessing your profile folders, then try again."""))
def _defaultBase(self): def _defaultBase(self):
if isWin: if isWin:
loc = QStandardPaths.writableLocation(QStandardPaths.AppDataLocation) from aqt.winpaths import get_appdata
# the returned value seem to automatically include the app name, but we use Anki2 rather return os.path.join(get_appdata(), "Anki2")
# than Anki
assert loc.endswith("/Anki")
loc += "2"
return loc
elif isMac: elif isMac:
return os.path.expanduser("~/Library/Application Support/Anki2") return os.path.expanduser("~/Library/Application Support/Anki2")
else: else:
@ -407,3 +398,39 @@ please see:
self.db.execute(sql, self._pickle(self.meta), "_global") self.db.execute(sql, self._pickle(self.meta), "_global")
self.db.commit() self.db.commit()
anki.lang.setLang(code, local=False) anki.lang.setLang(code, local=False)
# OpenGL
######################################################################
def _glPath(self):
return os.path.join(self.base, "gldriver")
def glMode(self):
assert not isMac
path = self._glPath()
if not os.path.exists(path):
return "software"
mode = open(path, "r").read().strip()
if mode == "angle" and isWin:
return mode
elif mode == "software":
return mode
return "auto"
def setGlMode(self, mode):
open(self._glPath(), "w").write(mode)
def nextGlMode(self):
mode = self.glMode()
if mode == "software":
self.setGlMode("auto")
elif mode == "auto":
if isWin:
self.setGlMode("angle")
else:
self.setGlMode("software")
elif mode == "angle":
self.setGlMode("software")