2 Commits

12 changed files with 135 additions and 165 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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 = []

View File

@ -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"

View File

@ -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