Skip to content

Commit

Permalink
feat: add audio server switch support
Browse files Browse the repository at this point in the history
add audio server switch support

Issue: linuxdeepin/developer-center#5819
Log: add audio server switch support
  • Loading branch information
chenchongbiao committed Nov 14, 2023
1 parent 67fabb1 commit 6a7f603
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 25 deletions.
235 changes: 210 additions & 25 deletions audio1/audio.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import (

dbus "github.com/godbus/dbus/v5"
"github.com/linuxdeepin/dde-daemon/common/dsync"
notifications "github.com/linuxdeepin/go-dbus-factory/session/org.freedesktop.notifications"
systemd1 "github.com/linuxdeepin/go-dbus-factory/system/org.freedesktop.systemd1"
gio "github.com/linuxdeepin/go-gir/gio-2.0"
"github.com/linuxdeepin/go-lib/dbusutil"
"github.com/linuxdeepin/go-lib/dbusutil/gsprop"
. "github.com/linuxdeepin/go-lib/gettext"
"github.com/linuxdeepin/go-lib/pulse"
"golang.org/x/xerrors"
)
Expand All @@ -43,7 +45,13 @@ const (
dbusPath = "/org/deepin/dde/Audio1"
dbusInterface = dbusServiceName

dbusPulseaudioServer = "org.pulseaudio.Server"
pulseaudioService = "pulseaudio.service"
pipewireService = "pipewire.service"
pipewirePulseService = "pipewire-pulse.service"

pulseaudioSocket = "pulseaudio.socket"
pipewireSocket = "pipewire.socket"
pipewirePulseSocket = "pipewire-pulse.socket"

increaseMaxVolume = 1.5
normalMaxVolume = 1.0
Expand All @@ -52,6 +60,10 @@ const (
dsgKeyAutoSwitchPort = "autoSwitchPort"
dsgKeyBluezModeFilterList = "bluezModeFilterList"
dsgKeyPortFilterList = "portFilterList"

changeIconStart = "notification-change-start"
changeIconFailed = "notification-change-failed"
changeIconFinished = "notification-change-finished"
)

var (
Expand All @@ -61,6 +73,13 @@ var (
gMaxUIVolume float64
)

const (
// 音频服务更改状态:已经完成
AudioStateChanged = true
// 音频服务更改状态:正在修改中
AudioStateChanging = false
)

//go:generate dbusutil-gen -type Audio,Sink,SinkInput,Source,Meter -import github.com/godbus/dbus/v5 audio.go sink.go sinkinput.go source.go meter.go
//go:generate dbusutil-gen em -type Audio,Sink,SinkInput,Source,Meter

Expand Down Expand Up @@ -108,6 +127,8 @@ type Audio struct {
BluetoothAudioMode string // 蓝牙模式
// dbusutil-gen: equal=isStrvEqual
BluetoothAudioModeOpts []string // 可用的蓝牙模式
CurrentAudioServer string // 当前使用的音频服务
AudioServerState bool // 音频服务状态

// dbusutil-gen: ignore
IncreaseVolume gsprop.Bool `prop:"access:rw"`
Expand Down Expand Up @@ -184,10 +205,11 @@ type Audio struct {

func newAudio(service *dbusutil.Service) *Audio {
a := &Audio{
service: service,
meters: make(map[string]*Meter),
MaxUIVolume: pulse.VolumeUIMax,
enableSource: true,
service: service,
meters: make(map[string]*Meter),
MaxUIVolume: pulse.VolumeUIMax,
enableSource: true,
AudioServerState: AudioStateChanged,
}

a.settings = gio.NewSettings(gsSchemaAudio)
Expand All @@ -197,6 +219,7 @@ func newAudio(service *dbusutil.Service) *Audio {
a.PausePlayer = false
a.ReduceNoise = false
a.emitPropChangedReduceNoise(a.ReduceNoise)
a.CurrentAudioServer = a.getCurrentAudioServer()
a.headphoneUnplugAutoPause = a.settings.GetBoolean(gsKeyHeadphoneUnplugAutoPause)
a.outputAutoSwitchCountMax = int(a.settings.GetInt(gsKeyOutputAutoSwitchCountMax))
if a.IncreaseVolume.Get() {
Expand All @@ -214,42 +237,204 @@ func newAudio(service *dbusutil.Service) *Audio {
return a
}

func (a *Audio) setAudioServerFailed(oldAudioServer string) {
sendNotify(changeIconFailed, "", Tr("Failed to change Audio Server, please try later"))
// 还原音频服务
a.PropsMu.Lock()
a.setPropCurrentAudioServer(oldAudioServer)
a.setPropAudioServerState(AudioStateChanged)
a.PropsMu.Unlock()
}

func (a *Audio) getCurrentAudioServer() (serverName string) {
audioServers := []string{pulseaudioService, pipewireService}
systemd := systemd1.NewManager(a.service.Conn())

for _, server := range audioServers {
path, err := systemd.GetUnit(0, server)
if err == nil {
serverSystemdUnit, err := systemd1.NewUnit(a.service.Conn(), path)
if err == nil {
state, err := serverSystemdUnit.Unit().LoadState().Get(0)
if err != nil {
logger.Warning("Failed to get LoadState of unit", path)
} else if state == "loaded" {
return strings.Split(server, ".")[0]
}
}
}
}

return ""
}

func (a *Audio) SetCurrentAudioServer(serverName string) *dbus.Error {
a.PropsMu.Lock()
a.setPropAudioServerState(AudioStateChanging)
a.setPropCurrentAudioServer(serverName)
a.PropsMu.Unlock()

sendNotify(changeIconStart, "", Tr("Changing Audio Server, please wait..."))

var activeServers, deactiveServers []string
if serverName == "pulseaudio" {
activeServers = []string{pulseaudioService, pulseaudioSocket}
deactiveServers = []string{pipewireService, pipewirePulseService, pipewireSocket, pipewirePulseSocket}
} else {
activeServers = []string{pipewireService, pipewirePulseService, pipewireSocket, pipewirePulseSocket}
deactiveServers = []string{pulseaudioService, pulseaudioSocket}
}

oldAudioServer := a.CurrentAudioServer
systemd := systemd1.NewManager(a.service.Conn())
_, err := systemd.UnmaskUnitFiles(0, activeServers, false)
if err != nil {
logger.Warning("Failed to unmask unit files", activeServers, "\nError:", err)
a.setAudioServerFailed(oldAudioServer)
return dbusutil.ToError(err)
}

_, err = systemd.MaskUnitFiles(0, deactiveServers, false, true)
if err != nil {
logger.Warning("Failed to mask unit files", deactiveServers, "\nError:", err)
a.setAudioServerFailed(oldAudioServer)
return dbusutil.ToError(err)
}

err = systemd.Reload(0)
if err != nil {
logger.Warning("Failed to reload unit files. Error:", err)
return dbusutil.ToError(err)
}

sendNotify(changeIconFinished, "", Tr("Audio Server changed, please log out and then log in"))

a.PropsMu.Lock()
a.setPropAudioServerState(AudioStateChanged)
a.PropsMu.Unlock()
return nil
}

func sendNotify(icon, summary, body string) {
sessionBus, err := dbus.SessionBus()
if err != nil {
logger.Warning(err)
return
}
n := notifications.NewNotifications(sessionBus)
_, err = n.Notify(0, "dde-control-center", 0,
icon, summary, body,
nil, nil, -1)
logger.Debugf("send notification icon: %q, summary: %q, body: %q",
icon, summary, body)

if err != nil {
logger.Warning(err)
}
}

func startAudioServer(service *dbusutil.Service) error {
var serverPath dbus.ObjectPath
audioServers := []string{"pipewire-pulse.service", "pulseaudio.service"}
var activeServices, deactiveServices []string
var pulseaudioState string
audioServers := []string{pulseaudioService, pipewireService}

systemd := systemd1.NewManager(service.Conn())

for _, server := range audioServers {
path, err := systemd.GetUnit(0, server)
if err == nil {
serverPath = path
break
serverSystemdUnit, err := systemd1.NewUnit(service.Conn(), path)
if err != nil {
logger.Warning("failed to create service systemd unit", err)
return err
}

state, err := serverSystemdUnit.Unit().LoadState().Get(0)
if err != nil {
logger.Warning("failed to get service active state", err)
return err
}

if server == pulseaudioService {
pulseaudioState = state
}

// 做一个特判,pulseaudio 与 pipewire 同时预装,但是没法在装包的时候进行 mask 操作,
if state == "loaded" && server == pulseaudioService {
activeServices = []string{pulseaudioService, pulseaudioSocket}
deactiveServices = []string{pipewireService, pipewirePulseService, pipewireSocket, pipewirePulseSocket}
} else if pulseaudioState == "masked" && server == pipewireService {
activeServices = []string{pipewireService, pipewirePulseService, pipewireSocket, pipewirePulseSocket}
deactiveServices = []string{pulseaudioService, pulseaudioSocket}
}

}
}

logger.Debug("ready to start audio server", serverPath)
for _, deactiveService := range deactiveServices {
deactiveServicePath, err := systemd.GetUnit(0, deactiveService)
if err == nil {
if len(deactiveServicePath) != 0 {
serverSystemdUnit, err := systemd1.NewUnit(service.Conn(), deactiveServicePath)

if len(serverPath) != 0 {
serverSystemdUnit, err := systemd1.NewUnit(service.Conn(), serverPath)
if err != nil {
logger.Warning("failed to create audio server systemd unit", err)
return err
}
if err != nil {
logger.Warning("failed to create service systemd unit", err)
return err
}

state, err := serverSystemdUnit.Unit().ActiveState().Get(0)
if err != nil {
logger.Warning("failed to get audio server active state", err)
return err
state, err := serverSystemdUnit.Unit().LoadState().Get(0)
if err != nil {
logger.Warning("failed to get service active state", err)
return err
}

if state != "masked" {
_, err := systemd.MaskUnitFiles(0, []string{deactiveService}, false, true)

if err != nil {
logger.Warning("Failed to mask unit files", err)
return err
}
}

// 服务在 mask 之前服务,可能被激活,调用 stop
_, err = systemd.StopUnit(0, deactiveService, "replace")
if err != nil {
logger.Warning("Failed to stop service", err)
return err
}
}
}
}

for _, activeService := range activeServices {
activeServicePath, err := systemd.GetUnit(0, activeService)
if err == nil {
logger.Debug("ready to start audio server", activeServicePath)

if state != "active" {
go func() {
_, err := serverSystemdUnit.Unit().Start(0, "replace")
if len(activeServicePath) != 0 {
serverSystemdUnit, err := systemd1.NewUnit(service.Conn(), activeServicePath)
if err != nil {
logger.Warning("failed to start audio server unit:", err)
logger.Warning("failed to create audio server systemd unit", err)
return err
}
}()

state, err := serverSystemdUnit.Unit().ActiveState().Get(0)
if err != nil {
logger.Warning("failed to get audio server active state", err)
return err
}

if state != "active" {
go func() {
_, err := serverSystemdUnit.Unit().Start(0, "replace")
if err != nil {
logger.Warning("failed to start audio server unit:", err)
}
}()
}
}

}
}

Expand Down
26 changes: 26 additions & 0 deletions audio1/audio_dbusutil.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions audio1/exported_methods_auto.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6a7f603

Please sign in to comment.