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

(cherry picked from commit 68065d2)
  • Loading branch information
chenchongbiao authored and fly602 committed Sep 4, 2024
1 parent 8db8a21 commit 6351c78
Show file tree
Hide file tree
Showing 3 changed files with 245 additions and 25 deletions.
239 changes: 214 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 Down Expand Up @@ -46,7 +48,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 @@ -59,6 +67,10 @@ const (
dsgKeyInputDefaultPriorities = "inputDefaultPrioritiesByType"
dsgKeyOutputDefaultPriorities = "outputDefaultPrioritiesByType"
dsgKeyBluezModeDefault = "bluezModeDefault"

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

var (
Expand All @@ -67,6 +79,17 @@ var (
defaultHeadphoneOutputVolume = 0.17
gMaxUIVolume float64
defaultReduceNoise = false

// 保存 pulaudio ,pipewire 相关的服务
pulseaudioServices = []string{pulseaudioService, pulseaudioSocket}
pipewireServices = []string{pipewireService, pipewirePulseService, pipewireSocket, pipewirePulseSocket}
)

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

//go:generate dbusutil-gen -type Audio,Sink,SinkInput,Source,Meter -import github.com/godbus/dbus audio.go sink.go sinkinput.go source.go meter.go
Expand Down Expand Up @@ -116,6 +139,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 @@ -205,10 +230,11 @@ func isStringInSlice(list []string, str string) bool {

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 @@ -219,6 +245,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 Down Expand Up @@ -247,42 +274,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 activeServices, deactiveServices []string
if serverName == "pulseaudio" {
activeServices = pulseaudioServices
deactiveServices = pipewireServices
} else {
activeServices = pipewireServices
deactiveServices = pulseaudioServices
}

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

_, err = systemd.MaskUnitFiles(0, deactiveServices, false, true)
if err != nil {
logger.Warning("Failed to mask unit files", deactiveServices, "\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 pulseaudioState string
var activeServices, deactiveServices []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 = pulseaudioServices
deactiveServices = pipewireServices
} else if pulseaudioState == "masked" && server == pipewireService {
activeServices = pipewireServices
deactiveServices = pulseaudioServices
}

}
}

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 len(activeServicePath) != 0 {
serverSystemdUnit, err := systemd1.NewUnit(service.Conn(), activeServicePath)
if err != nil {
logger.Warning("failed to create audio server systemd unit", err)
return err
}

if state != "active" {
go func() {
_, err := serverSystemdUnit.Unit().Start(0, "replace")
state, err := serverSystemdUnit.Unit().ActiveState().Get(0)
if err != nil {
logger.Warning("failed to start audio server unit:", err)
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 6351c78

Please sign in to comment.