diff --git a/include/cameraserver_c.h b/include/cameraserver_c.h index d01e8f21bd..dc0c35e820 100644 --- a/include/cameraserver_c.h +++ b/include/cameraserver_c.h @@ -238,6 +238,8 @@ void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status); // // Listener Functions // +void CS_SetListenerOnStart(void (*onStart)(void* data), void* data); +void CS_SetListenerOnExit(void (*onExit)(void* data), void* data); CS_Listener CS_AddListener(void* data, void (*callback)(void* data, const CS_Event* event), int eventMask, int immediateNotify, @@ -245,6 +247,8 @@ CS_Listener CS_AddListener(void* data, void CS_RemoveListener(CS_Listener handle, CS_Status* status); +int CS_NotifierDestroyed(void); + // // Utility Functions // diff --git a/include/cameraserver_cpp.h b/include/cameraserver_cpp.h index f3d7ec3e55..a106cd713b 100644 --- a/include/cameraserver_cpp.h +++ b/include/cameraserver_cpp.h @@ -83,11 +83,36 @@ struct RawEvent { kSourcePropertyChoicesUpdated = CS_SOURCE_PROPERTY_CHOICES_UPDATED }; + RawEvent() = default; + RawEvent(llvm::StringRef name_, CS_Handle handle_, RawEvent::Type type_) + : type{type_}, name{name_} { + if (type_ == kSinkCreated || type_ == kSinkDestroyed || + type_ == kSinkEnabled || type_ == kSinkDisabled) + sinkHandle = handle_; + else + sourceHandle = handle_; + } + RawEvent(llvm::StringRef name_, CS_Source source_, const VideoMode& mode_) + : type{kSourceVideoModeChanged}, + sourceHandle{source_}, + name{name_}, + mode{mode_} {} + RawEvent(llvm::StringRef name_, CS_Source source_, RawEvent::Type type_, + CS_Property property_, CS_PropertyType propertyType_, int value_, + llvm::StringRef valueStr_) + : type{type_}, + sourceHandle{source_}, + name{name_}, + propertyHandle{property_}, + propertyType{propertyType_}, + value{value_}, + valueStr{valueStr_} {} + Type type; // Valid for kSource* and kSink* respectively - CS_Source sourceHandle; - CS_Sink sinkHandle; + CS_Source sourceHandle = CS_INVALID_HANDLE; + CS_Sink sinkHandle = CS_INVALID_HANDLE; // Source/sink name std::string name; @@ -95,7 +120,7 @@ struct RawEvent { // Fields for kSourceVideoModeChanged event VideoMode mode; - // Fields for CS_SOURCE_PROPERTY_* events + // Fields for kSourceProperty* events CS_Property propertyHandle; CS_PropertyType propertyType; int value; @@ -225,11 +250,16 @@ void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status); // // Listener Functions // +void SetListenerOnStart(std::function onStart); +void SetListenerOnExit(std::function onExit); + CS_Listener AddListener(std::function callback, int eventMask, bool immediateNotify, CS_Status* status); void RemoveListener(CS_Listener handle, CS_Status* status); +bool NotifierDestroyed(); + // // Utility Functions // @@ -242,4 +272,4 @@ llvm::ArrayRef EnumerateSinkHandles( } // namespace cs -#endif /* CAMERASERVER_CPP_H_ */ +#endif // CAMERASERVER_CPP_H_ diff --git a/include/cameraserver_oo.h b/include/cameraserver_oo.h index 2fe7d5388e..9b732d0c1c 100644 --- a/include/cameraserver_oo.h +++ b/include/cameraserver_oo.h @@ -358,16 +358,30 @@ class CvSink : public VideoSink { void SetEnabled(bool enabled); }; +/// An event generated by the library and provided to event listeners. class VideoEvent : public RawEvent { public: + /// Get the source associated with the event (if any). VideoSource GetSource() const; + + /// Get the sink associated with the event (if any). VideoSink GetSink() const; + + /// Get the property associated with the event (if any). VideoProperty GetProperty() const; }; +/// An event listener. This calls back to a desigated callback function when +/// an event matching the specified mask is generated by the library. class VideoListener { public: VideoListener() : m_handle(0) {} + + /// Create an event listener. + /// @param callback Callback function + /// @param eventMask Bitmask of VideoEvent::Type values + /// @param immediateNotify Whether callback should be immediately called with + /// a representative set of events for the current library state. VideoListener(std::function callback, int eventMask, bool immediateNotify); @@ -389,4 +403,4 @@ class VideoListener { #include "cameraserver_oo.inl" -#endif /* CAMERASERVER_OO_H_ */ +#endif // CAMERASERVER_OO_H_ diff --git a/java/lib/CameraServerJNI.cpp b/java/lib/CameraServerJNI.cpp index 3e9ab2a7c7..166102aede 100644 --- a/java/lib/CameraServerJNI.cpp +++ b/java/lib/CameraServerJNI.cpp @@ -22,6 +22,29 @@ using namespace wpi::java; static JavaVM *jvm = nullptr; static jclass usbCameraInfoCls = nullptr; static jclass videoModeCls = nullptr; +static jclass videoEventCls = nullptr; +// Thread-attached environment for listener callbacks. +static JNIEnv *listenerEnv = nullptr; + +static void ListenerOnStart() { + if (!jvm) return; + JNIEnv *env; + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_2; + args.name = const_cast("CSListener"); + args.group = nullptr; + if (jvm->AttachCurrentThreadAsDaemon(reinterpret_cast(&env), + &args) != JNI_OK) + return; + if (!env || !env->functions) return; + listenerEnv = env; +} + +static void ListenerOnExit() { + listenerEnv = nullptr; + if (!jvm) return; + jvm->DetachCurrentThread(); +} extern "C" { @@ -47,6 +70,16 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { if (!videoModeCls) return JNI_ERR; env->DeleteLocalRef(local); + local = env->FindClass("edu/wpi/cameraserver/VideoEvent"); + if (!local) return JNI_ERR; + videoEventCls = static_cast(env->NewGlobalRef(local)); + if (!videoEventCls) return JNI_ERR; + env->DeleteLocalRef(local); + + // Initial configuration of listener start/exit + cs::SetListenerOnStart(ListenerOnStart); + cs::SetListenerOnExit(ListenerOnExit); + return JNI_VERSION_1_6; } @@ -57,11 +90,43 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { // Delete global references if (usbCameraInfoCls) env->DeleteGlobalRef(usbCameraInfoCls); if (videoModeCls) env->DeleteGlobalRef(videoModeCls); + if (videoEventCls) env->DeleteGlobalRef(videoEventCls); jvm = nullptr; } } // extern "C" +// +// Helper class to create and clean up a global reference +// +template +class JGlobal { + public: + JGlobal(JNIEnv *env, T obj) + : m_obj(static_cast(env->NewGlobalRef(obj))) {} + ~JGlobal() { + if (!jvm || cs::NotifierDestroyed()) return; + JNIEnv *env; + bool attached = false; + // don't attach and de-attach if already attached to a thread. + if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) == + JNI_EDETACHED) { + if (jvm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != + JNI_OK) + return; + attached = true; + } + if (!env || !env->functions) return; + env->DeleteGlobalRef(m_obj); + if (attached) jvm->DetachCurrentThread(); + } + operator T() { return m_obj; } + T obj() { return m_obj; } + + private: + T m_obj; +}; + static void ReportError(JNIEnv *env, CS_Status status, bool do_throw = true) { // TODO } @@ -90,6 +155,29 @@ static jobject MakeJObject(JNIEnv *env, const cs::VideoMode &videoMode) { static_cast(videoMode.fps)); } +static jobject MakeJObject(JNIEnv *env, const cs::RawEvent &event) { + static jmethodID constructor = + env->GetMethodID(videoEventCls, "", + "(IIILjava/lang/String;IIIIIIILjava/lang/String;)V"); + JLocal name(env, MakeJString(env, event.name)); + JLocal valueStr(env, MakeJString(env, event.valueStr)); + return env->NewObject( + videoEventCls, + constructor, + static_cast(event.type), + static_cast(event.sourceHandle), + static_cast(event.sinkHandle), + name.obj(), + static_cast(event.mode.pixelFormat), + static_cast(event.mode.width), + static_cast(event.mode.height), + static_cast(event.mode.fps), + static_cast(event.propertyHandle), + static_cast(event.propertyType), + static_cast(event.value), + valueStr.obj()); +} + extern "C" { /* @@ -798,6 +886,57 @@ JNIEXPORT void JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_setSinkEnabled CheckStatus(env, status); } +/* + * Class: edu_wpi_cameraserver_CameraServerJNI + * Method: addListener + * Signature: (Ledu/wpi/cameraserver/CameraServerJNI/ConnectionListenerFunction;IZ)I + */ +JNIEXPORT jint JNICALL Java_edu_wpi_cameraserver_CameraServerJNI_addListener + (JNIEnv *envouter, jclass, jobject listener, jint eventMask, jboolean immediateNotify) +{ + // the shared pointer to the weak global will keep it around until the + // entry listener is destroyed + auto listener_global = + std::make_shared>(envouter, listener); + + // cls is a temporary here; cannot be used within callback functor + jclass cls = envouter->GetObjectClass(listener); + if (!cls) return 0; + + // method ids, on the other hand, are safe to retain + jmethodID mid = envouter->GetMethodID(cls, "apply", + "(Ledu/wpi/cameraserver/VideoEvent;)V"); + if (!mid) return 0; + + CS_Status status = 0; + CS_Listener handle = cs::AddListener( + [=](const cs::RawEvent &event) { + JNIEnv *env = listenerEnv; + if (!env || !env->functions) return; + + // get the handler + auto handler = listener_global->obj(); + + // convert into the appropriate Java type + JLocal jobj{env, MakeJObject(env, event)}; + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return; + } + if (!jobj) return; + + env->CallVoidMethod(handler, mid, jobj.obj()); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + }, + eventMask, immediateNotify != JNI_FALSE, &status); + CheckStatus(envouter, status); + return handle; +} + /* * Class: edu_wpi_cameraserver_CameraServerJNI * Method: removeListener diff --git a/java/src/edu/wpi/cameraserver/CameraServerJNI.java b/java/src/edu/wpi/cameraserver/CameraServerJNI.java index ef4f4f7539..81c7038567 100644 --- a/java/src/edu/wpi/cameraserver/CameraServerJNI.java +++ b/java/src/edu/wpi/cameraserver/CameraServerJNI.java @@ -121,9 +121,6 @@ public class CameraServerJNI { public static native void setSourceConnected(int source, boolean connected); public static native void setSourceDescription(int source, String description); public static native int createSourceProperty(int source, String name, int type, int minimum, int maximum, int step, int defaultValue, int value); - //public static native int createSourcePropertyCallback(int source, String name, - // int type, int minimum, int maximum, int step, int defaultValue, int value, - // void (*onChange)(int property)); public static native void setSourceEnumPropertyChoices(int source, int property, String[] choices); // @@ -156,8 +153,8 @@ public class CameraServerJNI { // // Listener Functions // - //public static native int addListener(void (*callback)(VideoEvent event), - // int eventMask); + public static native int addListener(VideoListenerFunction listener, + int eventMask, boolean immediateNotify); public static native void removeListener(int handle); diff --git a/java/src/edu/wpi/cameraserver/VideoEvent.java b/java/src/edu/wpi/cameraserver/VideoEvent.java new file mode 100644 index 0000000000..1d0b0e203c --- /dev/null +++ b/java/src/edu/wpi/cameraserver/VideoEvent.java @@ -0,0 +1,83 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.cameraserver; + +/// Video event +public class VideoEvent { + public enum Type { + kSourceCreated(0x0001), + kSourceDestroyed(0x0002), + kSourceConnected(0x0004), + kSourceDisconnected(0x0008), + kSourceVideoModesUpdated(0x0010), + kSourceVideoModeChanged(0x0020), + kSinkCreated(0x0100), + kSinkDestroyed(0x0200), + kSinkEnabled(0x0400), + kSinkDisabled(0x0800), + kSourcePropertyCreated(0x1000), + kSourcePropertyValueUpdated(0x2000), + kSourcePropertyChoicesUpdated(0x4000); + + private int value; + + private Type(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + private static final Type[] m_typeValues = Type.values(); + + VideoEvent(int type, int source, int sink, String name, int pixelFormat, + int width, int height, int fps, int property, int propertyType, + int value, String valueStr) { + this.type = m_typeValues[type]; + this.sourceHandle = source; + this.sinkHandle = sink; + this.name = name; + this.mode = new VideoMode(pixelFormat, width, height, fps); + this.propertyHandle = property; + this.propertyType = VideoProperty.m_typeValues[propertyType]; + this.value = value; + this.valueStr = valueStr; + } + + public Type type; + + // Valid for kSource* and kSink* respectively + private int sourceHandle; + private int sinkHandle; + + // Source/sink name + public String name; + + // Fields for kSourceVideoModeChanged event + public VideoMode mode; + + // Fields for kSourceProperty* events + private int propertyHandle; + public VideoProperty.Type propertyType; + public int value; + public String valueStr; + + public VideoSource getSource() { + return new VideoSource(CameraServerJNI.copySource(sourceHandle)); + } + + public VideoSink getSink() { + return new VideoSink(CameraServerJNI.copySink(sinkHandle)); + } + + public VideoProperty getProperty() { + return new VideoProperty(propertyHandle, propertyType); + } + +} diff --git a/java/src/edu/wpi/cameraserver/VideoListener.java b/java/src/edu/wpi/cameraserver/VideoListener.java new file mode 100644 index 0000000000..091a2cb303 --- /dev/null +++ b/java/src/edu/wpi/cameraserver/VideoListener.java @@ -0,0 +1,35 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.cameraserver; + +/// An event listener. This calls back to a desigated callback function when +/// an event matching the specified mask is generated by the library. +public class VideoListener { + /// Create an event listener. + /// @param listener Listener function + /// @param eventMask Bitmask of VideoEvent.Type values + /// @param immediateNotify Whether callback should be immediately called with + /// a representative set of events for the current library state. + public VideoListener(VideoListenerFunction listener, int eventMask, + boolean immediateNotify) { + m_handle = CameraServerJNI.addListener(listener, eventMask, immediateNotify); + } + + public synchronized void free() { + if (m_handle != 0) { + CameraServerJNI.removeListener(m_handle); + } + m_handle = 0; + } + + public boolean isValid() { + return m_handle != 0; + } + + private int m_handle; +} diff --git a/java/src/edu/wpi/cameraserver/VideoListenerFunction.java b/java/src/edu/wpi/cameraserver/VideoListenerFunction.java new file mode 100644 index 0000000000..d11e616b5a --- /dev/null +++ b/java/src/edu/wpi/cameraserver/VideoListenerFunction.java @@ -0,0 +1,12 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.cameraserver; + +public interface VideoListenerFunction { + void apply(VideoEvent event); +} diff --git a/java/src/edu/wpi/cameraserver/VideoProperty.java b/java/src/edu/wpi/cameraserver/VideoProperty.java index b5a71a1d9c..70e9dcaf8d 100644 --- a/java/src/edu/wpi/cameraserver/VideoProperty.java +++ b/java/src/edu/wpi/cameraserver/VideoProperty.java @@ -20,7 +20,7 @@ public class VideoProperty { return value; } } - private static final Type[] m_typeValues = Type.values(); + static final Type[] m_typeValues = Type.values(); public String getName() { return CameraServerJNI.getPropertyName(m_handle); @@ -94,6 +94,11 @@ public class VideoProperty { m_type = m_typeValues[CameraServerJNI.getPropertyType(handle)]; } + VideoProperty(int handle, Type type) { + m_handle = handle; + m_type = type; + } + int m_handle; private Type m_type; } diff --git a/src/Notifier.cpp b/src/Notifier.cpp new file mode 100644 index 0000000000..09ceed7b21 --- /dev/null +++ b/src/Notifier.cpp @@ -0,0 +1,187 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015-2016. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "Notifier.h" + +#include +#include + +#include "Handle.h" + +using namespace cs; + +ATOMIC_STATIC_INIT(Notifier) +bool Notifier::s_destroyed = false; + +namespace { +// Vector which provides an integrated freelist for removal and reuse of +// individual elements. +template +class UidVector { + public: + typedef typename std::vector::size_type size_type; + + size_type size() const { return m_vector.size(); } + T& operator[](size_type i) { return m_vector[i]; } + const T& operator[](size_type i) const { return m_vector[i]; } + + // Add a new T to the vector. If there are elements on the freelist, + // reuses the last one; otherwise adds to the end of the vector. + // Returns the resulting element index (+1). + template + unsigned int emplace_back(Args&&... args) { + unsigned int uid; + if (m_free.empty()) { + uid = m_vector.size(); + m_vector.emplace_back(std::forward(args)...); + } else { + uid = m_free.back(); + m_free.pop_back(); + m_vector[uid] = T(std::forward(args)...); + } + return uid + 1; + } + + // Removes the identified element by replacing it with a default-constructed + // one. The element is added to the freelist for later reuse. + void erase(unsigned int uid) { + --uid; + if (uid >= m_vector.size() || !m_vector[uid]) return; + m_free.push_back(uid); + m_vector[uid] = T(); + } + + private: + std::vector m_vector; + std::vector m_free; +}; + +} // anonymous namespace + +class Notifier::Thread : public wpi::SafeThread { + public: + Thread(std::function on_start, std::function on_exit) + : m_on_start(on_start), m_on_exit(on_exit) {} + + void Main(); + + struct Listener { + Listener() = default; + Listener(std::function callback_, + int eventMask_) + : callback(callback_), eventMask(eventMask_) {} + + explicit operator bool() const { return bool(callback); } + + std::string prefix; + std::function callback; + int eventMask; + }; + UidVector m_listeners; + + std::queue m_notifications; + + std::function m_on_start; + std::function m_on_exit; +}; + +Notifier::Notifier() { s_destroyed = false; } + +Notifier::~Notifier() { s_destroyed = true; } + +void Notifier::Start() { + auto thr = m_owner.GetThread(); + if (!thr) m_owner.Start(new Thread(m_on_start, m_on_exit)); +} + +void Notifier::Stop() { m_owner.Stop(); } + +void Notifier::Thread::Main() { + if (m_on_start) m_on_start(); + + std::unique_lock lock(m_mutex); + while (m_active) { + while (m_notifications.empty()) { + m_cond.wait(lock); + if (!m_active) goto done; + } + + while (!m_notifications.empty()) { + if (!m_active) goto done; + auto item = std::move(m_notifications.front()); + m_notifications.pop(); + + // Use index because iterator might get invalidated. + for (std::size_t i = 0; i < m_listeners.size(); ++i) { + if (!m_listeners[i]) continue; // removed + + // Event type must be within requested set for this listener. + if ((item.type & m_listeners[i].eventMask) == 0) continue; + + // make a copy of the callback so we can safely release the mutex + auto callback = m_listeners[i].callback; + + // Don't hold mutex during callback execution! + lock.unlock(); + callback(item); + lock.lock(); + } + } + } + +done: + if (m_on_exit) m_on_exit(); +} + +int Notifier::AddListener( + std::function callback, int eventMask) { + Start(); + auto thr = m_owner.GetThread(); + return thr->m_listeners.emplace_back(callback, eventMask); +} + +void Notifier::RemoveListener(int uid) { + auto thr = m_owner.GetThread(); + if (!thr) return; + thr->m_listeners.erase(uid); +} + +void Notifier::NotifySource(llvm::StringRef name, CS_Source source, + RawEvent::Type type) { + auto thr = m_owner.GetThread(); + if (!thr) return; + thr->m_notifications.emplace(name, source, type); + thr->m_cond.notify_one(); +} + +void Notifier::NotifySourceVideoMode(llvm::StringRef name, CS_Source source, + const VideoMode& mode) { + auto thr = m_owner.GetThread(); + if (!thr) return; + thr->m_notifications.emplace(name, source, mode); + thr->m_cond.notify_one(); +} + +void Notifier::NotifySourceProperty(llvm::StringRef name, CS_Source source, + RawEvent::Type type, int property, + CS_PropertyType propertyType, int value, + llvm::StringRef valueStr) { + auto thr = m_owner.GetThread(); + if (!thr) return; + thr->m_notifications.emplace(name, source, type, + Handle{source, property, Handle::kProperty}, + propertyType, value, valueStr); + thr->m_cond.notify_one(); +} + +void Notifier::NotifySink(llvm::StringRef name, CS_Sink sink, + RawEvent::Type type) { + auto thr = m_owner.GetThread(); + if (!thr) return; + thr->m_notifications.emplace(name, sink, type); + thr->m_cond.notify_one(); +} diff --git a/src/Notifier.h b/src/Notifier.h new file mode 100644 index 0000000000..7ce8f88ca2 --- /dev/null +++ b/src/Notifier.h @@ -0,0 +1,67 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015-2016. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CAMERASERVER_NOTIFIER_H_ +#define CAMERASERVER_NOTIFIER_H_ + +#include + +#include "support/atomic_static.h" +#include "support/SafeThread.h" +#include "cameraserver_cpp.h" + +namespace cs { + +class Notifier { + friend class NotifierTest; + + public: + static Notifier& GetInstance() { + ATOMIC_STATIC(Notifier, instance); + return instance; + } + ~Notifier(); + + void Start(); + void Stop(); + + static bool destroyed() { return s_destroyed; } + + void SetOnStart(std::function on_start) { m_on_start = on_start; } + void SetOnExit(std::function on_exit) { m_on_exit = on_exit; } + + int AddListener(std::function callback, + int eventMask); + void RemoveListener(int uid); + + // Notification events + void NotifySource(llvm::StringRef name, CS_Source source, + RawEvent::Type type); + void NotifySourceVideoMode(llvm::StringRef name, CS_Source source, + const VideoMode& mode); + void NotifySourceProperty(llvm::StringRef name, CS_Source source, + RawEvent::Type type, int property, + CS_PropertyType propertyType, int value, + llvm::StringRef valueStr); + void NotifySink(llvm::StringRef name, CS_Sink sink, RawEvent::Type type); + + private: + Notifier(); + + class Thread; + wpi::SafeThreadOwner m_owner; + + std::function m_on_start; + std::function m_on_exit; + + ATOMIC_STATIC_DECL(Notifier) + static bool s_destroyed; +}; + +} // namespace cs + +#endif // CAMERASERVER_NOTIFIER_H_ diff --git a/src/cameraserver_c.cpp b/src/cameraserver_c.cpp index 5a421cd619..9ba5bd80e8 100644 --- a/src/cameraserver_c.cpp +++ b/src/cameraserver_c.cpp @@ -211,6 +211,14 @@ void CS_ReleaseSink(CS_Sink sink, CS_Status* status) { return cs::ReleaseSink(sink, status); } +void CS_SetListenerOnStart(void (*onStart)(void* data), void* data) { + cs::SetListenerOnStart([=]() { onStart(data); }); +} + +void CS_SetListenerOnExit(void (*onExit)(void* data), void* data) { + cs::SetListenerOnExit([=]() { onExit(data); }); +} + CS_Listener CS_AddListener(void* data, void (*callback)(void* data, const CS_Event* event), int eventMask, int immediateNotify, @@ -236,6 +244,8 @@ void CS_RemoveListener(CS_Listener handle, CS_Status* status) { return cs::RemoveListener(handle, status); } +int CS_NotifierDestroyed(void) { return cs::NotifierDestroyed(); } + CS_Source* CS_EnumerateSources(int* count, CS_Status* status) { llvm::SmallVector buf; auto handles = cs::EnumerateSourceHandles(buf, status); diff --git a/src/cameraserver_cpp.cpp b/src/cameraserver_cpp.cpp index 70a9869be9..a19589fcba 100644 --- a/src/cameraserver_cpp.cpp +++ b/src/cameraserver_cpp.cpp @@ -9,6 +9,7 @@ #include "llvm/SmallString.h" +#include "Notifier.h" #include "SinkImpl.h" #include "SourceImpl.h" #include "Handle.h" @@ -424,16 +425,35 @@ void ReleaseSink(CS_Sink sink, CS_Status* status) { // Listener Functions // +void SetListenerOnStart(std::function onStart) { + Notifier::GetInstance().SetOnStart(onStart); +} + +void SetListenerOnExit(std::function onExit) { + Notifier::GetInstance().SetOnExit(onExit); +} + CS_Listener AddListener(std::function callback, int eventMask, bool immediateNotify, CS_Status* status) { - return 0; // TODO + int uid = Notifier::GetInstance().AddListener(callback, eventMask); + if (immediateNotify) { + // TODO + } + return Handle{uid, Handle::kListener}; } void RemoveListener(CS_Listener handle, CS_Status* status) { - // TODO + int uid = Handle{handle}.GetTypedIndex(Handle::kListener); + if (uid < 0) { + *status = CS_INVALID_HANDLE; + return; + } + Notifier::GetInstance().RemoveListener(uid); } +bool NotifierDestroyed() { return Notifier::destroyed(); } + // // Utility Functions //