Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
4bcf82178e | |||
beae1866bb |
@ -74,7 +74,7 @@ KEY_FENRIR,KEY_0=bookmark_10
|
|||||||
KEY_FENRIR,KEY_KPSLASH=set_window_application
|
KEY_FENRIR,KEY_KPSLASH=set_window_application
|
||||||
2,KEY_FENRIR,KEY_KPSLASH=clear_window_application
|
2,KEY_FENRIR,KEY_KPSLASH=clear_window_application
|
||||||
KEY_KPPLUS=progress_bar_monitor
|
KEY_KPPLUS=progress_bar_monitor
|
||||||
KEY_FENRIR,KEY_KPPLUS=silence_until_prompt
|
#KEY_FENRIR,KEY_KPPLUS=silence_until_prompt
|
||||||
KEY_FENRIR,KEY_F2=toggle_braille
|
KEY_FENRIR,KEY_F2=toggle_braille
|
||||||
KEY_FENRIR,KEY_F3=toggle_sound
|
KEY_FENRIR,KEY_F3=toggle_sound
|
||||||
KEY_FENRIR,KEY_F4=toggle_speech
|
KEY_FENRIR,KEY_F4=toggle_speech
|
||||||
|
@ -76,7 +76,7 @@ KEY_FENRIR,KEY_F3=toggle_sound
|
|||||||
KEY_FENRIR,KEY_F4=toggle_speech
|
KEY_FENRIR,KEY_F4=toggle_speech
|
||||||
KEY_FENRIR,KEY_ENTER=temp_disable_speech
|
KEY_FENRIR,KEY_ENTER=temp_disable_speech
|
||||||
KEY_FENRIR,KEY_SHIFT,KEY_P=progress_bar_monitor
|
KEY_FENRIR,KEY_SHIFT,KEY_P=progress_bar_monitor
|
||||||
KEY_FENRIR,KEY_SHIFT,KEY_ENTER=silence_until_prompt
|
#KEY_FENRIR,KEY_SHIFT,KEY_ENTER=silence_until_prompt
|
||||||
KEY_FENRIR,KEY_SHIFT,KEY_CTRL,KEY_P=toggle_punctuation_level
|
KEY_FENRIR,KEY_SHIFT,KEY_CTRL,KEY_P=toggle_punctuation_level
|
||||||
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
|
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
|
||||||
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_ENTER=toggle_output
|
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_ENTER=toggle_output
|
||||||
|
@ -124,7 +124,9 @@ interruptOnKeyPressFilter=
|
|||||||
doubleTapTimeout=0.2
|
doubleTapTimeout=0.2
|
||||||
|
|
||||||
[general]
|
[general]
|
||||||
debugLevel=0
|
# Debug levels: 0=DEACTIVE, 1=ERROR, 2=WARNING, 3=INFO (most verbose)
|
||||||
|
# For production use, WARNING (2) provides good balance of useful info without spam
|
||||||
|
debugLevel=2
|
||||||
# debugMode sets where the debug output should send to:
|
# debugMode sets where the debug output should send to:
|
||||||
# debugMode=File writes to debugFile (Default:/tmp/fenrir-PID.log)
|
# debugMode=File writes to debugFile (Default:/tmp/fenrir-PID.log)
|
||||||
# debugMode=Print just prints on the screen
|
# debugMode=Print just prints on the screen
|
||||||
|
@ -20,6 +20,9 @@ class command():
|
|||||||
return
|
return
|
||||||
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', str(self.env['commandBuffer']['enableSpeechOnKeypress']))
|
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', str(self.env['commandBuffer']['enableSpeechOnKeypress']))
|
||||||
self.env['commandBuffer']['enableSpeechOnKeypress'] = False
|
self.env['commandBuffer']['enableSpeechOnKeypress'] = False
|
||||||
|
# Also disable prompt watching since speech was manually re-enabled
|
||||||
|
if 'silenceUntilPrompt' in self.env['commandBuffer']:
|
||||||
|
self.env['commandBuffer']['silenceUntilPrompt'] = False
|
||||||
self.env['runtime']['outputManager'].presentText(_("speech enabled"), soundIcon='SpeechOn', interrupt=True)
|
self.env['runtime']['outputManager'].presentText(_("speech enabled"), soundIcon='SpeechOn', interrupt=True)
|
||||||
|
|
||||||
def setCallback(self, callback):
|
def setCallback(self, callback):
|
||||||
|
@ -24,6 +24,9 @@ class command():
|
|||||||
return
|
return
|
||||||
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', str(self.env['commandBuffer']['enableSpeechOnKeypress']))
|
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', str(self.env['commandBuffer']['enableSpeechOnKeypress']))
|
||||||
self.env['commandBuffer']['enableSpeechOnKeypress'] = False
|
self.env['commandBuffer']['enableSpeechOnKeypress'] = False
|
||||||
|
# Also disable prompt watching since speech was manually re-enabled
|
||||||
|
if 'silenceUntilPrompt' in self.env['commandBuffer']:
|
||||||
|
self.env['commandBuffer']['silenceUntilPrompt'] = False
|
||||||
self.env['runtime']['outputManager'].presentText(_("speech enabled"), soundIcon='SpeechOn', interrupt=True)
|
self.env['runtime']['outputManager'].presentText(_("speech enabled"), soundIcon='SpeechOn', interrupt=True)
|
||||||
|
|
||||||
def setCallback(self, callback):
|
def setCallback(self, callback):
|
||||||
|
@ -76,11 +76,14 @@ class command():
|
|||||||
# Note: Auto-disable on 100% completion removed to respect user settings
|
# Note: Auto-disable on 100% completion removed to respect user settings
|
||||||
|
|
||||||
# Pattern 1: Percentage (50%, 25.5%, etc.)
|
# Pattern 1: Percentage (50%, 25.5%, etc.)
|
||||||
|
# Filter out common non-progress percentages (weather, system stats, etc.)
|
||||||
percentMatch = re.search(r'(\d+(?:\.\d+)?)\s*%', text)
|
percentMatch = re.search(r'(\d+(?:\.\d+)?)\s*%', text)
|
||||||
if percentMatch:
|
if percentMatch:
|
||||||
percentage = float(percentMatch.group(1))
|
percentage = float(percentMatch.group(1))
|
||||||
# Only trigger on realistic progress percentages (0-100%)
|
# Only trigger on realistic progress percentages (0-100%)
|
||||||
if 0 <= percentage <= 100:
|
if 0 <= percentage <= 100:
|
||||||
|
# Filter out weather/system stats that contain percentages
|
||||||
|
if not re.search(r'\b(?:humidity|cpu|memory|disk|usage|temp|weather|forecast)\b', text, re.IGNORECASE):
|
||||||
self.env['runtime']['debug'].writeDebugOut("Found percentage: " + str(percentage), debug.debugLevel.INFO)
|
self.env['runtime']['debug'].writeDebugOut("Found percentage: " + str(percentage), debug.debugLevel.INFO)
|
||||||
if percentage != self.env['commandBuffer']['lastProgressValue']:
|
if percentage != self.env['commandBuffer']['lastProgressValue']:
|
||||||
self.env['runtime']['debug'].writeDebugOut("Playing tone for: " + str(percentage), debug.debugLevel.INFO)
|
self.env['runtime']['debug'].writeDebugOut("Playing tone for: " + str(percentage), debug.debugLevel.INFO)
|
||||||
|
@ -118,6 +118,9 @@ class command():
|
|||||||
"""Helper method to restore speech when prompt is detected"""
|
"""Helper method to restore speech when prompt is detected"""
|
||||||
# Disable silence mode
|
# Disable silence mode
|
||||||
self.env['commandBuffer']['silenceUntilPrompt'] = False
|
self.env['commandBuffer']['silenceUntilPrompt'] = False
|
||||||
|
# Also disable the keypress-based speech restoration since we're enabling it now
|
||||||
|
if 'enableSpeechOnKeypress' in self.env['commandBuffer']:
|
||||||
|
self.env['commandBuffer']['enableSpeechOnKeypress'] = False
|
||||||
# Re-enable speech
|
# Re-enable speech
|
||||||
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', 'True')
|
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', 'True')
|
||||||
self.env['runtime']['outputManager'].presentText(_("Speech restored"), soundIcon='SpeechOn', interrupt=True)
|
self.env['runtime']['outputManager'].presentText(_("Speech restored"), soundIcon='SpeechOn', interrupt=True)
|
||||||
|
@ -184,6 +184,10 @@ class outputManager():
|
|||||||
if self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled'):
|
if self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled'):
|
||||||
self.presentText(_("speech temporary disabled"), soundIcon='SpeechOff', interrupt=True)
|
self.presentText(_("speech temporary disabled"), soundIcon='SpeechOff', interrupt=True)
|
||||||
self.env['commandBuffer']['enableSpeechOnKeypress'] = True
|
self.env['commandBuffer']['enableSpeechOnKeypress'] = True
|
||||||
|
# Also enable prompt watching for automatic speech restoration
|
||||||
|
if 'silenceUntilPrompt' not in self.env['commandBuffer']:
|
||||||
|
self.env['commandBuffer']['silenceUntilPrompt'] = False
|
||||||
|
self.env['commandBuffer']['silenceUntilPrompt'] = True
|
||||||
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', str(not self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled')))
|
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', str(not self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled')))
|
||||||
self.interruptOutput()
|
self.interruptOutput()
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@ class screenManager():
|
|||||||
self.currScreenText = ''
|
self.currScreenText = ''
|
||||||
self.colums = None
|
self.colums = None
|
||||||
self.rows = None
|
self.rows = None
|
||||||
|
# Compile regex once for better performance
|
||||||
|
self._space_normalize_regex = re.compile(' +')
|
||||||
def getRows(self):
|
def getRows(self):
|
||||||
return self.rows
|
return self.rows
|
||||||
def getColumns(self):
|
def getColumns(self):
|
||||||
@ -124,11 +126,6 @@ class screenManager():
|
|||||||
# This code detects and categorizes screen content changes to provide appropriate
|
# This code detects and categorizes screen content changes to provide appropriate
|
||||||
# speech feedback (typing echo vs incoming text vs screen updates)
|
# speech feedback (typing echo vs incoming text vs screen updates)
|
||||||
|
|
||||||
# Pre-process screen text for comparison - collapse multiple spaces to single space
|
|
||||||
# This normalization prevents spurious diffs from spacing inconsistencies
|
|
||||||
oldScreenText = re.sub(' +',' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['oldContentText']))
|
|
||||||
newScreenText = re.sub(' +',' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['newContentText']))
|
|
||||||
|
|
||||||
# Track whether this appears to be typing (user input) vs other screen changes
|
# Track whether this appears to be typing (user input) vs other screen changes
|
||||||
typing = False
|
typing = False
|
||||||
diffList = []
|
diffList = []
|
||||||
@ -137,6 +134,10 @@ class screenManager():
|
|||||||
# Special case: Initial screen content (going from empty to populated)
|
# Special case: Initial screen content (going from empty to populated)
|
||||||
# This handles first screen load or TTY switch scenarios
|
# This handles first screen load or TTY switch scenarios
|
||||||
if self.env['screen']['newContentText'] != '' and self.env['screen']['oldContentText'] == '':
|
if self.env['screen']['newContentText'] != '' and self.env['screen']['oldContentText'] == '':
|
||||||
|
# Pre-process screen text for comparison - collapse multiple spaces to single space
|
||||||
|
# This normalization prevents spurious diffs from spacing inconsistencies
|
||||||
|
oldScreenText = self._space_normalize_regex.sub(' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['oldContentText']))
|
||||||
|
newScreenText = self._space_normalize_regex.sub(' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['newContentText']))
|
||||||
if oldScreenText == '' and\
|
if oldScreenText == '' and\
|
||||||
newScreenText != '':
|
newScreenText != '':
|
||||||
self.env['screen']['newDelta'] = newScreenText
|
self.env['screen']['newDelta'] = newScreenText
|
||||||
@ -194,6 +195,12 @@ class screenManager():
|
|||||||
# GENERAL SCREEN CHANGE DETECTION
|
# GENERAL SCREEN CHANGE DETECTION
|
||||||
# Not typing - handle as line-by-line content change
|
# Not typing - handle as line-by-line content change
|
||||||
# This catches: incoming messages, screen updates, application output, etc.
|
# This catches: incoming messages, screen updates, application output, etc.
|
||||||
|
|
||||||
|
# Pre-process screen text for comparison - collapse multiple spaces to single space
|
||||||
|
# This normalization prevents spurious diffs from spacing inconsistencies
|
||||||
|
oldScreenText = self._space_normalize_regex.sub(' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['oldContentText']))
|
||||||
|
newScreenText = self._space_normalize_regex.sub(' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['newContentText']))
|
||||||
|
|
||||||
diff = self.differ.compare(oldScreenText.split('\n'),\
|
diff = self.differ.compare(oldScreenText.split('\n'),\
|
||||||
newScreenText.split('\n'))
|
newScreenText.split('\n'))
|
||||||
diffList = list(diff)
|
diffList = list(diff)
|
||||||
|
@ -234,16 +234,72 @@ class settingsManager():
|
|||||||
elif isinstance(self.settings[section][setting], bool):
|
elif isinstance(self.settings[section][setting], bool):
|
||||||
if not value in ['True','False']:
|
if not value in ['True','False']:
|
||||||
raise ValueError('could not convert string to bool: '+ value)
|
raise ValueError('could not convert string to bool: '+ value)
|
||||||
|
v = value == 'True'
|
||||||
elif isinstance(self.settings[section][setting], int):
|
elif isinstance(self.settings[section][setting], int):
|
||||||
v = int(value)
|
v = int(value)
|
||||||
elif isinstance(self.settings[section][setting], float):
|
elif isinstance(self.settings[section][setting], float):
|
||||||
v = float(value)
|
v = float(value)
|
||||||
|
|
||||||
|
# Content validation for critical settings
|
||||||
|
self._validateSettingValue(section, setting, v)
|
||||||
|
|
||||||
self.settingArgDict[section][setting] = str(value)
|
self.settingArgDict[section][setting] = str(value)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('settingsManager:setOptionArgDict:Datatype missmatch: '+ section + '#' + setting + '=' + value + ' Error:' + str(e))
|
print('settingsManager:setOptionArgDict:Datatype missmatch: '+ section + '#' + setting + '=' + value + ' Error:' + str(e))
|
||||||
#self.env['runtime']['debug'].writeDebugOut('settingsManager:setOptionArgDict:Datatype missmatch: '+ section + '#' + setting + '=' + value + ' Error:' + str(e), debug.debugLevel.ERROR)
|
#self.env['runtime']['debug'].writeDebugOut('settingsManager:setOptionArgDict:Datatype missmatch: '+ section + '#' + setting + '=' + value + ' Error:' + str(e), debug.debugLevel.ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _validateSettingValue(self, section, setting, value):
|
||||||
|
"""Validate setting values for critical screen reader functionality.
|
||||||
|
Only validates settings that could cause crashes or accessibility issues.
|
||||||
|
Invalid values raise ValueError which is caught by the calling method."""
|
||||||
|
|
||||||
|
# Speech settings validation - critical for accessibility
|
||||||
|
if section == 'speech':
|
||||||
|
if setting == 'rate':
|
||||||
|
if not (0.0 <= value <= 3.0):
|
||||||
|
raise ValueError(f'Speech rate must be between 0.0 and 3.0, got {value}')
|
||||||
|
elif setting == 'pitch':
|
||||||
|
if not (0.0 <= value <= 2.0):
|
||||||
|
raise ValueError(f'Speech pitch must be between 0.0 and 2.0, got {value}')
|
||||||
|
elif setting == 'volume':
|
||||||
|
if not (0.0 <= value <= 1.5):
|
||||||
|
raise ValueError(f'Speech volume must be between 0.0 and 1.5, got {value}')
|
||||||
|
elif setting == 'driver':
|
||||||
|
valid_drivers = ['speechdDriver', 'genericDriver', 'dummyDriver']
|
||||||
|
if value not in valid_drivers:
|
||||||
|
raise ValueError(f'Invalid speech driver: {value}. Valid options: {valid_drivers}')
|
||||||
|
|
||||||
|
# Sound settings validation
|
||||||
|
elif section == 'sound':
|
||||||
|
if setting == 'volume':
|
||||||
|
if not (0.0 <= value <= 1.5):
|
||||||
|
raise ValueError(f'Sound volume must be between 0.0 and 1.5, got {value}')
|
||||||
|
elif setting == 'driver':
|
||||||
|
valid_drivers = ['genericDriver', 'gstreamerDriver', 'dummyDriver']
|
||||||
|
if value not in valid_drivers:
|
||||||
|
raise ValueError(f'Invalid sound driver: {value}. Valid options: {valid_drivers}')
|
||||||
|
|
||||||
|
# Screen settings validation
|
||||||
|
elif section == 'screen':
|
||||||
|
if setting == 'driver':
|
||||||
|
valid_drivers = ['vcsaDriver', 'ptyDriver', 'dummyDriver']
|
||||||
|
if value not in valid_drivers:
|
||||||
|
raise ValueError(f'Invalid screen driver: {value}. Valid options: {valid_drivers}')
|
||||||
|
|
||||||
|
# Input settings validation
|
||||||
|
elif section == 'keyboard':
|
||||||
|
if setting == 'driver':
|
||||||
|
valid_drivers = ['evdevDriver', 'ptyDriver', 'atspiDriver', 'dummyDriver']
|
||||||
|
if value not in valid_drivers:
|
||||||
|
raise ValueError(f'Invalid input driver: {value}. Valid options: {valid_drivers}')
|
||||||
|
|
||||||
|
# General settings validation
|
||||||
|
elif section == 'general':
|
||||||
|
if setting == 'debugLevel':
|
||||||
|
if not (0 <= value <= 3):
|
||||||
|
raise ValueError(f'Debug level must be between 0 and 3, got {value}')
|
||||||
|
|
||||||
def parseSettingArgs(self, settingArgs):
|
def parseSettingArgs(self, settingArgs):
|
||||||
for optionElem in settingArgs.split(';'):
|
for optionElem in settingArgs.split(';'):
|
||||||
settingValList = []
|
settingValList = []
|
||||||
|
@ -4,5 +4,5 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributers.
|
# By Chrys, Storm Dragon, and contributers.
|
||||||
|
|
||||||
version = "2025.06.25"
|
version = "2025.06.28"
|
||||||
codeName = "testing"
|
codeName = "testing"
|
||||||
|
@ -9,146 +9,26 @@ mkdir -p "$xdgPath/pipewire"
|
|||||||
mkdir -p "$xdgPath/wireplumber/main.lua.d"
|
mkdir -p "$xdgPath/wireplumber/main.lua.d"
|
||||||
mkdir -p "$xdgPath/wireplumber/bluetooth.lua.d"
|
mkdir -p "$xdgPath/wireplumber/bluetooth.lua.d"
|
||||||
|
|
||||||
#create the file that tells the pipewire-pulse server to use a second socket located at /tmp/pulse.sock
|
# Create drop-in configuration for PipeWire-Pulse console access
|
||||||
# Warn user if we are going to overwrite an existing pipewire-pulse.conf
|
mkdir -p "$xdgPath/pipewire/pipewire-pulse.conf.d"
|
||||||
if [ -f "$xdgPath/pipewire/pipewire-pulse.conf" ]; then
|
# Warn user if we are going to overwrite an existing fenrir console config
|
||||||
read -p "This will replace the current file located at $xdgPath/pipewire/pipewire-pulse.conf, press enter to continue or control+c to abort. " continue
|
if [ -f "$xdgPath/pipewire/pipewire-pulse.conf.d/50-fenrir-console.conf" ]; then
|
||||||
|
read -p "This will replace the current file located at $xdgPath/pipewire/pipewire-pulse.conf.d/50-fenrir-console.conf, press enter to continue or control+c to abort. " continue
|
||||||
fi
|
fi
|
||||||
cat << "EOF" > "$xdgPath/pipewire/pipewire-pulse.conf"
|
cat << "EOF" > "$xdgPath/pipewire/pipewire-pulse.conf.d/50-fenrir-console.conf"
|
||||||
# PulseAudio config file for PipeWire version "0.3.49" #
|
# Fenrir console audio support
|
||||||
#
|
# Adds secondary socket for console applications like Fenrir
|
||||||
# Copy and edit this file in /etc/pipewire for system-wide changes
|
|
||||||
# or in ~/.config/pipewire for local changes.
|
|
||||||
#
|
|
||||||
# It is also possible to place a file with an updated section in
|
|
||||||
# /etc/pipewire/pipewire-pulse.conf.d/ for system-wide changes or in
|
|
||||||
# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes.
|
|
||||||
#
|
|
||||||
|
|
||||||
context.properties = {
|
|
||||||
## Configure properties in the system.
|
|
||||||
#mem.warn-mlock = false
|
|
||||||
#mem.allow-mlock = true
|
|
||||||
#mem.mlock-all = false
|
|
||||||
#log.level = 2
|
|
||||||
|
|
||||||
#default.clock.quantum-limit = 8192
|
|
||||||
}
|
|
||||||
|
|
||||||
context.spa-libs = {
|
|
||||||
audio.convert.* = audioconvert/libspa-audioconvert
|
|
||||||
support.* = support/libspa-support
|
|
||||||
}
|
|
||||||
|
|
||||||
context.modules = [
|
|
||||||
{ name = libpipewire-module-rt
|
|
||||||
args = {
|
|
||||||
nice.level = -11
|
|
||||||
#rt.prio = 88
|
|
||||||
#rt.time.soft = -1
|
|
||||||
#rt.time.hard = -1
|
|
||||||
}
|
|
||||||
flags = [ ifexists nofail ]
|
|
||||||
}
|
|
||||||
{ name = libpipewire-module-protocol-native }
|
|
||||||
{ name = libpipewire-module-client-node }
|
|
||||||
{ name = libpipewire-module-adapter }
|
|
||||||
{ name = libpipewire-module-metadata }
|
|
||||||
|
|
||||||
{ name = libpipewire-module-protocol-pulse
|
|
||||||
args = {
|
|
||||||
# contents of pulse.properties can also be placed here
|
|
||||||
# to have config per server.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
# Extra modules can be loaded here. Setup in default.pa can be moved here
|
|
||||||
context.exec = [
|
|
||||||
{ path = "pactl" args = "load-module module-always-sink" }
|
|
||||||
{ path = "pactl" args = "load-module module-switch-on-connect" }
|
|
||||||
#{ path = "/usr/bin/sh" args = "~/.config/pipewire/default.pw" }
|
|
||||||
]
|
|
||||||
|
|
||||||
stream.properties = {
|
|
||||||
#node.latency = 1024/48000
|
|
||||||
#node.autoconnect = true
|
|
||||||
#resample.quality = 4
|
|
||||||
#channelmix.normalize = false
|
|
||||||
#channelmix.mix-lfe = false
|
|
||||||
#channelmix.upmix = true
|
|
||||||
#channelmix.upmix-method = simple # none, psd
|
|
||||||
#channelmix.lfe-cutoff = 120
|
|
||||||
#channelmix.fc-cutoff = 6000
|
|
||||||
#channelmix.rear-delay = 12.0
|
|
||||||
#channelmix.stereo-widen = 0.1
|
|
||||||
#channelmix.hilbert-taps = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pulse.properties = {
|
pulse.properties = {
|
||||||
# the addresses this server listens on
|
# the addresses this server listens on
|
||||||
server.address = [
|
server.address = [
|
||||||
"unix:native"
|
"unix:native"
|
||||||
"unix:/tmp/pulse.sock" # absolute paths may be used
|
"unix:/tmp/pulse.sock" # console access socket
|
||||||
#"tcp:4713" # IPv4 and IPv6 on all addresses
|
|
||||||
#"tcp:[::]:9999" # IPv6 on all addresses
|
|
||||||
#"tcp:127.0.0.1:8888" # IPv4 on a single address
|
|
||||||
#
|
|
||||||
#{ address = "tcp:4713" # address
|
|
||||||
# max-clients = 64 # maximum number of clients
|
|
||||||
# listen-backlog = 32 # backlog in the server listen queue
|
|
||||||
# client.access = "restricted" # permissions for clients
|
|
||||||
#}
|
|
||||||
]
|
]
|
||||||
#pulse.min.req = 256/48000 # 5ms
|
|
||||||
#pulse.default.req = 960/48000 # 20 milliseconds
|
|
||||||
#pulse.min.frag = 256/48000 # 5ms
|
|
||||||
#pulse.default.frag = 96000/48000 # 2 seconds
|
|
||||||
#pulse.default.tlength = 96000/48000 # 2 seconds
|
|
||||||
#pulse.min.quantum = 256/48000 # 5ms
|
|
||||||
#pulse.default.format = F32
|
|
||||||
#pulse.default.position = [ FL FR ]
|
|
||||||
# These overrides are only applied when running in a vm.
|
|
||||||
vm.overrides = {
|
|
||||||
pulse.min.quantum = 1024/48000 # 22ms
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# client/stream specific properties
|
# client/stream specific properties
|
||||||
pulse.rules = [
|
pulse.rules = [
|
||||||
{
|
|
||||||
matches = [
|
|
||||||
{
|
|
||||||
# all keys must match the value. ~ starts regex.
|
|
||||||
#client.name = "Firefox"
|
|
||||||
#application.process.binary = "teams"
|
|
||||||
#application.name = "~speech-dispatcher.*"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
actions = {
|
|
||||||
update-props = {
|
|
||||||
#node.latency = 512/48000
|
|
||||||
}
|
|
||||||
# Possible quirks:"
|
|
||||||
# force-s16-info forces sink and source info as S16 format
|
|
||||||
# remove-capture-dont-move removes the capture DONT_MOVE flag
|
|
||||||
#quirks = [ ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
# skype does not want to use devices that don't have an S16 sample format.
|
|
||||||
matches = [
|
|
||||||
{ application.process.binary = "teams" }
|
|
||||||
{ application.process.binary = "skypeforlinux" }
|
|
||||||
]
|
|
||||||
actions = { quirks = [ force-s16-info ] }
|
|
||||||
}
|
|
||||||
{
|
|
||||||
# firefox marks the capture streams as don't move and then they
|
|
||||||
# can't be moved with pavucontrol or other tools.
|
|
||||||
matches = [ { application.process.binary = "firefox" } ]
|
|
||||||
actions = { quirks = [ remove-capture-dont-move ] }
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
# speech dispatcher asks for too small latency and then underruns.
|
# speech dispatcher asks for too small latency and then underruns.
|
||||||
matches = [ { application.name = "~speech-dispatcher*" } ]
|
matches = [ { application.name = "~speech-dispatcher*" } ]
|
||||||
@ -162,12 +42,16 @@ pulse.rules = [
|
|||||||
]
|
]
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
#Creates the file that tells pipewire not to suspend any sinks for all devices. This makes sure audio doesn't die after switching to the console.
|
# Create WirePlumber configuration to prevent audio device suspension on console switch
|
||||||
# Warn user if we are going to overwrite an existing 50-do-not-suspend.lua
|
# Warn user if we are going to overwrite an existing 50-fenrir-no-suspend.lua
|
||||||
if [ -f "$xdgPath/wireplumber/main.lua.d/50-do-not-suspend.lua" ]; then
|
if [ -f "$xdgPath/wireplumber/main.lua.d/50-fenrir-no-suspend.lua" ]; then
|
||||||
read -p "This will replace the current file located at $xdgPath/wireplumber/main.lua.d/50-do-not-suspend.lua, press enter to continue or control+c to abort. " continue
|
read -p "This will replace the current file located at $xdgPath/wireplumber/main.lua.d/50-fenrir-no-suspend.lua, press enter to continue or control+c to abort. " continue
|
||||||
fi
|
fi
|
||||||
echo 'alsa_monitor.rules = {
|
cat << "EOF" > "$xdgPath/wireplumber/main.lua.d/50-fenrir-no-suspend.lua"
|
||||||
|
-- Fenrir console audio support
|
||||||
|
-- Prevents audio device suspension when switching to TTY console
|
||||||
|
|
||||||
|
alsa_monitor.rules = {
|
||||||
{
|
{
|
||||||
matches = {
|
matches = {
|
||||||
{
|
{
|
||||||
@ -178,7 +62,7 @@ echo 'alsa_monitor.rules = {
|
|||||||
["api.alsa.use-acp"] = true,
|
["api.alsa.use-acp"] = true,
|
||||||
["api.acp.auto-profile"] = false,
|
["api.acp.auto-profile"] = false,
|
||||||
["api.acp.auto-port"] = false,
|
["api.acp.auto-port"] = false,
|
||||||
["session.suspend-timeout-seconds"] = 0
|
["session.suspend-timeout-seconds"] = 0
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -194,14 +78,19 @@ echo 'alsa_monitor.rules = {
|
|||||||
["session.suspend-timeout-seconds"] = 0
|
["session.suspend-timeout-seconds"] = 0
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}' > $xdgPath/wireplumber/main.lua.d/50-do-not-suspend.lua
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
#Creates the file that disables the logind module for wireplumber which causes bluetooth to disconnect when switching tty
|
# Create WirePlumber bluetooth configuration to prevent disconnection on TTY switch
|
||||||
# Warn user if we are going to overwrite an existing 30-bluez-monitor.lua
|
# Warn user if we are going to overwrite an existing 30-fenrir-bluez.lua
|
||||||
if [ -f "$xdgPath/wireplumber/bluetooth.lua.d/30-bluez-monitor.lua" ]; then
|
if [ -f "$xdgPath/wireplumber/bluetooth.lua.d/30-fenrir-bluez.lua" ]; then
|
||||||
read -p "This will replace the current file located at $xdgPath/wireplumber/bluetooth.lua.d/30-bluez-monitor.lua, press enter to continue or control+c to abort. " continue
|
read -p "This will replace the current file located at $xdgPath/wireplumber/bluetooth.lua.d/30-fenrir-bluez.lua, press enter to continue or control+c to abort. " continue
|
||||||
fi
|
fi
|
||||||
echo 'bluez_monitor = {}
|
cat << "EOF" > "$xdgPath/wireplumber/bluetooth.lua.d/30-fenrir-bluez.lua"
|
||||||
|
-- Fenrir console audio support
|
||||||
|
-- Disables logind module to prevent bluetooth disconnection when switching TTY
|
||||||
|
|
||||||
|
bluez_monitor = {}
|
||||||
bluez_monitor.properties = {}
|
bluez_monitor.properties = {}
|
||||||
bluez_monitor.rules = {}
|
bluez_monitor.rules = {}
|
||||||
|
|
||||||
@ -210,8 +99,8 @@ function bluez_monitor.enable()
|
|||||||
properties = bluez_monitor.properties,
|
properties = bluez_monitor.properties,
|
||||||
rules = bluez_monitor.rules,
|
rules = bluez_monitor.rules,
|
||||||
})
|
})
|
||||||
|
end
|
||||||
end' > $xdgPath/wireplumber/bluetooth.lua.d/30-bluez-monitor.lua
|
EOF
|
||||||
|
|
||||||
echo "Please ensure that your user is added to the audio group."
|
echo "Please ensure that your user is added to the audio group."
|
||||||
echo "If you have not yet done so, please run this script as root to write the client.conf file."
|
echo "If you have not yet done so, please run this script as root to write the client.conf file."
|
||||||
@ -220,12 +109,12 @@ else
|
|||||||
xdgPath="/root/.config"
|
xdgPath="/root/.config"
|
||||||
mkdir -p "$xdgPath/pulse"
|
mkdir -p "$xdgPath/pulse"
|
||||||
|
|
||||||
# Warn user if we are going to overwrite an existing default.pa
|
# Warn user if we are going to overwrite an existing client.conf
|
||||||
if [ -f "$xdgPath/pulse/default.pa" ]; then
|
if [ -f "$xdgPath/pulse/client.conf" ]; then
|
||||||
read -p "This will replace the current file located at $xdgPath/pulse/default.pa, press enter to continue or control+c to abort. " continue
|
read -p "This will replace the current file located at $xdgPath/pulse/client.conf, press enter to continue or control+c to abort. " continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat << EOF > "$xdgPath/pulse/client.conf"
|
cat << "EOF" > "$xdgPath/pulse/client.conf"
|
||||||
# This file is part of PulseAudio.
|
# This file is part of PulseAudio.
|
||||||
#
|
#
|
||||||
# PulseAudio is free software; you can redistribute it and/or modify
|
# PulseAudio is free software; you can redistribute it and/or modify
|
||||||
|
Reference in New Issue
Block a user