duvc-ctl 2.0.0
USB Video Class Camera Control Library
Loading...
Searching...
No Matches
device.cpp
Go to the documentation of this file.
1
7#include <duvc-ctl/detail/com_helpers.h>
8
9#ifdef _WIN32
10#include <comdef.h>
11#include <dbt.h>
12#include <dshow.h>
15
16// DirectShow GUIDs - properly declared
17EXTERN_C const CLSID CLSID_SystemDeviceEnum;
18EXTERN_C const CLSID CLSID_VideoInputDeviceCategory;
19EXTERN_C const IID IID_ICreateDevEnum;
20EXTERN_C const IID IID_IPropertyBag;
21
22namespace duvc {
23
24using namespace detail;
25
26// Global device monitoring state
28HWND g_notification_window = nullptr;
29HDEVNOTIFY g_device_notify = nullptr;
30
31// DirectShow enumeration helpers - these need to be exported for
32// connection_pool.cpp
33com_ptr<ICreateDevEnum> create_dev_enum() {
34 com_ptr<ICreateDevEnum> dev;
35 HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr,
36 CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
37 reinterpret_cast<void **>(dev.put()));
38 if (FAILED(hr))
39 throw_hr(hr, "CoCreateInstance(SystemDeviceEnum)");
40 return dev;
41}
42
43com_ptr<IEnumMoniker> enum_video_devices(ICreateDevEnum *dev) {
44 com_ptr<IEnumMoniker> e;
45 HRESULT hr =
46 dev->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, e.put(), 0);
47 if (hr == S_FALSE)
48 return {}; // No devices
49 if (FAILED(hr))
50 throw_hr(hr, "CreateClassEnumerator(VideoInputDeviceCategory)");
51 return e;
52}
53
54static std::wstring read_prop_bstr(IPropertyBag *bag, const wchar_t *key) {
55 if (!bag || !key) {
56 return L"";
57 }
58
59 VARIANT v;
60 VariantInit(&v);
61 std::wstring res;
62
63 HRESULT hr = bag->Read(key, &v, nullptr);
64 if (SUCCEEDED(hr) && v.vt == VT_BSTR && v.bstrVal) {
65 _bstr_t bstr(v.bstrVal); // manages lifetime
66 const wchar_t *ptr = static_cast<const wchar_t*>(bstr);
67 if (ptr) {
68 size_t len = bstr.length();
69 if (len > 0 && len < 2048) {
70 res.assign(ptr, len);
71 }
72 }
74 }
75
76 return res;
77}
78
79std::wstring read_friendly_name(IMoniker *mon) {
80 if (!mon) {
81 return L"";
82 }
83
84 com_ptr<IPropertyBag> bag;
85 HRESULT hr = mon->BindToStorage(
86 nullptr,
87 nullptr,
89 reinterpret_cast<void**>(bag.put())
90 );
91 if (FAILED(hr) || !bag) {
92 return L"";
93 }
94
95 return read_prop_bstr(bag.get(), L"FriendlyName");
96}
97
98std::wstring read_device_path(IMoniker *mon) {
99 if (!mon) {
100 return L"";
101 }
102
103 com_ptr<IPropertyBag> bag;
104 HRESULT hr = mon->BindToStorage(
105 nullptr,
106 nullptr,
108 reinterpret_cast<void**>(bag.put())
109 );
110 if (SUCCEEDED(hr) && bag) {
111 auto dp = read_prop_bstr(bag.get(), L"DevicePath");
112 if (!dp.empty()) {
113 return dp;
114 }
115 }
116
117 // Fallback: IMoniker::GetDisplayName
118 LPOLESTR disp = nullptr;
119 std::wstring res;
120
121 if (SUCCEEDED(mon->GetDisplayName(nullptr, nullptr, &disp)) && disp) {
122 _bstr_t bstr(disp); // safe wrapper around LPOLESTR
123 const wchar_t *ptr = static_cast<const wchar_t*>(bstr);
124 if (ptr) {
125 size_t len = bstr.length();
126 if (len > 0 && len < 512) {
127 res.assign(ptr, len);
128 }
129 }
130 CoTaskMemFree(disp);
131
132 if (!res.empty()) {
133 // Trim trailing whitespace / control chars
134 res.erase(res.find_last_not_of(L"\r\n \t\0") + 1);
135 }
136 }
137
138 return res;
139}
140
141bool is_same_device(const Device &d, const std::wstring &name,
142 const std::wstring &path) {
143 if (!d.path.empty() && !path.empty()) {
144 if (_wcsicmp(d.path.c_str(), path.c_str()) == 0)
145 return true;
146 }
147
148 if (!d.name.empty() && !name.empty()) {
149 if (_wcsicmp(d.name.c_str(), name.c_str()) == 0)
150 return true;
151 }
152
153 return false;
154}
155
156std::vector<Device> list_devices() {
157 com_apartment com;
158 std::vector<Device> out;
159
160 auto de = create_dev_enum();
161 auto en = enum_video_devices(de.get());
162 if (!en)
163 return out;
164
165 ULONG fetched = 0;
166 com_ptr<IMoniker> mon;
167 while (en->Next(1, mon.put(), &fetched) == S_OK && fetched) {
168 Device d;
169 d.name = read_friendly_name(mon.get());
170 d.path = read_device_path(mon.get());
171 out.emplace_back(std::move(d));
172 mon.reset();
173 }
174
175 return out;
176}
177
178bool is_device_connected(const Device &dev) {
179 try {
180 // First try: Check if device still exists in enumeration
181 com_apartment com;
182 auto de = create_dev_enum();
183 auto en = enum_video_devices(de.get());
184 if (!en)
185 return false;
186
187 ULONG fetched = 0;
188 com_ptr<IMoniker> mon;
189 while (en->Next(1, mon.put(), &fetched) == S_OK && fetched) {
190 auto fname = read_friendly_name(mon.get());
191 auto dpath = read_device_path(mon.get());
192 if (is_same_device(dev, fname, dpath)) {
193 // Found in enumeration - now try lightweight access test
194 try {
195 DeviceConnection conn(dev);
196 return conn.is_valid();
197 } catch (...) {
198 // If connection fails, device exists but might be busy
199 return true;
200 }
201 }
202 mon.reset();
203 }
204
205 return false; // Not found in enumeration
206 } catch (...) {
207 return false;
208 }
209}
210
212 if (device_path.empty()) {
213 throw std::runtime_error("Device path cannot be empty");
214 }
218 IID_ICreateDevEnum, reinterpret_cast<void**>(de.put()));
219 if (FAILED(hr)) {
220 throw_hr(hr, "CoCreateInstance(SystemDeviceEnum)");
221 }
223 hr = de->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, reinterpret_cast<IEnumMoniker**>(en.put()), 0);
224 if (FAILED(hr)) {
225 if (hr == S_FALSE) {
226 throw std::runtime_error("No video devices available");
227 }
228 throw_hr(hr, "CreateClassEnumerator(VideoInputDeviceCategory)");
229 }
230 ULONG fetched = 0;
232 while (en->Next(1, reinterpret_cast<IMoniker**>(mon.put()), &fetched) == S_OK && fetched) {
233 std::wstring dpath = read_device_path(mon.get());
234 if (!dpath.empty()) {
235 dpath.erase(dpath.find_last_not_of(L"\r\n \t\0") + 1);
236 }
237 std::wstring fname = read_friendly_name(mon.get());
238 if (dpath == device_path || _wcsicmp(dpath.c_str(), device_path.c_str()) == 0) {
240 result.name = std::move(fname);
241 result.path = std::move(dpath);
242 return result;
243 }
244 }
245 throw std::runtime_error(
246 "Device with specified path not found. Ensure the device is connected and the path is valid.");
247}
248
249} // namespace duvc
250
251#else // _WIN32
252
253// Non-Windows stubs
254namespace duvc {
255
256std::vector<Device> list_devices() { return {}; }
257
258bool is_device_connected(const Device &) { return false; }
259
261
263
264} // namespace duvc
265
266#endif // _WIN32
Result type that can contain either a value or an error.
Definition result.h:75
Windows-specific device connection pooling.
EXTERN_C const IID IID_IPropertyBag
Definition device.cpp:20
EXTERN_C const CLSID CLSID_SystemDeviceEnum
Definition device.cpp:17
EXTERN_C const CLSID CLSID_VideoInputDeviceCategory
Definition device.cpp:18
EXTERN_C const IID IID_ICreateDevEnum
Definition device.cpp:19
Device enumeration and management functions.
Definition core.h:13
std::wstring read_friendly_name(IMoniker *mon)
Read friendly name from device moniker.
Definition core.cpp:199
static std::wstring read_prop_bstr(IPropertyBag *bag, const wchar_t *key)
Definition core.cpp:189
bool is_same_device(const Device &d, const std::wstring &name, const std::wstring &path)
Check if two device identifiers refer to same device.
Definition core.cpp:250
static DeviceChangeCallback g_device_callback
Definition core.cpp:147
com_ptr< IEnumMoniker > enum_video_devices(ICreateDevEnum *dev)
Enumerate video input devices.
Definition core.cpp:181
static void throw_hr(HRESULT hr, const char *where)
Definition core.cpp:115
std::vector< Device > list_devices()
Enumerate all available video input devices.
Definition core.cpp:626
std::wstring read_device_path(IMoniker *mon)
Read device path from moniker.
Definition core.cpp:207
void unregister_device_change_callback()
Unregister device change callback.
Definition core.cpp:537
static HDEVNOTIFY g_device_notify
Definition core.cpp:149
bool is_device_connected(const Device &dev)
Check if a device is currently connected and accessible.
Definition core.cpp:549
com_ptr< ICreateDevEnum > create_dev_enum()
Create DirectShow device enumerator.
Definition core.cpp:173
void register_device_change_callback(DeviceChangeCallback callback)
Register callback for device hotplug events.
Definition core.cpp:511
std::function< void(bool device_added, const std::wstring &device_path)> DeviceChangeCallback
Device change callback function type.
Definition core.h:34
Device find_device_by_path(const std::wstring &device_path)
Find device by unique Windows device path.
Definition device.cpp:211
static HWND g_notification_window
Definition core.cpp:148
String conversion utilities for enums and types.
Represents a camera device.
Definition types.h:131