66
77local obs = obslua
88local ffi = require (" ffi" )
9- local VERSION = " 1.0"
9+ local VERSION = " 1.0.2 "
1010local 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+
1216local source_name = " "
1317local source = nil
1418local sceneitem = nil
@@ -63,6 +67,9 @@ local monitor_override_sx = 0
6367local monitor_override_sy = 0
6468local monitor_override_dw = 0
6569local monitor_override_dh = 0
70+ local use_socket = false
71+ local socket_port = 0
72+ local socket_poll = 1000
6673local debug_logs = false
6774local is_obs_loaded = false
6875local is_script_loaded = false
@@ -153,27 +160,32 @@ end
153160function 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
985997end
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+
9871050function 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
12161324function 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
12811398end
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
13081429end
13091430
13101431function 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 )
13311455end
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
14021543end
14031544
14041545function populate_zoom_sources (list )
0 commit comments