├── 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 | ![](https://github.com/differentrain/Remote-Volume-Controller/raw/master/pics/App.png) 16 | 17 | ### 2. Active main window 18 | 19 | ![](https://github.com/differentrain/Remote-Volume-Controller/raw/master/pics/Tray.png) 20 | 21 | ### 3. Enable server 22 | 23 | ![](https://github.com/differentrain/Remote-Volume-Controller/raw/master/pics/Enable.png) 24 | 25 | ### 4. View in browser 26 | 27 | ![](https://github.com/differentrain/Remote-Volume-Controller/raw/master/pics/ViewInBrowser.png) 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 | ![](https://github.com/differentrain/Remote-Volume-Controller/raw/master/pics/EnablePort.png) 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 --------------------------------------------------------------------------------