Commit Graph

27170 Commits

Author SHA1 Message Date
Isaac Connor
615055e496 fix: default analysis overlay to off in zms streams
MonitorStream.js initialized analyse_frames = true, causing zone
overlays to always appear on stream start regardless of view settings.
Change default to false so watch view starts clean. The zone editor
view already explicitly enables analysis after monitor creation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:16:55 -05:00
Isaac Connor
995b60db80 fix: use int instead of unsigned int for zone pixel coordinates in zm_zone.cpp
The lo_x/lo_y/hi_x/hi_y variables were unsigned int despite being assigned
from signed int32 Vector2 members and compared against signed int dimensions.
This caused scattered (int) and (unsigned int) casts throughout CheckAlarms,
std_alarmedpixels, and Setup. Switching to int eliminates the casts, fixes
the signed/unsigned comparison warnings, and makes the -1 sentinel check in
std_alarmedpixels explicit rather than relying on unsigned wraparound.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:16:55 -05:00
Isaac Connor
a2bf848ce0 feat: expand ONVIF config_types with full device/media/imaging/PTZ queries
Add config type entries for Hostname, DNS, NTP, NetworkInterfaces,
Capabilities, Scopes, Services, VideoSources, VideoSourceConfigurations,
AudioSources, AudioSourceConfigurations, AudioEncoderConfigurations,
StreamUri, SnapshotUri, ImagingOptions, PTZConfigurations, PTZNodes,
PTZPresets, and PTZStatus.

- Support __PROFILE_TOKEN__ substitution in request bodies
- Add writable flag to config types and enforce it in set_config
- Add NTP set handler for SetNTP ONVIF command
- Organize config_types by ONVIF service (device, media, imaging, PTZ)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:16:55 -05:00
Isaac Connor
2013339b1e fix: clamp zone polygon extents to actual frame dimensions in CheckAlarms
When a camera reconnects at a different resolution than the zone polygons
were configured for, the polygon bounding box can exceed the frame
dimensions. This caused a heap buffer overflow in the memset that zeros
the bbox rows of the diff image, confirmed by Valgrind (invalid write
at 0 bytes past a 921600-byte / 1280x720 block).

- Validate delta_image buffer and dimensions at entry
- Clamp hi_y/hi_x to image height/width before memset and pixel loops
- Guard filter, blob, and HighlightEdges loops against empty rows where
  ranges[y].lo_x is -1 (would wrap to UINT_MAX as unsigned)
- Clamp extents in std_alarmedpixels independently for defense in depth

Warning() logs identify which zones need polygon reconfiguration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:16:55 -05:00
IgorA100
7d32aeff22 Removed styles for GridStack, since in version 12 styles are now generated dynamically (montage.css) 2026-02-23 18:16:55 -05:00
IgorA100
054764091a I created some slightly rounded corners for the streams, tables, and navigation block: .imageFeed, .nav, .fixed-table-body, id='videoFeedStream*' (skin.css) 2026-02-23 18:16:55 -05:00
IgorA100
62c4d1d4d8 Fix: Eslint (skin.js) 2026-02-23 18:07:49 -05:00
IgorA100
a27b6b4d36 Changed the style for ".monitor" a bit (watch.css) 2026-02-23 18:07:49 -05:00
IgorA100
643aa37ebc Added styles for id=liveStream* (skin.css) 2026-02-23 18:07:49 -05:00
IgorA100
386484a15c Merging and moving two identical functions monitorsSetScale from montage.js and watch.js to skin.js
And take into account "scrollTop" when performing scaleToFit
2026-02-23 18:07:49 -05:00
IgorA100
50f3d5690d Moving "monitorsSetScale" function from to skin.js (watch.js)
And Remove Event listener when restarting the stream
2026-02-23 18:07:49 -05:00
IgorA100
96b09329e5 Wrap "#dvrControls" and "#extButton" in a single DIV "#bottomBlock" for correct scaling in scale=Auto mode (watch.php) 2026-02-23 18:07:49 -05:00
IgorA100
5e13d85675 Moving "monitorsSetScale" function from montage.js to skin.js (montage.js) 2026-02-23 18:07:49 -05:00
Isaac Connor
a5fd6e063e feat: add --protocol mode to zmcontrol.pl for direct module testing
Add --protocol, --address, and --device CLI options that bypass the
database and socket machinery to load a control module directly and
execute a single command. This enables standalone testing of control
modules (e.g. ONVIF.pm get_config) without a running ZoneMinder
instance. Also update POD with full documentation and examples.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:07:49 -05:00
Isaac Connor
bc942f194b fix: update heartbeat during camera close and reconnect
avformat_close_input() can block for 75-90s on TCP retransmit timeout
when an RTSP camera becomes unresponsive, and the connect() retry loop
also lacks heartbeat updates. This causes zmwatch to kill zmc with a
stale heartbeat even though the process is actively reconnecting.

Add SetHeartbeatTime() calls before/after Close() and in the connect()
retry loop so zmwatch knows zmc is still alive during reconnection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:08 -05:00
Isaac Connor
13aa97a669 Add 4px border-radius to thumbnail img 2026-02-15 15:38:08 -05:00
Nic Boet
d27b565a8d fix: correct App::uses package path in CameraModel
App::uses('AppModel', 'CameraModel') tells CakePHP to look for AppModel
in a non-existent 'CameraModel' package. The correct second argument is
'Model', which points to app/Model/AppModel.php where the base class
actually lives.

This was likely a copy-paste error — every other model in the codebase
correctly uses App::uses('AppModel', 'Model'). The bug may go unnoticed
when another model loads AppModel first via CakePHP's autoloader, but
causes a fatal error if CameraModel is the first model resolved in a
request (e.g. hitting the camera models API endpoint directly).
2026-02-15 15:38:08 -05:00
IgorA100
886e8f8e06 Fix: Stop executing "streamQuery" if "zmsBroke" (event.js)
For example, we viewed an event, but it was then deleted or destroyed.
Currently, queries will continue to run indefinitely.
2026-02-15 15:38:07 -05:00
IgorA100
d7bfbbf137 Color input[type='checkbox'], input[type='radio'] on dark skin (skin.css) 2026-02-15 15:38:07 -05:00
IgorA100
a856cf7a97 Fix: Color of some buttons on a dark skin (skin.css) 2026-02-15 15:38:07 -05:00
Isaac Connor
66f25b8027 fix: warn when zone coordinates extend beyond image dimensions
Before limitPoints() silently clamps coordinates, check if any zone
point exceeds the monitor's image dimensions and show a warning icon
with tooltip in the zones table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
Isaac Connor
f813658f6d fix: skip V4L2 AutoSelectFormat in non-capture mode
AutoSelectFormat() opens the video device to enumerate formats, but
non-capture processes like zms (QUERY mode) should never need to access
the device. Guard the call with the existing capture flag so zms no
longer fails with "No such file or directory" errors when running under
Apache with systemd PrivateDevices=yes.

Also downgrade the fallback message from Error to Warning since YUYV
fallback is a recoverable condition, and add a hint about PrivateDevices
to the ENOENT error in AutoSelectFormat for cases where capture mode
legitimately can't open the device.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
Isaac Connor
f4a7c1d42b fix: remove file_exists() gate on /dev/video in settings modal
Debian Forky's stock apache2 systemd unit sets PrivateDevices=yes,
giving Apache its own /dev namespace without video devices. This
caused file_exists('/dev/video0') to return false even though the
device exists and www-data is in the video group.

Remove the file_exists() pre-check and just attempt v4l2-ctl directly.
When it fails, provide targeted diagnostics: missing v4l2-ctl, systemd
PrivateDevices detected, or generic permission error with the running
username.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
Isaac Connor
66628ae163 perf: replace sleep polling with condition wait in Event::Run()
The event thread was sleeping 33ms (ZM_SAMPLE_RATE) between checks for
packet decoded/analyzed status. Replace with a condition variable wait
on the packet itself, using a 2ms timeout as safety net for the race
between flag check and wait entry.

Add packet->notify_all() at every site where decoded or analyzed is set
to true, so the event thread wakes up near-instantly. Add wait_for()
to ZMPacketLock to support timed waits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
Isaac Connor
b196585b0d perf: reduce lock contention in PacketQueue
Remove per-packet ZMPacketLock trylock() from clearPackets() scan loops
and queuePacket() GOP deletion — the iterator check is sufficient since
threads only access packets through their own shared_ptr.

Add a monotonic uint64_t queue_index to ZMPacket, assigned on enqueue.
clearPackets() now finds the earliest iterator position with a single
min() over the 2-3 iterators, then uses one integer comparison per
scanned packet instead of walking all iterators per packet.

Defer packet destruction outside the mutex in both clearPackets() and
queuePacket() by collecting removed shared_ptrs into a local vector
and releasing the lock before they are destroyed.

Raise per-packet deletion Debug(1) to Debug(4) in both paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
Pliable Pixels
e97a37df29 fix: preserve legacy execlp() behavior for commands without % tokens
If EventStartCommand/EventEndCommand contains a % character, use the
new token substitution (%EID%, %MID%, %EC%) with sh -c execution.
Otherwise, fall back to the original execlp() behavior that passes
event_id and monitor_id as $1 and $2, so existing installs are not
broken.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
Pliable Pixels
cf3f44a466 fix: shell-escape %EC% token to prevent command injection
cause can include trigger_data->trigger_cause (writable via zmtrigger
over the network) and zone labels (user-configured). Without escaping,
shell metacharacters in cause would be interpreted by sh -c.

Wraps cause in single quotes (with embedded single-quote escaping)
before substitution. %EID% and %MID% are safe as they are always
numeric from std::to_string.

Note on backward compatibility: the old execlp() passed event_id and
monitor_id as argv[1]/argv[2]. This PR intentionally does not preserve
that behavior — the old execlp() treated the entire command string as
an executable path, making it impossible to pass arguments, so any
working setup was already a simple path with no args. Users should
migrate to %EID%/%MID% tokens which are more explicit and flexible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
Pliable Pixels
7f9fc2ee2f feat: support token substitution in EventStartCommand/EventEndCommand
Replace execlp() with execl("/bin/sh") and substitute %EID%, %MID%,
and %EC% tokens before execution. This allows users to pass arguments
directly in the command string, e.g.:

  /path/to/zm_detect.py -c /etc/zm/config.yml -e %EID% -m %MID% -r "%EC%" -n

Previously execlp() treated the entire command string as the executable
path, making it impossible to pass arguments without a wrapper script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:38:07 -05:00
IgorA100
90891e3456 Fix: Clear "srcObject" for "Stream" and adjust Janus connection timeout (MonitorStream.js) 2026-02-15 15:38:07 -05:00
Isaac Connor
e1effdb1c7 fix: restore filter Name and UserId after save/reload
The post-save redirect included filter query params in the URL, which
caused the view to populate from request data instead of loading from
the database. Since the querystring omitted Name and UserId, both
fields appeared empty/wrong after reload. Remove the querystring from
the redirect so the view loads the saved filter from the database.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 12:33:46 -05:00
Isaac Connor
d8f9e13804 feat: update Controls entries to use unified ONVIF.pm module
Add new 'ONVIF' Controls entry with corrected brightness/contrast
ranges (0-100). Update legacy entries (ONVIF Camera, Netcat ONVIF,
Reolink RLC-423/411/420) to use Protocol 'ONVIF' instead of the
old per-vendor module names (onvif, Netcat, Reolink).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 12:17:39 -05:00
Isaac Connor
409a9391b7 feat: add get_config/set_config to ONVIF control module
Add configuration read/write support following the Uniview.pm pattern.
get_config() queries five ONVIF categories (DeviceInformation, DateTime,
ImagingSettings, VideoEncoderConfiguration, Profiles) via SOAP and returns
parsed XML as hashes. set_config() supports writing ImagingSettings
(Brightness/Contrast/Saturation/Sharpness/ColorSaturation) and DateTime,
treating other categories as read-only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 12:10:10 -05:00
Isaac Connor
a329b34486 fix: repair imaging bugs in onvif, Reolink, Netcat, and TapoC520WS control modules
- onvif.pm: irisAbsClose checked lowercase 'brightness' so getCamParams
  was never called on decrease, sending stale/zero values
- Reolink.pm: same brightness case bug, plus whiteAbsIn and whiteAbsOut
  built the SOAP message but never called sendCmd — contrast changes
  were silently discarded
- Netcat.pm: same brightness case bug, plus debug() call should be Debug()
- TapoC520WS_ONVIF.pm: same brightness case bug

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 11:48:45 -05:00
Isaac Connor
d59c38aa51 Tell agents to run with sudo -u www-data 2026-02-13 11:48:29 -05:00
Isaac Connor
57054cdd5b feat: add unified ONVIF.pm control module replacing four per-vendor copies
Single ONVIF SOAP/PTZ implementation that replaces onvif.pm, Reolink.pm,
Netcat.pm, and TapoC520WS_ONVIF.pm. All state is kept in instance variables
(no package globals), SSL verification falls back automatically, and
sendCmd routes to the correct ONVIF service endpoint per command type.

Bug fixes from the originals:
- Brightness decrease checked lowercase 'brightness' (never matched)
- whiteAbsIn/whiteAbsOut in Reolink/Netcat/TapoC520WS never called sendCmd
- Imaging commands sent to PTZ endpoint instead of /onvif/imaging
- Reboot sent to PTZ endpoint instead of /onvif/device_service
- Package globals leaked state between camera instances

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 11:46:34 -05:00
Isaac Connor
025dca865f feat: merge MacVendors.json with zm_utils, adding 39 new MAC prefixes
Merge MAC vendor database from zm_utils into ZoneMinder. Adds entries for
Reolink, Dahua, Hanwha, Buffalo, Google, Sagemcom, and many additional
HikVision/Ubiquiti/Grandstream MACs. Sorted by vendor type for readability.
Removes duplicates from zm_utils source. Preserves ZM-only Uniview and
TP-Link entries. Keeps IC Realtime Dahua control mapping that zm_utils lost.
Updates Vivotek with ePTZ control and normalizes D-Link type name.

34 -> 73 entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 10:30:53 -05:00
Isaac Connor
d74913e3bf fix: resolve multiple bugs in AxisV2 control module and add config/probe support
Fix critical crash in open() where guess_credentials() boolean return was
treated as URI object. Rewrite open() to use base class get_realm() for
auth discovery, matching the Uniview/HikVision pattern.

Bug fixes:
- focusRelNear: remove double-$self in recursive call, use instance var
- focusRelFar: fix copy-paste error calling focusRelNear instead of self,
  remove double-$self, use instance var
- focusAbs: fix .cgi.cgi typo in opticssetup URL
- Replace package-global $use_optics with $$self{use_optics} to prevent
  state leaking between camera instances
- Remove unused our $uri, use ZoneMinder::Config, use URI imports
- Move use LWP::UserAgent to module-level imports

New features:
- get_config/set_config via Axis param.cgi API
- probe() for network camera discovery
- rtsp_url() returning standard Axis RTSP path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 10:10:49 -05:00
Isaac Connor
7212556f71 Tell Claude to run perl tests with sudo 2026-02-13 10:10:14 -05:00
Isaac Connor
37e377acf4 feat: implement LAPI config get/set and fix PTZ in Uniview control module
Replace empty get_config/set_config stubs with working implementations
using Uniview's native LAPI JSON REST API. Modernize open() to use base
class guess_credentials() and get_realm() instead of duplicated auth
logic. Rename put() to PutCmd() to fix broken PTZ methods. Remove
package-level globals in favor of instance variables. Add lapi_get/
lapi_put helpers, probe via LAPI with ISAPI fallback, and rtsp_url().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 09:58:00 -05:00
Isaac Connor
f127359fe0 feat: add network probe function for Uniview cameras
Add probeUniview() that queries the camera's LAPI for device model,
name, resolution, and codec. Uses RTSP main stream URL format
rtsp://ip:554/media/video1 and LAPI snapshot endpoint for thumbnails.
Includes OUI alias probeZhejiangUniviewTechnologiesCoLtd for IEEE
vendor name matching. Adds all 4 registered Uniview MAC OUI prefixes
(48ea63, 6cf17e, 88263f, c47905) to MacVendors.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 09:17:58 -05:00
Isaac Connor
0882a3ad1e fix: resolve Event::Run thread hang preventing zmc clean shutdown
Event::Run could block indefinitely in PacketQueue methods during normal
event closing (closeEvent from analysis thread), because their wait
predicates only check deleting/zm_terminate, not Event's terminate_ flag.

Three changes fix this:
- get_packet_no_wait: return immediately when iterator at end instead of
  blocking on condition variable (makes it truly non-blocking)
- Event::Run: use increment_it(wait=false) since deletePacket can advance
  the iterator to end() during AddPacket_ without the queue lock
- Event::Stop: call packetqueue->notify_all() to wake timed waits so
  Run() checks terminate_ promptly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:00:59 -05:00
Isaac Connor
7b71ad2149 fix: pass analysis=true in zone view stream URL on initial load
The zone view enables analysis frames by default but the initial image
src and the rebuilt streaming URL were missing the analysis query param.
Add analysis=true to the getStreamHTML call in zone.php and preserve it
in MonitorStream.start() when switching from single to jpeg mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 18:12:06 -05:00
Isaac Connor
3e067f18b2 feat: store zone coordinates as percentages for resolution independence (src only)
Cherry-picked src/ changes from edge branch commit 17450d122.
Adds ParsePercentagePolygon() to convert percentage-based zone coordinates
to pixel values using monitor dimensions. Zone::Load() now checks the Units
column to dispatch between legacy pixel parsing and percentage-based parsing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 18:06:56 -05:00
Isaac Connor
31aa65bd11 perf: split delta/mask buffers in CheckAlarms to eliminate full-image copy
Instead of copying the entire delta_image into the per-zone mask buffer
every frame (4MB memcpy for 4MP), allocate a persistent grayscale mask
and only zero the polygon bounding-box rows.  alarmedpixels_row now
reads from delta_image (const, read-only) and writes threshold results
to the separate mask buffer.

Key changes:
- alarmedpixels_row takes separate pdelta (read) and pmask (write) pointers
- std_alarmedpixels accepts both delta_image (const) and mask_image
- CheckAlarms allocates mask with explicit linesize == width to match
  filter/blob pointer arithmetic (avoids FFALIGN padding mismatch)
- Full buffer zero on allocation; bbox-only zero on reuse
- No functional change to filter, blob, or HighlightEdges stages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 17:41:50 -05:00
Isaac Connor
2e2e9576ac refactor: move format_absolute_time_iso8601 from zm_monitor_onvif to zm_time
Move the ISO 8601 time formatting function to zm_time.cpp/h so it is
reusable and not duplicated. Remove the local copies from
zm_monitor_onvif.cpp (was static) and tests/zm_onvif_renewal.cpp
(was a copy for testing). Both now use the shared declaration from
zm_time.h.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 16:53:39 -05:00
Isaac Connor
ca22ae3737 Remove fatal version check. Just error. 2026-02-12 16:53:29 -05:00
Isaac Connor
8b3ed97fa4 perf: auto-vectorize alarmedpixels loop for SIMD on all platforms
Restructure std_alarmedpixels inner loop so GCC/Clang can auto-vectorize
it at -O2/-O3. The compiler now emits 16-byte SIMD (SSE2/NEON) processing
16 pixels per iteration instead of 1.

Three changes enable this:
- Extract inner loop into static alarmedpixels_row() with __restrict__
  on function parameters, giving the compiler a strong no-alias guarantee
- Use branchless bitwise AND instead of short-circuit && to avoid
  branches that block vectorization
- Remove per-row Debug(7) call that clobbered memory from the compiler's
  perspective, invalidating pointer analysis

The original hand-written SSE2 ASM (removed in 2011, commit 8e9ccfe1e)
had alignment restrictions and didn't use per-row polygon ranges. This
approach is portable, maintainable, and achieves equivalent throughput.

GCC confirms: "loop vectorized using 16 byte vectors"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 14:36:13 -05:00
Isaac Connor
bf185cf4b4 fix: replace global include_directories with target_include_directories
Replace 13 global include_directories() calls with list(APPEND
ZM_INCLUDE_DIRS ...) in root CMakeLists.txt, then apply them scoped
to the zm static library target via target_include_directories() in
src/CMakeLists.txt.

Previously all library include paths (MySQL, OpenSSL, curl, zlib, JPEG,
pthread, PCRE, VLC, VNC, libunwind, GnuTLS, Mosquitto) leaked into
vendored deps (RtspServer, CxxUrl, bcrypt). Now only the zm target and
its dependents see them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 13:32:23 -05:00
Isaac Connor
7df902abdc fix: resolve "no url in button" when adding detected cameras
- Provide default rtsp://<ip>/ monitor entry for cameras discovered
  via ARP that lack a vendor-specific probe function, so they always
  have a URL for the Add button
- Only render the Add button and populate ProbeResults when url is
  non-empty, preventing the "No url in button" alert
- Fix curl_getinfo() called after curl_close() which broke HTTP
  response body parsing in probe functions
- Add missing break in import switch case to prevent fall-through
  to default warning

maybe fixes #4613

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 12:55:32 -05:00
Isaac Connor
56a9c53948 fix: scope add_definitions to zm target with target_compile_definitions
Replace global add_definitions(-DWITH_GSOAP) and add_definitions(-DZM_STRIP_NEON=1)
with target_compile_definitions on the zm static library target. These defines are
only consumed by src/ files but were leaking into all vendored deps (RtspServer,
CxxUrl, bcrypt, etc).

WITH_GSOAP is PUBLIC on zm since the executables linking zm include headers
that check it. ZM_STRIP_NEON is PRIVATE since it's only used in zm_image.cpp.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 12:52:10 -05:00