Skip to content

Commit

Permalink
separate out session to os-specific implementations, add baseSEssions…
Browse files Browse the repository at this point in the history
… struct for embedding
  • Loading branch information
omriharel committed May 5, 2020
1 parent 21fd48d commit 8d826d8
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 159 deletions.
177 changes: 18 additions & 159 deletions session.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
package deej

import (
"errors"
"fmt"
"strings"

ole "github.com/go-ole/go-ole"
ps "github.com/mitchellh/go-ps"
wca "github.com/moutend/go-wca"
"go.uber.org/zap"

"github.com/omriharel/deej/util"
)

// Session represents a single addressable audio session
Expand All @@ -26,170 +19,36 @@ type Session interface {
Release()
}

var errNoSuchProcess = errors.New("No such process")

type wcaSession struct {
pid uint32
processName string
system bool
const (

logger *zap.SugaredLogger
control *wca.IAudioSessionControl2
volume *wca.ISimpleAudioVolume
// ideally these would share a common ground in baseSession
// but it will not call the child GetVolume correctly :/
sessionCreationLogMessage = "Created audio session instance"

eventCtx *ole.GUID
}
// format this with s.humanReadableDesc and whatever the current volume is
sessionStringFormat = "<session: %s, vol: %.2f>"
)

type masterSession struct {
type baseSession struct {
logger *zap.SugaredLogger
system bool
master bool

volume *wca.IAudioEndpointVolume

eventCtx *ole.GUID
}

func newWCASession(
logger *zap.SugaredLogger,
control *wca.IAudioSessionControl2,
volume *wca.ISimpleAudioVolume,
pid uint32,
eventCtx *ole.GUID,
) (*wcaSession, error) {

s := &wcaSession{
control: control,
volume: volume,
pid: pid,
eventCtx: eventCtx,
}

// special treatment for system sounds session
if pid == 0 {
s.system = true
} else {

// find our session's process name
process, err := ps.FindProcess(int(pid))
if err != nil {
logger.Warnw("Failed to find process name by ID", "pid", pid, "error", err)
defer s.Release()

return nil, fmt.Errorf("find process name by pid: %w", err)
}

// this PID may be invalid - this means the process has already been
// closed and we shouldn't create a session for it.
if process == nil {
logger.Debugw("Process already exited, not creating audio session", "pid", pid)
return nil, errNoSuchProcess
}

s.processName = process.Executable()
}

// use a self-identifying session name e.g. deej.sessions.chrome
s.logger = logger.Named(strings.TrimSuffix(s.Key(), ".exe"))
s.logger.Debugw("Created audio session instance", "session", s)

return s, nil
}

func newMasterSession(
logger *zap.SugaredLogger,
volume *wca.IAudioEndpointVolume,
eventCtx *ole.GUID,
) (*masterSession, error) {
s := &masterSession{
logger: logger.Named(masterSessionName),
volume: volume,
eventCtx: eventCtx,
}

s.logger.Debugw("Created audio session instance", "session", s)

return s, nil
}

func (s *wcaSession) GetVolume() float32 {
var level float32

if err := s.volume.GetMasterVolume(&level); err != nil {
s.logger.Warnw("Failed to get session volume", "error", err)
}

return util.NormalizeScalar(level)
}

func (s *wcaSession) SetVolume(v float32) error {
if err := s.volume.SetMasterVolume(v, s.eventCtx); err != nil {
s.logger.Warnw("Failed to set session volume", "error", err)
return fmt.Errorf("adjust session volume: %w", err)
}

s.logger.Debugw("Adjusting session volume", "to", fmt.Sprintf("%.2f", v))
// used by Key(), needs to be set by child
name string

return nil
// used by String(), needs to be set by child
humanReadableDesc string
}

func (s *wcaSession) Release() {
s.logger.Debug("Releasing audio session")

s.volume.Release()
s.control.Release()
}

func (s *wcaSession) Key() string {
func (s *baseSession) Key() string {
if s.system {
return systemSessionName
}

return strings.ToLower(s.processName)
}

func (s *wcaSession) String() string {
sessionDesc := fmt.Sprintf("%s (pid %d)", s.processName, s.pid)

if s.system {
sessionDesc = "system sounds"
}

return fmt.Sprintf("<session: %s, vol: %.2f>", sessionDesc, s.GetVolume())
}

func (s *masterSession) GetVolume() float32 {
var level float32

if err := s.volume.GetMasterVolumeLevelScalar(&level); err != nil {
s.logger.Warnw("Failed to get session volume", "error", err)
}

return util.NormalizeScalar(level)
}

func (s *masterSession) SetVolume(v float32) error {
if err := s.volume.SetMasterVolumeLevelScalar(v, s.eventCtx); err != nil {
s.logger.Warnw("Failed to set session volume",
"error", err,
"volume", v)

return fmt.Errorf("adjust session volume: %w", err)
if s.master {
return masterSessionName
}

s.logger.Debugw("Adjusting session volume", "to", fmt.Sprintf("%.2f", v))

return nil
}

func (s *masterSession) Release() {
s.logger.Debug("Releasing audio session")

s.volume.Release()
}

func (s *masterSession) Key() string {
return masterSessionName
}

func (s *masterSession) String() string {
return fmt.Sprintf("<session: %s, vol: %.2f>", masterSessionName, s.GetVolume())
return strings.ToLower(s.name)
}
2 changes: 2 additions & 0 deletions session_finder_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ import "go.uber.org/zap"
func newSessionFinder(logger *zap.Logger) (SessionFinder, error) {
return nil, nil
}

// implement PulseAudio session finder here
5 changes: 5 additions & 0 deletions session_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// +build linux

package deej

// implement PulseAudio session here
175 changes: 175 additions & 0 deletions session_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// +build windows

package deej

import (
"errors"
"fmt"
"strings"

ole "github.com/go-ole/go-ole"
ps "github.com/mitchellh/go-ps"
wca "github.com/moutend/go-wca"
"go.uber.org/zap"

"github.com/omriharel/deej/util"
)

var errNoSuchProcess = errors.New("No such process")

type wcaSession struct {
baseSession

pid uint32
processName string

control *wca.IAudioSessionControl2
volume *wca.ISimpleAudioVolume

eventCtx *ole.GUID
}

type masterSession struct {
baseSession

volume *wca.IAudioEndpointVolume

eventCtx *ole.GUID
}

func newWCASession(
logger *zap.SugaredLogger,
control *wca.IAudioSessionControl2,
volume *wca.ISimpleAudioVolume,
pid uint32,
eventCtx *ole.GUID,
) (*wcaSession, error) {

s := &wcaSession{
control: control,
volume: volume,
pid: pid,
eventCtx: eventCtx,
}

// special treatment for system sounds session
if pid == 0 {
s.system = true
s.name = systemSessionName
s.humanReadableDesc = "system sounds"
} else {

// find our session's process name
process, err := ps.FindProcess(int(pid))
if err != nil {
logger.Warnw("Failed to find process name by ID", "pid", pid, "error", err)
defer s.Release()

return nil, fmt.Errorf("find process name by pid: %w", err)
}

// this PID may be invalid - this means the process has already been
// closed and we shouldn't create a session for it.
if process == nil {
logger.Debugw("Process already exited, not creating audio session", "pid", pid)
return nil, errNoSuchProcess
}

s.processName = process.Executable()
s.name = s.processName
s.humanReadableDesc = fmt.Sprintf("%s (pid %d)", s.processName, s.pid)
}

// use a self-identifying session name e.g. deej.sessions.chrome
s.logger = logger.Named(strings.TrimSuffix(s.Key(), ".exe"))
s.logger.Debugw(sessionCreationLogMessage, "session", s)

return s, nil
}

func newMasterSession(
logger *zap.SugaredLogger,
volume *wca.IAudioEndpointVolume,
eventCtx *ole.GUID,
) (*masterSession, error) {

s := &masterSession{
volume: volume,
eventCtx: eventCtx,
}

s.logger = logger.Named(masterSessionName)
s.master = true
s.name = masterSessionName
s.humanReadableDesc = masterSessionName

s.logger.Debugw(sessionCreationLogMessage, "session", s)

return s, nil
}

func (s *wcaSession) GetVolume() float32 {
var level float32

if err := s.volume.GetMasterVolume(&level); err != nil {
s.logger.Warnw("Failed to get session volume", "error", err)
}

return util.NormalizeScalar(level)
}

func (s *wcaSession) SetVolume(v float32) error {
if err := s.volume.SetMasterVolume(v, s.eventCtx); err != nil {
s.logger.Warnw("Failed to set session volume", "error", err)
return fmt.Errorf("adjust session volume: %w", err)
}

s.logger.Debugw("Adjusting session volume", "to", fmt.Sprintf("%.2f", v))

return nil
}

func (s *wcaSession) Release() {
s.logger.Debug("Releasing audio session")

s.volume.Release()
s.control.Release()
}

func (s *wcaSession) String() string {
return fmt.Sprintf(sessionStringFormat, s.humanReadableDesc, s.GetVolume())
}

func (s *masterSession) GetVolume() float32 {
var level float32

if err := s.volume.GetMasterVolumeLevelScalar(&level); err != nil {
s.logger.Warnw("Failed to get session volume", "error", err)
}

return util.NormalizeScalar(level)
}

func (s *masterSession) SetVolume(v float32) error {
if err := s.volume.SetMasterVolumeLevelScalar(v, s.eventCtx); err != nil {
s.logger.Warnw("Failed to set session volume",
"error", err,
"volume", v)

return fmt.Errorf("adjust session volume: %w", err)
}

s.logger.Debugw("Adjusting session volume", "to", fmt.Sprintf("%.2f", v))

return nil
}

func (s *masterSession) Release() {
s.logger.Debug("Releasing audio session")

s.volume.Release()
}

func (s *masterSession) String() string {
return fmt.Sprintf(sessionStringFormat, s.humanReadableDesc, s.GetVolume())
}

0 comments on commit 8d826d8

Please sign in to comment.