Get the full name of a waveIn device - winapi

I have been using waveInGetDevCaps to get the name of waveIn devices, but the WAVEINCAPS structure only supports 31 characters plus a null, meaning that on my computer, the device names I get back are truncated:
Microphone / Line In (SigmaTel
Microphone Array (SigmaTel High,
I am sure that there must be a way of getting the full device name, but does anyone know what that is?

Yes, there's a workaround. I've solved this problem several times in shipping code.
Enumerate audio capture devices with DirectSoundCapture. The API is DirectSoundCaptureEnumerate. It will return you the full length name of the devices.
Of course, you're probably thinking "That's great, but the rest of my code is setup to use the Wave API, not DirectSound. I don't want to switch it all over. So how can I map the GUID IDs returned by DirectSoundCaptureEnumerate to the integer IDs used by the WaveIn API?"
The solution is to CoCreateInstance for the DirectSoundPrivate object (or call GetClassObject directly from dsound.dll) to get a pointer to an IKsPropertySet interface. From this interface, you can obtain the DSound GUID to Wave ID mapping. For more details see this web page:
http://msdn.microsoft.com/en-us/library/bb206182(VS.85).aspx
You want to use the DSPROPERTY_DIRECTSOUNDDEVICE_WAVEDEVICEMAPPING as described on the web page listed above.

Improved/full C# WPF code based on #Andrea Bertucelli answer
using NAudio.CoreAudioApi;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
foreach (KeyValuePair<string, MMDevice> device in GetInputAudioDevices())
{
Console.WriteLine("Name: {0}, State: {1}", device.Key, device.Value.State);
}
}
public Dictionary<string, MMDevice> GetInputAudioDevices()
{
Dictionary<string, MMDevice> retVal = new Dictionary<string, MMDevice>();
MMDeviceEnumerator enumerator = new MMDeviceEnumerator();
int waveInDevices = WaveIn.DeviceCount;
for (int waveInDevice = 0; waveInDevice < waveInDevices; waveInDevice++)
{
WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice);
foreach (MMDevice device in enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.All))
{
if (device.FriendlyName.StartsWith(deviceInfo.ProductName))
{
retVal.Add(device.FriendlyName, device);
break;
}
}
}
return retVal;
}
}
}

I completed the names of waveIn devices, exploring the names returned from MMDeviceEnumerator. For each waveIn device, when the name incompleted is part of full name of one of EnumerateAudioEndPoints, I used this full name for populate combobox in the same order of waveIn devices.
VisualBasic .NET:
Dim wain = New WaveIn()
Dim DeviceInfoI As WaveInCapabilities
Dim nomedevice As String
For de = 0 To wain.DeviceCount - 1
DeviceInfoI = wain.GetCapabilities(de)
nomedevice = DeviceInfoI.ProductName
For deg = 0 To devices.Count - 1
If InStr(devices.Item(deg).FriendlyName, nomedevice) Then
nomedevice = devices.Item(deg).FriendlyName
Exit For
End If
Next
cmbMessaggiVocaliMIC.Items.Add(nomedevice)
Next
cmbMessaggiVocaliMIC.SelectedIndex = 0
waveIn.DeviceNumber = cmbMessaggiVocaliMIC.SelectedIndex

There's a way involving the registry that's simpler than using DirectSound. If you use the WAVEINCAPS2 structure, it has a name GUID that references a key under HKLM\System\CurrentControlSet\Control\MediaCategories. If the key doesn't exist, then just use the name in the structure. This is documented on http://msdn.microsoft.com/en-us/library/windows/hardware/ff536382%28v=vs.85%29.aspx. Here's an example:
public static ICollection<AudioInputDevice> GetDevices()
{
RegistryKey namesKey = Registry.LocalMachine.OpenSubKey(#"System\CurrentControlSet\Control\MediaCategories");
List<AudioInputDevice> devices = new List<AudioInputDevice>();
for(int i=0, deviceCount=waveInGetNumDevs(); i<deviceCount; i++)
{
WAVEINCAPS2 caps;
if(waveInGetDevCaps(new IntPtr(i), out caps, Marshal.SizeOf(typeof(WAVEINCAPS2))) == 0 && caps.Formats != 0)
{
string name = null;
if(namesKey != null)
{
RegistryKey nameKey = namesKey.OpenSubKey(caps.NameGuid.ToString("B"));
if(nameKey != null) name = nameKey.GetValue("Name") as string;
}
devices.Add(new AudioInputDevice(name ?? caps.Name, caps.ProductGuid));
}
}
return devices;
}
struct WAVEINCAPS2
{
public short ManufacturerId, ProductId;
public uint DriverVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] public string Name;
public uint Formats;
public short Channels;
ushort Reserved;
public Guid ManufacturerGuid, ProductGuid, NameGuid;
}
[DllImport("winmm.dll")]
static extern int waveInGetDevCaps(IntPtr deviceId, out WAVEINCAPS2 caps, int capsSize);
[DllImport("winmm.dll", ExactSpelling=true)]
static extern int waveInGetNumDevs();

Looks like DirectSoundPrivate has some issues. I am trying to access it from an empty project and it works fine. However, when I try to access it from COM DLL or from a DLL thread it returns E_NOTIMPL error from IKsPropertySet::Get.
But I figured out another trick. It seems DirectSound enumerates capture and render devices in wave id order (excluding first default).
We still have to interact with old Wave API and it still lacks a proper way to do that. DirectShow provides audio input devices based on WaveIn and I need to get corresponding a WASAPI id and vice-versa.

Using NAudio, i use this code to get full device name...
using NAudio.CoreAudioApi;
using NAudio.Wave;
For getting all recording devices:
//create enumerator
var enumerator = new MMDeviceEnumerator();
//cycle through all audio devices
for (int i = 0; i < WaveIn.DeviceCount; i++)
Console.WriteLine(enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active)[i]);
//clean up
enumerator.Dispose();
For getting all capture devices:
//create enumerator
var enumerator = new MMDeviceEnumerator();
//cyckle trough all audio devices
for (int i = 0; i < WaveOut.DeviceCount; i++)
Console.WriteLine(enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active)[i]);
//clean up
enumerator.Dispose();

I have found another way using the registry to find audio devices' full name, both Input and Output.
Works on Windows 7 and Windows 10.
This method tries Adam M.'s approach in the first place. His method didn't work for me, but just in case it works for you I added as preferred method.
Delphi:
procedure TForm_Config.FormCreate(Sender: TObject);
type
tagWAVEOUTCAPS2A = packed record
wMid: WORD;
wPid: WORD;
vDriverVersion: MMVERSION;
szPname: array[0..MAXPNAMELEN-1] of AnsiChar;
dwFormats: DWORD;
wChannels: WORD;
wReserved1: WORD;
dwSupport: DWORD;
ManufacturerGuid: System.TGUID;
ProductGuid: System.TGUID;
NameGuid: System.TGUID;
end;
var
i,outdevs: Integer;
woCaps: tagWAVEOUTCAPS2A;
RegistryService: TRegistry;
iClasses, iSubClasses, iNames: Integer;
audioDeviceClasses, audioDeviceSubClasses, audioDeviceNames: TStringList;
initialDeviceName, partialDeviceName, fullDeviceName: string;
begin
audioDeviceClasses := TStringList.Create;
audioDeviceSubClasses := TStringList.Create;
audioDeviceNames := TStringList.Create;
try
RegistryService := TRegistry.Create;
try
RegistryService.RootKey := HKEY_LOCAL_MACHINE;
if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\') then begin
RegistryService.GetKeyNames(audioDeviceClasses);
RegistryService.CloseKey();
for iClasses := 0 to audioDeviceClasses.Count - 1 do begin
if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\'+audioDeviceClasses[iClasses]) then begin
RegistryService.GetKeyNames(audioDeviceSubClasses);
RegistryService.CloseKey();
for iSubClasses := 0 to audioDeviceSubClasses.Count - 1 do begin
if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\'+audioDeviceClasses[iClasses]+'\'+audioDeviceSubClasses[iSubClasses]) then begin
if RegistryService.ValueExists('DeviceDesc') then begin
fullDeviceName := Trim(RegistryService.ReadString('DeviceDesc'));
if AnsiPos(';',fullDeviceName) > 0 then begin
fullDeviceName := Trim(AnsiMidStr(fullDeviceName, AnsiPos(';',fullDeviceName)+1, Length(fullDeviceName)));
end;
audioDeviceNames.Add(fullDeviceName);
end;
RegistryService.CloseKey();
end;
end;
end;
end;
end;
finally
FreeAndNil(RegistryService);
end;
// WaveOutDevComboBox is a selection box (combo) placed in the form and will receive the list of output audio devices
WaveOutDevComboBox.Clear;
try
outdevs := waveOutGetNumDevs;
for i := 0 to outdevs - 1 do begin
ZeroMemory(#woCaps, sizeof(woCaps));
if waveOutGetDevCaps(i, #woCaps, sizeof(woCaps)) = MMSYSERR_NOERROR then begin
RegistryService := TRegistry.Create;
try
RegistryService.RootKey := HKEY_LOCAL_MACHINE;
if RegistryService.OpenKeyReadOnly('\System\CurrentControlSet\Control\MediaCategories\' + GUIDToString(woCaps.NameGuid)) then begin
WaveOutDevComboBox.Items.Add(RegistryService.ReadString('Name'));
RegistryService.CloseKey();
end
else begin
initialDeviceName := '';
partialDeviceName := Trim(woCaps.szPname);
if AnsiPos('(',partialDeviceName) > 0 then begin
initialDeviceName := Trim(AnsiLeftStr(partialDeviceName,AnsiPos('(',partialDeviceName)-1));
partialDeviceName := Trim(AnsiMidStr(partialDeviceName,AnsiPos('(',partialDeviceName)+1,Length(partialDeviceName)));
if AnsiPos(')',partialDeviceName) > 0 then begin
partialDeviceName := Trim(AnsiLeftStr(partialDeviceName,AnsiPos(')',partialDeviceName)-1));
end;
end;
for iNames := 0 to audioDeviceNames.Count - 1 do begin
fullDeviceName := audioDeviceNames[iNames];
if AnsiStartsText(partialDeviceName,fullDeviceName) then begin
break;
end
else begin
fullDeviceName := partialDeviceName;
end;
end;
WaveOutDevComboBox.Items.Add(initialDeviceName + IfThen(initialDeviceName<>EmptyStr,' (','') + fullDeviceName + IfThen(initialDeviceName<>EmptyStr,')',''));
end;
finally
FreeAndNil(RegistryService);
end;
end;
end;
except
WaveOutDevComboBox.Enabled := False;
end;
finally
FreeAndNil(audioDeviceClasses);
FreeAndNil(audioDeviceSubClasses);
FreeAndNil(audioDeviceNames);
end;
end;

Related

How to show the content of a directory in File Explorer as thumbnails?

In a Delphi 10.4.2 win-32 VCL Application in Windows 10, I show the content of a directory in Windows File Explorer using this code and passing a path e.g. C:\MyDirectory\:
procedure ShellOpen(const Url: string; const Params: string = '');
begin
Winapi.ShellAPI.ShellExecute(0, 'Open', PChar(Url), PChar(Params), nil, SW_SHOWNORMAL);
end;
This works. But how can I force Explorer to show the files in this directory using THUMBNAILS? Are there any parameters for this that I could use in this procedure?
I have searched a lot for this but did not find anything.
You want to use the IFolderView::SetCurrentViewMode method.
Here is a C++ (using Visual Studio's ATL) example:
int main()
{
CoInitialize(NULL);
{
// get a shell item
CComPtr<IShellItem> folder;
ATLASSERT(SUCCEEDED(SHCreateItemFromParsingName(L"c:\\myPath1\myPath2", nullptr, IID_PPV_ARGS(&folder))));
// get its PIDL
CComHeapPtr<ITEMIDLIST> pidl;
ATLASSERT(SUCCEEDED(CComQIPtr<IPersistIDList>(folder)->GetIDList(&pidl)));
// open the item
SHELLEXECUTEINFO info = { };
info.cbSize = sizeof(info);
info.fMask = SEE_MASK_IDLIST;
info.nShow = SW_SHOW;
info.lpIDList = pidl;
ATLASSERT(ShellExecuteEx(&info));
// build a variant from the PIDL
UINT size = ILGetSize(pidl);
SAFEARRAY* psa = SafeArrayCreateVector(VT_UI1, 0, size);
CopyMemory(psa->pvData, pidl, size);
CComVariant v;
v.parray = psa;
v.vt = VT_ARRAY | VT_UI1;
// find the opened window
CComPtr<IShellWindows> windows;
ATLASSERT(SUCCEEDED(windows.CoCreateInstance(CLSID_ShellWindows)));
CComVariant empty;
long hwnd;
CComPtr<IDispatch> disp;
do
{
windows->FindWindowSW(&v, &empty, SWC_BROWSER, &hwnd, SWFO_NEEDDISPATCH, &disp);
if (disp)
break;
// we sleep for a while but using events would be better
// see https://stackoverflow.com/a/59974072/403671
Sleep(500);
} while (true);
// get IFolderView
CComPtr<IFolderView> view;
ATLASSERT(SUCCEEDED(IUnknown_QueryService(disp, IID_IFolderView, IID_PPV_ARGS(&view))));
// change view mode
ATLASSERT(SUCCEEDED(view->SetCurrentViewMode(FOLDERVIEWMODE::FVM_THUMBNAIL)));
}
CoUninitialize();
return 0;
}
Here's a Delphi version of the approach given by Simon Mourier:
uses
ComObj, ShellAPI, ShlObj, ActiveX, SHDocVw, ShLwApi;
function IUnknown_QueryService(punk: IUnknown; const guidService: TGUID;
const IID: TGUID; out Obj): HRESULT; stdcall; external 'ShLwApi'
name 'IUnknown_QueryService';
type
TFolderViewMode = (fvmAuto, fvmIcon, fvmSmallIcon, fvmList, fvmDetails,
fvmThumbnail, fvmTile, fvmThumbstrip, fvmContent);
procedure OpenFolder(AHandle: HWND; const AFolder: string; AViewMode: TFolderViewMode);
const
FolderViewModes: array[TFolderViewMode] of Cardinal =
(Cardinal(FVM_AUTO), FVM_ICON, FVM_SMALLICON, FVM_LIST, FVM_DETAILS,
FVM_THUMBNAIL, FVM_TILE, FVM_THUMBSTRIP, FVM_CONTENT);
var
ShellItem: IShellItem;
PIDL: PItemIDList;
SEInfo: TShellExecuteInfo;
ILSize: Cardinal;
SafeArray: PSafeArray;
v: OleVariant;
ShellWindows: IShellWindows;
ExplorerHWND: Integer;
disp: IDispatch;
view: IFolderView;
dummy: OleVariant;
begin
OleCheck(CoInitialize(nil));
try
OleCheck(SHCreateItemFromParsingName(PChar(AFolder), nil, IShellItem, ShellItem));
try
OleCheck((ShellItem as IPersistIDList).GetIDList(PIDL));
try
ZeroMemory(#SEInfo, SizeOf(SEInfo));
SEInfo.cbSize := SizeOf(SEInfo);
SEInfo.Wnd := AHandle;
SEInfo.fMask := SEE_MASK_IDLIST;
SEInfo.nShow := SW_SHOW;
SEInfo.lpIDList := PIDL;
Win32Check(ShellExecuteEx(#SEInfo));
ILSize := ILGetSize(PIDL);
SafeArray := SafeArrayCreateVector(VT_UI1, 0, ILSize);
CopyMemory(SafeArray.pvData, PIDL, ILSize);
PVariantArg(#v).vt := VT_ARRAY or VT_UI1;
PVariantArg(#v).parray := SafeArray;
OleCheck(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_LOCAL_SERVER,
IShellWindows, ShellWindows));
try
dummy := Unassigned;
var c: Integer := 0;
repeat
if c > 0 then
Sleep(200);
disp := ShellWindows.FindWindowSW(v, dummy, SWC_BROWSER, ExplorerHWND,
SWFO_NEEDDISPATCH);
Inc(c);
until Assigned(disp) or (c > 15);
if disp = nil then
Exit;
OleCheck(IUnknown_QueryService(disp, IFolderView, IFolderView, view));
try
OleCheck(view.SetCurrentViewMode(FolderViewModes[AViewMode]));
finally
view := nil;
end;
finally
ShellWindows := nil;
end;
finally
CoTaskMemFree(PIDL);
end;
finally
ShellItem := nil;
end;
finally
CoUninitialize;
end;
end;
Instead of sleep-polling indefinitely for the window (and potentially killing the application!), I give up after 3 seconds.
Example usage:
procedure TForm1.Button1Click(Sender: TObject);
begin
OpenFolder(Handle, 'C:\Users\Andreas Rejbrand\Skrivbord\Test', fvmThumbnail);
end;
The view modes,
type
TFolderViewMode = (fvmAuto, fvmIcon, fvmSmallIcon, fvmList, fvmDetails,
fvmThumbnail, fvmTile, fvmThumbstrip, fvmContent);
are mapped directly to Windows' FOLDERVIEWMODEs. Please note that your version of Windows might not support all of them.
No, EXPLORER.EXE has no parameter for this - it neither had one in Windows 7, nor does it have one in Windows 10. There are surprisingly few parameters available anyway.
Your best bet is starting the Explorer thru CreateProcessW() to then obtain the handle of its main thread and finally find the new window. Then you can manipulate individual controls, such as the file list. See this answer, based on AutoIt: Automating Windows Explorer - it basically uses IShellBrowser and (beyond Windows XP) IFolderView2.SetViewModeAndIconSize() to then apply FVM_THUMBNAIL.

Lazarus error "External: SIGSEGV" on variable increment?

I got a problem in my Lazarus project: everytime I want to use a function it throws the above error (External: SIGSEGV). I don't know what that means, but some debugging showed me, that this is the code, causing the error:
class function TUtils.AsStringArray(const Strs:TStrings): TStringArray;
var
s:string;
i:integer;
begin
SetLength(Result, Strs.Count);
i := 1;
for s in Strs do
begin
Result[i] := s;
i := i + 1;
end;
end;
And the definitions
TStringArray = array of string;
TUtils = class
public
[...]
class function AsStringArray(const Strs:TStrings): TStringArray; static;
end;
The exception occurs after i := i + 1;. I would be really thankful if you could help me!
Dynamic arrays such as TStringArray = array of string; are zero-based; your code uses it as 1-based and raises access violation.
You should replace i := 1; by i := 0;
To the second Problem, it is because you are accesing to the index i, wich at the start it is 1 that is why you have the problem, the range of the array is determined by "length - 1", so if your length is 1, then your range is 0. So to solve the problem in your for loop you have to put Result[i-1] := s; like this you acces the index you really want.
More of this on http://wiki.freepascal.org/Dynamic_array

Get OSX HD Serial Number

I need to get the HD serial Number on OSX. I could not find any Delphi examples so far.
I found this C++ Builder example:
AnsiString GetSerialNumber()
{
AnsiString result;
io_service_t platformExpert =
IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("IOPlatformExpertDevice"));
if (platformExpert) {
CFTypeRef serialNumberAsCFString =
IORegistryEntryCreateCFProperty(platformExpert,
CFSTR(kIOPlatformSerialNumberKey),
kCFAllocatorDefault, 0);
if (serialNumberAsCFString)
{
result = CFStringGetCStringPtr((CFStringRef) serialNumberAsCFString, 0);
CFRelease(serialNumberAsCFString);
}
IOObjectRelease(platformExpert);
}
return result;
}
I'm using XE7.
Help porting this to Delphi will be highly appreciated.
#David - in Macapi.IOKit, IOServiceGetMatchingService point to CFDictionaryRef while IOServiceMatching point to CFMutableDictionaryRef.
I could not find any doc how to cast CFMutableDictionaryRef to CFDictionaryRef.
That's what I came up with so far:
function GetMacSerialNo: String;
Const
kIOPlatformSerialNumberKey = 'IOPlatformSerialNumber';
Var
PlatformExpert: io_service_t;
M: CFMutableDictionaryRef;
SerialNumberAsCFString: CFTypeRef;
_AnsiChar: PAnsiChar;
begin
M := IOServiceMatching('IOPlatformExpertDevice');
PlatformExpert := IOServiceGetMatchingService(kIOMasterPortDefault,M); --> E2010 Incompatible types: 'CFDictionaryRef' and 'CFMutableDictionaryRef'
SerialNumberAsCFString := IORegistryEntryCreateCFProperty(PlatformExpert,
CFSTR(kIOPlatformSerialNumberKey),kCFAllocatorDefault,0);
_AnsiChar := CFStringGetCStringPtr(SerialNumberAsCFString,0);
Result := String(AnsiString(_AnsiChar));
end;
Turns out casting a CFMutableDictionaryRef is simpler than I thought.
Here's the working code for anyone who may needs it.
Function GetMacSerialNo: String;
Const
kIOPlatformSerialNumberKey = 'IOPlatformSerialNumber';
Var
PlatformExpert: io_service_t;
M: CFMutableDictionaryRef;
SerialNumberAsCFString: CFTypeRef;
_AnsiChar: PAnsiChar;
begin
M := IOServiceMatching('IOPlatformExpertDevice');
PlatformExpert := IOServiceGetMatchingService(kIOMasterPortDefault,CFDictionaryRef(M));
SerialNumberAsCFString := IORegistryEntryCreateCFProperty(PlatformExpert,
CFSTR(kIOPlatformSerialNumberKey),kCFAllocatorDefault,0);
_AnsiChar := CFStringGetCStringPtr(SerialNumberAsCFString,0);
Result := String(AnsiString(_AnsiChar));
IOObjectRelease(PlatformExpert);
End;

Get the full audio device name from Windows

Is there a way to get the full audio device name in Windows XP and later?
I can use MIXERCAPS but the szPname member will limit to 32 characters (including NULL). For an audio device name of "Microphone (High Definition Audio Device)", I only get back "Microphone (High Definition Aud". This is due to MAXPNAMELEN being defined to 32. I have tried redefining it to a larger number to no effect.
Here is the code I am using:
MIXERCAPS mc;
ZeroMemory( &mc, sizeof(MIXERCAPS) );
mm = mixerGetDevCaps( reinterpret_cast<UINT_PTR>(m_hMixer), &mc, sizeof(MIXERCAPS) );
I saw this question, but it references Vista and later.
If you use the classic Windows Multimedia interface you probably can't get around the MAXPNAMELEN limitation, since that's compiled into Windows itself.
However you might be able to get the full device name if you use DirectSound instead. The following code is untested but I think it should work.
BOOL CALLBACK EnumCallback(LPGUID guid, LPCSTR descr, LPCSTR modname, LPVOID ctx)
{
std::vector<std::string> *names = (std::vector<std::string>*)ctx;
names->push_back(std::string(descr));
return TRUE;
}
int main()
{
std::vector<std::string> names;
if (!FAILED(DirectSoundEnumerate(&EnumCallback, &names)))
{
// do stuff
}
}
Below is my (Delphi) code:
This is using DirectShow/ActiveX,
It enumurates DirectSound devices, which include wrapped WaveOut devices as well.
procedure EnumAudioDevices;
var
dsCreateDevEnum : ICreateDevEnum;
EnumDevice : IEnumMoniker;
DeviceMoniker : IMoniker;
Data : Integer;
DevicePropBag : IPropertyBag;
DeviceName : OLEVariant;
I : Integer;
begin
// CLSID_CQzFilterClassManager = Entire DirectShow Filter List
If CoCreateInstance(CLSID_SystemDeviceEnum,nil,CLSCTX_INPROC_SERVER,IID_ICreateDevEnum,dsCreateDevEnum) = S_OK then
Begin
If dsCreateDevEnum.CreateClassEnumerator(CLSID_AudioRendererCategory,EnumDevice,0) = S_OK then
Begin
I := 0;
EnumDevice.Reset;
While EnumDevice.Next(1,DeviceMoniker,#Data) = S_OK do
Begin
If DeviceMoniker.BindToStorage(nil,nil,IID_IPropertyBag,DevicePropBag) = NOERROR then
Begin
If DevicePropBag.Read('FriendlyName',DeviceName,nil) = NOERROR then
Begin
// Success
ShowMessage(DeviceName);
Inc(I);
End;
DevicePropBag := nil;
End;
DeviceMoniker := nil;
End;
EnumDevice := nil;
End;
dsCreateDevEnum := nil;
End;
End;
You could try using devcon. Available at Microsoft's site here.
I think devcon listclass media may give you the result you're looking for.

Update fonts recursively on a Delphi form

I'm trying to iterate all the controls on a form and enable ClearType font smoothing. Something like this:
procedure TForm4.UpdateControls(AParent: TWinControl);
var
I: Integer;
ACtrl: TControl;
tagLOGFONT: TLogFont;
begin
for I := 0 to AParent.ControlCount-1 do
begin
ACtrl:= AParent.Controls[I];
// if ParentFont=False, update the font here...
if ACtrl is TWinControl then
UpdateControls(Ctrl as TWinControl);
end;
end;
Now, is there a easy way to check if ACtrl have a Font property so i can pass the Font.Handle to somethink like:
GetObject(ACtrl.Font.Handle, SizeOf(TLogFont), #tagLOGFONT);
tagLOGFONT.lfQuality := 5;
ACtrl.Font.Handle := CreateFontIndirect(tagLOGFONT);
Thank you in advance.
You use TypInfo unit, more specifically methods IsPublishedProp and GetOrdProp.
In your case, it would be something like:
if IsPublishedProp(ACtrl, 'Font') then
ModifyFont(TFont(GetOrdProp(ACtrl, 'Font')))
A fragment from one of my libraries that should put you on the right path:
function ContainsNonemptyControl(controlParent: TWinControl;
const requiredControlNamePrefix: string;
const ignoreControls: string = ''): boolean;
var
child : TControl;
iControl: integer;
ignored : TStringList;
obj : TObject;
begin
Result := true;
if ignoreControls = '' then
ignored := nil
else begin
ignored := TStringList.Create;
ignored.Text := ignoreControls;
end;
try
for iControl := 0 to controlParent.ControlCount-1 do begin
child := controlParent.Controls[iControl];
if (requiredControlNamePrefix = '') or
SameText(requiredControlNamePrefix, Copy(child.Name, 1,
Length(requiredControlNamePrefix))) then
if (not assigned(ignored)) or (ignored.IndexOf(child.Name) < 0) then
if IsPublishedProp(child, 'Text') and (GetStrProp(child, 'Text') <> '') then
Exit
else if IsPublishedProp(child, 'Lines') then begin
obj := TObject(cardinal(GetOrdProp(child, 'Lines')));
if (obj is TStrings) and (Unwrap(TStrings(obj).Text, child) <> '') then
Exit;
end;
end; //for iControl
finally FreeAndNil(ignored); end;
Result := false;
end; { ContainsNonemptyControl }
There's no need to use RTTI for this. Every TControl descendant has a Font property. At TControl level its visibility is protected but you can use this workaround to access it:
type
THackControl = class(TControl);
ModifyFont(THackControl(AParent.Controls[I]).Font);
One other thing worth mentioning. Every control has a ParentFont property, which - if set - allows the Form's font choice to ripple down to every control. I tend to make sure ParentFont is set true wherever possible, which also makes it easier to theme forms according to the current OS.
Anyway, surely you shouldn't need to do anything to enable ClearType smoothing? It should just happen automatically if you use a TrueType font and the user has enabled the Cleartype "effect".
Here's a C++Builder example of TOndrej's answer:
struct THackControl : TControl
{
__fastcall virtual THackControl(Classes::TComponent* AOwner);
TFont* Font() { return TControl::Font; };
};
for(int ControlIdx = 0; ControlIdx < ControlCount; ++ControlIdx)
{
((THackControl*)Controls[ControlIdx])->Font()->Color = clRed;
}

Resources