Skip to content

Commit 781b2d0

Browse files
Merge pull request #23 from BlankSourceCode/remote
Add basic dual machine support
2 parents c891420 + 7c91066 commit 781b2d0

File tree

2 files changed

+184
-35
lines changed

2 files changed

+184
-35
lines changed

obs-zoom-to-mouse.lua

Lines changed: 176 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@
66

77
local obs = obslua
88
local ffi = require("ffi")
9-
local VERSION = "1.0"
9+
local VERSION = "1.0.2"
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
local is_obs_loaded = false
6875
local is_script_loaded = false
@@ -153,27 +160,32 @@ end
153160
function get_mouse_pos()
154161
local mouse = { x = 0, y = 0 }
155162

156-
if ffi.os == "Windows" then
157-
if win_point and ffi.C.GetCursorPos(win_point) ~= 0 then
158-
mouse.x = win_point[0].x
159-
mouse.y = win_point[0].y
160-
end
161-
elseif ffi.os == "Linux" then
162-
if x11_lib ~= nil and x11_display ~= nil and x11_root ~= nil and x11_mouse ~= nil then
163-
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
164-
mouse.x = tonumber(x11_mouse.win_x[0])
165-
mouse.y = tonumber(x11_mouse.win_y[0])
163+
if socket_mouse ~= nil then
164+
mouse.x = socket_mouse.x
165+
mouse.y = socket_mouse.y
166+
else
167+
if ffi.os == "Windows" then
168+
if win_point and ffi.C.GetCursorPos(win_point) ~= 0 then
169+
mouse.x = win_point[0].x
170+
mouse.y = win_point[0].y
166171
end
167-
end
168-
elseif ffi.os == "OSX" then
169-
if osx_lib ~= nil and osx_nsevent ~= nil and osx_mouse_location ~= nil then
170-
local point = osx_mouse_location(osx_nsevent.class, osx_nsevent.sel)
171-
mouse.x = point.x
172-
if monitor_info ~= nil then
173-
if monitor_info.display_height > 0 then
174-
mouse.y = monitor_info.display_height - point.y
175-
else
176-
mouse.y = monitor_info.height - point.y
172+
elseif ffi.os == "Linux" then
173+
if x11_lib ~= nil and x11_display ~= nil and x11_root ~= nil and x11_mouse ~= nil then
174+
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
175+
mouse.x = tonumber(x11_mouse.win_x[0])
176+
mouse.y = tonumber(x11_mouse.win_y[0])
177+
end
178+
end
179+
elseif ffi.os == "OSX" then
180+
if osx_lib ~= nil and osx_nsevent ~= nil and osx_mouse_location ~= nil then
181+
local point = osx_mouse_location(osx_nsevent.class, osx_nsevent.sel)
182+
mouse.x = point.x
183+
if monitor_info ~= nil then
184+
if monitor_info.display_height > 0 then
185+
mouse.y = monitor_info.display_height - point.y
186+
else
187+
mouse.y = monitor_info.height - point.y
188+
end
177189
end
178190
end
179191
end
@@ -984,6 +996,57 @@ function on_timer()
984996
end
985997
end
986998

999+
function on_socket_timer()
1000+
if not socket_server then
1001+
return
1002+
end
1003+
1004+
repeat
1005+
local data, status = socket_server:receive_from()
1006+
if data then
1007+
local sx, sy = data:match("(-?%d+) (-?%d+)")
1008+
if sx and sy then
1009+
local x = tonumber(sx, 10)
1010+
local y = tonumber(sy, 10)
1011+
if not socket_mouse then
1012+
log("Socket server client connected")
1013+
socket_mouse = { x = x, y = y }
1014+
else
1015+
socket_mouse.x = x
1016+
socket_mouse.y = y
1017+
end
1018+
end
1019+
elseif status ~= "timeout" then
1020+
error(status)
1021+
end
1022+
until data == nil
1023+
end
1024+
1025+
function start_server()
1026+
if socket_available then
1027+
local address = socket.find_first_address("*", socket_port)
1028+
1029+
socket_server = socket.create("inet", "dgram", "udp")
1030+
if socket_server ~= nil then
1031+
socket_server:set_option("reuseaddr", 1)
1032+
socket_server:set_blocking(false)
1033+
socket_server:bind(address, socket_port)
1034+
obs.timer_add(on_socket_timer, socket_poll)
1035+
log("Socket server listening on port " .. socket_port .. "...")
1036+
end
1037+
end
1038+
end
1039+
1040+
function stop_server()
1041+
if socket_server ~= nil then
1042+
log("Socket server stopped")
1043+
obs.timer_remove(on_socket_timer)
1044+
socket_server:close()
1045+
socket_server = nil
1046+
socket_mouse = nil
1047+
end
1048+
end
1049+
9871050
function set_crop_settings(crop)
9881051
if crop_filter ~= nil and crop_filter_settings ~= nil then
9891052
-- Call into OBS to update our crop filter with the new settings
@@ -1042,6 +1105,7 @@ function on_settings_modified(props, prop, settings)
10421105
-- Show/Hide the settings based on if the checkbox is checked or not
10431106
if name == "use_monitor_override" then
10441107
local visible = obs.obs_data_get_bool(settings, "use_monitor_override")
1108+
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_label"), not visible)
10451109
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_x"), visible)
10461110
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_y"), visible)
10471111
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_w"), visible)
@@ -1051,6 +1115,12 @@ function on_settings_modified(props, prop, settings)
10511115
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_dw"), visible)
10521116
obs.obs_property_set_visible(obs.obs_properties_get(props, "monitor_override_dh"), visible)
10531117
return true
1118+
elseif name == "use_socket" then
1119+
local visible = obs.obs_data_get_bool(settings, "use_socket")
1120+
obs.obs_property_set_visible(obs.obs_properties_get(props, "socket_label"), not visible)
1121+
obs.obs_property_set_visible(obs.obs_properties_get(props, "socket_port"), visible)
1122+
obs.obs_property_set_visible(obs.obs_properties_get(props, "socket_poll"), visible)
1123+
return true
10541124
elseif name == "allow_all_sources" then
10551125
local sources_list = obs.obs_properties_get(props, "source")
10561126
populate_zoom_sources(sources_list)
@@ -1085,7 +1155,11 @@ function log_current_settings()
10851155
monitor_override_sy = monitor_override_sy,
10861156
monitor_override_dw = monitor_override_dw,
10871157
monitor_override_dh = monitor_override_dh,
1088-
debug_logs = debug_logs
1158+
use_socket = use_socket,
1159+
socket_port = socket_port,
1160+
socket_poll = socket_poll,
1161+
debug_logs = debug_logs,
1162+
version = VERSION
10891163
}
10901164

10911165
log("OBS Version: " .. string.format("%.1f", major) .. "." .. minor)
@@ -1118,7 +1192,16 @@ function on_print_help()
11181192
"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" ..
11191193
"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" ..
11201194
"Monitor Width: The width of the monitor that is showing the source (in pixels)\n" ..
1121-
"Monitor Height: The height of the monitor that is showing the source (in pixels)\n" ..
1195+
"Monitor Height: The height of the monitor that is showing the source (in pixels)\n"
1196+
1197+
if socket_available then
1198+
help = help ..
1199+
"Enable remote mouse listener: True to start a UDP socket server that will listen for mouse position messages from a remote client, see: https://github.com/BlankSourceCode/obs-zoom-to-mouse-remote\n" ..
1200+
"Port: The port number to use for the socket server\n" ..
1201+
"Poll Delay: The time between updating the mouse position (in milliseconds)\n"
1202+
end
1203+
1204+
help = help ..
11221205
"More Info: Show this text in the script log\n" ..
11231206
"Enable debug logging: Show additional debug information in the script log\n\n"
11241207

@@ -1171,24 +1254,47 @@ function script_properties()
11711254
obs.obs_property_set_long_description(allow_all, "Enable to allow selecting any source as the Zoom Source\n" ..
11721255
"You MUST set manual source position for non-display capture sources")
11731256

1174-
local override = obs.obs_properties_add_bool(props, "use_monitor_override", "Set manual source position ")
1175-
obs.obs_property_set_long_description(override,
1257+
local override_props = obs.obs_properties_create();
1258+
local override_label = obs.obs_properties_add_text(override_props, "monitor_override_label", "", obs.OBS_TEXT_INFO)
1259+
local override_x = obs.obs_properties_add_int(override_props, "monitor_override_x", "X", -10000, 10000, 1)
1260+
local override_y = obs.obs_properties_add_int(override_props, "monitor_override_y", "Y", -10000, 10000, 1)
1261+
local override_w = obs.obs_properties_add_int(override_props, "monitor_override_w", "Width", 0, 10000, 1)
1262+
local override_h = obs.obs_properties_add_int(override_props, "monitor_override_h", "Height", 0, 10000, 1)
1263+
local override_sx = obs.obs_properties_add_float(override_props, "monitor_override_sx", "Scale X ", 0, 100, 0.01)
1264+
local override_sy = obs.obs_properties_add_float(override_props, "monitor_override_sy", "Scale Y ", 0, 100, 0.01)
1265+
local override_dw = obs.obs_properties_add_int(override_props, "monitor_override_dw", "Monitor Width ", 0, 10000, 1)
1266+
local override_dh = obs.obs_properties_add_int(override_props, "monitor_override_dh", "Monitor Height ", 0, 10000, 1)
1267+
local override = obs.obs_properties_add_group(props, "use_monitor_override", "Set manual source position ",
1268+
obs.OBS_GROUP_CHECKABLE, override_props)
1269+
1270+
obs.obs_property_set_long_description(override_label,
11761271
"When enabled the specified size/position settings will be used for the zoom source instead of the auto-calculated ones")
1177-
1178-
local override_x = obs.obs_properties_add_int(props, "monitor_override_x", "X", -10000, 10000, 1)
1179-
local override_y = obs.obs_properties_add_int(props, "monitor_override_y", "Y", -10000, 10000, 1)
1180-
local override_w = obs.obs_properties_add_int(props, "monitor_override_w", "Width", 0, 10000, 1)
1181-
local override_h = obs.obs_properties_add_int(props, "monitor_override_h", "Height", 0, 10000, 1)
1182-
local override_sx = obs.obs_properties_add_float(props, "monitor_override_sx", "Scale X ", 0, 100, 0.01)
1183-
local override_sy = obs.obs_properties_add_float(props, "monitor_override_sy", "Scale Y ", 0, 100, 0.01)
1184-
local override_dw = obs.obs_properties_add_int(props, "monitor_override_dw", "Monitor Width ", 0, 10000, 1)
1185-
local override_dh = obs.obs_properties_add_int(props, "monitor_override_dh", "Monitor Height ", 0, 10000, 1)
1186-
11871272
obs.obs_property_set_long_description(override_sx, "Usually 1 - unless you are using a scaled source")
11881273
obs.obs_property_set_long_description(override_sy, "Usually 1 - unless you are using a scaled source")
11891274
obs.obs_property_set_long_description(override_dw, "X resolution of your montior")
11901275
obs.obs_property_set_long_description(override_dh, "Y resolution of your monitor")
11911276

1277+
if socket_available then
1278+
local socket_props = obs.obs_properties_create();
1279+
local r_label = obs.obs_properties_add_text(socket_props, "socket_label", "", obs.OBS_TEXT_INFO)
1280+
local r_port = obs.obs_properties_add_int(socket_props, "socket_port", "Port ", 1024, 65535, 1)
1281+
local r_poll = obs.obs_properties_add_int(socket_props, "socket_poll", "Poll Delay (ms) ", 0, 1000, 1)
1282+
local socket = obs.obs_properties_add_group(props, "use_socket", "Enable remote mouse listener ",
1283+
obs.OBS_GROUP_CHECKABLE, socket_props)
1284+
1285+
obs.obs_property_set_long_description(r_label,
1286+
"When enabled a UDP socket server will listen for mouse position messages from a remote client")
1287+
obs.obs_property_set_long_description(r_port,
1288+
"You must restart the server after changing the port (Uncheck then re-check 'Enable remote mouse listener')")
1289+
obs.obs_property_set_long_description(r_poll,
1290+
"You must restart the server after changing the poll delay (Uncheck then re-check 'Enable remote mouse listener')")
1291+
1292+
obs.obs_property_set_visible(r_label, not use_socket)
1293+
obs.obs_property_set_visible(r_port, use_socket)
1294+
obs.obs_property_set_visible(r_poll, use_socket)
1295+
obs.obs_property_set_modified_callback(socket, on_settings_modified)
1296+
end
1297+
11921298
-- Add a button for more information
11931299
local help = obs.obs_properties_add_button(props, "help_button", "More Info", on_print_help)
11941300
obs.obs_property_set_long_description(help,
@@ -1198,6 +1304,7 @@ function script_properties()
11981304
obs.obs_property_set_long_description(debug,
11991305
"When enabled the script will output diagnostics messages to the script log (useful for debugging/github issues)")
12001306

1307+
obs.obs_property_set_visible(override_label, not use_monitor_override)
12011308
obs.obs_property_set_visible(override_x, use_monitor_override)
12021309
obs.obs_property_set_visible(override_y, use_monitor_override)
12031310
obs.obs_property_set_visible(override_w, use_monitor_override)
@@ -1207,6 +1314,7 @@ function script_properties()
12071314
obs.obs_property_set_visible(override_dw, use_monitor_override)
12081315
obs.obs_property_set_visible(override_dh, use_monitor_override)
12091316
obs.obs_property_set_modified_callback(override, on_settings_modified)
1317+
12101318
obs.obs_property_set_modified_callback(allow_all, on_settings_modified)
12111319
obs.obs_property_set_modified_callback(debug, on_settings_modified)
12121320

@@ -1216,6 +1324,11 @@ end
12161324
function script_load(settings)
12171325
sceneitem_info_orig = nil
12181326

1327+
-- Workaround for detecting if OBS is already loaded and we were reloaded using "Reload Scripts"
1328+
local current_scene = obs.obs_frontend_get_current_scene()
1329+
is_obs_loaded = current_scene ~= nil -- Current scene is nil on first OBS load
1330+
obs.obs_source_release(current_scene)
1331+
12191332
-- Add our hotkey
12201333
hotkey_zoom_id = obs.obs_hotkey_register_frontend("toggle_zoom_hotkey", "Toggle zoom to mouse",
12211334
on_toggle_zoom)
@@ -1251,6 +1364,9 @@ function script_load(settings)
12511364
monitor_override_sy = obs.obs_data_get_double(settings, "monitor_override_sy")
12521365
monitor_override_dw = obs.obs_data_get_int(settings, "monitor_override_dw")
12531366
monitor_override_dh = obs.obs_data_get_int(settings, "monitor_override_dh")
1367+
use_socket = obs.obs_data_get_bool(settings, "use_socket")
1368+
socket_port = obs.obs_data_get_int(settings, "socket_port")
1369+
socket_poll = obs.obs_data_get_int(settings, "socket_poll")
12541370
debug_logs = obs.obs_data_get_bool(settings, "debug_logs")
12551371

12561372
obs.obs_frontend_add_event_callback(on_frontend_event)
@@ -1277,6 +1393,7 @@ function script_load(settings)
12771393
end
12781394

12791395
source_name = ""
1396+
use_socket = false
12801397
is_script_loaded = true
12811398
end
12821399

@@ -1305,6 +1422,10 @@ function script_unload()
13051422
x11_display = nil
13061423
x11_lib = nil
13071424
end
1425+
1426+
if socket_server ~= nil then
1427+
stop_server()
1428+
end
13081429
end
13091430

13101431
function script_defaults(settings)
@@ -1327,6 +1448,9 @@ function script_defaults(settings)
13271448
obs.obs_data_set_default_double(settings, "monitor_override_sy", 1)
13281449
obs.obs_data_set_default_int(settings, "monitor_override_dw", 1920)
13291450
obs.obs_data_set_default_int(settings, "monitor_override_dh", 1080)
1451+
obs.obs_data_set_default_bool(settings, "use_socket", false)
1452+
obs.obs_data_set_default_int(settings, "socket_port", 12345)
1453+
obs.obs_data_set_default_int(settings, "socket_poll", 10)
13301454
obs.obs_data_set_default_bool(settings, "debug_logs", false)
13311455
end
13321456

@@ -1356,6 +1480,9 @@ function script_update(settings)
13561480
local old_sy = monitor_override_sy
13571481
local old_dw = monitor_override_dw
13581482
local old_dh = monitor_override_dh
1483+
local old_socket = use_socket
1484+
local old_port = socket_port
1485+
local old_poll = socket_poll
13591486

13601487
-- Update the settings
13611488
source_name = obs.obs_data_get_string(settings, "source")
@@ -1377,6 +1504,9 @@ function script_update(settings)
13771504
monitor_override_sy = obs.obs_data_get_double(settings, "monitor_override_sy")
13781505
monitor_override_dw = obs.obs_data_get_int(settings, "monitor_override_dw")
13791506
monitor_override_dh = obs.obs_data_get_int(settings, "monitor_override_dh")
1507+
use_socket = obs.obs_data_get_bool(settings, "use_socket")
1508+
socket_port = obs.obs_data_get_int(settings, "socket_port")
1509+
socket_poll = obs.obs_data_get_int(settings, "socket_poll")
13801510
debug_logs = obs.obs_data_get_bool(settings, "debug_logs")
13811511

13821512
-- Only do the expensive refresh if the user selected a new source
@@ -1399,6 +1529,17 @@ function script_update(settings)
13991529
monitor_info = get_monitor_info(source)
14001530
end
14011531
end
1532+
1533+
if old_socket ~= use_socket then
1534+
if use_socket then
1535+
start_server()
1536+
else
1537+
stop_server()
1538+
end
1539+
elseif use_socket and (old_poll ~= socket_poll or old_port ~= socket_port) then
1540+
stop_server()
1541+
start_server()
1542+
end
14021543
end
14031544

14041545
function populate_zoom_sources(list)

readme.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ Inspired by [tryptech](https://github.com/tryptech)'s [obs-zoom-and-follow](http
6666
* Add a hotkey for `Toggle zoom to mouse` to zoom in and out
6767
* Add a hotkey for `Toggle follow mouse during zoom` to turn mouse tracking on and off (*Optional*)
6868

69+
### Dual Machine Support
70+
1. The script also has some **basic** dual machine setup support. By using my related project [obs-zoom-to-mouse-remote](https://github.com/BlankSourceCode/obs-zoom-to-mouse-remote) you will be able to track the mouse on your second machine
71+
1. When 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:
72+
* **Enable remote mouse listener**: True to start a UDP socket server that will listen for mouse position messages from a remote client
73+
* **Port**: The port number to use for the socket server
74+
* **Poll Delay**: The time between updating the mouse position (in milliseconds)
75+
* For more information see [obs-zoom-to-mouse-remote](https://github.com/BlankSourceCode/obs-zoom-to-mouse-remote)
76+
6977
### More information on how mouse tracking works
7078
When you press the `Toggle zoom` hotkey the script will use the current mouse position as the center of the zoom. The script will then animate the width/height values of a crop/pan filter so it appears to zoom into that location. If you have `Auto follow mouse` turned on, then the x/y values of the filter will also change to keep the mouse in view as it is animating the zoom. Once the animation is complete, the script gives you a "safe zone" to move your cursor in without it moving the "camera". The idea was that you'd want to zoom in somewhere and move your mouse around to highlight code or whatever, without the screen moving so it would be easier to read text in the video.
7179

0 commit comments

Comments
 (0)