Skip to content

Commit

Permalink
Merge branch 'release/v20240210'
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreMiras committed Feb 21, 2024
2 parents 8aa35fb + ced4f3f commit 6883b1b
Show file tree
Hide file tree
Showing 20 changed files with 209 additions and 31 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/pypi-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ on: [push]

jobs:
pypi:
runs-on: windows-2019
runs-on: windows-2022
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- name: Install dependencies
run: python -m pip install --upgrade setuptools wheel twine
- name: Build
Expand Down
42 changes: 29 additions & 13 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,51 @@ on: [push, pull_request]

jobs:
linter:
runs-on: windows-2019
runs-on: windows-2022
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- run: pip install tox
- run: tox -e lint-check
test:
runs-on: windows-2019
runs-on: windows-2022
strategy:
matrix:
python: ['3.7', '3.8', '3.9', '3.10']
python: ['3.8', '3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v2
# Virtual network sound card for Microsoft Windows
- name: Install Scream
- uses: actions/checkout@v4
- name: Start audio server
run: net start audiosrv
- name: Disable time sync with Hyper-V & setting system date time
# TODO: Remove the time workaround when virtual audio device certificate is valid again, refs:
# https://github.com/duncanthrax/scream/issues/202
run: |
Set-Service -Name vmictimesync -Status stopped -StartupType disabled
Set-ItemProperty HKLM:\SYSTEM\CurrentControlSet\services\W32Time\Parameters -Name 'Type' -Value 'NoSync'
net stop w32time; Set-Date (Get-Date "2023-07-04 12:00:00")
- name: Install virtual audio device (Scream)
timeout-minutes: 3
env:
VERSION: '4.0'
shell: powershell
run: |
Invoke-WebRequest https://github.com/duncanthrax/scream/releases/download/3.8/Scream3.8.zip -OutFile Scream3.8.zip
Expand-Archive -Path Scream3.8.zip -DestinationPath Scream
Import-Certificate -FilePath Scream\Install\driver\x64\Scream.cat -CertStoreLocation Cert:\LocalMachine\TrustedPublisher
Invoke-WebRequest https://github.com/duncanthrax/scream/releases/download/${{ env.VERSION }}/Scream${{ env.VERSION }}.zip -OutFile Scream${{ env.VERSION }}.zip
Expand-Archive -Path Scream${{ env.VERSION }}.zip -DestinationPath Scream
Import-Certificate -FilePath Scream\Install\driver\x64\scream.cat -CertStoreLocation Cert:\LocalMachine\TrustedPublisher
Scream\Install\helpers\devcon-x64.exe install Scream\Install\driver\x64\Scream.inf *Scream
- uses: actions/setup-python@v2
- name: Resetting system date time
run: |
Set-Service -Name vmictimesync -Status running -StartupType automatic
Set-ItemProperty HKLM:\SYSTEM\CurrentControlSet\services\W32Time\Parameters -Name 'Type' -Value 'NTP'
net start w32time; w32tm /resync /force; $currentDate = Get-Date; Write-Host "Current System Date: $currentDate";
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- run: pip install tox
- run: tox -e py
- name: Run Coverage
if: matrix.python == '3.10'
if: matrix.python == '3.12'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: tox -e coveralls
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Change Log

## [20240210]
- fix double free due to using cast rather than QueryInterface, refs #74 (@mrob95)
- add: example for IMMNotificationClient, refs #77 (@Invisi)
- IChannelAudioVolume support, refs #87 (@mltony)
- Format fix (@AndreMiras), refs #89
- GitHub Actions versions bumps (@AndreMiras), refs #88
- Bump Python versions (@AndreMiras), refs #90
- Fix CI virtual audio install (@AndreMiras), refs #91

## [20230407]
- Fixes memory leak from PROPVARIANT, refs #72 (@KillerBOSS2019)

Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,12 @@ choco install visualcpp-build-tools
## Usage

```Python
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(
IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
volume = cast(interface, POINTER(IAudioEndpointVolume))
volume = interface.QueryInterface(IAudioEndpointVolume)
volume.GetMute()
volume.GetMasterVolumeLevel()
volume.GetVolumeRange()
Expand Down
2 changes: 1 addition & 1 deletion docs/Release.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This is documenting the release process.

## Git flow & CHANGELOG.md

Make sure the CHANGELOG.md is up to date and follows the http://keepachangelog.com guidelines.
Make sure the CHANGELOG.md is up to date and follows the https://keepachangelog.com guidelines.
Start the release with git flow:
```batch
git flow release start vYYYYMMDD
Expand Down
1 change: 1 addition & 0 deletions examples/audio_controller_class_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Per session GetMute() SetMute() GetMasterVolume() SetMasterVolume() using
SimpleAudioVolume.
"""

from pycaw.pycaw import AudioUtilities


Expand Down
3 changes: 1 addition & 2 deletions examples/audio_endpoint_volume_example.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""
Get and set access to master volume example.
"""
from ctypes import POINTER, cast

from comtypes import CLSCTX_ALL

Expand All @@ -11,7 +10,7 @@
def main():
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
volume = cast(interface, POINTER(IAudioEndpointVolume))
volume = interface.QueryInterface(IAudioEndpointVolume)
print("volume.GetMute(): %s" % volume.GetMute())
print("volume.GetMasterVolumeLevel(): %s" % volume.GetMasterVolumeLevel())
print("volume.GetVolumeRange(): (%s, %s, %s)" % volume.GetVolumeRange())
Expand Down
22 changes: 22 additions & 0 deletions examples/channel_audio_volume_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
Adjusting volume of left channel using IChannelAudioVolume.
"""

from pycaw.pycaw import AudioUtilities


def main():
sessions = AudioUtilities.GetAllSessions()
for session in sessions:
volume = session.channelAudioVolume()
print(f"Session {session}")
count = volume.GetChannelCount()
volumes = [volume.GetChannelVolume(i) for i in range(count)]
print(f" volumes = {volumes}")
if count == 2:
volume.SetChannelVolume(0, 0.1, None)
print(" Set the volume of left channel to 0.5!")


if __name__ == "__main__":
main()
98 changes: 98 additions & 0 deletions examples/notification_client_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""
This file contains example usage of MMNotificationClient, the following
callbacks are implemented:
:: Gets called when the state of an audio endpoint device has changed
IMMNotificationClient.OnDeviceStateChanged()
-> on_device_state_changed()
:: Gets called when the value of an audio endpoint property has changed
IMMNotificationClient.OnPropertyValueChanged()
-> on_property_value_changed()
https://learn.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nn-mmdeviceapi-immnotificationclient
"""

import time

from comtypes import GUID, COMError
from comtypes.automation import VT_BLOB
from comtypes.persist import STGM_READ

from pycaw.callbacks import MMNotificationClient
from pycaw.utils import AudioUtilities

known_keys = {
# Recording
"{24DBB0FC-9311-4B3D-9CF0-18FF155639D4} 0": "Playback through this device",
"{24DBB0FC-9311-4B3D-9CF0-18FF155639D4} 1": "Listen to this device",
# Playback & Recording
"{9855C4CD-DF8C-449C-A181-8191B68BD06C} 0": "Volume",
"{9855C4CD-DF8C-449C-A181-8191B68BD06C} 1": "Device muted",
}


class Client(MMNotificationClient):
def __init__(self):
self.enumerator = AudioUtilities.GetDeviceEnumerator()

def on_device_state_changed(self, device_id, new_state, new_state_id):
print(f"on_device_state_changed: {device_id} {new_state} {new_state_id}")

def on_property_value_changed(self, device_id, property_struct, fmtid, pid):
key = f"{fmtid} {pid}"

value = self._find_property(device_id, fmtid, pid)
print(
f"on_property_value_changed: key={key} "
f"purpose=\"{known_keys.get(key, '?')}\" value={value}",
)

def _find_property(self, device_id: str, fmtid: GUID, pid: int) -> str | None:
"""Helper function to find the value of a property"""
dev = self.enumerator.GetDevice(device_id)
store = dev.OpenPropertyStore(STGM_READ)
if store is None:
print("no store")
return

search_value = bytes(fmtid)
for j in range(store.GetCount()):
try:
pk = store.GetAt(j)

if not (bytes(pk.fmtid) == search_value and pk.pid == pid):
continue

value = store.GetValue(pk)
if value.vt == VT_BLOB:
return bytes(value).hex(" ")

return value.GetValue()
except COMError as exc:
print(
f"COMError attempting to get property {j} "
f"from device {dev}: {exc}"
)
continue


def add_callback():
cb = Client()
enumerator = AudioUtilities.GetDeviceEnumerator()
enumerator.RegisterEndpointNotificationCallback(cb)
print("registered")

try:
# wait for callbacks
time.sleep(300)
except KeyboardInterrupt:
pass
finally:
print("unregistering")
enumerator.UnregisterEndpointNotificationCallback(cb)


if __name__ == "__main__":
add_callback()
1 change: 1 addition & 0 deletions examples/session_callback_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
https://docs.microsoft.com/en-us/windows/win32/api/audiopolicy/nn-audiopolicy-iaudiosessionevents
"""

import time

from comtypes import COMError
Expand Down
1 change: 1 addition & 0 deletions examples/simple_audio_volume_example.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Per session GetMute() SetMute() using ISimpleAudioVolume.
"""

from pycaw.pycaw import AudioUtilities, ISimpleAudioVolume


Expand Down
1 change: 1 addition & 0 deletions examples/volume_by_process_example.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Mutes the volume of all processes, but unmutes chrome.exe process.
"""

from pycaw.pycaw import AudioUtilities


Expand Down
3 changes: 1 addition & 2 deletions examples/volume_callback_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
IAudioEndpointVolumeCallback.OnNotify() example.
The OnNotify() callback method gets called on volume change.
"""
from ctypes import POINTER, cast

from comtypes import CLSCTX_ALL, COMObject

Expand All @@ -23,7 +22,7 @@ def OnNotify(self, pNotify):
def main():
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
volume = cast(interface, POINTER(IAudioEndpointVolume))
volume = interface.QueryInterface(IAudioEndpointVolume)
callback = AudioEndpointVolumeCallback()
volume.RegisterControlChangeNotify(callback)
for i in range(3):
Expand Down
24 changes: 24 additions & 0 deletions pycaw/api/audioclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,27 @@ class IAudioClient(IUnknown):
(["out"], POINTER(POINTER(IUnknown)), "ppv"),
),
)


class IChannelAudioVolume(IUnknown):
_iid_ = GUID("{1c158861-b533-4b30-b1cf-e853e51c59b8}")
_methods_ = (
COMMETHOD(
[], HRESULT, "GetChannelCount", (["out"], POINTER(UINT32), "pnChannelCount")
),
COMMETHOD(
[],
HRESULT,
"SetChannelVolume",
(["in"], UINT32, "dwIndex"),
(["in"], c_float, "fLevel"),
(["in"], POINTER(GUID), "EventContext"),
),
COMMETHOD(
[],
HRESULT,
"GetChannelVolume",
(["in"], UINT32, "dwIndex"),
(["out"], POINTER(c_float), "pfLevel"),
),
)
1 change: 1 addition & 0 deletions pycaw/pycaw.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Python wrapper around the Core Audio Windows API.
"""

# import here all newly split up modules,
# to keep backwards compatibility

Expand Down
Loading

0 comments on commit 6883b1b

Please sign in to comment.