diff --git a/CI/install-dependencies-macos.sh b/CI/install-dependencies-macos.sh index 0adf464b..267fa49a 100755 --- a/CI/install-dependencies-macos.sh +++ b/CI/install-dependencies-macos.sh @@ -35,13 +35,8 @@ done # qtwebsockets deps echo "[obs-websocket] Installing obs-websocket dependency 'QT 5.10.1'.." -# =!= NOTICE =!= -# When building QT5 from sources on macOS 10.13+, use local qt5 formula: -# brew install ./CI/macos/qt.rb -# Pouring from the bottle is much quicker though, so use bottle for now. -# =!= NOTICE =!= -brew install https://gist.githubusercontent.com/DDRBoxman/b3956fab6073335a4bf151db0dcbd4ad/raw/ed1342a8a86793ea8c10d8b4d712a654da121ace/qt.rb +brew install ./CI/macos/qt.rb # Pin this version of QT5 to avoid `brew upgrade` # upgrading it to incompatible version diff --git a/CI/macos/qt.rb b/CI/macos/qt.rb index 50ff4f57..4405a240 100644 --- a/CI/macos/qt.rb +++ b/CI/macos/qt.rb @@ -1,163 +1,145 @@ # Patches for Qt must be at the very least submitted to Qt's Gerrit codereview # rather than their bug-report Jira. The latter is rarely reviewed by Qt. class Qt < Formula - desc "Cross-platform application and UI framework" - homepage "https://www.qt.io/" - url "https://download.qt.io/archive/qt/5.10/5.10.1/single/qt-everywhere-src-5.10.1.tar.xz" - mirror "https://mirrorservice.org/sites/download.qt-project.org/official_releases/qt/5.10/5.10.1/single/qt-everywhere-src-5.10.1.tar.xz" - sha256 "05ffba7b811b854ed558abf2be2ddbd3bb6ddd0b60ea4b5da75d277ac15e740a" - head "https://code.qt.io/qt/qt5.git", :branch => "5.10.1", :shallow => false + desc "Cross-platform application and UI framework" + homepage "https://www.qt.io/" + url "https://download.qt.io/official_releases/qt/5.10/5.10.1/single/qt-everywhere-src-5.10.1.tar.xz" + mirror "https://www.mirrorservice.org/sites/download.qt-project.org/official_releases/qt/5.10/5.10.1/single/qt-everywhere-src-5.10.1.tar.xz" + sha256 "05ffba7b811b854ed558abf2be2ddbd3bb6ddd0b60ea4b5da75d277ac15e740a" + head "https://code.qt.io/qt/qt5.git", :branch => "5.10", :shallow => false - bottle do - sha256 "8b4bad005596a5f8790150fe455db998ac2406f4e0f04140d6656205d844d266" => :high_sierra - sha256 "9c488554935fb573554a4e36d36d3c81e47245b7fefc4b61edef894e67ba1740" => :sierra - sha256 "c0407afba5951df6cc4c6f6c1c315972bd41c99cecb4e029919c4c15ab6f7bdc" => :el_capitan - end + bottle do + sha256 "8b4bad005596a5f8790150fe455db998ac2406f4e0f04140d6656205d844d266" => :high_sierra + sha256 "9c488554935fb573554a4e36d36d3c81e47245b7fefc4b61edef894e67ba1740" => :sierra + sha256 "c0407afba5951df6cc4c6f6c1c315972bd41c99cecb4e029919c4c15ab6f7bdc" => :el_capitan + end - keg_only "Qt 5 has CMake issues when linked" + keg_only "Qt 5 has CMake issues when linked" - option "with-docs", "Build documentation" - option "with-examples", "Build examples" + option "with-docs", "Build documentation" + option "with-examples", "Build examples" + option "without-proprietary-codecs", "Don't build with proprietary codecs (e.g. mp3)" - deprecated_option "with-mysql" => "with-mysql-client" + # OS X 10.7 Lion is still supported in Qt 5.5, but is no longer a reference + # configuration and thus untested in practice. Builds on OS X 10.7 have been + # reported to fail: . + # depends_on :macos => :mountain_lion - # OS X 10.7 Lion is still supported in Qt 5.5, but is no longer a reference - # configuration and thus untested in practice. Builds on OS X 10.7 have been - # reported to fail: . - depends_on :macos => :mountain_lion + depends_on "pkg-config" => :build + depends_on :xcode => :build + depends_on "mysql" => :optional + depends_on "postgresql" => :optional - depends_on "pkg-config" => :build - depends_on :xcode => :build - depends_on "mysql-client" => :optional - depends_on "postgresql" => :optional + # Restore `.pc` files for framework-based build of Qt 5 on OS X. This + # partially reverts merged + # between the 5.5.1 and 5.6.0 releases. (Remove this as soon as feasible!) + # + # Core formulae known to fail without this patch (as of 2016-10-15): + # * gnuplot (with `--with-qt` option) + # * mkvtoolnix (with `--with-qt` option, silent build failure) + # * poppler (with `--with-qt` option) + patch do + url "https://raw.githubusercontent.com/Homebrew/formula-patches/e8fe6567/qt5/restore-pc-files.patch" + sha256 "48ff18be2f4050de7288bddbae7f47e949512ac4bcd126c2f504be2ac701158b" + end - # Restore `.pc` files for framework-based build of Qt 5 on OS X. This - # partially reverts merged - # between the 5.5.1 and 5.6.0 releases. (Remove this as soon as feasible!) - # - # Core formulae known to fail without this patch (as of 2016-10-15): - # * gnuplot (with `--with-qt` option) - # * mkvtoolnix (with `--with-qt` option, silent build failure) - # * poppler (with `--with-qt` option) - patch do - url "https://raw.githubusercontent.com/Homebrew/formula-patches/e8fe6567/qt5/restore-pc-files.patch" - sha256 "48ff18be2f4050de7288bddbae7f47e949512ac4bcd126c2f504be2ac701158b" - end + def install + args = %W[ + -verbose + -prefix #{prefix} + -release + -opensource -confirm-license + -system-zlib + -qt-libpng + -qt-libjpeg + -qt-freetype + -qt-pcre + -nomake tests + -no-rpath + -pkg-config + -dbus-runtime + ] - # Fix compile error on macOS 10.13 around QFixed: - # https://github.com/Homebrew/homebrew-core/issues/27095 - # https://bugreports.qt.io/browse/QTBUG-67545 - patch do - url "https://raw.githubusercontent.com/z00m1n/formula-patches/0de0e229/qt/QTBUG-67545.patch" - sha256 "4a115097c7582c7dce4207f5500d13feb8c990eb8a05a43f41953985976ebe6c" - end + args << "-nomake" << "examples" if build.without? "examples" - # Fix compile error on macOS 10.13 caused by qtlocation dependency - # mapbox-gl-native using Boost 1.62.0 does not build with C++ 17: - # https://github.com/Homebrew/homebrew-core/issues/27095 - # https://bugreports.qt.io/browse/QTBUG-67810 - patch do - url "https://raw.githubusercontent.com/z00m1n/formula-patches/a1a1f0dd/qt/QTBUG-67810.patch" - sha256 "8ee0bf71df1043f08ebae3aa35036be29c4d9ebff8a27e3b0411a6bd635e9382" - end + if build.with? "mysql" + args << "-plugin-sql-mysql" + (buildpath/"brew_shim/mysql_config").write <<~EOS + #!/bin/sh + if [ x"$1" = x"--libs" ]; then + mysql_config --libs | sed "s/-lssl -lcrypto//" + else + exec mysql_config "$@" + fi + EOS + chmod 0755, "brew_shim/mysql_config" + args << "-mysql_config" << buildpath/"brew_shim/mysql_config" + end - def install - args = %W[ - -verbose - -prefix #{prefix} - -release - -opensource -confirm-license - -system-zlib - -qt-libpng - -qt-libjpeg - -qt-freetype - -qt-pcre - -nomake tests - -no-rpath - -pkg-config - -dbus-runtime - -no-assimp - ] + args << "-plugin-sql-psql" if build.with? "postgresql" + args << "-proprietary-codecs" if build.with? "proprietary-codecs" - args << "-nomake" << "examples" if build.without? "examples" + system "./configure", *args + system "make" + ENV.deparallelize + system "make", "install" - if build.with? "mysql-client" - args << "-plugin-sql-mysql" - (buildpath/"brew_shim/mysql_config").write <<~EOS - #!/bin/sh - if [ x"$1" = x"--libs" ]; then - mysql_config --libs | sed "s/-lssl -lcrypto//" - else - exec mysql_config "$@" - fi - EOS - chmod 0755, "brew_shim/mysql_config" - args << "-mysql_config" << buildpath/"brew_shim/mysql_config" - end + if build.with? "docs" + system "make", "docs" + system "make", "install_docs" + end - args << "-plugin-sql-psql" if build.with? "postgresql" + # Some config scripts will only find Qt in a "Frameworks" folder + frameworks.install_symlink Dir["#{lib}/*.framework"] - system "./configure", *args - system "make" - ENV.deparallelize - system "make", "install" + # The pkg-config files installed suggest that headers can be found in the + # `include` directory. Make this so by creating symlinks from `include` to + # the Frameworks' Headers folders. + Pathname.glob("#{lib}/*.framework/Headers") do |path| + include.install_symlink path => path.parent.basename(".framework") + end - if build.with? "docs" - system "make", "docs" - system "make", "install_docs" - end + # Move `*.app` bundles into `libexec` to expose them to `brew linkapps` and + # because we don't like having them in `bin`. + # (Note: This move breaks invocation of Assistant via the Help menu + # of both Designer and Linguist as that relies on Assistant being in `bin`.) + libexec.mkpath + Pathname.glob("#{bin}/*.app") { |app| mv app, libexec } + end - # Some config scripts will only find Qt in a "Frameworks" folder - frameworks.install_symlink Dir["#{lib}/*.framework"] + def caveats; <<~EOS + We agreed to the Qt opensource license for you. + If this is unacceptable you should uninstall. + EOS + end - # The pkg-config files installed suggest that headers can be found in the - # `include` directory. Make this so by creating symlinks from `include` to - # the Frameworks' Headers folders. - Pathname.glob("#{lib}/*.framework/Headers") do |path| - include.install_symlink path => path.parent.basename(".framework") - end + test do + (testpath/"hello.pro").write <<~EOS + QT += core + QT -= gui + TARGET = hello + CONFIG += console + CONFIG -= app_bundle + TEMPLATE = app + SOURCES += main.cpp + EOS - # Move `*.app` bundles into `libexec` to expose them to `brew linkapps` and - # because we don't like having them in `bin`. - # (Note: This move breaks invocation of Assistant via the Help menu - # of both Designer and Linguist as that relies on Assistant being in `bin`.) - libexec.mkpath - Pathname.glob("#{bin}/*.app") { |app| mv app, libexec } - end + (testpath/"main.cpp").write <<~EOS + #include + #include - def caveats; <<~EOS - We agreed to the Qt opensource license for you. - If this is unacceptable you should uninstall. - EOS - end + int main(int argc, char *argv[]) + { + QCoreApplication a(argc, argv); + qDebug() << "Hello World!"; + return 0; + } + EOS - test do - (testpath/"hello.pro").write <<~EOS - QT += core - QT -= gui - TARGET = hello - CONFIG += console - CONFIG -= app_bundle - TEMPLATE = app - SOURCES += main.cpp - EOS - - (testpath/"main.cpp").write <<~EOS - #include - #include - - int main(int argc, char *argv[]) - { - QCoreApplication a(argc, argv); - qDebug() << "Hello World!"; - return 0; - } - EOS - - system bin/"qmake", testpath/"hello.pro" - system "make" - assert_predicate testpath/"hello", :exist? - assert_predicate testpath/"main.o", :exist? - system "./hello" - end -end + system bin/"qmake", testpath/"hello.pro" + system "make" + assert_predicate testpath/"hello", :exist? + assert_predicate testpath/"main.o", :exist? + system "./hello" + end +end \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9230d197..261d8c78 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) add_definitions(-DASIO_STANDALONE) +if (${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") + set(CMAKE_CXX_FLAGS "-mfpu=neon") +endif() + if (WIN32 OR APPLE) include(external/FindLibObs.cmake) endif() diff --git a/README.md b/README.md index 074c30de..741ca5cf 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,10 @@ Binaries for Windows, MacOS, and Linux are available in the [Releases](https://g ## Using obs-websocket -A web client and frontend made by [t2t2](https://github.com/t2t2/obs-tablet-remote) (compatible with tablets and other touch interfaces) is available here : http://t2t2.github.io/obs-tablet-remote/ +Here is a list of available web clients: (compatible with tablets and other touch interfaces) + +- [Niek/obs-web](https://github.com/Niek/obs-web) +- [t2t2/obs-tablet-remote](https://github.com/t2t2/obs-tablet-remote) It is **highly recommended** to protect obs-websocket with a password against unauthorized control. To do this, open the "Websocket server settings" dialog under OBS' "Tools" menu. In the settings dialogs, you can enable or disable authentication and set a password for it. diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 607ab2f2..69fbed16 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -3,6 +3,7 @@ OBSWebsocket.Settings.ServerEnable="Enable WebSockets server" OBSWebsocket.Settings.ServerPort="Server Port" OBSWebsocket.Settings.AuthRequired="Enable authentication" OBSWebsocket.Settings.Password="Password" +OBSWebsocket.Settings.LockToIPv4="Lock server to only using IPv4" OBSWebsocket.Settings.DebugEnable="Enable debug logging" OBSWebsocket.Settings.AlertsEnable="Enable System Tray Alerts" OBSWebsocket.NotifyConnect.Title="New WebSocket connection" diff --git a/docs/generated/comments.json b/docs/generated/comments.json index 6635e97e..a703f6d3 100644 --- a/docs/generated/comments.json +++ b/docs/generated/comments.json @@ -2515,7 +2515,8 @@ "{String} `sourceName` Source name", "{Array} `filters` Ordered Filters list", "{String} `filters.*.name` Filter name", - "{String} `filters.*.type` Filter type" + "{String} `filters.*.type` Filter type", + "{boolean} `filters.*.enabled` Filter visibility status" ], "api": "events", "name": "SourceFiltersReordered", @@ -2541,6 +2542,11 @@ "type": "String", "name": "filters.*.type", "description": "Filter type" + }, + { + "type": "boolean", + "name": "filters.*.enabled", + "description": "Filter visibility status" } ], "names": [ @@ -4059,6 +4065,120 @@ "lead": "", "type": "class", "examples": [] + }, + { + "subheads": [], + "description": "Executes hotkey routine, identified by hotkey unique name", + "param": "{String} `hotkeyName` Unique name of the hotkey, as defined when registering the hotkey (e.g. \"ReplayBuffer.Save\")", + "api": "requests", + "name": "TriggerHotkeyByName", + "category": "general", + "since": "unreleased", + "params": [ + { + "type": "String", + "name": "hotkeyName", + "description": "Unique name of the hotkey, as defined when registering the hotkey (e.g. \"ReplayBuffer.Save\")" + } + ], + "names": [ + { + "name": "", + "description": "TriggerHotkeyByName" + } + ], + "categories": [ + { + "name": "", + "description": "general" + } + ], + "sinces": [ + { + "name": "", + "description": "unreleased" + } + ], + "heading": { + "level": 2, + "text": "TriggerHotkeyByName" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Executes hotkey routine, identified by bound combination of keys. A single key combination might trigger multiple hotkey routines depending on user settings", + "param": [ + "{String} `keyId` Main key identifier (e.g. `OBS_KEY_A` for key \"A\"). Available identifiers [here](https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h)", + "{Object (Optional)} `keyModifiers` Optional key modifiers object. False entries can be ommitted", + "{boolean} `keyModifiers.shift` Trigger Shift Key", + "{boolean} `keyModifiers.alt` Trigger Alt Key", + "{boolean} `keyModifiers.control` Trigger Control (Ctrl) Key", + "{boolean} `keyModifiers.command` Trigger Command Key (Mac)" + ], + "api": "requests", + "name": "TriggerHotkeyBySequence", + "category": "general", + "since": "unreleased", + "params": [ + { + "type": "String", + "name": "keyId", + "description": "Main key identifier (e.g. `OBS_KEY_A` for key \"A\"). Available identifiers [here](https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h)" + }, + { + "type": "Object (Optional)", + "name": "keyModifiers", + "description": "Optional key modifiers object. False entries can be ommitted" + }, + { + "type": "boolean", + "name": "keyModifiers.shift", + "description": "Trigger Shift Key" + }, + { + "type": "boolean", + "name": "keyModifiers.alt", + "description": "Trigger Alt Key" + }, + { + "type": "boolean", + "name": "keyModifiers.control", + "description": "Trigger Control (Ctrl) Key" + }, + { + "type": "boolean", + "name": "keyModifiers.command", + "description": "Trigger Command Key (Mac)" + } + ], + "names": [ + { + "name": "", + "description": "TriggerHotkeyBySequence" + } + ], + "categories": [ + { + "name": "", + "description": "general" + } + ], + "sinces": [ + { + "name": "", + "description": "unreleased" + } + ], + "heading": { + "level": 2, + "text": "TriggerHotkeyBySequence" + }, + "lead": "", + "type": "class", + "examples": [] } ], "media control": [ @@ -7972,8 +8092,9 @@ { "subheads": [], "description": "Get a list of all scene items in a scene.", - "param": "{String} `sceneName` Name of the scene to get the list of scene items from.", + "param": "{String (optional)} `sceneName` Name of the scene to get the list of scene items from. Defaults to the current scene if not specified.", "return": [ + "{String} `sceneName` Name of the requested (or current) scene", "{Array} `sceneItems` Array of scene items", "{int} `sceneItems.*.itemId` Unique item id of the source item", "{String} `sceneItems.*.sourceKind` ID if the scene item's source. For example `vlc_source` or `image_source`", @@ -7985,6 +8106,11 @@ "category": "scene items", "since": "unreleased", "returns": [ + { + "type": "String", + "name": "sceneName", + "description": "Name of the requested (or current) scene" + }, { "type": "Array", "name": "sceneItems", @@ -8013,9 +8139,9 @@ ], "params": [ { - "type": "String", + "type": "String (optional)", "name": "sceneName", - "description": "Name of the scene to get the list of scene items from." + "description": "Name of the scene to get the list of scene items from. Defaults to the current scene if not specified." } ], "names": [ @@ -9233,7 +9359,7 @@ "api": "requests", "name": "SetSceneTransitionOverride", "category": "scenes", - "since": "unreleased", + "since": "4.8.0", "params": [ { "type": "String", @@ -9266,7 +9392,7 @@ "sinces": [ { "name": "", - "description": "unreleased" + "description": "4.8.0" } ], "heading": { @@ -9284,7 +9410,7 @@ "api": "requests", "name": "RemoveSceneTransitionOverride", "category": "scenes", - "since": "unreleased", + "since": "4.8.0", "params": [ { "type": "String", @@ -9307,7 +9433,7 @@ "sinces": [ { "name": "", - "description": "unreleased" + "description": "4.8.0" } ], "heading": { @@ -9329,7 +9455,7 @@ "api": "requests", "name": "GetSceneTransitionOverride", "category": "scenes", - "since": "unreleased", + "since": "4.8.0", "returns": [ { "type": "String", @@ -9364,7 +9490,7 @@ "sinces": [ { "name": "", - "description": "unreleased" + "description": "4.8.0" } ], "heading": { @@ -10395,6 +10521,112 @@ "lead": "", "type": "class", "examples": [] + }, + { + "subheads": [], + "description": "Get the current settings of a transition", + "param": "{String} `transitionName` Transition name", + "return": "{Object} `transitionSettings` Current transition settings", + "api": "requests", + "name": "GetTransitionSettings", + "category": "transitions", + "since": "unreleased", + "returns": [ + { + "type": "Object", + "name": "transitionSettings", + "description": "Current transition settings" + } + ], + "params": [ + { + "type": "String", + "name": "transitionName", + "description": "Transition name" + } + ], + "names": [ + { + "name": "", + "description": "GetTransitionSettings" + } + ], + "categories": [ + { + "name": "", + "description": "transitions" + } + ], + "sinces": [ + { + "name": "", + "description": "unreleased" + } + ], + "heading": { + "level": 2, + "text": "GetTransitionSettings" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Change the current settings of a transition", + "param": [ + "{String} `transitionName` Transition name", + "{Object} `transitionSettings` Transition settings (they can be partial)" + ], + "return": "{Object} `transitionSettings` Updated transition settings", + "api": "requests", + "name": "SetTransitionSettings", + "category": "transitions", + "since": "unreleased", + "returns": [ + { + "type": "Object", + "name": "transitionSettings", + "description": "Updated transition settings" + } + ], + "params": [ + { + "type": "String", + "name": "transitionName", + "description": "Transition name" + }, + { + "type": "Object", + "name": "transitionSettings", + "description": "Transition settings (they can be partial)" + } + ], + "names": [ + { + "name": "", + "description": "SetTransitionSettings" + } + ], + "categories": [ + { + "name": "", + "description": "transitions" + } + ], + "sinces": [ + { + "name": "", + "description": "unreleased" + } + ], + "heading": { + "level": 2, + "text": "SetTransitionSettings" + }, + "lead": "", + "type": "class", + "examples": [] } ] } diff --git a/docs/generated/protocol.md b/docs/generated/protocol.md index d2f4f324..7d3f7610 100644 --- a/docs/generated/protocol.md +++ b/docs/generated/protocol.md @@ -136,6 +136,8 @@ You can also refer to any of the client libraries listed on the [README](README. + [BroadcastCustomMessage](#broadcastcustommessage-1) + [GetVideoInfo](#getvideoinfo) + [OpenProjector](#openprojector) + + [TriggerHotkeyByName](#triggerhotkeybyname) + + [TriggerHotkeyBySequence](#triggerhotkeybysequence) * [Media Control](#media-control) + [PlayPauseMedia](#playpausemedia) + [RestartMedia](#restartmedia) @@ -253,6 +255,8 @@ You can also refer to any of the client libraries listed on the [README](README. + [SetTransitionDuration](#settransitionduration) + [GetTransitionDuration](#gettransitionduration) + [GetTransitionPosition](#gettransitionposition) + + [GetTransitionSettings](#gettransitionsettings) + + [SetTransitionSettings](#settransitionsettings) @@ -1073,6 +1077,7 @@ Filters in a source have been reordered. | `filters` | _Array<Object>_ | Ordered Filters list | | `filters.*.name` | _String_ | Filter name | | `filters.*.type` | _String_ | Filter type | +| `filters.*.enabled` | _boolean_ | Filter visibility status | --- @@ -1648,6 +1653,51 @@ Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 | `name` | _String (Optional)_ | Name of the source or scene to be displayed (ignored for other projector types). | +**Response Items:** + +_No additional response items._ + +--- + +### TriggerHotkeyByName + + +- Unreleased + +Executes hotkey routine, identified by hotkey unique name + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `hotkeyName` | _String_ | Unique name of the hotkey, as defined when registering the hotkey (e.g. "ReplayBuffer.Save") | + + +**Response Items:** + +_No additional response items._ + +--- + +### TriggerHotkeyBySequence + + +- Unreleased + +Executes hotkey routine, identified by bound combination of keys. A single key combination might trigger multiple hotkey routines depending on user settings + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `keyId` | _String_ | Main key identifier (e.g. `OBS_KEY_A` for key "A"). Available identifiers [here](https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h) | +| `keyModifiers` | _Object (Optional)_ | Optional key modifiers object. False entries can be ommitted | +| `keyModifiers.shift` | _boolean_ | Trigger Shift Key | +| `keyModifiers.alt` | _boolean_ | Trigger Alt Key | +| `keyModifiers.control` | _boolean_ | Trigger Control (Ctrl) Key | +| `keyModifiers.command` | _boolean_ | Trigger Command Key (Mac) | + + **Response Items:** _No additional response items._ @@ -3213,13 +3263,14 @@ Get a list of all scene items in a scene. | Name | Type | Description | | ---- | :---: | ------------| -| `sceneName` | _String_ | Name of the scene to get the list of scene items from. | +| `sceneName` | _String (optional)_ | Name of the scene to get the list of scene items from. Defaults to the current scene if not specified. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| +| `sceneName` | _String_ | Name of the requested (or current) scene | | `sceneItems` | _Array<Object>_ | Array of scene items | | `sceneItems.*.itemId` | _int_ | Unique item id of the source item | | `sceneItems.*.sourceKind` | _String_ | ID if the scene item's source. For example `vlc_source` or `image_source` | @@ -3629,7 +3680,7 @@ _No additional response items._ ### SetSceneTransitionOverride -- Unreleased +- Added in v4.8.0 Set a scene to use a specific transition override. @@ -3651,7 +3702,7 @@ _No additional response items._ ### RemoveSceneTransitionOverride -- Unreleased +- Added in v4.8.0 Remove any transition override on a scene. @@ -3671,7 +3722,7 @@ _No additional response items._ ### GetSceneTransitionOverride -- Unreleased +- Added in v4.8.0 Get the current scene transition override. @@ -4136,3 +4187,50 @@ _No specified parameters._ --- +### GetTransitionSettings + + +- Unreleased + +Get the current settings of a transition + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `transitionName` | _String_ | Transition name | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `transitionSettings` | _Object_ | Current transition settings | + + +--- + +### SetTransitionSettings + + +- Unreleased + +Change the current settings of a transition + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `transitionName` | _String_ | Transition name | +| `transitionSettings` | _Object_ | Transition settings (they can be partial) | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `transitionSettings` | _Object_ | Updated transition settings | + + +--- + diff --git a/installer/installer.iss b/installer/installer.iss index 243f3a31..e94ff579 100644 --- a/installer/installer.iss +++ b/installer/installer.iss @@ -23,6 +23,7 @@ DefaultGroupName={#MyAppName} OutputBaseFilename=obs-websocket-Windows-Installer Compression=lzma SolidCompression=yes +DirExistsWarning=no [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" diff --git a/src/Config.cpp b/src/Config.cpp index 4ed30608..1a17065f 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -25,6 +25,7 @@ with this program. If not, see #define SECTION_NAME "WebsocketAPI" #define PARAM_ENABLE "ServerEnabled" #define PARAM_PORT "ServerPort" +#define PARAM_LOCKTOIPV4 "LockToIPv4" #define PARAM_DEBUG "DebugEnabled" #define PARAM_ALERT "AlertsEnabled" #define PARAM_AUTHREQUIRED "AuthRequired" @@ -41,6 +42,7 @@ with this program. If not, see Config::Config() : ServerEnabled(true), ServerPort(4444), + LockToIPv4(false), DebugEnabled(false), AlertsEnabled(true), AuthRequired(false), @@ -67,6 +69,7 @@ void Config::Load() ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE); ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT); + LockToIPv4 = config_get_bool(obsConfig, SECTION_NAME, PARAM_LOCKTOIPV4); DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG); AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT); @@ -82,6 +85,7 @@ void Config::Save() config_set_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled); config_set_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort); + config_set_bool(obsConfig, SECTION_NAME, PARAM_LOCKTOIPV4, LockToIPv4); config_set_bool(obsConfig, SECTION_NAME, PARAM_DEBUG, DebugEnabled); config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled); @@ -104,6 +108,8 @@ void Config::SetDefaults() SECTION_NAME, PARAM_ENABLE, ServerEnabled); config_set_default_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort); + config_set_default_bool(obsConfig, + SECTION_NAME, PARAM_LOCKTOIPV4, LockToIPv4); config_set_default_bool(obsConfig, SECTION_NAME, PARAM_DEBUG, DebugEnabled); @@ -205,16 +211,17 @@ void Config::OnFrontendEvent(enum obs_frontend_event event, void* param) bool previousEnabled = config->ServerEnabled; uint64_t previousPort = config->ServerPort; + bool previousLock = config->LockToIPv4; config->SetDefaults(); config->Load(); - if (config->ServerEnabled != previousEnabled || config->ServerPort != previousPort) { + if (config->ServerEnabled != previousEnabled || config->ServerPort != previousPort || config->LockToIPv4 != previousLock) { auto server = GetServer(); server->stop(); if (config->ServerEnabled) { - server->start(config->ServerPort); + server->start(config->ServerPort, config->LockToIPv4); if (previousEnabled != config->ServerEnabled) { Utils::SysTrayNotify(startMessage, QSystemTrayIcon::MessageIcon::Information); @@ -246,6 +253,13 @@ void Config::MigrateFromGlobalSettings() config_remove_value(source, SECTION_NAME, PARAM_PORT); } + + if(config_has_user_value(source, SECTION_NAME, PARAM_LOCKTOIPV4)) { + bool value = config_get_bool(source, SECTION_NAME, PARAM_LOCKTOIPV4); + config_set_bool(destination, SECTION_NAME, PARAM_LOCKTOIPV4, value); + + config_remove_value(source, SECTION_NAME, PARAM_LOCKTOIPV4); + } if(config_has_user_value(source, SECTION_NAME, PARAM_DEBUG)) { bool value = config_get_bool(source, SECTION_NAME, PARAM_DEBUG); diff --git a/src/Config.h b/src/Config.h index 0845f874..f21e396a 100644 --- a/src/Config.h +++ b/src/Config.h @@ -42,6 +42,7 @@ class Config { bool ServerEnabled; uint64_t ServerPort; + bool LockToIPv4; bool DebugEnabled; bool AlertsEnabled; diff --git a/src/Utils.cpp b/src/Utils.cpp index 148a0b11..cd197519 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -51,6 +51,25 @@ obs_bounds_type getBoundsTypeFromName(QString name) { return boundTypeNames.key(name); } +bool Utils::StringInStringList(char** strings, const char* string) { + if (!strings) { + return false; + } + + size_t index = 0; + while (strings[index] != NULL) { + char* value = strings[index]; + + if (strcmp(value, string) == 0) { + return true; + } + + index++; + } + + return false; +} + obs_data_array_t* Utils::StringListToArray(char** strings, const char* key) { obs_data_array_t* list = obs_data_array_create(); @@ -485,11 +504,8 @@ QString Utils::OBSVersionString() { } QSystemTrayIcon* Utils::GetTrayIcon() { - QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window(); - if (!main) return nullptr; - - QList trays = main->findChildren(); - return trays.isEmpty() ? nullptr : trays.first(); + void* systemTray = obs_frontend_get_system_tray(); + return reinterpret_cast(systemTray); } void Utils::SysTrayNotify(QString text, diff --git a/src/Utils.h b/src/Utils.h index de1d7f7a..0851a8ea 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -35,6 +35,7 @@ typedef void(*PauseRecordingFunction)(bool); typedef bool(*RecordingPausedFunction)(); namespace Utils { + bool StringInStringList(char** strings, const char* string); obs_data_array_t* StringListToArray(char** strings, const char* key); obs_data_array_t* GetSceneItems(obs_source_t* source); obs_data_t* GetSceneItemData(obs_sceneitem_t* item); diff --git a/src/WSEvents.cpp b/src/WSEvents.cpp index 0818fada..56d24c8a 100644 --- a/src/WSEvents.cpp +++ b/src/WSEvents.cpp @@ -1397,6 +1397,7 @@ void WSEvents::OnSourceFilterVisibilityChanged(void* param, calldata_t* data) { * @return {Array} `filters` Ordered Filters list * @return {String} `filters.*.name` Filter name * @return {String} `filters.*.type` Filter type + * @return {boolean} `filters.*.enabled` Filter visibility status * * @api events * @name SourceFiltersReordered diff --git a/src/WSRequestHandler.cpp b/src/WSRequestHandler.cpp index fd9e1be0..bb897b44 100644 --- a/src/WSRequestHandler.cpp +++ b/src/WSRequestHandler.cpp @@ -28,7 +28,7 @@ using namespace std::placeholders; -const QHash WSRequestHandler::messageMap { +const QHash WSRequestHandler::messageMap{ { "GetVersion", &WSRequestHandler::GetVersion }, { "GetAuthRequired", &WSRequestHandler::GetAuthRequired }, { "Authenticate", &WSRequestHandler::Authenticate }, @@ -43,6 +43,9 @@ const QHash WSRequestHandler::messageMap { { "BroadcastCustomMessage", &WSRequestHandler::BroadcastCustomMessage }, + { "TriggerHotkeyByName", &WSRequestHandler::TriggerHotkeyByName }, + { "TriggerHotkeyBySequence", &WSRequestHandler::TriggerHotkeyBySequence }, + { "SetCurrentScene", &WSRequestHandler::SetCurrentScene }, { "GetCurrentScene", &WSRequestHandler::GetCurrentScene }, { "GetSceneList", &WSRequestHandler::GetSceneList }, @@ -93,6 +96,8 @@ const QHash WSRequestHandler::messageMap { { "SetTransitionDuration", &WSRequestHandler::SetTransitionDuration }, { "GetTransitionDuration", &WSRequestHandler::GetTransitionDuration }, { "GetTransitionPosition", &WSRequestHandler::GetTransitionPosition }, + { "GetTransitionSettings", &WSRequestHandler::GetTransitionSettings }, + { "SetTransitionSettings", &WSRequestHandler::SetTransitionSettings }, { "CreateSource", &WSRequestHandler::CreateSource }, { "SetVolume", &WSRequestHandler::SetVolume }, diff --git a/src/WSRequestHandler.h b/src/WSRequestHandler.h index b7404527..047734a5 100644 --- a/src/WSRequestHandler.h +++ b/src/WSRequestHandler.h @@ -61,6 +61,9 @@ class WSRequestHandler { RpcResponse BroadcastCustomMessage(const RpcRequest&); + RpcResponse TriggerHotkeyByName(const RpcRequest&); + RpcResponse TriggerHotkeyBySequence(const RpcRequest&); + RpcResponse SetCurrentScene(const RpcRequest&); RpcResponse GetCurrentScene(const RpcRequest&); RpcResponse GetSceneList(const RpcRequest&); @@ -110,6 +113,8 @@ class WSRequestHandler { RpcResponse SetTransitionDuration(const RpcRequest&); RpcResponse GetTransitionDuration(const RpcRequest&); RpcResponse GetTransitionPosition(const RpcRequest&); + RpcResponse GetTransitionSettings(const RpcRequest&); + RpcResponse SetTransitionSettings(const RpcRequest&); RpcResponse CreateSource(const RpcRequest&); RpcResponse SetVolume(const RpcRequest&); diff --git a/src/WSRequestHandler_General.cpp b/src/WSRequestHandler_General.cpp index ea1c6a63..d42513bc 100644 --- a/src/WSRequestHandler_General.cpp +++ b/src/WSRequestHandler_General.cpp @@ -345,3 +345,73 @@ RpcResponse WSRequestHandler::OpenProjector(const RpcRequest& request) { obs_frontend_open_projector(type, monitor, geometry, name); return request.success(); } + +/** +* Executes hotkey routine, identified by hotkey unique name +* +* @param {String} `hotkeyName` Unique name of the hotkey, as defined when registering the hotkey (e.g. "ReplayBuffer.Save") +* +* @api requests +* @name TriggerHotkeyByName +* @category general +* @since unreleased +*/ +RpcResponse WSRequestHandler::TriggerHotkeyByName(const RpcRequest& request) { + const char* name = obs_data_get_string(request.parameters(), "hotkeyName"); + + obs_hotkey_t* hk = Utils::FindHotkeyByName(name); + if (!hk) { + return request.failed("hotkey not found"); + } + obs_hotkey_trigger_routed_callback(obs_hotkey_get_id(hk), true); + return request.success(); +} + +/** +* Executes hotkey routine, identified by bound combination of keys. A single key combination might trigger multiple hotkey routines depending on user settings +* +* @param {String} `keyId` Main key identifier (e.g. `OBS_KEY_A` for key "A"). Available identifiers [here](https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h) +* @param {Object (Optional)} `keyModifiers` Optional key modifiers object. False entries can be ommitted +* @param {boolean} `keyModifiers.shift` Trigger Shift Key +* @param {boolean} `keyModifiers.alt` Trigger Alt Key +* @param {boolean} `keyModifiers.control` Trigger Control (Ctrl) Key +* @param {boolean} `keyModifiers.command` Trigger Command Key (Mac) +* +* @api requests +* @name TriggerHotkeyBySequence +* @category general +* @since unreleased +*/ +RpcResponse WSRequestHandler::TriggerHotkeyBySequence(const RpcRequest& request) { + if (!request.hasField("keyId")) { + return request.failed("missing request keyId parameter"); + } + + OBSDataAutoRelease data = obs_data_get_obj(request.parameters(), "keyModifiers"); + + obs_key_combination_t combo = {0}; + uint32_t modifiers = 0; + if (obs_data_get_bool(data, "shift")) + modifiers |= INTERACT_SHIFT_KEY; + if (obs_data_get_bool(data, "control")) + modifiers |= INTERACT_CONTROL_KEY; + if (obs_data_get_bool(data, "alt")) + modifiers |= INTERACT_ALT_KEY; + if (obs_data_get_bool(data, "command")) + modifiers |= INTERACT_COMMAND_KEY; + + combo.modifiers = modifiers; + combo.key = obs_key_from_name(obs_data_get_string(request.parameters(), "keyId")); + + if (!modifiers + && (combo.key == OBS_KEY_NONE || combo.key >= OBS_KEY_LAST_VALUE)) { + return request.failed("invalid key-modifier combination"); + } + + // Inject hotkey press-release sequence + obs_hotkey_inject_event(combo, false); + obs_hotkey_inject_event(combo, true); + obs_hotkey_inject_event(combo, false); + + return request.success(); +} diff --git a/src/WSRequestHandler_Profiles.cpp b/src/WSRequestHandler_Profiles.cpp index 3d2d3a37..55d39b83 100644 --- a/src/WSRequestHandler_Profiles.cpp +++ b/src/WSRequestHandler_Profiles.cpp @@ -17,13 +17,22 @@ RpcResponse WSRequestHandler::SetCurrentProfile(const RpcRequest& request) { return request.failed("missing request parameters"); } - QString profileName = obs_data_get_string(request.parameters(), "profile-name"); - if (profileName.isEmpty()) { + const char* profileName = obs_data_get_string(request.parameters(), "profile-name"); + if (!profileName) { return request.failed("invalid request parameters"); } - // TODO : check if profile exists - obs_frontend_set_current_profile(profileName.toUtf8()); + char** profiles = obs_frontend_get_profiles(); + bool profileExists = Utils::StringInStringList(profiles, profileName); + bfree(profiles); + if (!profileExists) { + return request.failed("profile does not exist"); + } + + obs_queue_task(OBS_TASK_UI, [](void* param) { + obs_frontend_set_current_profile(reinterpret_cast(param)); + }, (void*)profileName, true); + return request.success(); } diff --git a/src/WSRequestHandler_SceneCollections.cpp b/src/WSRequestHandler_SceneCollections.cpp index 0359144e..77ce5d0a 100644 --- a/src/WSRequestHandler_SceneCollections.cpp +++ b/src/WSRequestHandler_SceneCollections.cpp @@ -17,13 +17,22 @@ RpcResponse WSRequestHandler::SetCurrentSceneCollection(const RpcRequest& reques return request.failed("missing request parameters"); } - QString sceneCollection = obs_data_get_string(request.parameters(), "sc-name"); - if (sceneCollection.isEmpty()) { + const char* sceneCollection = obs_data_get_string(request.parameters(), "sc-name"); + if (!sceneCollection) { return request.failed("invalid request parameters"); } - // TODO : Check if specified collection exists and if changing is allowed - obs_frontend_set_current_scene_collection(sceneCollection.toUtf8()); + char** collections = obs_frontend_get_scene_collections(); + bool collectionExists = Utils::StringInStringList(collections, sceneCollection); + bfree(collections); + if (!collectionExists) { + return request.failed("scene collection does not exist"); + } + + obs_queue_task(OBS_TASK_UI, [](void* param) { + obs_frontend_set_current_scene_collection(reinterpret_cast(param)); + }, (void*)sceneCollection, true); + return request.success(); } diff --git a/src/WSRequestHandler_SceneItems.cpp b/src/WSRequestHandler_SceneItems.cpp index 38771d22..b5334d89 100644 --- a/src/WSRequestHandler_SceneItems.cpp +++ b/src/WSRequestHandler_SceneItems.cpp @@ -5,8 +5,9 @@ /** * Get a list of all scene items in a scene. * -* @param {String} `sceneName` Name of the scene to get the list of scene items from. +* @param {String (optional)} `sceneName` Name of the scene to get the list of scene items from. Defaults to the current scene if not specified. * +* @return {String} `sceneName` Name of the requested (or current) scene * @return {Array} `sceneItems` Array of scene items * @return {int} `sceneItems.*.itemId` Unique item id of the source item * @return {String} `sceneItems.*.sourceKind` ID if the scene item's source. For example `vlc_source` or `image_source` @@ -19,12 +20,15 @@ * @since unreleased */ RpcResponse WSRequestHandler::GetSceneItemList(const RpcRequest& request) { - if (!request.hasField("sceneName")) { - return request.failed("missing request parameters"); + const char* sceneName = obs_data_get_string(request.parameters(), "sceneName"); + + OBSSourceAutoRelease sceneSource; + if (sceneName && strcmp(sceneName, "") != 0) { + sceneSource = obs_get_source_by_name(sceneName); + } else { + sceneSource = obs_frontend_get_current_scene(); } - const char* sceneName = obs_data_get_string(request.parameters(), "sceneName"); - OBSSourceAutoRelease sceneSource = obs_get_source_by_name(sceneName); OBSScene scene = obs_scene_from_source(sceneSource); if (!scene) { return request.failed("requested scene is invalid or doesnt exist"); @@ -64,6 +68,7 @@ RpcResponse WSRequestHandler::GetSceneItemList(const RpcRequest& request) { obs_scene_enum_items(scene, sceneItemEnumProc, sceneItemArray); OBSDataAutoRelease response = obs_data_create(); + obs_data_set_string(response, "sceneName", obs_source_get_name(sceneSource)); obs_data_set_array(response, "sceneItems", sceneItemArray); return request.success(response); diff --git a/src/WSRequestHandler_Scenes.cpp b/src/WSRequestHandler_Scenes.cpp index 18783119..9c317cdf 100644 --- a/src/WSRequestHandler_Scenes.cpp +++ b/src/WSRequestHandler_Scenes.cpp @@ -184,7 +184,7 @@ RpcResponse WSRequestHandler::ReorderSceneItems(const RpcRequest& request) { * @api requests * @name SetSceneTransitionOverride * @category scenes - * @since unreleased + * @since 4.8.0 */ RpcResponse WSRequestHandler::SetSceneTransitionOverride(const RpcRequest& request) { if (!request.hasField("sceneName") || !request.hasField("transitionName")) { @@ -230,7 +230,7 @@ RpcResponse WSRequestHandler::SetSceneTransitionOverride(const RpcRequest& reque * @api requests * @name RemoveSceneTransitionOverride * @category scenes - * @since unreleased + * @since 4.8.0 */ RpcResponse WSRequestHandler::RemoveSceneTransitionOverride(const RpcRequest& request) { if (!request.hasField("sceneName")) { @@ -266,7 +266,7 @@ RpcResponse WSRequestHandler::RemoveSceneTransitionOverride(const RpcRequest& re * @api requests * @name GetSceneTransitionOverride * @category scenes - * @since unreleased + * @since 4.8.0 */ RpcResponse WSRequestHandler::GetSceneTransitionOverride(const RpcRequest& request) { if (!request.hasField("sceneName")) { diff --git a/src/WSRequestHandler_Sources.cpp b/src/WSRequestHandler_Sources.cpp index 28039323..4968d238 100644 --- a/src/WSRequestHandler_Sources.cpp +++ b/src/WSRequestHandler_Sources.cpp @@ -586,6 +586,7 @@ RpcResponse WSRequestHandler::GetSourceSettings(const RpcRequest& request) const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); + if (!source) { return request.failed("specified source doesn't exist"); } @@ -646,21 +647,17 @@ RpcResponse WSRequestHandler::SetSourceSettings(const RpcRequest& request) } } - OBSDataAutoRelease currentSettings = obs_source_get_settings(source); OBSDataAutoRelease newSettings = obs_data_get_obj(request.parameters(), "sourceSettings"); - OBSDataAutoRelease sourceSettings = obs_data_create(); - obs_data_apply(sourceSettings, currentSettings); - obs_data_apply(sourceSettings, newSettings); - - obs_source_update(source, sourceSettings); + obs_source_update(source, newSettings); obs_source_update_properties(source); + OBSDataAutoRelease updatedSettings = obs_source_get_settings(source); + OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "sourceName", obs_source_get_name(source)); obs_data_set_string(response, "sourceType", obs_source_get_id(source)); - obs_data_set_obj(response, "sourceSettings", sourceSettings); - + obs_data_set_obj(response, "sourceSettings", updatedSettings); return request.success(response); } diff --git a/src/WSRequestHandler_Transitions.cpp b/src/WSRequestHandler_Transitions.cpp index 9bcee450..ef03352d 100644 --- a/src/WSRequestHandler_Transitions.cpp +++ b/src/WSRequestHandler_Transitions.cpp @@ -139,3 +139,68 @@ RpcResponse WSRequestHandler::GetTransitionPosition(const RpcRequest& request) { return request.success(response); } + +/** + * Get the current settings of a transition + * + * @param {String} `transitionName` Transition name + * + * @return {Object} `transitionSettings` Current transition settings + * + * @api requests + * @name GetTransitionSettings + * @category transitions + * @since unreleased + */ +RpcResponse WSRequestHandler::GetTransitionSettings(const RpcRequest& request) { + if (!request.hasField("transitionName")) { + return request.failed("missing request parameters"); + } + + const char* transitionName = obs_data_get_string(request.parameters(), "transitionName"); + OBSSourceAutoRelease transition = Utils::GetTransitionFromName(transitionName); + if (!transition) { + return request.failed("specified transition doesn't exist"); + } + + OBSDataAutoRelease transitionSettings = obs_source_get_settings(transition); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_obj(response, "transitionSettings", transitionSettings); + return request.success(response); +} + +/** + * Change the current settings of a transition + * + * @param {String} `transitionName` Transition name + * @param {Object} `transitionSettings` Transition settings (they can be partial) + * + * @return {Object} `transitionSettings` Updated transition settings + * + * @api requests + * @name SetTransitionSettings + * @category transitions + * @since unreleased + */ +RpcResponse WSRequestHandler::SetTransitionSettings(const RpcRequest& request) { + if (!request.hasField("transitionName") || !request.hasField("transitionSettings")) { + return request.failed("missing request parameters"); + } + + const char* transitionName = obs_data_get_string(request.parameters(), "transitionName"); + OBSSourceAutoRelease transition = Utils::GetTransitionFromName(transitionName); + if (!transition) { + return request.failed("specified transition doesn't exist"); + } + + OBSDataAutoRelease newSettings = obs_data_get_obj(request.parameters(), "transitionSettings"); + obs_source_update(transition, newSettings); + obs_source_update_properties(transition); + + OBSDataAutoRelease updatedSettings = obs_source_get_settings(transition); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_obj(response, "transitionSettings", updatedSettings); + return request.success(response); +} diff --git a/src/WSServer.cpp b/src/WSServer.cpp index 33f1bcbb..add0e1e1 100644 --- a/src/WSServer.cpp +++ b/src/WSServer.cpp @@ -60,10 +60,10 @@ WSServer::~WSServer() stop(); } -void WSServer::start(quint16 port) +void WSServer::start(quint16 port, bool lockToIPv4) { - if (_server.is_listening() && port == _serverPort) { - blog(LOG_INFO, "WSServer::start: server already on this port. no restart needed"); + if (_server.is_listening() && (port == _serverPort && _lockToIPv4 == lockToIPv4)) { + blog(LOG_INFO, "WSServer::start: server already on this port and protocol mode. no restart needed"); return; } @@ -74,9 +74,16 @@ void WSServer::start(quint16 port) _server.reset(); _serverPort = port; + _lockToIPv4 = lockToIPv4; websocketpp::lib::error_code errorCode; - _server.listen(_serverPort, errorCode); + if (lockToIPv4) { + blog(LOG_INFO, "WSServer::start: Locked to IPv4 bindings"); + _server.listen(websocketpp::lib::asio::ip::tcp::v4(), _serverPort, errorCode); + } else { + blog(LOG_INFO, "WSServer::start: Not locked to IPv4 bindings"); + _server.listen(_serverPort, errorCode); + } if (errorCode) { std::string errorCodeMessage = errorCode.message(); diff --git a/src/WSServer.h b/src/WSServer.h index 723f51af..1ca0734b 100644 --- a/src/WSServer.h +++ b/src/WSServer.h @@ -44,7 +44,7 @@ Q_OBJECT public: explicit WSServer(); virtual ~WSServer(); - void start(quint16 port); + void start(quint16 port, bool lockToIPv4); void stop(); void broadcast(const RpcEvent& event); QThreadPool* threadPool() { @@ -62,6 +62,7 @@ private: server _server; quint16 _serverPort; + bool _lockToIPv4; std::set> _connections; std::map> _connectionProperties; QMutex _clMutex; diff --git a/src/forms/settings-dialog.cpp b/src/forms/settings-dialog.cpp index 6aa2d4f1..ca686dfe 100644 --- a/src/forms/settings-dialog.cpp +++ b/src/forms/settings-dialog.cpp @@ -45,6 +45,7 @@ void SettingsDialog::showEvent(QShowEvent* event) { ui->serverEnabled->setChecked(conf->ServerEnabled); ui->serverPort->setValue(conf->ServerPort); + ui->lockToIPv4->setChecked(conf->LockToIPv4); ui->debugEnabled->setChecked(conf->DebugEnabled); ui->alertsEnabled->setChecked(conf->AlertsEnabled); @@ -72,6 +73,7 @@ void SettingsDialog::FormAccepted() { conf->ServerEnabled = ui->serverEnabled->isChecked(); conf->ServerPort = ui->serverPort->value(); + conf->LockToIPv4 = ui->lockToIPv4->isChecked(); conf->DebugEnabled = ui->debugEnabled->isChecked(); conf->AlertsEnabled = ui->alertsEnabled->isChecked(); @@ -95,7 +97,7 @@ void SettingsDialog::FormAccepted() { auto server = GetServer(); if (conf->ServerEnabled) { - server->start(conf->ServerPort); + server->start(conf->ServerPort, conf->LockToIPv4); } else { server->stop(); } diff --git a/src/forms/settings-dialog.ui b/src/forms/settings-dialog.ui index 6964a153..237fb932 100644 --- a/src/forms/settings-dialog.ui +++ b/src/forms/settings-dialog.ui @@ -2,150 +2,157 @@ SettingsDialog - - - 0 - 0 - 407 - 195 - - - - - 0 - 0 - - - - OBSWebsocket.Settings.DialogTitle - - - false - - - - QLayout::SetDefaultConstraint - - - - - - - OBSWebsocket.Settings.AuthRequired - - - - - - - OBSWebsocket.Settings.Password - - - - - - - QLineEdit::Password - - - - - - - OBSWebsocket.Settings.ServerEnable - - - true - - - - - - - OBSWebsocket.Settings.ServerPort - - - - - - - 1024 - - - 65535 - - - 4444 - - - - - - - OBSWebsocket.Settings.AlertsEnable - - - true - - - - - - - OBSWebsocket.Settings.DebugEnable - - - false - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + + + 0 + 0 + 407 + 216 + + + + + 0 + 0 + + + + OBSWebsocket.Settings.DialogTitle + + + false + + + + QLayout::SetDefaultConstraint + + + + + + + OBSWebsocket.Settings.AuthRequired + + + + + + + OBSWebsocket.Settings.Password + + + + + + + QLineEdit::Password + + + + + + + OBSWebsocket.Settings.ServerEnable + + + true + + + + + + + OBSWebsocket.Settings.ServerPort + + + + + + + 1024 + + + 65535 + + + 4444 + + + + + + + OBSWebsocket.Settings.AlertsEnable + + + true + + + + + + + OBSWebsocket.Settings.DebugEnable + + + false + + + + + + + OBSWebsocket.Settings.LockToIPv4 + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + - - buttonBox - accepted() - SettingsDialog - accept() - - - 248 - 294 - - - 157 - 314 - - - - - buttonBox - rejected() - SettingsDialog - reject() - - - 316 - 300 - - - 286 - 314 - - - + + buttonBox + accepted() + SettingsDialog + accept() + + + 248 + 294 + + + 157 + 314 + + + + + buttonBox + rejected() + SettingsDialog + reject() + + + 316 + 300 + + + 286 + 314 + + + diff --git a/src/obs-websocket.cpp b/src/obs-websocket.cpp index 9f19ab9e..b198ebb7 100644 --- a/src/obs-websocket.cpp +++ b/src/obs-websocket.cpp @@ -81,7 +81,7 @@ bool obs_module_load(void) { auto eventCallback = [](enum obs_frontend_event event, void *param) { if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) { if (_config->ServerEnabled) { - _server->start(_config->ServerPort); + _server->start(_config->ServerPort, _config->LockToIPv4); } obs_frontend_remove_event_callback((obs_frontend_event_cb)param, nullptr); }