Skip to content

Commit 4c5cae6

Browse files
Add mouse socket listener
1 parent 23b27d9 commit 4c5cae6

File tree

2 files changed

+173
-33
lines changed

2 files changed

+173
-33
lines changed

obs-zoom-to-mouse.lua

Lines changed: 167 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ local ffi = require("ffi")
99
local VERSION = "1.0"
1010
local CROP_FILTER_NAME = "obs-zoom-to-mouse-crop"
1111

12+
local socket_available, socket = pcall(require, "ljsocket")
13+
local socket_server = nil
14+
local socket_mouse = nil
15+
1216
local source_name = ""
1317
local source = nil
1418
local sceneitem = nil
@@ -63,6 +67,9 @@ local monitor_override_sx = 0
6367
local monitor_override_sy = 0
6468
local monitor_override_dw = 0
6569
local monitor_override_dh = 0
70+
local use_socket = false
71+
local socket_port = 0
72+
local socket_poll = 1000
6673
local debug_logs = false
6774

6875
local ZoomState = {
@@ -149,27 +156,32 @@ end
149156
function get_mouse_pos()
150157
local mouse = { x = 0, y = 0 }
151158

152-
if ffi.os == "Windows" then
153-
if win_point and ffi.C.GetCursorPos(win_point) ~= 0 then
154-
mouse.x = win_point[0].x
155-
mouse.y = win_point[0].y
156-
end
157-
elseif ffi.os == "Linux" then
158-
if x11_lib ~= nil and x11_display ~= nil and x11_root ~= nil and x11_mouse ~= nil then
159-
if x11_lib.XQueryPointer(x11_display, x11_root, x11_mouse.root_win, x11_mouse.child_win, x11_mouse.root_x, x11_mouse.root_y, x11_mouse.win_x, x11_mouse.win_y, x11_mouse.mask) ~= 0 then
160-
mouse.x = tonumber(x11_mouse.win_x[0])
161-
mouse.y = tonumber(x11_mouse.win_y[0])
159+
if socket_mouse ~= nil then
160+
mouse.x = socket_mouse.x
161+
mouse.y = socket_mouse.y
162+
else
163+
if ffi.os == "Windows" then
164+
if win_point and ffi.C.GetCursorPos(win_point) ~= 0 then
165+
mouse.x = win_point[0].x
166+
mouse.y = win_point[0].y
162167
end
163-
end
164-
elseif ffi.os == "OSX" then
165-
if osx_lib ~= nil and osx_nsevent ~= nil and osx_mouse_location ~= nil then
166-
local point = osx_mouse_location(osx_nsevent.class, osx_nsevent.sel)
167-
mouse.x = point.x
168-
if monitor_info ~= nil then
169-
if monitor_info.display_height > 0 then
170-
mouse.y = monitor_info.display_height - point.y
171-
else
172-
mouse.y = monitor_info.height - point.y
168+
elseif ffi.os == "Linux" then
169+
if x11_lib ~= nil and x11_display ~= nil and x11_root ~= nil and x11_mouse ~= nil then
170+
if x11_lib.XQueryPointer(x11_display, x11_root, x11_mouse.root_win, x11_mouse.child_win, x11_mouse.root_x, x11_mouse.root_y, x11_mouse.win_x, x11_mouse.win_y, x11_mouse.mask) ~= 0 then
171+
mouse.x = tonumber(x11_mouse.win_x[0])
172+
mouse.y = tonumber(x11_mouse.win_y[0])
173+
end
174+
end
175+
elseif ffi.os == "OSX" then
176+
if osx_lib ~= nil and osx_nsevent ~= nil and osx_mouse_location ~= nil then
177+
local point = osx_mouse_location(osx_nsevent.class, osx_nsevent.sel)
178+
mouse.x = point.x
179+
if monitor_info ~= nil then
180+
if monitor_info.display_height > 0 then
181+
mouse.y = monitor_info.display_height - point.y
182+
else
183+
mouse.y = monitor_info.height - point.y
184+
end
173185
end
174186
end
175187
end
@@ -978,6 +990,56 @@ function on_timer()
978990
end
979991
end
980992

993+
function on_socket_timer()
994+
if not socket_server then
995+
return
996+
end
997+
998+
repeat
999+
local data, status = socket_server:receive_from()
1000+
if data then
1001+
local sx, sy = data:match("(-?%d+) (-?%d+)")
1002+
if sx and sy then
1003+
local x = tonumber(sx, 10)
1004+
local y = tonumber(sy, 10)
1005+
if not socket_mouse then
1006+
log("Socket server client connected")
1007+
socket_mouse = { x = x, y = y }
1008+
else
1009+
socket_mouse.x = x
1010+
socket_mouse.y = y
1011+
end
1012+
end
1013+
elseif status ~= "timeout" then
1014+
error(status)
1015+
end
1016+
until data == nil
1017+
end
1018+
1019+
function start_server()
1020+
if socket_available then
1021+
local address = socket.find_first_address("*", socket_port)
1022+
1023+
socket_server = socket.create("inet", "dgram", "udp")
1024+
if socket_server ~= nil then
1025+
socket_server:set_option("reuseaddr", 1)
1026+
socket_server:set_blocking(false)
1027+
socket_server:bind(address, socket_port)
1028+
obs.timer_add(on_socket_timer, socket_poll)
1029+
log("Socket server listening on port " .. socket_port .. "...")
1030+
end
1031+
end
1032+
end
1033+
1034+
function stop_server()
1035+
if socket_server ~= nil then
1036+
log("Socket server stopped")
1037+
socket_server:close()
1038+
socket_server = nil
1039+
socket_mouse = nil
1040+
end
1041+
end
1042+
9811043
function set_crop_settings(crop)
9821044
if crop_filter ~= nil and crop_filter_settings ~= nil then
9831045
-- Call into OBS to update our crop filter with the new settings
@@ -1018,6 +1080,7 @@ function on_settings_modified(props, prop, settings)
10181080
-- Show/Hide the settings based on if the checkbox is checked or not
10191081
if name == "use_monitor_override" then
10201082
local visible = obs.obs_data_get_bool(settings, "use_monitor_override")
1083+
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_label"), not visible)
10211084
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_x"), visible)
10221085
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_y"), visible)
10231086
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_w"), visible)
@@ -1027,6 +1090,12 @@ function on_settings_modified(props, prop, settings)
10271090
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_dw"), visible)
10281091
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_dh"), visible)
10291092
return true
1093+
elseif name == "use_socket" then
1094+
local visible = obs.obs_data_get_bool(settings, "use_socket")
1095+
obs.obs_property_set_visible(obs.obs_properties_get(props, "socket_label"), not visible)
1096+
obs.obs_property_set_visible(obs.obs_properties_get(props, "socket_port"), visible)
1097+
obs.obs_property_set_visible(obs.obs_properties_get(props, "socket_poll"), visible)
1098+
return true
10301099
elseif name == "allow_all_sources" then
10311100
local sources_list = obs.obs_properties_get(props, "source")
10321101
populate_zoom_sources(sources_list)
@@ -1061,6 +1130,9 @@ function log_current_settings()
10611130
monitor_override_sy = monitor_override_sy,
10621131
monitor_override_dw = monitor_override_dw,
10631132
monitor_override_dh = monitor_override_dh,
1133+
use_socket = use_socket,
1134+
socket_port = socket_port,
1135+
socket_poll = socket_poll,
10641136
debug_logs = debug_logs
10651137
}
10661138

@@ -1093,7 +1165,16 @@ function on_print_help()
10931165
"Scale X: The x scale factor to apply to the mouse position if the source size is not 1:1 (useful for cloned sources)\n" ..
10941166
"Scale Y: The y scale factor to apply to the mouse position if the source size is not 1:1 (useful for cloned sources)\n" ..
10951167
"Monitor Width: The width of the monitor that is showing the source (in pixels)\n" ..
1096-
"Monitor Height: The height of the monitor that is showing the source (in pixels)\n" ..
1168+
"Monitor Height: The height of the monitor that is showing the source (in pixels)\n"
1169+
1170+
if socket_available then
1171+
help = help ..
1172+
"Enable remote mouse listener: True to start a UDP socket server that will listen for mouse position messages from a remote client\n" ..
1173+
"Port: The port number to use for the socket server\n" ..
1174+
"Poll Delay: The time between updating the mouse position (in milliseconds)\n"
1175+
end
1176+
1177+
help = help ..
10971178
"More Info: Show this text in the script log\n" ..
10981179
"Enable debug logging: Show additional debug information in the script log\n\n"
10991180

@@ -1146,24 +1227,47 @@ function script_properties()
11461227
obs.obs_property_set_long_description(allow_all, "Enable to allow selecting any source as the Zoom Source\n" ..
11471228
"You MUST set manual source position for non-display capture sources")
11481229

1149-
local override = obs.obs_properties_add_bool(props, "use_monitor_override", "Set manual source position ")
1150-
obs.obs_property_set_long_description(override,
1230+
local override_props = obs.obs_properties_create();
1231+
local override_label = obs.obs_properties_add_text(override_props, "monitor_override_label", "", obs.OBS_TEXT_INFO)
1232+
local override_x = obs.obs_properties_add_int(override_props, "monitor_override_x", "X", -10000, 10000, 1)
1233+
local override_y = obs.obs_properties_add_int(override_props, "monitor_override_y", "Y", -10000, 10000, 1)
1234+
local override_w = obs.obs_properties_add_int(override_props, "monitor_override_w", "Width", 0, 10000, 1)
1235+
local override_h = obs.obs_properties_add_int(override_props, "monitor_override_h", "Height", 0, 10000, 1)
1236+
local override_sx = obs.obs_properties_add_float(override_props, "monitor_override_sx", "Scale X ", 0, 100, 0.01)
1237+
local override_sy = obs.obs_properties_add_float(override_props, "monitor_override_sy", "Scale Y ", 0, 100, 0.01)
1238+
local override_dw = obs.obs_properties_add_int(override_props, "monitor_override_dw", "Monitor Width ", 0, 10000, 1)
1239+
local override_dh = obs.obs_properties_add_int(override_props, "monitor_override_dh", "Monitor Height ", 0, 10000, 1)
1240+
local override = obs.obs_properties_add_group(props, "use_monitor_override", "Set manual source position ",
1241+
obs.OBS_GROUP_CHECKABLE, override_props)
1242+
1243+
obs.obs_property_set_long_description(override_label,
11511244
"When enabled the specified size/position settings will be used for the zoom source instead of the auto-calculated ones")
1152-
1153-
local override_x = obs.obs_properties_add_int(props, "monitor_override_x", "X", -10000, 10000, 1)
1154-
local override_y = obs.obs_properties_add_int(props, "monitor_override_y", "Y", -10000, 10000, 1)
1155-
local override_w = obs.obs_properties_add_int(props, "monitor_override_w", "Width", 0, 10000, 1)
1156-
local override_h = obs.obs_properties_add_int(props, "monitor_override_h", "Height", 0, 10000, 1)
1157-
local override_sx = obs.obs_properties_add_float(props, "monitor_override_sx", "Scale X ", 0, 100, 0.01)
1158-
local override_sy = obs.obs_properties_add_float(props, "monitor_override_sy", "Scale Y ", 0, 100, 0.01)
1159-
local override_dw = obs.obs_properties_add_int(props, "monitor_override_dw", "Monitor Width ", 0, 10000, 1)
1160-
local override_dh = obs.obs_properties_add_int(props, "monitor_override_dh", "Monitor Height ", 0, 10000, 1)
1161-
11621245
obs.obs_property_set_long_description(override_sx, "Usually 1 - unless you are using a scaled source")
11631246
obs.obs_property_set_long_description(override_sy, "Usually 1 - unless you are using a scaled source")
11641247
obs.obs_property_set_long_description(override_dw, "X resolution of your montior")
11651248
obs.obs_property_set_long_description(override_dh, "Y resolution of your monitor")
11661249

1250+
if socket_available then
1251+
local socket_props = obs.obs_properties_create();
1252+
local r_label = obs.obs_properties_add_text(socket_props, "socket_label", "", obs.OBS_TEXT_INFO)
1253+
local r_port = obs.obs_properties_add_int(socket_props, "socket_port", "Port ", 1024, 65535, 1)
1254+
local r_poll = obs.obs_properties_add_int(socket_props, "socket_poll", "Poll Delay (ms) ", 0, 1000, 1)
1255+
local socket = obs.obs_properties_add_group(props, "use_socket", "Enable remote mouse listener ",
1256+
obs.OBS_GROUP_CHECKABLE, socket_props)
1257+
1258+
obs.obs_property_set_long_description(r_label,
1259+
"When enabled a UDP socket server will listen for mouse position messages from a remote client")
1260+
obs.obs_property_set_long_description(r_port,
1261+
"You must restart the server after changing the port (Uncheck then re-check 'Enable remote mouse listener')")
1262+
obs.obs_property_set_long_description(r_poll,
1263+
"You must restart the server after changing the poll delay (Uncheck then re-check 'Enable remote mouse listener')")
1264+
1265+
obs.obs_property_set_visible(r_label, not use_socket)
1266+
obs.obs_property_set_visible(r_port, use_socket)
1267+
obs.obs_property_set_visible(r_poll, use_socket)
1268+
obs.obs_property_set_modified_callback(socket, on_settings_modified)
1269+
end
1270+
11671271
-- Add a button for more information
11681272
local help = obs.obs_properties_add_button(props, "help_button", "More Info", on_print_help)
11691273
obs.obs_property_set_long_description(help,
@@ -1173,6 +1277,7 @@ function script_properties()
11731277
obs.obs_property_set_long_description(debug,
11741278
"When enabled the script will output diagnostics messages to the script log (useful for debugging/github issues)")
11751279

1280+
obs.obs_property_set_visible(override_label, not use_monitor_override)
11761281
obs.obs_property_set_visible(override_x, use_monitor_override)
11771282
obs.obs_property_set_visible(override_y, use_monitor_override)
11781283
obs.obs_property_set_visible(override_w, use_monitor_override)
@@ -1182,6 +1287,7 @@ function script_properties()
11821287
obs.obs_property_set_visible(override_dw, use_monitor_override)
11831288
obs.obs_property_set_visible(override_dh, use_monitor_override)
11841289
obs.obs_property_set_modified_callback(override, on_settings_modified)
1290+
11851291
obs.obs_property_set_modified_callback(allow_all, on_settings_modified)
11861292
obs.obs_property_set_modified_callback(debug, on_settings_modified)
11871293

@@ -1226,6 +1332,9 @@ function script_load(settings)
12261332
monitor_override_sy = obs.obs_data_get_double(settings, "monitor_override_sy")
12271333
monitor_override_dw = obs.obs_data_get_int(settings, "monitor_override_dw")
12281334
monitor_override_dh = obs.obs_data_get_int(settings, "monitor_override_dh")
1335+
use_socket = obs.obs_data_get_bool(settings, "use_socket")
1336+
socket_port = obs.obs_data_get_int(settings, "socket_port")
1337+
socket_poll = obs.obs_data_get_int(settings, "socket_poll")
12291338
debug_logs = obs.obs_data_get_bool(settings, "debug_logs")
12301339

12311340
obs.obs_frontend_add_event_callback(on_frontend_event)
@@ -1250,6 +1359,9 @@ function script_load(settings)
12501359
log("ERROR: Could not get X11 Display for Linux\n" ..
12511360
"Mouse position will be incorrect.")
12521361
end
1362+
1363+
source_name = ""
1364+
use_socket = false
12531365
end
12541366

12551367
function script_unload()
@@ -1272,6 +1384,12 @@ function script_unload()
12721384

12731385
if x11_lib ~= nil and x11_display ~= nil then
12741386
x11_lib.XCloseDisplay(x11_display)
1387+
x11_display = nil
1388+
x11_lib = nil
1389+
end
1390+
1391+
if socket_server ~= nil then
1392+
stop_server()
12751393
end
12761394
end
12771395

@@ -1295,6 +1413,9 @@ function script_defaults(settings)
12951413
obs.obs_data_set_default_double(settings, "monitor_override_sy", 1)
12961414
obs.obs_data_set_default_int(settings, "monitor_override_dw", 1920)
12971415
obs.obs_data_set_default_int(settings, "monitor_override_dh", 1080)
1416+
obs.obs_data_set_default_bool(settings, "use_socket", false)
1417+
obs.obs_data_set_default_int(settings, "socket_port", 12345)
1418+
obs.obs_data_set_default_int(settings, "socket_poll", 10)
12981419
obs.obs_data_set_default_bool(settings, "debug_logs", false)
12991420
end
13001421

@@ -1324,6 +1445,8 @@ function script_update(settings)
13241445
local old_sy = monitor_override_sy
13251446
local old_dw = monitor_override_dw
13261447
local old_dh = monitor_override_dh
1448+
local old_socket = use_socket
1449+
local old_port = socket_port
13271450

13281451
-- Update the settings
13291452
source_name = obs.obs_data_get_string(settings, "source")
@@ -1345,6 +1468,9 @@ function script_update(settings)
13451468
monitor_override_sy = obs.obs_data_get_double(settings, "monitor_override_sy")
13461469
monitor_override_dw = obs.obs_data_get_int(settings, "monitor_override_dw")
13471470
monitor_override_dh = obs.obs_data_get_int(settings, "monitor_override_dh")
1471+
use_socket = obs.obs_data_get_bool(settings, "use_socket")
1472+
socket_port = obs.obs_data_get_int(settings, "socket_port")
1473+
socket_poll = obs.obs_data_get_int(settings, "socket_poll")
13481474
debug_logs = obs.obs_data_get_bool(settings, "debug_logs")
13491475

13501476
-- Only do the expensive refresh if the user selected a new source
@@ -1365,6 +1491,14 @@ function script_update(settings)
13651491
monitor_override_h ~= old_dh then
13661492
monitor_info = get_monitor_info(source)
13671493
end
1494+
1495+
if old_socket ~= use_socket then
1496+
if use_socket then
1497+
start_server()
1498+
else
1499+
stop_server()
1500+
end
1501+
end
13681502
end
13691503

13701504
function populate_zoom_sources(list)

readme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ Inspired by [tryptech](https://github.com/tryptech)'s [obs-zoom-and-follow](http
6262
* **More Info**: Show this text in the script log
6363
* **Enable debug logging**: Show additional debug information in the script log
6464

65+
1. If you have [ljsocket.lua](https://github.com/BlankSourceCode/obs-zoom-to-mouse-remote) in the same directory as `obs-zoom-to-mouse.lua`, the following settings will also be available:
66+
* **Enable remote mouse listener**: True to start a UDP socket server that will listen for mouse position messages from a remote client
67+
* **Port**: The port number to use for the socket server
68+
* **Poll Delay**: The time between updating the mouse position (in milliseconds)
69+
* For more information see [obs-zoom-to-mouse-remote](https://github.com/BlankSourceCode/obs-zoom-to-mouse-remote)
70+
6571
1. In OBS, open File -> Settings -> Hotkeys
6672
* Add a hotkey for `Toggle zoom to mouse` to zoom in and out
6773
* Add a hotkey for `Toggle follow mouse during zoom` to turn mouse tracking on and off (*Optional*)

0 commit comments

Comments
 (0)