diff --git a/audio1/audio.go b/audio1/audio.go index 2dcb77ba0..dd19ac989 100644 --- a/audio1/audio.go +++ b/audio1/audio.go @@ -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" ) @@ -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 @@ -59,6 +67,10 @@ const ( dsgKeyInputDefaultPriorities = "inputDefaultPrioritiesByType" dsgKeyOutputDefaultPriorities = "outputDefaultPrioritiesByType" dsgKeyBluezModeDefault = "bluezModeDefault" + + changeIconStart = "notification-change-start" + changeIconFailed = "notification-change-failed" + changeIconFinished = "notification-change-finished" ) var ( @@ -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 @@ -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"` @@ -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) @@ -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() { @@ -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) + } + }() } - }() + } + } } diff --git a/audio1/audio_dbusutil.go b/audio1/audio_dbusutil.go index b13ffeab5..537f4e1ad 100644 --- a/audio1/audio_dbusutil.go +++ b/audio1/audio_dbusutil.go @@ -123,6 +123,32 @@ func (v *Audio) emitPropChangedBluetoothAudioModeOpts(value []string) error { return v.service.EmitPropertyChanged(v, "BluetoothAudioModeOpts", value) } +func (v *Audio) setPropCurrentAudioServer(value string) (changed bool) { + if v.CurrentAudioServer != value { + v.CurrentAudioServer = value + v.emitPropChangedCurrentAudioServer(value) + return true + } + return false +} + +func (v *Audio) emitPropChangedCurrentAudioServer(value string) error { + return v.service.EmitPropertyChanged(v, "CurrentAudioServer", value) +} + +func (v *Audio) setPropAudioServerState(value bool) (changed bool) { + if v.AudioServerState != value { + v.AudioServerState = value + v.emitPropChangedAudioServerState(value) + return true + } + return false +} + +func (v *Audio) emitPropChangedAudioServerState(value bool) error { + return v.service.EmitPropertyChanged(v, "AudioServerState", value) +} + func (v *Audio) setPropPausePlayer(value bool) (changed bool) { if v.PausePlayer != value { v.PausePlayer = value diff --git a/audio1/exported_methods_auto.go b/audio1/exported_methods_auto.go index a2ea024e6..1263869d6 100644 --- a/audio1/exported_methods_auto.go +++ b/audio1/exported_methods_auto.go @@ -37,6 +37,11 @@ func (v *Audio) GetExportedMethods() dbusutil.ExportedMethods { Fn: v.SetPortEnabled, InArgs: []string{"cardId", "portName", "enabled"}, }, + { + Name: "SetCurrentAudioServer", + Fn: v.SetCurrentAudioServer, + InArgs: []string{"serverName"}, + }, } } func (v *Meter) GetExportedMethods() dbusutil.ExportedMethods {