def deletePreset(self, *_):
		settings.setValue('customRecordingPresets', [
			setting
			for setting in settings.value('customRecordingPresets', [])
			if setting != self.uiPresets.currentData()
		])
		self.uiPresets.removeItem(self.uiPresets.currentIndex())
		self.selectCorrectPreset() #Select Custom again.
	def savePreset(self):
		itemData = self.uiPresets.currentData()
		itemData['temporary'] = False
		itemData['name'] = f"{self.uiHRes.value()}×{self.uiVRes.value()} @ {int(self.uiFps.value())}fps" #Store name, probably will be editable one day.
		presets = [itemData] + settings.value('customRecordingPresets', [])
		settings.setValue('customRecordingPresets', presets)
		self.uiPresets.setItemData(self.uiPresets.currentIndex(), itemData)
		self.uiPresets.setItemText(self.uiPresets.currentIndex(), itemData['name'])
		
		self.updatePresetDropdownButtons()
Beispiel #3
0
	def show(self, screen):
		"""Switch between the screens of the back-of-camera interface.
		
		Example: self.uiPlayAndSave.clicked.connect(lambda: window.show('play_and_save'))"""
		
		#DDR 2019-03-01: This function will occasionally be reentered. This
		#occurs when a nav button is tapped twice in a very short period of
		#time. (Less than our touchscreen debounce should be.) This manifests
		#itself as a black (frozen?) screen on the camera. I believe this has
		#something to do with Qt's signals being inappropriately
		#multithreaded, but I do not know for sure. This bug, at least, has
		#the uncomfortable implication that *all* the signal handlers we've
		#registered for buttons are reentrant, whether that's safe or not."""
		if(self._screenStack[-1] != self.currentScreen):
			log.warn(f'Tried to open {screen}, but another screen ({self._screenStack[-1]}) is already in the process of being opened from {self.currentScreen}')
			return
		
		# Prevent screen from disappearing entirely. Because we open the screen next screen then hide the current, if both are the same it shows then hides the screen so it goes away.
		if(screen == self.currentScreen):
			log.warn(f'Tried to open {screen}, but it was already open. This probably indicates an application logic error.') # Also print a warning, because this probably indicates a logic error.
			return dbg() #This gets stubbed out in production, so it doesn't actually freeze the app.
		
		report("screen_transition", {"new": screen, "old": self.currentScreen})
		
		#If you loop through to a screen again, which can easily happen because we don't always use window.back() to return from screens, discard the loop to keep history from growing forever.
		self._screenStack += [screen]
		self._screenStack = self._screenStack[:self._screenStack.index(screen)+1]
		
		try:
			self._ensureInstantiated(screen)
			
			if hasattr(self._screens[screen], 'onShow'):
				self._screens[screen].onShow()	
			self._screens[screen].show()
			
			self._screens[self.currentScreen].hide()
			if hasattr(self._screens[self.currentScreen], 'onHide'):
				self._screens[self.currentScreen].onHide()
			self.hideInput()
			
			#Only set the setting value after everything has worked, to avoid trying to load a crashing screen.
			self.currentScreen = screen
			settings.setValue('current screen', screen)
			
			#print(f'current breadcrumb: {self._screenStack}')
		except Exception as e:
			#Add some resiliance: If the screen isn't openable, allow other screens to be. (Otherwise we always trip the double-opening protection.)
			self._screenStack = self._screenStack[:-1]
			raise e
	def __init__(self, window):
		super().__init__()
		self.setupUi(self)

		# API init.
		self.control = api.control()

		# Panel init.
		self.setFixedSize(window.app.primaryScreen().virtualSize())
		self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
		self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
		self.uiCalibratedOnTemplate = self.uiCalibratedOn.text()
		
		# Button binding.
		api.observe('cameraSerial', self.recieveSerial)
		self.uiSetSerialBtn.clicked.connect(self.sendSerial)
	
		self.uiCal.clicked.connect(self.runCal)

		self.uiExportCalData.clicked.connect(self.runExportCalData)

		self.uiImportCalData.clicked.connect(self.runImportCalData)
		
		settings.observe('last factory cal', None, self.recieveFactoryCalDate)
		
		settings.observe('debug controls enabled', False, lambda x:
			self.uiShowDebugControls.setChecked(x) )
		self.uiShowDebugControls.stateChanged.connect(lambda x:
			settings.setValue('debug controls enabled', bool(x)) )
		
		self.uiDone.clicked.connect(lambda: window.show('main'))
	def __init__(self, window):
		super().__init__()
		self.setupUi(self)

		# API init.
		self.control = api.control()
		
		# Panel init.
		self.setFixedSize(window.app.primaryScreen().virtualSize())
		self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
		self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
		
		# Button binding.
		self.window_ = window
		
		#Side and rotated are not quite correct, as askBeforeDiscarding is, but they are correct enough for now. Having the final result come from two values confused things a bit.
		self.uiInterfaceSide.setCurrentIndex(
			int(settings.value('interface handedness', None) == 'left'))
		self.uiInterfaceSide.currentIndexChanged.connect(lambda index:
			settings.setValue('interface handedness', 'left' if index else 'right') )
		settings.observe('interface handedness', None, self.updateInterfaceSide)
		
		self.uiInterfaceRotated.setCurrentIndex(
			int(settings.value('interface rotation', None) == '180'))
		self.uiInterfaceRotated.currentIndexChanged.connect(lambda index:
			settings.setValue('interface rotation', '180' if index else '0') )
		settings.observe('interface rotation', None, self.updateInterfaceSide)
		
		settings.observe('theme', 'dark', self.updateInterfaceSide)
		
		#Note the operations attached here:
		#	- We must observe a silenced callback to update the state. This prevents an infinite loop.
		#	- We update the state from a callback attached to the widget.
		settings.observe('ask before discarding', 'if not reviewed', self.updateAskBeforeDiscarding)
		self.uiAskBeforeDiscarding.currentIndexChanged.connect(lambda index:
			settings.setValue('ask before discarding',
				["always", "if not reviewed", "never"][index] ) )
		
		
		api.observe('dateTime', self.stopEditingDate) #When the date is changed, always display the update even if an edit is in progress. Someone probably set the date some other way instead of this, or this was being edited in error.
		self.uiSystemTime.focusInEvent = self.sysTimeFocusIn
		self.uiSystemTime.editingFinished.connect(self.sysTimeFocusOut)
		self._timeUpdateTimer = QtCore.QTimer()
		self._timeUpdateTimer.timeout.connect(self.updateDisplayedSystemTime)
		
		
		self.uiDone.clicked.connect(window.back)
Beispiel #6
0
	def saveChanges(self, *_):
		ioMapping = api.apiValues.get('ioMapping')
		
		existingVirtuals = settings.value('virtually triggered actions', {})
		newVirtuals = {
			trigger
			for trigger, configuration in self.newIOMapping.items()
			if 'virtual' in configuration.values()
		}
		
		virtuals = { #Merge old and new virtuals configurations, filtering out old virtuals which are no longer and adding new virtuals.
			action: { #Merge the keys of the old and new virtual, since someone could have updated only, say, invert.
				key: default(
					self.newIOMapping.get(action, {}).get(key), #Find "most recent" value, defaulting to false if it was never set.
					existingVirtuals.get(action, {}).get(key),
					False )
				for key in self.newIOMapping.get(action, {}).keys() | existingVirtuals.get(action, {}).keys()
			}
			for action in existingVirtuals.keys() | newVirtuals #Consider only actions which are or were virtuals. Now, we need to filter out items which aren't verticals any more, and merge those which are.
			if self.newIOMapping.get(action, {}).get('source') in (None, 'virtual') #Filter out newly non-virtual actions, allowing unchanged and newly-virtual actions.
		}
		settings.setValue('virtually triggered actions', virtuals)
		pp({
			'newIOMapping': self.newIOMapping,
			'existingVirtuals': existingVirtuals, 
			'newVirtuals': newVirtuals, 
			'virtuals': virtuals,
		})
		
		self.control.set('ioMapping', dump('sending IO mapping', { #Send along the update delta. Strictly speaking, we could send the whole thing and not a delta, but it seems more correct to only send what we want to change. data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAMAAAApB0NrAAAAdVBMVEUAAAAAFBwCJTYbKjQjOUwoPlI/QT5GTFRDVmVQY3N4XGhgZGbUPobePYXgQY2BZnedZ4GBeH7EYqCXdYjbX5yigJOIjI+NkZTgfai5jZ2gnaHkire6m6fknsPxm8CtsrToqczrr8S9wsTwttHztszzuNPe4N2lYYACAAAAAXRSTlMAQObYZgAAAaBJREFUOMt9kwFTgzAMhVGn4ibMuqql40WFxP//E01TNgpu5o47Lnx9SV9CVS3Dp6iuRVPEZeK1RJrWN43/V+WKWinjg2/zyzUZDxGGvyA0MwkhypC/zHgiFgiqNeObkiGAyXIFo00W3QBdB5h0wWieMtVi7GJ0255XjCcRIR8ABMHh/WOIyEy1ZIRF0Onjhrr+jFbreWZaZGDvemX7n7h7ykxRrDkyCXo3DIOa0+/cw+YddnnvX086XjvBNsbaKfPtNjf3W3xpeuEOhPpt/Nnd98yEt/1LMnE1CO0azkX3eNCqzNBL0Hr31Dp+c5uHu4OoxToMnWr1FwpdrG83qZY5gYkBUMzUd67ew0RERhCMSHgx94A+pcxPQqoG40umBfOYETtPoHwCxf4cNat7ocshpgAUTDY1cD5MYypmTU2qCVHtYEs6B86t2cXUZBYOQRaBUWYPIKPtT26QTufJoMkd8PS1bNNq8Oxkdk3s69HTCdKBnZ0BzU1Q285Ci2mdC6TFn2+3nOpUjo/JyCtMZZNh2+HARUMrSP21/zWMf3V+AVFTVrq9UKSZAAAAAElFTkSuQmCC
			action: 
				{
					key: default(newConfig[key], value)
					for key, value in ioMapping[action].items()
				}
				if action not in virtuals else
				{ 
					'source': 'none', #Disable the input of any virtual element. It is set to "always" to implement the virtual trigger firing.
					'invert': default(newConfig['invert'], ioMapping[action]['invert']),
					'debounce': default(newConfig['debounce'], ioMapping[action]['debounce']),
				}
			for action, newConfig in self.newIOMapping.items()
			if [value for value in newConfig.values() if value is not None]
		})).then(self.saveChanges2)
	def setSSHPort(self, num):
		log.debug(f'setSSHPort {num}')
		settings.setValue('ssh port', num)
		
		#lol i bet this is going to cause problems
		with open('/etc/ssh/sshd_config', 'r+', encoding='utf8') as sshd_config:
			configuration = sshd_config.read()
			sshd_config.seek(0)
			print(
				re.sub(
					r'\n#? ?Port \d+\n',
					f'\nPort {num}\n',
					configuration,
				),
				file = sshd_config, 
				end = '',
			)
			sshd_config.truncate()
		
		self.reloadSSH()
Beispiel #8
0
	def updateCMPreset(self) -> None:
		"""Set color matrix preset, based on what is displayed."""
		
		for index in range(self.uiColorMatrixPreset.count()):
			presetName = self.uiColorMatrixPreset.itemData(index)
			
			#Custom preset. Always true. Should be last element in list.
			if not presetName:
				settings.setValue('customColorMatrix',
					[cmInput.value() for cmInput in self.colorMatrixInputs()])
				break
			
			
			#Match one of the existing presets.
			preset = presets[presetName]
			if False not in [
				abs(presetValue - cmInput.value()) < 0.001
				for presetValue, cmInput in zip(preset, self.colorMatrixInputs())
			]:
				break
		
		self.uiColorMatrixPreset.setCurrentIndex(index)
	def setHTTPPort(self, num):
		log.debug(f'setHTTPPort {num}')
		settings.setValue('http port', num)
		webServer.setValue('port', json.dumps(num))
		self.reloadHTTP()
Beispiel #10
0
	def setHTTP(self, *, on=True):
		log.debug(f'setHTTP {on}')
		settings.setValue('http enabled', on)
		
		if on:
			self.uiHTTPStatus.showMessage(
				f"Status: Starting…",
				timeout = 0 )
			
			external_process.run(self,
				['systemctl', 'enable', 'chronos-web-api'],
				
				lambda err: (
					self.uiHTTPStatus.showError(
						f"Status: Error. See journalctl.", 
						timeout = 0 ),
					log.error(f'Internal command failed with code {err}.'),
				),
				
				lambda *_: external_process.run(self,
					['service', 'chronos-web-api', 'start'],
					
					lambda err: (
						self.uiHTTPStatus.showError(
							f"Status: Error. See journalctl.", 
							timeout = 0 ),
						log.error(f'Internal command failed with code {err}.'),
					),
					
					lambda *_:
						self.uiHTTPStatus.showMessage(
							f"Status: Running.",
							timeout = 0 )
				)
			)
		
		else: #off
			self.uiHTTPStatus.showMessage(
				f"Status: Stopping…",
				timeout = 0 )
			
			external_process.run(self,
				['service', 'chronos-web-api', 'stop'],
				
				lambda err: (
					self.uiHTTPStatus.showError(
						f"Status: Error. See journalctl.", 
						timeout = 0 ),
					log.error(f'[9quMdB] external process returned {err}'),
				),
				
				lambda *_: external_process.run(self,
					['systemctl', 'disable', 'chronos-web-api'],
					
					lambda err: (
						self.uiHTTPStatus.showError(
							f"Status: Error. See journalctl.", 
							timeout = 0 ),
						log.error(f'[kNIh2U] external process returned {err}'),
					),
					
					lambda *_:
						self.uiHTTPStatus.showMessage(
							f"Status: Stopped.",
							timeout = 0 )
				)
			)
Beispiel #11
0
	def setSSH(self, *, on=True):
		log.debug(f'setSSH {on}')
		settings.setValue('ssh enabled', on)
			
		if on:
			self.uiSSHStatus.showMessage(
				f"Status: Starting…",
				timeout = 0 )
			
			external_process.run(self,
				['systemctl', 'enable', 'ssh'],
				
				lambda err: (
					self.uiSSHStatus.showError(
						f"Status: Error. See journalctl.", 
						timeout = 0 ),
					log.error(f'[wf8F10] external process returned {err}'),
				),
				
				lambda *_: external_process.run(self,
					['service', 'ssh', 'start'],
					
					lambda err: (
						self.uiSSHStatus.showError(
							f"Status: Error. See journalctl.", 
							timeout = 0 ),
						log.error(f'[iJGaZW] external process returned {err}'),
					),
					
					lambda *_:
						self.uiSSHStatus.showMessage(
							f"Status: Running.",
							timeout = 0 )
				)
			)
		
		else: #off
			self.uiSSHStatus.showMessage(
				f"Status: Stopping…",
				timeout = 0 )
			
			external_process.run(self,
				['service', 'ssh', 'stop'],
				
				lambda err: (
					self.uiSSHStatus.showError(
						f"Status: Error. See journalctl.", 
						timeout = 0 ),
					log.error(f'Internal command failed with code {err}.'),
				),
				
				lambda *_: external_process.run(self,
					['systemctl', 'disable', 'ssh'],
					
					lambda err: (
						self.uiSSHStatus.showError(
							f"Status: Error. See journalctl.", 
							timeout = 0 ),
						log.error(f'Internal command failed with code {err}.'),
					),
					
					lambda *_:
						self.uiSSHStatus.showMessage(
							f"Status: Stopped.",
							timeout = 0 )
				)
			)
Beispiel #12
0
	def setPassword(self, password):
		log.debug(f'setPassword {password}')
		settings.setValue('password', password)
		self.setHTTPPassword(password)
		self.setSSHPassword(password)
	def runCal(self):
		settings.setValue('last factory cal', 
			datetime.now().strftime(self.uiCalibratedOnTemplate))
		log.error("We don't have these routines in the API yet.")
Beispiel #14
0
	def __init__(self, window):
		super().__init__()
		self.setupUi(self)
		
		# Panel init.
		self.setFixedSize(window.app.primaryScreen().virtualSize())
		self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
		self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
		
		# Hide some stuff we haven't programmed yet. (Filesize, file name preview.)
		for id_ in {
			'headerlabel_2', 'uiDiskSpaceVisualization',
			'frame', 'frame_2', 'frame_3',
			'uiDiskSpaceFree', 'uiDiskSpaceRequired', 'uiDiskSpaceUsed',
			'label_4', 'widget_2', 'label_5'
		}:
			getattr(self, id_).deleteLater()
			
			
			
		
		# Button binding.
		#The preferred external partition is the one set in settings' preferredFileSavingUUID, OR the most recent partition.
		settings.observe('preferredFileSavingUUID', '', self.setPreferredSavingDevice)
		api.externalPartitions.observe(lambda *_: self.setPreferredSavingDevice(
			settings.value('preferredFileSavingUUID', '') ))
		self.uiSavedVideoLocation.currentIndexChanged.connect(lambda *_: 
			self.uiSavedVideoLocation.hasFocus() and settings.setValue(
				'preferredFileSavingUUID',
				(self.uiSavedVideoLocation.currentData() or {}).get('uuid') ) )
		
		
		self.uiSavedVideoName.setText(
			settings.value('savedVideoName', self.uiSavedVideoName.text()) )
		self.uiSavedVideoName.textChanged.connect(lambda value:
			settings.setValue('savedVideoName', value) )
		
		self.uiSavedVideoFileExtention.setCurrentText(
			settings.value('savedVideoFileExtention', self.uiSavedVideoFileExtention.currentText()) )
		self.uiSavedVideoFileExtention.currentTextChanged.connect(lambda value:
			settings.setValue('savedVideoFileExtention', value) )
		
		
		self.uiSavedFileFramerate.setValue(
			settings.value('savedFileFramerate', self.uiSavedFileFramerate.value()) )
		self.uiSavedFileFramerate.valueChanged.connect(lambda value:
			settings.setValue('savedFileFramerate', value) )
		
		self.uiSavedFileBPP.setValue(
			settings.value('savedFileBPP', self.uiSavedFileBPP.value()) )
		self.uiSavedFileBPP.valueChanged.connect(lambda value:
			settings.setValue('savedFileBPP', value) )
		
		self.uiSavedFileMaxBitrate.setValue(
			settings.value('savedFileMaxBitrate', self.uiSavedFileMaxBitrate.value()) )
		self.uiSavedFileMaxBitrate.valueChanged.connect(lambda value:
			settings.setValue('savedFileMaxBitrate', value) )
		
		
		self.uiAutoSaveVideo.setCheckState( #[autosave]
			bool(settings.value('autoSaveVideo', self.uiAutoSaveVideo.checkState())) * 2 )
		self.uiAutoSaveVideo.stateChanged.connect(lambda value:
			settings.setValue('autoSaveVideo', bool(value)) )
		
		self.uiResumeRecordingAfterSave.setCheckState( #[autosave]
			bool(settings.value('resumeRecordingAfterSave', self.uiResumeRecordingAfterSave.checkState())) * 2 )
		self.uiResumeRecordingAfterSave.stateChanged.connect(lambda value:
			settings.setValue('resumeRecordingAfterSave', bool(value)) )
		
		
		self.uiFormatStorage.clicked.connect(lambda: window.show('storage'))
		self.uiDone.clicked.connect(window.back)
	
	
		self.uiSafelyRemove.clicked.connect(lambda: window.show('storage'))
Beispiel #15
0
	def __init__(self, app):
		super().__init__()
		
		self.app = app
		app.window = self #Yuck. Couldn't find a decent way to plumb self.showInput through to the widgets otherwise.
		
		
		
		################################
		# Screen-loading functionality #
		################################
		
		from .screens.main2 import Main
		from .screens.play_and_save import PlayAndSave
		from .screens.recording_settings import RecordingSettings
		from .screens.storage import Storage
		from .screens.color import Color
		from .screens.about_camera import AboutCamera
		from .screens.file_settings import FileSettings
		from .screens.power import Power
		from .screens.primary_settings import PrimarySettings
		from .screens.record_mode import RecordMode
		from .screens.remote_access import RemoteAccess
		from .screens.replay import Replay
		from .screens.scripts import Scripts
		from .screens.service_screen import ServiceScreenLocked, ServiceScreenUnlocked
		from .screens.stamp import Stamp
		from .screens.test import Test
		from .screens.trigger_delay import TriggerDelay
		from .screens.triggers_and_io import TriggersAndIO
		from .screens.update_firmware import UpdateFirmware
		from .screens.user_settings import UserSettings
		
		self._availableScreens = {
			'main': Main, #load order, load items on main screen first, main screen submenus next, and it doesn't really matter after that.
			'play_and_save': PlayAndSave,
			'recording_settings': RecordingSettings,
			'about_camera': AboutCamera,
			'color': Color,
			'storage': Storage,
			'file_settings': FileSettings,
			'power': Power,
			'primary_settings': PrimarySettings,
			'record_mode': RecordMode,
			'remote_access': RemoteAccess,
			'replay': Replay,
			'scripts': Scripts,
			'service_screen.locked': ServiceScreenLocked,
			'service_screen.unlocked': ServiceScreenUnlocked,
			'stamp': Stamp,
			'test': Test,
			'trigger_delay': TriggerDelay,
			'triggers_and_io': TriggersAndIO,
			'update_firmware': UpdateFirmware,
			'user_settings': UserSettings,
		}
		
		self._screens = {}
		
		# Set the initial screen. If in dev mode, due to the frequent restarts,
		# reopen the previous screen. If in the hands of an end-user, always
		# open the main screen when rebooting to provide an escape route for a
		# confusing or broken screen.
		
		#settings.setValue('current screen', 'widget_test')
		
		if settings.value('debug controls enabled', False):
			self.currentScreen = settings.value('current screen', 'main')
		else:
			self.currentScreen = 'main'
		
		if self.currentScreen not in self._availableScreens: 
			self.currentScreen = 'main'
		
		self._screenStack = ['main', self.currentScreen] if self.currentScreen != 'main' else ['main'] #Start off with main loaded into history, since we don't always start on main during development and going back should get you *somewhere* useful rather than crashing.
		
		self._ensureInstantiated(self.currentScreen)
		
		#for screen in self._availableScreens:
		#	self._ensureInstantiated(screen)
		
		if hasattr(self._screens[self.currentScreen], 'onShow'):
			self._screens[self.currentScreen].onShow()
		self._screens[self.currentScreen].show()
		
		settings.setValue('current screen', self.currentScreen)
		
		#Cache all screens, cached screens load about 150-200ms faster I think.
		self._lazyLoadTimer = QtCore.QTimer()
		self._lazyLoadTimer.timeout.connect(self._loadAScreen)
		self._lazyLoadTimer.setSingleShot(True)
		PRECACHE_ALL_SCREENS and self._lazyLoadTimer.start(0) #ms
		
		
		from chronosGui2.input_panels import KeyboardNumericWithUnits, KeyboardNumericWithoutUnits
		from chronosGui2.input_panels import KeyboardAlphanumeric
		self._keyboards = {
			"numeric_with_units": KeyboardNumericWithUnits(self),
			"numeric_without_units": KeyboardNumericWithoutUnits(self),
			"alphanumeric": KeyboardAlphanumeric(self),
		}
		
		self._activeKeyboard = ''
Beispiel #16
0
	def changeShownTrigger(self, index):
		self.uiRecordModePanes.setCurrentIndex(index)
		settings.setValue('active record mode', self.availableRecordModeIds[index])
		self.control.set('recMode', self.availableRecordModeIds[index])