-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProgram.cs
300 lines (280 loc) · 13.6 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
using System;
using System.Collections.Generic;
using NAudio.CoreAudioApi;
using System.IO;
using System.Text.Json;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;
static class Globals
{
public static bool FailedSetup = false; //if the setup fails, this gets flagged as true
public static bool NeedsSetup = false;
}
namespace CtrlVolume
{
internal class Program
{
static void Main(string[] args)
{
/*check if need to run setup*/
var generic = new Generic();
generic.SettingsPath(out string DefaultSettingsPath);
var FileOps = new FileOperations();
if (!FileOps.CheckFile(DefaultSettingsPath))
{
Globals.NeedsSetup = true;
generic.AskUserForDevice();
}
else if(FileOps.ReadFile<Settings>(DefaultSettingsPath).CtrlFriendlyName == "" || FileOps.ReadFile<Settings>(DefaultSettingsPath).CtrlFriendlyName == " ")
{
//checks if file is invalid and then starts the setup again.
Globals.NeedsSetup = true;
generic.AskUserForDevice();
}
if (!Globals.NeedsSetup)
{
//only start the volume monitor if there are no erros in the setup
Thread NotificationThread = new Thread(generic.SpawnNotification);
//start volume monitor and notification in different threads so both can run simultaniously
var vol_monitor = new Monitor();
Thread Monitor = new Thread(vol_monitor.VolumeChange);
NotificationThread.Start();
Monitor.Start();
}
else
{
MessageBox.Show("The setup was not completed.\nCtrlVolume will not work without being set up.\nThe setup will start the next time you launch the program.");
}
}
}
public class GetDevices
{
private MMDeviceEnumerator _enumerator;
public GetDevices()
{
_enumerator = new MMDeviceEnumerator(); //make an enumerator all methods can use in GetDevices so i wouldnt have to define them for every method || var enumerator = new MMDeviceEnumerator(); removed from all methods
}
public void All(out List<string> AllAudioDevices)
{
//gets all devices
List<string> AudioDevices = new List<string>();
var devices = _enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
foreach (var device in devices)
{
AudioDevices.Add(device.ToString());
}
AllAudioDevices = AudioDevices;
}
public void Default(out string DefaultAudioDevice)
{
//gets the default windows device
var DefaultAudio = _enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
DefaultAudioDevice = DefaultAudio.ToString();
}
}
public class Volume
{
private MMDeviceEnumerator _enumerator;
public Volume()
{
_enumerator = new MMDeviceEnumerator(); //make an enumerator all methods can use in Volume so i wouldnt have to define them for every method || var enumerator = new MMDeviceEnumerator(); removed from all methods
}
public void GetVolume(string DeviceFriendlyName, out float Volume)
{
Volume = -1;
var allDevices = _enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
// Find the device by friendly name
foreach (var device in allDevices)
{
if (device.FriendlyName == DeviceFriendlyName)
{ // Get the audio session manager and master volume
var audioMeter = device.AudioEndpointVolume;
Volume = (audioMeter.MasterVolumeLevelScalar * 100);
break;
}
}
}
public void WriteVolume(string DeviceFriendlyName, float Volume)
{
var devices = _enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
foreach (var device in devices)
{
if (device.FriendlyName == DeviceFriendlyName)
{
var volume = device.AudioEndpointVolume;
volume.MasterVolumeLevelScalar = Volume / 100.0f;
break;
}
}
}
public void MuteUnmuteDevice(string DeviceFriendlyName, bool Mute)
{
//true to mute, false to unmute
var devices = _enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
foreach (var device in devices)
{
if (device.FriendlyName == DeviceFriendlyName)
{
var volume = device.AudioEndpointVolume;
volume.Mute = Mute;
break;
}
}
}
}
public class FileOperations
{
public bool CheckFile(string FilePath)
{
return File.Exists(FilePath); //checks if the file/directory exists
}
public void WriteFile(string FilePath, object Data)
{
string JsonString = JsonSerializer.Serialize(Data);
File.WriteAllText(FilePath, JsonString);
}
public T ReadFile<T>(string FilePath)
{
string JsonString = File.ReadAllText(FilePath);
if(JsonString == "")
{
JsonString = "{\"CtrlFriendlyName\":\"\"}";
}
return JsonSerializer.Deserialize<T>(JsonString);
}
}
public class Settings
{
public string CtrlFriendlyName { get; set; }
}
public class Generic
{
public void AskUserForDevice()
{
var devices = new GetDevices();
devices.All(out List<string> AllAudioDevices);
devices.Default(out string DefaultAudioDevice);
//subtract default device
AllAudioDevices.Remove(DefaultAudioDevice);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new SetupForm(AllAudioDevices));
}
public void SettingsPath(out string DefaultSettingsPath)
{
DefaultSettingsPath = "./CtrlVolume_settings.json";
}
public T Clamp<T>(T val, T min, T max) where T : IComparable<T>
{
if (val.CompareTo(min) < 0) return min;
else if (val.CompareTo(max) > 0) return max;
else return val;
}
public void SpawnNotification()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
NotifyIcon notifyIcon = new NotifyIcon();//new notification icon
notifyIcon.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); //use the same icon as the application uses but now for the notification icon
notifyIcon.ContextMenuStrip = new ContextMenuStrip();
notifyIcon.ContextMenuStrip.Items.Add("Stop CtrlVolume", null, (sender, e) =>
{
// Clean up and close the application.
notifyIcon.Dispose();
Application.Exit();
Environment.Exit(0);
});
notifyIcon.ContextMenuStrip.Items.Add("Open Settings file", null, (sender, e) =>
{
SettingsPath(out string DefaultSettingsPath);
Process.Start(@"" + Environment.CurrentDirectory + "\\" + DefaultSettingsPath.Remove(0, 1));
});
notifyIcon.ContextMenuStrip.Items.Add("Made with ❤ by Sparky", null, (sender,e)=>
{
Process.Start("https://protosparky.uk"); //open url in browser
});
notifyIcon.Text = "CtrlVolume is active";
notifyIcon.Visible = true;
Application.Run();
}
}
public class Monitor
{
[DllImport("user32.dll")]
public static extern short GetAsyncKeyState(int vKey);
// Define the virtual key code for the Ctrl key
private const int VK_CONTROL = 0x11;
public void VolumeChange()
{
var volume = new Volume();
var AudioDevices = new GetDevices();
var FileOPs = new FileOperations();
var GenericOPs = new Generic();
GenericOPs.SettingsPath(out string DefaultSettingsPath);
AudioDevices.Default(out string DefaultAudioDevice);
volume.GetVolume(DefaultAudioDevice, out float PreviousDefaultDeviceVolume);
string SelectedAudioDevice = FileOPs.ReadFile<Settings>(DefaultSettingsPath).CtrlFriendlyName;
bool ctrlPressed = false;
bool deviceExists = true;
while (true)
{
if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0)
{
if (!ctrlPressed)
{
ctrlPressed = true;
volume.GetVolume(DefaultAudioDevice, out PreviousDefaultDeviceVolume); //update previous volume if ctrl key is pressed
if(PreviousDefaultDeviceVolume == -1) { deviceExists = false; }//set device flag as not false if any of the audio output devices cannot be found
}
volume.GetVolume(DefaultAudioDevice, out float CurrentDefaultDeviceVolume); //get the volume of the default audio device // used to check volume change and apply that to the select device
if (CurrentDefaultDeviceVolume == -1) { deviceExists = false; }//set device flag as not false if any of the audio output devices cannot be found
if (PreviousDefaultDeviceVolume < CurrentDefaultDeviceVolume)
{
int VolumeChange = (int)(Math.Round(PreviousDefaultDeviceVolume, 0) - Math.Round(CurrentDefaultDeviceVolume, 0));
VolumeChange = VolumeChange * -1;
if (deviceExists) { volume.WriteVolume(DefaultAudioDevice, CurrentDefaultDeviceVolume - VolumeChange); }//freeze default audio device if it exists
volume.GetVolume(SelectedAudioDevice, out float SelectedAudioDeviceVolume); //get volume of selected device
if(SelectedAudioDeviceVolume == -1) { deviceExists = false; }//set device flag as not false if any of the audio output devices cannot be found
int SelectedVolume = (int)Math.Round(SelectedAudioDeviceVolume, 0) + VolumeChange; //convert to int and apply volume change
SelectedVolume = GenericOPs.Clamp(SelectedVolume, 0, 100);
if (deviceExists) { volume.WriteVolume(SelectedAudioDevice, SelectedVolume); } //write new volume to the selected audio device
}
else if (PreviousDefaultDeviceVolume > CurrentDefaultDeviceVolume)
{
int VolumeChange = (int)(Math.Round(PreviousDefaultDeviceVolume, 0) - Math.Round(CurrentDefaultDeviceVolume, 0));
VolumeChange = VolumeChange * -1;
if (CurrentDefaultDeviceVolume == 0) {
//IF the volume reaches 0%, windows will try to mute the default audio device. The problem is that once the value to cancel the volume adjustment is written to the default device
// it'll stay muted even though the value is not 0% anymore.
//This unmutes the device if it ever reaches 0% during the volume adjustment
//Multiple unmute commands are written to be EXTRA SURE that the device is unmuted again (unmute is sticky and doenst always trigger in windows)
//I fucking hate that i had to do this but here we are
volume.MuteUnmuteDevice(DefaultAudioDevice, false);
volume.MuteUnmuteDevice(DefaultAudioDevice, false);
volume.MuteUnmuteDevice(DefaultAudioDevice, false);
}
if (deviceExists) { volume.WriteVolume(DefaultAudioDevice, CurrentDefaultDeviceVolume - VolumeChange); }//freeze default audio device if it exists
volume.GetVolume(SelectedAudioDevice, out float SelectedAudioDeviceVolume); //get volume of selected device
if(SelectedAudioDeviceVolume == -1) { deviceExists = false; }//set device flag as not false if any of the audio output devices cannot be found
int SelectedVolume = (int)Math.Round(SelectedAudioDeviceVolume, 0) + VolumeChange; //convert to int and apply volume change
SelectedVolume = GenericOPs.Clamp(SelectedVolume, 0, 100);
if (deviceExists) { volume.WriteVolume(SelectedAudioDevice, SelectedVolume); }//write new volume to the selected audio device
}
if(deviceExists == false)
{
Console.WriteLine("One or more of the configured audio devices do not exist. Skipping volume adjustment!");
}
deviceExists = true;
}
else
{
ctrlPressed = false;
}
Thread.Sleep(50);
}
}
}
}