Skip to content

Commit 0563952

Browse files
committed
fix: Stop device scanning on window shutdown in device control
Applications using the device control would fail to shut down if closed while scanning. Require shutdown on owning window closure. Fixes #401
1 parent 7d0d765 commit 0563952

1 file changed

Lines changed: 202 additions & 180 deletions

File tree

Lines changed: 202 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,202 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Collections.ObjectModel;
4-
using System.Collections.Specialized;
5-
using System.ComponentModel;
6-
using System.Linq;
7-
using System.Windows;
8-
using System.Windows.Controls;
9-
using System.Windows.Threading;
10-
using Buttplug.Core;
11-
using Buttplug.Core.Messages;
12-
using Buttplug.Server;
13-
using JetBrains.Annotations;
14-
15-
namespace Buttplug.Components.Controls
16-
{
17-
/// <summary>
18-
/// Interaction logic for ButtplugDeviceControl.xaml
19-
/// </summary>
20-
public partial class ButtplugDeviceControl : UserControl
21-
{
22-
private class DeviceListItem
23-
{
24-
public ButtplugDeviceInfo Info;
25-
26-
public bool Connected;
27-
28-
public DeviceListItem(ButtplugDeviceInfo aInfo)
29-
{
30-
Info = aInfo;
31-
Connected = true;
32-
}
33-
34-
public override string ToString()
35-
{
36-
return $"{Info}" + (Connected ? string.Empty : " (disconnected)");
37-
}
38-
}
39-
40-
private class DeviceList : ObservableCollection<DeviceListItem>
41-
{
42-
public void UpdateList()
43-
{
44-
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
45-
}
46-
}
47-
48-
public event EventHandler<List<ButtplugDeviceInfo>> DeviceSelectionChanged;
49-
50-
public event EventHandler StartScanning;
51-
52-
public event EventHandler StopScanning;
53-
54-
private readonly DeviceList _devices;
55-
56-
[NotNull]
57-
private readonly List<DispatcherOperation> _ops;
58-
59-
private ButtplugServer _bpServer;
60-
61-
public ButtplugDeviceControl()
62-
{
63-
InitializeComponent();
64-
_devices = new DeviceList();
65-
InitializeComponent();
66-
DeviceListBox.ItemsSource = _devices;
67-
DeviceListBox.SelectionMode = SelectionMode.Multiple;
68-
DeviceListBox.SelectionChanged += SelectionChangedHandler;
69-
_ops = new List<DispatcherOperation>();
70-
}
71-
72-
public void SetButtplugServer(ButtplugServer aServer)
73-
{
74-
_bpServer = aServer;
75-
_bpServer.MessageReceived += OnMessageReceived;
76-
}
77-
78-
public void Reset()
79-
{
80-
_devices.Clear();
81-
StoppedScanning();
82-
}
83-
84-
public void DeviceAdded(ButtplugDeviceInfo aDev)
85-
{
86-
var devAdd = from dl in _devices
87-
where dl.Info.Index == aDev.Index
88-
select dl;
89-
if (devAdd.Any())
90-
{
91-
foreach (var dr in devAdd.ToList())
92-
{
93-
dr.Connected = true;
94-
_devices.UpdateList();
95-
}
96-
}
97-
else
98-
{
99-
_devices.Add(new DeviceListItem(aDev));
100-
}
101-
}
102-
103-
public void DeviceRemoved(uint aIndex)
104-
{
105-
var devRem = from dl in _devices
106-
where dl.Info.Index == aIndex
107-
select dl;
108-
foreach (var dr in devRem.ToList())
109-
{
110-
dr.Connected = false;
111-
_devices.UpdateList();
112-
}
113-
}
114-
115-
private void OnMessageReceived(object aObj, MessageReceivedEventArgs aEvent)
116-
{
117-
var op = Dispatcher.InvokeAsync(() =>
118-
{
119-
switch (aEvent.Message)
120-
{
121-
case DeviceAdded m:
122-
DeviceAdded(new ButtplugDeviceInfo(m.DeviceIndex, m.DeviceName, m.DeviceMessages));
123-
break;
124-
125-
case DeviceRemoved d:
126-
DeviceRemoved(d.DeviceIndex);
127-
break;
128-
}
129-
});
130-
_ops.Add(op);
131-
op.Completed += OperationCompletedHandler;
132-
}
133-
134-
private void OperationCompletedHandler(object aObj, EventArgs aEvent)
135-
{
136-
_ops.Remove(aObj as DispatcherOperation);
137-
}
138-
139-
private void SelectionChangedHandler(object aObj, EventArgs aEvent)
140-
{
141-
DeviceSelectionChanged?.Invoke(this,
142-
DeviceListBox.SelectedItems.Cast<DeviceListItem>()
143-
.Where(aLI => aLI.Connected)
144-
.Select(aLI => aLI.Info).ToList());
145-
}
146-
147-
public void StoppedScanning()
148-
{
149-
ScanButton.Content = "Start Scanning";
150-
}
151-
152-
private async void ScanButton_Click(object aSender, RoutedEventArgs aEvent)
153-
{
154-
// Disable button until we're done here
155-
ScanButton.Click -= ScanButton_Click;
156-
if ((string)ScanButton.Content == "Start Scanning")
157-
{
158-
StartScanning?.Invoke(this, new EventArgs());
159-
if (_bpServer != null)
160-
{
161-
await _bpServer.SendMessage(new StartScanning());
162-
}
163-
164-
ScanButton.Content = "Stop Scanning";
165-
}
166-
else
167-
{
168-
StopScanning?.Invoke(this, new EventArgs());
169-
if (_bpServer != null)
170-
{
171-
await _bpServer?.SendMessage(new StopScanning());
172-
}
173-
174-
ScanButton.Content = "Start Scanning";
175-
}
176-
177-
ScanButton.Click += ScanButton_Click;
178-
}
179-
}
180-
}
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Collections.Specialized;
5+
using System.ComponentModel;
6+
using System.Linq;
7+
using System.Windows;
8+
using System.Windows.Controls;
9+
using System.Windows.Threading;
10+
using Buttplug.Core;
11+
using Buttplug.Core.Messages;
12+
using Buttplug.Server;
13+
using JetBrains.Annotations;
14+
15+
namespace Buttplug.Components.Controls
16+
{
17+
/// <summary>
18+
/// Interaction logic for ButtplugDeviceControl.xaml
19+
/// </summary>
20+
public partial class ButtplugDeviceControl : UserControl
21+
{
22+
private class DeviceListItem
23+
{
24+
public ButtplugDeviceInfo Info;
25+
26+
public bool Connected;
27+
28+
public DeviceListItem(ButtplugDeviceInfo aInfo)
29+
{
30+
Info = aInfo;
31+
Connected = true;
32+
}
33+
34+
public override string ToString()
35+
{
36+
return $"{Info}" + (Connected ? string.Empty : " (disconnected)");
37+
}
38+
}
39+
40+
private class DeviceList : ObservableCollection<DeviceListItem>
41+
{
42+
public void UpdateList()
43+
{
44+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
45+
}
46+
}
47+
48+
public event EventHandler<List<ButtplugDeviceInfo>> DeviceSelectionChanged;
49+
50+
public event EventHandler StartScanning;
51+
52+
public event EventHandler StopScanning;
53+
54+
private readonly DeviceList _devices;
55+
56+
[NotNull]
57+
private readonly List<DispatcherOperation> _ops;
58+
59+
private ButtplugServer _bpServer;
60+
61+
public ButtplugDeviceControl()
62+
{
63+
InitializeComponent();
64+
_devices = new DeviceList();
65+
InitializeComponent();
66+
DeviceListBox.ItemsSource = _devices;
67+
DeviceListBox.SelectionMode = SelectionMode.Multiple;
68+
DeviceListBox.SelectionChanged += SelectionChangedHandler;
69+
_ops = new List<DispatcherOperation>();
70+
71+
// We can only attach our window closing handler over load is complete.
72+
Loaded += OnControlLoad;
73+
}
74+
75+
public void SetButtplugServer(ButtplugServer aServer)
76+
{
77+
_bpServer = aServer;
78+
_bpServer.MessageReceived += OnMessageReceived;
79+
}
80+
81+
public void Reset()
82+
{
83+
_devices.Clear();
84+
StoppedScanning();
85+
}
86+
87+
public void DeviceAdded(ButtplugDeviceInfo aDev)
88+
{
89+
var devAdd = from dl in _devices
90+
where dl.Info.Index == aDev.Index
91+
select dl;
92+
if (devAdd.Any())
93+
{
94+
foreach (var dr in devAdd.ToList())
95+
{
96+
dr.Connected = true;
97+
_devices.UpdateList();
98+
}
99+
}
100+
else
101+
{
102+
_devices.Add(new DeviceListItem(aDev));
103+
}
104+
}
105+
106+
public void DeviceRemoved(uint aIndex)
107+
{
108+
var devRem = from dl in _devices
109+
where dl.Info.Index == aIndex
110+
select dl;
111+
foreach (var dr in devRem.ToList())
112+
{
113+
dr.Connected = false;
114+
_devices.UpdateList();
115+
}
116+
}
117+
118+
private void OnControlLoad(object aObj, RoutedEventArgs aArgs)
119+
{
120+
// Attach a closing handler to the window, to make sure scanning stops when the
121+
// application is closed.
122+
var window = Window.GetWindow(this);
123+
window.Closing += OnWindowClosing;
124+
}
125+
126+
private async void OnWindowClosing(object aSender, CancelEventArgs aE)
127+
{
128+
// If server is live, stop scanning, and clear our handler.
129+
if (_bpServer != null)
130+
{
131+
await _bpServer?.SendMessage(new StopScanning());
132+
}
133+
var window = Window.GetWindow(this);
134+
window.Closing -= OnWindowClosing;
135+
}
136+
137+
private void OnMessageReceived(object aObj, MessageReceivedEventArgs aEvent)
138+
{
139+
var op = Dispatcher.InvokeAsync(() =>
140+
{
141+
switch (aEvent.Message)
142+
{
143+
case DeviceAdded m:
144+
DeviceAdded(new ButtplugDeviceInfo(m.DeviceIndex, m.DeviceName, m.DeviceMessages));
145+
break;
146+
147+
case DeviceRemoved d:
148+
DeviceRemoved(d.DeviceIndex);
149+
break;
150+
}
151+
});
152+
_ops.Add(op);
153+
op.Completed += OperationCompletedHandler;
154+
}
155+
156+
private void OperationCompletedHandler(object aObj, EventArgs aEvent)
157+
{
158+
_ops.Remove(aObj as DispatcherOperation);
159+
}
160+
161+
private void SelectionChangedHandler(object aObj, EventArgs aEvent)
162+
{
163+
DeviceSelectionChanged?.Invoke(this,
164+
DeviceListBox.SelectedItems.Cast<DeviceListItem>()
165+
.Where(aLI => aLI.Connected)
166+
.Select(aLI => aLI.Info).ToList());
167+
}
168+
169+
public void StoppedScanning()
170+
{
171+
ScanButton.Content = "Start Scanning";
172+
}
173+
174+
private async void ScanButton_Click(object aSender, RoutedEventArgs aEvent)
175+
{
176+
// Disable button until we're done here
177+
ScanButton.Click -= ScanButton_Click;
178+
if ((string)ScanButton.Content == "Start Scanning")
179+
{
180+
StartScanning?.Invoke(this, new EventArgs());
181+
if (_bpServer != null)
182+
{
183+
await _bpServer.SendMessage(new StartScanning());
184+
}
185+
186+
ScanButton.Content = "Stop Scanning";
187+
}
188+
else
189+
{
190+
StopScanning?.Invoke(this, new EventArgs());
191+
if (_bpServer != null)
192+
{
193+
await _bpServer?.SendMessage(new StopScanning());
194+
}
195+
196+
ScanButton.Content = "Start Scanning";
197+
}
198+
199+
ScanButton.Click += ScanButton_Click;
200+
}
201+
}
202+
}

0 commit comments

Comments
 (0)