├── LICENSE
├── README.md
├── RemoteVolumeController.sln
├── RemoteVolumeController
├── FormMain.Designer.cs
├── FormMain.cs
├── FormMain.resx
├── Program.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── RemoteVolumeController.csproj
├── RemoteVolumeControllerService
│ ├── BeefwebClient.cs
│ ├── HttpVolumeControllerServer.cs
│ └── NativeMethods.cs
├── SystemInfomation
│ ├── NativeComponent
│ │ ├── DefaultEndpointChangeClient.cs
│ │ ├── EndpointVolumeChangeClient.cs
│ │ ├── EndpointVolumeChangedEventArgs.cs
│ │ ├── IAudioEndpointVolume.cs
│ │ ├── IAudioEndpointVolumeCallback.cs
│ │ ├── IMMDevice.cs
│ │ ├── IMMDeviceEnumerator.cs
│ │ ├── IMMNotificationClient.cs
│ │ └── NativeConstants.cs
│ └── SystemVolume.cs
├── Utilities.cs
└── speaker(1).ico
└── pics
├── App.png
├── Enable.png
├── EnablePort.png
├── Tray.png
└── ViewInBrowser.png
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Bridge Law
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Remote-Volume-Controller
2 | Control your windows volume on other device by web browser.
3 |
4 | ## Requirement
5 | * Windows7 or later
6 | * .NET Framework 4.5.2 or later
7 |
8 | ## Notice
9 | If the app crashed, please send me the log file(RemoteVolumeController.log) in the same directory as this app.
10 |
11 | ## How To Use
12 |
13 | ### 1. Run app
14 |
15 | 
16 |
17 | ### 2. Active main window
18 |
19 | 
20 |
21 | ### 3. Enable server
22 |
23 | 
24 |
25 | ### 4. View in browser
26 |
27 | 
28 |
29 | ## About foobar remote control (new feature)
30 |
31 | ### 1. Install [Beefweb Remote Control](http://www.foobar2000.org/components/view/foo_beefweb) component for foobar
32 |
33 | ### 2. Enable the option below:
34 | 
35 |
36 | ### 3. Enjoy it!
37 |
--------------------------------------------------------------------------------
/RemoteVolumeController.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29509.3
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteVolumeController", "RemoteVolumeController\RemoteVolumeController.csproj", "{10275915-BE49-4291-86A1-C2622C7146FE}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {10275915-BE49-4291-86A1-C2622C7146FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {10275915-BE49-4291-86A1-C2622C7146FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {10275915-BE49-4291-86A1-C2622C7146FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {10275915-BE49-4291-86A1-C2622C7146FE}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {1D8CCF45-F79B-4CF6-9651-AC6B2009F64B}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/RemoteVolumeController/FormMain.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace RemoteVolumeController
2 | {
3 | partial class FormMain
4 | {
5 | ///
6 | /// 必需的设计器变量。
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// 清理所有正在使用的资源。
12 | ///
13 | /// 如果应释放托管资源,为 true;否则为 false。
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows 窗体设计器生成的代码
24 |
25 | ///
26 | /// 设计器支持所需的方法 - 不要修改
27 | /// 使用代码编辑器修改此方法的内容。
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.components = new System.ComponentModel.Container();
32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormMain));
33 | this.CheckBoxMute = new System.Windows.Forms.CheckBox();
34 | this.TrackBarVol = new System.Windows.Forms.TrackBar();
35 | this.LabelVol = new System.Windows.Forms.Label();
36 | this.MyNotifyIcon = new System.Windows.Forms.NotifyIcon(this.components);
37 | this.MyContextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components);
38 | this.ToolStripMenuItemShow = new System.Windows.Forms.ToolStripMenuItem();
39 | this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
40 | this.ToolStripMenuItemExit = new System.Windows.Forms.ToolStripMenuItem();
41 | this.GroupBoxVol = new System.Windows.Forms.GroupBox();
42 | this.GroupBoxConfig = new System.Windows.Forms.GroupBox();
43 | this.TextBoxAddress = new System.Windows.Forms.TextBox();
44 | this.CheckBoxAutoRunServer = new System.Windows.Forms.CheckBox();
45 | this.CheckBoxStartServer = new System.Windows.Forms.CheckBox();
46 | this.CheckBoxAutoRunApp = new System.Windows.Forms.CheckBox();
47 | this.LabelBeefwebPort = new System.Windows.Forms.Label();
48 | this.TextBoxFoobarPort = new System.Windows.Forms.TextBox();
49 | ((System.ComponentModel.ISupportInitialize)(this.TrackBarVol)).BeginInit();
50 | this.MyContextMenuStrip.SuspendLayout();
51 | this.GroupBoxVol.SuspendLayout();
52 | this.GroupBoxConfig.SuspendLayout();
53 | this.SuspendLayout();
54 | //
55 | // CheckBoxMute
56 | //
57 | this.CheckBoxMute.AutoSize = true;
58 | this.CheckBoxMute.Location = new System.Drawing.Point(9, 20);
59 | this.CheckBoxMute.Name = "CheckBoxMute";
60 | this.CheckBoxMute.Size = new System.Drawing.Size(48, 16);
61 | this.CheckBoxMute.TabIndex = 0;
62 | this.CheckBoxMute.TabStop = false;
63 | this.CheckBoxMute.Text = "静音";
64 | this.CheckBoxMute.UseVisualStyleBackColor = true;
65 | this.CheckBoxMute.CheckedChanged += new System.EventHandler(this.CheckBoxMute_CheckedChanged);
66 | //
67 | // TrackBarVol
68 | //
69 | this.TrackBarVol.AutoSize = false;
70 | this.TrackBarVol.Location = new System.Drawing.Point(54, 17);
71 | this.TrackBarVol.Maximum = 100;
72 | this.TrackBarVol.Name = "TrackBarVol";
73 | this.TrackBarVol.Size = new System.Drawing.Size(247, 28);
74 | this.TrackBarVol.TabIndex = 1;
75 | this.TrackBarVol.TabStop = false;
76 | this.TrackBarVol.TickStyle = System.Windows.Forms.TickStyle.None;
77 | this.TrackBarVol.Scroll += new System.EventHandler(this.TrackBarVol_Scroll);
78 | //
79 | // LabelVol
80 | //
81 | this.LabelVol.Location = new System.Drawing.Point(307, 21);
82 | this.LabelVol.Name = "LabelVol";
83 | this.LabelVol.Size = new System.Drawing.Size(23, 15);
84 | this.LabelVol.TabIndex = 2;
85 | this.LabelVol.Text = "100";
86 | //
87 | // MyNotifyIcon
88 | //
89 | this.MyNotifyIcon.ContextMenuStrip = this.MyContextMenuStrip;
90 | this.MyNotifyIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("MyNotifyIcon.Icon")));
91 | this.MyNotifyIcon.Text = "远程音量控制";
92 | this.MyNotifyIcon.Visible = true;
93 | this.MyNotifyIcon.DoubleClick += new System.EventHandler(this.MyNotifyIcon_DoubleClick);
94 | //
95 | // MyContextMenuStrip
96 | //
97 | this.MyContextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
98 | this.ToolStripMenuItemShow,
99 | this.toolStripSeparator1,
100 | this.ToolStripMenuItemExit});
101 | this.MyContextMenuStrip.Name = "MyContextMenuStrip";
102 | this.MyContextMenuStrip.Size = new System.Drawing.Size(157, 54);
103 | //
104 | // ToolStripMenuItemShow
105 | //
106 | this.ToolStripMenuItemShow.Name = "ToolStripMenuItemShow";
107 | this.ToolStripMenuItemShow.Size = new System.Drawing.Size(156, 22);
108 | this.ToolStripMenuItemShow.Text = "打开主窗口(&M)";
109 | this.ToolStripMenuItemShow.Click += new System.EventHandler(this.ToolStripMenuItemShow_Click);
110 | //
111 | // toolStripSeparator1
112 | //
113 | this.toolStripSeparator1.Name = "toolStripSeparator1";
114 | this.toolStripSeparator1.Size = new System.Drawing.Size(153, 6);
115 | //
116 | // ToolStripMenuItemExit
117 | //
118 | this.ToolStripMenuItemExit.Name = "ToolStripMenuItemExit";
119 | this.ToolStripMenuItemExit.Size = new System.Drawing.Size(156, 22);
120 | this.ToolStripMenuItemExit.Text = "退出(&Q)";
121 | this.ToolStripMenuItemExit.Click += new System.EventHandler(this.ToolStripMenuItemExit_Click);
122 | //
123 | // GroupBoxVol
124 | //
125 | this.GroupBoxVol.Controls.Add(this.CheckBoxMute);
126 | this.GroupBoxVol.Controls.Add(this.TrackBarVol);
127 | this.GroupBoxVol.Controls.Add(this.LabelVol);
128 | this.GroupBoxVol.Location = new System.Drawing.Point(12, 12);
129 | this.GroupBoxVol.Name = "GroupBoxVol";
130 | this.GroupBoxVol.Size = new System.Drawing.Size(338, 51);
131 | this.GroupBoxVol.TabIndex = 5;
132 | this.GroupBoxVol.TabStop = false;
133 | this.GroupBoxVol.Text = "音量";
134 | //
135 | // GroupBoxConfig
136 | //
137 | this.GroupBoxConfig.Controls.Add(this.TextBoxAddress);
138 | this.GroupBoxConfig.Controls.Add(this.CheckBoxAutoRunServer);
139 | this.GroupBoxConfig.Controls.Add(this.CheckBoxStartServer);
140 | this.GroupBoxConfig.Controls.Add(this.CheckBoxAutoRunApp);
141 | this.GroupBoxConfig.Location = new System.Drawing.Point(12, 71);
142 | this.GroupBoxConfig.Name = "GroupBoxConfig";
143 | this.GroupBoxConfig.Size = new System.Drawing.Size(338, 74);
144 | this.GroupBoxConfig.TabIndex = 6;
145 | this.GroupBoxConfig.TabStop = false;
146 | this.GroupBoxConfig.Text = "设置";
147 | //
148 | // TextBoxAddress
149 | //
150 | this.TextBoxAddress.Location = new System.Drawing.Point(110, 44);
151 | this.TextBoxAddress.Name = "TextBoxAddress";
152 | this.TextBoxAddress.ReadOnly = true;
153 | this.TextBoxAddress.Size = new System.Drawing.Size(222, 21);
154 | this.TextBoxAddress.TabIndex = 8;
155 | //
156 | // CheckBoxAutoRunServer
157 | //
158 | this.CheckBoxAutoRunServer.AutoSize = true;
159 | this.CheckBoxAutoRunServer.Location = new System.Drawing.Point(151, 20);
160 | this.CheckBoxAutoRunServer.Name = "CheckBoxAutoRunServer";
161 | this.CheckBoxAutoRunServer.Size = new System.Drawing.Size(132, 16);
162 | this.CheckBoxAutoRunServer.TabIndex = 7;
163 | this.CheckBoxAutoRunServer.Text = "Auto enable server";
164 | this.CheckBoxAutoRunServer.UseVisualStyleBackColor = true;
165 | this.CheckBoxAutoRunServer.Click += new System.EventHandler(this.CheckBoxAutoRunServer_Click);
166 | //
167 | // CheckBoxStartServer
168 | //
169 | this.CheckBoxStartServer.AutoSize = true;
170 | this.CheckBoxStartServer.Location = new System.Drawing.Point(9, 46);
171 | this.CheckBoxStartServer.Name = "CheckBoxStartServer";
172 | this.CheckBoxStartServer.Size = new System.Drawing.Size(102, 16);
173 | this.CheckBoxStartServer.TabIndex = 6;
174 | this.CheckBoxStartServer.Text = "Enable server";
175 | this.CheckBoxStartServer.UseVisualStyleBackColor = true;
176 | this.CheckBoxStartServer.Click += new System.EventHandler(this.CheckBoxStartServer_Click);
177 | //
178 | // CheckBoxAutoRunApp
179 | //
180 | this.CheckBoxAutoRunApp.AutoSize = true;
181 | this.CheckBoxAutoRunApp.Location = new System.Drawing.Point(9, 20);
182 | this.CheckBoxAutoRunApp.Name = "CheckBoxAutoRunApp";
183 | this.CheckBoxAutoRunApp.Size = new System.Drawing.Size(138, 16);
184 | this.CheckBoxAutoRunApp.TabIndex = 5;
185 | this.CheckBoxAutoRunApp.Text = "Auto run at startup";
186 | this.CheckBoxAutoRunApp.UseVisualStyleBackColor = true;
187 | this.CheckBoxAutoRunApp.Click += new System.EventHandler(this.CheckBoxAutoRunApp_Click);
188 | //
189 | // LabelBeefwebPort
190 | //
191 | this.LabelBeefwebPort.AutoSize = true;
192 | this.LabelBeefwebPort.Location = new System.Drawing.Point(12, 155);
193 | this.LabelBeefwebPort.Name = "LabelBeefwebPort";
194 | this.LabelBeefwebPort.Size = new System.Drawing.Size(275, 12);
195 | this.LabelBeefwebPort.TabIndex = 7;
196 | this.LabelBeefwebPort.Text = "Beefweb(foobar remote control component) Port";
197 | //
198 | // TextBoxFoobarPort
199 | //
200 | this.TextBoxFoobarPort.Location = new System.Drawing.Point(293, 152);
201 | this.TextBoxFoobarPort.MaxLength = 5;
202 | this.TextBoxFoobarPort.Name = "TextBoxFoobarPort";
203 | this.TextBoxFoobarPort.Size = new System.Drawing.Size(57, 21);
204 | this.TextBoxFoobarPort.TabIndex = 100;
205 | this.TextBoxFoobarPort.TabStop = false;
206 | this.TextBoxFoobarPort.TextChanged += new System.EventHandler(this.TextBoxFoobarPort_TextChanged);
207 | //
208 | // FormMain
209 | //
210 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
211 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
212 | this.ClientSize = new System.Drawing.Size(361, 177);
213 | this.Controls.Add(this.TextBoxFoobarPort);
214 | this.Controls.Add(this.LabelBeefwebPort);
215 | this.Controls.Add(this.GroupBoxConfig);
216 | this.Controls.Add(this.GroupBoxVol);
217 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
218 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
219 | this.MaximizeBox = false;
220 | this.Name = "FormMain";
221 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
222 | this.Text = "Remote Volume Controll";
223 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing);
224 | ((System.ComponentModel.ISupportInitialize)(this.TrackBarVol)).EndInit();
225 | this.MyContextMenuStrip.ResumeLayout(false);
226 | this.GroupBoxVol.ResumeLayout(false);
227 | this.GroupBoxVol.PerformLayout();
228 | this.GroupBoxConfig.ResumeLayout(false);
229 | this.GroupBoxConfig.PerformLayout();
230 | this.ResumeLayout(false);
231 | this.PerformLayout();
232 |
233 | }
234 |
235 | #endregion
236 |
237 | private System.Windows.Forms.CheckBox CheckBoxMute;
238 | private System.Windows.Forms.TrackBar TrackBarVol;
239 | private System.Windows.Forms.Label LabelVol;
240 | private System.Windows.Forms.NotifyIcon MyNotifyIcon;
241 | private System.Windows.Forms.GroupBox GroupBoxVol;
242 | private System.Windows.Forms.GroupBox GroupBoxConfig;
243 | private System.Windows.Forms.CheckBox CheckBoxStartServer;
244 | private System.Windows.Forms.CheckBox CheckBoxAutoRunApp;
245 | private System.Windows.Forms.CheckBox CheckBoxAutoRunServer;
246 | private System.Windows.Forms.TextBox TextBoxAddress;
247 | private System.Windows.Forms.ContextMenuStrip MyContextMenuStrip;
248 | private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItemShow;
249 | private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
250 | private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItemExit;
251 | private System.Windows.Forms.Label LabelBeefwebPort;
252 | private System.Windows.Forms.TextBox TextBoxFoobarPort;
253 | }
254 | }
255 |
256 |
--------------------------------------------------------------------------------
/RemoteVolumeController/FormMain.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Forms;
3 | using RemoteVolumeController.RemoteVolumeControllerService;
4 | using RemoteVolumeController.SystemInfomation;
5 |
6 | namespace RemoteVolumeController
7 | {
8 | public partial class FormMain : Form
9 | {
10 | private readonly SystemVolume SysVol = new SystemVolume();
11 | private readonly HttpVolumeControllerServer Server;
12 | private readonly Action SetValue;
13 |
14 | private bool _isSet;
15 |
16 | public FormMain()
17 | {
18 | InitializeComponent();
19 |
20 | this.Text = Utilities.StrTitle;
21 | GroupBoxConfig.Text = Utilities.StrConfig;
22 | GroupBoxVol.Text = Utilities.StrVolume;
23 | CheckBoxMute.Text = Utilities.StrMute;
24 | CheckBoxAutoRunApp.Text = Utilities.StrAutoRunApp;
25 | CheckBoxAutoRunServer.Text = Utilities.StrAutoRunServer;
26 | CheckBoxStartServer.Text = Utilities.StrStartServer;
27 | MyNotifyIcon.Text = Utilities.StrTitle;
28 | ToolStripMenuItemShow.Text = Utilities.StrOpenMainWindow;
29 | ToolStripMenuItemExit.Text = Utilities.StrExit;
30 | LabelBeefwebPort.Text = Utilities.StrFoobarPort;
31 |
32 | SysVol.VolumeChanged += SysVol_VolumeChanged;
33 | SetValue = new Action(() =>
34 | {
35 | _isSet = true;
36 | var value = Math.Ceiling(SysVol.MasterVolume * 100);
37 | LabelVol.Text = value.ToString();
38 | TrackBarVol.Value = (int)value;
39 | CheckBoxMute.Checked = SysVol.Mute;
40 | _isSet = false;
41 | });
42 | SetValue();
43 | Server = HttpVolumeControllerServer.GetOrCreateServer(SysVol);
44 |
45 | CheckBoxAutoRunApp.Checked = Utilities.AutoRunApp;
46 | CheckBoxAutoRunServer.Checked = Utilities.AutoRunServer;
47 |
48 | if (Utilities.AutoRunServer)
49 | {
50 | Server.Start();
51 | CheckBoxStartServer.Checked = true;
52 | TextBoxAddress.Text = Server.ServerAddress;
53 | }
54 |
55 | _textboxReset = true;
56 | TextBoxFoobarPort.Text = Utilities.FoobarPort.ToString();
57 | }
58 |
59 | private void SysVol_VolumeChanged(object sender, bool e)
60 | {
61 | // if (!e) return;
62 | Invoke(SetValue);
63 | }
64 |
65 |
66 |
67 |
68 | private void TrackBarVol_Scroll(object sender, EventArgs e)
69 | {
70 | if (_isSet) return;
71 | SysVol.MasterVolume = TrackBarVol.Value / 100f;
72 | LabelVol.Text = TrackBarVol.Value.ToString();
73 | }
74 |
75 | private void CheckBoxMute_CheckedChanged(object sender, EventArgs e)
76 | {
77 | if (_isSet) return;
78 | SysVol.Mute = CheckBoxMute.Checked;
79 | }
80 |
81 | private void Form1_FormClosing(object sender, FormClosingEventArgs e)
82 | {
83 | if (e.CloseReason != CloseReason.UserClosing)
84 | {
85 | SysVol.Dispose();
86 | Server.Dispose();
87 | }
88 | else
89 | {
90 | e.Cancel = true;
91 | this.Visible = false;
92 | }
93 | }
94 |
95 | private void ToolStripMenuItemExit_Click(object sender, EventArgs e) => Application.Exit();
96 | private void ToolStripMenuItemShow_Click(object sender, EventArgs e)
97 | {
98 | ShowWindow();
99 | }
100 | private void MyNotifyIcon_DoubleClick(object sender, EventArgs e)
101 | {
102 | ShowWindow();
103 | }
104 |
105 |
106 | private void ShowWindow()
107 | {
108 | this.Visible = true;
109 | ShowInTaskbar = true;
110 | this.WindowState = FormWindowState.Normal;
111 | }
112 |
113 | private void CheckBoxAutoRunApp_Click(object sender, EventArgs e)
114 | {
115 | Utilities.AutoRunApp = CheckBoxAutoRunApp.Checked;
116 | }
117 |
118 | private void CheckBoxAutoRunServer_Click(object sender, EventArgs e)
119 | {
120 | Utilities.AutoRunServer = CheckBoxAutoRunServer.Checked;
121 | }
122 |
123 | private void CheckBoxStartServer_Click(object sender, EventArgs e)
124 | {
125 | if (CheckBoxStartServer.Checked)
126 | {
127 | Server.Start();
128 | TextBoxAddress.Text = Server.ServerAddress;
129 | }
130 | else
131 | {
132 | Server.Stop();
133 | TextBoxAddress.Text = string.Empty;
134 | }
135 | }
136 |
137 |
138 | protected override void OnLoad(EventArgs e)
139 | {
140 | Visible = false; // Hide form window.
141 | ShowInTaskbar = false; // Remove from taskbar.
142 | //Opacity = 0;
143 |
144 | base.OnLoad(e);
145 | }
146 |
147 | private bool _textboxReset = false;
148 | private void TextBoxFoobarPort_TextChanged(object sender, EventArgs e)
149 | {
150 | if (_textboxReset == true)
151 | {
152 | _textboxReset = false;
153 | return;
154 | }
155 |
156 | if (string.IsNullOrWhiteSpace(TextBoxFoobarPort.Text) ||
157 | !ushort.TryParse(TextBoxFoobarPort.Text, out var port) ||
158 | port < 1024)
159 | {
160 | _textboxReset = true;
161 | TextBoxFoobarPort.Text = Utilities.FoobarPort.ToString();
162 | return;
163 | }
164 | Utilities.FoobarPort = port;
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/RemoteVolumeController/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace RemoteVolumeController
4 | {
5 | static class Program
6 | {
7 | ///
8 | /// 应用程序的主入口点。
9 | ///
10 | [STAThread]
11 | static void Main()
12 | {
13 | Utilities.RunApp("10275915-be49-4291-86a1-c2622c7146fe");
14 | }
15 |
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/RemoteVolumeController/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("RemoteVolumeController")]
9 | [assembly: AssemblyDescription("远程音量控制工具")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("differentrain")]
12 | [assembly: AssemblyProduct("RemoteVolumeController")]
13 | [assembly: AssemblyCopyright("Copyright © differentrain 2019")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // 将 ComVisible 设置为 false 会使此程序集中的类型
18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("10275915-be49-4291-86a1-c2622c7146fe")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
33 | //通过使用 "*",如下所示:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.1.0")]
36 | [assembly: AssemblyFileVersion("1.0.1.0")]
37 |
--------------------------------------------------------------------------------
/RemoteVolumeController/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本: 4.0.30319.42000
5 | //
6 | // 对此文件的更改可能导致不正确的行为,如果
7 | // 重新生成代码,则所做更改将丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace RemoteVolumeController.Properties
12 | {
13 |
14 |
15 | ///
16 | /// 强类型资源类,用于查找本地化字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或删除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// 返回此类使用的缓存 ResourceManager 实例。
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RemoteVolumeController.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// 覆盖当前线程的 CurrentUICulture 属性
56 | /// 使用此强类型的资源类的资源查找。
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/RemoteVolumeController/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/RemoteVolumeController/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace RemoteVolumeController.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/RemoteVolumeController/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/RemoteVolumeController/RemoteVolumeController.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {10275915-BE49-4291-86A1-C2622C7146FE}
8 | WinExe
9 | RemoteVolumeController
10 | RemoteVolumeController
11 | v4.5.2
12 | 512
13 | true
14 | true
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 | false
26 | true
27 |
28 |
29 | AnyCPU
30 | none
31 | true
32 | bin\Release\
33 |
34 |
35 | none
36 | 4
37 | false
38 | true
39 |
40 |
41 | speaker%281%29.ico
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Form
58 |
59 |
60 | FormMain.cs
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | FormMain.cs
80 |
81 |
82 | ResXFileCodeGenerator
83 | Resources.Designer.cs
84 | Designer
85 |
86 |
87 | True
88 | Resources.resx
89 |
90 |
91 | SettingsSingleFileGenerator
92 | Settings.Designer.cs
93 |
94 |
95 | True
96 | Settings.settings
97 | True
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/RemoteVolumeController/RemoteVolumeControllerService/BeefwebClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 |
5 | namespace RemoteVolumeController.RemoteVolumeControllerService
6 | {
7 | public static class BeefwebClient
8 | {
9 |
10 | public const int DefaultPort = 8880;
11 |
12 | private static readonly HttpClient _client = new HttpClient() { Timeout = TimeSpan.FromSeconds(2) };
13 |
14 | private static string _apiUrl = $"http://{Utilities.GetLocalIP()}:{DefaultPort}/api/player/";
15 |
16 | private static int _port = DefaultPort;
17 |
18 | public static int Port
19 | {
20 | get => _port;
21 | set
22 | {
23 | if (value < 1024 || value > ushort.MaxValue) throw new ArgumentOutOfRangeException();
24 | if (_port == value) return;
25 | _port = value;
26 | _apiUrl = $"http://{Utilities.GetLocalIP()}:{_port}/api/player/";
27 | }
28 | }
29 |
30 | public static async Task Previous() => await PostApi("previous");
31 |
32 | public static async Task Next() => await PostApi("next");
33 |
34 | public static async Task Play() => await PostApi("play");
35 |
36 | public static async Task Stop() => await PostApi("stop");
37 |
38 | public static async Task Toggle() => await PostApi("pause/toggle");
39 |
40 |
41 | private static async Task PostApi(string api)
42 | {
43 | try
44 | {
45 | await _client.PostAsync(_apiUrl + api, null);
46 | }
47 | #pragma warning disable CA1031 // Do not catch general exception types
48 | catch { }
49 | #pragma warning restore CA1031 // Do not catch general exception types
50 | }
51 |
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/RemoteVolumeController/RemoteVolumeControllerService/HttpVolumeControllerServer.cs:
--------------------------------------------------------------------------------
1 | using RemoteVolumeController.SystemInfomation;
2 | using System;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Net.NetworkInformation;
7 | using System.Security.Principal;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace RemoteVolumeController.RemoteVolumeControllerService
12 | {
13 | public sealed class HttpVolumeControllerServer : IDisposable
14 | {
15 |
16 | private readonly HttpListener _listener;
17 | private readonly SystemVolume _sysVol;
18 |
19 | private readonly int _port;
20 |
21 | private readonly string _cmdArgAddUrl;
22 | private readonly string _cmdArgDelUrl;
23 |
24 | private HttpVolumeControllerServer(int port, SystemVolume sysVol)
25 | {
26 | _listener = new HttpListener();
27 |
28 | var serAddr = $"http://+:{port.ToString()}/volume/";
29 | _cmdArgAddUrl = $"http add urlacl url={serAddr} user={WindowsIdentity.GetCurrent().Name}";
30 | _cmdArgDelUrl = $"http delete urlacl url={serAddr}";
31 |
32 | _listener.Prefixes.Add(serAddr);
33 | _sysVol = sysVol;
34 | ServerAddress = $"http://{Utilities.GetLocalIP()}:{port}/volume";
35 | _port = port;
36 | }
37 |
38 | public string ServerAddress { get; }
39 |
40 | public void Start()
41 | {
42 | if (_listener.IsListening) return;
43 |
44 | NetshSetSet(_cmdArgAddUrl, CmdArgAddPort + _port.ToString());
45 | _listener.Start();
46 |
47 | Task.Factory.StartNew(async () =>
48 | {
49 | try
50 | {
51 | while (true) await ListenFunction(_listener).ConfigureAwait(false);
52 | }
53 | #pragma warning disable CA1031 // Do not catch general exception types
54 | catch { }
55 | #pragma warning restore CA1031 // Do not catch general exception types
56 | }, TaskCreationOptions.LongRunning);
57 | }
58 |
59 | public void Stop()
60 | {
61 | if (!_listener.IsListening) return;
62 | NetshSetSet(_cmdArgDelUrl, CmdArgDelPort + _port.ToString());
63 | _listener.Stop();
64 | }
65 |
66 | private async Task ListenFunction(HttpListener l)
67 | {
68 | //try
69 | //{
70 | var context = await l.GetContextAsync().ConfigureAwait(false);
71 | var request = context.Request;
72 |
73 | var response = context.Response;
74 |
75 | if (request.HttpMethod != "GET")
76 | {
77 | response.Close();
78 | return;
79 | }
80 | string responseString;
81 |
82 | if (request.RawUrl == "/volume/" || request.RawUrl == "/volume")
83 | {
84 | responseString = Utilities.HtmpTempl;
85 | }
86 | else if (request.RawUrl.StartsWith("/volume/api/getinfo"))
87 | {
88 | responseString = $@"{{""mute"":{_sysVol.Mute.ToString().ToLower()},""vol"":{Math.Ceiling(_sysVol.MasterVolume * 100)}}}";
89 | }
90 | else if (request.RawUrl.StartsWith("/volume/api/setinfo?"))
91 | {
92 | var muteStr = request.QueryString.Get("mute");
93 | if (muteStr != null && bool.TryParse(muteStr, out var mute)) _sysVol.Mute = mute;
94 | var volStr = request.QueryString.Get("vol");
95 | if (volStr != null && int.TryParse(volStr, out var vol)) _sysVol.MasterVolume = vol / 100f;
96 | response.Close();
97 | return;
98 | }
99 | else if (request.RawUrl.StartsWith("/volume/api/lockscreen"))
100 | {
101 | NativeMethods.LockWorkStation();
102 | response.Close();
103 | return;
104 | }
105 | else if (request.RawUrl.Length >= 22 && request.RawUrl.StartsWith("/volume/api/foobar"))
106 | {
107 | switch (request.RawUrl[20])
108 | {
109 | case 'x': // next
110 | await BeefwebClient.Next();
111 | break;
112 | case 'e': // previous
113 | await BeefwebClient.Previous();
114 | break;
115 | case 'a': // start
116 | await BeefwebClient.Play();
117 | break;
118 | case 'o': // stop
119 | await BeefwebClient.Stop();
120 | break;
121 | case 'g': // toggle
122 | await BeefwebClient.Toggle();
123 | break;
124 | }
125 | response.Close();
126 | return;
127 | }
128 | else
129 | {
130 | response.Close();
131 | return;
132 | }
133 | byte[] buffer = Encoding.UTF8.GetBytes(responseString);
134 | response.ContentLength64 = buffer.Length;
135 | using (var output = response.OutputStream)
136 | await output.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
137 | }
138 |
139 |
140 |
141 |
142 | #region static members
143 | private const int MIN_DYANMIC_PORT = 49152;
144 |
145 | private static HttpVolumeControllerServer _server = null;
146 |
147 | public static HttpVolumeControllerServer GetOrCreateServer(SystemVolume sysVol) => GetOrCreateServer(MIN_DYANMIC_PORT, sysVol);
148 |
149 | public static HttpVolumeControllerServer GetOrCreateServer(int minPort, SystemVolume sysVol)
150 | {
151 | if (minPort < 1024 || minPort > ushort.MaxValue) throw new ArgumentOutOfRangeException(nameof(minPort));
152 | if (_server != null && !_server.disposedValue) return _server;
153 | _server = new HttpVolumeControllerServer(GetAvailablePort(minPort), sysVol);
154 | return _server;
155 | }
156 |
157 |
158 |
159 | private static int GetAvailablePort(int startingPort)
160 | {
161 | var properties = IPGlobalProperties.GetIPGlobalProperties();
162 | var x = properties.GetActiveTcpConnections()
163 | .Where(c => c.LocalEndPoint.Port >= startingPort)
164 | .Select(c => c.LocalEndPoint.Port)
165 | .Concat(
166 | properties.GetActiveTcpListeners()
167 | .Where(t => t.Port >= startingPort)
168 | .Select(t => t.Port))
169 | .Concat(
170 | properties.GetActiveUdpListeners()
171 | .Where(u => u.Port >= startingPort)
172 | .Select(u => u.Port)
173 | ).OrderBy(p => p);
174 |
175 | foreach (var item in x)
176 | if (item == startingPort) ++startingPort;
177 | return startingPort;
178 | }
179 | private static readonly ProcessStartInfo cmdStartInfo = new ProcessStartInfo()
180 | {
181 | FileName = "netsh",
182 | UseShellExecute = true,
183 | CreateNoWindow = true,
184 | WindowStyle = ProcessWindowStyle.Hidden,
185 | Verb = "runas"
186 | };
187 | private const string CmdArgAddPort = @"advfirewall firewall add rule name=""RemoteVolumeControl10275915-be49-4291-86a1-c2622c7146fe"" dir=in action=allow protocol=TCP localport=";
188 | private const string CmdArgDelPort = @"advfirewall firewall delete rule name=""RemoteVolumeControl10275915-be49-4291-86a1-c2622c7146fe"" protocol=TCP localport=";
189 |
190 | private static void NetshSetSet(params string[] args)
191 | {
192 | for (int i = 0; i < 2; i++)
193 | {
194 | cmdStartInfo.Arguments = args[i];
195 | using (var p = Process.Start(cmdStartInfo))
196 | {
197 | p.WaitForExit();
198 | }
199 | }
200 |
201 | }
202 |
203 |
204 | #region IDisposable Support
205 | private bool disposedValue = false;
206 | public void Dispose()
207 | {
208 | if (!disposedValue)
209 | {
210 | NetshSetSet(_cmdArgDelUrl, CmdArgDelPort + _port.ToString());
211 | _listener.Abort();
212 | _listener.Close();
213 | disposedValue = true;
214 | }
215 | }
216 | #endregion
217 |
218 |
219 |
220 | #endregion
221 |
222 |
223 |
224 |
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/RemoteVolumeController/RemoteVolumeControllerService/NativeMethods.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | namespace RemoteVolumeController.RemoteVolumeControllerService
4 | {
5 | internal static class NativeMethods
6 | {
7 | [DllImport("user32.dll", SetLastError = false)]
8 | public static extern bool LockWorkStation();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/RemoteVolumeController/SystemInfomation/NativeComponent/DefaultEndpointChangeClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 |
5 | namespace RemoteVolumeController.SystemInfomation.NativeComponent
6 | {
7 | [Guid("7991EEC9-7E89-4D85-8390-6C703CEC60C0")]
8 | internal sealed class DefaultEndpointChangeClient : IMMNotificationClient
9 | {
10 | public event EventHandler OnDefaultEndpointChanged;
11 |
12 | int IMMNotificationClient.Reserve1() => throw new NotImplementedException();
13 | int IMMNotificationClient.Reserve2() => throw new NotImplementedException();
14 | int IMMNotificationClient.Reserve3() => throw new NotImplementedException();
15 | int IMMNotificationClient.OnDefaultDeviceChanged(int dataFlow, int role, string pwstrDefaultDeviceId)
16 | {
17 | if (string.IsNullOrEmpty(pwstrDefaultDeviceId)) throw new InvalidComObjectException();
18 | if (dataFlow == NativeConstants.EDataFlow_eRender &&
19 | role == NativeConstants.ERole_eMultimedia)
20 | {
21 | OnDefaultEndpointChanged?.Invoke(this, pwstrDefaultDeviceId);
22 | }
23 | return 0;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/RemoteVolumeController/SystemInfomation/NativeComponent/EndpointVolumeChangeClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 |
5 | namespace RemoteVolumeController.SystemInfomation.NativeComponent
6 | {
7 | [Guid("657804FA-D6AD-4496-8A60-352752AF4F89")]
8 | internal sealed class EndpointVolumeChangeClient : IAudioEndpointVolumeCallback
9 | {
10 | public event EventHandler OnVolumeChanged;
11 |
12 |
13 | int IAudioEndpointVolumeCallback.OnNotify(IntPtr pNotify)
14 | {
15 | OnVolumeChanged?.Invoke(this,new EndpointVolumeChangedEventArgs(pNotify));
16 | return 0;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/RemoteVolumeController/SystemInfomation/NativeComponent/EndpointVolumeChangedEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 |
5 | namespace RemoteVolumeController.SystemInfomation.NativeComponent
6 | {
7 |
8 | internal sealed class EndpointVolumeChangedEventArgs : EventArgs
9 | {
10 |
11 | public readonly Guid GuidEventContext;
12 | public readonly bool Muted;
13 | public readonly float MasterVolume;
14 | public readonly uint Channels;
15 | public readonly float[] ChannelVolumes;
16 |
17 |
18 | public EndpointVolumeChangedEventArgs(IntPtr ptr)
19 | {
20 | if (ptr == IntPtr.Zero) throw new InvalidComObjectException();
21 | unsafe
22 | {
23 | GuidEventContext = *(Guid*)ptr;
24 | ptr += NativeConstants.GUID_SIZE;
25 | Muted = *(bool*)ptr;
26 | MasterVolume = *(float*)(ptr + 4);
27 | Channels = *(uint*)(ptr + 8);
28 | ChannelVolumes = new float[Channels];
29 | fixed (float* pf = ChannelVolumes)
30 | {
31 | for (int i = 0; i < Channels; i++)
32 | {
33 | pf[i] = *(float*)(ptr + 12 + i * 4);
34 | }
35 | }
36 | }
37 | Marshal.Release(ptr);
38 | }
39 |
40 |
41 |
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/RemoteVolumeController/SystemInfomation/NativeComponent/IAudioEndpointVolume.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 |
5 | namespace RemoteVolumeController.SystemInfomation.NativeComponent
6 | {
7 | [Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
8 | internal interface IAudioEndpointVolume
9 | {
10 | [PreserveSig]
11 | int RegisterControlChangeNotify([In] IAudioEndpointVolumeCallback pNotify);
12 |
13 | [PreserveSig]
14 | int UnregisterControlChangeNotify([In] IAudioEndpointVolumeCallback pNotify);
15 |
16 | [PreserveSig]
17 | int GetChannelCount(
18 | [MarshalAs(UnmanagedType.U4), Out] out uint channelCount);
19 |
20 | int Reserve1();
21 |
22 | [PreserveSig]
23 | int SetMasterVolumeLevelScalar(
24 | [MarshalAs(UnmanagedType.R4), In] float level,
25 | [MarshalAs(UnmanagedType.LPStruct), In]Guid eventContext);
26 |
27 | int Reserve2();
28 |
29 | [PreserveSig]
30 | int GetMasterVolumeLevelScalar(
31 | [MarshalAs(UnmanagedType.R4), Out] out float level);
32 |
33 | int Reserve3();
34 |
35 | [PreserveSig]
36 | int SetChannelVolumeLevelScalar(
37 | [MarshalAs(UnmanagedType.U4), In] uint channelNumber,
38 | [MarshalAs(UnmanagedType.R4), In] float level,
39 | [MarshalAs(UnmanagedType.LPStruct), In] Guid eventContext);
40 |
41 | int Reserve4();
42 |
43 | [PreserveSig]
44 | int GetChannelVolumeLevelScalar(
45 | [MarshalAs(UnmanagedType.U4), In] uint channelNumber,
46 | [MarshalAs(UnmanagedType.R4), Out] out float level);
47 |
48 | [PreserveSig]
49 | int SetMute(
50 | [MarshalAs(UnmanagedType.Bool), In] bool isMuted,
51 | [MarshalAs(UnmanagedType.LPStruct), In] Guid eventContext);
52 |
53 | [PreserveSig]
54 | int GetMute(
55 | [MarshalAs(UnmanagedType.Bool), Out] out bool isMuted);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/RemoteVolumeController/SystemInfomation/NativeComponent/IAudioEndpointVolumeCallback.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 |
5 | namespace RemoteVolumeController.SystemInfomation.NativeComponent
6 | {
7 | [ComImport]
8 | [Guid("657804FA-D6AD-4496-8A60-352752AF4F89"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
9 | internal interface IAudioEndpointVolumeCallback
10 | {
11 | [PreserveSig]
12 | int OnNotify(IntPtr notifyData);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/RemoteVolumeController/SystemInfomation/NativeComponent/IMMDevice.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 |
5 | namespace RemoteVolumeController.SystemInfomation.NativeComponent
6 | {
7 | [ComImport]
8 | [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
9 | internal interface IMMDevice
10 | {
11 | [PreserveSig]
12 | int Activate(
13 | [MarshalAs(UnmanagedType.LPStruct), In] Guid iid,
14 | [In] int dwClsCtx,
15 | [In] IntPtr pActivationParams,
16 | [Out] out IAudioEndpointVolume ppInterface);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/RemoteVolumeController/SystemInfomation/NativeComponent/IMMDeviceEnumerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 |
5 | namespace RemoteVolumeController.SystemInfomation.NativeComponent
6 | {
7 | [ComImport]
8 | [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
9 | internal interface IMMDeviceEnumerator
10 | {
11 | int Reserve1();
12 |
13 | [PreserveSig]
14 | int GetDefaultAudioEndpoint(
15 | [In] int dataFlow,
16 | [In] int role,
17 | [Out] out IMMDevice ppDevice);
18 |
19 | [PreserveSig]
20 | int GetDevice(
21 | [MarshalAs(UnmanagedType.LPWStr), In] string pwstrId,
22 | [Out] out IMMDevice ppDevice);
23 |
24 | [PreserveSig]
25 | int RegisterEndpointNotificationCallback(
26 | [In] IMMNotificationClient pClient);
27 |
28 | [PreserveSig]
29 | int UnregisterEndpointNotificationCallback(
30 | [In] IMMNotificationClient pClient);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/RemoteVolumeController/SystemInfomation/NativeComponent/IMMNotificationClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace RemoteVolumeController.SystemInfomation.NativeComponent
5 | {
6 | [ComImport]
7 | [Guid("7991EEC9-7E89-4D85-8390-6C703CEC60C0"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
8 | internal interface IMMNotificationClient
9 | {
10 | int Reserve1();
11 | int Reserve2();
12 | int Reserve3();
13 |
14 | [PreserveSig]
15 | int OnDefaultDeviceChanged([In] int dataFlow, [In] int role, [MarshalAs(UnmanagedType.LPWStr), In] string pwstrDefaultDeviceId);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/RemoteVolumeController/SystemInfomation/NativeComponent/NativeConstants.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace RemoteVolumeController.SystemInfomation.NativeComponent
4 | {
5 | internal static class NativeConstants
6 | {
7 | public const int GUID_SIZE = 16;
8 | public const int EDataFlow_eRender = 0;
9 | public const int ERole_eMultimedia = 1;
10 | public const int CLSCTX_ALL = 0x01 | 0x02 | 0x04 | 0x10;
11 | public static readonly Guid MMDeviceEnumeratorCLSID = new Guid("BCDE0395-E52F-467C-8E3D-C4579291692E");
12 | public static readonly Guid IAudioEndpointVolumeCLSID = typeof(IAudioEndpointVolume).GUID;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/RemoteVolumeController/SystemInfomation/SystemVolume.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using RemoteVolumeController.SystemInfomation.NativeComponent;
4 |
5 | namespace RemoteVolumeController.SystemInfomation
6 | {
7 | public sealed class SystemVolume : IDisposable
8 | {
9 |
10 | private readonly Guid _mGuid;
11 |
12 | private readonly DefaultEndpointChangeClient _endpointChangedClient;
13 | private readonly EndpointVolumeChangeClient _endpointVolumeClient;
14 |
15 | private readonly IMMDeviceEnumerator _devices;
16 | private IAudioEndpointVolume _endpoint;
17 |
18 | private bool _isMuted;
19 | private float _masterVol;
20 | private uint _channelCount;
21 | private float[] _channelVols;
22 |
23 | public SystemVolume()
24 | {
25 | _devices = (IMMDeviceEnumerator)Activator.CreateInstance(Type.GetTypeFromCLSID(NativeConstants.MMDeviceEnumeratorCLSID));
26 | _mGuid = Guid.NewGuid();
27 | _endpointChangedClient = new DefaultEndpointChangeClient();
28 | _endpointChangedClient.OnDefaultEndpointChanged += EndpointChangedClient_OnDefaultEndpointChanged;
29 | _endpointVolumeClient = new EndpointVolumeChangeClient();
30 | _endpointVolumeClient.OnVolumeChanged += EndpointVolumeClient_OnVolumeChanged;
31 | Initialize(null);
32 | }
33 |
34 | public event EventHandler VolumeChanged;
35 |
36 | public bool Mute
37 | {
38 | get => _isMuted;
39 | set
40 | {
41 | if (_endpoint.SetMute(value, _mGuid) < 0) throw new InvalidOperationException();
42 | }
43 | }
44 |
45 | public int ChannelCount => (int)_channelCount;
46 |
47 | public float MasterVolume
48 | {
49 | get => _masterVol;
50 | set
51 | {
52 | if (_masterVol < 0 || _masterVol > 1) throw new ArgumentOutOfRangeException();
53 | if (_endpoint.SetMasterVolumeLevelScalar(value, _mGuid) < 0) throw new InvalidOperationException();
54 | }
55 | }
56 |
57 | public float this[int channelIndex]
58 | {
59 | get => _channelVols[channelIndex];
60 | set
61 | {
62 | if (_endpoint.SetChannelVolumeLevelScalar((uint)channelIndex, value, _mGuid) < 0) throw new InvalidOperationException();
63 | }
64 |
65 | }
66 |
67 | private void EndpointVolumeClient_OnVolumeChanged(object sender, EndpointVolumeChangedEventArgs e)
68 | {
69 | _isMuted = e.Muted;
70 | _masterVol = e.MasterVolume;
71 | _channelCount = e.Channels;
72 | _channelVols = e.ChannelVolumes;
73 | VolumeChanged?.Invoke(this, e.GuidEventContext != _mGuid);
74 | }
75 |
76 | private void EndpointChangedClient_OnDefaultEndpointChanged(object sender, string e)
77 | {
78 | Release();
79 | Initialize(e);
80 | }
81 |
82 | private void Initialize(string deviceID = null)
83 | {
84 | disposedValue = true;
85 |
86 | IMMDevice defaultDevice;
87 | if (deviceID == null)
88 | {
89 | if (_devices.GetDefaultAudioEndpoint(
90 | NativeConstants.EDataFlow_eRender,
91 | NativeConstants.ERole_eMultimedia,
92 | out defaultDevice) < 0)
93 | {
94 | ThrowInvalid();
95 | }
96 | }
97 | else if (_devices.GetDevice(deviceID, out defaultDevice) < 0)
98 | {
99 | ThrowInvalid();
100 | }
101 |
102 | if (defaultDevice.Activate(
103 | NativeConstants.IAudioEndpointVolumeCLSID,
104 | NativeConstants.CLSCTX_ALL,
105 | IntPtr.Zero,
106 | out _endpoint) < 0)
107 | {
108 | Marshal.ReleaseComObject(defaultDevice);
109 | ThrowInvalid();
110 | }
111 | Marshal.ReleaseComObject(defaultDevice);
112 |
113 |
114 | if (_endpoint.GetMute(out _isMuted) < 0 ||
115 | _endpoint.GetMasterVolumeLevelScalar(out _masterVol) < 0 ||
116 | _endpoint.GetChannelCount(out _channelCount) < 0)
117 | {
118 | ThrowInvalidAll();
119 | }
120 |
121 | _channelVols = new float[_channelCount];
122 |
123 | unsafe
124 | {
125 | fixed (float* pf = _channelVols)
126 | {
127 | for (uint i = 0; i < _channelCount; i++)
128 | {
129 | if (_endpoint.GetChannelVolumeLevelScalar(i, out pf[i]) < 0) ThrowInvalidAll();
130 | }
131 | }
132 | }
133 |
134 | disposedValue = false;
135 |
136 | _devices.RegisterEndpointNotificationCallback(_endpointChangedClient);
137 | _endpoint.RegisterControlChangeNotify(_endpointVolumeClient);
138 |
139 | if (deviceID != null) VolumeChanged?.Invoke(this, default);
140 |
141 |
142 | void ThrowInvalid()
143 | {
144 | Marshal.ReleaseComObject(_devices);
145 | throw new InvalidOperationException();
146 | }
147 |
148 | void ThrowInvalidAll()
149 | {
150 | Marshal.ReleaseComObject(_endpoint);
151 | ThrowInvalid();
152 | }
153 | }
154 |
155 | private void Release()
156 | {
157 | try
158 | {
159 | _devices.UnregisterEndpointNotificationCallback(_endpointChangedClient);
160 | _endpoint.UnregisterControlChangeNotify(_endpointVolumeClient);
161 | }
162 | #pragma warning disable CA1031 // Do not catch general exception types
163 | catch { }
164 | #pragma warning restore CA1031 // Do not catch general exception types
165 | Marshal.ReleaseComObject(_endpoint);
166 | Marshal.ReleaseComObject(_devices);
167 | }
168 |
169 |
170 |
171 | #region IDisposable Support
172 | private bool disposedValue = false;
173 |
174 |
175 | private void DisposeCore()
176 | {
177 | if (!disposedValue)
178 | {
179 | Release();
180 | _endpointChangedClient.OnDefaultEndpointChanged -= EndpointChangedClient_OnDefaultEndpointChanged;
181 | _endpointVolumeClient.OnVolumeChanged -= EndpointVolumeClient_OnVolumeChanged;
182 | disposedValue = true;
183 | }
184 | }
185 |
186 | public void Dispose()
187 | {
188 | DisposeCore();
189 | GC.SuppressFinalize(this);
190 | }
191 | ~SystemVolume() => DisposeCore();
192 |
193 |
194 | #endregion
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/RemoteVolumeController/Utilities.cs:
--------------------------------------------------------------------------------
1 | using RemoteVolumeController.RemoteVolumeControllerService;
2 | using System;
3 | using System.Globalization;
4 | using System.IO;
5 | using System.Net;
6 | using System.Net.Sockets;
7 | using System.Runtime.InteropServices;
8 | using System.Runtime.InteropServices.ComTypes;
9 | using System.Text;
10 | using System.Threading;
11 | using System.Windows.Forms;
12 |
13 | namespace RemoteVolumeController
14 | {
15 | internal static class Utilities
16 | {
17 | private static readonly string _savefilePath = Application.StartupPath + @"\RemoteVolumeController.config";
18 | private static readonly string _errorLogPath = Application.StartupPath + @"\RemoteVolumeController.log";
19 |
20 | public static readonly string StrTitle;
21 | public static readonly string StrStartServer;
22 | public static readonly string StrAutoRunApp;
23 | public static readonly string StrAutoRunServer;
24 | public static readonly string StrConfig;
25 | public static readonly string StrVolume;
26 | public static readonly string StrMute;
27 | public static readonly string StrOpenMainWindow;
28 | public static readonly string StrExit;
29 | public static readonly string StrLockScreen;
30 | public static readonly string StrRefresh;
31 |
32 | public static readonly string StrPlayer;
33 | public static readonly string StrPrevious;
34 | public static readonly string StrNext;
35 | public static readonly string StrStart;
36 | public static readonly string StrStop;
37 | public static readonly string StrToggle;
38 | public static readonly string StrFoobarPort;
39 |
40 | public static readonly string DefaultServerName;
41 | public static readonly string HtmpTempl;
42 |
43 | private static bool _autoRunApp = false;
44 | private static bool _autoRunServer = false;
45 | private static uint _port = 8880;
46 |
47 | #pragma warning disable CA1810 // Initialize reference type static fields inline
48 | static Utilities()
49 | #pragma warning restore CA1810 // Initialize reference type static fields inline
50 | {
51 | if (File.Exists(_savefilePath))
52 | {
53 | using (var fr = new FileStream(_savefilePath, FileMode.Open, FileAccess.Read))
54 | {
55 | if (fr.Length != 3)
56 | {
57 | fr.Dispose();
58 | File.Delete(_savefilePath);
59 | WriteConfig();
60 | return;
61 | }
62 |
63 |
64 | _autoRunApp = File.Exists(_myAutoRunPath);
65 | if (_autoRunApp) SetShortcut(true);
66 |
67 |
68 | var buf = new byte[3];
69 | fr.Read(buf, 0, 3);
70 | _autoRunServer = buf[0] == 1;
71 | _port = BitConverter.ToUInt16(buf, 1);
72 | BeefwebClient.Port = (int)_port;
73 | }
74 | }
75 | else
76 | {
77 | SetShortcut(false);
78 | WriteConfig();
79 | }
80 |
81 | if (CultureInfo.InstalledUICulture.ThreeLetterISOLanguageName == "zho")
82 | {
83 | StrTitle = "远程音量控制";
84 | StrConfig = "设置";
85 | StrStartServer = "启用服务器";
86 | StrAutoRunApp = "开机自动运行";
87 | StrAutoRunServer = "自动启用服务器";
88 | StrVolume = "音量";
89 | StrMute = "静音";
90 | StrOpenMainWindow = "打开主窗口(&M)";
91 | StrExit = "退出(&Q)";
92 | StrLockScreen = "锁定屏幕";
93 | StrRefresh = "刷新";
94 |
95 | StrPlayer = "播放器";
96 | StrPrevious = "前一首";
97 | StrNext = "下一首";
98 | StrStart = "播放";
99 | StrStop = "停止";
100 | StrToggle = "切换";
101 | StrFoobarPort = "Beefweb(foobar远程控制组件)端口号:";
102 | }
103 | else
104 | {
105 | StrTitle = "Remote Volume Controll";
106 | StrConfig = "Config";
107 | StrStartServer = "Enable Server";
108 | StrAutoRunApp = "Auto Run At Startup";
109 | StrAutoRunServer = "Auto Enable Server";
110 | StrVolume = "Vol";
111 | StrMute = "Mute";
112 | StrOpenMainWindow = "Open Main Window(&M)";
113 | StrExit = "Quit(&Q)";
114 | StrLockScreen = "Lock Screen";
115 | StrRefresh = "Refresh";
116 | StrPlayer = "player";
117 | StrPrevious = "Previous";
118 | StrNext = "Next";
119 | StrStart = "Play";
120 | StrStop = "Stop";
121 | StrToggle = "Toggle";
122 | StrFoobarPort = "Beefweb(foobar remote control component) Port";
123 | }
124 |
125 | DefaultServerName = Dns.GetHostName() + $" {StrVolume}";
126 | HtmpTempl = Encoding.UTF8.GetString(
127 | Convert.FromBase64String(
128 | "PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+PG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xIj48bWV0YSBjaGFyc2V0PSJVVEYtOCI+PHRpdGxlPiVTZXJ2ZXJOYW1lJTwvdGl0bGU+PHN0eWxlPi5zbGlkZXJ7LXdlYmtpdC1hcHBlYXJhbmNlOm5vbmU7d2lkdGg6OTglO2hlaWdodDoyNXB4O2JhY2tncm91bmQ6I2QzZDNkMztvdXRsaW5lOjA7b3BhY2l0eTouNzstd2Via2l0LXRyYW5zaXRpb246LjJzO3RyYW5zaXRpb246b3BhY2l0eSAuMnN9LnNsaWRlcjpob3ZlcntvcGFjaXR5OjF9LnNsaWRlcjo6LXdlYmtpdC1zbGlkZXItdGh1bWJ7LXdlYmtpdC1hcHBlYXJhbmNlOm5vbmU7YXBwZWFyYW5jZTpub25lO3dpZHRoOjI1cHg7aGVpZ2h0OjI1cHg7YmFja2dyb3VuZDojNGNhZjUwO2N1cnNvcjpwb2ludGVyfS5zbGlkZXI6Oi1tb3otcmFuZ2UtdGh1bWJ7d2lkdGg6MjVweDtoZWlnaHQ6MjVweDtiYWNrZ3JvdW5kOiM0Y2FmNTA7Y3Vyc29yOnBvaW50ZXJ9PC9zdHlsZT48c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCI+d2luZG93Lm9ubG9hZD1mdW5jdGlvbigpe2dldEluZm8oKTt2b2w9ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoIm15UmFuZ2UiKTtjb25zdCByYW5nZXM9UmFuZ2VUb3VjaC5zZXR1cCh2b2wpfTtmdW5jdGlvbiBnZXRJbmZvKCl7dm9sPWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJteVJhbmdlIik7dHh0PWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJteVRleHQiKTttdXRlPWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJteU11dGUiKTt2YXIgeG1saHR0cD1uZXcgWE1MSHR0cFJlcXVlc3QoKTt4bWxodHRwLm9ucmVhZHlzdGF0ZWNoYW5nZT1mdW5jdGlvbigpe2lmKHhtbGh0dHAucmVhZHlTdGF0ZT09NCYmeG1saHR0cC5zdGF0dXM9PTIwMCl7dmFyIGpzb249SlNPTi5wYXJzZSh4bWxodHRwLnJlc3BvbnNlVGV4dCk7bXV0ZS5jaGVja2VkPWpzb24ubXV0ZTt2b2wudmFsdWU9anNvbi52b2w7dHh0LmlubmVySFRNTD0iJVZvbCXvvJoiK3ZvbC52YWx1ZX19O3htbGh0dHAub3BlbigiR0VUIiwiL3ZvbHVtZS9hcGkvZ2V0aW5mbyIsZmFsc2UpO3htbGh0dHAuc2VuZCgpfWZ1bmN0aW9uIGxvY2tTY3JlZW4oKXt2YXIgeG1saHR0cD1uZXcgWE1MSHR0cFJlcXVlc3QoKTt4bWxodHRwLm9wZW4oIkdFVCIsIi92b2x1bWUvYXBpL2xvY2tzY3JlZW4iLGZhbHNlKTt4bWxodHRwLnNlbmQoKX1mdW5jdGlvbiBmb29iYXJOZXh0KCl7dmFyIHhtbGh0dHA9bmV3IFhNTEh0dHBSZXF1ZXN0KCk7eG1saHR0cC5vcGVuKCJHRVQiLCIvdm9sdW1lL2FwaS9mb29iYXJOZXh0IixmYWxzZSk7eG1saHR0cC5zZW5kKCl9ZnVuY3Rpb24gZm9vYmFyUHJldmlvdXMoKXt2YXIgeG1saHR0cD1uZXcgWE1MSHR0cFJlcXVlc3QoKTt4bWxodHRwLm9wZW4oIkdFVCIsIi92b2x1bWUvYXBpL2Zvb2JhclByZXZpb3VzIixmYWxzZSk7eG1saHR0cC5zZW5kKCl9ZnVuY3Rpb24gZm9vYmFyU3RhcnQoKXt2YXIgeG1saHR0cD1uZXcgWE1MSHR0cFJlcXVlc3QoKTt4bWxodHRwLm9wZW4oIkdFVCIsIi92b2x1bWUvYXBpL2Zvb2JhclN0YXJ0IixmYWxzZSk7eG1saHR0cC5zZW5kKCl9ZnVuY3Rpb24gZm9vYmFyU3RvcCgpe3ZhciB4bWxodHRwPW5ldyBYTUxIdHRwUmVxdWVzdCgpO3htbGh0dHAub3BlbigiR0VUIiwiL3ZvbHVtZS9hcGkvZm9vYmFyU3RvcCIsZmFsc2UpO3htbGh0dHAuc2VuZCgpfWZ1bmN0aW9uIGZvb2JhclRvZ2dsZSgpe3ZhciB4bWxodHRwPW5ldyBYTUxIdHRwUmVxdWVzdCgpO3htbGh0dHAub3BlbigiR0VUIiwiL3ZvbHVtZS9hcGkvZm9vYmFyVG9nZ2xlIixmYWxzZSk7eG1saHR0cC5zZW5kKCl9ZnVuY3Rpb24gc2V0SW5mbygpe3ZhciB4bWxodHRwPW5ldyBYTUxIdHRwUmVxdWVzdCgpO3ZvbD1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgibXlSYW5nZSIpO3R4dD1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgibXlUZXh0Iik7dHh0LmlubmVySFRNTD0iJVZvbCXvvJoiK3ZvbC52YWx1ZTttdXRlPWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJteU11dGUiKTt4bWxodHRwLm9wZW4oIkdFVCIsIi92b2x1bWUvYXBpL3NldGluZm8/IisibXV0ZT0iK211dGUuY2hlY2tlZCsiJnZvbD0iK3ZvbC52YWx1ZSxmYWxzZSk7eG1saHR0cC5zZW5kKCl9OyFmdW5jdGlvbihlLHQpeyJvYmplY3QiPT10eXBlb2YgZXhwb3J0cyYmInVuZGVmaW5lZCIhPXR5cGVvZiBtb2R1bGU/bW9kdWxlLmV4cG9ydHM9dCgpOiJmdW5jdGlvbiI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKCJSYW5nZVRvdWNoIix0KTplLlJhbmdlVG91Y2g9dCgpfSh0aGlzLGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2Z1bmN0aW9uIGUoZSx0KXtmb3IodmFyIG49MDtuPHQubGVuZ3RoO24rKyl7dmFyIHI9dFtuXTtyLmVudW1lcmFibGU9ci5lbnVtZXJhYmxlfHwhMSxyLmNvbmZpZ3VyYWJsZT0hMCwidmFsdWUiaW4gciYmKHIud3JpdGFibGU9ITApLE9iamVjdC5kZWZpbmVQcm9wZXJ0eShlLHIua2V5LHIpfX12YXIgdD17YWRkQ1NTOiEwLHRodW1iV2lkdGg6MTUsd2F0Y2g6ITB9O3ZhciBuPWZ1bmN0aW9uKGUpe3JldHVybiBudWxsIT1lP2UuY29uc3RydWN0b3I6bnVsbH0scj1mdW5jdGlvbihlLHQpe3JldHVybiEhKGUmJnQmJmUgaW5zdGFuY2VvZiB0KX0sdT1mdW5jdGlvbihlKXtyZXR1cm4gbnVsbD09ZX0saT1mdW5jdGlvbihlKXtyZXR1cm4gbihlKT09PU9iamVjdH0sbz1mdW5jdGlvbihlKXtyZXR1cm4gbihlKT09PVN0cmluZ30sYT1mdW5jdGlvbihlKXtyZXR1cm4gQXJyYXkuaXNBcnJheShlKX0sYz1mdW5jdGlvbihlKXtyZXR1cm4gcihlLE5vZGVMaXN0KX0sbD17bnVsbE9yVW5kZWZpbmVkOnUsb2JqZWN0OmksbnVtYmVyOmZ1bmN0aW9uKGUpe3JldHVybiBuKGUpPT09TnVtYmVyJiYhTnVtYmVyLmlzTmFOKGUpfSxzdHJpbmc6byxib29sZWFuOmZ1bmN0aW9uKGUpe3JldHVybiBuKGUpPT09Qm9vbGVhbn0sZnVuY3Rpb246ZnVuY3Rpb24oZSl7cmV0dXJuIG4oZSk9PT1GdW5jdGlvbn0sYXJyYXk6YSxub2RlTGlzdDpjLGVsZW1lbnQ6ZnVuY3Rpb24oZSl7cmV0dXJuIHIoZSxFbGVtZW50KX0sZXZlbnQ6ZnVuY3Rpb24oZSl7cmV0dXJuIHIoZSxFdmVudCl9LGVtcHR5OmZ1bmN0aW9uKGUpe3JldHVybiB1KGUpfHwobyhlKXx8YShlKXx8YyhlKSkmJiFlLmxlbmd0aHx8aShlKSYmIU9iamVjdC5rZXlzKGUpLmxlbmd0aH19O2Z1bmN0aW9uIHMoZSx0KXtpZigxPnQpe3ZhciBuPWZ1bmN0aW9uKGUpe3ZhciB0PSIiLmNvbmNhdChlKS5tYXRjaCgvKD86XC4oXGQrKSk/KD86W2VFXShbKy1dP1xkKykpPyQvKTtyZXR1cm4gdD9NYXRoLm1heCgwLCh0WzFdP3RbMV0ubGVuZ3RoOjApLSh0WzJdPyt0WzJdOjApKTowfSh0KTtyZXR1cm4gcGFyc2VGbG9hdChlLnRvRml4ZWQobikpfXJldHVybiBNYXRoLnJvdW5kKGUvdCkqdH1yZXR1cm4gZnVuY3Rpb24oKXtmdW5jdGlvbiBuKGUscil7KGZ1bmN0aW9uKGUsdCl7aWYoIShlIGluc3RhbmNlb2YgdCkpdGhyb3cgbmV3IFR5cGVFcnJvcigiQ2Fubm90IGNhbGwgYSBjbGFzcyBhcyBhIGZ1bmN0aW9uIil9KSh0aGlzLG4pLGwuZWxlbWVudChlKT90aGlzLmVsZW1lbnQ9ZTpsLnN0cmluZyhlKSYmKHRoaXMuZWxlbWVudD1kb2N1bWVudC5xdWVyeVNlbGVjdG9yKGUpKSxsLmVsZW1lbnQodGhpcy5lbGVtZW50KSYmbC5lbXB0eSh0aGlzLmVsZW1lbnQucmFuZ2VUb3VjaCkmJih0aGlzLmNvbmZpZz1PYmplY3QuYXNzaWduKHt9LHQsciksdGhpcy5pbml0KCkpfXJldHVybiByPW4saT1be2tleToic2V0dXAiLHZhbHVlOmZ1bmN0aW9uKGUpe3ZhciByPTE8YXJndW1lbnRzLmxlbmd0aCYmdm9pZCAwIT09YXJndW1lbnRzWzFdP2FyZ3VtZW50c1sxXTp7fSx1PW51bGw7aWYobC5lbXB0eShlKXx8bC5zdHJpbmcoZSk/dT1BcnJheS5mcm9tKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwobC5zdHJpbmcoZSk/ZTonaW5wdXRbdHlwZT0icmFuZ2UiXScpKTpsLmVsZW1lbnQoZSk/dT1bZV06bC5ub2RlTGlzdChlKT91PUFycmF5LmZyb20oZSk6bC5hcnJheShlKSYmKHU9ZS5maWx0ZXIobC5lbGVtZW50KSksbC5lbXB0eSh1KSlyZXR1cm4gbnVsbDt2YXIgaT1PYmplY3QuYXNzaWduKHt9LHQscik7bC5zdHJpbmcoZSkmJmkud2F0Y2gmJm5ldyBNdXRhdGlvbk9ic2VydmVyKGZ1bmN0aW9uKHQpe0FycmF5LmZyb20odCkuZm9yRWFjaChmdW5jdGlvbih0KXtBcnJheS5mcm9tKHQuYWRkZWROb2RlcykuZm9yRWFjaChmdW5jdGlvbih0KXtsLmVsZW1lbnQodCkmJmZ1bmN0aW9uKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIEFycmF5LmZyb20oZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCh0KSkuaW5jbHVkZXModGhpcyl9LmNhbGwoZSx0KX0odCxlKSYmbmV3IG4odCxpKX0pfSl9KS5vYnNlcnZlKGRvY3VtZW50LmJvZHkse2NoaWxkTGlzdDohMCxzdWJ0cmVlOiEwfSk7cmV0dXJuIHUubWFwKGZ1bmN0aW9uKGUpe3JldHVybiBuZXcgbihlLHIpfSl9fSx7a2V5OiJlbmFibGVkIixnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4ib250b3VjaHN0YXJ0ImluIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudH19XSwodT1be2tleToiaW5pdCIsdmFsdWU6ZnVuY3Rpb24oKXtuLmVuYWJsZWQmJih0aGlzLmNvbmZpZy5hZGRDU1MmJih0aGlzLmVsZW1lbnQuc3R5bGUudXNlclNlbGVjdD0ibm9uZSIsdGhpcy5lbGVtZW50LnN0eWxlLndlYktpdFVzZXJTZWxlY3Q9Im5vbmUiLHRoaXMuZWxlbWVudC5zdHlsZS50b3VjaEFjdGlvbj0ibWFuaXB1bGF0aW9uIiksdGhpcy5saXN0ZW5lcnMoITApLHRoaXMuZWxlbWVudC5yYW5nZVRvdWNoPXRoaXMpfX0se2tleToiZGVzdHJveSIsdmFsdWU6ZnVuY3Rpb24oKXtuLmVuYWJsZWQmJih0aGlzLmxpc3RlbmVycyghMSksdGhpcy5lbGVtZW50LnJhbmdlVG91Y2g9bnVsbCl9fSx7a2V5OiJsaXN0ZW5lcnMiLHZhbHVlOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMsbj1lPyJhZGRFdmVudExpc3RlbmVyIjoicmVtb3ZlRXZlbnRMaXN0ZW5lciI7WyJ0b3VjaHN0YXJ0IiwidG91Y2htb3ZlIiwidG91Y2hlbmQiXS5mb3JFYWNoKGZ1bmN0aW9uKGUpe3QuZWxlbWVudFtuXShlLGZ1bmN0aW9uKGUpe3JldHVybiB0LnNldChlKX0sITEpfSl9fSx7a2V5OiJnZXQiLHZhbHVlOmZ1bmN0aW9uKGUpe2lmKCFuLmVuYWJsZWR8fCFsLmV2ZW50KGUpKXJldHVybiBudWxsO3ZhciB0LHI9ZS50YXJnZXQsdT1lLmNoYW5nZWRUb3VjaGVzWzBdLGk9cGFyc2VGbG9hdChyLmdldEF0dHJpYnV0ZSgibWluIikpfHwwLG89cGFyc2VGbG9hdChyLmdldEF0dHJpYnV0ZSgibWF4IikpfHwxMDAsYT1wYXJzZUZsb2F0KHIuZ2V0QXR0cmlidXRlKCJzdGVwIikpfHwxLGM9ci5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxmPTEwMC9jLndpZHRoKih0aGlzLmNvbmZpZy50aHVtYldpZHRoLzIpLzEwMDtyZXR1cm4gMD4odD0xMDAvYy53aWR0aCoodS5jbGllbnRYLWMubGVmdCkpP3Q9MDoxMDA8dCYmKHQ9MTAwKSw1MD50P3QtPSgxMDAtMip0KSpmOjUwPHQmJih0Kz0yKih0LTUwKSpmKSxpK3ModC8xMDAqKG8taSksYSl9fSx7a2V5OiJzZXQiLHZhbHVlOmZ1bmN0aW9uKGUpe24uZW5hYmxlZCYmbC5ldmVudChlKSYmIWUudGFyZ2V0LmRpc2FibGVkJiYoZS5wcmV2ZW50RGVmYXVsdCgpLGUudGFyZ2V0LnZhbHVlPXRoaXMuZ2V0KGUpLGZ1bmN0aW9uKGUsdCl7aWYoZSYmdCl7dmFyIG49bmV3IEV2ZW50KHQpO2UuZGlzcGF0Y2hFdmVudChuKX19KGUudGFyZ2V0LCJ0b3VjaGVuZCI9PT1lLnR5cGU/ImNoYW5nZSI6ImlucHV0IikpfX1dKSYmZShyLnByb3RvdHlwZSx1KSxpJiZlKHIsaSksbjt2YXIgcix1LGl9KCl9KTs8L3NjcmlwdD48L2hlYWQ+PGJvZHk+PGgyPiVTZXJ2ZXJOYW1lJTwvaDI+PGxhYmVsPjxpbnB1dCB0eXBlPSJjaGVja2JveCIgaWQ9Im15TXV0ZSIgb25jaGFuZ2U9InNldEluZm8oKSIgPiVNdXRlJTwvbGFiZWw+PGJyPjxicj48aW5wdXQgdHlwZT0icmFuZ2UiIG1pbj0iMCIgbWF4PSIxMDAiIHZhbHVlPSI1MCIgY2xhc3M9InNsaWRlciIgaWQ9Im15UmFuZ2UiIG9uY2hhbmdlPSJzZXRJbmZvKCkiPjxicj48bGFiZWwgY2xhc3M9InR4dFZvbCIgaWQ9Im15VGV4dCIgPjwvbGFiZWw+PGJyPjxicj48YnV0dG9uIG9uY2xpY2s9ImdldEluZm8oKSIgPiVSZWZyZXNoJTwvYnV0dG9uPiAgJm5ic3A7PGJ1dHRvbiBvbmNsaWNrPSJsb2NrU2NyZWVuKCkiID4lTG9ja1NjcmVlbiU8L2J1dHRvbj48YnI+PGJyPjxoMj5mb29iYXIgJVBsYXllciU8L2gyPjxidXR0b24gb25jbGljaz0iZm9vYmFyU3RhcnQoKSIgPiVTdGFydCU8L2J1dHRvbj4gICZuYnNwOzxidXR0b24gb25jbGljaz0iZm9vYmFyU3RvcCgpIiA+JVN0b3AlPC9idXR0b24+ICAmbmJzcDs8YnV0dG9uIG9uY2xpY2s9ImZvb2JhclRvZ2dsZSgpIiA+JVRvZ2dsZSU8L2J1dHRvbj4mbmJzcDs8YnV0dG9uIG9uY2xpY2s9ImZvb2JhclByZXZpb3VzKCkiID4lUHJldmlvdXMlPC9idXR0b24+ICAmbmJzcDs8YnV0dG9uIG9uY2xpY2s9ImZvb2Jhck5leHQoKSIgPiVOZXh0JTwvYnV0dG9uPjwvYm9keT48L2h0bWw+Cg=="
129 | ))
130 | .Replace("%ServerName%", DefaultServerName)
131 | .Replace("%Mute%", StrMute)
132 | .Replace("%Refresh%", StrRefresh)
133 | .Replace("%LockScreen%", StrLockScreen)
134 | .Replace("%Vol%", StrVolume)
135 | .Replace("%Start%", StrStart)
136 | .Replace("%Stop%", StrStop)
137 | .Replace("%Previous%", StrPrevious)
138 | .Replace("%Next%", StrNext)
139 | .Replace("%Toggle%", StrToggle)
140 | .Replace("%Player%", StrPlayer);
141 | }
142 |
143 | public static bool AutoRunApp
144 | {
145 | get => _autoRunApp;
146 | set
147 | {
148 | if (value == _autoRunApp) return;
149 | _autoRunApp = value;
150 | SetShortcut(value);
151 | }
152 | }
153 |
154 |
155 |
156 |
157 | public static bool AutoRunServer
158 | {
159 | get => _autoRunServer;
160 | set
161 | {
162 | if (value == _autoRunServer) return;
163 | _autoRunServer = value;
164 | WriteConfig();
165 | }
166 | }
167 |
168 | public static int FoobarPort
169 | {
170 | get => (int)_port;
171 | set
172 | {
173 | if (value == _port) return;
174 | _port = (ushort)value;
175 | BeefwebClient.Port = value;
176 | WriteConfig();
177 | }
178 | }
179 |
180 |
181 |
182 | public static void RunApp(string mutexStr) where T : Form, new()
183 | {
184 |
185 | using (var mutex = new Mutex(true, mutexStr,out var createdNew))
186 | {
187 | if (createdNew)
188 | {
189 | Application.EnableVisualStyles();
190 | Application.SetCompatibleTextRenderingDefault(false);
191 |
192 | try
193 | {
194 | Application.Run(new T());
195 | }
196 | #pragma warning disable CA1031 // Do not catch general exception types
197 | catch (Exception e)
198 | {
199 | using (var sw = new StreamWriter(_errorLogPath, true))
200 | {
201 | sw.WriteLine("=======error==============");
202 | sw.WriteLine(e.Source.ToString());
203 | sw.WriteLine(e.TargetSite);
204 | sw.WriteLine(e.Message);
205 | sw.WriteLine(e.StackTrace);
206 | }
207 | }
208 | #pragma warning restore CA1031 // Do not catch general exception types
209 | }
210 | }
211 | }
212 |
213 |
214 |
215 | private static void WriteConfig()
216 | {
217 | using (var fs = new FileStream(_savefilePath, FileMode.Create, FileAccess.ReadWrite))
218 | {
219 | var bt = BitConverter.GetBytes(_port);
220 | var buf = new byte[] {
221 | _autoRunServer ? (byte)1 : (byte)0,
222 | bt[0],bt[1]};
223 | fs.Write(buf, 0, 3);
224 | }
225 | }
226 |
227 |
228 | public static string GetLocalIP()
229 | {
230 | try
231 | {
232 | IPHostEntry IpEntry = Dns.GetHostEntry(Dns.GetHostName());
233 | foreach (IPAddress item in IpEntry.AddressList)
234 | {
235 | if (item.AddressFamily == AddressFamily.InterNetwork)
236 | {
237 | return item.ToString();
238 | }
239 | }
240 | return "";
241 | }
242 | #pragma warning disable CA1031 // Do not catch general exception types
243 | catch { return ""; }
244 | #pragma warning restore CA1031 // Do not catch general exception types
245 | }
246 |
247 |
248 |
249 |
250 | private static readonly string _myAutoRunPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup) + @"\10275915-be49-4291-86a1-c2622c7146fe.lnk";
251 |
252 | private static void SetShortcut(bool vaule)
253 | {
254 | if (File.Exists(_myAutoRunPath)) File.Delete(_myAutoRunPath);
255 |
256 | if (!vaule) return;
257 | var link = (IShellLinkW)new ShellLink();
258 | try
259 | {
260 | link.SetDescription("RemoteVolumeController");
261 | link.SetPath(Application.ExecutablePath);
262 | IPersistFile file = (IPersistFile)link;
263 | file.Save(_myAutoRunPath, false);
264 | }
265 | finally
266 | {
267 |
268 | Marshal.ReleaseComObject(link);
269 | }
270 | }
271 |
272 | [ComImport]
273 | [Guid("00021401-0000-0000-C000-000000000046")]
274 | private class ShellLink
275 | {
276 | }
277 |
278 |
279 |
280 | [ComImport]
281 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
282 | [Guid("000214F9-0000-0000-C000-000000000046")]
283 | private interface IShellLinkW
284 | {
285 | int Reserve1();
286 | int Reserve2();
287 | int Reserve3();
288 | int Reserve4();
289 |
290 | [PreserveSig]
291 | int SetDescription([MarshalAs(UnmanagedType.LPWStr), In] string pszName);
292 |
293 | int Reserve5();
294 | int Reserve6();
295 | int Reserve7();
296 | int Reserve8();
297 | int Reserve9();
298 | int Reserve10();
299 | int Reserve11();
300 | int Reserve12();
301 | int Reserve13();
302 | int Reserve14();
303 | int Reserve15();
304 | int Reserve16();
305 |
306 | [PreserveSig]
307 | int SetPath([MarshalAs(UnmanagedType.LPWStr), In] string pszFile);
308 | }
309 |
310 | }
311 | }
312 |
--------------------------------------------------------------------------------
/RemoteVolumeController/speaker(1).ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/differentrain/Remote-Volume-Controller/b949c497fc2143c74e5726a97eef460cfc2d27b6/RemoteVolumeController/speaker(1).ico
--------------------------------------------------------------------------------
/pics/App.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/differentrain/Remote-Volume-Controller/b949c497fc2143c74e5726a97eef460cfc2d27b6/pics/App.png
--------------------------------------------------------------------------------
/pics/Enable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/differentrain/Remote-Volume-Controller/b949c497fc2143c74e5726a97eef460cfc2d27b6/pics/Enable.png
--------------------------------------------------------------------------------
/pics/EnablePort.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/differentrain/Remote-Volume-Controller/b949c497fc2143c74e5726a97eef460cfc2d27b6/pics/EnablePort.png
--------------------------------------------------------------------------------
/pics/Tray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/differentrain/Remote-Volume-Controller/b949c497fc2143c74e5726a97eef460cfc2d27b6/pics/Tray.png
--------------------------------------------------------------------------------
/pics/ViewInBrowser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/differentrain/Remote-Volume-Controller/b949c497fc2143c74e5726a97eef460cfc2d27b6/pics/ViewInBrowser.png
--------------------------------------------------------------------------------