From f362efd23e95f4631fd7f16bdde3fdbe68bcef6f Mon Sep 17 00:00:00 2001 From: nic <31355096+nabbi@users.noreply.github.com> Date: Sun, 4 Oct 2026 07:52:10 -0500 Subject: [PATCH] fix: make the Control view send PTZ to the monitor's own server (#5172) The standalone Control view's controlCmd() still had its 2020 (control, event, xtell, ytell) signature, but ptzControls() binds the buttons as data-on-mousedown/mouseup handlers that receive the event, and it read the monitor id with $j('#mid').getAttribute(), which jQuery objects don't have. Every PTZ button threw before sending anything. Port the watch view's event-based controlCmd(): the button's value is the command, mouseup sends moveStop, xge/yge come from data-xtell and data-ytell. Send requests to the monitor's server (monitorUrl, from Monitor::UrlToIndex() as on the watch view) instead of this server: on a multi-server install only the owning server's zmcontrol.pl can drive the camera, and relaying through Monitor::sendControlCommand() puts the raw zmcontrol arguments into a daemonControl API path, which fails. Co-authored-by: Claude Opus 5.5 --- web/skins/classic/views/js/control.js | 45 +++++++++++++---------- web/skins/classic/views/js/control.js.php | 6 +++ 2 files changed, 31 insertions(+), 20 deletions(-) create mode 100644 web/skins/classic/views/js/control.js.php diff --git a/web/skins/classic/views/js/control.js b/web/skins/classic/views/js/control.js index 8a4d2d257..b288fb12b 100644 --- a/web/skins/classic/views/js/control.js +++ b/web/skins/classic/views/js/control.js @@ -1,7 +1,7 @@ var form = $j('#controlForm'); function controlReq(data) { - $j.getJSON(zmAuth.appendTo(thisUrl + '?view=request&request=control'), data) + $j.getJSON(zmAuth.appendTo(monitorUrl + '?view=request&request=control'), data) .done(getControlResponse) .fail(logAjaxFail); } @@ -16,39 +16,44 @@ function getControlResponse(respObj, respText) { } } -function controlCmd( control, event, xtell, ytell ) { - var mid = $j('#mid').getAttribute('value'); +// Bound by ptzControls() as data-on-mousedown/mouseup (or data-on-click): +// the button's value is the command, releasing a continuous move stops it. +function controlCmd(event) { + const button = event.currentTarget || event.target; + const control = (event.type == 'mouseup') ? 'moveStop' : button.getAttribute('value'); + const xtell = button.getAttribute('data-xtell'); + const ytell = button.getAttribute('data-ytell'); + const data = {}; - if ( event && (xtell || ytell) ) { - var data = {}; - var target = $j(event.target); - var offset = target.offset(); - var width = target.width(); - var height = target.height(); + if (xtell || ytell) { + const target = $j(button); + const offset = target.offset(); + const width = target.width(); + const height = target.height(); - var x = event.pageX - offset.left; - var y = event.pageY - offset.top; + const x = event.pageX - offset.left; + const y = event.pageY - offset.top; - if ( xtell ) { - var xge = parseInt( (x*100)/width ); - if ( xtell == -1 ) { + if (xtell) { + let xge = parseInt((x*100)/width); + if (xtell == -1) { xge = 100 - xge; - } else if ( xtell == 2 ) { + } else if (xtell == 2) { xge = 2*(50 - xge); } data.xge = xge; } - if ( ytell ) { - var yge = parseInt( (y*100)/height ); - if ( ytell == -1 ) { + if (ytell) { + let yge = parseInt((y*100)/height); + if (ytell == -1) { yge = 100 - yge; - } else if ( ytell == 2 ) { + } else if (ytell == 2) { yge = 2*(50 - yge); } data.yge = yge; } } - data.id = mid; + data.id = $j('#mid').val(); data.control = control; controlReq(data); } diff --git a/web/skins/classic/views/js/control.js.php b/web/skins/classic/views/js/control.js.php new file mode 100644 index 000000000..7ca01dfa8 --- /dev/null +++ b/web/skins/classic/views/js/control.js.php @@ -0,0 +1,6 @@ + +// PTZ requests go to the server that runs the monitor, as on the watch view: +// only that server's zmcontrol.pl can drive the camera. +var monitorUrl = 'UrlToIndex(ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>';