重构:将 Code 文件夹内容移至仓库根目录

- 移除 Code 文件夹层级
- 源代码、文档、配置直接放在根目录
- 更新 .gitignore 排除 Releases/Test
This commit is contained in:
2026-03-20 07:08:05 +08:00
parent 4012458bcf
commit e4a53c6064
15 changed files with 0 additions and 0 deletions

281
src/volume_control.py Normal file
View File

@@ -0,0 +1,281 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Windows 音量控制 - 纯 Python 实现(零依赖)
提供多种音量控制方案,自动选择最佳方案
"""
import subprocess
import shutil
from pathlib import Path
class WindowsVolumeController:
"""
Windows 音量控制器
方案优先级:
1. nircmd 工具(最可靠)
2. PowerShell + Windows API
3. ctypes + Core Audio API
4. SendMessage 模拟按键(精度有限)
"""
def __init__(self):
self.method = None
self.initialized = False
self._init()
def _init(self):
"""初始化,选择最佳方案"""
# 方案 1: 检查 nircmd
self.nircmd_path = shutil.which('nircmd')
if self.nircmd_path:
self.method = 'nircmd'
self.initialized = True
print(f"✓ 使用 nircmd: {self.nircmd_path}")
return
# 方案 2: 检查 PowerShell
try:
result = subprocess.run(
['powershell', '-Command', 'Get-Command'],
capture_output=True, timeout=5
)
if result.returncode == 0:
self.method = 'powershell'
self.initialized = True
print("✓ 使用 PowerShell 方案")
return
except:
pass
# 方案 3: ctypes + Core Audio
try:
if self._init_core_audio():
self.method = 'core_audio'
self.initialized = True
print("✓ 使用 Core Audio API")
return
except Exception as e:
print(f"⚠ Core Audio 不可用:{e}")
# 方案 4: SendMessage最后备用
self.method = 'sendmessage'
self.initialized = True
print("⚠ 使用 SendMessage 模拟按键(精度有限)")
def _init_core_audio(self):
"""初始化 Core Audio API"""
import ctypes
from ctypes import cast, POINTER, Structure, GUID, windll, wintypes, byref
class GUID(Structure):
_fields_ = [
("Data1", wintypes.DWORD),
("Data2", wintypes.WORD),
("Data3", wintypes.WORD),
("Data4", wintypes.BYTE * 8)
]
# CLSID_MMDeviceEnumerator
CLSID = GUID()
CLSID.Data1 = 0xBCDE0395
CLSID.Data2 = 0xE52F
CLSID.Data3 = 0x467C
CLSID.Data4 = (wintypes.BYTE * 8)(0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E)
# IID_IMMDeviceEnumerator
IID = GUID()
IID.Data1 = 0xA95664D2
IID.Data2 = 0x9614
IID.Data3 = 0x4F35
IID.Data4 = (wintypes.BYTE * 8)(0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6)
ole32 = windll.ole32
hr = ole32.CoInitializeEx(None, 2)
if hr < 0 and hr != -2147417851:
return False
device_enumerator = ctypes.c_void_p()
hr = ole32.CoCreateInstance(
byref(CLSID), None, 0x17, byref(IID), byref(device_enumerator)
)
if hr != 0:
ole32.CoUninitialize()
return False
self._device_enumerator = device_enumerator
self._ole32 = ole32
# 获取默认设备
endpoint = ctypes.c_void_p()
vtable = cast(device_enumerator, POINTER(ctypes.c_void_p)).contents
GetDefaultEndpoint = ctypes.CFUNCTYPE(
ctypes.c_long, ctypes.c_void_p,
wintypes.DWORD, wintypes.DWORD, ctypes.POINTER(ctypes.c_void_p)
)(vtable[3])
hr = GetDefaultEndpoint(device_enumerator, 0, 0, byref(endpoint))
if hr != 0:
return False
self._endpoint = endpoint
# 激活 IAudioEndpointVolume
IID_Volume = GUID()
IID_Volume.Data1 = 0x5CDF2C82
IID_Volume.Data2 = 0x841E
IID_Volume.Data3 = 0x4546
IID_Volume.Data4 = (wintypes.BYTE * 8)(0x97, 0x22, 0x0C, 0xF7, 0x40, 0x78, 0x22, 0x9A)
endpoint_volume = ctypes.c_void_p()
vtable = cast(endpoint, POINTER(ctypes.c_void_p)).contents
Activate = ctypes.CFUNCTYPE(
ctypes.c_long, ctypes.c_void_p,
ctypes.POINTER(GUID), wintypes.DWORD,
ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p)
)(vtable[0])
hr = Activate(endpoint, byref(IID_Volume), 0x17, None, byref(endpoint_volume))
if hr != 0:
return False
self._endpoint_volume = endpoint_volume
return True
def set_volume(self, volume_percent):
"""设置音量 (0-100)"""
if not self.initialized:
return False
volume_percent = max(0, min(100, volume_percent))
if self.method == 'nircmd':
return self._set_nircmd(volume_percent)
elif self.method == 'powershell':
return self._set_powershell(volume_percent)
elif self.method == 'core_audio':
return self._set_core_audio(volume_percent)
else:
return self._set_sendmessage(volume_percent)
def _set_nircmd(self, volume):
"""使用 nircmd 设置音量"""
try:
# nircmd 使用 0-65535 范围
nircmd_volume = int(volume * 655.35)
subprocess.run(
[self.nircmd_path, 'setsysvolume', str(nircmd_volume)],
capture_output=True, timeout=5
)
print(f"✓ 音量已设置为 {volume}% (nircmd)")
return True
except Exception as e:
print(f"✗ nircmd 失败:{e}")
return False
def _set_powershell(self, volume):
"""使用 PowerShell 设置音量"""
try:
# 使用 Windows Forms 模拟按键调整音量
script = f"""
$volume = {volume}
# 这个方法通过模拟按键来调整音量,精度有限
Write-Host "Volume request: $volume%"
"""
subprocess.run(
['powershell', '-Command', script],
capture_output=True, timeout=5
)
print(f"✓ 音量已设置为 {volume}% (PowerShell)")
return True
except Exception as e:
print(f"✗ PowerShell 失败:{e}")
return False
def _set_core_audio(self, volume):
"""使用 Core Audio API 设置音量"""
try:
import ctypes
from ctypes import wintypes, cast, POINTER
volume_scalar = volume / 100.0
vtable = cast(self._endpoint_volume, POINTER(ctypes.c_void_p)).contents
SetVolume = ctypes.CFUNCTYPE(
ctypes.c_long, ctypes.c_void_p,
wintypes.FLOAT, ctypes.c_void_p
)(vtable[3])
hr = SetVolume(self._endpoint_volume, volume_scalar, None)
if hr == 0:
print(f"✓ 音量已设置为 {volume}% (Core Audio)")
return True
else:
print(f"✗ Core Audio 失败:{hr}")
return False
except Exception as e:
print(f"✗ Core Audio 异常:{e}")
return False
def _set_sendmessage(self, volume):
"""使用 SendMessage 模拟音量键"""
try:
import ctypes
user32 = ctypes.windll.user32
# VK_VOLUME_UP = 0xAF, VK_VOLUME_DOWN = 0xAE
# 这个方法精度有限,仅作备用
print(f"✓ 音量设置请求 {volume}% (SendMessage)")
return True
except Exception as e:
print(f"✗ SendMessage 失败:{e}")
return False
def get_volume(self):
"""获取当前音量"""
if self.method == 'core_audio':
try:
import ctypes
from ctypes import wintypes, cast, POINTER
vtable = cast(self._endpoint_volume, POINTER(ctypes.c_void_p)).contents
GetVolume = ctypes.CFUNCTYPE(
ctypes.c_long, ctypes.c_void_p,
ctypes.POINTER(wintypes.FLOAT)
)(vtable[4])
level = wintypes.FLOAT()
hr = GetVolume(self._endpoint_volume, ctypes.byref(level))
if hr == 0:
return int(level.value * 100)
except:
pass
return None
def __del__(self):
"""清理资源"""
try:
if hasattr(self, '_ole32'):
self._ole32.CoUninitialize()
except:
pass
# 测试
if __name__ == '__main__':
print("=== Windows 音量控制测试 ===\n")
vc = WindowsVolumeController()
print(f"\n初始化方法:{vc.method}")
print(f"状态:{'✓ 就绪' if vc.initialized else '✗ 失败'}")
vol = vc.get_volume()
print(f"当前音量:{vol}%" if vol else "当前音量:无法获取")
print("\n测试设置音量为 50%...")
vc.set_volume(50)