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
141// ONLY use device path to verify
142bool is_same_device(const Device &d, const std::wstring & /*name*/,
143 const std::wstring &path) {
144 if (d.path.empty() || path.empty())
145 return false;
146
147 return _wcsicmp(d.path.c_str(), path.c_str()) == 0;
148}
149
150std::vector<Device> list_devices() {
151 com_apartment com;
152 std::vector<Device> out;
153
154 auto de = create_dev_enum();
155 auto en = enum_video_devices(de.get());
156 if (!en)
157 return out;
158
159 ULONG fetched = 0;
160 com_ptr<IMoniker> mon;
161 while (en->Next(1, mon.put(), &fetched) == S_OK && fetched) {
162 Device d;
163 d.name = read_friendly_name(mon.get());
164 d.path = read_device_path(mon.get());
165
166 // Skip completely invalid entries
167 if (d.name.empty() && d.path.empty()) {
168 mon.reset();
169 continue;
170 }
171
172 out.emplace_back(std::move(d));
173 mon.reset();
174 }
175
176 return out;
177}
178
179bool is_device_connected(const Device &dev) {
180 try {
181 // First try: Check if device still exists in enumeration
182 com_apartment com;
183 auto de = create_dev_enum();
184 auto en = enum_video_devices(de.get());
185 if (!en)
186 return false;
187
188 ULONG fetched = 0;
189 com_ptr<IMoniker> mon;
190 while (en->Next(1, mon.put(), &fetched) == S_OK && fetched) {
191 auto fname = read_friendly_name(mon.get());
192 auto dpath = read_device_path(mon.get());
193 if (is_same_device(dev, fname, dpath)) {
194 // Found in enumeration - now try lightweight access test
195 try {
196 DeviceConnection conn(dev);
197 return conn.is_valid();
198 } catch (...) {
199 // If connection fails, device exists but might be busy
200 return true;
201 }
202 }
203 mon.reset();
204 }
205
206 return false; // Not found in enumeration
207 } catch (...) {
208 return false;
209 }
210}
211
213 if (device_path.empty()) {
214 throw std::runtime_error("Device path cannot be empty");
215 }
219 IID_ICreateDevEnum, reinterpret_cast<void**>(de.put()));
220 if (FAILED(hr)) {
221 throw_hr(hr, "CoCreateInstance(SystemDeviceEnum)");
222 }
224 hr = de->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, reinterpret_cast<IEnumMoniker**>(en.put()), 0);
225 if (FAILED(hr)) {
226 if (hr == S_FALSE) {
227 throw std::runtime_error("No video devices available");
228 }
229 throw_hr(hr, "CreateClassEnumerator(VideoInputDeviceCategory)");
230 }
231 ULONG fetched = 0;
233 while (en->Next(1, reinterpret_cast<IMoniker**>(mon.put()), &fetched) == S_OK && fetched) {
234 std::wstring dpath = read_device_path(mon.get());
235 if (!dpath.empty()) {
236 auto pos = dpath.find_last_not_of(L"\r\n \t\0");
237 if (pos != std::wstring::npos)
238 dpath.erase(pos + 1);
239 }
240 std::wstring fname = read_friendly_name(mon.get());
241 if (_wcsicmp(dpath.c_str(), device_path.c_str()) == 0) {
243 result.name = std::move(fname);
244 result.path = std::move(dpath);
245 return result;
246 }
247 }
248 throw std::runtime_error(
249 "Device with specified path not found. Ensure the device is connected and the path is valid.");
250}
251
252} // namespace duvc
253
254#else // _WIN32
255
256// Non-Windows stubs
257namespace duvc {
258
259std::vector<Device> list_devices() { return {}; }
260
261bool is_device_connected(const Device &) { return false; }
262
264
266
267} // namespace duvc
268
269#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:212
static HWND g_notification_window
Definition core.cpp:148
String conversion utilities for enums and types.
Represents a camera device.
Definition types.h:131