Skip to content

Commit ee6f2c5

Browse files
committed
feat(Notification::DeviceMenu): First version of the new device menu triggered by device changed.
1 parent 7ba1d18 commit ee6f2c5

9 files changed

+264
-27
lines changed

SoundSwitch/Framework/Banner/BannerManager.cs

+17
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
********************************************************************/
1414

1515
using System;
16+
using System.Linq;
17+
using SoundSwitch.Audio.Manager;
18+
using SoundSwitch.Audio.Manager.Interop.Enum;
19+
using SoundSwitch.Model;
20+
using SoundSwitch.UI.Forms;
21+
using SoundSwitch.UI.Forms.Components;
1622

1723
namespace SoundSwitch.Framework.Banner
1824
{
@@ -23,21 +29,32 @@ public class BannerManager
2329
{
2430
private static System.Threading.SynchronizationContext syncContext;
2531
private static BannerForm banner;
32+
private static DeviceSelectorMenu menu;
2633

2734
/// <summary>
2835
/// Show a banner notification with the given data
2936
/// </summary>
3037
/// <param name="data"></param>
3138
public void ShowNotification(BannerData data)
3239
{
40+
var defaultDevice = AudioSwitcher.Instance.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eConsole);
41+
3342
// Execute the banner in the context of the UI thread
3443
syncContext.Post((d) =>
3544
{
45+
if (menu == null)
46+
{
47+
menu = new DeviceSelectorMenu();
48+
menu.Disposed += (sender, args) => menu = null;
49+
}
50+
51+
menu.SetData(AppModel.Instance.AvailablePlaybackDevices.Select(info => new AudioDeviceBox.Payload(info.LargeIcon.ToBitmap(), info.NameClean, defaultDevice.Id == info.Id)));
3652
if (banner == null)
3753
{
3854
banner = new BannerForm();
3955
banner.Disposed += (s, e) => banner = null;
4056
}
57+
4158
banner.SetData(data);
4259
}, null);
4360
}

SoundSwitch/Model/AppModel.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ public bool Telemetry
133133

134134
public IEnumerable<DeviceInfo> SelectedDevices => AppConfigs.Configuration.SelectedDevices.OrderBy(info => info.DiscoveredAt);
135135

136-
public IEnumerable<DeviceInfo> AvailablePlaybackDevices => ActiveAudioDeviceLister.PlaybackDevices.IntersectWith(SelectedDevices);
136+
public IEnumerable<DeviceFullInfo> AvailablePlaybackDevices => ActiveAudioDeviceLister.PlaybackDevices.IntersectWith(SelectedDevices);
137137

138-
public IEnumerable<DeviceInfo> AvailableRecordingDevices => ActiveAudioDeviceLister.RecordingDevices.IntersectWith(SelectedDevices);
138+
public IEnumerable<DeviceFullInfo> AvailableRecordingDevices => ActiveAudioDeviceLister.RecordingDevices.IntersectWith(SelectedDevices);
139139

140140
public bool SetCommunications
141141
{

SoundSwitch/Model/IAppModel.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ public interface IAppModel : IDisposable
3939
/// <summary>
4040
/// An union between the Active <see cref="IAudioDevice" /> of Windows and <see cref="SelectedPlaybackDevicesList" />
4141
/// </summary>
42-
IEnumerable<DeviceInfo> AvailablePlaybackDevices { get; }
42+
IEnumerable<DeviceFullInfo> AvailablePlaybackDevices { get; }
4343

4444
/// <summary>
4545
/// An union between the Active <see cref="IAudioDevice" /> of Windows and <see cref="SelectedRecordingDevicesList" />
4646
/// </summary>
47-
IEnumerable<DeviceInfo> AvailableRecordingDevices { get; }
47+
IEnumerable<DeviceFullInfo> AvailableRecordingDevices { get; }
4848

4949
/// <summary>
5050
/// If the Playback device need also to be set for Communications.

SoundSwitch/UI/Forms/Components/AudioDeviceBox.Designer.cs

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.ComponentModel;
2-
using System.Drawing;
1+
using System.Drawing;
32
using System.Windows.Forms;
43

54
namespace SoundSwitch.UI.Forms.Components
@@ -8,34 +7,19 @@ public partial class AudioDeviceBox : UserControl
87
{
98
private bool _selected;
109

11-
public record Payload(Image Image, string Label, bool Selected);
12-
13-
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
14-
[DefaultValue(false)]
15-
public bool Selected
10+
public record Payload(Image Image, string Label, bool Selected)
1611
{
17-
get => _selected;
18-
set
19-
{
20-
var oldValue = _selected;
21-
_selected = value;
22-
OnSelectedChanged(oldValue, _selected);
23-
}
12+
public Color Color => Selected ? Color.Teal : Color.Black;
2413
}
2514

2615

2716
public AudioDeviceBox(Payload payload)
2817
{
18+
InitializeComponent();
19+
2920
iconBox.DataBindings.Add(nameof(PictureBox.Image), payload, nameof(payload.Image), false, DataSourceUpdateMode.OnPropertyChanged);
3021
deviceName.DataBindings.Add(nameof(Label.Text), payload, nameof(payload.Label), false, DataSourceUpdateMode.OnPropertyChanged);
31-
Selected = payload.Selected;
32-
33-
InitializeComponent();
34-
}
35-
36-
protected void OnSelectedChanged(bool oldValue, bool newValue)
37-
{
38-
ForeColor = newValue ? Color.Teal : Color.Black;
22+
DataBindings.Add(nameof(BackColor), payload, nameof(payload.Color), false, DataSourceUpdateMode.OnPropertyChanged);
3923
}
4024
}
4125
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<root>
2+
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
3+
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
4+
<xsd:element name="root" msdata:IsDataSet="true">
5+
<xsd:complexType>
6+
<xsd:choice maxOccurs="unbounded">
7+
<xsd:element name="metadata">
8+
<xsd:complexType>
9+
<xsd:sequence>
10+
<xsd:element name="value" type="xsd:string" minOccurs="0" />
11+
</xsd:sequence>
12+
<xsd:attribute name="name" use="required" type="xsd:string" />
13+
<xsd:attribute name="type" type="xsd:string" />
14+
<xsd:attribute name="mimetype" type="xsd:string" />
15+
<xsd:attribute ref="xml:space" />
16+
</xsd:complexType>
17+
</xsd:element>
18+
<xsd:element name="assembly">
19+
<xsd:complexType>
20+
<xsd:attribute name="alias" type="xsd:string" />
21+
<xsd:attribute name="name" type="xsd:string" />
22+
</xsd:complexType>
23+
</xsd:element>
24+
<xsd:element name="data">
25+
<xsd:complexType>
26+
<xsd:sequence>
27+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
28+
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
29+
</xsd:sequence>
30+
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
31+
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
32+
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
33+
<xsd:attribute ref="xml:space" />
34+
</xsd:complexType>
35+
</xsd:element>
36+
<xsd:element name="resheader">
37+
<xsd:complexType>
38+
<xsd:sequence>
39+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
40+
</xsd:sequence>
41+
<xsd:attribute name="name" type="xsd:string" use="required" />
42+
</xsd:complexType>
43+
</xsd:element>
44+
</xsd:choice>
45+
</xsd:complexType>
46+
</xsd:element>
47+
</xsd:schema>
48+
<resheader name="resmimetype">
49+
<value>text/microsoft-resx</value>
50+
</resheader>
51+
<resheader name="version">
52+
<value>2.0</value>
53+
</resheader>
54+
<resheader name="reader">
55+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
56+
</resheader>
57+
<resheader name="writer">
58+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
59+
</resheader>
60+
</root>

SoundSwitch/UI/Forms/DeviceSelectorMenu.Designer.cs

+60
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.Collections.Generic;
2+
using System.Windows.Forms;
3+
using SoundSwitch.UI.Forms.Components;
4+
5+
namespace SoundSwitch.UI.Forms
6+
{
7+
public partial class DeviceSelectorMenu : Form
8+
{
9+
protected override bool ShowWithoutActivation => true;
10+
11+
/// <summary>
12+
/// Override the parameters used to create the window handle.
13+
/// Ensure that the window will be top-most and do not activate or take focus.
14+
/// </summary>
15+
protected override CreateParams CreateParams
16+
{
17+
get
18+
{
19+
CreateParams p = base.CreateParams;
20+
p.ExStyle |= 0x08000000; // WS_EX_NOACTIVATE
21+
p.ExStyle |= 0x00000008; // WS_EX_TOPMOST
22+
return p;
23+
}
24+
}
25+
26+
public DeviceSelectorMenu()
27+
{
28+
InitializeComponent();
29+
SetLocationToCursor();
30+
}
31+
32+
public void SetData(IEnumerable<AudioDeviceBox.Payload> payloads)
33+
{
34+
Hide();
35+
SetLocationToCursor();
36+
Controls.Clear();
37+
Height = 0;
38+
var top = 5;
39+
foreach (var payload in payloads)
40+
{
41+
var control = new AudioDeviceBox(payload);
42+
control.Top = top;
43+
Controls.Add(control);
44+
top += control.Height;
45+
}
46+
Show();
47+
}
48+
49+
private void SetLocationToCursor()
50+
{
51+
SetDesktopLocation(Cursor.Position.X, Cursor.Position.Y);
52+
Location = Cursor.Position;
53+
}
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<root>
2+
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
3+
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
4+
<xsd:element name="root" msdata:IsDataSet="true">
5+
<xsd:complexType>
6+
<xsd:choice maxOccurs="unbounded">
7+
<xsd:element name="metadata">
8+
<xsd:complexType>
9+
<xsd:sequence>
10+
<xsd:element name="value" type="xsd:string" minOccurs="0" />
11+
</xsd:sequence>
12+
<xsd:attribute name="name" use="required" type="xsd:string" />
13+
<xsd:attribute name="type" type="xsd:string" />
14+
<xsd:attribute name="mimetype" type="xsd:string" />
15+
<xsd:attribute ref="xml:space" />
16+
</xsd:complexType>
17+
</xsd:element>
18+
<xsd:element name="assembly">
19+
<xsd:complexType>
20+
<xsd:attribute name="alias" type="xsd:string" />
21+
<xsd:attribute name="name" type="xsd:string" />
22+
</xsd:complexType>
23+
</xsd:element>
24+
<xsd:element name="data">
25+
<xsd:complexType>
26+
<xsd:sequence>
27+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
28+
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
29+
</xsd:sequence>
30+
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
31+
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
32+
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
33+
<xsd:attribute ref="xml:space" />
34+
</xsd:complexType>
35+
</xsd:element>
36+
<xsd:element name="resheader">
37+
<xsd:complexType>
38+
<xsd:sequence>
39+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
40+
</xsd:sequence>
41+
<xsd:attribute name="name" type="xsd:string" use="required" />
42+
</xsd:complexType>
43+
</xsd:element>
44+
</xsd:choice>
45+
</xsd:complexType>
46+
</xsd:element>
47+
</xsd:schema>
48+
<resheader name="resmimetype">
49+
<value>text/microsoft-resx</value>
50+
</resheader>
51+
<resheader name="version">
52+
<value>2.0</value>
53+
</resheader>
54+
<resheader name="reader">
55+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
56+
</resheader>
57+
<resheader name="writer">
58+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
59+
</resheader>
60+
</root>

0 commit comments

Comments
 (0)