Skip to content

Commit bacb7de

Browse files
Fahim-zzzFahimzzzz
andauthored
DAIRemote App Settings Page (#137)
* Implemented DAIRemote App settings page, allowing user configuration of auto connect, heartbeat timeout, heartbeat interval, and maximum missed heartbeats. * Implemented modifying default audio device for display profile tiles on DAIRemote desktop application window for each display profile tile. * Updated README.md instructions. Co-authored-by: Fahimzzzz <70451976+Fahimzzzz@users.noreply.github.com>
1 parent 84726f4 commit bacb7de

File tree

17 files changed

+660
-36
lines changed

17 files changed

+660
-36
lines changed

AudioManager/AudioDeviceManager.cs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,13 @@ private void OnDefaultDeviceChanged(DeviceChangedArgs args)
4242

4343
private void OnAudioDeviceChanged(DeviceChangedArgs args)
4444
{
45-
SetAudioDefaults();
46-
AudioDevicesUpdated?.Invoke(ActiveDeviceNames);
45+
Thread.Sleep(1000);
46+
// Only refresh if the device state changed (ignores volume/mute)
47+
if (args.ChangedType == DeviceChangedType.StateChanged)
48+
{
49+
SetAudioDefaults();
50+
AudioDevicesUpdated?.Invoke(ActiveDeviceNames);
51+
}
4752
}
4853

4954
public void SubscribeAudioDevices()
@@ -65,16 +70,23 @@ public void SubscribeAudioDevices()
6570

6671
private void SetAudioDefaults()
6772
{
68-
// Set the initial values for variables needed for functions
69-
defaultAudioDevice = audioController.DefaultPlaybackDevice;
70-
// Get list of active devices
71-
SetActiveDevices();
73+
if (audioController != null)
74+
{
75+
// Set the initial values for variables needed for functions
76+
defaultAudioDevice = audioController.DefaultPlaybackDevice;
77+
// Get list of active devices
78+
SetActiveDevices();
79+
}
7280
}
7381

7482
public void RefreshAudioDeviceSubscriptions()
7583
{
76-
audioController?.Dispose();
77-
audioController = new CoreAudioController();
84+
// Clear existing subscriptions
85+
foreach (var sub in deviceSubscriptions)
86+
{
87+
sub.Dispose();
88+
}
89+
deviceSubscriptions.Clear();
7890

7991
// Renew default device subscription
8092
defaultDeviceSubscription?.Dispose();
@@ -118,10 +130,7 @@ public void SetDefaultAudioDevice(string deviceFullName)
118130

119131
if (matchedDevice != null)
120132
{
121-
if (matchedDevice != this.defaultAudioDevice)
122-
{
123-
SetDefaultAudioDevice(matchedDevice);
124-
}
133+
SetDefaultAudioDevice(matchedDevice);
125134
}
126135
}
127136

DAIRemote/DAIRemoteApplicationUI.cs

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using DisplayProfileManager;
1+
using DisplayProfileManager;
22
using Microsoft.Win32;
3+
using Newtonsoft.Json;
4+
using Newtonsoft.Json.Linq;
35
using UDPServerManagerForm;
46

57
namespace DAIRemote;
@@ -8,7 +10,9 @@ public partial class DAIRemoteApplicationUI : Form
810
{
911
private readonly TrayIconManager trayIconManager;
1012
private Form profileDialog;
13+
private Form audioDialog;
1114
private ListBox profileListBox;
15+
private ListBox audioListBox;
1216
private AudioManager.AudioDeviceManager audioManager;
1317
public static event EventHandler<NotificationEventArgs> NotificationRequested;
1418

@@ -38,6 +42,7 @@ public DAIRemoteApplicationUI()
3842
SetStartupStatus(); // Checks onStartup default value to set
3943
InitializeDisplayProfilesLayouts(); // Initialize load and delete display profile flow layouts
4044
InitializeDisplayProfilesList(); // Initialize the form & listbox used for showing display profiles list
45+
InitializeAudioDevicesList();
4146

4247
// Listen for display profile changes
4348
DisplayProfileWatcher.Initialize(DisplayConfig.GetDisplayProfilesDirectory());
@@ -142,6 +147,10 @@ private void ShowProfileOptionsMenu(object sender)
142147
ToolStripMenuItem setHotkeyItem = new("Set Hotkey");
143148
setHotkeyItem.Click += (s, e) => SetHotkeyProfileButton_Click(optionsButton, e);
144149

150+
// Add Set Default Audio Device option
151+
ToolStripMenuItem setDefaultAudioDevice = new("Set Audio");
152+
setDefaultAudioDevice.Click += (s, e) => SetDefaultAudioDevice_Click(optionsButton, e);
153+
145154
// Add Save option
146155
ToolStripMenuItem saveItem = new("Overwrite");
147156
saveItem.Click += (s, e) => SaveProfileButton_Click(optionsButton, e);
@@ -153,6 +162,7 @@ private void ShowProfileOptionsMenu(object sender)
153162
// Add options to the context menu
154163
_ = optionsMenu.Items.Add(renameItem);
155164
_ = optionsMenu.Items.Add(setHotkeyItem);
165+
_ = optionsMenu.Items.Add(setDefaultAudioDevice);
156166
_ = optionsMenu.Items.Add(saveItem);
157167
_ = optionsMenu.Items.Add(deleteItem);
158168

@@ -187,13 +197,30 @@ private void RenameProfileButton_Click(object sender, EventArgs e)
187197
);
188198
}
189199

200+
// Set hotkey tooltip function
190201
private void SetHotkeyProfileButton_Click(object sender, EventArgs e)
191202
{
192203
string profilePath = ((sender as Button)?.Tag ?? (sender as Panel)?.Tag).ToString();
193204
trayIconManager.GetHotkeyManager().ShowHotkeyInput(Path.GetFileNameWithoutExtension(profilePath), () => DisplayConfig.SetDisplaySettings(profilePath));
194205
trayIconManager.RefreshSystemTray();
195206
}
196207

208+
// Set hotkey main application pop up function to allow choosing a profile from a list and setting the hotkey.
209+
private void BtnSetDisplayProfileHotkey_click(object sender, EventArgs e)
210+
{
211+
string fileName = ShowDisplayProfilesList(DisplayConfig.GetDisplayProfilesDirectory());
212+
if (!string.IsNullOrEmpty(fileName))
213+
{
214+
trayIconManager.GetHotkeyManager().ShowHotkeyInput(fileName, () => DisplayConfig.SetDisplaySettings(DisplayConfig.GetFullDisplayProfilePath(fileName)));
215+
trayIconManager.RefreshSystemTray();
216+
}
217+
}
218+
219+
private void SetDefaultAudioDevice_Click(object sender, EventArgs e)
220+
{
221+
ShowAudioDevicesList(((sender as Button)?.Tag ?? (sender as Panel)?.Tag).ToString());
222+
}
223+
197224
private void SaveProfileButton_Click(object sender, EventArgs e)
198225
{
199226
string profilePath = ((sender as Button)?.Tag ?? (sender as Panel)?.Tag).ToString();
@@ -370,6 +397,42 @@ private void InitializeDisplayProfilesList()
370397
profileDialog.Controls.Add(actionButton);
371398
}
372399

400+
private void InitializeAudioDevicesList()
401+
{
402+
audioDialog = new()
403+
{
404+
Text = "Audio Devices",
405+
Size = new Size(400, 300),
406+
StartPosition = FormStartPosition.CenterScreen
407+
};
408+
409+
audioListBox = new()
410+
{
411+
Dock = DockStyle.Fill
412+
};
413+
audioDialog.Controls.Add(audioListBox);
414+
415+
System.Windows.Forms.Button actionButton = new()
416+
{
417+
Text = "Select Device",
418+
Dock = DockStyle.Bottom,
419+
Height = 50,
420+
FlatStyle = FlatStyle.Flat,
421+
};
422+
423+
actionButton.Click += (s, e) =>
424+
{
425+
if (audioListBox.SelectedItem == null)
426+
{
427+
_ = MessageBox.Show("Please select a new default audio device.");
428+
return;
429+
}
430+
audioDialog.DialogResult = DialogResult.OK;
431+
audioDialog.Close();
432+
};
433+
audioDialog.Controls.Add(actionButton);
434+
}
435+
373436
private string ShowDisplayProfilesList(string folderPath)
374437
{
375438
if (!Directory.Exists(folderPath))
@@ -384,13 +447,32 @@ private string ShowDisplayProfilesList(string folderPath)
384447
return profileListBox.SelectedItem?.ToString();
385448
}
386449

387-
private void BtnSetDisplayProfileHotkey_click(object sender, EventArgs e)
450+
private void ShowAudioDevicesList(string profilePath)
388451
{
389-
string fileName = ShowDisplayProfilesList(DisplayConfig.GetDisplayProfilesDirectory());
390-
if (!string.IsNullOrEmpty(fileName))
452+
// Read current settings first
453+
string json = File.ReadAllText(profilePath);
454+
JObject displaySettings = JObject.Parse(json);
455+
string currentDefaultDevice = displaySettings["defaultAudioDevice"]?.ToString();
456+
457+
// Populate the list
458+
audioListBox.Items.Clear();
459+
audioListBox.Items.AddRange(audioManager.ActiveDeviceNames.Cast<object>().ToArray());
460+
461+
// Set the current default device as selected if it exists in the list
462+
if (!string.IsNullOrEmpty(currentDefaultDevice))
391463
{
392-
trayIconManager.GetHotkeyManager().ShowHotkeyInput(fileName, () => DisplayConfig.SetDisplaySettings(DisplayConfig.GetFullDisplayProfilePath(fileName)));
393-
trayIconManager.RefreshSystemTray();
464+
int index = audioListBox.Items.Cast<string>().ToList().IndexOf(currentDefaultDevice);
465+
if (index >= 0)
466+
{
467+
audioListBox.SelectedIndex = index;
468+
}
469+
}
470+
471+
// Show dialog and check result
472+
if (audioDialog.ShowDialog() == DialogResult.OK && audioListBox.SelectedItem != null)
473+
{
474+
displaySettings["defaultAudioDevice"] = audioListBox.SelectedItem.ToString();
475+
File.WriteAllText(profilePath, displaySettings.ToString(Formatting.Indented));
394476
}
395477
}
396478

@@ -402,4 +484,4 @@ private void DAIRemoteApplicationUI_Resize(object sender, EventArgs e)
402484
trayIconManager.minimized = true;
403485
}
404486
}
405-
}
487+
}

DAIRemote/HotkeyManager.Designer.cs

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

DAIRemote/Program.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ static void Main()
1414
Application.SetCompatibleTextRenderingDefault(false);
1515
ApplicationConfiguration.Initialize();
1616
Application.Run(new DAIRemoteApplicationUI());
17-
18-
19-
2017
}
2118

2219
}

DAIRemoteApp/app/src/main/java/com/example/dairemote_app/MainActivity.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
8181
R.id.nav_about -> {
8282
navController.navigate(R.id.aboutFragment)
8383
}
84+
85+
R.id.nav_settings -> {
86+
navController.navigate(R.id.settingsFragment)
87+
}
8488
}
8589

8690
binding.drawerLayout.closeDrawer(GravityCompat.START)

DAIRemoteApp/app/src/main/java/com/example/dairemote_app/fragments/InteractionFragment.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import com.example.dairemote_app.utils.BackspaceEditText
4545
import com.example.dairemote_app.utils.ConnectionMonitor
4646
import com.example.dairemote_app.utils.DisplayProfilesRecyclerAdapter
4747
import com.example.dairemote_app.utils.KeyboardToolbar
48+
import com.example.dairemote_app.utils.SharedPrefsHelper
4849
import com.example.dairemote_app.viewmodels.ConnectionViewModel
4950
import java.net.SocketException
5051

@@ -53,6 +54,7 @@ class InteractionFragment : Fragment() {
5354
private val binding get() = _binding!!
5455
private lateinit var viewModel: ConnectionViewModel
5556
private var connectionMonitor: ConnectionMonitor? = null
57+
private lateinit var sharedPrefsHelper: SharedPrefsHelper
5658

5759
// Other variables from your Activity
5860
private lateinit var editText: BackspaceEditText
@@ -159,6 +161,7 @@ class InteractionFragment : Fragment() {
159161
viewModel.connectionManager!!
160162
)
161163
displayRecyclerViewOptions.setAdapter(displayProfilesRecyclerAdapter)
164+
sharedPrefsHelper = SharedPrefsHelper(requireContext())
162165

163166
/* val tutorial = TutorialMediator.GetInstance(AlertDialog.Builder(requireContext()))
164167
if (tutorial != null) {
@@ -188,6 +191,9 @@ class InteractionFragment : Fragment() {
188191

189192
private fun setupConnectionMonitoring(delay: Int) {
190193
connectionMonitor = viewModel.connectionManager?.let { ConnectionMonitor.getInstance(it, viewModel) }
194+
connectionMonitor?.setHeartbeatTimeout(sharedPrefsHelper.getHeartbeatTimeout())
195+
connectionMonitor?.setHeartbeatInterval(sharedPrefsHelper.getHeartbeatInterval())
196+
connectionMonitor?.setHeartmeatMaxMissed(sharedPrefsHelper.getMaxMissedHeartbeats())
191197
connectionMonitor!!.startHeartbeat(delay)
192198
}
193199

DAIRemoteApp/app/src/main/java/com/example/dairemote_app/fragments/MainFragment.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,12 @@ class MainFragment : Fragment() {
5151

5252
sharedPrefsHelper = SharedPrefsHelper(requireContext())
5353

54-
// Check for saved host and attempt automatic connection
55-
val savedHost = sharedPrefsHelper.getLastConnectedHost()
56-
if (savedHost != null && viewModel.connectionState.value != true) {
57-
attemptConnection()
54+
if(sharedPrefsHelper.getAutoConnectionSetting()) {
55+
// Check for saved host and attempt automatic connection
56+
val savedHost = sharedPrefsHelper.getLastConnectedHost()
57+
if (savedHost != null && viewModel.connectionState.value != true) {
58+
attemptConnection()
59+
}
5860
}
5961

6062
setupBackPressHandler()

0 commit comments

Comments
 (0)