def run(): global mw from anki.utils import isWin, isMac # on osx we'll need to add the qt plugins to the search path if isMac and getattr(sys, 'frozen', None): rd = os.path.abspath(moduleDir + "/../../..") QCoreApplication.setLibraryPaths([rd]) # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): # we've signaled the primary instance, so we should close return # parse args opts, args = parseArgs(sys.argv) opts.base = unicode(opts.base or "", sys.getfilesystemencoding()) opts.profile = unicode(opts.profile or "", sys.getfilesystemencoding()) # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, args) app.exec_()
def run(): global mw from anki.utils import isMac # parse args opts, args = parseArgs(sys.argv) opts.base = unicode(opts.base or "", sys.getfilesystemencoding()) opts.profile = unicode(opts.profile or "", sys.getfilesystemencoding()) # on osx we'll need to add the qt plugins to the search path if isMac and getattr(sys, "frozen", None): rd = os.path.abspath(moduleDir + "/../../..") QCoreApplication.setLibraryPaths([rd]) # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): # we've signaled the primary instance, so we should close return # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""", ) return # qt version must be up to date if qtmajor <= 4 and qtminor <= 6: QMessageBox.warning( None, "Error", "Your Qt version is known to be buggy. Until you " "upgrade to a newer Qt, you may experience issues such as images " "failing to show up during review.", ) # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, args) app.exec_()
def _run(): global mw # parse args opts, args = parseArgs(sys.argv) opts.base = unicode(opts.base or "", sys.getfilesystemencoding()) opts.profile = unicode(opts.profile or "", sys.getfilesystemencoding()) # on osx we'll need to add the qt plugins to the search path if isMac and getattr(sys, 'frozen', None): rd = os.path.abspath(moduleDir + "/../../..") QCoreApplication.setLibraryPaths([rd]) if isMac: QFont.insertSubstitution(".Lucida Grande UI", "Lucida Grande") # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): # we've signaled the primary instance, so we should close return # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return # qt version must be up to date if qtmajor <= 4 and qtminor <= 6: QMessageBox.warning( None, "Error", "Your Qt version is known to be buggy. Until you " "upgrade to a newer Qt, you may experience issues such as images " "failing to show up during review.") # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, args) app.exec_()
def _run(): global mw # parse args opts, args = parseArgs(sys.argv) opts.base = opts.base or "" opts.profile = opts.profile or "" # work around pyqt loading wrong GL library if isLin: import ctypes ctypes.CDLL('libGL.so.1', ctypes.RTLD_GLOBAL) # opt in to full hidpi support QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): # we've signaled the primary instance, so we should close return # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() print( "This is an BETA build - please do not package it up for Linux distributions" ) # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, args) app.exec_()
def _run(): global mw # parse args opts, args = parseArgs(sys.argv) opts.base = opts.base or "" opts.profile = opts.profile or "" # on osx we'll need to add the qt plugins to the search path if isMac and getattr(sys, 'frozen', None): rd = os.path.abspath(moduleDir + "/../../../plugins") QCoreApplication.setLibraryPaths([rd]) # work around pyqt loading wrong GL library if isLin: import ctypes ctypes.CDLL('libGL.so.1', ctypes.RTLD_GLOBAL) # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): # we've signaled the primary instance, so we should close return # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, args) app.exec_()
def _run(): global mw # parse args opts, args = parseArgs(sys.argv) opts.base = opts.base or "" opts.profile = opts.profile or "" # work around pyqt loading wrong GL library if isLin: import ctypes ctypes.CDLL('libGL.so.1', ctypes.RTLD_GLOBAL) # opt in to full hidpi support QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): # we've signaled the primary instance, so we should close return # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() print("This is an BETA build - please do not package it up for Linux distributions") # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, args) app.exec_()
def __init__(self, base, profile): self.base = unicode(base or "", sys.getfilesystemencoding()) self.profile = unicode(profile or "", sys.getfilesystemencoding()) self.pm = ProfileManager(self.base, self.profile) if self.profile: self.pm.ensureProfile() else: # defaulting to first frofile found name = self.pm.profiles()[0]; self.pm = ProfileManager(self.base, name) self.pm.ensureProfile() self.collection = Collection(self.pm.collectionPath())
def _run(): global mw # parse args opts, args = parseArgs(sys.argv) opts.base = opts.base or "" opts.profile = opts.profile or "" # on osx we'll need to add the qt plugins to the search path if isMac and getattr(sys, 'frozen', None): rd = os.path.abspath(moduleDir + "/../../../plugins") QCoreApplication.setLibraryPaths([rd]) # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): # we've signaled the primary instance, so we should close return # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, args) app.exec_()
def run(): global mw from anki.utils import isWin, isMac # on osx we'll need to add the qt plugins to the search path if isMac and getattr(sys, 'frozen', None): rd = os.path.abspath(moduleDir + "/../../..") QCoreApplication.setLibraryPaths([rd]) # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): # we've signaled the primary instance, so we should close return # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return # parse args opts, args = parseArgs(sys.argv) opts.base = unicode(opts.base or "", sys.getfilesystemencoding()) opts.profile = unicode(opts.profile or "", sys.getfilesystemencoding()) # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, args) app.exec_()
def main(delay): from PyQt4.QtCore import QTimer app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): print('Anki is already running') return from aqt.profiles import ProfileManager pm = ProfileManager('', '') setupLang(pm, app) pm.load('bz') pm.profile['autoSync'] = True pm.ensureProfile() def dummy(*args, **kwargs): pass # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, []) # prevent Anki from showing main window mw.show = dummy mw.activateWindow = dummy mw.raise_ = dummy def handler(): if mw.state == 'sync': # print('state sync') set_timer() return else: # print('state NOT sync') mw.onClose() mw.close() app.closeAllWindows() app.quit() def set_timer(): timer = QTimer(mw) timer.setSingleShot(True) timer.connect(timer, SIGNAL('timeout()'), handler) timer.start(delay) def start_sync(): # print('timer start_sync') mw.onSync(auto=True) set_timer() QTimer.singleShot(5000, start_sync) # print(dir(mw)) #print('executing app') #mw.setupProgress() #mw.progress.start(immediate=True) #mw.progress._lastUpdate = time.time() #mw.onSync() app.exec_()
def _init_load_collection(self, base, path): """Load the Anki collection""" from pathlib import Path from sqlite3 import OperationalError import anki from aqt.profiles import ProfileManager import click # Save CWD (because Anki changes it) save_cwd = os.getcwd() if path is None: basepath = Path(base) if not (basepath / 'prefs21.db').exists(): click.echo('Invalid base path!') click.echo(f'path = {basepath.absolute()}') raise click.Abort() # Initialize a profile manager to get an interface to the profile # settings and main database path; also required for syncing self.pm = ProfileManager(base) self.pm.setupMeta() self.pm.load(self.pm.profiles()[0]) # Load the main Anki database/collection path = self.pm.collectionPath() else: self.pm = None try: self.col = anki.Collection(path) except AssertionError: click.echo('Path to database is not valid!') click.echo(f'path = {path}') raise click.Abort() except OperationalError: click.echo('Database is NA/locked!') raise click.Abort() # Restore CWD (because Anki changes it) os.chdir(save_cwd)
def _init_load_collection(self, base, path, profile): """Load the Anki collection""" # Save CWD (because Anki changes it) save_cwd = os.getcwd() if path is None: if base is None: click.echo('Base path is not properly set!') raise click.Abort() basepath = Path(base) if not (basepath / 'prefs21.db').exists(): click.echo('Invalid base path!') click.echo(f'path = {basepath.absolute()}') raise click.Abort() # Initialize a profile manager to get an interface to the profile # settings and main database path; also required for syncing self.pm = ProfileManager(base) self.pm.setupMeta() if profile is None: profile = self.pm.profiles()[0] # Load the main Anki database/collection self.pm.load(profile) path = self.pm.collectionPath() else: self.pm = None try: self.col = anki.Collection(path) except AssertionError: click.echo('Path to database is not valid!') click.echo(f'path = {path}') raise click.Abort() except anki.errors.DBError: click.echo('Database is NA/locked!') raise click.Abort() # Restore CWD (because Anki changes it) os.chdir(save_cwd)
def run(): global mw from anki.utils import isWin, isMac # on osx we'll need to add the qt plugins to the search path if isMac and getattr(sys, "frozen", None): rd = os.path.abspath(moduleDir + "/../../..") QCoreApplication.setLibraryPaths([rd]) # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") # parse args import optparse parser = optparse.OptionParser() parser.usage = "%prog [OPTIONS]" parser.add_option("-b", "--base", help="path to base folder") parser.add_option("-p", "--profile", help="profile name to load") parser.add_option("-l", "--lang", help="interface language (en, de, etc)") (opts, args) = parser.parse_args(sys.argv[1:]) opts.base = unicode(opts.base or "", sys.getfilesystemencoding()) opts.profile = unicode(opts.profile or "", sys.getfilesystemencoding()) # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.checkPid() pm.ensureProfile() # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm) app.exec_()
def temporary_user(dir_name, name="__Temporary Test User__", lang="en_US"): # prevent popping up language selection dialog original = ProfileManager._setDefaultLang def set_default_lang(profileManager): profileManager.setLang(lang) ProfileManager._setDefaultLang = set_default_lang pm = ProfileManager(base=dir_name) if name in pm.profiles(): warn(f"Temporary user named {name} already exists") else: pm.create(name) pm.name = name yield name pm.remove(name) ProfileManager._setDefaultLang = original
def run(): global mw from anki.utils import isWin, isMac # on osx we'll need to add the qt plugins to the search path if isMac and getattr(sys, 'frozen', None): rd = os.path.abspath(moduleDir + "/../../..") QCoreApplication.setLibraryPaths([rd]) # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") # parse args import optparse parser = optparse.OptionParser() parser.usage = "%prog [OPTIONS]" parser.add_option("-b", "--base", help="Path to base folder") parser.add_option("-p", "--profile", help="Profile name to load") (opts, args) = parser.parse_args(sys.argv[1:]) # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # qt translations translationPath = '' if False: # not isWin and not isMac: translationPath = "/usr/share/qt4/translations/" long = conf['interfaceLang'] short = long.split('_')[0] qtTranslator = QTranslator() if qtTranslator.load("qt_" + long, translationPath) or \ qtTranslator.load("qt_" + short, translationPath): app.installTranslator(qtTranslator) import aqt.main mw = aqt.main.AnkiQt(app, pm) app.exec_()
def run(): global mw from anki.utils import isWin, isMac # on osx we'll need to add the qt plugins to the search path if isMac and getattr(sys, 'frozen', None): rd = os.path.abspath(moduleDir + "/../../..") QCoreApplication.setLibraryPaths([rd]) # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") # parse args import optparse parser = optparse.OptionParser() parser.usage = "%prog [OPTIONS]" parser.add_option("-b", "--base", help="path to base folder") parser.add_option("-p", "--profile", help="profile name to load") parser.add_option("-l", "--lang", help="interface language (en, de, etc)") (opts, args) = parser.parse_args(sys.argv[1:]) opts.base = unicode(opts.base or "", sys.getfilesystemencoding()) opts.profile = unicode(opts.profile or "", sys.getfilesystemencoding()) # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.checkPid() pm.ensureProfile() # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm) app.exec_()
def _run(argv=None, exec=True): """Start AnkiQt application or reuse an existing instance if one exists. If the function is invoked with exec=False, the AnkiQt will not enter the main event loop - instead the application object will be returned. The 'exec' and 'argv' arguments will be useful for testing purposes. If no 'argv' is supplied then 'sys.argv' will be used. """ global mw if argv is None: argv = sys.argv # parse args opts, args = parseArgs(argv) opts.base = opts.base or "" opts.profile = opts.profile or "" # work around pyqt loading wrong GL library if isLin: import ctypes ctypes.CDLL('libGL.so.1', ctypes.RTLD_GLOBAL) # opt in to full hidpi support QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # create the app app = AnkiApp(argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): # we've signaled the primary instance, so we should close return # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() print("This is an BETA build - please do not package it up for Linux distributions") # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, args) if exec: app.exec() else: return app
def _run(argv=None, exec=True) -> Optional[AnkiApp]: """Start AnkiQt application or reuse an existing instance if one exists. If the function is invoked with exec=False, the AnkiQt will not enter the main event loop - instead the application object will be returned. The 'exec' and 'argv' arguments will be useful for testing purposes. If no 'argv' is supplied then 'sys.argv' will be used. """ global mw global profiler if argv is None: argv = sys.argv # parse args opts, args = parseArgs(argv) if opts.version: print(f"Anki {appVersion}") return None elif opts.syncserver: from anki.syncserver import serve serve() return None if PROFILE_CODE: profiler = cProfile.Profile() profiler.enable() # default to specified/system language before getting user's preference so that we can localize some more strings lang = anki.lang.get_def_lang(opts.lang) anki.lang.set_lang(lang[1], locale_dir()) # profile manager pm = None try: pm = ProfileManager(opts.base) pmLoadResult = pm.setupMeta() except AnkiRestart as error: if error.exitcode: sys.exit(error.exitcode) return None except: # will handle below traceback.print_exc() pm = None if pm: # gl workarounds setupGL(pm) # apply user-provided scale factor os.environ["QT_SCALE_FACTOR"] = str(pm.uiScale()) # opt in to full hidpi support? if not os.environ.get("ANKI_NOHIGHDPI"): QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" os.environ["QT_SCALE_FACTOR_ROUNDING_POLICY"] = "PassThrough" # Opt into software rendering. Useful for buggy systems. if os.environ.get("ANKI_SOFTWAREOPENGL"): QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL) if ( isWin and (qtminor == 14 or (qtminor == 15 and qtpoint == 0)) and "QT_QPA_PLATFORM" not in os.environ ): os.environ["QT_QPA_PLATFORM"] = "windows:altgr" # create the app QCoreApplication.setApplicationName("Anki") QGuiApplication.setDesktopFileName("anki.desktop") app = AnkiApp(argv) if app.secondInstance(): # we've signaled the primary instance, so we should close return None if not pm: QMessageBox.critical( None, tr(TR.QT_MISC_ERROR), tr(TR.PROFILES_COULD_NOT_CREATE_DATA_FOLDER), ) return None # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # disable help button in title bar on qt versions that support it if isWin and qtminor >= 10: QApplication.setAttribute(Qt.AA_DisableWindowContextHelpButton) # proxy configured? from urllib.request import getproxies, proxy_bypass disable_proxies = False try: if "http" in getproxies(): # if it's not set up to bypass localhost, we'll # need to disable proxies in the webviews if not proxy_bypass("127.0.0.1"): disable_proxies = True except UnicodeDecodeError: # proxy_bypass can't handle unicode in hostnames; assume we need # to disable proxies disable_proxies = True if disable_proxies: print("webview proxy use disabled") proxy = QNetworkProxy() proxy.setType(QNetworkProxy.NoProxy) QNetworkProxy.setApplicationProxy(proxy) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, tr(TR.QT_MISC_ERROR), tr(TR.QT_MISC_NO_TEMP_FOLDER), ) return None if pmLoadResult.firstTime: pm.setDefaultLang(lang[0]) if pmLoadResult.loadError: QMessageBox.warning( None, tr(TR.PROFILES_PREFS_CORRUPT_TITLE), tr(TR.PROFILES_PREFS_FILE_IS_CORRUPT), ) if opts.profile: pm.openProfile(opts.profile) # i18n & backend backend = setupLangAndBackend(pm, app, opts.lang, pmLoadResult.firstTime) driver = pm.video_driver() if isLin and driver == VideoDriver.OpenGL: from aqt.utils import gfxDriverIsBroken if gfxDriverIsBroken(): pm.set_video_driver(driver.next()) QMessageBox.critical( None, tr(TR.QT_MISC_ERROR), tr(TR.QT_MISC_INCOMPATIBLE_VIDEO_DRIVER), ) sys.exit(1) # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, backend, opts, args) if exec: app.exec() else: return app if PROFILE_CODE: write_profile_results() return None
def temporary_user(dir_name, name="__Temporary Test User__", lang="en_US"): # prevent popping up language selection dialog original = ProfileManager.setDefaultLang def set_default_lang(profileManager): profileManager.setLang(lang) ProfileManager.setDefaultLang = set_default_lang pm = ProfileManager(base=dir_name) pm.setupMeta() # create profile no matter what (since we are starting in a unique temp directory) pm.create(name) # this needs to be called explicitly pm.setDefaultLang() pm.name = name yield name # cleanup pm.remove(name) ProfileManager.setDefaultLang = original
def _run(argv=None, exec=True): """Start AnkiQt application or reuse an existing instance if one exists. If the function is invoked with exec=False, the AnkiQt will not enter the main event loop - instead the application object will be returned. The 'exec' and 'argv' arguments will be useful for testing purposes. If no 'argv' is supplied then 'sys.argv' will be used. """ global mw if argv is None: argv = sys.argv # parse args opts, args = parseArgs(argv) # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base) # gl workarounds setupGL(pm) # opt in to full hidpi support? if not os.environ.get("ANKI_NOHIGHDPI"): QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # Opt into software rendering. Useful for buggy systems. if os.environ.get("ANKI_SOFTWAREOPENGL"): QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL) # create the app QCoreApplication.setApplicationName("Anki") QGuiApplication.setDesktopFileName("anki.desktop") app = AnkiApp(argv) if app.secondInstance(): # we've signaled the primary instance, so we should close return # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # proxy configured? from urllib.request import proxy_bypass, getproxies if 'http' in getproxies(): # if it's not set up to bypass localhost, we'll # need to disable proxies in the webviews if not proxy_bypass("127.0.0.1"): print("webview proxy use disabled") proxy = QNetworkProxy() proxy.setType(QNetworkProxy.NoProxy) QNetworkProxy.setApplicationProxy(proxy) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return pm.setupMeta() if opts.profile: pm.openProfile(opts.profile) # i18n setupLang(pm, app, opts.lang) if isLin and pm.glMode() == "auto": from aqt.utils import gfxDriverIsBroken if gfxDriverIsBroken(): pm.nextGlMode() QMessageBox.critical( None, "Error", "Your video driver is incompatible. Please start Anki again, and Anki will switch to a slower, more compatible mode." ) sys.exit(1) # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, opts, args) if exec: app.exec() else: return app
class Anki: """My Anki collection wrapper class.""" def __init__(self, base=None, path=None, debug=False): self.modified = False self._debug = debug self._init_load_collection(base, path) self._init_load_config() self.model_name_to_id = { m['name']: m['id'] for m in self.col.models.all() } self.model_names = self.model_name_to_id.keys() self.deck_name_to_id = { d['name']: d['id'] for d in self.col.decks.all() } self.deck_names = self.deck_name_to_id.keys() self.n_decks = len(self.deck_names) def _init_load_collection(self, base, path): """Load the Anki collection""" from pathlib import Path from sqlite3 import OperationalError import anki from aqt.profiles import ProfileManager import click # Save CWD (because Anki changes it) save_cwd = os.getcwd() if path is None: basepath = Path(base) if not (basepath / 'prefs21.db').exists(): click.echo('Invalid base path!') click.echo(f'path = {basepath.absolute()}') raise click.Abort() # Initialize a profile manager to get an interface to the profile # settings and main database path; also required for syncing self.pm = ProfileManager(base) self.pm.setupMeta() self.pm.load(self.pm.profiles()[0]) # Load the main Anki database/collection path = self.pm.collectionPath() else: self.pm = None try: self.col = anki.Collection(path) except AssertionError: click.echo('Path to database is not valid!') click.echo(f'path = {path}') raise click.Abort() except OperationalError: click.echo('Database is NA/locked!') raise click.Abort() # Restore CWD (because Anki changes it) os.chdir(save_cwd) def _init_load_config(self): """Load custom configuration""" import anki from apy.config import cfg # Update LaTeX commands # * Idea based on Anki addon #1546037973 ("Edit LaTeX build process") if 'pngCommands' in cfg: anki.latex.pngCommands = cfg['pngCommands'] if 'svgCommands' in cfg: anki.latex.svgCommands = cfg['svgCommands'] def __enter__(self): return self def __exit__(self, exception_type, exception_value, traceback): import click if self.modified and not self._debug: click.echo('Database was modified.') if self.pm is not None and self.pm.profile['syncKey']: click.secho('Remember to sync!', fg='blue') self.col.close() def sync(self): """Sync collection to AnkiWeb""" if self.pm is None: return import click if not self.pm.profile['syncKey']: click.echo('No sync auth registered in profile') return from anki.sync import (Syncer, MediaSyncer, RemoteServer, RemoteMediaServer) # Initialize servers and sync clients hkey = self.pm.profile['syncKey'] hostNum = self.pm.profile.get('hostNum') server = RemoteServer(hkey, hostNum=hostNum) main_client = Syncer(self.col, server) media_client = MediaSyncer( self.col, RemoteMediaServer(self.col, hkey, server.client, hostNum=hostNum)) # Perform main sync try: click.echo('Syncing deck ... ', nl=False) ret = main_client.sync() except Exception as e: if 'sync cancelled' in str(e): server.abort() click.secho('Error during sync!', fg='red') click.echo(e) raise click.Abort() # Parse return value if ret == "noChanges": click.echo('done (no changes)!') elif ret == "success": click.echo('done!') elif ret == "serverAbort": click.echo('aborted!') return elif ret == "fullSync": click.echo('aborted!') click.secho('Full sync required!', fg='red') return else: click.echo('failed!') click.echo(f'Message: {ret}') return # Perform media sync try: click.echo('Syncing media ... ', nl=False) save_cwd = os.getcwd() os.chdir(self.col.media.dir()) ret = media_client.sync() os.chdir(save_cwd) except Exception as e: if "sync cancelled" in str(e): return raise if ret == "noChanges": click.echo('done (no changes)!') elif ret in ("sanityCheckFailed", "corruptMediaDB"): click.echo('failed!') else: click.echo('done!') def check_media(self): """Check media (will rebuild missing LaTeX files)""" import click from apy.utilities import cd with cd(self.col.media.dir()): click.echo('Checking media DB ... ', nl=False) nohave, unused, warnings = self.col.media.check() click.echo('done!') if not warnings + unused + nohave: click.secho('No unused or missing files found.', fg='white') return for warning in warnings: click.secho(warning, fg='red') for file in nohave: click.secho(f'Missing: {file}', fg='red') if unused: for file in unused: click.secho(f'Unused: {file}', fg='red') if not click.confirm('Delete unused media?'): return for file in unused: if os.path.isfile(file): os.remove(file) def find_cards(self, query): """Find card ids in Collection that match query""" return self.col.findCards(query) def find_notes(self, query): """Find notes in Collection and return Note objects""" from apy.note import Note return (Note(self, self.col.getNote(i)) for i in self.col.findNotes(query)) def delete_notes(self, ids): """Delete notes by note ids""" if not isinstance(ids, list): ids = [ids] self.col.remNotes(ids) self.modified = True def get_model(self, model_name): """Get model from model name""" return self.col.models.get(self.model_name_to_id.get(model_name)) def set_model(self, model_name): """Set current model based on model name""" import click current = self.col.models.current(forDeck=False) if current['name'] == model_name: return current model = self.get_model(model_name) if model is None: click.secho(f'Model "{model_name}" was not recognized!') raise click.Abort() self.col.models.setCurrent(model) return model def edit_model_css(self, model_name): """Edit the CSS part of a given model.""" import tempfile import click from apy.utilities import editor model = self.get_model(model_name) with tempfile.NamedTemporaryFile(mode='w+', prefix='_apy_edit_', suffix='.css', delete=False) as tf: tf.write(model['css']) tf.flush() retcode = editor(tf.name) if retcode != 0: click.echo(f'Editor return with exit code {retcode}!') return with open(tf.name, 'r') as f: new_content = f.read() if model['css'] != new_content: model['css'] = new_content self.col.models.save(model, templates=True) self.modified = True def add_notes_with_editor(self, tags='', model_name=None, deck_name=None, template=None): """Add new notes to collection with editor""" import tempfile import click from apy.utilities import editor, choose from apy.note import Note if isinstance(template, Note): input_string = template.get_template() else: if model_name is None or model_name.lower() == 'ask': model_name = choose(sorted(self.model_names), "Choose model:") model = self.set_model(model_name) if deck_name is None: deck_name = self.col.decks.current()['name'] elif deck_name.lower() == 'ask': deck_name = choose(sorted(self.deck_names), "Choose deck:") input_string = [f'model: {model_name}'] if self.n_decks > 1: input_string += [f'deck: {deck_name}'] input_string += [f'tags: {tags}'] if model_name != 'Basic': input_string += ['markdown: false'] input_string += ['\n# Note\n'] input_string += [ x for y in [[f'## {field["name"]}', ''] for field in model['flds']] for x in y ] input_string = '\n'.join(input_string) + '\n' with tempfile.NamedTemporaryFile(mode='w+', dir=os.getcwd(), prefix='note_', suffix='.md', delete=False) as tf: tf.write(input_string) tf.flush() retcode = editor(tf.name) if retcode != 0: click.echo(f'Editor return with exit code {retcode}!') return [] return self.add_notes_from_file(tf.name) def add_notes_from_file(self, filename, tags=''): """Add new notes to collection from Markdown file""" from apy.convert import markdown_file_to_notes return self.add_notes_from_list(markdown_file_to_notes(filename), tags) def add_notes_from_list(self, parsed_notes, tags=''): """Add new notes to collection from note list (from parsed file)""" import click notes = [] for note in parsed_notes: model_name = note['model'] model = self.set_model(model_name) model_field_names = [field['name'] for field in model['flds']] field_names = note['fields'].keys() field_values = note['fields'].values() if len(field_names) != len(model_field_names): click.echo(f'Error: Not enough fields for model {model_name}!') self.modified = False raise click.Abort() for x, y in zip(model_field_names, field_names): if x != y: click.echo('Warning: Inconsistent field names ' f'({x} != {y})') notes.append( self._add_note(field_values, f"{tags} {note['tags']}", note['markdown'], note.get('deck'))) return notes def _add_note(self, fields, tags, markdown=True, deck=None): """Add new note to collection""" import click from apy.convert import markdown_to_html, plain_to_html from apy.note import Note note = self.col.newNote(forDeck=False) if deck is not None: note.model()['did'] = self.deck_name_to_id[deck] if markdown: note.fields = [markdown_to_html(x) for x in fields] else: note.fields = [plain_to_html(x) for x in fields] tags = tags.strip().split() for tag in tags: note.addTag(tag) if not note.dupeOrEmpty(): self.col.addNote(note) self.modified = True else: click.secho('Dupe detected, note was not added!', fg='red') click.echo('Question:') click.echo(list(fields)[0]) return Note(self, note)
class RogueAnki: def __init__(self, base, profile): self.base = unicode(base or "", sys.getfilesystemencoding()) self.profile = unicode(profile or "", sys.getfilesystemencoding()) self.pm = ProfileManager(self.base, self.profile) if self.profile: self.pm.ensureProfile() else: # defaulting to first frofile found name = self.pm.profiles()[0]; self.pm = ProfileManager(self.base, name) self.pm.ensureProfile() self.collection = Collection(self.pm.collectionPath()) def add(self, front, back, deck_name, model_name): default_deck = self.collection.decks.allNames()[0] default_model = self.collection.models.allNames()[0] deck_name = unicode(deck_name or default_deck, sys.getfilesystemencoding()) model_name = unicode(model_name or default_model, sys.getfilesystemencoding()) model = self.collection.models.byName(model_name) if not model: mm = ModelManager(self.collection) model = mm.new(model_name) mm.save(model) model['did'] = did = self.collection.decks.id(deck_name) self.collection.models.save() note = self.collection.newNote() note['Front']= unicode(front or "", sys.getfilesystemencoding()) note['Back']= unicode(back or "", sys.getfilesystemencoding()) note.model()['did']= did self.collection.addNote(note) self.collection.save() self.collection.genCards(self.collection.models.nids(model)) def list_decks(self): print "decks:" for x in self.collection.decks.allNames(): print x print "models:" for x in self.collection.models.allNames(): print x def sync(self): server= RemoteServer(self.pm.profile['syncKey']) client = Syncer(self.collection, server) ret = None try: print client.sync() except Exception, e: log = traceback.format_exc() try: err = unicode(e[0], "utf8", "ignore") except: # number, exception with no args, etc err = "" if "Unable to find the server" in err: print "offline" return else: if not isinstance(log, unicode): err = unicode(log, "utf8", "replace") print "error", log return if ret == "badAuth": print "badAuth" return elif ret == "clockOff": print "clockOff" return if ret == "fullSync": print "fullSync" return self.fullSync(server) # save and note success state self.collection.save() if ret == "noChanges": print "noChanges" else: print "success"
class Anki: """My Anki collection wrapper class.""" def __init__(self, base=None, path=None, profile=None, **_kwargs): self.modified = False self._init_load_collection(base, path, profile) self._init_load_config() self.model_name_to_id = {m['name']: m['id'] for m in self.col.models.all()} self.model_names = self.model_name_to_id.keys() self.deck_name_to_id = {d['name']: d['id'] for d in self.col.decks.all()} self.deck_names = self.deck_name_to_id.keys() self.n_decks = len(self.deck_names) def _init_load_collection(self, base, path, profile): """Load the Anki collection""" # Save CWD (because Anki changes it) save_cwd = os.getcwd() if path is None: if base is None: click.echo('Base path is not properly set!') raise click.Abort() basepath = Path(base) if not (basepath / 'prefs21.db').exists(): click.echo('Invalid base path!') click.echo(f'path = {basepath.absolute()}') raise click.Abort() # Initialize a profile manager to get an interface to the profile # settings and main database path; also required for syncing self.pm = ProfileManager(base) self.pm.setupMeta() if profile is None: profile = self.pm.profiles()[0] # Load the main Anki database/collection self.pm.load(profile) path = self.pm.collectionPath() else: self.pm = None try: self.col = anki.Collection(path) except AssertionError: click.echo('Path to database is not valid!') click.echo(f'path = {path}') raise click.Abort() except anki.errors.DBError: click.echo('Database is NA/locked!') raise click.Abort() # Restore CWD (because Anki changes it) os.chdir(save_cwd) @staticmethod def _init_load_config(): """Load custom configuration""" # Update LaTeX commands # * Idea based on Anki addon #1546037973 ("Edit LaTeX build process") if 'pngCommands' in cfg: anki.latex.pngCommands = cfg['pngCommands'] if 'svgCommands' in cfg: anki.latex.svgCommands = cfg['svgCommands'] def __enter__(self): return self def __exit__(self, exception_type, exception_value, traceback): if self.modified: click.echo('Database was modified.') if self.pm is not None and self.pm.profile['syncKey']: click.secho('Remember to sync!', fg='blue') self.col.close() elif self.col.db: self.col.close(False) def sync(self): """Sync collection to AnkiWeb""" if self.pm is None: return auth = self.pm.sync_auth() if auth is None: return # Make sure database is saved first self.col.save(trx=False) # Perform main sync try: debug_output = 'anki::sync=debug' in os.environ.get('RUST_LOG', '') if debug_output: click.secho('Syncing deck:', fg='blue') else: click.echo('Syncing deck ... ', nl=False) self.col.sync_collection(auth) if not debug_output: click.echo('done!') else: click.echo('') except Exception as e: click.secho('Error during sync!', fg='red') click.echo(e) raise click.Abort() # Perform media sync try: debug_output = 'media=debug' in os.environ.get('RUST_LOG', '') with cd(self.col.media.dir()): if debug_output: click.secho('Syncing media:', fg='blue') else: click.echo('Syncing media ... ', nl=False) self.col.sync_media(auth) if not debug_output: click.echo('done!') except Exception as e: if "sync cancelled" in str(e): return raise def check_media(self): """Check media (will rebuild missing LaTeX files)""" with cd(self.col.media.dir()): click.echo('Checking media DB ... ', nl=False) output = self.col.media.check() click.echo('done!') if len(output.missing) + len(output.unused) == 0: click.secho('No unused or missing files found.', fg='white') return for file in output.missing: click.secho(f'Missing: {file}', fg='red') if len(output.missing) > 0 \ and click.confirm('Render missing LaTeX?'): out = self.col.media.render_all_latex() if out is not None: nid, _ = out click.secho(f'Error prosessing node: {nid}', fg='red') if click.confirm('Review note?'): note = Note(self, self.col.getNote(nid)) note.review() for file in output.unused: click.secho(f'Unused: {file}', fg='red') if len(output.unused) > 0 \ and click.confirm('Delete unused media?'): for file in output.unused: if os.path.isfile(file): os.remove(file) def find_cards(self, query): """Find card ids in Collection that match query""" return self.col.findCards(query) def find_notes(self, query): """Find notes in Collection and return Note objects""" return (Note(self, self.col.getNote(i)) for i in set(self.col.findNotes(query))) def delete_notes(self, ids): """Delete notes by note ids""" if not isinstance(ids, list): ids = [ids] self.col.remNotes(ids) self.modified = True def get_model(self, model_name): """Get model from model name""" return self.col.models.get(self.model_name_to_id.get(model_name)) def set_model(self, model_name): """Set current model based on model name""" current = self.col.models.current(forDeck=False) if current['name'] == model_name: return current model = self.get_model(model_name) if model is None: click.secho(f'Model "{model_name}" was not recognized!') raise click.Abort() self.col.models.setCurrent(model) return model def rename_model(self, old_model_name, new_model_name): """Rename a model""" if old_model_name not in self.model_names: click.echo('Can''t rename model!') click.echo(f'No such model: {old_model_name}') raise click.Abort() # Change the name model = self.get_model(old_model_name) model['name'] = new_model_name # Update local storage self.model_name_to_id = {m['name']: m['id'] for m in self.col.models.all()} self.model_names = self.model_name_to_id.keys() # Save changes self.col.models.save(model) self.modified = True def list_tags(self): """List all tags""" tags = [(t, len(self.col.findNotes(f'tag:{t}'))) for t in self.col.tags.all()] width = len(max(tags, key=lambda x: len(x[0]))[0]) + 2 filler = " "*(cfg['width'] - 2*width - 8) for (t1, n1), (t2, n2) in zip( sorted(tags, key=lambda x: x[0]), sorted(tags, key=lambda x: x[1])): click.echo(f'{t1:{width}s}{n1:4d}{filler}{t2:{width}s}{n2:4d}') def change_tags(self, query, tags, add=True): """Add/Remove tags from notes that match query""" self.col.tags.bulkAdd(self.col.findNotes(query), tags, add) self.modified = True def edit_model_css(self, model_name): """Edit the CSS part of a given model.""" model = self.get_model(model_name) with tempfile.NamedTemporaryFile(mode='w+', prefix='_apy_edit_', suffix='.css', delete=False) as tf: tf.write(model['css']) tf.flush() retcode = editor(tf.name) if retcode != 0: click.echo(f'Editor return with exit code {retcode}!') return with open(tf.name, 'r') as f: new_content = f.read() if model['css'] != new_content: model['css'] = new_content self.col.models.save(model, templates=True) self.modified = True def list_notes(self, query, verbose=False): """List notes that match a query""" for note in self.find_notes(query): first_field = html_to_screen(note.n.values()[0]) first_field = first_field.replace('\n', ' ') first_field = re.sub(r'\s\s\s+', ' ', first_field) first_field = first_field[:cfg['width']-14] \ + click.style('', reset=True) first = 'Q: ' if note.suspended: first = click.style(first, fg='red') elif 'marked' in note.n.tags: first = click.style(first, fg='yellow') click.echo(f'{first}{first_field}') if verbose: click.echo(f'model: {note.model_name}\n') def list_cards(self, query, verbose=False): """List cards that match a query""" for cid in self.find_cards(query): c = self.col.getCard(cid) question = BeautifulSoup(html_to_screen(c.q()), features='html5lib') question = re.sub(r'\s\s+', ' ', question.get_text().replace('\n', ' ').strip()) answer = BeautifulSoup(html_to_screen(c.a()), features='html5lib') answer = re.sub(r'\s\s+', ' ', answer.get_text().replace('\n', ' ').strip()) card_type = ['new', 'learning', 'review', 'relearning'][c.type] click.echo(f"{click.style('', fg='reset')}" f"{click.style('Q:', fg='yellow')} " f"{question[:cfg['width']]}") if verbose: click.echo(f"{click.style('', fg='reset')}" f"{click.style('A:', fg='yellow')} " f"{answer[:cfg['width']]}") click.echo( f"{click.style('', fg='reset')}" f"{click.style('cid:', fg='yellow')} {cid} " f"{click.style('type:', fg='yellow')} {card_type} " f"{click.style('ease:', fg='yellow')} {c.factor/10}% " f"{click.style('lapses:', fg='yellow')} {c.lapses} " f"{click.style('due:', fg='yellow')} {c.due} " f"{click.style('model:', fg='yellow')} {c.model()['name']}\n") def add_notes_with_editor(self, tags='', model_name=None, deck_name=None, template=None): """Add new notes to collection with editor""" if isinstance(template, Note): input_string = template.get_template() else: if model_name is None or model_name.lower() == 'ask': model_name = choose(sorted(self.model_names), "Choose model:") model = self.set_model(model_name) if deck_name is None: deck_name = self.col.decks.current()['name'] elif deck_name.lower() == 'ask': deck_name = choose(sorted(self.deck_names), "Choose deck:") input_string = [f'model: {model_name}'] if self.n_decks > 1: input_string += [f'deck: {deck_name}'] input_string += [f'tags: {tags}'] if model_name not in cfg['markdown_models']: input_string += ['markdown: false'] input_string += ['\n# Note\n'] input_string += [x for y in [[f'## {field["name"]}', ''] for field in model['flds']] for x in y] input_string = '\n'.join(input_string) + '\n' with tempfile.NamedTemporaryFile(mode='w+', prefix='apy_note_', suffix='.md', delete=False) as tf: tf.write(input_string) tf.flush() retcode = editor(tf.name) if retcode != 0: click.echo(f'Editor return with exit code {retcode}!') return [] return self.add_notes_from_file(tf.name) def add_notes_from_file(self, filename, tags=''): """Add new notes to collection from Markdown file""" return self.add_notes_from_list(markdown_file_to_notes(filename), tags) def add_notes_from_list(self, parsed_notes, tags=''): """Add new notes to collection from note list (from parsed file)""" notes = [] for note in parsed_notes: model_name = note['model'] model = self.set_model(model_name) model_field_names = [field['name'] for field in model['flds']] field_names = note['fields'].keys() field_values = note['fields'].values() if len(field_names) != len(model_field_names): click.echo(f'Error: Not enough fields for model {model_name}!') self.modified = False raise click.Abort() for x, y in zip(model_field_names, field_names): if x != y: click.echo('Warning: Inconsistent field names ' f'({x} != {y})') notes.append(self._add_note(field_values, f"{tags} {note['tags']}", note['markdown'], note.get('deck'))) return notes def add_notes_single(self, fields, tags='', model=None, deck=None): """Add new note to collection from args""" if model is not None: self.set_model(model) self._add_note(fields, tags, False, deck) def _add_note(self, fields, tags, markdown=True, deck=None): """Add new note to collection""" note = self.col.newNote(forDeck=False) if deck is not None: note.model()['did'] = self.deck_name_to_id[deck] if markdown: note.fields = [markdown_to_html(x) for x in fields] else: note.fields = [plain_to_html(x) for x in fields] tags = tags.strip().split() for tag in tags: note.addTag(tag) if not note.dupeOrEmpty(): self.col.addNote(note) self.modified = True else: click.secho('Dupe detected, note was not added!', fg='red') click.echo('Question:') click.echo(list(fields)[0]) return Note(self, note)
def _run(argv=None, exec=True): """Start AnkiQt application or reuse an existing instance if one exists. If the function is invoked with exec=False, the AnkiQt will not enter the main event loop - instead the application object will be returned. The 'exec' and 'argv' arguments will be useful for testing purposes. If no 'argv' is supplied then 'sys.argv' will be used. """ global mw if argv is None: argv = sys.argv # parse args opts, args = parseArgs(argv) opts.base = opts.base or "" opts.profile = opts.profile or "" # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base) # gl workarounds setupGL(pm) # opt in to full hidpi support? if not os.environ.get("ANKI_NOHIGHDPI"): QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # create the app app = AnkiApp(argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): # we've signaled the primary instance, so we should close return # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return pm.setupMeta() if opts.profile: pm.openProfile(opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, opts, args) if exec: app.exec() else: return app
def _run(argv=None, exec=True): """Start AnkiQt application or reuse an existing instance if one exists. If the function is invoked with exec=False, the AnkiQt will not enter the main event loop - instead the application object will be returned. The 'exec' and 'argv' arguments will be useful for testing purposes. If no 'argv' is supplied then 'sys.argv' will be used. """ global mw if argv is None: argv = sys.argv # parse args opts, args = parseArgs(argv) # profile manager pm = ProfileManager(opts.base) pmLoadResult = pm.setupMeta() # gl workarounds setupGL(pm) # opt in to full hidpi support? if not os.environ.get("ANKI_NOHIGHDPI"): QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" os.environ["QT_SCALE_FACTOR_ROUNDING_POLICY"] = "PassThrough" # Opt into software rendering. Useful for buggy systems. if os.environ.get("ANKI_SOFTWAREOPENGL"): QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL) # apply user-provided scale factor os.environ["QT_SCALE_FACTOR"] = str(pm.uiScale()) # create the app QCoreApplication.setApplicationName("Anki") QGuiApplication.setDesktopFileName("anki.desktop") app = AnkiApp(argv) if app.secondInstance(): # we've signaled the primary instance, so we should close return # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # disable help button in title bar on qt versions that support it if isWin and qtminor >= 10: QApplication.setAttribute(Qt.AA_DisableWindowContextHelpButton) # proxy configured? from urllib.request import proxy_bypass, getproxies if "http" in getproxies(): # if it's not set up to bypass localhost, we'll # need to disable proxies in the webviews if not proxy_bypass("127.0.0.1"): print("webview proxy use disabled") proxy = QNetworkProxy() proxy.setType(QNetworkProxy.NoProxy) QNetworkProxy.setApplicationProxy(proxy) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""", ) return if pmLoadResult.loadError: QMessageBox.warning( None, "Preferences Corrupt", """\ Anki's prefs21.db file was corrupt and has been recreated. If you were using multiple \ profiles, please add them back using the same names to recover your cards.""", ) if opts.profile: pm.openProfile(opts.profile) # i18n setupLang(pm, app, opts.lang) if isLin and pm.glMode() == "auto": from aqt.utils import gfxDriverIsBroken if gfxDriverIsBroken(): pm.nextGlMode() QMessageBox.critical( None, "Error", "Your video driver is incompatible. Please start Anki again, and Anki will switch to a slower, more compatible mode.", ) sys.exit(1) # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, opts, args) if exec: app.exec() else: return app
def _run(argv=None, exec=True): """Start AnkiQt application or reuse an existing instance if one exists. If the function is invoked with exec=False, the AnkiQt will not enter the main event loop - instead the application object will be returned. The 'exec' and 'argv' arguments will be useful for testing purposes. If no 'argv' is supplied then 'sys.argv' will be used. """ global mw if argv is None: argv = sys.argv # parse args opts, args = parseArgs(argv) opts.base = opts.base or "" opts.profile = opts.profile or "" # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base) # gl workarounds setupGL(pm) # opt in to full hidpi support? if not os.environ.get("ANKI_NOHIGHDPI"): QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # create the app app = AnkiApp(argv) QCoreApplication.setApplicationName("Anki") if app.secondInstance(): # we've signaled the primary instance, so we should close return # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # proxy configured? from urllib.request import proxy_bypass, getproxies if 'http' in getproxies(): # if it's not set up to bypass localhost, we'll # need to disable proxies in the webviews if not proxy_bypass("127.0.0.1"): print("webview proxy use disabled") proxy = QNetworkProxy() proxy.setType(QNetworkProxy.NoProxy) QNetworkProxy.setApplicationProxy(proxy) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return pm.setupMeta() if opts.profile: pm.openProfile(opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, opts, args) if exec: app.exec() else: return app
def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiApp]: """Start AnkiQt application or reuse an existing instance if one exists. If the function is invoked with exec=False, the AnkiQt will not enter the main event loop - instead the application object will be returned. The 'exec' and 'argv' arguments will be useful for testing purposes. If no 'argv' is supplied then 'sys.argv' will be used. """ global mw global profiler if argv is None: argv = sys.argv # parse args opts, args = parseArgs(argv) if opts.version: print(f"Anki {appVersion}") return None elif opts.syncserver: from anki.syncserver import serve serve() return None if PROFILE_CODE: profiler = cProfile.Profile() profiler.enable() if (getattr(sys, "frozen", False) and os.getenv("QT_QPA_PLATFORM") == "wayland" and not os.getenv("ANKI_WAYLAND")): # users need to opt in to wayland support, given the issues it has print("Wayland support is disabled by default due to bugs.") print("You can force it on with an env var: ANKI_WAYLAND=1") os.environ["QT_QPA_PLATFORM"] = "xcb" # default to specified/system language before getting user's preference so that we can localize some more strings lang = anki.lang.get_def_lang(opts.lang) anki.lang.set_lang(lang[1]) # profile manager pm = None try: pm = ProfileManager(opts.base) pmLoadResult = pm.setupMeta() except: # will handle below traceback.print_exc() pm = None if pm: # gl workarounds setupGL(pm) # apply user-provided scale factor os.environ["QT_SCALE_FACTOR"] = str(pm.uiScale()) # opt in to full hidpi support? if not os.environ.get("ANKI_NOHIGHDPI") and qtmajor == 5: QCoreApplication.setAttribute( Qt.ApplicationAttribute.AA_EnableHighDpiScaling) # type: ignore QCoreApplication.setAttribute( Qt.ApplicationAttribute.AA_UseHighDpiPixmaps) # type: ignore os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" os.environ["QT_SCALE_FACTOR_ROUNDING_POLICY"] = "PassThrough" # Opt into software rendering. Useful for buggy systems. if os.environ.get("ANKI_SOFTWAREOPENGL"): QCoreApplication.setAttribute( Qt.ApplicationAttribute.AA_UseSoftwareOpenGL) if (isWin and qtmajor == 5 and (qtminor == 14 or (qtminor == 15 and qtpoint == 0)) and "QT_QPA_PLATFORM" not in os.environ): os.environ["QT_QPA_PLATFORM"] = "windows:altgr" # create the app QCoreApplication.setApplicationName("Anki") QGuiApplication.setDesktopFileName("anki.desktop") app = AnkiApp(argv) if app.secondInstance(): # we've signaled the primary instance, so we should close return None if not pm: QMessageBox.critical( None, tr.qt_misc_error(), tr.profiles_could_not_create_data_folder(), ) return None # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.ApplicationAttribute.AA_DontShowIconsInMenus) # disable help button in title bar on qt versions that support it if isWin and qtmajor == 5 and qtminor >= 10: QApplication.setAttribute( QApplication.Attribute. AA_DisableWindowContextHelpButton # type: ignore ) # proxy configured? from urllib.request import getproxies, proxy_bypass disable_proxies = False try: if "http" in getproxies(): # if it's not set up to bypass localhost, we'll # need to disable proxies in the webviews if not proxy_bypass("127.0.0.1"): disable_proxies = True except UnicodeDecodeError: # proxy_bypass can't handle unicode in hostnames; assume we need # to disable proxies disable_proxies = True if disable_proxies: print("webview proxy use disabled") proxy = QNetworkProxy() proxy.setType(QNetworkProxy.ProxyType.NoProxy) QNetworkProxy.setApplicationProxy(proxy) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, tr.qt_misc_error(), tr.qt_misc_no_temp_folder(), ) return None # make image resources available from aqt.utils import aqt_data_folder QDir.addSearchPath("icons", os.path.join(aqt_data_folder(), "qt", "icons")) if pmLoadResult.firstTime: pm.setDefaultLang(lang[0]) if pmLoadResult.loadError: QMessageBox.warning( None, tr.profiles_prefs_corrupt_title(), tr.profiles_prefs_file_is_corrupt(), ) if opts.profile: pm.openProfile(opts.profile) # i18n & backend backend = setupLangAndBackend(pm, app, opts.lang, pmLoadResult.firstTime) driver = pm.video_driver() if isLin and driver == VideoDriver.OpenGL: from aqt.utils import gfxDriverIsBroken if gfxDriverIsBroken(): pm.set_video_driver(driver.next()) QMessageBox.critical( None, tr.qt_misc_error(), tr.qt_misc_incompatible_video_driver(), ) sys.exit(1) # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, backend, opts, args) if exec: app.exec() else: return app if PROFILE_CODE: write_profile_results() return None
def _run(argv=None, exec=True): """Start AnkiQt application or reuse an existing instance if one exists. If the function is invoked with exec=False, the AnkiQt will not enter the main event loop - instead the application object will be returned. The 'exec' and 'argv' arguments will be useful for testing purposes. If no 'argv' is supplied then 'sys.argv' will be used. """ global mw if argv is None: argv = sys.argv # parse args opts, args = parseArgs(argv) opts.base = opts.base or "" opts.profile = opts.profile or "" if opts.dev: os.environ["ANKIDEV"] = "1" print("running in dev mode") if isMac: if getattr(sys, 'frozen', None): # on osx we'll need to add the qt plugins to the search path rd = os.path.abspath(moduleDir + "/../../..") QCoreApplication.setLibraryPaths([rd]) QFont.insertSubstitution(".Lucida Grande UI", "Lucida Grande") # create the app QCoreApplication.setApplicationName("Anki") app = AnkiApp(argv) if app.secondInstance(): # we've signaled the primary instance, so we should close return # disable icons on mac; this must be done before window created if isMac: app.setAttribute(Qt.AA_DontShowIconsInMenus) # disable help button in title bar on qt versions that support it if isWin and qtminor >= 10: QApplication.setAttribute(Qt.AA_DisableWindowContextHelpButton) # we must have a usable temp dir try: tempfile.gettempdir() except: QMessageBox.critical( None, "Error", """\ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return # qt version must be up to date if qtmajor <= 4 and qtminor <= 6: QMessageBox.warning( None, "Error", "Your Qt version is known to be buggy. Until you " "upgrade to a newer Qt, you may experience issues such as images " "failing to show up during review.") # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) # i18n setupLang(pm, app, opts.lang) # remaining pm init pm.ensureProfile() # load the main window import aqt.main mw = aqt.main.AnkiQt(app, pm, args) if exec: app.exec_() else: return app