cameracoordinator

package module
v0.0.0-...-538b93e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 25, 2026 License: GPL-3.0 Imports: 22 Imported by: 0

README

CameraCoordinator

Overview

CameraCoordinator is a Golang based library and daemon that detects when a webcam is turned on or off on Linux. On these events, a DBus event is triggered and scripts can be optionally executed. This allows for things such as:

  • Automatically controlling USB-based stream lights such as the Logitech Litra lights via the autolight program.
  • Sending a notification that the camera has been turned on to the desktop via a custom script.

How it works under the hood

By default, we use eBPF to trace specific kernel functions (vb2_ioctl_streamon/vb2_ioctl_streamoff and others) to determine when cameras are turned on or off. Upon detection of camera on/off, a signal is then sent via dbus's system bus and/or a script is executed according to configuration.

Run

CameraCoordinator is a root daemon as it needs to insert a BPF program into the kernel.

Basic usage:

sudo ./camera-coordinator

Run a script when the camera is detected

sudo ./camera-coordinator -on-script path-to-on-script.sh -off-script path-to-off-script.sh

Notes:

  • Running as root (sudo) is required for attaching the eBPF program.

Build

Clone the repository and build the daemon binary:

git clone https://github.com/your-org/CameraCoordinator.git
cd CameraCoordinator
go generate ./... # Optional. Can try without it first.
go build -o camera-coordinator ./cmd/camera-coordinator

The go generate ./... step will compile the BPF code. This requires clang and llvm to be installed on your system.

Autolight

Autolight is an user daemon that's bundled in this repo that listens to the DBus signal for camera on/off events and will turn on/off Logitech Litra lights.

Running Autolight together with CameraCoordinator as two background services will allow the lights to turn automatically on and off based on the state of the webcam:

Logitech Litra Glow automatically turning on and off when web cam turns on and off on Linux

Documentation

Index

Constants

View Source
const (
	// DBusObjectPath is the object path used when emitting signals.
	DBusObjectPath = dbus.ObjectPath("/io/github/shuhaowu/CameraCoordinator")

	// DBusInterface is the DBus interface name for signals emitted by the
	// notifier.
	DBusInterface = "io.github.shuhaowu.CameraCoordinator"

	// DBusMemberName is the member name for signals emitted by the notifier.
	DBusMemberName = "CameraEvent"

	// DBusSignalName is the fully-qualified signal name (interface.Signal).
	DBusSignalName = DBusInterface + "." + DBusMemberName
)
View Source
const VIDIOC_QUERYCAP = 0x80685600

https://github.com/torvalds/linux/blob/d79526b89571ae447c1a5cfd3d627efa07098348/include/uapi/linux/videodev2.h#L2730 Number is derived from _IOR('V', 0, struct v4l2_capability) in the kernel headers. This is AI generated and it seems to work. Linux should also be ABI stable.

Variables

This section is empty.

Functions

func V4L2DiscoverDevices

func V4L2DiscoverDevices() (map[string]V4L2Capability, error)

Types

type CameraCoordinator

type CameraCoordinator struct {
	// contains filtered or unexported fields
}

CameraCoordinator aggregates one or more detector event streams into a single logical view of camera state. It deduplicates events across detectors (emits one RecordingOn when the first detector goes active for a device, one RecordingOff when the last one goes inactive) and fans out the resulting events to a set of output channels provided at construction time.

func NewCameraCoordinator

func NewCameraCoordinator(outputs []chan CameraEvent) *CameraCoordinator

NewCameraCoordinator creates a CameraCoordinator that fans out deduplicated camera events to the supplied output channels.

func (*CameraCoordinator) Run

func (c *CameraCoordinator) Run(ctx context.Context, events <-chan CameraEvent) error

Run monitors the supplied events channel for camera on/off events and statefully emits coordinator-level on/off events to the output channels provided at construction time.

The caller is responsible for starting the detectors and providing their combined events channel. Run returns when ctx is cancelled or events is closed (i.e. all detectors have finished).

A camera on event is generated on the first event from the detectors for that device. A camera off event is generated on the last event from the detectors for that device.

The events in the buffer are not guaranteed to be processed on context cancellation.

Run can only be called once.

type CameraDetector

type CameraDetector interface {
	Run(context.Context, chan<- CameraEvent) error
	Name() string
}

type CameraEvent

type CameraEvent struct {
	// The string identifying the detector that emitted this event. This is useful for debugging.
	Detector string

	// The type of the event.
	Type CameraEventType

	// The video device file associated with this event, e.g. "video0".
	VideoDevice string

	// The card name from the V4L2 capability. Populated by the coordinator;
	// may be empty when the event originates directly from a detector or in tests.
	Card string

	// The bus info from the V4L2 capability. Populated by the coordinator;
	// may be empty when the event originates directly from a detector or in tests.
	BusInfo string
}

type CameraEventType

type CameraEventType uint32
const (
	// Synchronize this with camera_event_type in bpf/camera_detector_vb2_ioctl.bpf.c
	CameraEventRecordingOn  CameraEventType = 1
	CameraEventRecordingOff CameraEventType = 2
)

func (CameraEventType) String

func (t CameraEventType) String() string

type DBusNotifier

type DBusNotifier struct{}

DBusNotifier is a Notifier that emits a CameraEvent signal on the system DBus bus whenever a camera recording starts or stops.

func NewDBusNotifier

func NewDBusNotifier() *DBusNotifier

NewDBusNotifier creates a DBusNotifier.

func (*DBusNotifier) Run

func (d *DBusNotifier) Run(ctx context.Context, events <-chan CameraEvent) error

Run connects to the system bus, claims the well-known name, and listens for CameraEvents, emitting a DBus signal for each one until ctx is cancelled or the events channel is closed.

type DBusNotifierSignalBody

type DBusNotifierSignalBody struct {
	Detector    string
	Type        uint32
	VideoDevice string
	Card        string
	BusInfo     string
}

DBusNotifierSignalBody is the structured payload carried by every CameraEvent DBus signal.

type EBPFVb2IoctlStreamDetector

type EBPFVb2IoctlStreamDetector struct {
	// contains filtered or unexported fields
}

func NewEBPFVb2IoctlStreamDetector

func NewEBPFVb2IoctlStreamDetector() *EBPFVb2IoctlStreamDetector

func (*EBPFVb2IoctlStreamDetector) Name

func (*EBPFVb2IoctlStreamDetector) Run

type Notifier

type Notifier interface {
	// Run reads from events and processes them until ctx is cancelled or the
	// channel is closed. It is designed to be called in a goroutine.
	Run(ctx context.Context, events <-chan CameraEvent) error
}

Notifier processes camera events from a CameraCoordinator. Implementations are intended to be started in a background goroutine via Run.

type PrintNotifier

type PrintNotifier struct{}

PrintNotifier is a Notifier that logs camera recording on/off events using slog.

func NewPrintNotifier

func NewPrintNotifier() *PrintNotifier

func (*PrintNotifier) Run

func (p *PrintNotifier) Run(ctx context.Context, events <-chan CameraEvent) error

Run logs each CameraEvent until ctx is cancelled or events is closed.

type ScriptNotifier

type ScriptNotifier struct {
	// contains filtered or unexported fields
}

ScriptNotifier is a Notifier that executes shell scripts on camera recording on/off events.

func NewScriptNotifier

func NewScriptNotifier(cfg ScriptNotifierConfig) *ScriptNotifier

NewScriptNotifier creates a ScriptNotifier with the given configuration.

func (*ScriptNotifier) Run

func (s *ScriptNotifier) Run(ctx context.Context, events <-chan CameraEvent) error

Run listens for CameraEvents and executes the configured scripts until ctx is cancelled or the events channel is closed.

type ScriptNotifierConfig

type ScriptNotifierConfig struct {
	// OnScript is the path to the script to run when recording starts.
	// If empty, no script is run.
	OnScript string

	// OffScript is the path to the script to run when recording stops.
	// If empty, no script is run.
	OffScript string
	// BaseDir is the directory where the JSON config file was located. If
	// a script path starts with "./" it will be resolved relative to this
	// directory.
	BaseDir string
}

ScriptNotifierConfig holds the paths to scripts to run on camera events.

type V4L2Capability

type V4L2Capability struct {
	Driver       [16]uint8
	Card         [32]uint8
	BusInfo      [32]uint8
	Version      uint32
	Capabilities uint32
	DeviceCaps   uint32
	Reserved     [3]uint32
	// contains filtered or unexported fields
}

https://docs.kernel.org/userspace-api/media/v4l/vidioc-querycap.html#c.V4L.V4L2Capability

func V4L2DeviceCapability

func V4L2DeviceCapability(filename string) (V4L2Capability, error)

QueryDevice returns V4L2 capability information for the given device path.

func (V4L2Capability) BusInfoString

func (c V4L2Capability) BusInfoString() string

func (V4L2Capability) CardString

func (c V4L2Capability) CardString() string

func (V4L2Capability) DriverString

func (c V4L2Capability) DriverString() string

func (V4L2Capability) HasCapabilities

func (c V4L2Capability) HasCapabilities(caps ...V4L2CapabilityCapabilities) bool

func (V4L2Capability) VersionString

func (c V4L2Capability) VersionString() string

type V4L2CapabilityCapabilities

type V4L2CapabilityCapabilities uint32
const (
	V4L2CapVideoCapture       V4L2CapabilityCapabilities = 0x00000001
	V4L2CapVideoCaptureMplane V4L2CapabilityCapabilities = 0x00001000
	V4L2CapVideoOutput        V4L2CapabilityCapabilities = 0x00000002
	V4L2CapVideoOutputMplane  V4L2CapabilityCapabilities = 0x00002000
	V4L2CapVideoM2M           V4L2CapabilityCapabilities = 0x00008000
	V4L2CapVideoM2MMplane     V4L2CapabilityCapabilities = 0x00004000
	V4L2CapVideoOverlay       V4L2CapabilityCapabilities = 0x00000004
	V4L2CapVbiCapture         V4L2CapabilityCapabilities = 0x00000010
	V4L2CapVbiOutput          V4L2CapabilityCapabilities = 0x00000020
	V4L2CapSlicedVbiCapture   V4L2CapabilityCapabilities = 0x00000040
	V4L2CapSlicedVbiOutput    V4L2CapabilityCapabilities = 0x00000080
	V4L2CapRdsCapture         V4L2CapabilityCapabilities = 0x00000100
	V4L2CapVideoOutputOverlay V4L2CapabilityCapabilities = 0x00000200
	V4L2CapHwFreqSeek         V4L2CapabilityCapabilities = 0x00000400
	V4L2CapRdsOutput          V4L2CapabilityCapabilities = 0x00000800
	V4L2CapTuner              V4L2CapabilityCapabilities = 0x00010000
	V4L2CapAudio              V4L2CapabilityCapabilities = 0x00020000
	V4L2CapRadio              V4L2CapabilityCapabilities = 0x00040000
	V4L2CapModulator          V4L2CapabilityCapabilities = 0x00080000
	V4L2CapSdrCapture         V4L2CapabilityCapabilities = 0x00100000
	V4L2CapExtPixFormat       V4L2CapabilityCapabilities = 0x00200000
	V4L2CapSdrOutput          V4L2CapabilityCapabilities = 0x00400000
	V4L2CapMetaCapture        V4L2CapabilityCapabilities = 0x00800000
	V4L2CapReadwrite          V4L2CapabilityCapabilities = 0x01000000
	V4L2CapEdid               V4L2CapabilityCapabilities = 0x02000000
	V4L2CapStreaming          V4L2CapabilityCapabilities = 0x04000000
	V4L2CapMetaOutput         V4L2CapabilityCapabilities = 0x08000000
	V4L2CapTouch              V4L2CapabilityCapabilities = 0x10000000
	V4L2CapIoMc               V4L2CapabilityCapabilities = 0x20000000
	V4L2CapDeviceCaps         V4L2CapabilityCapabilities = 0x80000000
)

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL