def __init__(self, parent): super().__init__(parent) self.setWindowTitle( _('{appname} v{appversion}').format(appname=Meta.appName(), appversion=str( Meta.appVersion()))) iodev = QtCore.QFile(':/about.html') iodev.open(iodev.ReadOnly) try: about = bytes(iodev.readAll()).decode('utf-8') finally: iodev.close() about = about.format(author=Meta.appAuthor()) text = QtWidgets.QTextBrowser(self) text.setHtml(about) text.setOpenExternalLinks(True) btn = QtWidgets.QPushButton(_('Done'), self) bld = LayoutBuilder(self) with bld.vbox() as vbox: vbox.addWidget(text) with bld.hbox() as buttons: buttons.addStretch(1) buttons.addWidget(btn) btn.clicked.connect(self.accept) self.resize(800, 600)
def _launchUpdate(self): self.setTitle(_('Updating')) self.setSubTitle(_('Update in progress...')) self.wizard().button(self.wizard().CancelButton).setEnabled(False) self.wizard().button(self.wizard().BackButton).setEnabled(False) self._process = QtCore.QProcess(self) self._process.errorOccurred.connect(self._onError) self._process.finished.connect(self._onFinished) self._process.readyReadStandardError.connect(self._onStderr) self._process.readyReadStandardOutput.connect(self._onStdout) manifest = Meta.manifest() filename = os.path.join( Meta.dataPath('images'), manifest['leonardo']['firmware']['current']['name']) args = [ '-patmega32u4', '-cavr109', '-P%s' % self._devname, '-b57600', '-D', '-Uflash:w:%s:i' % filename, ] if platform.system() != 'Windows': args.insert(0, '-C%s' % Meta.avrdudeConf()) self._process.start(Settings().avrdude(), args)
def setup(): logging.TRACE = 5 def trace(self, fmt, *args, **kwargs): if self.isEnabledFor(logging.TRACE): self._log(logging.TRACE, fmt, args, **kwargs) # pylint: disable=W0212 logging.Logger.trace = trace logging.addLevelName(logging.TRACE, 'TRACE') logging.basicConfig( level=logging.INFO, format='%(asctime)-15s %(levelname)-8s %(name)-15s %(message)s') if platform.system() == 'Darwin': localeName = str(QtCore.QLocale.system().uiLanguages()[0]).replace( '-', '_') else: localeName = str(QtCore.QLocale.system().name()) try: locale.setlocale(locale.LC_ALL, localeName) except locale.Error as exc: logger = logging.getLogger('dsremap') logger.error('Cannot set locale to "%s": %s', localeName, exc) trans = gettext.translation(Meta.appName(), localedir=Meta.messages(), languages=[localeName], fallback=True) trans.install()
def cleanup(self): files = set() for configuration in self.configurations(): filename = configuration.thumbnail() if filename is None: continue files.add(os.path.basename(filename)) for name in os.listdir(Meta.dataPath('thumbnails')): if name not in files: os.remove(os.path.join(Meta.dataPath('thumbnails'), name))
def _gotManifest(self, downloader): downloader.downloadSize.disconnect(self._onSize) downloader.downloadProgress.disconnect(self._onProgress) try: unused, data = downloader.result() except AbortedError: self.logger.info('Aborted manifest') if self._getValue(self._manifest, self._path + ('current', 'version')) != 0: self.accept() else: self.reject() return except DownloadError as exc: self.logger.error('Downloading manifest: %s', exc) if self._getValue(self._manifest, self._path + ('current', 'version')) != 0: self.accept() else: self._msg.setText( _('Cannot download manifest:{error}').format( error='<br /><font color="red">%s</font>' % str(exc))) self._btn.setText(_('Bummer')) self._state = self.STATE_ERROR return data = json.loads(data) self._getValue(self._manifest, self._path)['latest'] = self._getValue( data, self._path + ('latest', )) if self._getValue(self._manifest, self._path + ('current', 'version')) == self._getValue( self._manifest, self._path + ('latest', 'version')): self.logger.info('Up to date') self.accept() return handle, filename = tempfile.mkstemp(dir=Meta.dataPath('images')) os.close(handle) self._state = self.STATE_DL self._msg.setText(_('Downloading image...')) self._downloader = FileDownloader(self, self.mainWindow().manager(), filename, callback=self._gotFile) self._downloader.downloadSize.connect(self._onSize) self._downloader.downloadProgress.connect(self._onProgress) self._downloader.get(Meta.imagesUrl().format( filename=self._getValue(self._manifest, self._path + ('latest', 'name'))))
def __init__(self): super().__init__([]) self.setApplicationName(Meta.appName()) self.setApplicationVersion(str(Meta.appVersion())) self.setOrganizationDomain(Meta.appDomain()) self.setWindowIcon(QtGui.QIcon(':icons/gamepad.svg')) # Builtin messages. trans = QtCore.QTranslator(self) path = QtCore.QLibraryInfo.location( QtCore.QLibraryInfo.TranslationsPath) trans.load(QtCore.QLocale.system(), 'qtbase', '_', path) self.installTranslator(trans)
def headers(): # Log messages from tools.build.genids import main main([ '-o', os.path.join('src', 'arduino', 'dsremap', 'messages.h'), '-s', os.path.join('tools', 'strings.pickle'), os.path.join('src', 'arduino', 'dsremap', 'messages.txt'), ]) # Opcodes from dsrlib.compiler.opcodes import export export(os.path.join('src', 'arduino', 'dsremap', 'opcodes.h')) # Version with codecs.getwriter('utf-8')(open( os.path.join('src', 'arduino', 'dsremap', 'version.h'), 'wb')) as fileobj: from dsrlib.meta import Meta version = Meta.firmwareVersion() fileobj.write('#ifndef _DSREMAP_VERSION_H_\n') fileobj.write('#define _DSREMAP_VERSION_H_\n') fileobj.write('#define FW_VERSION_MAJOR %d\n' % version.major) fileobj.write('#define FW_VERSION_MINOR %d\n' % version.minor) fileobj.write('#define FW_VERSION_PATCH %d\n' % version.patch) fileobj.write('#endif\n')
def _tick(self): if self._state == 0: newDevs = Meta.listSerials() - self._devices if newDevs: # XXXTODO: support multiple devices ? Would be hard since they disappear after a few seconds... self._devname = newDevs.pop() self.setSubTitle( _('Found Leonardo at {path}.').format(path=self._devname)) self._state = 1 if self._state == 1: # Reset error = None for unused in range(5): try: with serial.Serial(self._devname, 2000): pass except serial.SerialException as exc: error = exc time.sleep(0.5) # Retry else: break else: self.setSubTitle( _('Error resetting the device: {error}').format( error=str(error))) self._finished = True self._timer.stop() self.completeChanged.emit() return self._state = 2 if self._state == 2: self._state = 3 self._timer.stop() self._launchUpdate()
def exec_(self): # pylint: disable=C0103 self._msg.setText(_('Downloading manifest...')) self._downloader = StringDownloader(self, self.mainWindow().manager(), callback=self._gotManifest) self._downloader.downloadSize.connect(self._onSize) self._downloader.downloadProgress.connect(self._onProgress) self._downloader.get(Meta.imagesUrl().format(filename='manifest.json')) return super().exec_()
def initializePage(self): self.setTitle(_('Looking for Leonardo')) self.setSubTitle(_('Release the <b>reset</b> button now.')) self._state = 0 self._finished = False self._devices = Meta.listSerials() self._devname = None self._process = None self._timer.start(200)
def initializePage(self): self.setTitle(_('Image copy')) self.setSubTitle(_('Copying image file')) manifest = Meta.manifest() name = manifest['rpi0w-v2']['image']['current']['name'] self._src = os.path.join(Meta.dataPath('images'), name) self._dst = os.path.join( QtCore.QStandardPaths.writableLocation( QtCore.QStandardPaths.DownloadLocation), '%s.img' % os.path.splitext(name)[0]) ssid, password = self.wizard().wifi() self._thread = ImageCopyThread(self._src, self._dst, ssid, password, self.wizard().ssh()) self._thread.progress.connect(self._onProgress) self._thread.finished.connect(self._onFinished) self._thread.error.connect(self._onError) self._thread.start()
def _gotFile(self, downloader): downloader.downloadSize.disconnect(self._onSize) downloader.downloadProgress.disconnect(self._onProgress) try: downloader.result() except AbortedError: self.logger.info('Aborted image') if self._getValue(self._manifest, self._path + ('current', 'version')) != 0: self.accept() else: self.reject() return except DownloadError as exc: self.logger.error('Downloading image: %s', exc) if self._getValue(self._manifest, self._path + ('current', 'version')) != 0: self.accept() else: self._msg.setText( _('Cannot download image: {error}').format( error='<font color="red">%s</font>' % str(exc))) self._btn.setText(_('Bummer')) self._state = self.STATE_ERROR return src = downloader.filename() dst = os.path.join( Meta.dataPath('images'), self._getValue(self._manifest, self._path + ('latest', 'name'))) if os.path.exists(dst): os.remove(dst) os.rename(src, dst) self._getValue(self._manifest, self._path)['current'] = self._getValue( self._manifest, self._path).pop('latest') Meta.updateManifest(self._manifest) self.accept()
def gotChangelog(downloader): self._changelogDl = None try: _unused, text = downloader.result() except AbortedError: self.logger.info('Changelog download aborted') return except (DownloadError, SSLError) as exc: self.logger.exception('Cannot download changelog: %s', exc) return changelog = Changelog(text) if changelog.changesSince(Meta.appVersion()): win = ChangelogView(self, changelog) win.show() win.raise_()
def __init__(self, parent, changelog): super().__init__(parent) self.setWindowTitle(_('New release available')) view = QtWidgets.QTextBrowser(self) view.setHtml(HTMLChangelogFormatter().format( changelog.changesSince(Meta.appVersion()))) view.setOpenExternalLinks(True) btn = QtWidgets.QPushButton(_('OK'), self) btn.clicked.connect(self.accept) bld = LayoutBuilder(self) with bld.vbox() as vbox: vbox.addWidget(view) with bld.hbox() as hbox: hbox.addStretch(1) hbox.addWidget(btn) self.resize(640, 480)
def __init__(self, parent, *, path, **kwargs): super().__init__(parent, **kwargs) self._path = path self._state = self.STATE_MANIFEST self._manifest = Meta.manifest() self._downloader = None self._msg = QtWidgets.QLabel(self) self._progress = QtWidgets.QProgressBar(self) bld = LayoutBuilder(self) with bld.vbox() as vbox: vbox.setContentsMargins(5, 5, 5, 5) vbox.addWidget(self._msg) vbox.addWidget(self._progress) with bld.hbox() as hbox: self._btn = QtWidgets.QPushButton(_('Cancel'), self) hbox.addStretch(1) hbox.addWidget(self._btn) self._btn.clicked.connect(self._cancel)
def exe(): if platform.system() == 'Darwin': subprocess.run([sys.executable, 'setup.py', 'py2app'], check=True) import dmgbuild dmgbuild.build_dmg('dsremap-%s.dmg' % str(Meta.appVersion()), Meta.appName(), 'dmgbuild-settings.py') elif platform.system() == 'Windows': subprocess.run([sys.executable, 'setup.py', 'build'], check=True) distdir = r'build\exe.win-%s-%s' % (platform.machine().lower(), '%d.%d' % sys.version_info[:2]) with codecs.getwriter('utf-8')(open('installer.nsi', 'wb')) as dst: with codecs.getreader('utf-8')(open('installer.nsi.in', 'rb')) as src: for line in src: dst.write( line.replace('@APPNAME@', Meta.appName()).replace( '@APPVERSION@', str(Meta.appVersion())).replace( '@DISTDIR@', distdir).replace('@WEBSITE@', Meta.appSite())) import winreg key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE', 0, winreg.KEY_READ | winreg.KEY_WOW64_32KEY) try: path = winreg.QueryValue(key, 'NSIS') except: raise RuntimeError( 'Cannot find NSIS in registry, is it installed ?') finally: winreg.CloseKey(key) # Assuming NSIS 3.x here subprocess.run([ os.path.join(path, 'makensis.exe'), '/INPUTCHARSET', 'UTF8', 'installer.nsi' ], check=True) elif platform.system() == 'Linux': with codecs.getwriter('utf-8')(open('appimage.yml', 'wb')) as dst: with codecs.getreader('utf-8')(open('appimage.yml.in', 'rb')) as src: for line in src: dst.write(line.replace('@VERSION@', str(Meta.appVersion()))) subprocess.run( ['appimage-builder', '--recipe', 'appimage.yml', '--skip-test'], check=True) else: raise RuntimeError('Unsupported platform')
setup(app=['dsremap.py'], options={ 'py2app': { 'iconfile': os.path.join('icons', 'dsremap.icns') } }, setup_requires=['py2app'], data_files=data_files) if platform.system() == 'Windows': from cx_Freeze import setup, Executable options = { 'include_files': [ (r'res\avrdude.conf', r'resources\avrdude.conf'), (r'res\configurations', r'resources\configurations'), (r'i18n', r'resources\i18n'), ] } setup(name=Meta.appName(), version=str(Meta.appVersion()), description=Meta.appName(), options={'build_exe': options}, executables=[ Executable('dsremap.py', base='Win32GUI', icon=r'icons\dsremap.ico') ])
def pathFor(self, filename): dst = Meta.newThumbnail(filename) self._zipobj.extract(filename, os.path.dirname(dst)) os.rename(os.path.join(os.path.dirname(dst), filename), dst) return dst
def __init__(self): # pylint: disable=R0915 super().__init__() self._manager = QtNetwork.QNetworkAccessManager(self) self._workspace = Workspace() self.setCentralWidget( WorkspaceView(self, mainWindow=self, workspace=self._workspace)) self._workspace.load() self._devenum = DeviceEnumerator() self.setWindowTitle( _('{appName} v{appVersion}').format(appName=Meta.appName(), appVersion=str( Meta.appVersion()))) self.statusBar() filemenu = QtWidgets.QMenu(_('File'), self) filemenu.addAction( uicommands.ExportConfigurationUICommand( self, mainWindow=self, container=self.centralWidget())) filemenu.addAction( uicommands.ImportConfigurationUICommand(self, mainWindow=self, workspace=self._workspace)) filemenu.addAction( uicommands.ExportBytecodeUICommand(self, mainWindow=self, workspace=self._workspace, container=self.centralWidget())) self.menuBar().addMenu(filemenu) editmenu = QtWidgets.QMenu(_('Edit'), self) editmenu.addAction(uicommands.UndoUICommand(self, mainWindow=self)) editmenu.addAction(uicommands.RedoUICommand(self, mainWindow=self)) editmenu.addAction( uicommands.ShowSettingsDialogUICommand(self, mainWindow=self)) self.menuBar().addMenu(editmenu) devmenu = uicommands.DeviceMenu(self, mainWindow=self, workspace=self._workspace, enumerator=self._devenum) self.menuBar().addMenu(devmenu) uploadmenu = uicommands.UploadMenu(self, mainWindow=self, container=self.centralWidget(), workspace=self._workspace, enumerator=self._devenum) self.menuBar().addMenu(uploadmenu) helpmenu = QtWidgets.QMenu(_('Help'), self) helpmenu.addAction( uicommands.ShowAboutDialogUICommand(self, mainWindow=self)) helpmenu.addAction(uicommands.OpenDocsUICommand(self, mainWindow=self)) self.menuBar().addMenu(helpmenu) with Settings().grouped('UIState') as settings: if settings.contains('WindowGeometry'): self.restoreGeometry(settings.value('WindowGeometry')) else: self.resize(1280, 600) self.centralWidget().loadState(settings) self.raise_() self.show() settings = Settings() if settings.isFirstVersionLaunch(): QtGui.QDesktopServices.openUrl(QtCore.QUrl( Meta.documentationUrl())) self.check() def gotChangelog(downloader): self._changelogDl = None try: _unused, text = downloader.result() except AbortedError: self.logger.info('Changelog download aborted') return except (DownloadError, SSLError) as exc: self.logger.exception('Cannot download changelog: %s', exc) return changelog = Changelog(text) if changelog.changesSince(Meta.appVersion()): win = ChangelogView(self, changelog) win.show() win.raise_() self._changelogDl = StringDownloader(self, self.manager(), callback=gotChangelog) self._changelogDl.get(Meta.changelogUrl())
def __init__(self, *args, **kwargs): super().__init__( *args, text=_('About {appname}...').format(appname=Meta.appName()), **kwargs) self.setMenuRole(self.AboutRole)
def dropEvent(self, event): src = event.mimeData().urls()[0].toLocalFile() dst = Meta.newThumbnail(src) shutil.copyfile(src, dst) cmd = commands.ChangeConfigurationThumbnailCommand(configuration=self.configuration(), filename=dst) self.history().run(cmd)
#!/usr/bin/env python3 import os import sys import shutil from PyQt5 import QtCore sys.path.insert( 0, os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'src'))) from dsrlib.ui.main import Application from dsrlib.meta import Meta if __name__ == '__main__': app = Application() settings = QtCore.QSettings() settings.clear() settings.sync() shutil.rmtree(Meta.dataPath())
def isFirstVersionLaunch(self): with self.grouped('Versions'): key = 'v%d_%d_%d' % Meta.appVersion() ret = self.booleanValue(key, True) self.setBooleanValue(key, False) return ret
def _onStderr(self): text = Meta.decodePlatformString(self._process.readAllStandardError()) self.logger.info('E: %s', text.strip())
def _onStdout(self): text = Meta.decodePlatformString(self._process.readAllStandardOutput()) self.logger.info('O: %s', text.strip())
def accept(self): QtGui.QDesktopServices.openUrl(QtCore.QUrl(Meta.releasesUrl())) return super().accept()
def do(self): QtGui.QDesktopServices.openUrl(QtCore.QUrl(Meta.documentationUrl()))
def check(self): # pylint: disable=R0912,R0914,R0915 if platform.system() == 'Linux': # 1. plugdev group groupOk = False retcode = 0 try: proc = subprocess.Popen(['groups'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) outData, unused = proc.communicate() except Exception as exc: # pylint: disable=W0703 msg = str(exc) retcode = 1 else: retcode = proc.returncode msg = _('<i>groups</i> exited with code {code}').format( code=proc.returncode) if retcode: QtWidgets.QMessageBox.warning( self, _('Cannot find groups'), _('Unable to find out which groups you belong to:<p />{error}<p />For this program to work correctly, you must belong to the <i>plugdev</i> group.' ).format(error=msg)) groupOk = True else: groups = Meta.decodePlatformString(outData).split() groupOk = 'plugdev' in groups # 2. udev rules udevOk = os.path.exists('/etc/udev/rules.d/80-dsremap.rules') if not (udevOk and groupOk): msg = _( 'The following operations must be done before this program can work correctly:' ) + '<p /><ul>' if not udevOk: msg += '<li>' + _( 'Install udev rules to allow HID communication' ) + '</li>' if not groupOk: msg += '<li>' + _( 'Add you to the <i>plugdev</i> group') + '</li>' msg += '</ul>' if not groupOk: msg += '<p />' + _( 'You may have to logout and login again, or even reboot, and unplug/replug the Arduino for those changes to apply.' ) msg += '<p />' + _('You may be prompted for your password.') QtWidgets.QMessageBox.information(self, _('System setup'), msg) try: if not groupOk: sudoLaunch('usermod', '-a', '-G', 'plugdev', os.environ['USER']) if not udevOk: # Cannot copy a file from the res directory because root hasn't the permissions on the mount dir... sudoLaunch( 'sh', '-c', 'echo \'SUBSYSTEM=="usb", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="09cc", GROUP="plugdev", MODE="0660"\' > /etc/udev/rules.d/80-dsremap.rules' ) sudoLaunch( 'sh', '-c', 'echo \'SUBSYSTEM=="usb", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0ce6", GROUP="plugdev", MODE="0660"\' > /etc/udev/rules.d/80-dsremap.rules' ) sudoLaunch( 'sh', '-c', 'echo \'SUBSYSTEM=="tty", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="8036", GROUP="plugdev", MODE="0660"\' >> /etc/udev/rules.d/80-dsremap.rules' ) except SudoNotFound: QtWidgets.QMessageBox.critical( self, _('Error'), _('Unable to find <i>sudo</i> on the path.')) except SudoError as exc: QtWidgets.QMessageBox.critical( self, _('Error'), _('Command failed with exit code {code}.<p />{stderr}' ).format(code=exc.code, stderr=exc.stderr)) with Settings().grouped('DefaultConfigurations') as settings: imported = settings.value('Imported').split( ',') if settings.contains('Imported') else [] importer = JSONImporter() for name in glob.glob( os.path.join(Meta.defaultConfigurations(), '*.zip')): with zipfile.ZipFile(name, mode='r') as zipobj: configuration = importer.read(zipobj) if configuration.uuid() not in imported: imported.append(configuration.uuid()) self._workspace.configurations().addItem(configuration) settings.setValue('Imported', ','.join(imported)) if not Settings().firmwareUploaded(): wizard = FirstLaunchWizard(self, mainWindow=self) wizard.exec_()
# Mock imports from Meta class PyQt5: class QtCore: pass class QtGui: pass sys.modules['PyQt5'] = PyQt5 sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) from dsrlib.meta import Meta project = Meta.appName() copyright = '2020-2021, %s' % Meta.appAuthor() author = Meta.appAuthor() # The short X.Y version version = '%d.%d' % Meta.appVersion()[:2] # The full version, including alpha/beta/rc tags release = str(Meta.appVersion()) # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be
def save(self): filename = os.path.join(Meta.dataPath(), 'workspace.json') writer = JSONWriter() with codecs.getwriter('utf-8')(open(filename, 'wb')) as fileobj: writer.write(fileobj, self)