Reply to Message

View discussion in a popup

Replying to:
MiMenl
Enthusiast
Enthusiast

Part 2

100% sound level reset at reconnect.

This was a hard one and actually the one issue that started it all. Trying to find a solution on the internet never got me anywhere.

Lots of recommendations of doing something during log on and log off events but no real solution to the reset of the sound levels to 100 % when reconnecting.

Even asking around at lots of places never got my any further than learn to live with this cannot be fixed.

  1. And that just is an answer I don’t like, and don’t believe (see me introduction  :smileylaugh: )

So yes it was quite some work it involves some 3rth party libraries but in the end I got something that works, only downside due to the way windows manages events it sometimes lags for 1 to 3 seconds before changing the settings but this is always better than not changing them at all.

In this case the user should be able to set the volume themselves so I created a vb.net Form application which resides in the system tray to fix the sound issue.

The application uses xenolightning / AudioSwitcher (https://github.com/xenolightning/AudioSwitcher/blob/master/LICENSE) as wrapper dll to implement some fdunctions to change the sound settings.

These dll’s can be downloaded trough the NuGet function in visual studio :

nuget.png

These DLL’s really made things a lot easier.

And are under a Microsoft Public License

So for the program :

What do we have to fix ?

Well mainly the fact that when you reconnect to a VDI the sound will reset to 100 % and the settings cannot be saved anywhere, since there is no logon or logoff event a logon or logoff DEM script will not fix this either. But it would be nice if the user preferences could be saved and restored trough DEM so let’s keep that in mind.

So what is it I can use to actually know when to change the sound levels, let’s see if there is something triggered when I reconnect back to my VDI. And this seems to be the case. Apparently at least in our environment a reconnect is seen as a session logon or a session unlock two events that can be handled by a VB.net application so let’s try to (ab)use that, and this turned out to become the main part of the program :

Adding an event handler

AddHandler Microsoft.Win32.SystemEvents.SessionSwitch, AddressOf SessionSwitch

And adding a function that triggers

Private Sub SessionSwitch(ByVal sender As Object, ByVal e As Microsoft.Win32.SessionSwitchEventArgs)

If e.Reason = e.Reason = SessionSwitchReason.SessionLogon Or e.Reason = SessionSwitchReason.SessionUnlock Then

Some code to change sound settings and done

End if

End sub

This up here looks easy but after adding some code it just wasn’t working right somehow sound could not be set at once. I read in a post that increasing sound level by 1 and decreasing it again triggers a change with a different NirSoft tool so we had to work from there. And that became the code below.

I added some comments (and if you compile it this way you will have a paypal link to me in the about form but feel free to delete it, although it was quite some work getting this stuff working)

Since the forms contain some element it might be smart to download the source file since it will be easier to recreate.

Setup from scratch :

Add two forms to the project (Form1 and Form 2)

Here is a screenshot of the controls and names on form 1 and form 2

F1.pngF2.png

Add the components to the right forms and take over the right names for the code to work.

Now add the code below to Form1

Form1

'imports some needed libraries dont forget to also reference the dlls into your solution

Imports System.ComponentModel

Imports System.Runtime.InteropServices

Imports AudioSwitcher

Imports Microsoft.Win32

Public Class Form1

    'create a sound device to use

    Public device As AudioSwitcher.AudioApi.CoreAudio.CoreAudioDevice

    Public strappdata As String

    'hide the close button on the form

    Private Const CP_NOCLOSE_BUTTON As Integer = &H200

    Protected Overloads Overrides ReadOnly Property CreateParams() As CreateParams

        'make the close button dissapear

        Get

            Dim myCp As CreateParams = MyBase.CreateParams

            myCp.ClassStyle = myCp.ClassStyle Or CP_NOCLOSE_BUTTON

            Return myCp

        End Get

    End Property

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        'add an event handler to catch the logon and unlock events

        AddHandler Microsoft.Win32.SystemEvents.SessionSwitch, AddressOf SessionSwitch

        'get the appdata folder for teh user

        strappdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)

        'get the default playback device usually the master volume when no other devices are present

        device = New AudioApi.CoreAudio.CoreAudioController().DefaultPlaybackDevice

        'print info on a label on the form and resize properly

        Label1.Text = "Current volume level of: " & device.FullName.ToString

        lblvolume.Left = Label1.Left + Label1.Width + 10

        HScrollBar1.Left = lblvolume.Left + lblvolume.Width + 10

        btnmute.Left = HScrollBar1.Left

        Button1.Left = btnmute.Left + btnmute.Width - Button1.Width

        Me.Width = HScrollBar1.Left + HScrollBar1.Width + 25

        'check for setting file and if its present read values from it to set the master volume

        If System.IO.File.Exists(strappdata.ToString & "\SetVolume\Volume.ini") = True Then

            Dim sReader As System.IO.StreamReader = Nothing

            Dim strRead() As String = Nothing

            sReader = New IO.StreamReader(strappdata.ToString & "\SetVolume\Volume.ini")

            While sReader.Peek <> -1

                strRead = Split(sReader.ReadLine, "=")

                If strRead(0).ToString = "Volume" Then

                    'set volume

                    device.Volume = Val(strRead(1).ToString)

                End If

                If strRead(0).ToString = "Mute" Then

                    'set mute state

                    If strRead(1).ToString.ToUpper = "FALSE" Then

                        device.Mute(False)

                    End If

                    If strRead(1).ToString.ToUpper = "TRUE" Then

                        device.Mute(True)

                    End If

                End If

            End While

            sReader.Close()

        Else

            'create the appdata folder if tit is not there to save ini file and store current volume settings

            If System.IO.Directory.Exists(strappdata.ToString & "\SetVolume") = False Then

                System.IO.Directory.CreateDirectory(strappdata.ToString & "\SetVolume")

                System.IO.File.Create(strappdata.ToString & "\SetVolume\Volume.ini").Close()

                Dim sWriter As System.IO.StreamWriter = Nothing

                sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)

                sWriter.WriteLine("Volume=" & device.Volume.ToString)

                sWriter.WriteLine("Mute=" & device.IsMuted.ToString)

                sWriter.Close()

            Else

                'store settings in ini file

                System.IO.File.Create(strappdata.ToString & "\SetVolume\Volume.ini").Close()

                Dim sWriter As System.IO.StreamWriter = Nothing

                sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)

                sWriter.WriteLine("Volume=" & device.Volume.ToString)

                sWriter.WriteLine("Mute=" & device.IsMuted.ToString)

                sWriter.Close()

            End If

        End If

        'set lebels on primary form

        'change value of scrollbar to show current volume level

        lblvolume.Text = device.Volume & " %"

        HScrollBar1.Value = device.Volume

        If device.IsMuted = True Then

            lblmute.Text = "Muted"

            btnmute.Text = "Unmute"

        Else

            lblmute.Text = "Unmuted"

            btnmute.Text = "Mute"

        End If

        'set interval of timer to 30 seconds and let it write the ini file every 30 seconds to preserve changes.

        Timer1.Interval = 30000

        Timer1.Enabled = True

    End Sub

    Private Sub SessionSwitch(ByVal sender As Object, ByVal e As Microsoft.Win32.SessionSwitchEventArgs)

        'check for event logon or unlock

        If e.Reason = e.Reason = SessionSwitchReason.SessionLogon Or e.Reason = SessionSwitchReason.SessionUnlock Then

            'diabel timer so settings cannot be written and prevent sound setting mechanism to mess up user settings

            Timer1.Enabled = False

            'set volume to 1 and to 0

            device.Volume = 1

            device.Volume = 0

            For a = 0 To 10

                'make the device go to mute state by reapeating the setting (it needs to be repeated to work)

                'device will always start in mute state to prevent loud noises.

                device.Mute(False)

                device.Mute(True)

                Threading.Thread.Sleep(5)

            Next a

            'start reading user settings

            Dim sReader As System.IO.StreamReader = Nothing

            Dim strRead() As String = Nothing

            sReader = New IO.StreamReader(strappdata.ToString & "\SetVolume\Volume.ini")

            While sReader.Peek <> -1

                strRead = Split(sReader.ReadLine, "=")

                For i = 0 To 25 'repest settings 25 times

                    'check for volume

                    If strRead(0).ToString = "Volume" Then

                        Threading.Thread.Sleep(5)

                        'decrease volume by 1

                        device.Volume = Val(strRead(1).ToString) - 1

                        Threading.Thread.Sleep(5)

                        'set volume to desired level

                        device.Volume = Val(strRead(1).ToString)

                    End If

                    If strRead(0).ToString = "Mute" Then

                        'set mute sate

                        If strRead(1).ToString.ToUpper = "FALSE" Then

                            device.Mute(False)

                        End If

                        If strRead(1).ToString.ToUpper = "TRUE" Then

                            device.Mute(True)

                        End If

                    End If

                Next i

            End While

            sReader.Close()

        End If

        'enable timer to save user changes

        Timer1.Enabled = True

    End Sub

    Protected Overrides Sub Finalize()

        MyBase.Finalize()

    End Sub

    Private Sub btnmute_Click(sender As Object, e As EventArgs) Handles btnmute.Click

        'add mute button actions i know GOTO but it's so easy could have used a case instead.

        If device.IsMuted = False Then

            device.Mute(True)

            lblmute.Text = "Muted"

            btnmute.Text = "Unmute"

            GoTo 1

        End If

        If device.IsMuted = True Then

            device.Mute(False)

            lblmute.Text = "Unmuted"

            btnmute.Text = "Mute"

            GoTo 1

        End If

1:

        'write new state to ini

        Dim sWriter As System.IO.StreamWriter = Nothing

        sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)

        sWriter.WriteLine("Volume=" & device.Volume.ToString)

        sWriter.WriteLine("Mute=" & device.IsMuted.ToString)

        sWriter.Close()

    End Sub

    Private Sub HScrollBar1_ValueChanged(sender As Object, e As EventArgs) Handles HScrollBar1.ValueChanged

        'handle scrollbar and settings saving

        Try

            Dim sWriter As System.IO.StreamWriter = Nothing

            sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)

            device.Volume = HScrollBar1.Value

            lblvolume.Text = device.Volume & " %"

            sWriter.WriteLine("Volume=" & device.Volume.ToString)

            sWriter.WriteLine("Mute=" & device.IsMuted.ToString)

            If device.IsMuted = True Then

                lblmute.Text = "Muted"

                btnmute.Text = "Unmute"

            Else

                lblmute.Text = "Unmuted"

                btnmute.Text = "Mute"

            End If

            sWriter.Close()

        Catch

        End Try

    End Sub

    Private Sub NotifyIcon1_MouseDoubleClick(sender As Object, e As MouseEventArgs) Handles NotifyIcon1.MouseDoubleClick

        'set low interval so when the form becomes visible it will also show the changes made trough the windows sound control

        Timer1.Interval = 300

        Timer1.Enabled = True

        Me.Show()

        Try

            Me.WindowState = FormWindowState.Normal

            If device.IsMuted = True Then

                lblmute.Text = "Unmuted"

                btnmute.Text = "Mute"

            Else

                lblmute.Text = "Muted"

                btnmute.Text = "Unmute"

            End If

        Catch

        End Try

    End Sub

    Private Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click

        'hide the program again

        Me.Hide()

        Me.ShowInTaskbar = False

        Timer1.Interval = 45000

    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

        'save settings in the background every 45 seconds

        lblvolume.Text = device.Volume & " %"

        HScrollBar1.Value = device.Volume

        If device.IsMuted = True Then

            lblmute.Text = "Muted"

            btnmute.Text = "Unmute"

        Else

            lblmute.Text = "Unmuted"

            btnmute.Text = "Mute"

        End If

        Dim sWriter As System.IO.StreamWriter = Nothing

        sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)

        sWriter.WriteLine("Volume=" & device.Volume.ToString)

        sWriter.WriteLine("Mute=" & device.IsMuted.ToString)

        sWriter.Close()

        'make me jump in font of all windows when i am visible (!update this might make you loose focus on other apps comment it out to prevent this by adding ' in front)

        If Me.Visible = True Then Me.Activate()

    End Sub

    Private Sub AboutToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles AboutToolStripMenuItem.Click

        'handle about menu item

        Form2.ShowDialog()

    End Sub

    Private Sub ExitProgramToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ExitProgramToolStripMenuItem.Click

        'handle exit menu item

        Me.Close()

    End Sub

    Private Sub Form1_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing

        'save settings befor closing the app

        Try

            Dim sWriter As System.IO.StreamWriter = Nothing

            sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)

            device.Volume = HScrollBar1.Value

            lblvolume.Text = device.Volume & " %"

            sWriter.WriteLine("Volume=" & device.Volume.ToString)

            sWriter.WriteLine("Mute=" & device.IsMuted.ToString)

            If device.IsMuted = True Then

                lblmute.Text = "Muted"

                btnmute.Text = "Unmute"

            Else

                lblmute.Text = "Unmuted"

                btnmute.Text = "Mute"

            End If

            sWriter.Close()

        Catch

        End Try

    End Sub

    Private Sub OpenAppDataToolStripMenuItem1_Click(sender As Object, e As EventArgs) Handles OpenAppDataToolStripMenuItem1.Click

        'handle open appdata menu item

        Process.Start("c:\windows\explorer.exe", strappdata.ToString)

    End Sub

    Private Sub AboutToolStripMenuItem1_Click(sender As Object, e As EventArgs) Handles AboutToolStripMenuItem1.Click

        Form2.StartPosition = FormStartPosition.CenterScreen

        Form2.ShowDialog()

        Me.Activate()

    End Sub

    Private Sub ShowFormToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ShowFormToolStripMenuItem.Click

        Timer1.Interval = 300

        Timer1.Enabled = True

        Me.Show()

        ' Me.ShowInTaskbar = True

        Me.WindowState = FormWindowState.Normal

        If device.IsMuted = True Then

            lblmute.Text = "Unmuted"

            btnmute.Text = "Mute"

        Else

            lblmute.Text = "Muted"

            btnmute.Text = "Unmute"

        End If

    End Sub

End Class

This is the main program

Add yor own text to the label on form2 currently it is :

Program to store the default audio device volume settings

within a VDI session.

The program will restore the sound level of the default sound device

on a session reconnect.

Settings are stored in %AppData%\SetVolume\Volume.ini

© Mark Platte 2020

mimenl@gmail.com

I would appreciate it if you would leave this in.

Add the following code to Form2

:smileyinfo: This is the about stuff but since code from Form1 needs it I added it here too

From2

Public Class Form2

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Me.Hide()

    End Sub

    Private Sub lbldonate_LinkClicked(sender As Object, e As LinkLabelLinkClickedEventArgs) Handles lbldonate.LinkClicked

        System.Diagnostics.Process.Start(e.Link.LinkData.ToString())

    End Sub

    Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        lbldonate.Links.Clear()

        Try

            'jkust a check to take out the label when running i a known domain environment

            If Environment.UserDomainName.ToUpper.ToString <> "somedomain" Then

                If Environment.UserDomainName.ToUpper.ToString <> "somedomain1" Then

                    lbldonate.Visible = True

                    Label2.Visible = True

                    lbldonate.Text = "Donate"

                    lbldonate.Links.Add(0, 6, "https://paypal.me/mimenl")

                End If

            End If

        Catch a As Exception

            lbldonate.Visible = True

            Label2.Visible = True

            lbldonate.Text = "Donate"

            lbldonate.Links.Add(0, 6, "https://paypal.me/mimenl")

        End Try

    End Sub

End Class

Taking this out will have some impact on changing the code and the components when you build it from scratch based on the provided code samples.

If you download the source it can be changed quite easy. But I would like / appreciate it if you would keep mentioning me in some way. I just decide  to share since this problem was really bugging me and can be quite an inconvenience for people around you.

Well I hope it will also work for you, running in debug mode from Visual studio might not always work. Compiling and running the compiled exe seems to work better.

There is not much error handling and I could only test it in our environment so let me know if things don’t work out so I can see if I can fix it.

The user settings are stored in the roaming appdata folder and can easily be saved using DEM.

Basically the flow of the application is like this  :

Save usersettings -> user disconnects-> user reconnects -> unlock/reconnect event is triggers-> Load usersettings-> restore settings -> volume is on desired level.

As mentioned before the reconnect / unlock event is not always trigger before actual sound is played, which might still cause the sound to be at 100 % for a short while.

Conclusion

Well it seems that something as simple as sound is not so simple after all. I still think that it’s fundamentally wrong that we see the 100 % behavior happening, and still feel it should be fixed in a different way. Maybe by letting the horizon agent interact with the base system and make it able to change sound setting there.

Also it seems that windows 10 itself has some weird behavior too. Try muting your VDI without the program running, while playing something on youtube, now reconnect sound will be at 100% you hear YouTube w10 will still show your device is muted.

The fixes in this post seem to work in our environment but might still not be the best but for now we don’t have much choice.

The fun part is that even when a lot of people stated it couldn’t be done there is still something that does the trick !

Let me know what you think, and if you need any help or want to get a discussion going on sound and horizon I would be happy to join.

In the end I hope that all these things are not necessary and that we are doing something totally wrong and that someone could tell me how to fix it without any self-build tools just by using what’s available in DEM and Horizon.

But is sure was a great learning experience.

Well time to log off again, and in case the code doesn't appeal to you : "It isn't code it's spaghetti Smiley Happy"

It's not code it's spaghetti, and who doesn't like pasta ?