Compare commits

..

1189 Commits
0.3.1 ... 4.8.0

Author SHA1 Message Date
5d12dfa368 docs(ci): Update protocol.md - c428201 [skip ci] 2020-05-26 21:49:10 +00:00
c4282011fa Merge pull request #516 from Palakis/feature/SetSceneTransitionOverride
Requests: Add scene transition override requests
2020-05-26 23:48:33 +02:00
9ddc22fa94 request(SetSceneTransitionOverride): override duration only if it is explicity set so or not already defined 2020-05-26 23:36:18 +02:00
517788e86a request(scenes): refactor Set/Get/RemoveSceneTransitionOverride + use current transition duration 2020-05-26 23:26:37 +02:00
ed29ec528d docs(ci): Update protocol.md - 481b26d [skip ci] 2020-05-26 11:58:51 +00:00
481b26d09d Merge pull request #508 from Palakis/feature/setsourcename
Requests: Add SetSourceName (renaming sources)
2020-05-26 13:58:10 +02:00
da66ad6d41 Merge branch '4.x-current' into feature/setsourcename 2020-05-26 01:41:11 -07:00
1f75b305c3 docs(ci): Update protocol.md - 0802d03 [skip ci] 2020-05-26 07:42:47 +00:00
0802d03d74 Merge pull request #509 from Palakis/enhancement/sourcerenamed-isscene
Events: Add sourceType string to SourceRenamed
2020-05-26 09:42:03 +02:00
3970674003 Merge pull request #510 from Palakis/fix/fixwarnings
General: Fix a few warnings
2020-05-22 09:11:27 +02:00
19b33a8558 Requests: Add scene transition override requests
Adds these requests:
- `SetSceneTransitionOverride`
- `RemoveSceneTransitionOverride`
- `GetSceneTransitionOverride`
2020-05-18 14:44:25 -07:00
7bb91da1d0 Events: Add sourceType string to SourceRenamed
Since sources in OBS are also scenes, provide the user a string
so that they can differentiate.
2020-05-15 16:54:30 -07:00
e3e62c3903 General: Fix a few warnings
Fix a few build warnings on windows because im ocd
2020-05-15 16:52:08 -07:00
631ed022ed Requests: Add SetSourceName (renaming sources)
Adds the `SetSourceName` request which can be used to rename sources.

Also works on scenes since scenes in OBS are also sources.
2020-05-15 16:13:24 -07:00
590943ed95 docs(ci): Update protocol.md - e9c17c9 [skip ci] 2020-05-15 02:20:46 +00:00
e9c17c9a1d Merge pull request #496 from Palakis/request-setsourcemonitortype
Requests: Add audio monitoring requests
2020-05-15 04:20:07 +02:00
f8c8e42ae9 requests: rename Get/SetAudioMonitor to Get/SetAudioMonitorType 2020-05-15 04:10:40 +02:00
0b0560019a requests(Get/SetAudioMonitor): change values to camelCase for consistency 2020-05-15 04:08:40 +02:00
133d3fdda7 docs(ci): Update protocol.md - 53e98db [skip ci] 2020-05-15 00:48:51 +00:00
53e98dbe15 Merge pull request #484 from Palakis/add-screenshot-params
Requests: Add new parameters to TakeSourceScreenshot
2020-05-15 02:48:08 +02:00
0eaa9187ea requests(TakeSourceScreenshot): improved docs 2020-05-15 02:35:53 +02:00
344f5bda69 docs(ci): Update protocol.md - 33b080b [skip ci] 2020-05-14 20:48:16 +00:00
33b080b3b8 Merge pull request #495 from Palakis/volume-decibel-support
Requests: Add useDecibel bool to GetVolume and SetVolume
2020-05-14 22:47:33 +02:00
1c85894472 request(SetVolume): simplified params check 2020-05-14 22:31:10 +02:00
ba4e5959b1 requests(Get/SetVolume): code nitpicks 2020-05-14 22:24:58 +02:00
5534f9a248 docs(ci): Update protocol.md - eb20654 [skip ci] 2020-05-14 20:15:44 +00:00
eb206549ff Merge pull request #503 from Palakis/feature/get-tr-pos
Requests: Add GetTransitionPosition
2020-05-14 22:14:59 +02:00
0852f32b05 Merge branch '4.x-current' into request-setsourcemonitortype 2020-05-14 05:13:29 -07:00
00da210365 Merge pull request #504 from Palakis/bugfix/windows-ci-prepare-obs
ci(windows): simplified OBS Preparation script + shorter build times
2020-05-14 11:11:08 +02:00
bce6c86e60 Merge pull request #481 from Palakis/tt2468-ubuntu-fix
Fix install dir on Ubuntu by using new cmake flag
2020-05-14 11:04:36 +02:00
bed6a1b1e2 ci(windows): simplified OBS Preparation script + shorter build times 2020-05-14 11:02:51 +02:00
497443f012 Update CI scripts to use ubuntu fix 2020-05-14 01:47:55 -07:00
75a06afab0 Merge pull request #493 from Palakis/studio-mode-errors
Add error system to EnableStudioMode and DisableStudioMode
2020-05-14 10:34:05 +02:00
56371a7d80 Add GetTransitionPosition
Gets the current position of the active transition. Works on manual and also auto transition modes
2020-05-14 01:11:47 -07:00
2810787156 docs(ci): Update protocol.md - 6b53cb5 [skip ci] 2020-05-14 08:04:35 +00:00
6b53cb59a5 Merge pull request #501 from Palakis/tt2468-bump-docs
[Docs] Bump version to 4.8
2020-05-14 10:03:51 +02:00
71392613b2 [Docs] Bump version to 4.8 2020-05-12 17:46:49 -07:00
ff21f5b357 docs(ci): Update protocol.md - 98db248 [skip ci] 2020-05-12 09:37:41 +00:00
98db248776 Merge pull request #491 from Palakis/depricate-browserrequests
Deprecate SetBrowserSourceProperties and GetBrowserSourceProperties
2020-05-12 11:37:00 +02:00
ed4872b94c Merge pull request #483 from tt2468/add-http-request-script
Add obs-websocket-http
2020-05-12 11:33:04 +02:00
8c5c6958cf Merge pull request #476 from tt2468/patch-2
[Bug Fix] Fix FreeType2 source type recognition
2020-05-12 11:30:20 +02:00
f9c81f99f2 Install into both dirs on Ubuntu instead of only one 2020-05-12 02:24:27 -07:00
88c72cd80a Add audio monitoring request items. 2020-05-10 20:07:57 -07:00
728ea16701 Introduce useDecibel bool to GetVolume and SetVolume to give a better, built-in option for people to use decibels of attenuation instead of amplitude. 2020-05-10 18:39:24 -07:00
a3bc9f768a Add error system to EnableStudioMode and DisableStudioMode
As talked about in #144 this adds a check to `EnableStudioMode` and `DisableStudioMode` in order to be consistent with `StartRecording`/`StopRecording`
2020-05-08 15:30:40 -07:00
8d88bc19ed Deprecate SetBrowserSourceProperties and GetBrowserSourceProperties
Both of these requests are doing essentially the same thing as `GetSourceSettings` and `SetSourceSettings`, so there is no reason to have the extra bloat.
2020-05-08 13:15:24 -07:00
333ffa0e89 compressionQuality should be for embed and file saving 2020-04-30 20:17:11 -07:00
4ded810ba9 Add optional fileFormat and fileCompressionRatio parameters to TakeSourceScreenshot 2020-04-30 20:09:13 -07:00
b2aa54f3f8 Add obs-websocket-http
obs-websocket-http is a simple python script which allows users to run obs-websocket requests using an http-based api system. It is very simple and is a useful converter.
2020-04-30 16:25:31 -07:00
97836cc5eb Remove extra source type strings 2020-04-30 10:58:10 -07:00
b1df0dca97 Merge pull request #474 from tt2468/patch-1
Suppress websocketpp logging entries
2020-04-30 19:40:22 +02:00
4a6816575a Merge pull request #482 from Palakis/tt2468-start-error
Add websocketpp error message to error dialog box
2020-04-30 19:39:50 +02:00
31e2937b8d Merge pull request #477 from tt2468/patch-3
[Bug Fix] Don't build with captions on MacOS
2020-04-30 19:38:56 +02:00
0f434004a8 Add websocketpp error message to error dialog box 2020-04-30 10:08:56 -07:00
ba75c45cee Update build docs to add Ubuntu fix flag 2020-04-30 09:58:10 -07:00
47492c3fa2 Implement fix for Ubuntu users
#478 Broke building on linux. This implements a new variable to apply the fix.
2020-04-30 09:46:22 -07:00
819917c4bf Merge pull request #478 from Palakis/bugfix/ubuntu-packaging-fixes
Ubuntu packaging fixes
2020-04-28 11:40:20 +02:00
1ce0fd643c Revert "ci(linux): set package summary"
This reverts commit a7d02a79a9.
2020-04-28 11:18:22 +02:00
a7d02a79a9 ci(linux): set package summary 2020-04-28 10:29:48 +02:00
645cbf9888 ci(linux): fix debian package requires 2020-04-28 10:18:54 +02:00
b2d39ab2d7 cmake(linux): include GNUInstallDirs 2020-04-28 09:52:11 +02:00
5843521cf1 cmake(linux): use the standard LIBDIR and DATAROOTDIR install locations 2020-04-28 09:46:45 +02:00
4fbc45b40b ci(linux): make the deb package require OBS 25.0.7 2020-04-28 09:34:41 +02:00
2719da3685 [Bug Fix] Don't build with captions on MacOS
According to [this](https://github.com/obsproject/obs-studio/blob/master/UI/frontend-plugins/frontend-tools/CMakeLists.txt#L73), captions support is not active on MacOS, so we should not build it here.
2020-04-27 10:55:16 -07:00
f61bc809dd [Bug Fix] Fix FreeType2 source type recognition
OBS appears to be using a new format for the FreeType2 source, so we add the new kinds.

One thing I'm unsure on is if anything still uses the old naming stuff `text_ft2`/`text_ft2_v2`, since if not then we should not check
2020-04-27 08:30:39 -07:00
19f9593ac1 Supress websocketpp logging entries 2020-04-21 19:55:43 -07:00
e277cae799 Merge pull request #469 from tt2468/patch-1
Add simpleobsws to list of language APIs
2020-04-21 15:40:51 +02:00
05baf6b8ac Add simpleobsws to list of language APIs
I've created a simple async Python library for people who just want the simple stuff.
2020-04-21 05:30:21 -07:00
758441d65d readme: fix CI badges 2020-04-17 15:00:51 +02:00
631a008fa0 readme: fixes 2020-04-15 10:36:12 +02:00
54e79936e5 ci(macos): add automated codesigning + notarization (#464) 2020-04-10 21:41:45 +02:00
a833822eae docs(ci): Update protocol.md - cfa8d8d [skip ci] 2020-04-10 16:27:48 +00:00
cfa8d8d180 Merge pull request #463 from tt2468/patch-1
[Docs] Fix link to GetCurrentScene
2020-04-10 18:27:18 +02:00
b692756260 The actual fix 2020-04-10 08:14:25 -07:00
dbc62ad63a [Docs] Fix link to GetCurrentScene
For some reason the link to the `GetCurrentScene` section of the docs was put into a codeblock
2020-04-10 08:08:02 -07:00
2ac97dea35 installers: bump version 2020-04-07 17:35:49 +02:00
9451814299 ci(windows): fix installer suffix 2020-04-07 17:34:28 +02:00
5e01283018 Merge pull request #461 from Palakis/bugfix/disable-msvc-fh4
cmake(msvc): disable FH4
2020-04-06 20:00:27 +02:00
f7512c3cc0 cmake(msvc): disable FH4 2020-04-06 19:40:04 +02:00
f4997375e9 cmake: add version + fix /MP msvc flag 2020-04-06 19:09:01 +02:00
fbad54795e Merge pull request #453 from Palakis/bugfix/macos-qt-path
ci(macos): fix install_name_tool packaging step
2020-04-03 10:13:52 +02:00
2ca85d05f0 ci(macos): fix install_name_tool packaging step 2020-04-01 08:51:48 +02:00
67e89e79be docs(ci): Update protocol.md - e467666 [skip ci] 2020-03-29 22:20:01 +00:00
e4676668bb Merge pull request #449 from Palakis/bugfix/get-set-sceneitemproperties-item-id
Target Scene Items per ID, in addition to scene name and source name
2020-03-30 00:19:27 +02:00
3604995c2f requests(DeleteSceneItem + DuplicateSceneItem): backport dual "item" behaviour to existing functions 2020-03-30 00:02:36 +02:00
8b731f3ba4 fixes 2020-03-29 23:55:45 +02:00
83f702fbab requests(GetSceneItemProperties): add itemId response field 2020-03-29 23:28:07 +02:00
fbebf1c7d3 requests(scene item): fix docs 2020-03-29 23:04:20 +02:00
be14947668 oops 2020-03-29 22:12:54 +02:00
0fcf770043 requests(scene items): implement object mode on "item" request field 2020-03-29 22:07:55 +02:00
51ec3ede1f utils: add GetSceneItemFromRequestField 2020-03-29 21:42:10 +02:00
4e2302936f refactor bits 2020-03-29 21:41:38 +02:00
a6c6a42669 requests(scene items): doc fixes again 2020-03-29 19:39:57 +02:00
c486bc64c4 requests(scene items): refactor docs 2020-03-29 19:39:57 +02:00
3709ea1a95 docs(clarity): move scene items events into a proper "Scene Items" category
Namely:
- SourceOrderChanged
- SceneItemAdded
- SceneItemRemoved
- SceneItemVisibilityChanged
- SceneItemLockChanged
- SceneItemTransformChanged
- SceneItemSelected
- SceneItemDeselected
2020-03-29 19:39:57 +02:00
be7fa79327 docs(ci): Update protocol.md - c1660f8 [skip ci] 2020-03-29 17:39:56 +00:00
c1660f8ca2 chore: bump unreleased requests/events to 4.8.0 2020-03-29 19:39:19 +02:00
5e41eaf3dd Merge pull request #448 from Palakis/bugfix/text-gdiplus-v2
Allow Get/Set text gdiplus/ft2 properties functions to operate on v2 sources
2020-03-29 16:23:13 +02:00
898e761988 requests(freetype2 source): allow functions to operate on v2 sources 2020-03-29 16:13:28 +02:00
846d52ebe5 requests(gdiplus source): allow functions to operate on v2 sources 2020-03-29 16:09:16 +02:00
77ac6a7a26 Merge pull request #420 from Palakis/bugfix/crash-get-streaming-time
Fix crash when sending an event on exit
2020-03-29 15:57:07 +02:00
f23ba0c513 Merge branch '4.x-current' into bugfix/crash-get-streaming-time 2020-03-28 19:17:53 +01:00
6a733bbb13 Migrate Windows & Linux builds to Azure Pipelines (#422) 2020-03-28 19:13:55 +01:00
540e1f562f Merge pull request #434 from Palakis/bugfix/enablestudiomode-crash
Fix crash when calling EnableStudioMode/DisableStudioMode/ToggleStudioMode
2020-03-18 01:07:21 +01:00
bfcd16ea28 requests(studio mode): fix crash when calling frontend_set_preview_program_mode 2020-03-18 01:01:33 +01:00
3a8703de87 Merge pull request #438 from Palakis/feature/remove-dynamic-load-functions
Remove dynloaded functions
2020-03-17 23:57:45 +01:00
247ca71bf9 utils: remove dynloaded function for projector open 2020-03-17 23:46:24 +01:00
88d39ab47a utils: remove dynamic loaded functions for recording pause 2020-03-17 23:45:57 +01:00
a5af45fb31 ci(xenial): update frontend api to 25.0.0 2020-03-17 23:41:00 +01:00
dc04cb54ab chore: bump to 4.8.0 2020-03-17 23:37:49 +01:00
c9fa82f073 Merge pull request #437 from Palakis/bugfix/takesourcescreenshot-jpg
Fix JPEG support in TakeSourceScreenshot on Windows
2020-03-17 22:14:35 +01:00
c418cbf4ef cmake(windows): fix qjpeg path 2020-03-17 20:08:46 +01:00
3450c6b9a3 cmake(windows): copy qjpeg on RelWithDebInfo as well 2020-03-17 19:52:49 +01:00
1ada33a4f0 package(deb): fix ambiguous syntax 2020-03-17 19:41:43 +01:00
a669852694 cmake(windows): oops, fix copy command 2020-03-17 19:37:39 +01:00
a5b6ea6c4c cmake(windows): copy qt image format plugins 2020-03-17 19:34:53 +01:00
fe217ef5df package(deb): fix email address 2020-03-17 18:43:01 +01:00
e0a25dbf48 package(deb): add Qt image formats plugins dependency 2020-03-17 18:16:26 +01:00
86191aff7f docs(travis): Update protocol.md - 6bed39e [skip ci] 2020-03-15 22:22:42 +00:00
6bed39e4fa Merge pull request #410 from Lange/feat/transition-end-events
feat(events): add "TransitionEnd" and "TransitionVideoEnd" events
2020-03-15 23:21:53 +01:00
2a7aa432a9 events(TransitionEnd): remove from-scene because it's not available in transition_stop 2020-03-15 23:20:56 +01:00
4e19e41460 Merge branch 'bugfix/openprojector-build-error' into 4.x-current 2020-03-12 17:51:57 +01:00
061f5d5099 requests(OpenProjector): get function pointer from lib dynamically 2020-03-12 17:44:56 +01:00
796bdebd90 requests(OpenProjector): wrap version check with parentheses 2020-03-12 17:27:28 +01:00
010102da25 ci(linux): download frontend API headers from stable 2020-03-12 17:06:09 +01:00
d8f2aeb004 requests(OpenProjector): refactor libobs version check 2020-03-12 17:03:00 +01:00
5173338949 linux: add proper dependencies list in Deb package 2020-03-12 15:40:28 +01:00
a8b00b026e docs(travis): Update protocol.md - d4db0bd [skip ci] 2020-03-12 13:55:33 +00:00
d4db0bdfe5 Merge pull request #431 from Palakis/feature/list-supported-image-formats
request(GetVersion): add supported image export formats list
2020-03-12 14:54:41 +01:00
b26cd901ca request(GetVersion): add supported image export formats list 2020-03-12 10:58:30 +01:00
1e2fc783e5 Merge pull request #429 from Palakis/bugfix/negative-sync-offset
requests(SetSyncOffset): allow negative offsets
2020-03-11 14:18:59 +01:00
8667333416 ci(macos): switch to macOS 10.14 2020-03-11 14:13:22 +01:00
5088d7dd86 requests(SetSyncOffset): allow negative offsets 2020-03-11 14:10:38 +01:00
e78f26e9c0 Merge pull request #428 from Rosuav/add-muted-flag
Add a 'muted' flag to GetSceneItems
2020-03-11 14:06:12 +01:00
be10227829 Utils,GetSceneItemProperties: Document both muted and alignment 2020-03-11 23:02:23 +11:00
ef47c172c5 Utils: Add a 'muted' flag to GetSceneItems 2020-03-11 22:52:33 +11:00
053faa1661 docs(travis): Update protocol.md - 6d63779 [skip ci] 2020-03-11 11:12:19 +00:00
6d63779c70 Merge pull request #338 from Rosuav/open-projector
Create a command for opening a projector.
2020-03-11 12:11:23 +01:00
62ad1a483c docs(OpenProjector): add info on geometry encoding 2020-03-11 12:09:08 +01:00
f4f760a231 Punt on explaining what the geometry string actually means 2020-03-11 22:05:37 +11:00
0eaef1ac3b docs(travis): Update protocol.md - 27245cc [skip ci] 2020-03-05 09:24:10 +00:00
27245cca1a Merge pull request #426 from Palakis/bugfix/setstreamsettings-docs-typo
docs: replace stream service option use-auth with use_auth
2020-03-05 10:23:11 +01:00
afcc11af57 docs: replace stream service option use-auth with use_auth 2020-03-05 10:08:59 +01:00
9fc41e4245 Apply suggestions from code review
Fixes all the easy ones. I'll look into the more substantive changes in a bit.

Co-Authored-By: Stéphane Lepin <Palakis@users.noreply.github.com>
2020-03-05 05:14:46 +11:00
a0e0910117 docs(travis): Update protocol.md - 8a7d7a4 [skip ci] 2020-03-04 13:11:56 +00:00
8a7d7a41a4 Merge pull request #321 from Rosuav/item-locked-event
Fire an event whenever an item is locked/unlocked
2020-03-04 14:10:56 +01:00
45d5f4a760 events: rename SceneItemLockedChanged to SceneItemLockChanged 2020-03-04 14:04:26 +01:00
101b795c0a Merge pull request #324 from Rosuav/show-gravity
Show alignment (gravity) of scene elements when querying/switching scenes
2020-03-04 13:49:58 +01:00
8311bd3fd9 Merge pull request #416 from tt2468/4.x-current
Fix SetStreamSettings when switching type
2020-03-04 11:37:01 +01:00
ec1926f7a6 events: fix @since value for SceneItemLockedChanged 2020-03-04 11:26:48 +01:00
e1c92956f8 docs(travis): Update protocol.md - 9233d4f [skip ci] 2020-03-04 09:57:04 +00:00
9233d4f6dc Merge pull request #411 from Lange/fix/fixed-duration-transitions
fix(events): don't advertise a false duration for fixed transitions
2020-03-04 10:56:14 +01:00
f2f5661625 cmake: fix redundant c++ standard flag 2020-02-25 15:32:47 +01:00
4338cf57e6 OBSRemoteProtocol: rely only on event data when encoding an event 2020-02-25 15:24:26 +01:00
a0dce77a2f RpcEvent: streamTime and recordingTime are optional 2020-02-25 15:23:24 +01:00
ca93882870 Remove unnecessary utf8 string conversion 2020-02-15 14:51:59 -08:00
22fbb4262c Return check type instead of type provided in request 2020-02-15 14:47:46 -08:00
2845fd07a7 Final fixes before testing 2020-02-15 14:22:35 -08:00
c02f40beb6 Update WSRequestHandler_Streaming.cpp 2020-02-15 14:18:25 -08:00
79a7b444a2 Update WSRequestHandler_Streaming.cpp 2020-02-15 11:00:24 -08:00
9c11bbc7a1 Update WSRequestHandler_Streaming.cpp 2020-02-15 10:58:10 -08:00
e1cb57563a fix(events): don't advertise a false duration for fixed transitions
Some transitions, such as Stingers, have a fixed duration. There is currently no way to retrieve this value from the API, and `obs-websocket` will currently return an incorrect value when one of these transitions begins.

For now, I think it is better to explicitly return -1, which is a better indicator that we have no idea what the real duration is, instead of a misleading number which users might think looks correct at first glance.
2020-02-15 11:16:49 -06:00
1057d765f7 feat(events): add "TransitionEnd" and "TransitionVideoEnd" events
Adds events for `TransitionEnd` and `TransitionVideoEnd`.

`TransitionVideoEnd` is specific to Stinger transitions. It is fired when their video has actually completed playback. This is important, because `TransitionEnd` is fired at the cut point of the Stinger, which is nice to have but isn't always what the user might want.

This builds on #409, and should be merged after it.
2020-02-15 11:15:46 -06:00
30db1a8a63 Update WSRequestHandler_Streaming.cpp 2020-02-15 08:50:09 -08:00
c7b49b28c2 docs(travis): Update protocol.md - 40ea7cf [skip ci] 2020-02-15 16:29:59 +00:00
40ea7cfe15 Merge pull request #409 from Lange/feat/transition-type
feat(events): add "type" field to TransitionBegin
2020-02-15 17:29:08 +01:00
b3b2ae267a Update WSRequestHandler_Streaming.cpp 2020-02-15 07:39:01 -08:00
88176f4d2b Merge pull request #413 from Palakis/bugfix/event-recording-time-fix
WSEvents: fix recording time field after refactoring
2020-02-15 14:45:49 +01:00
6917ac48a3 WSEvents: fix recording time field 2020-02-15 13:00:58 +01:00
c326d65ad3 feat(events): add "type" field to TransitionBegin
This field reports the type of the transition (`obs_stinger_transition`, `fade_to_color_transition`, etc), which can be useful for code which needs to react differently depending on what type of transition is being played.

Of note is that OBS calls this an `id`, but I think that's kind of a misleading name. `type` seems like a more semantic name for this information to me, but I'm open to changing it if we want to stay consistent with the nomenclature that OBS itself uses.
2020-02-06 18:04:08 -06:00
e1a2f0c0f4 Merge pull request #405 from Palakis/utils-to-namespace
Utils: turn it into a namespace
2020-01-29 18:20:55 +01:00
6953bc6105 Utils: turn it into a namespace 2020-01-29 17:24:45 +01:00
209555d36d Merge branch '4.x-current' into open-projector 2020-01-29 14:23:47 +01:00
e65958f33e Merge pull request #390 from Palakis/handler-protocol-refactor
Request handler refactor
2020-01-29 14:06:46 +01:00
02fea8938a OBSRemoteProtocol(processMessage): fix field order 2020-01-29 13:16:47 +01:00
f5277e4931 Merge branch '4.x-current' into handler-protocol-refactor 2020-01-29 12:30:24 +01:00
d78ed32df5 Merge pull request #404 from Palakis/bugfix/memory-leaks
Fix memory leaks in GetCurrentProfile, GetCurrentSceneCollection and Heartbeat event
2020-01-29 12:28:48 +01:00
8447395482 requests(GetCurrentSceneCollection): fix memory leak 2020-01-29 12:20:23 +01:00
915a7b6e15 requests(GetCurrentProfile): fix memory leak 2020-01-29 12:18:11 +01:00
b70c39789e events(Heartbeat): fix memory leak 2020-01-29 12:17:15 +01:00
865cb4857b RpcEvent: refactor fields to additionalFields 2020-01-29 11:13:42 +01:00
774abd66fa RpcEvent + RpcRequest: move accessors into declaration 2020-01-28 18:59:35 +01:00
e52d86e6f9 Merge branch '4.x-current' into handler-protocol-refactor 2020-01-28 18:49:49 +01:00
3c0b5b0b43 Utils: fix memory leak in StartReplayBuffer 2020-01-28 18:49:37 +01:00
46068573c5 events: decouple events encoding from event emit 2020-01-28 18:37:25 +01:00
06c29b7810 Merge branch '4.x-current' into handler-protocol-refactor 2020-01-28 17:05:15 +01:00
631452567d OBSRemoteProtocol: correctly handling null message IDs 2020-01-28 17:02:25 +01:00
6c34f00cfe OBSRemoteProtocol(errorResponse): add missing additionalFields 2020-01-28 16:52:11 +01:00
208c7f18b2 OBSRemoteProtocol: refactor class + pass handler when calling processMessage 2020-01-28 16:49:35 +01:00
5e393ed3c4 OBSRemoteProtocol(buildResponse): fix release of obs_data_t 2020-01-28 16:41:42 +01:00
8b16abd370 OBSRemoteProtocol: fix mixed indents 2020-01-28 16:38:57 +01:00
94c232d78e docs(travis): Update protocol.md - 4b41c52 [skip ci] 2020-01-28 15:32:31 +00:00
4b41c52522 Merge pull request #397 from justweb1/patch-1
Fix SetSourceFilterVisibility filterEnabled type
2020-01-28 16:31:38 +01:00
6bfe533a20 chore: fix quality warnings 2020-01-28 16:16:19 +01:00
f0b8aad4b1 docs(SetSourceFilterVisibility): fix filterEnabled type 2020-01-28 16:07:19 +01:00
796cf36a7f Merge pull request #400 from dmerrick/patch-1
Update README to include MacOS
2020-01-28 15:54:26 +01:00
7706d66754 Merge pull request #401 from dmerrick/patch-2
Update README to add Golang package
2020-01-28 15:54:02 +01:00
d992536914 Update README.md 2019-12-29 13:05:16 -05:00
286b76672c Update README.md 2019-12-29 12:50:05 -05:00
f0d1b5a1e4 Fix SetSourceFilterVisibility filterEnabled type
Corrected from String to Boolean
2019-12-19 02:14:45 -06:00
71ad8930d8 Merge pull request #393 from talk2MeGooseman/4.x-current
Switch from using caption_text1 to caption_text2
2019-11-29 12:08:19 +01:00
0f6cbb0c5c Switch from using caption_text1 to caption_text2
- caption_text1 has a built in delay of 2 seconds for all captions that causes every increasing delays for captions
2019-11-28 12:59:36 -08:00
78584b3194 Fix version number in message 2019-11-26 00:59:57 +11:00
c0eb4d652b Mention the OBS version requirement in the docos 2019-11-26 00:58:28 +11:00
edbb28038f Guard open_projector() with a version check.
I can't find a way to probe for an actual feature here.
2019-11-25 22:36:17 +11:00
3b7e9b4eba Create a command for opening a projector.
Depends on obs-studio#1910 for back end functionality.
2019-11-25 16:21:28 +11:00
6d0d07abfe Fire an event whenever an item is locked/unlocked 2019-11-25 16:21:17 +11:00
e9f90b4990 OBSRemoteProtocol: fix params 2019-11-15 23:39:31 +01:00
d25b65c124 RpcResponse: fixes 2019-11-15 22:59:36 +01:00
5864864123 WSServer: reimplement protocol 2019-11-15 21:07:58 +01:00
2f244ae37e WSRequestHandler(SceneItems): fix error message 2019-11-15 20:54:06 +01:00
f83317f7c8 WSRequestHandler(SceneItems): fix DuplicateSceneItem 2019-11-15 20:39:39 +01:00
973b4b9f70 RpcRequest: fix forward declarations 2019-11-15 20:33:26 +01:00
9389ceaf4f WSRequestHandler(SceneItems): fixes 2019-11-15 20:33:13 +01:00
928dc30810 WSRequestHandler(Sources): fixes 2019-11-15 20:30:44 +01:00
c9d6d10995 WSRequesthandler(Scenes): fixes 2019-11-15 20:29:22 +01:00
9323e1cf59 WSRequestHandler(Streaming): fixes 2019-11-15 20:28:42 +01:00
93a456ca82 WSRequestHandler(StudioMode): fixes 2019-11-15 20:27:27 +01:00
f6693d5587 WSRequestHandler(Transitions): fixes 2019-11-15 20:26:44 +01:00
fd09a0ce54 WSRequestHandler(SceneCollections): fixes 2019-11-15 20:26:00 +01:00
50d3a2600e WSRequestHandler(ReplayBuffer): fixes 2019-11-15 20:25:28 +01:00
7e1b05933f WSRequestHandler(Recording): fixes 2019-11-15 20:25:00 +01:00
03c4fdc607 WSRequestHandler(Profiles): fixes 2019-11-15 20:21:40 +01:00
0485cf51ff WSRequestHandler(Outputs): fixes 2019-11-15 20:17:11 +01:00
ba9201831b WSRequestHandler(General): fix error returns 2019-11-15 20:12:38 +01:00
f660036a25 RpcRequest: rename okResponse to success 2019-11-15 20:09:43 +01:00
f09cbfddbc WSRequestHandler(General): fixes 2019-11-15 20:08:09 +01:00
a12f1dcb13 RpcRequest: create ok and error response builders 2019-11-15 20:05:07 +01:00
d2bb1ddc29 RpcRequest: fix accessors 2019-11-15 19:58:57 +01:00
528d81106e WSRequestHandler(processRequest): fix bind call 2019-11-15 19:52:05 +01:00
f0bed24742 WSRequestHandler: fix map 2019-11-15 19:49:06 +01:00
5b54d8135e WSRequestHandler: fixes 2019-11-15 19:41:19 +01:00
31559051f6 WSRequestHandler: request handler as object methods 2019-11-15 19:38:15 +01:00
e6dbc9bfdf WSRequestHandler(General): fixes 2019-11-15 19:30:30 +01:00
d1ee9d83b5 RpcResponse: allow additional fields in responses 2019-11-15 19:30:22 +01:00
ffa6371e3d WSRequestHandler: fix main 2019-11-15 19:29:34 +01:00
ee6e241144 RpcRequest: add has* methods 2019-11-15 19:26:49 +01:00
bdf1023b16 handler: request and response types 2019-11-15 19:26:42 +01:00
9a8f248a75 docs(travis): Update protocol.md - 6845cda [skip ci] 2019-11-12 10:41:34 +00:00
6845cda146 Update WSRequestHandler_Sources.cpp 2019-11-12 11:40:36 +01:00
4488b2b7a5 Merge pull request #385 from Palakis/bugfix/macos-deps-lookup
ci(macos): fix lookup path of Qt dependencies for new OBS bundle format
2019-11-11 15:38:34 +01:00
7dd8cb5d8d ci(macos): use framework rpath in package script 2019-11-11 15:27:27 +01:00
ebf631a15f ci(macos): fix lib paths in package script 2019-11-11 14:54:17 +01:00
3770b75a78 docs(travis): Update protocol.md - 5d81b61 [skip ci] 2019-11-11 13:41:29 +00:00
5d81b61325 Merge pull request #388 from Palakis/feature/enable-disable-filters
Get/set filter visibility (+ get single filter info)
2019-11-11 14:40:32 +01:00
a8e5171b40 utils(GetSourceFilterInfo): fix settings memory leak 2019-11-11 14:27:40 +01:00
a3e0abb6ce events: add SourceFilterVisibilityChanged 2019-11-11 14:12:37 +01:00
c51f20eb99 requests(SetSourceFilterVisibility): fix parameter value 2019-11-11 13:43:33 +01:00
be897c4df2 requests(sources): fix typo 2019-11-11 13:42:16 +01:00
11b948fb69 Revert "ci(macos): use @rpath in package script"
This reverts commit 070660848b.
2019-11-11 13:36:04 +01:00
070660848b ci(macos): use @rpath in package script 2019-11-11 13:27:33 +01:00
61af0ec9c6 requests(sources): add SetSourceFilterVisibility method 2019-11-11 12:58:36 +01:00
897f115363 requests(sources): add GetSourceFilterInfo method 2019-11-11 12:53:22 +01:00
b8fcf0355c requests(GetSourceFilters): add "enabled" status field 2019-11-11 12:44:13 +01:00
5bdff87e3f ci(macos): fix lookup path of Qt dependencies for new OBS bundle format 2019-11-04 13:07:15 +01:00
31991a3567 docs(travis): Update protocol.md - 25c369d [skip ci] 2019-10-17 14:19:25 +00:00
25c369d422 Merge pull request #384 from BarryCarlyon/TakeSourceScreenshotDocs
Take scene screenshot
2019-10-17 16:18:16 +02:00
8d396b1518 Adjust wording based on feedback 2019-10-17 11:53:32 +01:00
ba327b746a Wording Tweak 2019-10-15 20:13:00 +01:00
7d11f912c9 Revise Documentation to note that a Scene Name can be provided 2019-10-15 20:11:40 +01:00
919cdfb5a9 WSServer: remove remains of QWebSocket 2019-09-16 13:01:44 +02:00
da9dd6f775 docs(travis): Update protocol.md - 8b841f0 [skip ci] 2019-09-03 19:29:52 +00:00
8b841f026e Merge pull request #371 from Palakis/pause-recording
Recording Pause support
2019-09-03 21:28:55 +02:00
5342b39640 WSEvents: use frame count for streaming timecode 2019-09-03 21:04:45 +02:00
fc08add504 WSEvents: use stream time getter 2019-09-03 16:05:38 +02:00
20379ac61e WSEvents: recording time based on frame count and frame duration 2019-09-03 16:03:52 +02:00
af40aa59ab events + requests: fix dynamic runtime support for recording pauses 2019-09-03 13:50:44 +02:00
f9afc5597a requests(pause recording): fix memory leak 2019-09-03 13:22:08 +02:00
40178e4661 WSEvents(nsToTimestamp): fix sprintf compiler warning 2019-09-03 09:56:20 +02:00
ad56abd6f5 requests(Recording): refactor bits 2019-09-03 03:34:20 +02:00
9073a09d84 requests(pause recording): runtime feature detection 2019-09-03 03:30:06 +02:00
2a4e6fce3f WSEvents: fix constructor warning 2019-09-03 03:03:06 +02:00
0a3a407217 events: refactor timecode generation 2019-09-03 02:59:02 +02:00
9ca9cf80df Merge branch '4.x-current' into pause-recording 2019-09-03 02:41:42 +02:00
6c881a5da7 Merge pull request #372 from Palakis/frontend-events-ifelse-refactor
events: refactor the massive if else chain in the frontend event callback
2019-09-03 02:40:07 +02:00
5322acd58c events: add RecordingPaused and RecordingResumed 2019-09-03 02:35:54 +02:00
ce489e53c6 requests(GetStreamingStatus): add property "recording-paused" 2019-09-03 02:34:24 +02:00
0c95e509dc events(Heartbeat + StreamStatus): add property "recording-paused" 2019-09-03 02:34:24 +02:00
46a006ed54 requests(pause recording): only compile recording pause function bodies when supported 2019-09-03 02:34:24 +02:00
0cc9378d05 requests: add PauseRecording and ResumeRecording 2019-09-03 02:34:24 +02:00
f3edb2ee68 events: refactor the massive if else chain in the frontend event callback 2019-09-03 02:34:11 +02:00
a539f23194 Update README.md 2019-09-02 17:53:18 +02:00
6b6dec79c5 Merge pull request #369 from Palakis/bugfix/connection-onclose-broadcast-crash
WSServer: handle error codes to avoid throwing exceptions when sending a message
2019-09-02 12:52:58 +02:00
d7f5c042bf WSServer: handle error codes when sending a message 2019-09-01 00:16:09 +02:00
2fe9812b26 gitignore: .idea folder 2019-09-01 00:04:31 +02:00
1c15c3c7a3 Merge pull request #368 from Palakis/bugfix/timecode-sprintf-warning
WSEvents(nsToTimestamp): fix sprintf format warning
2019-08-31 21:50:46 +02:00
1ee5857139 WSEvents(nsToTimestamp): use unsigned long long constants 2019-08-31 21:35:58 +02:00
2f9c2b0d65 WSEvents(nsToTimestamp): fix sprintf format warning 2019-08-31 18:34:49 +02:00
6e5161f43b Merge pull request #367 from Palakis/bugfix/start-server-after-loading-finished
Start server after OBS finishes loading
2019-08-31 17:39:08 +02:00
d492d74a64 module: start server after OBS finishes loading 2019-08-31 16:03:32 +02:00
26506731c5 Merge pull request #366 from Palakis/bugfix/incorrect-recording-free-space
events(GetStats): fix recording path detection
2019-08-31 04:50:40 +02:00
b0c03d4f47 events(GetStats): fix recording path detection 2019-08-31 04:31:40 +02:00
3bc8348c32 Merge pull request #365 from Palakis/bugfix/reordersceneitems-crash
ReorderSceneItems: fix crash + refactor Utils
2019-08-31 03:29:21 +02:00
a034fbba83 utils(GetSceneItemFromItem): fix memory leaks with obs_data_item_t uses 2019-08-31 03:10:49 +02:00
73b5261c49 requests(ReorderSceneItems): atomic reorder + bring back GetSceneItemFromId 2019-08-31 03:03:32 +02:00
cee0cbebc2 Utils: use obs_scene_t* directly for scene-related helpers 2019-08-31 03:03:32 +02:00
9723147429 Utils: refactor GetSceneItemFromItem and get rid of GetSceneItemFromId 2019-08-31 03:03:32 +02:00
dca385ae87 requests(ReorderSceneItems): refactor 2019-08-31 03:03:32 +02:00
71f792944c request handler: fix obs_data_item_t memory leak 2019-08-31 02:52:08 +02:00
6041c4acd2 Merge pull request #364 from Palakis/bugfix/transitionbegin-cut-duration
event(TransitionBegin): set duration to 0 if it's a Cut transition
2019-08-30 14:10:46 +02:00
0c7529705e docs(travis): Update protocol.md - 63f1ea1 [skip ci] 2019-08-30 11:54:21 +00:00
63f1ea1cec docs: bump docs version to 4.7.0 2019-08-30 13:53:11 +02:00
4bbaa75f41 Merge pull request #358 from Palakis/request-helpers
Extend WSRequestHandler::hasField() to support field type checking
2019-08-30 13:50:25 +02:00
5fe9314b74 WSEvents: remove unused transitionIsCut helper 2019-08-30 13:41:50 +02:00
bfd26493ab Utils(GetTransitionDuration): return 0 if transition is a cut transition 2019-08-30 13:41:08 +02:00
5d74d5d03e WSEvents(OnTransitionBegin): refactor event code 2019-08-30 13:36:45 +02:00
3142e097a5 Merge pull request #360 from ZyanKLee/patch-1
allow for "linuxbrowser_source" to be controlled
2019-08-26 21:02:00 +02:00
b25d8d8201 requests(sources): fix linux browser source id 2019-08-26 21:01:05 +02:00
7e68cd016a ci(windows): exclude RCs when fetching the latest version of OBS 2019-08-24 12:39:46 +02:00
9e4fc9eb73 allow for "linuxbrowser_source" to be controlled
based on the request at https://github.com/bazukas/obs-linuxbrowser/issues/46 I did a quick search for the string "browser_source" and these seem to be the only two occurrences. Enhancing the conditional with the identifier of the obs-linuxbrowser plugin, this should solve the problem.

As I'm currently not at home, I can't verify this does what it should, but I will test it later tonight.

@mjc666 if you are still interested please give it a test run to verify if these two plugins work together now.
2019-08-22 19:44:12 +02:00
fb0d13a171 WSRequestHandler: add int and double type check methods 2019-08-02 21:52:50 +02:00
f097b36c2e WSRequestHandler: add type checks for field test methods 2019-08-02 21:40:42 +02:00
57ce3cf80b Merge pull request #320 from Palakis/feature/remove-hacks
utils: remove transition hacks
2019-07-31 17:38:40 +02:00
c1da62391f docs(travis): Update protocol.md - d8882ce [skip ci] 2019-07-31 15:37:41 +00:00
d8882ce979 Merge pull request #357 from julijane/fix-001
Fix spec of HandleBroadcastCustomMessage
2019-07-31 17:36:39 +02:00
9c1871494e Fix spec of HandleBroadcastCustomMessage 2019-07-26 14:40:54 +02:00
ac26c9114a docs(travis): Update protocol.md - fb0a570 [skip ci] 2019-07-26 07:31:42 +00:00
fb0a57003c BroadcastCustomMessage: fix command and event spec 2019-07-26 09:30:30 +02:00
cda22233a6 docs(travis): Update protocol.md - add3075 [skip ci] 2019-07-26 03:52:35 +00:00
add307577d Merge pull request #354 from julijane/4.x-current
Make it possible to broadcast messages (data) to other websocket clients
2019-07-26 05:51:42 +02:00
4af46ac2b9 use obs_data_t* in Event function 2019-07-25 17:20:31 +02:00
8d39752bda Move event emitting code 2019-07-25 17:00:32 +02:00
e3ff9c013e change function name, language 2019-07-25 14:47:47 +02:00
635870ba4b more comment fixes 2019-07-24 05:36:47 +02:00
a827afb05b Comment fix 2019-07-24 01:56:53 +02:00
0195e13bdc Implement proposed changes to Broadcast 2019-07-23 21:47:55 +02:00
76d8b688fd Add Code for BroadcastWebSocketMessage 2019-07-22 01:05:21 +02:00
b4857e2c79 docs(travis): Update protocol.md - ac2ae90 [skip ci] 2019-07-16 16:23:36 +00:00
ac2ae90e8a Merge pull request #343 from Palakis/feature/outputs-api
Outputs API
2019-07-16 18:22:40 +02:00
0b5cb76b5d outputs: remove TODOs 2019-07-16 18:22:07 +02:00
6eed9606ae docs: remove Boost from BUILDING.md 2019-06-23 22:38:36 +02:00
29679ff94c docs(travis): Update protocol.md - 65a1f8b [skip ci] 2019-06-18 16:41:13 +00:00
65a1f8b746 GetSourceTypesList: fix docs
Fixes #348
2019-06-18 18:38:52 +02:00
48182d35bd docs(travis): Update protocol.md - b526ed4 [skip ci] 2019-06-16 14:20:37 +00:00
b526ed47be Merge branch '4.x-current' of github.com:Palakis/obs-websocket into 4.x-current 2019-06-16 16:19:32 +02:00
2225dec2a9 docs(SourceAudioMixersChanged): fix remaining discrepancy 2019-06-16 16:19:22 +02:00
011458d642 docs(travis): Update protocol.md - e32fff3 [skip ci] 2019-06-16 14:18:12 +00:00
e32fff3f61 docs(SourceAudioMixersChanged): fix discrepancy 2019-06-16 16:14:26 +02:00
0b42e3d0f3 outputs: add more info fields 2019-06-11 19:32:52 +02:00
2b746d1353 cmake: add output requests 2019-06-11 19:32:52 +02:00
6fed6f9f7d StartOutput: provide output error message when start fails 2019-06-11 19:32:52 +02:00
480945073a outputs: start or stop only when possible 2019-06-11 19:32:52 +02:00
ef6df94838 outputs: todos 2019-06-11 19:32:52 +02:00
fb05848426 requests: outputs API WIP 2019-06-11 19:32:52 +02:00
e5be6b96fb general: version bump to 4.7 2019-06-11 19:32:32 +02:00
2d706a245a Merge pull request #337 from DieserMerlin/patch-2
Update README.md (+ obs-websocket-java library)
2019-06-04 11:17:43 +02:00
1abc4f491b readme: minor fix 2019-06-04 10:46:19 +02:00
b21a45b43e Update README.md 2019-06-01 00:08:19 +02:00
585f8450e1 Merge pull request #330 from Palakis/transitionbegin-hotfix
Fix TransitionBegin event
2019-05-21 00:05:24 +02:00
d3401db5fe general: version bump to 4.6.1 2019-05-21 00:02:48 +02:00
45dc6447bf events: fix how and when TransitionBegin is hooked 2019-05-20 23:54:13 +02:00
409c270ba2 events: fix TransitionBegin event not triggering 2019-05-20 23:48:49 +02:00
aed0234d47 docs(travis): Update protocol.md - 07ebbe5 [skip ci] 2019-05-20 19:15:50 +00:00
07ebbe56e6 docs: fix typos
Fixes #328
Fixes #329
2019-05-20 21:14:14 +02:00
47b485819f Update property -> return as this isn't a typedef 2019-05-20 01:47:25 +10:00
fd32f7f435 Explain how OBS interprets alignment numbers 2019-05-20 00:44:11 +10:00
c4b07b1a7b Merge remote-tracking branch 'origin/4.x-current' into show-gravity 2019-05-20 00:37:22 +10:00
95bbeb103e locale: update translations from Crowdin 2019-05-19 16:05:05 +02:00
bfb5570b7a scene item requests: defer updates to avoid triggering several transform events at once 2019-05-19 15:50:44 +02:00
1e19cf7ccc server: don't reuse sockets on windows
Other, conflicts with Port In Use detection
2019-05-19 15:43:27 +02:00
0d8999d64f GetVersion: bring back retrocompat version field 2019-05-19 13:41:23 +02:00
91fde777cf docs(travis): Update protocol.md - 061ab12 [skip ci] 2019-05-19 08:07:31 +00:00
061ab12b10 Merge pull request #317 from Palakis/group-fixes
Groups fixes
2019-05-19 10:06:08 +02:00
c516e87180 text: rename settings dialog + dedup strings 2019-05-17 12:59:43 +02:00
de6bfdca0a SceneItems(docs): add comment about coordinates 2019-05-16 14:30:39 +02:00
788f70d5f3 Utils(GetSceneItemData): remove property isGroup 2019-05-16 13:57:38 +02:00
b0a35789f6 utils + sceneitem: fix docs again 2019-05-16 13:55:33 +02:00
eb7fbf011f utils + sceneitems: fix docs 2019-05-16 13:54:06 +02:00
50bb6e7204 Utils: add parent property to scene item info and transform data + refactor bits 2019-05-16 13:50:24 +02:00
15610bb296 Utils(GetSceneItemData): add children list 2019-05-16 13:37:04 +02:00
ae0ffdc4d5 docs: rename typedef Source to SceneItem 2019-05-16 13:12:18 +02:00
033a6929c3 Utils(GetSceneItemFromId): recurse into groups wip 2019-05-16 11:03:32 +02:00
7c8292a88d Utils(GetSceneItemFromName): recurse into groups wip 2019-05-16 11:00:29 +02:00
ab5dad7d91 Utils: oops 2019-05-15 13:11:15 +02:00
0744d215c6 utils(GetSceneItemData): add isGroup flag + move Source typedef here 2019-05-15 13:11:15 +02:00
2622eb4663 Add alignment to the documented definition of a Source 2019-05-14 04:09:18 +10:00
74b3dc2831 Show alignment (gravity) of scene elements when querying/switching scenes 2019-05-14 04:05:22 +10:00
149ddfa8f8 Merge pull request #323 from Palakis/gpl-prompt-fix
installer(windows): fix EULA prompt into something GPL-compliant
2019-05-12 01:22:37 +02:00
3830870a83 installer(windows): fix EULA prompt into something GPL-compliant 2019-05-12 00:59:51 +02:00
b94be080f6 Merge pull request #322 from Palakis/remove-boost
general: remove boost remains
2019-05-11 17:40:17 +02:00
83382d8dcf general: remove boost remains 2019-05-11 17:13:41 +02:00
f815228677 Merge pull request #319 from Rosuav/video-info
Create an endpoint to view basic video info eg canvas size
2019-05-10 18:30:06 +02:00
d7d8d23de7 Refactor enum-to-string conversions into functions 2019-05-11 01:42:52 +10:00
5834c6ed54 Switch doc type from Number to int for clarity and precision 2019-05-10 18:48:51 +10:00
b0512b3ba7 Provide info about color space and scaling 2019-05-10 04:33:37 +10:00
d3a7a6ef55 Make small tweaks to formatting and layout.
* Switch to camelCase for output properties
* Remove done TODO
* Properly document all of the information returned
2019-05-10 04:08:01 +10:00
99fad5bc83 docs(travis): Update protocol.md - a54171e [skip ci] 2019-05-09 13:54:08 +00:00
a54171e1cd SetSceneItemProperties: fix docs (optional parameters)
Fixes #318
2019-05-09 15:52:35 +02:00
84629f6e63 utils: remove transition hacks 2019-05-09 13:28:50 +02:00
b18d597bdc Utils: remove unused SceneListItemToScene 2019-05-09 07:53:22 +02:00
9125dc4cce Utils: remove unused GetSceneListControl() 2019-05-09 07:51:41 +02:00
b0e3ea8765 Update docs to include GetVideoInfo 2019-05-09 06:03:14 +10:00
7c457546f1 Create an endpoint to view basic video info eg canvas size 2019-05-09 05:58:55 +10:00
83bef1a840 main: force-destroy server, events and config when unloading the plugin 2019-05-08 00:48:43 +02:00
b4d89d5666 Config + WSServer: provide singletons from plugin entry point 2019-05-08 00:43:46 +02:00
3ba8a77d9a Utils: remove empty space 2019-05-08 00:30:53 +02:00
82c31dc47a Merge branch 'better-signals-handling' into 4.x-current 2019-05-07 23:47:21 +02:00
7fce694577 main: global settings dialog instance not required 2019-05-07 23:40:30 +02:00
e352d9750d WSEvents: remove singleton + refactor pointer handling in plugin entrypoint 2019-05-07 23:36:43 +02:00
3d9eac8e6d WSEvents: connect to signals at construct and disconnect at destroy 2019-05-07 22:58:20 +02:00
6c85b33539 WSEvents: source signal connect/disconnect methods 2019-05-07 22:53:07 +02:00
d979ada4fe WSEvents: fix OnSourceDestroyed not triggering 2019-05-07 22:44:01 +02:00
faeeae17d1 docs(travis): Update protocol.md - 82d1a2b [skip ci] 2019-05-07 11:38:16 +00:00
82d1a2b4f9 GetSceneItemProperties: fix docs 2019-05-07 13:36:25 +02:00
cac3496145 readme: replace twitter account 2019-05-04 14:23:55 +02:00
53bfa077ae docs(travis): Update protocol.md - 0b9366f [skip ci] 2019-05-04 10:30:17 +00:00
0b9366fa28 Merge pull request #313 from Palakis/source-screenshot
Source Image Snapshot
2019-05-04 12:29:12 +02:00
c95b709f39 TakeSourceScreenshot: fix docs 2019-05-04 12:19:15 +02:00
5745da5466 TakeSourceScreenshot: always provide an absolute path in imageFile response 2019-05-04 11:50:29 +02:00
ef853e34d6 Merge pull request #316 from Rosuav/no-tray-icon
Return nullptr if there are no tray icons
2019-05-03 22:18:54 +02:00
7177913d9b TakeSourceScreenshot: ability to choose between save to file and/or data uri 2019-05-03 22:01:40 +02:00
86b6ddb625 Return nullptr if there are no tray icons 2019-05-04 04:20:22 +10:00
53b9a46feb TakeSourceScreenshot: direct file save 2019-05-03 19:14:17 +02:00
d46c1f8687 TakeSourceScreenshot: fix off-screen sources rendering 2019-05-03 01:14:58 +02:00
c92477b457 TakeSourceScreenshot: fix tearing issue when scaling 2019-05-03 01:11:36 +02:00
059244bb32 TakeSourceScreenshot: fix heap corruption on windows 2019-05-03 00:49:55 +02:00
f10c0d2558 TakeSourceScreenshot: use default quality 2019-05-03 00:02:53 +02:00
f5ae89500f TakeSourceScreenshot: fix docs 2019-05-02 23:13:11 +02:00
2e3a24d32e TakeSourceScreenshot: add width and height optional parameters 2019-05-02 23:13:11 +02:00
f955d81c5e requests: rename GetSourceImage to TakeSourceScreenshot 2019-05-02 23:13:11 +02:00
4f1fa3bc32 requests(GetSourceImage): add picture format parameter 2019-05-02 23:13:11 +02:00
8305d907e3 request(GetSourceImage): fix mime type 2019-05-02 23:13:11 +02:00
ab73d61178 request(GetSourceImage): add todo 2019-05-02 23:13:11 +02:00
8f88d1b7c4 request(GetSourceImage): use PNG 2019-05-02 23:13:11 +02:00
c5cdbc48a9 requests(sources): fix includes 2019-05-02 23:13:11 +02:00
fe00f31147 sources: fix GetSourceImage method 2019-05-02 23:13:11 +02:00
79ddb43ac3 use QImage buf directly 2019-05-02 23:13:11 +02:00
89f95f6459 switch to WebP 2019-05-02 23:13:11 +02:00
87c13b6f26 Working PNG output 2019-05-02 23:13:11 +02:00
d16ea653fe GetSourceImage WIP 2019-05-02 23:13:11 +02:00
51dc7fceb0 Merge pull request #312 from Palakis/feature/async-handling
Call the request handler asynchronously
2019-05-02 23:11:24 +02:00
bc1c6f7b9a remove boost atomic uses 2019-05-02 13:29:23 +02:00
3fdf77d29b ConnProperties: use std atomic 2019-05-02 13:22:04 +02:00
0b97502029 ci(macos): fix oops 2019-05-02 13:17:38 +02:00
cba2bf5cb8 ci(macos): fix obs build issue 2019-05-02 13:16:39 +02:00
d8b37328a1 ConnProperties: atomic "authenticated" field 2019-05-02 13:11:34 +02:00
bb9cf83744 server: dedicated ConnectionProperties object 2019-05-02 13:06:10 +02:00
56fc6ae47c server: connection properties reference 2019-05-02 12:51:07 +02:00
0cf17cf3db server: use shared connection properties instance 2019-04-28 11:59:03 +02:00
1bd3297055 server: update comment 2019-04-26 13:55:21 +02:00
6a10662bc4 server: use a thread pool 2019-04-26 13:52:56 +02:00
2d84ad8963 server: handle requests in a separate thread 2019-04-26 13:39:18 +02:00
1ec69cbc0d docs(travis): Update protocol.md - 0057744 [skip ci] 2019-04-25 12:35:06 +00:00
0057744e57 Merge branch '4.x-current' of github.com:Palakis/obs-websocket into 4.x-current 2019-04-25 14:33:53 +02:00
45892b4347 events: fix docs 2019-04-25 14:02:04 +02:00
3d41e8882a docs(travis): Update protocol.md - 436b821 [skip ci] 2019-04-25 11:44:54 +00:00
436b8216ec requests(streaming): add @since property for SendCaptions 2019-04-25 13:43:24 +02:00
03ba2a680e SetSceneItemProperties: fix string comparison for bounds type parameter
Fixes #309
2019-04-23 11:32:04 +02:00
2821962d12 docs(travis): Update protocol.md - 43d38a2 [skip ci] 2019-04-22 17:57:20 +00:00
43d38a2e63 Merge pull request #308 from Palakis/bugfix/285-item-id
Provide scene item id in events data and request responses
2019-04-22 19:56:08 +02:00
ea28d217e0 requests: add "id" to scene item data and fix docs 2019-04-22 19:45:32 +02:00
89fe6d57c9 events: add "item-id" property to scene item events 2019-04-22 19:34:27 +02:00
1d0e1143bf docs(travis): Update protocol.md - 1a99353 [skip ci] 2019-04-22 17:00:15 +00:00
1a99353559 Merge pull request #307 from Palakis/feature/271-item-selected
Item Select/Deselect events
2019-04-22 18:58:50 +02:00
ad97d04e37 events: scene item select and deselect events 2019-04-22 18:47:46 +02:00
25c3abe873 docs(travis): Update protocol.md - 8eeea50 [skip ci] 2019-04-22 16:32:39 +00:00
8eeea50ede Merge pull request #305 from Rosuav/fix-docs-formatting
Fix docs formatting (loose combining character)
2019-04-22 18:31:16 +02:00
0ded11c6a2 Merge pull request #306 from Rosuav/set-scene-item-locked
Allow the manipulation of the locked flag of scene items.
2019-04-22 18:30:20 +02:00
b6c542212d events: fix typo 2019-04-22 18:29:59 +02:00
182bde4884 Allow the manipulation of the locked flag of scene items.
Lockedness can be seen when you query an item or see the scene
contents, and can be changed (only) with SetSceneItemProperties.
2019-04-23 00:32:28 +10:00
eb84f677cf Merge pull request #304 from Palakis/feature/269-settings-per-profile
Move settings to profile
2019-04-22 16:29:01 +02:00
3ecb637e70 Fix docs formatting (loose combining character) 2019-04-23 00:23:06 +10:00
bfc826b898 config: fix types 2019-04-22 16:18:54 +02:00
efc6d4c15a config: enable settings migration 2019-04-22 16:13:12 +02:00
a96da35f11 server: don't notify disconnections when stopping the server 2019-04-22 16:03:53 +02:00
2bff3798af config: slight change 2019-04-22 15:46:59 +02:00
0fb50a3273 config: put alerts text to locale data 2019-04-22 15:43:41 +02:00
1c17eee125 config(profile change): fix notifications 2019-04-22 15:26:26 +02:00
14a43a9cd2 server: call reset before starting (fixes restarts) 2019-04-22 15:06:36 +02:00
7bbc7366f2 server: allow address reuse 2019-04-22 13:33:39 +02:00
879bedd781 server(stop): close all connections and wait for proper stop 2019-04-22 13:29:53 +02:00
8b79bfab4f config: set default before reloading from profile 2019-04-22 13:12:01 +02:00
f022e09938 config: show message if server is stopped after profile change 2019-04-21 18:59:04 +02:00
a63b6a0e3d config: per profile settings 2019-04-21 18:52:47 +02:00
dd1facec06 docs(travis): Update protocol.md - a46b571 [skip ci] 2019-04-21 15:44:12 +00:00
a46b5716be Merge pull request #303 from Palakis/feature/115-streamstatus-more-info
Add more stats to StreamStatus and Heartbeat + add GetStats request type
2019-04-21 17:42:36 +02:00
e96b7bfd62 fix docs 2019-04-21 16:03:44 +02:00
a60c1c1365 stats: provide recording disk space 2019-04-21 16:03:05 +02:00
4c39fcd614 events(GetStats): missing field 2019-04-21 15:49:08 +02:00
e6b341f2a0 requests: add GetStats 2019-04-21 15:44:24 +02:00
bdf9e76a6b events: more stats info in StreamStatus and Heartbeat 2019-04-21 15:38:27 +02:00
e0fc395fbe events: remove unused variable 2019-04-21 14:48:15 +02:00
107c03653d docs(travis): Update protocol.md - a5058cf [skip ci] 2019-04-21 12:27:27 +00:00
a5058cf951 Merge pull request #302 from Palakis/bugfix/168-sourceorderchanged-items-list
event(SourceOrderChanged): include scene items list
2019-04-21 14:26:25 +02:00
ed2726c9d2 SourceOrderChanged: include scene items list 2019-04-21 14:17:50 +02:00
6b31ff7e79 docs(travis): Update protocol.md - 86d3925 [skip ci] 2019-04-21 08:14:30 +00:00
86d3925bf4 Merge pull request #301 from Palakis/bugfix/233-setrecordingfolder-not-working
SetRecordingFolder: fix docs + simplify helper function
2019-04-21 10:13:07 +02:00
8c2cde4c13 SetRecordingFolder: fix docs + simplify helper function 2019-04-21 09:59:52 +02:00
2d6e34ee6d docs(travis): Update protocol.md - adb5577 [skip ci] 2019-04-20 23:05:02 +00:00
adb5577b01 Merge pull request #294 from Palakis/global-scene-events
events refactor (transitions & scene signals) + new events
2019-04-21 01:04:01 +02:00
84ef3f223d events: fix docs 2019-04-20 23:56:15 +02:00
fa50008f3d wsevents(filter removed): fix crash on exit 2019-04-20 23:52:35 +02:00
6a5537f90f utils: comments 2019-04-20 23:20:21 +02:00
e0d33a9683 events: remove static_cast 2019-04-20 23:09:59 +02:00
4092c9a473 events: add "source kind" param to events + various fixes 2019-04-20 23:08:06 +02:00
fcaac3d515 events: fix filter events docs + refactor filters list getter 2019-04-20 22:57:52 +02:00
e86d1aad02 wsevents: fix SourceMuteStateChange 2019-04-20 19:13:21 +02:00
2c94b4c332 wsevents: fix docs 2019-04-20 19:09:46 +02:00
2b6933f6e2 WSEvents: implement SourceAudioMixersChanged 2019-04-20 18:34:25 +02:00
2e2a7f073e wsevents: fix constructor 2019-04-20 18:05:51 +02:00
515c7852ba events: remove OnSourcePropertiesChanged (can't implement) 2019-04-20 18:04:38 +02:00
5577277944 events: new events 2019-04-20 18:04:38 +02:00
ed9e4ff168 events: new events WIP 2019-04-20 18:04:38 +02:00
d10915c7a8 tidy: todos 2019-04-20 18:04:38 +02:00
40763fc15d events: new types SourceCreated and SourceDestroyed 2019-04-20 18:04:38 +02:00
88d3271c40 events: refactor signal connection to transitions 2019-04-20 18:04:38 +02:00
21940c922d utils: fix compilation warnings 2019-04-20 18:04:38 +02:00
93adc8587d general: refactor qt includes 2019-04-20 18:04:36 +02:00
1907f8d1d1 events: fix sprintf warnings + simplified qt connect 2019-04-20 18:03:43 +02:00
ca8848827e events: refactoring + setup scene signals on scene creation 2019-04-20 18:03:19 +02:00
3a7473ba91 Merge pull request #300 from Palakis/bugfix/290-port-used-crash
server: implement "port already in use" message + fix crash with StreamStatus
2019-04-19 23:21:13 +02:00
a99da27ff6 events: fix crash caused by early StreamStatus call 2019-04-19 23:09:25 +02:00
c476649f3e server(start): fix error message 2019-04-19 22:12:04 +02:00
062473d6f4 server: show error message if port is already in use 2019-04-19 21:57:08 +02:00
d0ed43a8e5 docs(travis): Update protocol.md - 701098d [skip ci] 2019-04-19 18:18:53 +00:00
701098d19d Merge pull request #299 from Palakis/feature/183-streamstatus-replay-buffer
events(StreamStatus): new property "replay-buffer-active"
2019-04-19 20:17:21 +02:00
c33a0ab439 events(StreamStatus): new property "replay-buffer-active" 2019-04-19 20:02:19 +02:00
b8af848d3a Merge pull request #298 from Palakis/bugfix/264-debug-logging
fix debug logging
2019-04-19 19:16:11 +02:00
3dd7fe5d4a requests + events: log message to INFO instead of DEBUG
and slightly refactor the request handler
2019-04-19 19:00:52 +02:00
81ab199308 docs(travis): Update protocol.md - 92938d2 [skip ci] 2019-04-19 16:08:42 +00:00
92938d2c35 Merge pull request #297 from Palakis/bugfix/242-getsceneitemproperties-item-size
GetSceneItemProperties: add width & height values
2019-04-19 18:07:34 +02:00
c914632663 GetSceneItemProperties: add width & height values 2019-04-19 17:44:11 +02:00
c95104fada Merge pull request #295 from Rosuav/docs-patch-fix-call-name
Fix call name GetSourcesTypesList -> GetSourceTypesList
2019-04-19 12:29:51 +02:00
2209fe30e2 Fix call name GetSourcesTypesList -> GetSourceTypesList
The API call listed in the documentation doesn't work (tested on 4.3.1),
but without the 's', it works fine. Update documentation to match code.
2019-04-19 20:14:31 +10:00
979e0ddc88 docs(travis): Update protocol.md - 9fab714 [skip ci] 2019-04-16 23:23:02 +00:00
9fab714674 Merge pull request #293 from Vainock/patch-1
Make "WebSocket" consistent
2019-04-17 01:22:30 +02:00
2eb6463ab0 Merge pull request #272 from BarryCarlyon/scene_item_transform
Support item_transform signal
2019-04-17 01:21:46 +02:00
5e4d6fbd3f utils: refactored docs and impl of scene item properties 2019-04-17 01:09:05 +02:00
30df5f3558 events: fix typo 2019-04-17 00:15:15 +02:00
252dd7e09f utils: indent fixes 2019-04-17 00:15:15 +02:00
2915690d22 utils: use obs_data_t for returned values 2019-04-17 00:15:15 +02:00
1993596232 Requested changes to move the common code to a utils function. 2019-04-17 00:15:15 +02:00
8946e8997f Update WSEvents.cpp
Requested changes.

I swapped "obs_data_set_string(data, "name", itemName.toUtf8());" for scene-item and item-name as otherwise you don't know what scene the item was changed on (studio mode if you change items on a non active scene)
2019-04-17 00:15:15 +02:00
dfeb156da9 Documentation/comment fix 2019-04-17 00:15:15 +02:00
c10c35e38e Forgot to declare the function 2019-04-17 00:15:15 +02:00
67bfdde113 Update WSEvents.cpp
Clone OnSceneItemAdd to a new function to support the `item_transform` signal. Emit as `SourceItemChanged`
2019-04-17 00:15:15 +02:00
c62178a7fe docs(travis): Update protocol.md - c12a432 [skip ci] 2019-04-16 21:14:32 +00:00
c12a4323e7 Merge pull request #279 from derrod/captions
Add caption handler
2019-04-16 23:12:20 +02:00
fd1c4abad7 requests(streaming): conditional compilation of SendCaptions 2019-04-16 22:51:49 +02:00
bd4fe5a1a7 Make "WebSocket" consistent 2019-04-16 15:48:09 +02:00
190f5ebfc4 chore: empty commit to trigger ci again 2019-04-16 14:03:30 +02:00
79493df32e requests(streaming): fix docs on SendCaptions 2019-04-16 13:11:46 +02:00
71d523437e Merge pull request #287 from Lange/4.5.1-types-cleanup
docs(jsdoc): clean up types for compatability with obs-websocket-js typedef generator
2019-03-30 22:08:09 +01:00
268f503875 docs(jsdoc): clean up types for compatability with obs-websocket-js typedef generator 2019-03-30 14:26:58 -05:00
5ac47b823f docs(travis): Update protocol.md - ec572da [skip ci] 2019-03-30 14:36:45 +00:00
ec572da822 docs: update release 2019-03-30 15:35:18 +01:00
37f96b8cf2 chore: bump to 4.6.0 2019-03-30 15:34:06 +01:00
545db60b98 ci: enable/build captions 2019-03-22 10:27:27 +01:00
f65fdcdbc1 handler: Add caption handler 2019-03-22 10:24:25 +01:00
579acabe5e Merge pull request #283 from Palakis/ubuntu-deb-fix
Fix package on Ubuntu
2019-03-22 01:46:22 +01:00
f2a9ff8551 ci(linux): fix Ubuntu package 2019-03-22 01:40:29 +01:00
2e40e07563 Merge pull request #282 from Palakis/azure-pipelines
ci(macos): setup Azure Pipelines
2019-03-21 23:14:16 +01:00
cb3af837c6 readme: fix building.md 2019-03-21 22:36:30 +01:00
3cce89ea3f ci(macos): fix artifacts publishing 2019-03-21 22:28:08 +01:00
5f2dfb24ca ci(macos): checkout submodules 2019-03-21 22:18:29 +01:00
4bc02a7389 ci(macos): install boost 2019-03-21 21:56:07 +01:00
eeeca8afd0 ci(macos): remove set -ev 2019-03-17 15:38:59 +01:00
4f607df5fc ci(macos): fix typo 2019-03-17 15:01:01 +01:00
e06b3e2052 readme: Azure Pipelines CI badge 2019-03-17 14:48:17 +01:00
b14b18e4be ci(macos): setup Azure Pipelines 2019-03-17 14:45:41 +01:00
fb616b4b53 docs: update README and BUILDING 2019-01-04 11:36:38 +01:00
a1c5bc00bc Merge branch 'pragma-once' into 4.x-current 2019-01-01 18:46:38 +01:00
0921632f87 general(headers): replace include guards with "#pragma once" 2019-01-01 18:46:23 +01:00
38ad465233 Merge pull request #266 from Palakis/response-return
Return response in request handlers
2019-01-01 18:23:22 +01:00
881de01073 minor fixes 2019-01-01 18:08:47 +01:00
2d973e0b90 handler: return obs_data_t objects 2019-01-01 17:54:29 +01:00
e9b43b9b2a handler: refactor response passing 2019-01-01 17:25:08 +01:00
b9193989b0 requests(sources): missing return statements 2019-01-01 17:23:58 +01:00
be6d9791f5 Merge pull request #265 from Palakis/websocketpp-4.x
Replace QWebSocketServer with websocket++ in 4.x
2019-01-01 16:35:08 +01:00
baac1b1d80 server: clarifying things 2019-01-01 15:57:29 +01:00
40e2d410dd server: use std::string in broadcast 2019-01-01 05:29:51 +01:00
11617eea99 server: remove useless base64 helper 2019-01-01 05:16:57 +01:00
5748c4d0ec general: fix crash on exit 2019-01-01 05:13:08 +01:00
2e5b903eae events: singleton shared pointer refactor 2019-01-01 05:07:55 +01:00
9405b17e14 config: singleton shared pointer refactor 2019-01-01 05:01:23 +01:00
7d1f0e2a69 server: refactored singleton with QSharedPointer 2019-01-01 04:55:15 +01:00
c245c24752 server: broadcast to authenticated only + use references in handler 2019-01-01 01:18:23 +01:00
5b0410a207 server: per-connection properties 2019-01-01 01:07:50 +01:00
62e4c42aa6 server: stop on unload 2018-12-31 21:29:34 +01:00
fe1b14ff57 server: return refactor 2018-12-31 20:49:15 +01:00
c074088f2f fix appveyor builds 2018-12-31 20:32:24 +01:00
0391280c13 fix build issues 2018-12-31 20:27:52 +01:00
4f98b9e41b general: submodules 2018-12-31 20:23:16 +01:00
a8de9ac472 server: remove validate handler 2018-12-31 17:23:20 +01:00
ec7f3fa057 ci(macos): revert version change 2018-12-31 17:21:29 +01:00
5cfefd8b15 ci(docs): revert change 2018-12-31 17:19:24 +01:00
7e6b53311d import more changes from master 2018-12-31 17:18:07 +01:00
16bc68f2f9 server: import websockets++ server from master 2018-12-31 17:05:23 +01:00
974d6b48b2 chore: 4.5.0 release 2018-12-30 21:02:56 +01:00
db2b1e2dc7 docs(travis): Update protocol.md - 98656b5 [skip ci] 2018-12-30 17:41:58 +00:00
98656b5d2f Merge pull request #263 from Palakis/sceneitem-order
add SetSceneItemOrder from master
2018-12-30 18:40:55 +01:00
3c7570d814 scenes: rename SetSceneItemOrder to ReorderSceneItems + fix item freeing 2018-12-30 14:47:59 +01:00
fc3e30a826 docs(travis): Update protocol.md - 2f0476b [skip ci] 2018-12-30 13:34:11 +00:00
2f0476b43c docs: fix version + plugin name 2018-12-30 14:32:38 +01:00
e310c7d744 move SetSceneItemOrder to Scenes 2018-12-24 18:34:18 +01:00
8a649b89c8 scene items: import reorder method from master 2018-12-24 02:43:58 +01:00
95f52987ef docs(travis): Update protocol.md - 82b8c66 [skip ci] 2018-12-24 01:09:18 +00:00
82b8c66d51 docs: fix branch name in generation script 2018-12-24 02:07:55 +01:00
83fb1843ee Merge pull request #262 from Palakis/sources-methods-without-scene-name
sources: get rid of unnecessary `scene-name` param on Source Request Types
2018-12-24 00:33:19 +01:00
58b10069ab sources: fix issues 2018-12-24 00:10:45 +01:00
9cda739672 sceneitems: fix docs 2018-12-24 00:02:21 +01:00
276bba050b sources: nitpicking 2018-12-23 23:59:30 +01:00
147e49b362 sources + scene items: refactor + get rid of scene-name params 2018-12-23 23:54:25 +01:00
bc338c1f4a Merge pull request #261 from Palakis/freetype2-source-properties
FreeType 2 Sources: Get/Set Properties
2018-12-23 23:04:52 +01:00
e8fbb18a71 sources(ft2): fix docs 2018-12-23 23:00:33 +01:00
682c349831 sources(ft2): simplified qstring compare 2018-12-23 18:30:27 +01:00
14b311f6ab sources(ft2): fix errors 2018-12-23 18:26:57 +01:00
8a40f355c8 sources: fix indents again 2018-12-23 18:21:31 +01:00
ae2f90c5c2 sources: fix indents 2018-12-23 18:20:45 +01:00
7aff773e2c sources: fix ft2 method indents 2018-12-23 18:19:29 +01:00
0cdfa6e7f6 sources: import ft2 methods + fix them 2018-12-23 18:16:44 +01:00
fc637eef6d Merge pull request #254 from Lange/typedefs
docs: improve consistency of array typings; introduce typedefs category
2018-12-07 20:45:30 +01:00
b4c3141170 wip: remove redundant "as object" descriptions 2018-12-05 14:02:25 -06:00
41257f7af5 Merge pull request #249 from PatTheMav/4.x-current
Fix compile error and added dependency check in build script
2018-12-04 14:29:53 -08:00
b204f3ec90 wip: remove extraneous dash 2018-11-28 14:57:59 -06:00
b4926b3535 docs: improve consistency of comment docs; introduce typedefs category 2018-11-27 17:49:54 -06:00
77d63e9848 Added check for already installed packagesbuild 2018-11-08 21:29:39 +01:00
94dcd58c2e Merge pull request #251 from Palakis/auth-without-mbedtls
auth: get rid of mbedtls
2018-11-08 19:01:13 +01:00
689ce16f1b config: use RNG compatible with QT < 5.10 2018-11-08 08:58:26 +01:00
edc64b8336 auth: get rid of mbedtls 2018-11-08 00:52:26 +01:00
03db5bfd8d bugfix(actual): TransitionBegin not triggered after switching scene collections (#250) 2018-11-07 21:13:13 +01:00
9ad340ab02 bugfix: TransitionBegin not emitted after scene collection change (#250) 2018-11-07 13:08:03 +01:00
962e26040d ci(windows): build OBS with VS2015 2018-11-07 00:02:34 +01:00
b07884c1da ci(win): use VS2015 to fix build errors
`constexpr function 'qCountLeadingZeroBits' cannot result in a constant expression`
2018-11-06 23:52:03 +01:00
bad0fb62ed ci(windows): remove hardcoded image 2018-11-06 23:42:06 +01:00
28e522ce97 WSRequestHandler: fix typo 2018-11-06 20:49:33 +01:00
c206cdfa4c chore(release): version bump 2018-11-01 13:22:26 +01:00
c31ec077f5 docs(travis): Update protocol.md - afc6a60 [skip ci] 2018-11-01 12:15:56 +00:00
afc6a60746 Merge pull request #248 from RytoEX/4.3-fix-typos
general: Fix several typos throughout
2018-11-01 13:13:24 +01:00
0a495b67e6 bugfix(requests): register DuplicateSceneItem and DeleteSceneItem 2018-11-01 13:00:07 +01:00
37ea7073d5 general: Fix several typos throughout 2018-11-01 00:42:02 -04:00
feaeef5a70 Merge pull request #246 from PatTheMav/4.4-macOS-build-update
4.4 macOS build system update
2018-10-30 14:58:17 +01:00
5586670d38 Utils: add missing scene item methods 2018-10-30 14:53:48 +01:00
65a9139ffe Fixes Travis builds for macOS
Switching to Xcode 9.4 achieves parity with obs-studio.
Also QT 5.10.1 is not available as a bottle for Sierra
anymore, which leads to Travis building qt from sources.

By enabling output of the qt install step, the "missing
output" timeout in Travis should be fixed as well, once
qt is not available for High Sieera anymore.
2018-10-24 19:39:32 +02:00
3d76f078cd Silenced Homebrew update 2018-10-24 18:57:44 +02:00
a1de1b11bc Fixed Codacy commit check 2018-10-24 18:17:54 +02:00
b5a3e3a4f0 Fixed dependency check for homebrew packages 2018-10-24 16:04:09 +02:00
d7b0ad4916 Removed duplicate code (thanks CodeFactor..) 2018-10-24 15:17:43 +02:00
2a80a6b217 More POSIX sh fixes. 2018-10-24 15:14:50 +02:00
9df72f54d5 Fixed OS check and POSIX sh compatibility 2018-10-24 15:12:54 +02:00
ef75ca36c9 Added missing OS check in packaging script 2018-10-24 14:59:12 +02:00
84c0b698f5 Updated build files for macOS build
* Checks for OS type before executing
* Checks for Homebrew
* Checks for git (obs-studio build phase)
* Checks for cmake (obs-studio build phase)
* Installs/Updates depdencies
* Installs QT 5.10.1 from bottle (just like obs-studio)
* Pins QT so homebrew does not forcefully update it
* Removed wget dependency, uses macOS' curl
* More output
2018-10-24 14:55:17 +02:00
3d9a4ef1e6 cpp: fix build errors 2018-10-23 13:12:51 +02:00
cf51fdceef ci(macos): fix wget install error 2018-10-23 12:56:37 +02:00
85a52ab01f general: convert indents to tabs 2018-10-19 18:22:34 +02:00
f2792c0b40 docs(travis): Update protocol.md - 9710908 [skip ci] 2018-10-19 16:06:06 +00:00
97109087a4 DuplicateSceneItem bug fix
Courtesy of @TStod
2018-10-19 18:04:34 +02:00
953f07f21f new request types for filter management 2018-10-19 18:01:14 +02:00
03f1035690 Merge pull request #241 from caseymrm/patch-1
Add wget to install-dependencies-macos.sh
2018-10-19 17:32:59 +02:00
a561c60f7e Merge pull request #245 from PatTheMav/macos-10.14-qt-fix
CI: Fix QT 5.10 not building under macOS 10.13+
2018-10-19 17:32:19 +02:00
7963b328f9 Applied patches to enable QT 5.10 building 10.13+
Issue: https://github.com/Homebrew/homebrew-core/issues/27095
Applied: https://github.com/Homebrew/formula-patches/pull/237
Applied: https://github.com/Homebrew/homebrew-core/pull/27139
2018-10-18 01:26:00 +02:00
3b7e216409 Merge branch 'macos-qt-fix' into 4.3-maintenance 2018-10-06 12:24:21 +02:00
9ae43a6f75 ci(macos): fix Qt 5.10.0 installation 2018-10-06 12:06:43 +02:00
6b86de1fb9 Add wget to install-dependencies-macos.sh
wget is not installed by default, so when doing the build on a fresh machine, brew install it first
2018-09-27 14:07:14 -07:00
4e6d4ac437 events: fix triggering of PreviewSceneChanged 2018-07-31 11:27:10 +02:00
3b197651cc general: step by one commit to have CI running 2018-07-30 19:17:35 +02:00
c675f1c20c docs(travis): Update protocol.md - e87955d [skip ci] 2018-07-30 17:04:19 +00:00
e87955d59a ci(docs): fix docs deployment 2018-07-30 19:02:29 +02:00
c718d8d803 general: version bump + minor CI fixes 2018-07-30 18:57:24 +02:00
454a68d1b7 Merge pull request #199 from Palakis/4.3-jimtree-studiomode-fix
[not ready] events: fix triggering of PreviewSceneChanged
2018-07-29 17:01:48 +02:00
45f6f74cbe cmake: remove copy operation 2018-07-21 23:05:38 +02:00
cb7412a457 events: fix triggering of PreviewSceneChanged 2018-07-08 12:58:06 +02:00
a9fc82365c TransitionBegin: add source and destination scene names 2018-06-24 22:33:07 +02:00
edc0fed9e2 Merge branch 'transition-override-begin-event' into 4.3-maintenance 2018-06-24 22:20:55 +02:00
1c718963ea TransitionBegin: support for transition overrides 2018-06-24 22:19:45 +02:00
cd40ccdb9d Merge pull request #211 from wherget/build-fixes
MacOS Build fixes
2018-05-27 18:38:39 +02:00
80e1dc2446 Merge pull request #210 from RytoEX/build-instruction-update
Build instruction update
2018-05-27 18:35:07 +02:00
d03c4cc4b9 Merge pull request #219 from christopher-dG/cdg/replaystopping
docs: Fix ReplayStopping description
2018-05-27 18:34:58 +02:00
7bd434e755 ci(macos): get rid of manual Packages versioning 2018-04-28 23:53:47 +02:00
640bcb90c6 CI(macOS): Split off obs-studio build task
Split off the obs-studio build task to help separate CI log sections.
2018-04-28 23:53:42 +02:00
08e86a1378 CI(macOS): Use latest Qt from Homebrew
OBS Studio CI currently uses the latest Qt version in Homebrew.
2018-04-28 23:53:36 +02:00
fefcc3937a CI(macOS): Update Packages version
Update Packages to version 1.2.3, which released on April 7, 2018.
2018-04-28 23:53:28 +02:00
25210dfa52 CI and CMake improvements (#205)
* CMake: Copy PDB file to OBS build directory on Debug build

All native OBS build objects also bundle the associated PDB file for
debugging and handling crash reports.

* CMake: Add post-build commands for RelWithDebInfo

Add post-build commands for the RelWithDebInfo build config. OBS
official builds use RelWithDebInfo, so we should be able to treat it as
a release config.

* CI: Disable building OBS native plugins

Use the OBS CMake flag DISABLE_PLUGINS to disable building plugins
included with OBS (including submodule plugins like obs-browser). This
should speed up builds on Windows when we have to rebuild OBS and on
Mac.

* CI: Don't clone/update OBS submodules

The only submodules presently in OBS are in its plugins, which we don't
need to build.

* CI: Use obsproject/obs-studio instead of jp9000/obs-studio

The OBS GitHub recently changed from jp9000/obs-studio to
obsproject/obs-studio, so use that instead.

* CI: Build as RelWithDebInfo instead of Release

OBS official builds are produced with RelWithDebInfo. This will produce
a PDB file for the plugin, similar to the native OBS plugins.

* CI(Windows): Build OBS if current build config doesn't exist

If OBS libs for the current build config do not exist, build OBS before
building obs-websocket.
2018-03-26 11:12:39 +02:00
56fbb7b9cf CI(Windows): Build OBS with Visual Studio 2017
We should also build OBS with Visual Studio 2017.
2018-03-26 11:12:19 +02:00
c55d33b956 CI(Windows): Use Visual Studio 2017
Official OBS releases have switched to Visual Studio 2017. There is no
32-bit Qt 5.10.1 package for Visual Studio 2017, but the Visual Studio
2015 package is compatible.
2018-03-26 11:11:18 +02:00
0c5bce101e CI(linux): forgot to install checkinstall 2018-03-20 14:27:04 +01:00
0a50e2a95c ci(linux): use OBS dev deps from PPA release 2018-03-20 14:11:33 +01:00
5ad940924b CI(windows): build installer
ci(windows): escape path


ci(windows): add inno setup compiler to PATH


ci(windows): fix installer.iss path
2018-03-19 02:36:24 +01:00
6eb49930bf general: version bump to 4.3.3 2018-03-19 01:11:35 +01:00
3a0d5fb190 CI: let's try with VS2015 instead 2018-03-18 23:25:38 +01:00
8c2eee2e25 CI: update Windows builds to VS2017 and Qt 5.10.1 2018-03-18 23:25:31 +01:00
2e19c5f08a docs: update version 2018-03-14 23:03:14 +01:00
548b53437f general: version bump to 4.3.2 2018-03-14 22:58:53 +01:00
e1ca9a8029 notifications: check if system tray is available before notifying 2018-03-14 22:26:08 +01:00
389ef2aea9 Config: remove flawed qstring_data_copy and add toUtf8() macro 2018-03-14 22:26:00 +01:00
357691bad5 CI: bump to latest OBS version 2018-01-22 23:24:09 +01:00
beadb56b05 general: update version to 4.3.1 2018-01-22 23:13:27 +01:00
745fb5ea29 Merge branch 'scenecol-crash-fix' 2018-01-22 23:11:24 +01:00
58434cac3b events: fix crash on exit 2018-01-22 23:10:33 +01:00
c34dff17ff events: fix crash when switching between scene collections 2018-01-22 23:03:20 +01:00
73f00ca195 CI: update Packages version 2018-01-12 11:13:16 +01:00
40503b4212 docs(travis): Update protocol.md - 4ada388 [skip ci] 2018-01-12 08:28:38 +00:00
4ada388828 general: version bump 2018-01-12 09:25:41 +01:00
e2d261259d docs(travis): Update protocol.md - 677e393 [skip ci] 2018-01-12 08:20:52 +00:00
677e393f47 Add support for filename formatting get/set (#162) 2018-01-12 09:18:58 +01:00
e5dae8f5a7 UI: update translations from Crowdin 2018-01-12 08:54:12 +01:00
650de119a4 general: New Crowdin translations (#171) 2018-01-04 08:31:45 +01:00
6d7975afeb requests: Create folder for 'SetRecordingFolder' if it does not exist (#163) 2017-12-16 16:49:38 +01:00
811e3f8cc8 Update Crowdin configuration file 2017-11-14 10:09:14 +01:00
4f3be34db1 ui: update translations from Crowdin 2017-11-14 10:01:04 +01:00
b160fd2320 docs: update README and BUILDING documents 2017-11-14 09:44:24 +01:00
002bf08b97 docs(travis): Update protocol.md - a6bab96 [skip ci] 2017-11-14 08:24:23 +00:00
a6bab968f5 requests: Get/Set local file path in BrowserSource settings 2017-11-14 09:21:58 +01:00
408b336057 docs(travis): Update protocol.md - 7e57161 [skip ci] 2017-11-14 08:15:34 +00:00
7e5716185e code: reorder and refactor WSRequestHandlers 2017-11-14 09:14:33 +01:00
6d47bd6477 Merge pull request #159 from haganbmj/docs-safety
docs: fail CI build if documentation is invalid
2017-11-14 07:17:44 +01:00
989c8b1857 docs: fail CI build if documentation is invalid 2017-11-13 23:25:54 -05:00
06e9e0afab docs(travis): Update protocol.md - d75523c [skip ci] 2017-11-13 19:37:48 +00:00
d75523c111 Merge pull request #157 from haganbmj/patch-2
docs: update glob to account for relocation to src
2017-11-13 20:36:47 +01:00
dacec803c6 docs: Fix documentation failure 2017-11-13 13:57:42 -05:00
0ae4416242 docs: mustache helper safety check 2017-11-13 13:50:56 -05:00
079d7eb6ca docs(travis): Update protocol.md - b764b4d [skip ci] 2017-11-13 16:58:21 +00:00
b764b4d0e5 code: split WSRequestHandler into several code/compile units 2017-11-13 17:56:58 +01:00
2c2d61d908 docs(travis): Update protocol.md - 6a323b9 [skip ci] 2017-11-13 16:18:44 +00:00
6a323b9371 code: move code units to src/ 2017-11-13 17:17:34 +01:00
d76ff16162 ui: show a warning message when server can't bind to configured port 2017-11-13 16:39:32 +01:00
952f3a586a server: refactor connect/disconnect popup messages localization 2017-11-13 15:56:46 +01:00
3ecb9702ef docs(travis): Update protocol.md - 563936e [skip ci] 2017-11-13 14:03:18 +00:00
563936ea08 requests: add GetSourceTypesList 2017-11-13 15:01:41 +01:00
13cd2704ae Revert "docs: bump to 4.2.1 release"
This reverts commit ae83d9dca1.
2017-11-13 08:59:54 +01:00
ae83d9dca1 docs: bump to 4.2.1 release 2017-11-13 08:58:00 +01:00
66a059ecdf Automatic function-scope OBS _release() + refactor to camelCase (#149)
- Refactor all internal variables to camelCase
- Use obs pointers with *AutoRelease classes for automatic refcounting
    - Important: some pointers require prior investigation to determine if refcounting is needed, or which 
      between OBSRef and *AutoRelease should be used.
2017-11-13 08:44:26 +01:00
53936a4f76 request handler: use static request QHash
Inspired by @yinzara's fork
2017-11-06 13:03:05 +01:00
488a095fdf ci(macos): fix broken Packages installation 2017-11-06 12:34:47 +01:00
1878de9f6b docs(travis): Update protocol.md - 2a2f344 [skip ci] 2017-11-06 09:55:25 +00:00
2a2f3441ef requests: don't persist stream settings in StartStreaming 2017-11-06 10:53:05 +01:00
53c939a97f Merge pull request #146 from RytoEX/fix-ci
Fix CI builds on Windows and Linux
2017-10-28 18:13:20 +02:00
8d7ed32fc2 Windows CI: Build with Qt 5.7.1 if Qt 5.7.0 is unavailable 2017-10-28 00:29:48 -04:00
a1fa5dc3cb Windows CI: Only build obs-studio if not cached
Run a series of checks to determine if CI needs to build obs-studio
before trying to build obs-websocket.
2017-10-28 00:28:47 -04:00
4b9a84ccee docs(travis): Update protocol.md - 650957b [skip ci] 2017-10-27 21:10:39 +00:00
650957b6d1 Merge pull request #151 from haganbmj/patch-1
docs: correct GetSceneItemProperties name
2017-10-27 23:09:32 +02:00
6e21d041fe docs: correct GetSceneItemProperties name
Fixes #150
2017-10-27 12:45:29 -04:00
e2b70fd795 docs: realign comments 2017-10-27 15:51:23 +02:00
2d49bcc437 docs(travis): Update protocol.md - 8b3dce3 [skip ci] 2017-10-27 12:32:25 +00:00
8b3dce3256 requests: add GetSourcesList 2017-10-27 14:29:58 +02:00
8cf6a1e72c SetSourceSettings: fix oops 2017-10-27 14:29:41 +02:00
a3ce7197ac docs(travis): Update protocol.md - 5da1e55 [skip ci] 2017-10-27 12:27:53 +00:00
5da1e55f8e docs: Fix new requests names 2017-10-27 14:27:00 +02:00
661fd4efa8 docs(travis): Update protocol.md - 047e6e1 [skip ci] 2017-10-27 12:21:14 +00:00
047e6e11bf Merge remote-tracking branch 'origin/master' 2017-10-27 14:19:33 +02:00
db8bc1af2d requests: add GetSourceSettings and SetSourceSettings 2017-10-27 14:18:45 +02:00
1386e4f91c docs(travis): Update protocol.md - 11e2717 [skip ci] 2017-10-27 08:39:02 +00:00
11e2717809 docs(travis): Update protocol.md - 57bc0a2 2017-10-27 10:37:30 +02:00
57bc0a2b95 Merge pull request #136 from TStod/tstod/SceneItemGetter
[Safe For Merge] Unified Setter and Getter for scene specific source properties
2017-10-27 10:31:50 +02:00
8efb30c4ee Merge pull request #148 from haganbmj/docs-deprecated
docs: Add deprecated attribute
2017-10-27 10:31:16 +02:00
969feefcc5 Merge remote-tracking branch 'origin/master' 2017-10-27 10:25:07 +02:00
db1527ab9b StartStreaming: add missing comment 2017-10-27 10:24:58 +02:00
e88a60fa50 docs(travis): Update protocol.md - 7ab3e38 [skip ci] 2017-10-27 08:21:09 +00:00
7ab3e38da7 StartStreaming: fix request docs + code clean 2017-10-27 10:19:27 +02:00
7b0b836809 requests: continued refactor of Get/Set/Save/Start stream settings 2017-10-27 10:03:22 +02:00
c8e7cfcd7b requests: Refactor Get/SetStreamSettings code 2017-10-27 09:10:04 +02:00
c516c89c97 docs: Add deprecated attribute 2017-10-25 11:59:14 -05:00
c2937d7857 replay buffer: start without "Save" hotkey set 2017-10-25 18:29:42 +02:00
fe644cfa82 Merge pull request #142 from RytoEX/save-replay-buffer
Fix saving replay buffer when no hotkey is set
2017-10-25 09:46:28 +02:00
e84e5388a5 fixing scale data get in setter 2017-10-24 11:54:21 -04:00
5e11d0ea13 general: style fixes + change request handler lookup 2017-10-24 17:16:06 +02:00
a3ecb6e0e9 adding missing scale setter in setter 2017-10-24 02:16:39 -04:00
1d30f13fd8 adding error response object method 2017-10-23 12:04:43 -04:00
b9ae28483c adding error messages 2017-10-23 11:34:29 -04:00
6a6d316e09 ci(linux + win): bump to latest OBS version 2017-10-22 00:01:44 +02:00
22d2ee6bbd Merge pull request #141 from PicoCentauri/master
macOS CI: Updated dependencies and namespaces
2017-10-21 23:57:41 +02:00
68e55613fc macOS CI: Updated dependencies and namespaces
Following the discussion of #105 the Qt version in the dependency
installation is now hardcoded to version 5.9.2, which is the same as the
main obs version for 20.1.0. Furthermore the names are changed from
osx to MacOS.
2017-10-19 17:32:58 +02:00
1d44d3a109 Fix saving replay buffer when no hotkey is set
This commit makes saving the replay buffer not rely on whether or not a
hotkey is set in OBS.
2017-10-19 05:34:39 -04:00
11a641cc0d replay buffer: update for Advanced output mode 2017-10-19 10:09:00 +02:00
7570fbf7a4 strings to ENUM strings 2017-10-17 19:07:40 -04:00
67f7e28867 fixing bounds type setter 2017-10-16 12:29:49 -04:00
9b2d30b4d5 switch string to ifelseif 2017-10-16 12:17:29 -04:00
399815525f wrapping up setter and getter 2017-10-16 12:01:41 -04:00
191cfa08d5 server: Refactor mutex lock handling 2017-10-16 15:29:23 +02:00
fcf1fa8aff studio mode: Remove parts of Qt UI hacks 2017-10-16 15:28:54 +02:00
73302cb060 docs(travis): Update protocol.md - 70a8533 [skip ci] 2017-10-16 11:13:41 +00:00
70a8533f5e docs(requests): fix SetStreamSettings 2017-10-16 13:12:49 +02:00
fe724db12d removing copy paste error 2017-10-15 17:28:07 -04:00
096a8ec6ba wip on setter and getter 2017-10-15 14:21:45 -04:00
cc5f9c9aa7 refining bounds and adding scale to setter 2017-10-15 11:57:21 -04:00
3e14b41600 ands to ors 2017-10-14 16:44:02 -04:00
ccc2bd8667 trying to fix duplicate case statment error 2017-10-14 16:35:36 -04:00
45f86b17f1 more WIP on setter 2017-10-14 15:18:06 -04:00
90aebecc5b small fixes 2017-10-14 14:36:23 -04:00
b0170ef671 more WIP on setter 2017-10-14 13:10:40 -04:00
d418b4e624 fixing methods for getting inner object keys 2017-10-11 12:07:33 -04:00
47505547af fixing some types 2017-10-11 11:51:05 -04:00
de2e73c9d4 WIP setter and fixing small typo 2017-10-10 21:14:27 -04:00
ab1a43163b early return on bad param 2017-10-04 13:26:15 -04:00
2556dd320f fixing bounds alignemnt getter 2017-10-04 13:05:01 -04:00
2120381c0e correnting method name 2017-10-04 12:48:40 -04:00
dc06900f7f another typo 2017-10-04 12:44:07 -04:00
7571dd5042 fixing typo 2017-10-04 12:13:52 -04:00
d6caa872b8 WIP some small fixes 2017-10-04 02:06:38 -04:00
2e9829ddd1 WIP questions to be answered 2017-10-04 00:51:16 -04:00
9621ea90f7 more WIP 2017-10-03 20:30:51 -04:00
f58da1254b wip 2017-10-03 20:23:55 -04:00
a6ba7f8feb docs(travis): Update protocol.md - 9c89f12 [skip ci] 2017-10-02 07:08:16 +00:00
9c89f12275 docs: fix spec version links 2017-10-02 09:07:17 +02:00
41181f7260 docs(travis): Update protocol.md - 7251862 [skip ci] 2017-09-26 09:21:30 +00:00
7251862ecf Merge pull request #124 from haganbmj/patch-2
docs: HandleSetHeartbeat -> SetHeartbeat
2017-09-26 11:19:15 +02:00
c0512d5b5f docs: HandleSetHeartbeat -> SetHeartbeat 2017-09-25 11:32:34 -05:00
b9731dff21 docs(travis): Update protocol.md - 3981abc [skip ci] 2017-09-25 14:50:02 +00:00
3981abc5ca General: code and docs cleanup 2017-09-25 16:48:46 +02:00
6aef437f58 UI: update French localization 2017-09-25 16:29:50 +02:00
9b7752896a Adding Heartbeat to obs-websocket. (#112)
* Added a Heartbeat in WSEvents (.cpp and .h updated)
Lines where the coded is added are marked with "//mod-0x =============" (x denotes a number).

* Updated the Heartbeat:
- Default it is off, and via JSON request can be switched on.
- The Heartbeat has configurable elements. Each individual element can be requested via a JSON request to be added to the Heartbeat.
- Maximum elements are in this example JSON update:
{
    "current-profile": "#2-PGH",
    "current-scene": "#2-HG",
    "pulse": true,
    "recording": false,
    "streaming": false,
    "total-record-time": 0,
    "total-stream-time": 0,
    "update-type": "Heartbeat"
}

* Cleaning

* Process all Palakis his comment/advice, and added 3 key/value pairs.

* Processed cleaning up comments as proposed by Stephane.

* Still found tabs in the comment header, and replaced by spaces.

* FUNCTIONAL MODIFICATION:
- Removed selective content of Heartbeat, and show all key/value parameters.
- Only request left is "on" or "off" selection of heartbeat.
FIX:
- Show streaming/recording related data only when streaming/recording is active, due to OBS showing wrong values when not active.

* Changed request type to start the Heartbeat to a boolean parameter.

* Delete ZERO_CHECK.log

* Delete obs-websocket_autogen.log

* Delete obs-websocket.log
2017-09-24 22:54:29 +02:00
d074027610 Merge pull request #121 from haganbmj/toggle-notifications
general(notifications): Toggle tray notifications/alerts (Closes #120)
2017-09-24 22:53:41 +02:00
b137148186 general(notifications): Toggle tray notifications/alerts
Closes #120
2017-09-12 10:41:35 -05:00
e1f6260034 docs(travis): Update protocol.md - b24cbaa [skip ci] 2017-09-08 20:14:45 +00:00
b24cbaa904 General: version bump to 4.2.1 2017-09-08 22:13:58 +02:00
cc0b110d61 Utils: style nitpick 2017-09-08 21:54:40 +02:00
82a1e7d253 docs(travis): Update protocol.md - 87b4768 [skip ci] 2017-09-08 19:43:16 +00:00
87b47689eb Docs: more specific version number + tag unreleased methods/events 2017-09-08 21:42:13 +02:00
c3346f9192 Notifications: correct IP address formatting 2017-09-08 00:23:17 +02:00
3b0cf02574 Server: QThread wasn't actually used... 2017-09-08 00:01:26 +02:00
4404889019 Merge pull request #117 from haganbmj/docs-since
docs: Add @since
2017-09-01 14:07:20 +02:00
4e642d97fc docs: Add @since 2017-08-26 16:25:17 -04:00
44bd25646d docs(travis): Update protocol.md - 09b0403 [skip ci] 2017-08-24 18:28:28 +00:00
09b04037a0 Merge pull request #114 from haganbmj/patch-1
Update api doc for SwitchScenes
2017-08-24 20:27:18 +02:00
3c50e1e4d8 Update api doc for SwitchScenes 2017-08-24 10:18:00 -05:00
179ba9a9e6 docs(travis): Update protocol.md - 9ce2b19 [skip ci] 2017-08-19 13:06:57 +00:00
9ce2b1983a Protocol: add Replay Buffer Events and Requests (#104) 2017-08-19 15:05:42 +02:00
37dde278cb CI: updated name hack 2017-08-19 12:29:12 +02:00
1cc57e7d1d CI: hack to show "build names" in Travis 2017-08-19 12:26:23 +02:00
60a5cdb154 docs(travis): Update protocol.md - f290cbc [skip ci] 2017-08-19 10:17:09 +00:00
f290cbc148 Docs: update introduction with other versions 2017-08-19 12:15:57 +02:00
2e2e9b1332 Code: small style fixes 2017-08-19 11:40:42 +02:00
7915e3815b CMake on Windows: copy debug binaries to obs dev folder for testing 2017-08-19 11:40:25 +02:00
beb34deed8 docs(travis): Update protocol.md - a263d8a [skip ci] 2017-08-13 15:42:56 +00:00
a263d8a364 Docs: Initial generation of docs from code comments (#106) 2017-08-13 17:41:42 +02:00
1eccf4c899 Readme: update libraries and contributors 2017-08-12 22:11:06 +02:00
d8d19d839a CI: add branch exclusion for gh-pages 2017-08-12 17:48:23 +02:00
17e573d67f Merge pull request #109 from dragonbane0/request-types-sync-offset
Add request types: SetSyncOffset and GetSyncOffset
2017-08-12 14:51:26 +02:00
59de83b45d Update Protocol.md with the new request types
Added SetSyncOffset and GetSyncOffset to the documentation
2017-08-07 16:02:09 +02:00
712bd6e8f3 Fix if statement
Add missing closing bracket and use !source_name instead of source_name == NULL
2017-08-07 15:34:43 +02:00
75b21d070a Fix spacing 2017-08-07 02:34:54 +02:00
e91c9e7bf4 Fixed spacing 2017-08-07 02:34:20 +02:00
2562272775 Added new request types: SetSyncOffset and GetSyncOffset 2017-08-07 02:19:09 +02:00
819621e307 macOS CI: fix dynlink paths 2017-08-06 22:59:44 +02:00
4b9229311c macOS CI: oops, forgot to update some lines 2017-08-06 22:46:30 +02:00
4c4c1de190 macOS CI: "brew versions" command doesn't work 2017-08-06 22:35:16 +02:00
6b6b7feff2 macOS CI: properly use Qt from Homebrew 2017-08-06 22:28:07 +02:00
9e3ce26ba0 Revert "macOS CI: install qt5 from Homebrew's index"
This reverts commit ee1486274d.
2017-08-06 22:12:36 +02:00
eac19a7c83 Logging: log Qt versions at startup 2017-08-06 21:50:25 +02:00
ee1486274d macOS CI: install qt5 from Homebrew's index 2017-08-06 21:45:24 +02:00
8692e2afda CI: OBS version bump 2017-08-06 07:12:04 +02:00
f995268444 macOS CI: symlink paths were wrong, again 2017-08-06 07:11:03 +02:00
bd2e974718 macOS CI: fix symlink paths 2017-08-06 06:56:00 +02:00
d1c64c7509 macOS CI: more debug statements 2017-08-06 06:30:00 +02:00
30a19cfe8a macOS CI: add debug statements for rpath fix 2017-08-06 06:22:24 +02:00
cc3097b09a GetVersion: add claryfying comment 2017-08-06 05:12:30 +02:00
c00681b52d Protocol: remove useless API version field 2017-08-06 05:10:14 +02:00
61931c179f Protocol: update GetVersion with list of available request types 2017-08-06 00:09:44 +02:00
c7190cb94a Events: add transition info to TransitionBegin 2017-08-05 22:17:13 +02:00
a1bd27dfde Server: fix refactoring mistake 2017-08-05 19:16:30 +02:00
6ac3a3de57 Protocol: add ResetSceneItem for resetting source items (#108)
Use case: Reset media sources for recovery of timed-out input streams.
2017-08-05 19:11:01 +02:00
e198ed7a9c GitHub: update issue template 2017-08-05 18:50:03 +02:00
4b89464349 General: refactor continued again 2017-08-05 18:26:14 +02:00
dba599c127 General: refactor continued 2017-08-05 03:21:28 +02:00
586f9076f0 General: code style refactor 2017-08-05 03:14:07 +02:00
add39cfc5f General: version bump 2017-07-10 14:23:13 +02:00
e3ad148c15 Request types: get/set/save streaming settings (PR #100)
* Adding support for changing streaming server settings

* Updates after initial code review for customized rtmp settings

* Updating PROTOCOL.MD documentment for streaming service settings

* Changes based on code review
2017-07-06 13:07:06 +02:00
acffacd67d Request/Response: refactor logging code 2017-07-04 21:20:34 +02:00
54a16f4d2f Events: refactor logging code 2017-07-04 21:18:42 +02:00
35cb506d6e Merge pull request #98 from haganbmj/request-logging
General: Toggle to log incoming requests
2017-07-04 20:20:33 +02:00
7675a1ee58 General: Toggle to log outgoing response and events 2017-07-04 14:17:51 -04:00
863f5e28b3 Merge pull request #99 from Rosuav/patch-1
Fix formatting (trivial)
2017-07-03 15:54:19 +02:00
1a6f6096e4 Fix formatting (trivial) 2017-07-03 23:39:42 +10:00
b57982b6cb General: Toggle to log incoming requests 2017-06-29 22:11:51 -05:00
4132356141 Protocol: Add Get and Set Browser Source Properties (#95)
* Protocol: Add Get and Set Browser Source Properties

* address white space - I don't see the indentation issue at my end for protocol.md but I'll try
2017-06-22 18:51:19 +02:00
e560e95310 Update README.md 2017-06-22 17:11:57 +02:00
65f4ff6a30 Protocol: add request types to get/set Text GDI Plus sources (#94)
* Protocol: add request types to get/set Text GDI Plus sources

* address comments
2017-06-20 16:07:47 +02:00
ab38f33530 docs: Update README to add a Python library 2017-06-20 10:54:30 +02:00
1f04ee8252 CI: Fix wrong OS X deps paths 2017-06-20 09:41:07 +02:00
739bd6f696 CI: Fix wrong Qt paths 2017-06-08 12:25:44 +02:00
066145ab31 Server: replace Q_FOREACH with ranged for-loops 2017-06-08 11:18:50 +02:00
54d0f764d4 CI: Fix OS X non-working @rpath replace 2017-06-06 11:57:04 +02:00
1398689ebf CI: Fix OS X Qt 5.8 fetch 2017-06-06 11:09:53 +02:00
647625628d CI: Fix dependency versions 2017-06-06 10:35:36 +02:00
e17df69b80 Merge remote-tracking branch 'origin/master' 2017-06-06 09:50:12 +02:00
f001d18eea Protocol: add request types to get/set current recording folder 2017-06-06 09:50:02 +02:00
f49980350a General: code style refresh 2017-06-06 09:30:07 +02:00
d0a90ecea4 Update PROTOCOL.md 2017-06-06 00:54:32 +02:00
4506b46ba0 Docs: update Contributing Guidelines 2017-05-31 15:29:23 +02:00
a8c36d7366 Docs: add issue template 2017-05-31 15:24:58 +02:00
781eaec683 Docs: add Contributing Guidelines 2017-05-31 15:08:30 +02:00
bdee8f318a Update package-osx.sh 2017-05-30 22:29:06 +02:00
82dac4d208 CI: Create "latest" copy of OS X artifact 2017-05-30 22:06:23 +02:00
386e1f3b46 General: 80 columns refactor in C/C++ headers 2017-04-26 10:07:42 +02:00
a3cbbf3ea9 General: refactor to limit linesize to 80 columns 2017-04-26 10:02:02 +02:00
e647debcfb Handler: fix possible duplicate data release 2017-04-25 08:53:24 +02:00
fba9bd2b76 Auth: refactor CheckAuth and GenerateSecret to avoid hazardous operations 2017-04-24 22:45:08 +02:00
56217e0176 Revert "CMake: work towards more auto-detect on Linux"
This reverts commit 77c5801c4c.
2017-04-24 13:33:50 +02:00
77c5801c4c CMake: work towards more auto-detect on Linux 2017-04-24 13:14:51 +02:00
7db879cca9 Protocol spec: Specify plugin version number 2017-04-24 12:38:53 +02:00
8d0cb2e875 Auth: free string after use 2017-04-24 11:01:11 +02:00
dc7b386295 Create README.md 2017-04-24 10:20:46 +02:00
03c8c6385c Bugfix: wrong behaviour in TransitionToProgram with a specif. transition 2017-04-23 23:28:06 +02:00
841de2f752 Systray: notify disconnects 2017-04-23 19:12:53 +02:00
9de6e229d9 Preliminary work on Recording folder get/set 2017-04-23 18:54:24 +02:00
8dfe471ef2 Server: small refactor 2017-04-23 15:41:39 +02:00
da05f315be Systray: Localizable notification 2017-04-23 15:30:09 +02:00
d014a7ab25 Requests: Start/Stop Streaming/Recording request now have errors 2017-04-23 15:22:10 +02:00
d0118c63c0 Bugfix: PreviewSceneChanged not emitted when swapping scenes 2017-04-23 12:28:51 +02:00
9ddfad99ea Bugfix: calling SetPreviewScene with an unknown source doesn't errors 2017-04-23 12:09:58 +02:00
82d74fcb2f CI: Build plugin with latest known stable release of OBS 2017-04-21 21:20:17 +02:00
0f4f029a76 Tray icon: remove debug message 2017-04-21 17:34:33 +02:00
2c581e9998 Merge remote-tracking branch 'origin/master' 2017-04-21 16:19:59 +02:00
f2028c506a UI: System tray notification on new connections 2017-04-21 16:16:11 +02:00
200db77140 UI: System tray notification on new connections 2017-04-21 15:59:32 +02:00
007604cc21 Readme: changed G-monitor's username 2017-04-21 14:21:07 +02:00
89486e9172 Readme: fix layout again 2017-04-21 14:17:55 +02:00
a60ca96fd1 Readme: update layout 2017-04-21 14:06:14 +02:00
0ade2c869d Readme: fix layout 2017-04-21 14:03:16 +02:00
9986382850 Supporters: add MediaUnit 2017-04-21 14:02:22 +02:00
89b9165c25 Protocol spec: update ToC 2017-04-21 13:42:50 +02:00
5d290165a2 Add request type GetSpecialSources 2017-04-21 13:39:28 +02:00
d267171cc7 Merge remote-tracking branch 'origin/master' 2017-04-21 11:14:56 +02:00
cebe325e81 New request type GetMute + request handler refactor 2017-04-21 11:14:47 +02:00
4e6178881b Update README.md 2017-04-21 08:10:43 +02:00
6bca8194cb Update README.md 2017-04-21 00:28:51 +02:00
a4885f332d README: Add sponsor mention for Support Class 2017-04-21 00:28:20 +02:00
cf97fb2051 Misc: add Support Class logo for sponsor mention 2017-04-20 23:38:43 +02:00
20a8853854 Protocol spec: better readability 2017-04-20 23:26:41 +02:00
dd487a5055 Version bump 2017-04-20 22:58:52 +02:00
72ca07f571 Merge branch 'preview-api' 2017-04-20 22:27:46 +02:00
c7305889c3 Events: add scene description to SwitchScenes and PreviewSceneChanged 2017-04-20 19:41:58 +02:00
bc24497760 Merge branch 'master' of github.com:Palakis/obs-websocket 2017-04-20 17:22:19 +02:00
200e65c730 OS X: Fix locales packaging 2017-04-20 17:22:01 +02:00
b32d8ef1e7 Merge remote-tracking branch 'origin/master' 2017-04-20 17:09:22 +02:00
8cfc613a3d Protocol spec: add missing scene visibility field in spec 2017-04-20 17:09:13 +02:00
5fbc2cbac7 OS X: fix linked libraries paths 2017-04-20 16:44:00 +02:00
9d7a32aa1f OS X: Fixed server threading issue 2017-04-20 16:24:41 +02:00
bcc9ef82d1 Oops, forgot to uncomment something 2017-04-20 16:16:36 +02:00
d6b28191e8 Fix another linker issue in OS X 2017-04-20 15:58:21 +02:00
acd8a9ea69 Travis: don't include unwanted artifacts 2017-04-20 14:35:48 +02:00
d4b8a8ff9e OS X: Fix linker issue 2017-04-20 14:25:56 +02:00
a698f7bdf5 Merge branch 'master' of github.com:Palakis/obs-websocket 2017-04-20 14:00:35 +02:00
16f07ff0c3 Travis OS X Packaging: include QtWebSockets in pkg 2017-04-20 13:58:31 +02:00
ed4526751e Better param checks for Studio Mode request types 2017-04-20 10:06:43 +02:00
e241518f8d Add StudioModeSwitched event 2017-04-20 09:53:31 +02:00
a6677edbf5 Add ability to specify a transition when calling TransitionToProgram 2017-04-19 18:53:09 +02:00
5748e163f8 Added GetPreviewScene and modified GetStudioModeStatus 2017-04-19 15:21:21 +02:00
85fa6b60e2 Update PROTOCOL.md 2017-04-19 15:00:47 +02:00
f0bb941c47 New request type : TransitionToProgram 2017-04-19 14:36:53 +02:00
b7df1e8596 Add SetPreviewScene request type and PreviewSceneChanged event 2017-04-19 13:43:58 +02:00
ff8eda3682 Fixed detection and control behaviour for some functions used by Preview API 2017-04-19 11:50:49 +02:00
8c4bd91c78 New request types to get and set the status of Studio Mode 2017-04-19 11:34:37 +02:00
07c868edcd Preliminary work on the Preview request types 2017-04-19 11:21:10 +02:00
69061869a7 Update README.md 2017-04-17 02:17:45 +02:00
98b2ac9bdc Update BUILDING.md 2017-04-17 02:14:55 +02:00
ef2bbff4e5 Travis: add support for tag package naming 2017-04-17 01:32:22 +02:00
7bb8e56072 Travis: fix OS X Packages project path 2017-04-17 00:56:29 +02:00
df0926f6fd Travis: OS X deploy 2017-04-17 00:52:07 +02:00
75572279a9 Travis: OS X packaging 2017-04-17 00:40:50 +02:00
328c6a0f7c Travis: woops, wrong section 2017-04-16 05:12:00 +02:00
ecd5062975 Travis: allow all branches for Linux deploy 2017-04-16 05:03:44 +02:00
c01ae5610d Travis: pass tag env var to docker container 2017-04-16 04:55:03 +02:00
564e7f31c3 Travis: better idea 2017-04-16 04:42:25 +02:00
efa61952b3 Travis: better file glob 2017-04-16 04:35:08 +02:00
ae27a26ebe Travis: fix checkinstall issue with version numbers 2017-04-16 04:19:22 +02:00
d81076f720 Fix Travis deploy behaviour 2017-04-16 03:54:19 +02:00
528f16c5e1 Travis: different approach for Linux builds upload 2017-04-16 03:43:25 +02:00
13ac8bfa90 Travis: testing the contents of the folder but outside of the container 2017-04-16 03:11:53 +02:00
935c58b17b Travis: trying packaging in another dir for upload 2017-04-16 01:32:32 +02:00
31e133bf06 Travis: gosh 2017-04-16 01:22:26 +02:00
eb7fb6694c Travis: okay, trying another strategy to get rid of build failures 2017-04-16 00:58:46 +02:00
a298577da1 Travis: wrong path for artifacts 2017-04-16 00:43:20 +02:00
8cd6d43ec4 Travis: wth happens with checkinstall builds 2017-04-16 00:27:02 +02:00
b8fd143cc1 Travis Linux: fix possible permissions issue with the built package 2017-04-15 23:15:04 +02:00
045ae058a3 Travis: Linux upload still not working 2017-04-15 22:56:38 +02:00
52c9816db2 Travis: trying again 2017-04-15 22:42:58 +02:00
32440580f6 Travis: dynamically find artifacts to upload 2017-04-15 22:30:41 +02:00
906d986f4b Travis: trying to make artifact upload working 2017-04-15 22:20:03 +02:00
0198651ca0 Travis: trying to resolve the issue with failing Linux packaging 2017-04-15 22:09:24 +02:00
d911c40897 Travis: fix file permissions (again) 2017-04-15 21:58:58 +02:00
ca8a117335 Travis: renamed some scripts 2017-04-15 21:49:41 +02:00
f75e2f0ada Travis: push build artifacts to S3 2017-04-15 20:11:45 +02:00
ea12b62235 Fix typo in Protocol spec 2017-04-15 19:27:37 +02:00
0af4af7e50 Merge remote-tracking branch 'origin/master' 2017-04-15 17:15:28 +02:00
f017491f44 Travis: fix Debian package version 2017-04-15 17:08:34 +02:00
ed433d3312 Travis: fix script permission 2017-04-15 16:45:21 +02:00
9ba2e83857 Fixed typos in CMakeLists.txt 2017-04-15 15:52:15 +02:00
af0ec74704 Travis: Linux packaging (again) 2017-04-15 15:40:34 +02:00
251402e844 Merge remote-tracking branch 'origin/master' 2017-04-15 15:24:56 +02:00
1a5318ebe0 Travis: Linux packaging 2017-04-15 15:24:46 +02:00
15aac3f436 Update README.md 2017-04-15 13:59:41 +02:00
5f373632e6 Merge pull request #67 from inpothet/master
Dutch Locale
2017-04-15 13:58:19 +02:00
0d518bf8df Dutch Locale 2017-04-15 13:45:44 +02:00
16b735c3a4 Travis: Some preliminary work on OS X packaging 2017-04-15 13:28:18 +02:00
1239f094fd Trying something 2017-04-15 13:00:16 +02:00
6986fa51eb Travis: keep only the Docker method for Linux builds
This avoids hacks and allows us to use a recent Ubuntu release even of Travis' Ubuntu infrastructure
2017-04-15 01:26:27 +02:00
f4bc88bf73 Travis: remove possibly useless line 2017-04-15 01:24:31 +02:00
ad915536a3 Travis: Removed debug ls command 2017-04-15 01:21:23 +02:00
f48b31664e Trying with absolute paths 2017-04-15 01:08:48 +02:00
d6c0eb1998 Trying something else 2017-04-15 00:50:31 +02:00
d3dcfba463 Trying something 2017-04-15 00:42:47 +02:00
3619dcd777 Merge remote-tracking branch 'origin/master' 2017-04-14 23:41:59 +02:00
86c002e318 Okay, rolling back some changes 2017-04-14 23:41:25 +02:00
42a807ce9c Okay, rolling back some changes 2017-04-14 23:40:16 +02:00
9bfff9539e Meh, not working as expected 2017-04-14 23:27:39 +02:00
edf4d919fe Encore a fix for Travis OS X 2017-04-14 23:12:36 +02:00
b0fb3d8c61 Fix for Travis OS X 2017-04-14 22:47:37 +02:00
5143090142 Work on OS X Travis builds 2017-04-14 22:38:57 +02:00
bc1555ca5b Update before-script-osx.sh 2017-04-14 22:19:48 +02:00
146a392f1a Merge remote-tracking branch 'asquelt/master' 2017-04-14 22:11:24 +02:00
e2fc205a35 Update README.md 2017-04-14 21:25:41 +02:00
d4bdb216d2 verifying libobs location 2017-04-14 15:59:47 +02:00
bffc1d6a1f verifying libobs location 2017-04-14 15:51:11 +02:00
df0d890c11 Fixed typo 2017-04-14 15:46:10 +02:00
dceee4dede verifying libobs location 2017-04-14 15:39:14 +02:00
dedbbf12bb Implement #63 2017-04-14 15:36:05 +02:00
bdb53d07f5 upstream proposed fix for #59 2017-04-14 15:21:33 +02:00
547991df72 Try to do something for #59 2017-04-14 15:11:53 +02:00
fd17557a96 try to build osx version 2017-04-12 15:20:22 +02:00
a9e928a799 try to build osx version 2017-04-12 15:08:49 +02:00
84b3e086e6 try to build osx version 2017-04-12 14:38:12 +02:00
5e3c794aec try to build osx version 2017-04-11 14:16:10 +02:00
98acbb2b2b try to build osx version 2017-04-11 13:45:07 +02:00
318f0753b0 try to build osx version 2017-04-11 13:25:08 +02:00
8908adbdee try to build osx version 2017-04-11 13:07:44 +02:00
fad7a26550 try to build osx version 2017-04-11 12:49:48 +02:00
38005fc0ef Merge pull request #60 from asquelt/locale
Polish locale
2017-04-10 12:59:43 +02:00
e3d3181ad6 try to build osx version 2017-04-06 17:43:55 +02:00
83d069e4d9 try to build osx version 2017-04-06 17:42:51 +02:00
f2db5a8229 try to build osx version 2017-04-06 17:07:14 +02:00
6ebb777256 Polish locale 2017-04-06 16:27:57 +02:00
db50c531d9 try docker 2017-04-06 13:26:40 +02:00
218f7af2e3 try docker 2017-04-06 13:19:33 +02:00
a19d2956c1 try docker 2017-04-06 13:07:42 +02:00
7a6b5b965d try docker 2017-04-06 13:01:09 +02:00
8a871aaddd try docker 2017-04-06 12:56:59 +02:00
bec0a0df10 try docker 2017-04-06 12:52:57 +02:00
1068a2dfbe try docker 2017-04-06 12:47:57 +02:00
b62be5d584 try docker 2017-04-06 12:44:57 +02:00
9686019693 try docker 2017-04-06 12:38:59 +02:00
32a066b4d4 try docker 2017-04-06 12:31:49 +02:00
d5a415e01e try to build osx version 2017-04-06 11:46:39 +02:00
33f83b8486 try to build osx version 2017-04-06 11:34:10 +02:00
adedf8d34f try to build osx version 2017-04-06 11:23:30 +02:00
9243071f03 try to build osx version 2017-04-06 11:12:05 +02:00
36050850d4 try to build osx version 2017-04-06 11:01:03 +02:00
c6d5cc555a try to build osx version 2017-04-05 13:33:36 +02:00
b271aa2aaa try to build osx version 2017-04-05 13:19:57 +02:00
d24961b3b9 try to build osx version 2017-04-05 12:59:07 +02:00
e52543efe7 try to build osx version 2017-04-05 09:34:15 +02:00
0b0e27ad3b try to build osx version 2017-04-05 00:41:35 +02:00
9f460f6c99 try to build osx version 2017-04-05 00:36:07 +02:00
c4529bb9a3 try to build osx version 2017-04-05 00:01:53 +02:00
5a069d2ffc try to build osx version 2017-04-04 23:24:07 +02:00
4fc78e455a Merge branch 'master' of github.com:asquelt/obs-websocket 2017-04-04 22:57:22 +02:00
75e2198315 try to build osx version 2017-04-04 22:57:16 +02:00
0d4bb4ed2d try to build osx version 2017-04-04 22:53:38 +02:00
577738ad0a try to build osx version 2017-04-04 21:43:19 +02:00
a8dfdb03fb try to build osx version 2017-04-04 21:08:31 +02:00
aebd470d49 try to build osx version 2017-04-04 20:28:39 +02:00
ce3dfd9678 try to build osx version 2017-04-04 20:20:31 +02:00
b82801b145 try to build osx version 2017-04-04 19:32:07 +02:00
a6ab35f1fb Revert "try to build osx version and xenial docker version"
This reverts commit a265cea4fb.
2017-04-04 19:03:52 +02:00
a265cea4fb try to build osx version and xenial docker version 2017-04-04 18:56:05 +02:00
166760651e try to build osx version and xenial docker version 2017-04-04 18:53:02 +02:00
41a9191223 try to build osx version and xenial docker version 2017-04-04 18:47:21 +02:00
1465e7760e try to build osx version 2017-04-04 18:20:35 +02:00
1a043f1dc0 try travis build with libqt5websockets5-dev binary 2017-04-04 17:34:54 +02:00
70c5b00c90 try travis build with libqt5websockets5-dev binary 2017-04-04 17:22:32 +02:00
bb5177dc79 debug 2017-04-04 17:06:40 +02:00
f60a9a632b try travis build with xenial (16.04) 2017-04-04 16:29:53 +02:00
024c47132b try travis build with qt532-trusty installed 2017-04-04 16:21:24 +02:00
73ce9cb4cc try travis build with qtbase5-private-dev installed 2017-04-04 15:51:44 +02:00
f45d439094 try travis build with qtcreator installed 2017-04-04 14:10:32 +02:00
2d5749a78c New request type : GetTransitionDuration 2017-03-19 23:57:01 +01:00
f078a10028 New request types for streaming and recording control
`StartStreaming`, `StopStreaming`, `StartRecording` and `StopRecording`
2017-03-19 23:50:23 +01:00
701f88e532 Added "SetSceneItemCrop" request type 2017-03-19 23:38:26 +01:00
1524a1997d Update README.md 2017-03-15 20:35:57 +01:00
b16f812f91 Update README.md
Mentions to contributors and added a link to my C# library
2017-03-15 19:55:38 +01:00
cde307d644 Update README.md 2017-03-15 16:29:17 +01:00
c47f6d093c Create BUILDING.md 2017-03-15 16:28:09 +01:00
f5336938c9 Update README.md 2017-03-12 11:57:41 +01:00
4985548edf Update pt-BR.ini 2017-03-10 08:33:39 +01:00
8bc4841e84 Merge pull request #53 from laris151/laris151-portuguese
Add Portuguese Translate
2017-03-10 08:32:46 +01:00
634f9833a1 Add Portuguese Translate 2017-03-09 22:28:04 -03:00
80052e62ee Update README.md 2017-03-05 17:10:18 +01:00
0672b6a157 Fixed typo in PROTOCOL.md 2017-03-02 15:55:46 +01:00
1e71bfa151 Fixed a crash when switching to another scene after switching to another scene collection 2017-03-02 15:36:48 +01:00
d4c2c8197a Merge branch 'master' of github.com:Palakis/obs-websocket 2017-03-02 15:07:11 +01:00
c9baed2df9 Fixes #24 2017-03-02 15:02:26 +01:00
1e2065c84a Merge pull request #50 from G-monitor/chinese
Add chinese translation
2017-03-02 14:24:28 +01:00
537d683dfb Add chinese translation 2017-03-02 17:46:10 +08:00
e6f1b9f8c8 Travis: trying something 2017-03-02 01:08:57 +01:00
5815d2bfce Lower minimum cmake version 2017-03-02 00:49:35 +01:00
7692e93306 Travis: install Qt Websockets from source 2017-03-02 00:44:23 +01:00
8768b83251 Added execute permission to Travis CI scripts 2017-03-02 00:29:36 +01:00
35d18810fc Setting up Travis CI 2017-03-02 00:25:58 +01:00
ccd40a1834 Update README.md 2017-03-01 23:29:13 +01:00
5c1f0c3541 CI: Move cached deps outside of obs-studio source dir 2017-03-01 22:46:17 +01:00
e237e52ae4 CI: Force Qt 5.7.0 like OBS Releases 2017-03-01 22:43:29 +01:00
effec90528 More fixes for appveyor 2017-03-01 22:13:34 +01:00
de4a2247c0 CI: Change MSVC version 2017-03-01 22:06:07 +01:00
b20b7cbc98 Appveyor artifact name change 2017-03-01 22:02:06 +01:00
3c347b9b77 Oops, typo 2017-03-01 21:50:20 +01:00
794c4066d8 Refactored appveyor.yml 2017-03-01 21:49:42 +01:00
8c8e3072a7 Fix appveyor artifact packaging 2017-03-01 21:45:40 +01:00
228708eec4 Added update type SceneItemVisibilityChanged 2017-03-01 21:41:42 +01:00
dcafaedaa8 What the hell happens with LibObs_DIR 2017-03-01 21:22:14 +01:00
a85297f2c8 CI: Build artifact from obs-websocket release folder 2017-03-01 21:12:37 +01:00
91f7450cbd CI Fix 2017-03-01 21:07:50 +01:00
e7b074991d Fix appveyor CI + fix build script 2017-03-01 20:59:35 +01:00
5f83ce2a28 Setting up Appveyor CI 2017-03-01 20:46:21 +01:00
44af896dee Force C++11 on Linux 2017-03-01 17:53:44 +01:00
9eaa9a98ee Use UTF-8 strings for the password 2017-03-01 15:42:38 +01:00
629880cd58 Update README.md 2017-02-28 20:40:51 +01:00
42266ed14f Fixes #17 2017-02-28 16:56:03 +01:00
9dd7a197e4 Update PROTOCOL.md 2017-02-28 16:05:41 +01:00
6f39da20a9 Removed TransitionEnd event + changed TransitionBegin triggering 2017-02-28 16:01:06 +01:00
1ebb6f9257 Transition events + scene change compatibility with OBS 18.0.0 2017-02-28 15:40:40 +01:00
e30e982ef0 Added update type TransitionDurationChanged 2017-02-27 13:24:25 +01:00
42a80c6185 Ability to get and set transition duration 2017-02-27 11:53:44 +01:00
0d495f4d65 Merge pull request #46 from mikhailswift/add_scene_name_to_other_source_commands
Add scene name to SetSceneItemPosition and SetSceneItemTransform
2017-02-27 00:08:06 +01:00
71f5e66bd1 Added back accidental nuked new line 2017-02-26 18:03:20 -05:00
a6f71b68f3 Update protocol.md 2017-02-26 18:02:46 -05:00
a527f343cd Moved some repeated code to utils, added source_name to other commands 2017-02-26 18:01:09 -05:00
f8e1c454d9 Merge pull request #45 from mikhailswift/toggle_source_on_specified_scene
Toggle source on specified scene
2017-02-26 23:24:02 +01:00
da6e55f09f Used same error message elsewhere, fixed null check 2017-02-26 17:13:50 -05:00
d277e2788a Fixed missed changes from currentScene -> scene 2017-02-26 16:40:29 -05:00
53ba747b78 Update protocol readme for added parameter 2017-02-26 16:36:38 -05:00
b9862acd1d Add scene-name optional parmeter to set source render command 2017-02-26 16:35:08 -05:00
d1c19382a1 Fixes #34 2017-02-26 19:46:09 +01:00
4141983ccd Fixed a potential bug + WIP on scene collection/profile change 2017-02-26 17:04:48 +01:00
2b8b5001e0 Merge branch 'master' of github.com:Palakis/obs-websocket 2017-02-25 17:43:52 +01:00
532126561e Rollback changes : my dev environment had the wrong Qt version (5.7.0-1 instead of 5.7.0) 2017-02-25 17:42:58 +01:00
3c026c4eef Update README.md 2017-02-25 17:33:46 +01:00
ae6c15158f Bugfix : copy issue in CMakeLists.txt 2017-02-25 16:39:55 +01:00
6aa57247f9 Better build instructions (including release packager on Windows) 2017-02-25 16:32:20 +01:00
50862ac945 Typo fix 2017-02-25 14:48:09 +01:00
01ce6faa20 Add german translation to CMakeLists.txt 2017-02-24 20:33:45 +01:00
6e571aef95 Fixed issue with exception "Must construct a QApplication before a QWidget" 2017-02-24 20:26:19 +01:00
d6091c83e2 A bit of cleaning 2017-02-24 20:09:42 +01:00
5ca55fc13e sprintf is more cross-platform 2017-02-24 18:30:04 +01:00
2b60da02e8 sprintf_s() is not cross-platform 2017-02-24 17:40:41 +01:00
f02297152c Update Utils.h 2017-02-24 17:37:44 +01:00
af7c0bbc72 Removed a log message with a password in it 2017-02-24 00:50:16 +01:00
40727d5a6c Changed config key names from snake_case to CamelCase 2017-02-23 22:40:11 +01:00
9bdd73dbc6 Merge pull request #41 from Frahmer/german-translation-2
Add german translation
2017-02-23 22:34:22 +01:00
3e1ed09f12 Update README.md 2017-02-23 22:31:56 +01:00
3682c625d7 Add german translation
Add "ServerEnable" and "ServerPort" translation.
2017-02-23 22:30:08 +01:00
70cd52ac95 Update README.md 2017-02-23 22:29:42 +01:00
3264da4b2f Update README.md 2017-02-23 22:29:24 +01:00
06c1648f55 Updated PROTOCOL.md : new table of contents 2017-02-23 22:21:06 +01:00
94c3e5d41d Update PROTOCOL.md : better lisibility 2017-02-23 22:07:14 +01:00
7ae20d8c3b Semver : version number row left shift to 4.0.0 2017-02-23 21:36:00 +01:00
0ff4411abf Bugfix : deadlock when closing the server
Caused by a non-recursive mutex
2017-02-23 21:25:26 +01:00
3d68b7c9e5 Updated locale variables names + WIP Dynamic Server Settings 2017-02-23 21:01:24 +01:00
f8b1cae0c9 Merge pull request #40 from Frahmer/german-translation
Add german translation
2017-02-23 18:28:54 +01:00
5bee0fc453 Add german translation 2017-02-22 19:30:15 +01:00
7162765824 Fix compiler warning 2017-02-20 23:03:00 +01:00
99aa6be887 Updated PROTOCOL.md and fixed some types 2017-02-17 17:01:25 +01:00
afaaff298f Fixes #35 2017-02-17 11:41:41 +01:00
68cf9af6a3 Provide a item's base size in its infos 2017-02-17 11:25:46 +01:00
8d5752d6b5 Version bump to the next release during dev 2017-02-15 20:18:06 +01:00
98dbcc4c69 Copyright update and credit where it's due 2017-02-15 18:33:32 +01:00
687d8fd120 PROTOCOL.md fix 2017-02-15 18:27:37 +01:00
114ace23f7 Added OBS version number in the response fields of GetVersion 2017-02-15 18:25:44 +01:00
3fbc221db0 Style changes 2017-02-15 17:09:34 +01:00
f2e6e137a6 Fixed #21 2017-02-15 11:35:35 +01:00
bb232f1b3e Merge pull request #37 from mikhailswift/add_mute_source_command
Add mute source commands
2017-02-13 21:17:16 +01:00
9dc153bc22 Bugfix : std's map and set can cause crashes 2017-02-13 19:03:52 +01:00
b9bbdf5978 Renamed pClient to pHandler 2017-02-13 18:25:07 +01:00
e0db0e394d Added missing return statements to ToggleMute and SetMute handlers 2017-02-13 12:09:44 -05:00
3e9001721e Fixed incorrect header level for SetMute and ToggleMute 2017-02-13 12:08:36 -05:00
bbf3b0f86f updated mistake in protocol.md 2017-02-12 23:06:00 -05:00
7f3eb9f11b update spaces to tabs in wsrequesthandler for toggle and set mute 2017-02-12 22:54:29 -05:00
c783c51915 update spaces to tabs in wsrequesthandler for set and toggle mute 2017-02-12 22:52:20 -05:00
0816d222c6 Fixed mistake in ToggleMute 2017-02-12 17:55:06 -05:00
af16c70143 Added ToggleMute and SetMute handlers, updated PROTOCOL.md 2017-02-12 18:46:02 +00:00
748b6f6e2e Protect clients object list with a QMutex 2017-02-12 19:20:32 +01:00
3bd600ed52 Preliminary work on event timestamping 2017-02-05 02:00:21 +01:00
78e6ad0f59 Fixes #27 2017-02-05 01:46:38 +01:00
2d71dc68f1 WTH is this 2017-02-05 00:36:55 +01:00
7dc2a00d47 Fixes #10 and #28 2017-02-04 21:38:19 +01:00
7ece78a05b Merge pull request #33 from haganbmj/master
[Protocol] Add OnTransitionChange/OnTransitionListChange (Fixes #19)
2017-02-04 19:06:01 +01:00
ff2bace1bf [Protocol] Add OnTransitionChange/OnTransitionListChange (Fixes #19) 2017-02-03 01:30:54 -05:00
e63ce01bdc Update README.md 2017-01-17 14:07:04 +01:00
4439ce71d0 Really fixed IP address issue 2016-12-13 00:08:32 +01:00
27ec094775 Fixed char pointer issue in log messages 2016-12-12 21:08:59 +01:00
1bed53e07b Changed websocket server name 2016-12-12 18:53:41 +01:00
8ae06c0c4f Update README.md 2016-12-10 21:51:51 +01:00
372db7865b Update README.md 2016-12-10 21:50:19 +01:00
cfeffc551e Bumped installer version 2016-12-10 21:24:22 +01:00
d519815a7e Bump to 0.3.2 2016-12-10 21:15:24 +01:00
32932eacf5 Fixes #18 2016-12-04 20:38:49 +01:00
1a057cf5a3 Merge branch 'master' of github.com:Palakis/obs-websocket 2016-12-04 20:24:20 +01:00
e3936dad9b Updated installer 2016-12-04 20:23:59 +01:00
4ec9b85506 Update README.md 2016-12-01 12:43:56 +01:00
fbae081c33 Added installer 2016-11-30 20:49:08 +01:00
110 changed files with 24078 additions and 1791 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
[*]
insert_final_newline = true
[*.{c,cpp,h,hpp}]
indent_style = tab
[*.{yml,yaml}]
indent_style = space
indent_size = 2

22
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,22 @@
## Contributing to obs-websocket
### Translating obs-websocket to your language
Localization happens on Crowdin: https://crowdin.com/project/obs-websocket
### Writing code for obs-websocket
#### Coding Guidelines
- Function and variable names: snake_case for C names, CamelCase for C++ names
- Tabs are 8 columns wide
- 80 columns max.
#### Commit Guidelines
- Commits follow the 50/72 standard:
- 50 characters max for the title
- One empty line after the title
- Description wrapped to 72 columns max per line.
- Commit titles:
- Use present tense
- Prefix the title with a "scope" name
- e.g: "CI: fix wrong behaviour when packaging for OS X"
- Typical scopes: CI, General, Request, Event, Server
- Look at existing commits for more examples

16
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,16 @@
##### Issue type
Bug report? Feature request? Other?
##### Description
*Replace this with a description of the bug encountered or feature requested.*
##### Steps to reproduce and other useful info
*If it's a bug, please describe the steps to reproduce it and PLEASE include an OBS log file. Otherwise, remove this section.*
##### Technical information
- **Operating System** :
- **OBS Studio version** :
##### Development Environment
*If you're trying to compile obs-websocket, please describe your compiler type and version (e.g: GCC 4.7, VC2013, ...), and the CMake settings used.
Remove this section if it doesn't apply to your case.*

BIN
.github/images/mediaunit_logo_black.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

7
.gitignore vendored
View File

@ -1,3 +1,10 @@
*~
.DS_Store
/build/
/build32/
/build64/
/release/
/package/
/installer/Output/
.idea
.vscode

9
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "deps/mbedtls"]
path = deps/mbedtls
url = https://github.com/ARMmbed/mbedtls
[submodule "deps/websocketpp"]
path = deps/websocketpp
url = https://github.com/zaphoyd/websocketpp.git
[submodule "deps/asio"]
path = deps/asio
url = https://github.com/chriskohlhoff/asio.git

62
BUILDING.md Normal file
View File

@ -0,0 +1,62 @@
# Compiling obs-websocket
## Prerequisites
You'll need [Qt 5.10.x](https://download.qt.io/official_releases/qt/5.10/),
[CMake](https://cmake.org/download/) and a working [OBS Studio development environment](https://obsproject.com/wiki/install-instructions) installed on your
computer.
## Windows
In cmake-gui, you'll have to set the following variables :
- **QTDIR** (path) : location of the Qt environment suited for your compiler and architecture
- **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source code of OBS Studio
- **LIBOBS_LIB** (filepath) : location of the obs.lib file
- **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file
## Linux
On Debian/Ubuntu :
```shell
sudo apt-get install libboost-all-dev
git clone --recursive https://github.com/Palakis/obs-websocket.git
cd obs-websocket
mkdir build && cd build
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true ..
make -j4
sudo make install
```
## OS X
As a prerequisite, you will need Xcode for your current OSX version, the Xcode command line tools, and [Homebrew](https://brew.sh/).
Homebrew's setup will guide you in getting your system set up, you should be good to go once Homebrew is successfully up and running.
Use of the Travis macOS CI scripts is recommended. Please note that these
scripts install new software and can change several settings on your system. An
existing obs-studio development environment is not required, as
`install-build-obs-macos.sh` will install it for you. If you already have a
working obs-studio development environment and have built obs-studio, you can
skip that script.
Of course, you're encouraged to dig through the contents of these scripts to
look for issues or specificities.
```shell
git clone --recursive https://github.com/Palakis/obs-websocket.git
cd obs-websocket
./CI/install-dependencies-macos.sh
./CI/install-build-obs-macos.sh
./CI/build-macos.sh
./CI/package-macos.sh
```
This will result in a ready-to-use `obs-websocket.pkg` installer in the `release` subfolder.
## Automated Builds
- Windows: [![Automated Build status for Windows](https://ci.appveyor.com/api/projects/status/github/Palakis/obs-websocket)](https://ci.appveyor.com/project/Palakis/obs-websocket/history)
- Linux: [![Automated Build status for Linux](https://travis-ci.org/Palakis/obs-websocket.svg?branch=master)](https://travis-ci.org/Palakis/obs-websocket)
- macOS: [![Automated Build status for macOS](https://img.shields.io/azure-devops/build/Palakis/obs-websocket/Palakis.obs-websocket.svg)](https://dev.azure.com/Palakis/obs-websocket/_build)

28
CI/build-macos.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/sh
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS build script can be run on Darwin-type OS only."
exit 1
fi
HAS_CMAKE=$(type cmake 2>/dev/null)
if [ "${HAS_CMAKE}" = "" ]; then
echo "[obs-websocket - Error] CMake not installed - please run 'install-dependencies-macos.sh' first."
exit 1
fi
#export QT_PREFIX="$(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
echo "[obs-websocket] Building 'obs-websocket' for macOS."
mkdir -p build && cd build
cmake .. \
-DQTDIR=/usr/local/opt/qt \
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
-DLIBOBS_LIB=../../obs-studio/libobs \
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=/usr \
&& make -j4

6
CI/build-ubuntu.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/sh
set -ex
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true ..
make -j4

6
CI/download-obs-deps.cmd Normal file
View File

@ -0,0 +1,6 @@
if not exist %DepsBasePath% (
curl -o %DepsBasePath%.zip -kLO https://obsproject.com/downloads/dependencies2017.zip -f --retry 5 -C -
7z x %DepsBasePath%.zip -o%DepsBasePath%
) else (
echo "OBS dependencies are already there. Download skipped."
)

31
CI/generate-docs.sh Executable file
View File

@ -0,0 +1,31 @@
#!/bin/bash
set -e
echo "-- Generating documentation."
echo "-- Node version: $(node -v)"
echo "-- NPM version: $(npm -v)"
git fetch origin
git checkout ${CHECKOUT_REF/refs\/heads\//}
cd docs
npm install
npm run build
echo "-- Documentation successfully generated."
if git diff --quiet; then
echo "-- No documentation changes to commit."
exit 0
fi
REMOTE_URL="$(git config remote.origin.url)"
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
git config user.name "Azure CI"
git config user.email "$COMMIT_AUTHOR_EMAIL"
git add ./generated
git pull
git commit -m "docs(ci): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
git push -q $GITHUB_REPO

42
CI/install-build-obs-macos.sh Executable file
View File

@ -0,0 +1,42 @@
#!/bin/sh
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS obs-studio build script can be run on Darwin-type OS only."
exit 1
fi
HAS_CMAKE=$(type cmake 2>/dev/null)
HAS_GIT=$(type git 2>/dev/null)
if [ "${HAS_CMAKE}" = "" ]; then
echo "[obs-websocket - Error] CMake not installed - please run 'install-dependencies-macos.sh' first."
exit 1
fi
if [ "${HAS_GIT}" = "" ]; then
echo "[obs-websocket - Error] Git not installed - please install Xcode developer tools or via Homebrew."
exit 1
fi
echo "[obs-websocket] Downloading and unpacking OBS dependencies"
wget --quiet --retry-connrefused --waitretry=1 https://obs-nightly.s3.amazonaws.com/osx-deps-2018-08-09.tar.gz
tar -xf ./osx-deps-2018-08-09.tar.gz -C /tmp
# Build obs-studio
cd ..
echo "[obs-websocket] Cloning obs-studio from GitHub.."
git clone https://github.com/obsproject/obs-studio
cd obs-studio
OBSLatestTag=$(git describe --tags --abbrev=0)
git checkout $OBSLatestTag
mkdir build && cd build
echo "[obs-websocket] Building obs-studio.."
cmake .. \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 \
-DDISABLE_PLUGINS=true \
-DENABLE_SCRIPTING=0 \
-DDepsPath=/tmp/obsdeps \
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \
&& make -j4

View File

@ -0,0 +1,61 @@
#!/bin/sh
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS install dependencies script can be run on Darwin-type OS only."
exit 1
fi
HAS_BREW=$(type brew 2>/dev/null)
if [ "${HAS_BREW}" = "" ]; then
echo "[obs-websocket - Error] Please install Homebrew (https://www.brew.sh/) to build obs-websocket on macOS."
exit 1
fi
# OBS Studio deps
echo "[obs-websocket] Updating Homebrew.."
brew update >/dev/null
echo "[obs-websocket] Checking installed Homebrew formulas.."
BREW_PACKAGES=$(brew list)
BREW_DEPENDENCIES="jack speexdsp ccache swig mbedtls"
for DEPENDENCY in ${BREW_DEPENDENCIES}; do
if echo "${BREW_PACKAGES}" | grep -q "^${DEPENDENCY}\$"; then
echo "[obs-websocket] Upgrading OBS-Studio dependency '${DEPENDENCY}'.."
brew upgrade ${DEPENDENCY} 2>/dev/null
else
echo "[obs-websocket] Installing OBS-Studio dependency '${DEPENDENCY}'.."
brew install ${DEPENDENCY} 2>/dev/null
fi
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
# Pin this version of QT5 to avoid `brew upgrade`
# upgrading it to incompatible version
brew pin qt
# Fetch and install Packages app
# =!= NOTICE =!=
# Installs a LaunchDaemon under /Library/LaunchDaemons/fr.whitebox.packages.build.dispatcher.plist
# =!= NOTICE =!=
HAS_PACKAGES=$(type packagesbuild 2>/dev/null)
if [ "${HAS_PACKAGES}" = "" ]; then
echo "[obs-websocket] Installing Packaging app (might require password due to 'sudo').."
curl -o './Packages.pkg' --retry-connrefused -s --retry-delay 1 'https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg'
sudo installer -pkg ./Packages.pkg -target /
fi

View File

@ -0,0 +1,19 @@
#!/bin/sh
set -ex
sudo add-apt-repository -y ppa:obsproject/obs-studio
sudo apt-get -qq update
sudo apt-get install -y \
libc-dev-bin \
libc6-dev git \
build-essential \
checkinstall \
cmake \
obs-studio \
qtbase5-dev
# Dirty hack
sudo wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/25.0.0/UI/obs-frontend-api/obs-frontend-api.h
sudo ldconfig

8
CI/install-qt-win.cmd Normal file
View File

@ -0,0 +1,8 @@
if not exist %QtBaseDir% (
curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_5.10.1.7z -f --retry 5 -z Qt_5.10.1.7z
7z x Qt_5.10.1.7z -o%QtBaseDir%
) else (
echo "Qt is already installed. Download skipped."
)
dir %QtBaseDir%

View File

@ -0,0 +1,726 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PROJECT</key>
<dict>
<key>PACKAGE_FILES</key>
<dict>
<key>DEFAULT_INSTALL_LOCATION</key>
<string>/</string>
<key>HIERARCHY</key>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../build/obs-websocket.so</string>
<key>PATH_TYPE</key>
<integer>1</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>bin</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../data</string>
<key>PATH_TYPE</key>
<integer>1</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>obs-websocket</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>plugins</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>obs-studio</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Application Support</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Automator</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Documentation</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Extensions</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Filesystems</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Frameworks</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Input Methods</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Internet Plug-Ins</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>LaunchAgents</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>LaunchDaemons</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>PreferencePanes</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Preferences</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Printers</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>PrivilegedHelperTools</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>QuickLook</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>QuickTime</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Screen Savers</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Scripts</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Services</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Widgets</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Library</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Shared</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>1023</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Users</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>/</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<key>PAYLOAD_TYPE</key>
<integer>0</integer>
<key>VERSION</key>
<integer>4</integer>
</dict>
<key>PACKAGE_SCRIPTS</key>
<dict>
<key>RESOURCES</key>
<array/>
</dict>
<key>PACKAGE_SETTINGS</key>
<dict>
<key>AUTHENTICATION</key>
<integer>1</integer>
<key>CONCLUSION_ACTION</key>
<integer>0</integer>
<key>IDENTIFIER</key>
<string>fr.palakis.obs-websocket</string>
<key>OVERWRITE_PERMISSIONS</key>
<false/>
<key>VERSION</key>
<string>4.8.0</string>
</dict>
<key>PROJECT_COMMENTS</key>
<dict>
<key>NOTES</key>
<data>
PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M
IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv
c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l
cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7
IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250
ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp
dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u
dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD
b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuMTMiPgo8c3R5bGUg
dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5
Pgo8L2JvZHk+CjwvaHRtbD4K
</data>
</dict>
<key>PROJECT_SETTINGS</key>
<dict>
<key>BUILD_PATH</key>
<dict>
<key>PATH</key>
<string>../../release</string>
<key>PATH_TYPE</key>
<integer>1</integer>
</dict>
<key>EXCLUDED_FILES</key>
<array>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.DS_Store</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove .DS_Store files</string>
<key>PROXY_TOOLTIP</key>
<string>Remove ".DS_Store" files created by the Finder.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.pbdevelopment</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove .pbdevelopment files</string>
<key>PROXY_TOOLTIP</key>
<string>Remove ".pbdevelopment" files created by ProjectBuilder or Xcode.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>CVS</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.cvsignore</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.cvspass</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.svn</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.git</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.gitignore</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove SCM metadata</string>
<key>PROXY_TOOLTIP</key>
<string>Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>classes.nib</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>designable.db</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>info.nib</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Optimize nib files</string>
<key>PROXY_TOOLTIP</key>
<string>Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>Resources Disabled</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove Resources Disabled folders</string>
<key>PROXY_TOOLTIP</key>
<string>Remove "Resources Disabled" folders.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>SEPARATOR</key>
<true/>
</dict>
</array>
<key>NAME</key>
<string>obs-websocket</string>
</dict>
</dict>
<key>TYPE</key>
<integer>1</integer>
<key>VERSION</key>
<integer>2</integer>
</dict>
</plist>

163
CI/macos/qt.rb Normal file
View File

@ -0,0 +1,163 @@
# 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
bottle do
sha256 "8b4bad005596a5f8790150fe455db998ac2406f4e0f04140d6656205d844d266" => :high_sierra
sha256 "9c488554935fb573554a4e36d36d3c81e47245b7fefc4b61edef894e67ba1740" => :sierra
sha256 "c0407afba5951df6cc4c6f6c1c315972bd41c99cecb4e029919c4c15ab6f7bdc" => :el_capitan
end
keg_only "Qt 5 has CMake issues when linked"
option "with-docs", "Build documentation"
option "with-examples", "Build examples"
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: <https://github.com/Homebrew/homebrew/issues/45284>.
depends_on :macos => :mountain_lion
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 <https://codereview.qt-project.org/#/c/140954/> 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
# 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
# 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
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 << "-nomake" << "examples" if build.without? "examples"
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
args << "-plugin-sql-psql" if build.with? "postgresql"
system "./configure", *args
system "make"
ENV.deparallelize
system "make", "install"
if build.with? "docs"
system "make", "docs"
system "make", "install_docs"
end
# Some config scripts will only find Qt in a "Frameworks" folder
frameworks.install_symlink Dir["#{lib}/*.framework"]
# 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
# 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
def caveats; <<~EOS
We agreed to the Qt opensource license for you.
If this is unacceptable you should uninstall.
EOS
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
(testpath/"main.cpp").write <<~EOS
#include <QCoreApplication>
#include <QDebug>
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

90
CI/package-macos.sh Executable file
View File

@ -0,0 +1,90 @@
#!/bin/bash
set -e
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS package script can be run on Darwin-type OS only."
exit 1
fi
echo "[obs-websocket] Preparing package build"
export QT_CELLAR_PREFIX="$(/usr/bin/find /usr/local/Cellar/qt -d 1 | sort -t '.' -k 1,1n -k 2,2n -k 3,3n | tail -n 1)"
GIT_HASH=$(git rev-parse --short HEAD)
GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}')
VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
FILENAME_UNSIGNED="obs-websocket-$VERSION-Unsigned.pkg"
FILENAME="obs-websocket-$VERSION.pkg"
echo "[obs-websocket] Modifying obs-websocket.so"
install_name_tool \
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets \
@executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui \
@executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore \
@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \
./build/obs-websocket.so
# Check if replacement worked
echo "[obs-websocket] Dependencies for obs-websocket"
otool -L ./build/obs-websocket.so
if [[ "$RELEASE_MODE" == "True" ]]; then
echo "[obs-websocket] Signing plugin binary: obs-websocket.so"
codesign --sign "$CODE_SIGNING_IDENTITY" ./build/obs-websocket.so
else
echo "[obs-websocket] Skipped plugin codesigning"
fi
echo "[obs-websocket] Actual package build"
packagesbuild ./CI/macos/obs-websocket.pkgproj
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
mv ./release/obs-websocket.pkg ./release/$FILENAME_UNSIGNED
if [[ "$RELEASE_MODE" == "True" ]]; then
echo "[obs-websocket] Signing installer: $FILENAME"
productsign \
--sign "$INSTALLER_SIGNING_IDENTITY" \
./release/$FILENAME_UNSIGNED \
./release/$FILENAME
rm ./release/$FILENAME_UNSIGNED
echo "[obs-websocket] Submitting installer $FILENAME for notarization"
zip -r ./release/$FILENAME.zip ./release/$FILENAME
UPLOAD_RESULT=$(xcrun altool \
--notarize-app \
--primary-bundle-id "fr.palakis.obs-websocket" \
--username "$AC_USERNAME" \
--password "$AC_PASSWORD" \
--asc-provider "$AC_PROVIDER_SHORTNAME" \
--file "./release/$FILENAME.zip")
rm ./release/$FILENAME.zip
REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}')
echo "Request UUID: $REQUEST_UUID"
echo "[obs-websocket] Wait for notarization result"
# Pieces of code borrowed from rednoah/notarized-app
while sleep 30 && date; do
CHECK_RESULT=$(xcrun altool \
--notarization-info "$REQUEST_UUID" \
--username "$AC_USERNAME" \
--password "$AC_PASSWORD" \
--asc-provider "$AC_PROVIDER_SHORTNAME")
echo $CHECK_RESULT
if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then
echo "[obs-websocket] Staple ticket to installer: $FILENAME"
xcrun stapler staple ./release/$FILENAME
break
fi
done
else
echo "[obs-websocket] Skipped installer codesigning and notarization"
fi

23
CI/package-ubuntu.sh Executable file
View File

@ -0,0 +1,23 @@
#!/bin/bash
set -e
export GIT_HASH=$(git rev-parse --short HEAD)
export PKG_VERSION="1-$GIT_HASH-$BRANCH_SHORT_NAME-git"
if [[ "$BRANCH_FULL_NAME" =~ "^refs/tags/" ]]; then
export PKG_VERSION="$BRANCH_SHORT_NAME"
fi
cd ./build
PAGER="cat" sudo checkinstall -y --type=debian --fstrans=no --nodoc \
--backup=no --deldoc=yes --install=no \
--pkgname=obs-websocket --pkgversion="$PKG_VERSION" \
--pkglicense="GPLv2.0" --maintainer="stephane.lepin@gmail.com" \
--pkggroup="video" \
--pkgsource="https://github.com/Palakis/obs-websocket" \
--requires="obs-studio \(\>= 25.0.7\), libqt5core5a, libqt5widgets5, qt5-image-formats-plugins" \
--pakdir="../package"
sudo chmod ao+r ../package/*

12
CI/package-windows.cmd Normal file
View File

@ -0,0 +1,12 @@
mkdir package
cd package
git rev-parse --short HEAD > package-version.txt
set /p PackageVersion=<package-version.txt
del package-version.txt
REM Package ZIP archive
7z a "obs-websocket-%PackageVersion%-Windows.zip" "..\release\*"
REM Build installer
iscc ..\installer\installer.iss /O. /F"obs-websocket-%PackageVersion%-Windows-Installer"

View File

@ -0,0 +1,37 @@
@echo off
SETLOCAL EnableDelayedExpansion
REM If obs-studio directory does not exist, clone the git repo
if not exist %OBSPath% (
echo obs-studio directory does not exist
git clone https://github.com/obsproject/obs-studio %OBSPath%
cd /D %OBSPath%\
git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\obs-studio-latest-tag.txt"
set /p OBSLatestTag=<"%OBSPath%\obs-studio-latest-tag.txt"
)
REM Prepare OBS Studio builds
echo Running CMake...
cd /D %OBSPath%
echo git checkout %OBSLatestTag%
git checkout %OBSLatestTag%
echo:
if not exist build32 mkdir build32
if not exist build64 mkdir build64
echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
cd build32
cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DDepsPath="%DepsPath32%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
cd ..\build64
cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DDepsPath="%DepsPath64%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
dir "%OBSPath%\libobs"

7
CI/prepare-windows.cmd Normal file
View File

@ -0,0 +1,7 @@
mkdir build32
mkdir build64
cd build32
cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DLibObs_DIR="%OBSPath%\build32\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
cd ..\build64
cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DLibObs_DIR="%OBSPath%\build64\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..

View File

@ -1,79 +1,210 @@
cmake_minimum_required(VERSION 3.5)
project(obs-websocket)
project(obs-websocket VERSION 4.8.0)
set(CMAKE_PREFIX_PATH "${QTDIR}")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
include(external/FindLibObs.cmake)
find_package(LibObs REQUIRED)
find_package(Qt5Core REQUIRED)
find_package(Qt5WebSockets REQUIRED)
find_package(Qt5Widgets REQUIRED)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(deps/mbedtls EXCLUDE_FROM_ALL)
set(ENABLED_PROGRAMS false)
add_definitions(-DASIO_STANDALONE)
if (WIN32 OR APPLE)
include(external/FindLibObs.cmake)
endif()
find_package(LibObs REQUIRED)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
set(obs-websocket_SOURCES
obs-websocket.cpp
WSServer.cpp
WSRequestHandler.cpp
WSEvents.cpp
Config.cpp
Utils.cpp
forms/settings-dialog.cpp)
src/obs-websocket.cpp
src/WSServer.cpp
src/ConnectionProperties.cpp
src/WSRequestHandler.cpp
src/WSRequestHandler_General.cpp
src/WSRequestHandler_Profiles.cpp
src/WSRequestHandler_Recording.cpp
src/WSRequestHandler_ReplayBuffer.cpp
src/WSRequestHandler_SceneCollections.cpp
src/WSRequestHandler_Scenes.cpp
src/WSRequestHandler_SceneItems.cpp
src/WSRequestHandler_Sources.cpp
src/WSRequestHandler_Streaming.cpp
src/WSRequestHandler_StudioMode.cpp
src/WSRequestHandler_Transitions.cpp
src/WSRequestHandler_Outputs.cpp
src/WSEvents.cpp
src/Config.cpp
src/Utils.cpp
src/rpc/RpcRequest.cpp
src/rpc/RpcResponse.cpp
src/rpc/RpcEvent.cpp
src/protocol/OBSRemoteProtocol.cpp
src/forms/settings-dialog.cpp)
set(obs-websocket_HEADERS
obs-websocket.h
WSServer.h
WSRequestHandler.h
WSEvents.h
Config.h
Utils.h
forms/settings-dialog.h)
src/obs-websocket.h
src/WSServer.h
src/ConnectionProperties.h
src/WSRequestHandler.h
src/WSEvents.h
src/Config.h
src/Utils.h
src/rpc/RpcRequest.h
src/rpc/RpcResponse.h
src/rpc/RpcEvent.h
src/protocol/OBSRemoteProtocol.h
src/forms/settings-dialog.h)
# --- Platform-independent build settings ---
add_library(obs-websocket MODULE
${obs-websocket_SOURCES}
${obs-websocket_HEADERS})
add_dependencies(obs-websocket mbedcrypto)
include_directories(
"${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api"
${Qt5Core_INCLUDES}
${Qt5WebSockets_INCLUDES}
${Qt5Widgets_INCLUDES}
${mbedcrypto_INCLUDES}
"${CMAKE_SOURCE_DIR}/deps/mbedtls/include")
"${CMAKE_SOURCE_DIR}/deps/asio/asio/include"
"${CMAKE_SOURCE_DIR}/deps/websocketpp")
target_link_libraries(obs-websocket
libobs
Qt5::Core
Qt5::WebSockets
Qt5::Widgets
mbedcrypto)
Qt5::Widgets)
# --- End of section ---
# --- Windows-specific build settings and tasks ---
if(WIN32)
if(NOT DEFINED OBS_FRONTEND_LIB)
set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library")
if(OBS_FRONTEND_LIB EQUAL "OBS_FRONTEND_LIB-NOTFOUND")
message(FATAL_ERROR "Could not find OBS Frontend API\'s library !")
message(FATAL_ERROR "Could not find OBS Frontend API's library !")
endif()
if(MSVC)
# Enable Multicore Builds and disable FH4 (to not depend on VCRUNTIME140_1.DLL)
add_definitions(/MP /d2FH4-)
endif()
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(ARCH_NAME "64bit")
set(OBS_BUILDDIR_ARCH "build64")
else()
set(ARCH_NAME "32bit")
set(OBS_BUILDDIR_ARCH "build32")
endif()
include_directories(
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/UI"
)
target_link_libraries(obs-websocket
"${OBS_FRONTEND_LIB}")
# --- Release package helper ---
# The "release" folder has a structure similar OBS' one on Windows
set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release")
add_custom_command(TARGET obs-websocket POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"${QTDIR}/bin/Qt5WebSockets.dll"
"${QTDIR}/bin/Qt5Network.dll"
"${CMAKE_BINARY_DIR}/$<CONFIG>")
endif()
# If config is Release, package release files
COMMAND if $<CONFIG:Release>==1 (
"${CMAKE_COMMAND}" -E make_directory
"${RELEASE_DIR}/data/obs-plugins/obs-websocket"
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/data"
"${RELEASE_DIR}/data/obs-plugins/obs-websocket")
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:obs-websocket>"
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
# In Release mode, copy Qt image format plugins
COMMAND if $<CONFIG:Release>==1 (
"${CMAKE_COMMAND}" -E copy
"${QTDIR}/plugins/imageformats/qjpeg.dll"
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
"${CMAKE_COMMAND}" -E copy
"${QTDIR}/plugins/imageformats/qjpeg.dll"
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
# If config is RelWithDebInfo, package release files
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
"${CMAKE_COMMAND}" -E make_directory
"${RELEASE_DIR}/data/obs-plugins/obs-websocket"
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/data"
"${RELEASE_DIR}/data/obs-plugins/obs-websocket")
COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:obs-websocket>"
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy
"$<TARGET_PDB_FILE:obs-websocket>"
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
# Copy to obs-studio dev environment for immediate testing
COMMAND if $<CONFIG:Debug>==1 (
"${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:obs-websocket>"
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
COMMAND if $<CONFIG:Debug>==1 (
"${CMAKE_COMMAND}" -E copy
"$<TARGET_PDB_FILE:obs-websocket>"
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
COMMAND if $<CONFIG:Debug>==1 (
"${CMAKE_COMMAND}" -E make_directory
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/data/obs-plugins/obs-websocket")
COMMAND if $<CONFIG:Debug>==1 (
"${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/data"
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/data/obs-plugins/obs-websocket")
)
# --- End of sub-section ---
endif()
# --- End of section ---
# --- Linux-specific build settings and tasks ---
if(UNIX AND NOT APPLE)
target_compile_options(mbedcrypto PRIVATE -fPIC)
set_target_properties(obs-websocket PROPERTIES PREFIX "")
target_link_libraries(obs-websocket
obs-frontend-api)
include(GNUInstallDirs)
set_target_properties(obs-websocket PROPERTIES PREFIX "")
target_link_libraries(obs-websocket obs-frontend-api)
file(GLOB locale_files data/locale/*.ini)
if(${USE_UBUNTU_FIX})
install(TARGETS obs-websocket
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/obs-plugins)
install(FILES data/locale/en-US.ini data/locale/fr-FR.ini
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
LIBRARY DESTINATION "/usr/lib/obs-plugins")
endif()
install(TARGETS obs-websocket
LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/obs-plugins")
install(FILES ${locale_files}
DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/obs/obs-plugins/obs-websocket/locale")
endif()
# --- End of section ---
# -- OS X specific build settings and tasks --
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default")
set(CMAKE_SKIP_RPATH TRUE)
set_target_properties(obs-websocket PROPERTIES PREFIX "")
target_link_libraries(obs-websocket "${OBS_FRONTEND_LIB}")
endif()
# -- End of section --

View File

@ -1,153 +0,0 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <mbedtls/base64.h>
#include <mbedtls/sha256.h>
#include <obs-frontend-api.h>
#include "Config.h"
#define CONFIG_SECTION_NAME "obs-websocket"
#define CONFIG_PARAM_SECRET "auth_hash"
#define CONFIG_PARAM_SALT "auth_salt"
#define CONFIG_PARAM_AUTHREQUIRED "auth_required"
Config *Config::_instance = new Config();
Config::Config() {
// Default settings
AuthRequired = false;
Secret = "";
Salt = "";
SettingsLoaded = false;
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&rng);
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
//mbedtls_ctr_drbg_set_prediction_resistance(&rng, MBEDTLS_CTR_DRBG_PR_ON);
SessionChallenge = GenerateSalt();
}
Config::~Config() {
mbedtls_ctr_drbg_free(&rng);
mbedtls_entropy_free(&entropy);
}
const char* Config::GenerateSalt() {
// Generate 32 random chars
unsigned char *random_chars = (unsigned char *)bzalloc(32);
mbedtls_ctr_drbg_random(&rng, random_chars, 32);
// Convert the 32 random chars to a base64 string
unsigned char *salt = (unsigned char*)bzalloc(64);
size_t salt_bytes;
mbedtls_base64_encode(salt, 64, &salt_bytes, random_chars, 32);
salt[salt_bytes] = 0; // Null-terminate the string
bfree(random_chars);
return (char *)salt;
}
const char* Config::GenerateSecret(const char *password, const char *salt) {
size_t passwordLength = strlen(password);
size_t saltLength = strlen(salt);
// Concatenate the password and the salt
unsigned char *passAndSalt = (unsigned char*)bzalloc(passwordLength + saltLength);
memcpy(passAndSalt, password, passwordLength);
memcpy(passAndSalt + passwordLength, salt, saltLength);
passAndSalt[passwordLength + saltLength] = 0; // Null-terminate the string
// Generate a SHA256 hash of the password
unsigned char *challengeHash = (unsigned char *)bzalloc(32);
mbedtls_sha256(passAndSalt, passwordLength + saltLength, challengeHash, 0);
// Encode SHA256 hash to Base64
unsigned char *challenge = (unsigned char*)bzalloc(64);
size_t challenge_bytes = 0;
mbedtls_base64_encode(challenge, 64, &challenge_bytes, challengeHash, 32);
challenge[64] = 0; // Null-terminate the string
bfree(passAndSalt);
bfree(challengeHash);
return (char*)challenge;
}
void Config::SetPassword(const char *password) {
const char *new_salt = GenerateSalt();
const char *new_challenge = GenerateSecret(password, new_salt);
this->Salt = new_salt;
this->Secret = new_challenge;
}
bool Config::CheckAuth(const char *response) {
size_t secretLength = strlen(this->Secret);
size_t sessChallengeLength = strlen(this->SessionChallenge);
// Concatenate auth secret with the challenge sent to the user
char *challengeAndResponse = (char*)bzalloc(secretLength + sessChallengeLength);
memcpy(challengeAndResponse, this->Secret, secretLength);
memcpy(challengeAndResponse + secretLength, this->SessionChallenge, sessChallengeLength);
challengeAndResponse[secretLength + sessChallengeLength] = 0; // Null-terminate the string
// Generate a SHA256 hash of challengeAndResponse
unsigned char *hash = (unsigned char*)bzalloc(32);
mbedtls_sha256((unsigned char*)challengeAndResponse, secretLength + sessChallengeLength, hash, 0);
// Encode the SHA256 hash to Base64
unsigned char *expected_response = (unsigned char*)bzalloc(64);
size_t base64_size = 0;
mbedtls_base64_encode(expected_response, 64, &base64_size, hash, 32);
expected_response[64] = 0; // Null-terminate the string
if (strcmp((char*)expected_response, response) == 0) {
SessionChallenge = GenerateSalt();
return true;
}
else {
return false;
}
}
void Config::OBSSaveCallback(obs_data_t *save_data, bool saving, void *private_data) {
Config *conf = static_cast<Config *>(private_data);
if (saving) {
obs_data_t *settings = obs_data_create();
obs_data_set_bool(settings, CONFIG_PARAM_AUTHREQUIRED, conf->AuthRequired);
obs_data_set_string(settings, CONFIG_PARAM_SECRET, conf->Secret);
obs_data_set_string(settings, CONFIG_PARAM_SALT, conf->Salt);
obs_data_set_obj(save_data, CONFIG_SECTION_NAME, settings);
}
else {
obs_data_t *settings = obs_data_get_obj(save_data, CONFIG_SECTION_NAME);
if (settings) {
conf->AuthRequired = obs_data_get_bool(settings, CONFIG_PARAM_AUTHREQUIRED);
conf->Secret = obs_data_get_string(settings, CONFIG_PARAM_SECRET);
conf->Salt = obs_data_get_string(settings, CONFIG_PARAM_SALT);
conf->SettingsLoaded = true;
}
}
}
Config* Config::Current() {
return _instance;
}

View File

@ -1,241 +0,0 @@
obs-websocket protocol reference
================================
## General Introduction
Messages exchanged between the client and the server are JSON objects.
The protocol in general is based on the OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
### Table of contents
- [Events](#events)
- [Requests](#requests)
- [Authentication](#authentication)
## Events
### Description
Events are sent exclusively by the server and broadcast to each connected client.
An event message will contain at least one field :
- **update-type** (string) : the type of event
Additional fields will be present in the event message depending on the event type.
### Event Types
#### "SwitchScenes"
OBS is switching to another scene.
- **scene-name** (string) : The name of the scene being switched to.
#### "ScenesChanged"
The scene list has been modified (Scenes have been added, removed, or renamed).
#### "StreamStarting"
A request to start streaming has been issued.
- **preview-only** (bool) : Always false.
#### "StreamStarted"
Streaming started successfully.
*New in OBS Studio*
#### "StreamStopping"
A request to stop streaming has been issued.
- **preview-only** (bool) : Always false.
#### "StreamStopped"
Streaming stopped successfully.
*New in OBS Studio*
#### "RecordingStarting"
A request to start recording has been issued.
*New in OBS Studio*
#### "RecordingStarted"
Recording started successfully.
*New in OBS Studio*
#### "RecordingStopping"
A request to stop streaming has been issued.
*New in OBS Studio*
#### "RecordingStopped"
Recording stopped successfully.
*New in OBS Studio*
#### "StreamStatus"
Sent every 2 seconds with the following information :
- **streaming** (bool) : Current Streaming state.
- **recording** (bool) : Current Recording state.
- **preview-only** (bool) : Always false.
- **bytes-per-sec** (integer) : Amount of data per second (in bytes) transmitted by the stream encoder.
- **kbits-per-sec** (integer) : "bytes-per-sec" converted to kilobits per second
- **strain** (double) : Percentage of dropped frames
- **total-stream-time** (integer) : Total time (in seconds) since the stream started.
- **num-total-frames** (integer) : Total number of frames transmitted since the stream started.
- **num-dropped-frames** (integer) : Number of frames dropped by the encoder since the stream started.
- **fps** (double) : Current framerate.
#### "Exiting"
*New in OBS Studio*
OBS is exiting.
## Requests
### Description
Requests are sent by the client and must have at least the following two fields :
- **"request-type"** (string) : One of the request types listed in the sub-section "[Requests Types](#request-types)".
- **"message-id"** (string) : An identifier defined by the client which will be embedded in the server response.
Depending on the request type additional fields may be required (see the "[Request Types](#request-types)" section below for more information).
Once a request is sent, the server will return a JSON response with the following fields :
- **"message-id"** (string) : The identifier specified in the request.
- **"status"** (string) : Response status, will be one of the following : "ok", "error"
- **"error"** (string) : The error message associated with an "error" status.
Depending on the request type additional fields may be present (see the "[Request Types](#request-types)" section below for more information).
### Request Types
#### "GetVersion"
Returns the latest version of the plugin and the API.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"version"** (double) : OBSRemote API version. Fixed to 1.1 for retrocompatibility.
- **"obs-websocket-version"** (string) : obs-websocket version
#### "GetAuthRequired"
Tells the client if authentication is required. If it is, authentication parameters "challenge" and "salt" are passed in the response fields (see "Authentication").
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"authRequired"** (bool)
- **"challenge"** (string)
- **"salt"** (string)
#### "Authenticate"
Try to authenticate the client on the server.
__Request fields__ :
- **"auth"** (string) : response to the auth challenge (see "Authentication").
__Response__ : OK if auth succeeded, error if invalid credentials. No additional fields.
#### "GetCurrentScene"
Get the current scene's name and items.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"name"** (string) : name of the current scene
- **"sources"** (array of objects) : ordered list of the current scene's items descriptions
Objects in the "sources" array have the following fields :
- **"name"** (string) : name of the source associated with the scene item
- **"type"** (string) : internal source type name
- **"volume"** (double) : audio volume of the source, ranging from 0.0 to 1.0
- **"x"** (double) : X coordinate of the top-left corner of the item in the scene
- **"y"** (double) : Y coordinate of the top-left corner of the item in the scene
- **"cx"** (double) : width of the item (with scale applied)
- **"cy"** (double) : height of the item (with scale applied)
#### "SetCurrentScene"
Switch to the scene specified in "scene-name".
__Request fields__ :
- **"scene-name"** (string) : name of the scene to switch to.
__Response__ : always OK if scene exists, error if it doesn't. No additional fields
#### "GetSceneList"
List OBS' scenes.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"current-scene"** (string) : name of the currently active scene
- **"scenes"** (array of objects) : ordered list of scene descriptions (see `GetCurrentScene` for reference)
#### "SetSourceRender"
Show or hide a specific source in the current scene.
__Request fields__ :
- **"source"** (string) : name of the source in the currently active scene.
- **"render"** (bool) : desired visibility
__Response__ : OK if source exists in the current scene, error otherwise.
#### "StartStopStreaming"
Toggle streaming on or off.
__Request fields__ : none
__Response__ : always OK. No additional fields.
#### "StartStopRecording"
Toggle recording on or off.
__Request fields__ : none
__Response__ : always OK. No additional fields.
*New in OBS Studio*
#### "GetStreamingStatus"
Get current streaming and recording status.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"streaming"** (bool) : streaming status (active or not)
- **"recording"** (bool) : recording status (active or not)
- **"preview-only"** (bool) : always false. Retrocompat with OBSRemote.
#### "GetTransitionList"
List all transitions available in the frontend's dropdown menu.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"current-transition"** (string) : name of the current transition
- **"transitions"** (array of objects) : list of transition descriptions
Objects in the "transitions" array have only one field :
- **"name"** (string) : name of the transition
*New in OBS Studio*
#### "GetCurrentTransition"
Get the name of the currently selected transition in the frontend's dropdown menu.
__Request fields__ : none
__Request__ : always OK, with these additional fields :
- **"name"** (string) : name of the selected transition
*New in OBS Studio*
#### "SetCurrentTransition"
__Request fields__ :
- **"transition-name"** (string) : The name of the transition.
__Response__ : OK if specified transition exists, error otherwise.
*New in OBS Studio*
### Authentication
A call to `GetAuthRequired` gives the client two elements :
- A challenge : a random string that will be used to generate the auth response
- A salt : applied to the password when generating the auth response
The client knows a password and must it to authenticate itself to the server.
However, it must keep this password secret, and it is the purpose of the authentication mecanism used by obs-websocket.
After a call to `GetAuthRequired`, the client knows a password (kept secret), a challenge and a salt (sent by the server).
To generate the answer to the auth challenge, follow this procedure :
- Concatenate the password with the salt sent by the server (in this order : password + server salt), then generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64.
- Concatenate the base64 secret with the challenge sent by the server (in this order : base64 secret + server challenge), then generate a binary SHA256 hash of the result and encode it to base64.
- Voilà, this last base64 string is the auth response. You may now use it to authenticate to the server with the `Authenticate` request.
Here's how it looks in pseudocode :
```
password = "supersecretpassword"
challenge = "ztTBnnuqrqaKDzRM3xcVdbYm"
salt = "PZVbYpvAnZut2SS6JNJytDm9"
secret_string = password + salt
secret_hash = binary_sha256(secret_string)
secret = base64_encode(secret_hash)
auth_response_string = secret + challenge
auth_response_hash = binary_sha256(auth_response_string)
auth_response = base64_encode(auth_response_hash)
```

143
README.md
View File

@ -1,45 +1,134 @@
obs-websocket
==============
Websocket API for OBS Studio.
WebSockets API for OBS Studio.
Follow the main author on Twitter for news & updates : [@LePalakis](https://twitter.com/LePalakis)
[![Build Status](https://dev.azure.com/Palakis/obs-websocket/_apis/build/status/Palakis.obs-websocket?branchName=4.x-current)](https://dev.azure.com/Palakis/obs-websocket/_build/latest?definitionId=2&branchName=4.x-current)
## Downloads
Binaries for Windows are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section. Linux and OS X releases coming soon.
Binaries for Windows, MacOS, and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
## Using obs-websocket
The Websocket API server runs on port 4444 and a settings window is available in "Websocket server settings" under OBS' "Tools" menu. The obs-websocket protocol is documented in [PROTOCOL.md](PROTOCOL.md).
Here's a list of available language APIs for obs-websocket :
- Javascript (browser & nodejs) : [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by haganbmj
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/
There's currently no frontend available for obs-websocket.
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.
### Possible use cases
- Remote control OBS from a phone or tablet on the same local network
- Change your stream overlay/graphics based on the current scene (like the AGDQ overlay does)
- Change your stream overlay/graphics based on the current scene
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
### For developers
The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog).
The protocol understood by the server is documented in [PROTOCOL.md](docs/generated/protocol.md).
Here's a list of available language APIs for obs-websocket :
- Javascript (browser & nodejs): [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by Brendan Hagan
- C#/VB.NET: [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet)
- Python 2 and 3: [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi
- Python 3.5+ with asyncio: [obs-ws-rc](https://github.com/KirillMysnik/obs-ws-rc) by Kirill Mysnik
- Python 3.6+ with asyncio: [simpleobsws](https://github.com/IRLToolkit/simpleobsws) by tt2468
- Java 8+: [obs-websocket-java](https://github.com/Twasi/websocket-obs-java) by TwasiNET
- Golang: [go-obs-websocket](https://github.com/christopher-dG/go-obs-websocket) by Chris de Graaf
- HTTP API: [obs-websocket-http](https://github.com/IRLToolkit/obs-websocket-http) by tt2468
I'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop me an email at `stephane /dot/ lepin /at/ gmail /dot/ com` !
## Compiling obs-websocket
### Prerequisites
You'll need QT 5 with QtWebSockets, CMake, and a working development environment for OBS Studio installed on your computer.
### Windows
In cmake-gui, you'll have to set the following variables :
- **QTDIR** (path) : location of the Qt environment suited for your compiler and architecture
- **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source code of OBS Studio
- **LIBOBS_LIB** (filepath) : location of the obs.lib file
- **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file
See the [build instructions](BUILDING.md).
### Linux
On Debian/Ubuntu :
```
sudo apt-get install libqt5websockets5-dev
git clone --recursive https://github.com/Palakis/obs-websocket.git
cd obs-websocket
mkdir build && cd build
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr ..
make -j4
sudo make install
## Contributing
### Branches
Development happens on `4.x-current`
### Pull Requests
Pull Requests must never be based off your fork's main branch (in this case, `4.x-current`). Start your work in a new branch
based on the main one (e.g.: `cool-new-feature`, `fix-palakis-mistakes`, ...) and open a Pull Request once you feel ready to show your work.
**If your Pull Request is not ready to merge yet, create it as a Draft Pull Request** (open the little arrow menu next to the "Create pull request" button, then select "Create draft pull request").
### Code style & formatting
Source code is indented with tabs, with spaces allowed for alignment.
Regarding protocol changes: new and updated request types / events must always come with accompanying documentation comments (see existing protocol elements for examples).
These are required to automatically generate the [protocol specification document](docs/generated/protocol.md).
Among other recommendations: favor return-early code and avoid wrapping huge portions of code in conditionals. As an example, this:
```cpp
if (success) {
return req->SendOKResponse();
} else {
return req->SendErrorResponse("something went wrong");
}
```
### OS X
*To do*
is better like this:
```cpp
if (!success) {
return req->SendErrorResponse("something went wrong");
}
return req->SendOKResponse();
```
## Translations
**Your help is welcome on translations**. Please join the localization project on Crowdin: https://crowdin.com/project/obs-websocket
## Special thanks
In (almost) order of appearance:
- [Brendan H.](https://github.com/haganbmj): Code contributions and gooder English in the Protocol specification
- [Mikhail Swift](https://github.com/mikhailswift): Code contributions
- [Tobias Frahmer](https://github.com/Frahmer): Initial German localization
- [Genture](https://github.com/Genteure): Initial Simplified Chinese and Traditional Chinese localizations
- [Larissa Gabilan](https://github.com/laris151): Initial Portuguese localization
- [Andy Asquelt](https://github.com/asquelt): Initial Polish localization
- [Marcel Haazen](https://github.com/nekocentral): Initial Dutch localization
- [Peter Antonvich](https://github.com/pantonvich): Code contributions
- [yinzara](https://github.com/yinzara): Code contributions
- [Chris Angelico](https://github.com/Rosuav): Code contributions
- [Guillaume "Elektordi" Genty](https://github.com/Elektordi): Code contributions
- [Marwin M](https://github.com/dragonbane0): Code contributions
- [Logan S.](https://github.com/lsdaniel): Code contributions
- [RainbowEK](https://github.com/RainbowEK): Code contributions
- [RytoEX](https://github.com/RytoEX): CI script and code contributions
- [Theodore Stoddard](https://github.com/TStod): Code contributions
- [Philip Loche](https://github.com/PicoCentauri): Code contributions
- [Patrick Heyer](https://github.com/PatTheMav): Code contributions and CI fixes
- [Alex Van Camp](https://github.com/Lange): Code contributions
- [Freddie Meyer](https://github.com/DungFu): Code contributions
- [Casey Muller](https://github.com/caseymrm): CI fixes
- [Chris Angelico](https://github.com/Rosuav): Documentation fixes
And also: special thanks to supporters of the project!
## Supporters
They have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them!
---
[Support Class](http://supportclass.net) designs and develops professional livestreams, with services ranging from broadcast graphics design and integration to event organization, along many other skills.
[![Support Class](.github/images/supportclass_logo_blacktext.png)](http://supportclass.net)
---
[MediaUnit](http://www.mediaunit.no) is a Norwegian media company developing products and services for the media industry, primarly focused on web and events.
[![MediaUnit](.github/images/mediaunit_logo_black.png)](http://www.mediaunit.no/)

146
Utils.cpp
View File

@ -1,146 +0,0 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include "Utils.h"
obs_data_array_t* Utils::GetSceneItems(obs_source_t *source) {
obs_data_array_t *items = obs_data_array_create();
obs_scene_t *scene = obs_scene_from_source(source);
if (scene == NULL) {
return NULL;
}
obs_scene_enum_items(scene, [](obs_scene_t *scene, obs_sceneitem_t *currentItem, void *param) {
obs_data_array_t *data = static_cast<obs_data_array_t *>(param);
obs_data_t *item_data = GetSceneItemData(currentItem);
obs_data_array_insert(data, 0, item_data);
obs_data_release(item_data);
return true;
}, items);
return items;
}
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t *item) {
if (!item) {
return NULL;
}
vec2 pos;
obs_sceneitem_get_pos(item, &pos);
vec2 bounds;
obs_sceneitem_get_bounds(item, &bounds);
obs_data_t *data = obs_data_create();
obs_data_set_string(data, "name", obs_source_get_name(obs_sceneitem_get_source(item)));
obs_data_set_string(data, "type", obs_source_get_id(obs_sceneitem_get_source(item)));
obs_data_set_double(data, "volume", obs_source_get_volume(obs_sceneitem_get_source(item)));
obs_data_set_double(data, "x", pos.x);
obs_data_set_double(data, "y", pos.y);
obs_data_set_double(data, "cx", bounds.x);
obs_data_set_double(data, "cy", bounds.y);
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
return data;
}
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* name) {
struct current_search {
const char* query;
obs_sceneitem_t* result;
};
current_search search;
search.query = name;
search.result = NULL;
obs_scene_t *scene = obs_scene_from_source(source);
if (scene == NULL) {
return NULL;
}
obs_scene_enum_items(scene, [](obs_scene_t *scene, obs_sceneitem_t *currentItem, void *param) {
current_search *search = static_cast<current_search *>(param);
const char* currentItemName = obs_source_get_name(obs_sceneitem_get_source(currentItem));
if (strcmp(currentItemName, search->query) == 0) {
search->result = currentItem;
obs_sceneitem_addref(search->result);
return false;
}
return true;
}, &search);
return search.result;
}
obs_source_t* Utils::GetTransitionFromName(const char *search_name) {
obs_source_t *found_transition = NULL;
obs_frontend_source_list transition_list = {};
obs_frontend_get_transitions(&transition_list);
for (size_t i = 0; i < transition_list.sources.num; i++) {
obs_source_t *transition = transition_list.sources.array[i];
const char *transition_name = obs_source_get_name(transition);
if (strcmp(transition_name, search_name) == 0) {
found_transition = transition;
obs_source_addref(found_transition);
break;
}
}
obs_frontend_source_list_free(&transition_list);
return found_transition;
}
obs_data_array_t* Utils::GetScenes() {
obs_frontend_source_list sceneList = {};
obs_frontend_get_scenes(&sceneList);
obs_data_array_t* scenes = obs_data_array_create();
for (size_t i = 0; i < sceneList.sources.num; i++) {
obs_source_t *scene = sceneList.sources.array[i];
obs_data_t *scene_data = GetSceneData(scene);
obs_data_array_push_back(scenes, scene_data);
obs_data_release(scene_data);
}
obs_frontend_source_list_free(&sceneList);
return scenes;
}
obs_data_t* Utils::GetSceneData(obs_source *source) {
obs_data_array_t *scene_items = GetSceneItems(source);
obs_data_t* sceneData = obs_data_create();
obs_data_set_string(sceneData, "name", obs_source_get_name(source));
obs_data_set_array(sceneData, "sources", scene_items);
obs_data_array_release(scene_items);
return sceneData;
}

View File

@ -1,231 +0,0 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include "WSEvents.h"
WSEvents::WSEvents(WSServer *server) {
_srv = server;
obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this);
QTimer *statusTimer = new QTimer();
connect(statusTimer, SIGNAL(timeout()), this, SLOT(StreamStatus()));
statusTimer->start(2000); // equal to frontend's constant BITRATE_UPDATE_SECONDS
}
WSEvents::~WSEvents() {
obs_frontend_remove_event_callback(WSEvents::FrontendEventHandler, this);
}
void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void *private_data)
{
WSEvents *owner = static_cast<WSEvents *>(private_data);
// TODO : implement SourceChanged, SourceOrderChanged and RepopulateSources
if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) {
owner->OnSceneChange();
}
else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED) {
owner->OnSceneListChange();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTING) {
owner->OnStreamStarting();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) {
owner->OnStreamStarted();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) {
owner->OnStreamStopping();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) {
owner->OnStreamStopped();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) {
owner->OnRecordingStarting();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) {
owner->OnRecordingStarted();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING) {
owner->OnRecordingStopping();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) {
owner->OnRecordingStopped();
}
else if (event == OBS_FRONTEND_EVENT_EXIT) {
obs_frontend_save();
owner->OnExit();
}
}
void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFields = NULL) {
obs_source_t *source = obs_frontend_get_current_scene();
const char *name = obs_source_get_name(source);
obs_data_t *update = obs_data_create();
obs_data_set_string(update, "update-type", updateType);
if (additionalFields != NULL) {
obs_data_apply(update, additionalFields);
}
_srv->broadcast(obs_data_get_json(update));
obs_data_release(update);
obs_source_release(source);
}
void WSEvents::OnSceneChange() {
// Implements an existing update type from bilhamil's OBS Remote
obs_source_t *transition = obs_frontend_get_current_transition();
obs_source_t *new_scene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_B);
if (!new_scene) {
obs_source_release(transition);
return;
}
obs_data_t *data = obs_data_create();
obs_data_set_string(data, "scene-name", obs_source_get_name(new_scene));
broadcastUpdate("SwitchScenes", data);
obs_data_release(data);
obs_source_release(new_scene);
obs_source_release(transition);
}
void WSEvents::OnSceneListChange() {
broadcastUpdate("ScenesChanged");
}
void WSEvents::OnStreamStarting() {
// Implements an existing update type from bilhamil's OBS Remote
obs_data_t *data = obs_data_create();
obs_data_set_bool(data, "preview-only", false);
broadcastUpdate("StreamStarting", data);
obs_data_release(data);
}
void WSEvents::OnStreamStarted() {
// New update type specific to OBS Studio
_streamStartTime = os_gettime_ns();
_lastBytesSent = 0;
broadcastUpdate("StreamStarted");
}
void WSEvents::OnStreamStopping() {
// Implements an existing update type from bilhamil's OBS Remote
obs_data_t *data = obs_data_create();
obs_data_set_bool(data, "preview-only", false);
broadcastUpdate("StreamStopping", data);
obs_data_release(data);
}
void WSEvents::OnStreamStopped() {
// New update type specific to OBS Studio
_streamStartTime = 0;
broadcastUpdate("StreamStopped");
}
void WSEvents::OnRecordingStarting() {
// New update type specific to OBS Studio
broadcastUpdate("RecordingStarting");
}
void WSEvents::OnRecordingStarted() {
// New update type specific to OBS Studio
broadcastUpdate("RecordingStarted");
}
void WSEvents::OnRecordingStopping() {
// New update type specific to OBS Studio
broadcastUpdate("RecordingStopping");
}
void WSEvents::OnRecordingStopped() {
// New update type specific to OBS Studio
broadcastUpdate("RecordingStopped");
}
void WSEvents::OnExit() {
// New update type specific to OBS Studio
broadcastUpdate("Exiting");
}
void WSEvents::StreamStatus() {
bool streaming_active = obs_frontend_streaming_active();
bool recording_active = obs_frontend_recording_active();
obs_output_t *stream_output = obs_frontend_get_streaming_output();
if (!stream_output || !streaming_active) {
if (stream_output) {
obs_output_release(stream_output);
}
return;
}
uint64_t bytes_sent = obs_output_get_total_bytes(stream_output);
uint64_t bytes_sent_time = os_gettime_ns();
if (bytes_sent < _lastBytesSent) {
bytes_sent = 0;
}
if (bytes_sent == 0) {
_lastBytesSent = 0;
}
uint64_t bytes_between = bytes_sent - _lastBytesSent;
double time_passed = double(bytes_sent_time - _lastBytesSentTime) / 1000000000.0;
uint64_t bytes_per_sec = bytes_between / time_passed;
_lastBytesSent = bytes_sent;
_lastBytesSentTime = bytes_sent_time;
uint64_t totalStreamTime = (os_gettime_ns() - _streamStartTime) / 1000000000;
int total_frames = obs_output_get_total_frames(stream_output);
int dropped_frames = obs_output_get_frames_dropped(stream_output);
float strain = 0.0;
if (total_frames > 0) {
strain = (dropped_frames / total_frames) * 100.0;
}
obs_data_t *data = obs_data_create();
obs_data_set_bool(data, "streaming", streaming_active);
obs_data_set_bool(data, "recording", recording_active);
obs_data_set_int(data, "bytes-per-sec", bytes_per_sec);
obs_data_set_int(data, "kbits-per-sec", (bytes_per_sec * 8) / 1024);
obs_data_set_int(data, "total-stream-time", totalStreamTime);
obs_data_set_int(data, "num-total-frames", total_frames);
obs_data_set_int(data, "num-dropped-frames", dropped_frames);
obs_data_set_double(data, "fps", obs_get_active_fps());
obs_data_set_double(data, "strain", strain);
obs_data_set_bool(data, "preview-only", false); // Retrocompat with OBSRemote
broadcastUpdate("StreamStatus", data);
obs_data_release(data);
obs_output_release(stream_output);
}

View File

@ -1,63 +0,0 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifndef WSEVENTS_H
#define WSEVENTS_H
#include <QtWebSockets/QWebSocket>
#include <QTimer>
#include <obs-frontend-api.h>
#include <util/platform.h>
#include "WSServer.h"
class WSEvents : public QObject
{
Q_OBJECT
public:
explicit WSEvents(WSServer *server);
~WSEvents();
static void FrontendEventHandler(enum obs_frontend_event event, void *private_data);
private Q_SLOTS:
void StreamStatus();
private:
WSServer *_srv;
uint64_t _streamStartTime;
uint64_t _lastBytesSent;
uint64_t _lastBytesSentTime;
void broadcastUpdate(const char *updateType, obs_data_t *additionalFields);
void OnSceneChange();
void OnSceneListChange();
void OnStreamStarting();
void OnStreamStarted();
void OnStreamStopping();
void OnStreamStopped();
void OnRecordingStarting();
void OnRecordingStarted();
void OnRecordingStopping();
void OnRecordingStopped();
void OnExit();
};
#endif // WSEVENTS_H

View File

@ -1,347 +0,0 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include "WSRequestHandler.h"
#include "obs-websocket.h"
#include "Config.h"
#include "Utils.h"
WSRequestHandler::WSRequestHandler(QWebSocket *client) :
_authenticated(false),
_messageId(0),
_requestType(""),
_requestData(nullptr)
{
_client = client;
messageMap["GetVersion"] = WSRequestHandler::HandleGetVersion;
messageMap["GetAuthRequired"] = WSRequestHandler::HandleGetAuthRequired;
messageMap["Authenticate"] = WSRequestHandler::HandleAuthenticate;
messageMap["SetCurrentScene"] = WSRequestHandler::HandleSetCurrentScene;
messageMap["GetCurrentScene"] = WSRequestHandler::HandleGetCurrentScene;
messageMap["GetSceneList"] = WSRequestHandler::HandleGetSceneList;
messageMap["SetSourceOrder"] = WSRequestHandler::ErrNotImplemented;
messageMap["SetSourceRender"] = WSRequestHandler::HandleSetSourceRender;
messageMap["SetSceneItemPositionAndSize"] = WSRequestHandler::ErrNotImplemented;
messageMap["GetStreamingStatus"] = WSRequestHandler::HandleGetStreamingStatus;
messageMap["StartStopStreaming"] = WSRequestHandler::HandleStartStopStreaming;
messageMap["StartStopRecording"] = WSRequestHandler::HandleStartStopRecording;
messageMap["ToggleMute"] = WSRequestHandler::ErrNotImplemented;
messageMap["GetVolumes"] = WSRequestHandler::ErrNotImplemented;
messageMap["SetVolume"] = WSRequestHandler::ErrNotImplemented;
messageMap["GetTransitionList"] = WSRequestHandler::HandleGetTransitionList;
messageMap["GetCurrentTransition"] = WSRequestHandler::HandleGetCurrentTransition;
messageMap["SetCurrentTransition"] = WSRequestHandler::HandleSetCurrentTransition;
authNotRequired.insert("GetVersion");
authNotRequired.insert("GetAuthRequired");
authNotRequired.insert("Authenticate");
blog(LOG_INFO, "[obs-websockets] new client connected from %s:%d", _client->peerAddress().toString().toLocal8Bit(), _client->peerPort());
connect(_client, &QWebSocket::textMessageReceived, this, &WSRequestHandler::processTextMessage);
connect(_client, &QWebSocket::disconnected, this, &WSRequestHandler::socketDisconnected);
}
void WSRequestHandler::processTextMessage(QString textMessage) {
QByteArray msgData = textMessage.toLocal8Bit();
const char *msg = msgData;
_requestData = obs_data_create_from_json(msg);
if (!_requestData) {
blog(LOG_ERROR, "[obs-websockets] invalid JSON payload for '%s'", msg);
SendErrorResponse("invalid JSON payload");
return;
}
_requestType = obs_data_get_string(_requestData, "request-type");
_messageId = obs_data_get_string(_requestData, "message-id");
if (Config::Current()->AuthRequired
&& !_authenticated
&& authNotRequired.find(_requestType) == authNotRequired.end())
{
SendErrorResponse("Not Authenticated");
return;
}
void (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]);
if (handlerFunc != NULL) {
handlerFunc(this);
}
else {
SendErrorResponse("invalid request type");
}
obs_data_release(_requestData);
}
void WSRequestHandler::socketDisconnected() {
blog(LOG_INFO, "[obs-websockets] client %s:%d disconnected", _client->peerAddress().toString().toStdString(), _client->peerPort());
_authenticated = false;
_client->deleteLater();
emit disconnected();
}
void WSRequestHandler::sendTextMessage(QString textMessage) {
_client->sendTextMessage(textMessage);
}
bool WSRequestHandler::isAuthenticated() {
return _authenticated;
}
WSRequestHandler::~WSRequestHandler() {
if (_requestData != NULL) {
obs_data_release(_requestData);
}
}
void WSRequestHandler::SendOKResponse(obs_data_t *additionalFields) {
obs_data_t *response = obs_data_create();
obs_data_set_string(response, "status", "ok");
obs_data_set_string(response, "message-id", _messageId);
if (additionalFields != NULL) {
obs_data_apply(response, additionalFields);
}
_client->sendTextMessage(obs_data_get_json(response));
obs_data_release(response);
}
void WSRequestHandler::SendErrorResponse(const char *errorMessage) {
obs_data_t *response = obs_data_create();
obs_data_set_string(response, "status", "error");
obs_data_set_string(response, "error", errorMessage);
obs_data_set_string(response, "message-id", _messageId);
_client->sendTextMessage(obs_data_get_json(response));
obs_data_release(response);
}
void WSRequestHandler::HandleGetVersion(WSRequestHandler *owner) {
obs_data_t *data = obs_data_create();
obs_data_set_double(data, "version", 1.1);
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
//obs_data_set_string(data, "obs-studio-version", OBS_VERSION); // Wrong
owner->SendOKResponse(data);
obs_data_release(data);
}
void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler *owner) {
bool authRequired = Config::Current()->AuthRequired;
obs_data_t *data = obs_data_create();
obs_data_set_bool(data, "authRequired", authRequired);
if (authRequired) {
obs_data_set_string(data, "challenge", Config::Current()->SessionChallenge);
obs_data_set_string(data, "salt", Config::Current()->Salt);
}
owner->SendOKResponse(data);
obs_data_release(data);
}
void WSRequestHandler::HandleAuthenticate(WSRequestHandler *owner) {
const char *auth = obs_data_get_string(owner->_requestData, "auth");
if (!auth || strlen(auth) < 1) {
owner->SendErrorResponse("auth not specified!");
return;
}
if (!(owner->_authenticated) && Config::Current()->CheckAuth(auth)) {
owner->_authenticated = true;
owner->SendOKResponse();
}
else {
owner->SendErrorResponse("Authentication Failed.");
}
}
void WSRequestHandler::HandleSetCurrentScene(WSRequestHandler *owner) {
const char *sceneName = obs_data_get_string(owner->_requestData, "scene-name");
obs_source_t *source = obs_get_source_by_name(sceneName);
if (source) {
obs_frontend_set_current_scene(source);
owner->SendOKResponse();
}
else {
owner->SendErrorResponse("requested scene does not exist");
}
obs_source_release(source);
}
// Indirectly causes memory leaks
void WSRequestHandler::HandleGetCurrentScene(WSRequestHandler *owner) {
obs_source_t *current_scene = obs_frontend_get_current_scene();
const char *name = obs_source_get_name(current_scene);
obs_data_array_t *scene_items = Utils::GetSceneItems(current_scene); // Causes memory leaks
obs_data_t *data = obs_data_create();
obs_data_set_string(data, "name", name);
obs_data_set_array(data, "sources", scene_items);
owner->SendOKResponse(data);
obs_data_release(data);
obs_data_array_release(scene_items);
obs_source_release(current_scene);
}
void WSRequestHandler::HandleGetSceneList(WSRequestHandler *owner) {
obs_source_t *current_scene = obs_frontend_get_current_scene();
obs_data_array_t *scenes = Utils::GetScenes();
obs_data_t *data = obs_data_create();
obs_data_set_string(data, "current-scene", obs_source_get_name(current_scene));
obs_data_set_array(data, "scenes", scenes);
owner->SendOKResponse(data);
obs_data_release(data);
obs_data_array_release(scenes);
obs_source_release(current_scene);
}
void WSRequestHandler::HandleSetSourceRender(WSRequestHandler *owner) {
const char *itemName = obs_data_get_string(owner->_requestData, "source");
bool isVisible = obs_data_get_bool(owner->_requestData, "render");
if (itemName == NULL) {
owner->SendErrorResponse("invalid request parameters");
return;
}
obs_source_t* currentScene = obs_frontend_get_current_scene();
obs_sceneitem_t *sceneItem = Utils::GetSceneItemFromName(currentScene, itemName);
if (sceneItem != NULL) {
obs_sceneitem_set_visible(sceneItem, isVisible);
obs_sceneitem_release(sceneItem);
owner->SendOKResponse();
}
else {
owner->SendErrorResponse("specified scene item doesn't exist");
}
obs_source_release(currentScene);
}
void WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler *owner) {
obs_data_t *data = obs_data_create();
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
obs_data_set_bool(data, "recording", obs_frontend_recording_active());
obs_data_set_bool(data, "preview-only", false);
owner->SendOKResponse(data);
obs_data_release(data);
}
void WSRequestHandler::HandleStartStopStreaming(WSRequestHandler *owner) {
if (obs_frontend_streaming_active()) {
obs_frontend_streaming_stop();
}
else {
obs_frontend_streaming_start();
}
owner->SendOKResponse();
}
void WSRequestHandler::HandleStartStopRecording(WSRequestHandler *owner) {
if (obs_frontend_recording_active()) {
obs_frontend_recording_stop();
}
else {
obs_frontend_recording_start();
}
owner->SendOKResponse();
}
void WSRequestHandler::HandleGetTransitionList(WSRequestHandler *owner) {
obs_source_t *current_transition = obs_frontend_get_current_transition();
obs_frontend_source_list transitionList = {};
obs_frontend_get_transitions(&transitionList);
obs_data_array_t* transitions = obs_data_array_create();
for (size_t i = 0; i < transitionList.sources.num; i++) {
obs_source_t* transition = transitionList.sources.array[i];
obs_data_t *obj = obs_data_create();
obs_data_set_string(obj, "name", obs_source_get_name(transition));
obs_data_array_push_back(transitions, obj);
obs_data_release(obj);
}
obs_frontend_source_list_free(&transitionList);
obs_data_t *response = obs_data_create();
obs_data_set_string(response, "current-transition", obs_source_get_name(current_transition));
obs_data_set_array(response, "transitions", transitions);
owner->SendOKResponse(response);
obs_data_release(response);
obs_data_array_release(transitions);
obs_source_release(current_transition);
}
void WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler *owner) {
obs_source_t *current_transition = obs_frontend_get_current_transition();
obs_data_t *response = obs_data_create();
obs_data_set_string(response, "name", obs_source_get_name(current_transition));
owner->SendOKResponse(response);
obs_data_release(response);
obs_source_release(current_transition);
}
void WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler *owner) {
const char *name = obs_data_get_string(owner->_requestData, "transition-name");
obs_source_t *transition = Utils::GetTransitionFromName(name);
if (transition) {
obs_frontend_set_current_transition(transition);
owner->SendOKResponse();
obs_source_release(transition);
}
else {
owner->SendErrorResponse("requested transition does not exist");
}
}
void WSRequestHandler::ErrNotImplemented(WSRequestHandler *owner) {
owner->SendErrorResponse("not implemented");
}

View File

@ -1,76 +0,0 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifndef WSREQUESTHANDLER_H
#define WSREQUESTHANDLER_H
#include <map>
#include <set>
#include <QtWebSockets/QWebSocket>
#include <obs-frontend-api.h>
class WSRequestHandler : public QObject
{
Q_OBJECT
public:
explicit WSRequestHandler(QWebSocket *client);
~WSRequestHandler();
void sendTextMessage(QString textMessage);
bool isAuthenticated();
private Q_SLOTS:
void processTextMessage(QString textMessage);
void socketDisconnected();
Q_SIGNALS:
void disconnected();
private:
QWebSocket *_client;
bool _authenticated;
const char *_messageId;
const char *_requestType;
obs_data_t *_requestData;
std::map<std::string, void(*)(WSRequestHandler*)> messageMap;
std::set<std::string> authNotRequired;
void SendOKResponse(obs_data_t *additionalFields = NULL);
void SendErrorResponse(const char *errorMessage);
static void ErrNotImplemented(WSRequestHandler *owner);
static void HandleGetVersion(WSRequestHandler *owner);
static void HandleGetAuthRequired(WSRequestHandler *owner);
static void HandleAuthenticate(WSRequestHandler *owner);
static void HandleSetCurrentScene(WSRequestHandler *owner);
static void HandleGetCurrentScene(WSRequestHandler *owner);
static void HandleGetSceneList(WSRequestHandler *owner);
static void HandleSetSourceRender(WSRequestHandler *owner);
static void HandleGetStreamingStatus(WSRequestHandler *owner);
static void HandleStartStopStreaming(WSRequestHandler *owner);
static void HandleStartStopRecording(WSRequestHandler *owner);
static void HandleGetTransitionList(WSRequestHandler *owner);
static void HandleGetCurrentTransition(WSRequestHandler *owner);
static void HandleSetCurrentTransition(WSRequestHandler *owner);
};
#endif // WSPROTOCOL_H

View File

@ -1,88 +0,0 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include "WSServer.h"
#include "WSRequestHandler.h"
#include "Config.h"
#include <QtWebSockets/QWebSocketServer>
#include <QtWebSockets/QWebSocket>
#include <QtCore/QDebug>
#include <QtCore/QThread>
#include <obs-frontend-api.h>
QT_USE_NAMESPACE
WSServer::WSServer(quint16 port, QObject *parent) :
QObject(parent),
_wsServer(Q_NULLPTR),
_clients()
{
_serverThread = new QThread();
_wsServer = new QWebSocketServer(
QStringLiteral("OBS Websocket API"),
QWebSocketServer::NonSecureMode,
this);
_wsServer->moveToThread(_serverThread);
_serverThread->start();
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
if (serverStarted) {
connect(_wsServer, &QWebSocketServer::newConnection, this, &WSServer::onNewConnection);
}
}
WSServer::~WSServer()
{
_wsServer->close();
qDeleteAll(_clients.begin(), _clients.end());
}
void WSServer::broadcast(QString message)
{
Q_FOREACH(WSRequestHandler *pClient, _clients) {
if (Config::Current()->AuthRequired == true
&& pClient->isAuthenticated() == false) {
// Skip this client if unauthenticated
continue;
}
pClient->sendTextMessage(message);
}
}
void WSServer::onNewConnection()
{
QWebSocket *pSocket = _wsServer->nextPendingConnection();
if (pSocket) {
WSRequestHandler *pHandler = new WSRequestHandler(pSocket);
connect(pHandler, &WSRequestHandler::disconnected, this, &WSServer::socketDisconnected);
_clients << pHandler;
}
}
void WSServer::socketDisconnected()
{
WSRequestHandler *pClient = qobject_cast<WSRequestHandler *>(sender());
if (pClient) {
_clients.removeAll(pClient);
pClient->deleteLater();
}
}

View File

@ -1,49 +0,0 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifndef WSSERVER_H
#define WSSERVER_H
#include <QtCore/QObject>
#include <QtCore/QList>
#include <QtCore/QByteArray>
#include "WSRequestHandler.h"
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
QT_FORWARD_DECLARE_CLASS(QWebSocket)
class WSServer : public QObject
{
Q_OBJECT
public:
explicit WSServer(quint16 port, QObject *parent = Q_NULLPTR);
virtual ~WSServer();
void broadcast(QString message);
private Q_SLOTS:
void onNewConnection();
void socketDisconnected();
private:
QWebSocketServer *_wsServer;
QList<WSRequestHandler *> _clients;
QThread *_serverThread;
};
#endif // WSSERVER_H

183
azure-pipelines.yml Normal file
View File

@ -0,0 +1,183 @@
variables:
isReleaseMode: ${{ startsWith(variables['Build.SourceBranch'], 'refs/tags/') }}
trigger:
branches:
include:
- master
tags:
include:
- '*'
jobs:
- job: 'GenerateDocs'
condition: |
or(
eq(variables['Build.SourceBranch'], 'refs/heads/4.x-current'),
eq(variables['Build.SourceBranch'], 'refs/heads/master')
)
pool:
vmImage: 'ubuntu-18.04'
steps:
- checkout: self
submodules: false
- script: ./CI/generate-docs.sh
displayName: 'Generate docs'
env:
CHECKOUT_REF: $(Build.SourceBranch)
GH_TOKEN: $(GithubToken)
- job: 'Build_Windows'
pool:
vmImage: 'windows-2019'
variables:
build_config: RelWithDebInfo
DepsBasePath: 'D:\obsdependencies'
DepsPath32: '$(DepsBasePath)\win32'
DepsPath64: '$(DepsBasePath)\win64'
QtBaseDir: 'D:\QtDep'
QTDIR32: '$(QtBaseDir)\5.10.1\msvc2017'
QTDIR64: '$(QtBaseDir)\5.10.1\msvc2017_64'
OBSPath: 'D:\obs-studio'
steps:
- checkout: self
submodules: true
- script: ./CI/install-qt-win.cmd
displayName: 'Install Qt'
env:
QtBaseDir: $(QtBaseDir)
- task: Cache@2
displayName: Restore cached OBS Studio dependencies
inputs:
key: 'obsdeps | "$(Agent.OS)"'
restoreKeys: |
obsdeps | "$(Agent.OS)"
path: $(DepsBasePath)
- script: ./CI/download-obs-deps.cmd
displayName: 'Download OBS Studio dependencies'
- task: Cache@2
displayName: Restore cached OBS Studio builds
inputs:
key: 'obs | "$(Agent.OS)"'
restoreKeys: |
obs | "$(Agent.OS)"
path: $(OBSPath)
- script: ./CI/prepare-obs-windows.cmd
displayName: 'Checkout & CMake OBS Studio'
env:
build_config: $(build_config)
DepsPath32: $(DepsPath32)
DepsPath64: $(DepsPath64)
QTDIR32: $(QTDIR32)
QTDIR64: $(QTDIR64)
OBSPath: $(OBSPath)
- task: MSBuild@1
displayName: 'Build OBS Studio 32-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '$(OBSPath)\build32\obs-studio.sln'
- task: MSBuild@1
displayName: 'Build OBS Studio 64-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '$(OBSPath)\build64\obs-studio.sln'
- script: ./CI/prepare-windows.cmd
displayName: 'CMake obs-websocket'
env:
build_config: $(build_config)
QTDIR32: $(QTDIR32)
QTDIR64: $(QTDIR64)
OBSPath: $(OBSPath)
- task: MSBuild@1
displayName: 'Build obs-websocket 32-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '.\build32\obs-websocket.sln'
- task: MSBuild@1
displayName: 'Build obs-websocket 64-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '.\build64\obs-websocket.sln'
- script: ./CI/package-windows.cmd
displayName: 'Package obs-websocket'
- task: PublishBuildArtifacts@1
displayName: 'Upload package artifacts'
inputs:
pathtoPublish: './package'
artifactName: 'windows_build'
- job: 'Build_Linux'
pool:
vmImage: 'ubuntu-18.04'
variables:
BUILD_REASON: $(Build.Reason)
BRANCH_SHORT_NAME: $(Build.SourceBranchName)
BRANCH_FULL_NAME: $(Build.SourceBranch)
steps:
- checkout: self
submodules: true
- script: ./CI/install-dependencies-ubuntu.sh
displayName: 'Install dependencies'
- script: ./CI/build-ubuntu.sh
displayName: 'Build obs-websocket'
- script: ./CI/package-ubuntu.sh
displayName: 'Package obs-websocket'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: './package'
artifactName: 'deb_build'
- job: 'Build_macOS'
pool:
vmImage: 'macos-10.14'
steps:
- checkout: self
submodules: true
- script: ./CI/install-dependencies-macos.sh
displayName: 'Install dependencies'
- script: ./CI/install-build-obs-macos.sh
displayName: 'Build OBS'
- script: ./CI/build-macos.sh
displayName: 'Build obs-websocket'
- task: InstallAppleCertificate@1
displayName: 'Install release signing certificates'
condition: eq(variables['isReleaseMode'], true)
inputs:
certSecureFile: 'Certificates.p12'
certPwd: $(secrets.macOS.certificatesImportPassword)
- script: ./CI/package-macos.sh
displayName: 'Package obs-websocket'
env:
RELEASE_MODE: $(isReleaseMode)
CODE_SIGNING_IDENTITY: $(secrets.macOS.codeSigningIdentity)
INSTALLER_SIGNING_IDENTITY: $(secrets.macOS.installerSigningIdentity)
AC_USERNAME: $(secrets.macOS.notarization.username)
AC_PASSWORD: $(secrets.macOS.notarization.password)
AC_PROVIDER_SHORTNAME: $(secrets.macOS.notarization.providerShortName)
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: './release'
artifactName: 'macos_build'

3
crowdin.yml Normal file
View File

@ -0,0 +1,3 @@
files:
- source: /data/locale/en-US.ini
translation: /data/locale/%locale%.ini

0
data/locale/ar-SA.ini Normal file
View File

16
data/locale/de-DE.ini Normal file
View File

@ -0,0 +1,16 @@
OBSWebsocket.Settings.DialogTitle="WebSockets-Servereinstellungen"
OBSWebsocket.Settings.ServerEnable="WebSockets-Server aktivieren"
OBSWebsocket.Settings.ServerPort="Server-Port"
OBSWebsocket.Settings.AuthRequired="Authentifizierung aktivieren"
OBSWebsocket.Settings.Password="Passwort"
OBSWebsocket.Settings.DebugEnable="Debug-Protokollierung aktivieren"
OBSWebsocket.Settings.AlertsEnable="Infobereichbenachrichtigungen aktivieren"
OBSWebsocket.NotifyConnect.Title="Neue Websocket-Verbindung"
OBSWebsocket.NotifyConnect.Message="Client %1 verbunden"
OBSWebsocket.NotifyDisconnect.Title="Websocket-Client getrennt"
OBSWebsocket.NotifyDisconnect.Message="Client %1 getrennt"
OBSWebsocket.Server.StartFailed.Title="Websocket-Serverfehler"
OBSWebsocket.Server.StartFailed.Message="Der WebSockets-Server konnte nicht gestartet werden, mögliche Gründe:\n - Der TCP-Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den Websocket-Servereinstellungen zu setzen oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Ein unbekannter Netzwerkfehler ist aufgetreten. Versuchen Sie es mit anderen Einstellungen, einem OBS-Neustart oder einem Systemneustart erneut."
OBSWebsocket.ProfileChanged.Started="WebSockets-Server in diesem Profil aktiviert. Server gestartet."
OBSWebsocket.ProfileChanged.Stopped="WebSockets-Server in diesem Profil deaktiviert. Server gestoppt."
OBSWebsocket.ProfileChanged.Restarted="WebSockets-Server in diesem Profil geändert. Server startet neu."

View File

@ -1,4 +1,16 @@
Menu.SettingsItem="Websocket server settings"
Settings.DialogTitle="obs-websocket"
Settings.AuthRequired="Enable authentication"
Settings.Password="Password"
OBSWebsocket.Settings.DialogTitle="WebSockets Server Settings"
OBSWebsocket.Settings.ServerEnable="Enable WebSockets server"
OBSWebsocket.Settings.ServerPort="Server Port"
OBSWebsocket.Settings.AuthRequired="Enable authentication"
OBSWebsocket.Settings.Password="Password"
OBSWebsocket.Settings.DebugEnable="Enable debug logging"
OBSWebsocket.Settings.AlertsEnable="Enable System Tray Alerts"
OBSWebsocket.NotifyConnect.Title="New WebSocket connection"
OBSWebsocket.NotifyConnect.Message="Client %1 connected"
OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected"
OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected"
OBSWebsocket.Server.StartFailed.Title="WebSockets Server failure"
OBSWebsocket.Server.StartFailed.Message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - Error message: %2"
OBSWebsocket.ProfileChanged.Started="WebSockets server enabled in this profile. Server started."
OBSWebsocket.ProfileChanged.Stopped="WebSockets server disabled in this profile. Server stopped."
OBSWebsocket.ProfileChanged.Restarted="WebSockets server port changed in this profile. Server restarted."

12
data/locale/es-ES.ini Normal file
View File

@ -0,0 +1,12 @@
OBSWebsocket.Settings.ServerEnable="Habilitar el servidor WebSockets"
OBSWebsocket.Settings.ServerPort="Puerto del Servidor"
OBSWebsocket.Settings.AuthRequired="Habilitar autenticación"
OBSWebsocket.Settings.Password="Contraseña"
OBSWebsocket.Settings.DebugEnable="Habilitar registro de depuración"
OBSWebsocket.Settings.AlertsEnable="Habilitar alertas en la bandeja de sistema"
OBSWebsocket.NotifyConnect.Title="Nueva conexión WebSocket"
OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
OBSWebsocket.NotifyDisconnect.Title="Cliente WebSocket desconectado"
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
OBSWebsocket.Server.StartFailed.Title="Falla en el servidor WebSockets"
OBSWebsocket.Server.StartFailed.Message="El servidor obs-websocket no se pudo iniciar, tal vez porque: \n - el puerto TCP %1 podría estar actualmente siendo usado este sistema, posiblemente por otra aplicación. Intente configurar un puerto TCP diferente en la configuración del servidor WebSocket, o detenga cualquier aplicación que pudiese estar utilizando este puerto \n - Un error de red desconocido ha afectado su sistema. Inténtelo de nuevo cambiando la configuración, reiniciando OBS o reiniciando su sistema."

View File

@ -1,4 +1,16 @@
Menu.SettingsItem="Paramètres du serveur Websocket"
Settings.DialogTitle="obs-websocket"
Settings.AuthRequired="Activer l'authentification"
Settings.Password="Mot de passe"
OBSWebsocket.Settings.DialogTitle="Paramètres du serveur WebSockets"
OBSWebsocket.Settings.ServerEnable="Activer le serveur WebSocket"
OBSWebsocket.Settings.ServerPort="Port du serveur"
OBSWebsocket.Settings.AuthRequired="Activer l'authentification"
OBSWebsocket.Settings.Password="Mot de passe"
OBSWebsocket.Settings.DebugEnable="Débogage dans le fichier journal"
OBSWebsocket.Settings.AlertsEnable="Notifications de connexion/déconnexion"
OBSWebsocket.NotifyConnect.Title="Nouvelle connexion WebSocket"
OBSWebsocket.NotifyConnect.Message="Le client %1 s'est connecté"
OBSWebsocket.NotifyDisconnect.Title="Déconnexion WebSocket"
OBSWebsocket.NotifyDisconnect.Message="Le client %1 s'est déconnecté"
OBSWebsocket.Server.StartFailed.Title="Impossible de démarrer le serveur WebSockets"
OBSWebsocket.Server.StartFailed.Message="Le serveur WebSockets n'a pas pu démarrer, peut-être parce que :\n - Le port TCP %1 est en cours d'utilisation sur ce système, certainement par un autre programme. Essayez un port différent dans les réglages du serveur WebSocket, ou arrêtez tout programme susceptible d'utiliser ce port.\n - Une erreur réseau inconnue est survenue. Essayez à nouveau en modifiant vos réglages, en redémarrant OBS ou en redémarrant votre ordinateur."
OBSWebsocket.ProfileChanged.Started="Serveur WebSockets actif dans ce profil."
OBSWebsocket.ProfileChanged.Stopped="Serveur WebSockets désactivé dans ce profil."
OBSWebsocket.ProfileChanged.Restarted="Le port actuel diffère du port configuré dans ce profil. Serveur WebSockets redémarré."

0
data/locale/hi-IN.ini Normal file
View File

12
data/locale/it-IT.ini Normal file
View File

@ -0,0 +1,12 @@
OBSWebsocket.Settings.ServerEnable="Abilitare il server WebSockets"
OBSWebsocket.Settings.ServerPort="Porta del server"
OBSWebsocket.Settings.AuthRequired="Abilitare l'autenticazione"
OBSWebsocket.Settings.Password="Password"
OBSWebsocket.Settings.DebugEnable="Attivare la registrazione di debug"
OBSWebsocket.Settings.AlertsEnable="Attivare gli avvisi di vassoio di sistema"
OBSWebsocket.NotifyConnect.Title="Nuova connessione WebSocket"
OBSWebsocket.NotifyConnect.Message="%1 cliente collegato"
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente disconnesso"
OBSWebsocket.NotifyDisconnect.Message="%1 cliente disconnesso"
OBSWebsocket.Server.StartFailed.Title="Errore del WebSocket Server"
OBSWebsocket.Server.StartFailed.Message="Impossibile avviare, forse perché il server di WebSockets: \n - %1 porta TCP potrebbe essere attualmente in uso altrove su questo sistema, possibilmente da un'altra applicazione. Provare a impostare una porta TCP diversa nelle impostazioni del server di WebSockets, o arrestare tutte le applicazioni che potrebbero utilizzare questa porta. \n - è verificato un errore di rete sconosciuto sul sistema. Riprova modificando le impostazioni, riavviare OBS o riavvio del sistema."

11
data/locale/ja-JP.ini Normal file
View File

@ -0,0 +1,11 @@
OBSWebsocket.Settings.ServerEnable="WebSockets サーバーを有効にする"
OBSWebsocket.Settings.ServerPort="サーバーポート"
OBSWebsocket.Settings.AuthRequired="認証を有効にする"
OBSWebsocket.Settings.Password="パスワード"
OBSWebsocket.Settings.DebugEnable="デバッグログを有効にする"
OBSWebsocket.Settings.AlertsEnable="システムトレイ通知を有効にする"
OBSWebsocket.NotifyConnect.Title="新しい WebSocket 接続"
OBSWebsocket.NotifyConnect.Message="接続されているクライアント %1"
OBSWebsocket.NotifyDisconnect.Title="WebSocket クライアントが切断されました"
OBSWebsocket.NotifyDisconnect.Message="切断されたクライアント %1"
OBSWebsocket.Server.StartFailed.Title="WebSockets サーバー障害"

0
data/locale/ko-KR.ini Normal file
View File

9
data/locale/nl-NL.ini Normal file
View File

@ -0,0 +1,9 @@
OBSWebsocket.Settings.ServerPort="Serverpoort"
OBSWebsocket.Settings.DebugEnable="Activeer debug logs"
OBSWebsocket.Settings.AlertsEnable="Systemvak waarschuwingen inschakelen"
OBSWebsocket.NotifyConnect.Title="Nieuwe WebSocket verbinding"
OBSWebsocket.NotifyConnect.Message="Client %1 verbonden"
OBSWebsocket.NotifyDisconnect.Title="WebSocket client connectie verbroken"
OBSWebsocket.NotifyDisconnect.Message="Client %1 losgekoppeld"
OBSWebsocket.Server.StartFailed.Title="Fout in WebSocket server"
OBSWebsocket.Server.StartFailed.Message="De obs-websocket server kan niet worden gestart, misschien omdat: \n - TCP-poort %1 momenteel elders wordt gebruikt op dit systeem, eventueel door een andere toepassing. Probeer een andere TCP-poort in te stellen in de WebSocket Server-instellingen of stop elke toepassing die deze poort zou kunnen gebruiken.\n Een onbekende Netwerkfout op uw systeem. Probeer het opnieuw door de instellingen te wijzigen, OBS te herstarten of uw systeem te herstarten."

10
data/locale/pl-PL.ini Normal file
View File

@ -0,0 +1,10 @@
OBSWebsocket.Settings.ServerEnable="Włącz serwer WebSockets"
OBSWebsocket.Settings.AuthRequired="Wymagaj uwierzytelniania"
OBSWebsocket.Settings.DebugEnable="Włącz rejestrowanie debugowania"
OBSWebsocket.Settings.AlertsEnable="Włącz powiadomienia w zasobniku systemowym"
OBSWebsocket.NotifyConnect.Title="Nowe połączenie WebSocket"
OBSWebsocket.NotifyConnect.Message="Klient %1 połączony"
OBSWebsocket.NotifyDisconnect.Title="Klient WebSocket odłączony"
OBSWebsocket.NotifyDisconnect.Message="Klient %1 rozłączony"
OBSWebsocket.Server.StartFailed.Title="Awaria serwera WebSockets"
OBSWebsocket.Server.StartFailed.Message="Nie udało się uruchomić serwera WebSockets, może a powodu: \n - TCP port %1 może być obecnie używany gdzie indziej w tym systemie, możliwie przez inną aplikację. Spróbuj ustawić inny port TCP w ustawieniach serwera WebSockets, lub zatrzymać dowolną aplikację, która może używać tego portu \n -nieznany błąd sieci wydarzył się w systemie. Spróbuj ponownie, zmieniając ustawienia, ponownie uruchamiając OBS lub ponownie uruchamiając system."

12
data/locale/pt-PT.ini Normal file
View File

@ -0,0 +1,12 @@
OBSWebsocket.Settings.ServerEnable="Habilitar servidor de WebSockets"
OBSWebsocket.Settings.ServerPort="Porta do Servidor"
OBSWebsocket.Settings.AuthRequired="Activar autenticação"
OBSWebsocket.Settings.Password="Palavra passe"
OBSWebsocket.Settings.DebugEnable="Habilitar registro de debug"
OBSWebsocket.Settings.AlertsEnable="Ativar Alertas da bandeja do sistema"
OBSWebsocket.NotifyConnect.Title="Nova conexão WebSocket"
OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente desconectado"
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
OBSWebsocket.Server.StartFailed.Title="Falha do servidor de WebSocket"
OBSWebsocket.Server.StartFailed.Message="O servidor de WebSockets falhou ao iniciar, talvez porque: \n - TCP port %1 pode estar atualmente em uso em outro lugar sobre este sistema, possivelmente por outro aplicativo. Tente definir uma porta TCP diferente nas configurações do servidor de WebSockets, ou parar qualquer aplicativo que poderia estar usando este porto \n - um erro de rede desconhecido aconteceu no seu sistema. Tente novamente alterar configurações, reiniciando OBS ou reiniciando o sistema."

13
data/locale/ru-RU.ini Normal file
View File

@ -0,0 +1,13 @@
OBSWebsocket.Settings.DialogTitle="Настройки сервера WebSockets"
OBSWebsocket.Settings.ServerEnable="Включить сервер WebSockets"
OBSWebsocket.Settings.ServerPort="Порт сервера"
OBSWebsocket.Settings.AuthRequired="Включить авторизацию"
OBSWebsocket.Settings.Password="Пароль"
OBSWebsocket.Settings.DebugEnable="Включить ведение журнала отладки"
OBSWebsocket.Settings.AlertsEnable="Включить оповещения в системном трее"
OBSWebsocket.NotifyConnect.Title="Новое соединение WebSocket"
OBSWebsocket.NotifyConnect.Message="Клиент %1 подключен"
OBSWebsocket.NotifyDisconnect.Title="Клиент WebSocket отключён"
OBSWebsocket.NotifyDisconnect.Message="Клиент %1 отключен"
OBSWebsocket.Server.StartFailed.Title="Сбой сервера WebSockets"
OBSWebsocket.Server.StartFailed.Message="Ошибка запуска сервера WebSockets. Вероятные причины:\n - Возможно, TCP-порт %1 занят другим приложением в системе. Попробуйте задать другой TCP-порт в настройках сервера WebSockets или закройте приложение, которое может использовать данный порт.\n - Произошла неизвестная сетевая ошибка в системе. Попробуйте снова после изменения настроек, перезапуска OBS или системы."

12
data/locale/zh-CN.ini Normal file
View File

@ -0,0 +1,12 @@
OBSWebsocket.Settings.ServerEnable="启用 WebSockets 服务器"
OBSWebsocket.Settings.ServerPort="服务器端口"
OBSWebsocket.Settings.AuthRequired="启用身份验证"
OBSWebsocket.Settings.Password="密码"
OBSWebsocket.Settings.DebugEnable="启用调试日志"
OBSWebsocket.Settings.AlertsEnable="启用系统托盘通知"
OBSWebsocket.NotifyConnect.Title="新 WebSocket 连接"
OBSWebsocket.NotifyConnect.Message="客户端 %1 已连接"
OBSWebsocket.NotifyDisconnect.Title="WebSocket 客户端已断开"
OBSWebsocket.NotifyDisconnect.Message="客户端 %1 已断开连接"
OBSWebsocket.Server.StartFailed.Title="WebSockets 服务器错误"
OBSWebsocket.Server.StartFailed.Message="WebSockets 服务器启动失败,可能是因为:\n - TCP 端口 %1 可能被本机的另一个应用程序占用。尝试在 WebSockets 服务器设置中设置不同的 TCP 端口,或关闭任何可能使用此端口的应用程序。\n - 在您的系统上发生了未知的网络错误。请尝试更改设置、重新启动 OBS 或重新启动系统。"

9
data/locale/zh-TW.ini Normal file
View File

@ -0,0 +1,9 @@
OBSWebsocket.Settings.ServerPort="伺服器連接埠"
OBSWebsocket.Settings.DebugEnable="啟用除錯日誌"
OBSWebsocket.Settings.AlertsEnable="啟用系統列通知"
OBSWebsocket.NotifyConnect.Title="新的 WebSocket 連線"
OBSWebsocket.NotifyConnect.Message="客戶端 %1 已連線"
OBSWebsocket.NotifyDisconnect.Title="WebSocket 客戶端已離線"
OBSWebsocket.NotifyDisconnect.Message="客戶端 %1 已離線"
OBSWebsocket.Server.StartFailed.Title="WebSocket 伺服器錯誤"
OBSWebsocket.Server.StartFailed.Message="WebSockets 伺服器啟動失敗,可能的原因有:\n - TCP 連接埠 %1 被系統的其他程式所使用,試著為 WebSockets 伺服器指定不同的 TCP 連接埠,或是關閉任何可能使用此連接埠的程式。\n - 發生的未知的網路錯誤,試著更改設定、重新啟動 OBS 或是重新啟動您的系統。"

1
deps/asio vendored Submodule

Submodule deps/asio added at b73dc1d2c0

1
deps/mbedtls vendored

Submodule deps/mbedtls deleted from 1a6a15c795

1
deps/websocketpp vendored Submodule

Submodule deps/websocketpp added at c6d7e295bf

11
docs/.editorconfig Normal file
View File

@ -0,0 +1,11 @@
[*]
end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
[*.md, *.mustache]
trim_trailing_whitespace = false
insert_final_newline = false

4
docs/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
logs
*.log
npm-debug.log*

1
docs/.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

21
docs/README.md Normal file
View File

@ -0,0 +1,21 @@
## Installation
Install node and update npm if necessary.
```sh
cd obs-websocket/docs
npm install
```
## Build
```sh
# Just extract the comments.
npm run comments
# Just render the markdown.
npm run docs
# Do both comments and markdown.
npm run build
```

104
docs/comments.js Normal file
View File

@ -0,0 +1,104 @@
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const parseComments = require('parse-comments');
const config = require('./config.json');
/**
* Read each file and call `parse-comments` on it.
*
* @param {String|Array} `files` List of file paths to read from.
* @return {Object|Array} Array of `parse-comments` objects.
*/
const parseFiles = files => {
let response = [];
files.forEach(file => {
const f = fs.readFileSync(file, 'utf8').toString();
response = response.concat(parseComments(f));
});
return response;
};
/**
* Filters/sorts the results from `parse-comments`.
* @param {Object|Array} `comments` Array of `parse-comments` objects.
* @return {Object} Filtered comments sorted by `@api` and `@category`.
*/
const processComments = comments => {
let sorted = {};
let errors = [];
comments.forEach(comment => {
if (comment.typedef) {
comment.comment = undefined;
comment.context = undefined;
sorted['typedefs'] = sorted['typedefs'] || [];
sorted['typedefs'].push(comment);
return;
}
if (typeof comment.api === 'undefined') return;
let validationFailures = validateComment(comment);
if (validationFailures) {
errors.push(validationFailures);
}
// Store the object based on its api (ie. requests, events) and category (ie. general, scenes, etc).
comment.category = comment.category || 'miscellaneous';
// Remove some unnecessary properties to avoid result differences in travis.
comment.comment = undefined;
comment.context = undefined;
// Create an entry in sorted for the api/category if one does not exist.
sorted[comment.api] = sorted[comment.api] || {};
sorted[comment.api][comment.category] = sorted[comment.api][comment.category] || [];
// Store the comment in the appropriate api/category.
sorted[comment.api][comment.category].push(comment);
});
if (errors.length) {
throw JSON.stringify(errors, null, 2);
}
return sorted;
};
// Rudimentary validation of documentation content, returns an error object or undefined.
const validateComment = comment => {
let errors = [];
[].concat(comment.params).concat(comment.returns).filter(Boolean).forEach(param => {
if (typeof param.name !== 'string' || param.name === '') {
errors.push({
description: `Invalid param or return value name`,
param: param
});
}
if (typeof param.type !== 'string' || param.type === '') {
errors.push({
description: `Invalid param or return value type`,
param: param
});
}
});
if (errors.length) {
return {
errors: errors,
fullContext: Object.assign({}, comment)
};
}
};
const files = glob.sync(config.srcGlob);
const comments = processComments(parseFiles(files));
if (!fs.existsSync(config.outDirectory)){
fs.mkdirSync(config.outDirectory);
}
fs.writeFileSync(path.join(config.outDirectory, 'comments.json'), JSON.stringify(comments, null, 2));

5
docs/config.json Normal file
View File

@ -0,0 +1,5 @@
{
"srcGlob": "./../src/**/*.@(cpp|h)",
"srcTemplate": "./protocol.hbs",
"outDirectory": "./generated"
}

37
docs/docs.js Normal file
View File

@ -0,0 +1,37 @@
const fs = require('fs');
const path = require('path');
const toc = require('markdown-toc');
const handlebars = require('handlebars');
const config = require('./config.json');
const helpers = require('handlebars-helpers')({
handlebars: handlebars
});
// Allows pipe characters to be used within markdown tables.
handlebars.registerHelper('depipe', (text) => {
return typeof text === 'string' ? text.replace('|', '\\|') : text;
});
const insertHeader = (text) => {
return '<!-- This file was generated based on handlebars templates. Do not edit directly! -->\n\n' + text;
};
/**
* Writes `protocol.md` using `protocol.mustache`.
*
* @param {Object} `data` Data to assign to the mustache template.
*/
const generateProtocol = (templatePath, data) => {
const template = fs.readFileSync(templatePath).toString();
const generated = handlebars.compile(template)(data);
return insertHeader(toc.insert(generated));
};
if (!fs.existsSync(config.outDirectory)){
fs.mkdirSync(config.outDirectory);
}
const comments = fs.readFileSync(path.join(config.outDirectory, 'comments.json'), 'utf8');
const markdown = generateProtocol(config.srcTemplate, JSON.parse(comments));
fs.writeFileSync(path.join(config.outDirectory, 'protocol.md'), markdown);

9057
docs/generated/comments.json Normal file

File diff suppressed because it is too large Load Diff

3539
docs/generated/protocol.md Normal file

File diff suppressed because it is too large Load Diff

21
docs/package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "obs-websocket-docs",
"version": "1.0.0",
"description": "",
"main": "docs.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"docs": "node ./docs.js",
"comments": "node ./comments.js",
"build": "npm run comments && npm run docs"
},
"author": "",
"license": "ISC",
"dependencies": {
"glob": "^7.1.2",
"handlebars": "^4.0.10",
"handlebars-helpers": "^0.9.6",
"markdown-toc": "^1.1.0",
"parse-comments": "^0.4.3"
}
}

View File

@ -0,0 +1,11 @@
# Events
Events are broadcast by the server to each connected client when a recognized action occurs within OBS.
An event message will contain at least the following base fields:
- `update-type` _String_: the type of event.
- `stream-timecode` _String (optional)_: time elapsed between now and stream start (only present if OBS Studio is streaming).
- `rec-timecode` _String (optional)_: time elapsed between now and recording start (only present if OBS Studio is recording).
Timecodes are sent using the format: `HH:MM:SS.mmm`
Additional fields may be present in the event message depending on the event type.

View File

@ -0,0 +1,34 @@
# obs-websocket 4.8.0 protocol reference
# General Introduction
Messages are exchanged between the client and the server as JSON objects.
This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
# Authentication
`obs-websocket` uses SHA256 to transmit credentials.
A request for [`GetAuthRequired`](#getauthrequired) returns two elements:
- A `challenge`: a random string that will be used to generate the auth response.
- A `salt`: applied to the password when generating the auth response.
To generate the answer to the auth challenge, follow this procedure:
- Concatenate the user declared password with the `salt` sent by the server (in this order: `password + server salt`).
- Generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64, known as a `base64 secret`.
- Concatenate the base64 secret with the `challenge` sent by the server (in this order: `base64 secret + server challenge`).
- Generate a binary SHA256 hash of the result and encode it to base64.
- Voilà, this last base64 string is the `auth response`. You may now use it to authenticate to the server with the [`Authenticate`](#authenticate) request.
Pseudo Code Example:
```
password = "supersecretpassword"
challenge = "ztTBnnuqrqaKDzRM3xcVdbYm"
salt = "PZVbYpvAnZut2SS6JNJytDm9"
secret_string = password + salt
secret_hash = binary_sha256(secret_string)
secret = base64_encode(secret_hash)
auth_response_string = secret + challenge
auth_response_hash = binary_sha256(auth_response_string)
auth_response = base64_encode(auth_response_hash)
```

View File

@ -0,0 +1,11 @@
# Requests
Requests are sent by the client and require at least the following two fields:
- `request-type` _String_: String name of the request type.
- `message-id` _String_: Client defined identifier for the message, will be echoed in the response.
Once a request is sent, the server will return a JSON response with at least the following fields:
- `message-id` _String_: The client defined identifier specified in the request.
- `status` _String_: Response status, will be one of the following: `ok`, `error`
- `error` _String_: An error message accompanying an `error` status.
Additional information may be required/returned depending on the request type. See below for more information.

View File

@ -0,0 +1,2 @@
# Typedefs
These are complex types, such as `Source` and `Scene`, which are used as arguments or return values in multiple requests and/or events.

112
docs/protocol.hbs Normal file
View File

@ -0,0 +1,112 @@
{{#read "partials/introduction.md"}}{{/read}}
# Table of Contents
<!-- toc -->
{{#read "partials/typedefsHeader.md"}}{{/read}}
{{#each typedefs}}
## {{typedefs.0.name}}
| Name | Type | Description |
| ---- | :---: | ------------|
{{#each properties}}
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
{{/each}}
{{/each}}
{{#read "partials/eventsHeader.md"}}{{/read}}
{{#each events}}
## {{capitalizeAll @key}}
{{#each this}}
### {{name}}
{{#if deprecated}}
- **⚠️ Deprecated. {{deprecated}} ⚠️**
{{/if}}
{{#eq since "unreleased"}}
- Unreleased
{{else}}
- Added in v{{since}}
{{/eq}}
{{{description}}}
**Response Items:**
{{#if returns.length}}
| Name | Type | Description |
| ---- | :---: | ------------|
{{#each returns}}
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
{{/each}}
{{else}}
_No additional response items._
{{/if}}
---
{{/each}}
{{/each}}
{{#read "partials/requestsHeader.md"}}{{/read}}
{{#each requests}}
## {{capitalizeAll @key}}
{{#each this}}
### {{name}}
{{#if deprecated}}
- **⚠️ Deprecated. {{deprecated}} ⚠️**
{{/if}}
{{#eq since "unreleased"}}
- Unreleased
{{else}}
- Added in v{{since}}
{{/eq}}
{{{description}}}
**Request Fields:**
{{#if params.length}}
| Name | Type | Description |
| ---- | :---: | ------------|
{{#each params}}
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
{{/each}}
{{else}}
_No specified parameters._
{{/if}}
**Response Items:**
{{#if returns.length}}
| Name | Type | Description |
| ---- | :---: | ------------|
{{#each returns}}
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
{{/each}}
{{else}}
_No additional response items._
{{/if}}
---
{{/each}}
{{/each}}

View File

@ -1,88 +0,0 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <obs-frontend-api.h>
#include "settings-dialog.h"
#include "ui_settings-dialog.h"
#include "Config.h"
#define CHANGE_ME "changeme"
SettingsDialog::SettingsDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::SettingsDialog)
{
ui->setupUi(this);
connect(ui->authRequired, &QCheckBox::stateChanged, this, &SettingsDialog::AuthCheckboxChanged);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::FormAccepted);
AuthCheckboxChanged();
}
void SettingsDialog::showEvent(QShowEvent *event) {
ui->authRequired->setChecked(Config::Current()->AuthRequired);
ui->password->setText(CHANGE_ME);
}
void SettingsDialog::ToggleShowHide() {
if (!isVisible()) {
setVisible(true);
}
else {
setVisible(false);
}
}
void SettingsDialog::AuthCheckboxChanged() {
if (ui->authRequired->isChecked()) {
ui->password->setEnabled(true);
}
else {
ui->password->setEnabled(false);
}
}
void SettingsDialog::FormAccepted() {
if (ui->authRequired->isChecked()) {
if (ui->password->text() != CHANGE_ME) {
QByteArray pwd = ui->password->text().toLocal8Bit();
const char *new_password = pwd;
blog(LOG_INFO, "new password : %s", new_password);
Config::Current()->SetPassword(new_password);
}
if (strcmp(Config::Current()->Secret, "") != 0) {
Config::Current()->AuthRequired = true;
}
else {
Config::Current()->AuthRequired = false;
}
}
else {
Config::Current()->AuthRequired = false;
}
obs_frontend_save();
}
SettingsDialog::~SettingsDialog()
{
delete ui;
}

View File

@ -1,101 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDialog</class>
<widget class="QDialog" name="SettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>354</width>
<height>110</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Settings.DialogTitle</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="3" column="0">
<widget class="QLabel" name="lbl_password">
<property name="text">
<string>Settings.Password</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="password">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="authRequired">
<property name="text">
<string>Settings.AuthRequired</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

68
installer/installer.iss Normal file
View File

@ -0,0 +1,68 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "obs-websocket"
#define MyAppVersion "4.8.0"
#define MyAppPublisher "Stephane Lepin"
#define MyAppURL "http://github.com/Palakis/obs-websocket"
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{117EE44F-48E1-49E5-A381-CC8D9195CF35}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={code:GetDirName}
DefaultGroupName={#MyAppName}
OutputBaseFilename=obs-websocket-Windows-Installer
Compression=lzma
SolidCompression=yes
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\LICENSE"; Flags: dontcopy
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
[Code]
procedure InitializeWizard();
var
GPLText: AnsiString;
Page: TOutputMsgMemoWizardPage;
begin
ExtractTemporaryFile('LICENSE');
LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText);
Page := CreateOutputMsgMemoPage(wpWelcome,
'License Information', 'Please review the license terms before installing obs-websocket',
'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.',
String(GPLText)
);
end;
// credit where it's due :
// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45
function GetDirName(Value: string): string;
var
InstallPath: string;
begin
// initialize default path, which will be returned when the following registry
// key queries fail due to missing keys or for some different reason
Result := '{pf}\obs-studio';
// query the first registry value; if this succeeds, return the obtained value
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
Result := InstallPath
end;

View File

@ -1,63 +0,0 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <obs-module.h>
#include <obs-frontend-api.h>
#include <QAction>
#include "obs-websocket.h"
#include "WSEvents.h"
#include "WSServer.h"
#include "Config.h"
#include "forms/settings-dialog.h"
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
WSEvents *eventHandler;
WSServer *server;
SettingsDialog *settings_dialog;
bool obs_module_load(void)
{
blog(LOG_INFO, "[obs-websockets] you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
server = new WSServer(4444);
eventHandler = new WSEvents(server);
obs_frontend_add_save_callback(Config::OBSSaveCallback, Config::Current());
QAction *menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(obs_module_text("Menu.SettingsItem"));
obs_frontend_push_ui_translation(obs_module_get_string);
settings_dialog = new SettingsDialog();
obs_frontend_pop_ui_translation();
auto menu_cb = [] {
settings_dialog->ToggleShowHide();
};
menu_action->connect(menu_action, &QAction::triggered, menu_cb);
return true;
}
void obs_module_unload()
{
blog(LOG_INFO, "[obs-websockets] goodbye !");
}

286
src/Config.cpp Normal file
View File

@ -0,0 +1,286 @@
/*
obs-websocket
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <obs-frontend-api.h>
#include <QtCore/QCryptographicHash>
#include <QtCore/QTime>
#include <QtWidgets/QSystemTrayIcon>
#define SECTION_NAME "WebsocketAPI"
#define PARAM_ENABLE "ServerEnabled"
#define PARAM_PORT "ServerPort"
#define PARAM_DEBUG "DebugEnabled"
#define PARAM_ALERT "AlertsEnabled"
#define PARAM_AUTHREQUIRED "AuthRequired"
#define PARAM_SECRET "AuthSecret"
#define PARAM_SALT "AuthSalt"
#include "Utils.h"
#include "WSServer.h"
#include "Config.h"
#define QT_TO_UTF8(str) str.toUtf8().constData()
Config::Config() :
ServerEnabled(true),
ServerPort(4444),
DebugEnabled(false),
AlertsEnabled(true),
AuthRequired(false),
Secret(""),
Salt(""),
SettingsLoaded(false)
{
qsrand(QTime::currentTime().msec());
SetDefaults();
SessionChallenge = GenerateSalt();
obs_frontend_add_event_callback(OnFrontendEvent, this);
}
Config::~Config()
{
obs_frontend_remove_event_callback(OnFrontendEvent, this);
}
void Config::Load()
{
config_t* obsConfig = GetConfigStore();
ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE);
ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT);
DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG);
AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT);
AuthRequired = config_get_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED);
Secret = config_get_string(obsConfig, SECTION_NAME, PARAM_SECRET);
Salt = config_get_string(obsConfig, SECTION_NAME, PARAM_SALT);
}
void Config::Save()
{
config_t* obsConfig = GetConfigStore();
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_DEBUG, DebugEnabled);
config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled);
config_set_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_string(obsConfig, SECTION_NAME, PARAM_SECRET,
QT_TO_UTF8(Secret));
config_set_string(obsConfig, SECTION_NAME, PARAM_SALT,
QT_TO_UTF8(Salt));
config_save(obsConfig);
}
void Config::SetDefaults()
{
// OBS Config defaults
config_t* obsConfig = GetConfigStore();
if (obsConfig) {
config_set_default_bool(obsConfig,
SECTION_NAME, PARAM_ENABLE, ServerEnabled);
config_set_default_uint(obsConfig,
SECTION_NAME, PARAM_PORT, ServerPort);
config_set_default_bool(obsConfig,
SECTION_NAME, PARAM_DEBUG, DebugEnabled);
config_set_default_bool(obsConfig,
SECTION_NAME, PARAM_ALERT, AlertsEnabled);
config_set_default_bool(obsConfig,
SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_default_string(obsConfig,
SECTION_NAME, PARAM_SECRET, QT_TO_UTF8(Secret));
config_set_default_string(obsConfig,
SECTION_NAME, PARAM_SALT, QT_TO_UTF8(Salt));
}
}
config_t* Config::GetConfigStore()
{
return obs_frontend_get_profile_config();
}
QString Config::GenerateSalt()
{
// Generate 32 random chars
const size_t randomCount = 32;
QByteArray randomChars;
for (size_t i = 0; i < randomCount; i++) {
randomChars.append((char)qrand());
}
// Convert the 32 random chars to a base64 string
QString salt = randomChars.toBase64();
return salt;
}
QString Config::GenerateSecret(QString password, QString salt)
{
// Concatenate the password and the salt
QString passAndSalt = "";
passAndSalt += password;
passAndSalt += salt;
// Generate a SHA256 hash of the password and salt
auto challengeHash = QCryptographicHash::hash(
passAndSalt.toUtf8(),
QCryptographicHash::Algorithm::Sha256
);
// Encode SHA256 hash to Base64
QString challenge = challengeHash.toBase64();
return challenge;
}
void Config::SetPassword(QString password)
{
QString newSalt = GenerateSalt();
QString newChallenge = GenerateSecret(password, newSalt);
this->Salt = newSalt;
this->Secret = newChallenge;
}
bool Config::CheckAuth(QString response)
{
// Concatenate auth secret with the challenge sent to the user
QString challengeAndResponse = "";
challengeAndResponse += Secret;
challengeAndResponse += SessionChallenge;
// Generate a SHA256 hash of challengeAndResponse
auto hash = QCryptographicHash::hash(
challengeAndResponse.toUtf8(),
QCryptographicHash::Algorithm::Sha256
);
// Encode the SHA256 hash to Base64
QString expectedResponse = hash.toBase64();
bool authSuccess = false;
if (response == expectedResponse) {
SessionChallenge = GenerateSalt();
authSuccess = true;
}
return authSuccess;
}
void Config::OnFrontendEvent(enum obs_frontend_event event, void* param)
{
auto config = reinterpret_cast<Config*>(param);
if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED) {
obs_frontend_push_ui_translation(obs_module_get_string);
QString startMessage = QObject::tr("OBSWebsocket.ProfileChanged.Started");
QString stopMessage = QObject::tr("OBSWebsocket.ProfileChanged.Stopped");
QString restartMessage = QObject::tr("OBSWebsocket.ProfileChanged.Restarted");
obs_frontend_pop_ui_translation();
bool previousEnabled = config->ServerEnabled;
uint64_t previousPort = config->ServerPort;
config->SetDefaults();
config->Load();
if (config->ServerEnabled != previousEnabled || config->ServerPort != previousPort) {
auto server = GetServer();
server->stop();
if (config->ServerEnabled) {
server->start(config->ServerPort);
if (previousEnabled != config->ServerEnabled) {
Utils::SysTrayNotify(startMessage, QSystemTrayIcon::MessageIcon::Information);
} else {
Utils::SysTrayNotify(restartMessage, QSystemTrayIcon::MessageIcon::Information);
}
} else {
Utils::SysTrayNotify(stopMessage, QSystemTrayIcon::MessageIcon::Information);
}
}
}
}
void Config::MigrateFromGlobalSettings()
{
config_t* source = obs_frontend_get_global_config();
config_t* destination = obs_frontend_get_profile_config();
if(config_has_user_value(source, SECTION_NAME, PARAM_ENABLE)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_ENABLE);
config_set_bool(destination, SECTION_NAME, PARAM_ENABLE, value);
config_remove_value(source, SECTION_NAME, PARAM_ENABLE);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_PORT)) {
uint64_t value = config_get_uint(source, SECTION_NAME, PARAM_PORT);
config_set_uint(destination, SECTION_NAME, PARAM_PORT, value);
config_remove_value(source, SECTION_NAME, PARAM_PORT);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_DEBUG)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_DEBUG);
config_set_bool(destination, SECTION_NAME, PARAM_DEBUG, value);
config_remove_value(source, SECTION_NAME, PARAM_DEBUG);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_ALERT)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_ALERT);
config_set_bool(destination, SECTION_NAME, PARAM_ALERT, value);
config_remove_value(source, SECTION_NAME, PARAM_ALERT);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_AUTHREQUIRED)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_AUTHREQUIRED);
config_set_bool(destination, SECTION_NAME, PARAM_AUTHREQUIRED, value);
config_remove_value(source, SECTION_NAME, PARAM_AUTHREQUIRED);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_SECRET)) {
const char* value = config_get_string(source, SECTION_NAME, PARAM_SECRET);
config_set_string(destination, SECTION_NAME, PARAM_SECRET, value);
config_remove_value(source, SECTION_NAME, PARAM_SECRET);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_SALT)) {
const char* value = config_get_string(source, SECTION_NAME, PARAM_SALT);
config_set_string(destination, SECTION_NAME, PARAM_SALT, value);
config_remove_value(source, SECTION_NAME, PARAM_SALT);
}
config_save(destination);
}

View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -16,35 +16,42 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifndef CONFIG_H
#define CONFIG_H
#pragma once
#include <obs-module.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <obs-frontend-api.h>
#include <util/config-file.h>
#include <QtCore/QString>
#include <QtCore/QSharedPointer>
class Config {
public:
Config();
~Config();
void SetPassword(const char *password);
bool CheckAuth(const char *userChallenge);
const char* GenerateSalt();
static const char* GenerateSecret(const char *password, const char *salt);
static void OBSSaveCallback(obs_data_t *save_data, bool saving, void *);
void Load();
void Save();
void SetDefaults();
config_t* GetConfigStore();
void MigrateFromGlobalSettings();
void SetPassword(QString password);
bool CheckAuth(QString userChallenge);
QString GenerateSalt();
static QString GenerateSecret(
QString password, QString salt);
bool ServerEnabled;
uint64_t ServerPort;
bool DebugEnabled;
bool AlertsEnabled;
bool AuthRequired;
const char *Secret;
const char *Salt;
const char *SessionChallenge;
QString Secret;
QString Salt;
QString SessionChallenge;
bool SettingsLoaded;
static Config* Current();
private:
static Config *_instance;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context rng;
static void OnFrontendEvent(enum obs_frontend_event event, void* param);
};
#endif // CONFIG_H

View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -16,22 +16,19 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifndef UTILS_H
#define UTILS_H
#include "ConnectionProperties.h"
#include <obs-module.h>
#include <obs-frontend-api.h>
class Utils
ConnectionProperties::ConnectionProperties()
: _authenticated(false)
{
public:
static obs_data_array_t* GetSceneItems(obs_source_t *source);
static obs_data_t* GetSceneItemData(obs_scene_item *item);
static obs_sceneitem_t* GetSceneItemFromName(obs_source_t *source, const char *name);
static obs_source_t* GetTransitionFromName(const char *search_name);
}
static obs_data_array_t* GetScenes();
static obs_data_t* GetSceneData(obs_source *source);
};
bool ConnectionProperties::isAuthenticated()
{
return _authenticated.load();
}
#endif // UTILS_H
void ConnectionProperties::setAuthenticated(bool authenticated)
{
_authenticated.store(authenticated);
}

View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -16,9 +16,16 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifndef OBSWEBSOCKET_H
#define OBSWEBSOCKET_H
#pragma once
#define OBS_WEBSOCKET_VERSION "0.3.1"
#include <atomic>
#endif // OBSWEBSOCKET_H
class ConnectionProperties
{
public:
explicit ConnectionProperties();
bool isAuthenticated();
void setAuthenticated(bool authenticated);
private:
std::atomic<bool> _authenticated;
};

861
src/Utils.cpp Normal file
View File

@ -0,0 +1,861 @@
/*
obs-websocket
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <inttypes.h>
#include <QtWidgets/QMainWindow>
#include <QtCore/QDir>
#include <QtCore/QUrl>
#include <obs-frontend-api.h>
#include <obs.hpp>
#include <util/platform.h>
#include "obs-websocket.h"
#include "Utils.h"
#include "Config.h"
Q_DECLARE_METATYPE(OBSScene);
const QHash<obs_bounds_type, QString> boundTypeNames = {
{ OBS_BOUNDS_STRETCH, "OBS_BOUNDS_STRETCH" },
{ OBS_BOUNDS_SCALE_INNER, "OBS_BOUNDS_SCALE_INNER" },
{ OBS_BOUNDS_SCALE_OUTER, "OBS_BOUNDS_SCALE_OUTER" },
{ OBS_BOUNDS_SCALE_TO_WIDTH, "OBS_BOUNDS_SCALE_TO_WIDTH" },
{ OBS_BOUNDS_SCALE_TO_HEIGHT, "OBS_BOUNDS_SCALE_TO_HEIGHT" },
{ OBS_BOUNDS_MAX_ONLY, "OBS_BOUNDS_MAX_ONLY" },
{ OBS_BOUNDS_NONE, "OBS_BOUNDS_NONE" },
};
QString getBoundsNameFromType(obs_bounds_type type) {
QString fallback = boundTypeNames.value(OBS_BOUNDS_NONE);
return boundTypeNames.value(type, fallback);
}
obs_bounds_type getBoundsTypeFromName(QString name) {
return boundTypeNames.key(name);
}
obs_data_array_t* Utils::StringListToArray(char** strings, const char* key) {
obs_data_array_t* list = obs_data_array_create();
if (!strings || !key) {
return list; // empty list
}
size_t index = 0;
char* value = nullptr;
do {
value = strings[index];
OBSDataAutoRelease item = obs_data_create();
obs_data_set_string(item, key, value);
if (value) {
obs_data_array_push_back(list, item);
}
index++;
} while (value != nullptr);
return list;
}
obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) {
obs_data_array_t* items = obs_data_array_create();
OBSScene scene = obs_scene_from_source(source);
if (!scene) {
return nullptr;
}
obs_scene_enum_items(scene, [](
obs_scene_t* scene,
obs_sceneitem_t* currentItem,
void* param)
{
obs_data_array_t* data = reinterpret_cast<obs_data_array_t*>(param);
OBSDataAutoRelease itemData = GetSceneItemData(currentItem);
obs_data_array_insert(data, 0, itemData);
return true;
}, items);
return items;
}
/**
* @typedef {Object} `SceneItem` An OBS Scene Item.
* @property {Number} `cy`
* @property {Number} `cx`
* @property {Number} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
* @property {String} `name` The name of this Scene Item.
* @property {int} `id` Scene item ID
* @property {Boolean} `render` Whether or not this Scene Item is set to "visible".
* @property {Boolean} `muted` Whether or not this Scene Item is muted.
* @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around
* @property {Number} `source_cx`
* @property {Number} `source_cy`
* @property {String} `type` Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown"
* @property {Number} `volume`
* @property {Number} `x`
* @property {Number} `y`
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
* @property {Array<SceneItem> (optional)} `groupChildren` List of children (if this item is a group)
*/
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
if (!item) {
return nullptr;
}
vec2 pos;
obs_sceneitem_get_pos(item, &pos);
vec2 scale;
obs_sceneitem_get_scale(item, &scale);
// obs_sceneitem_get_source doesn't increase the refcount
OBSSource itemSource = obs_sceneitem_get_source(item);
float item_width = float(obs_source_get_width(itemSource));
float item_height = float(obs_source_get_height(itemSource));
obs_data_t* data = obs_data_create();
obs_data_set_string(data, "name",
obs_source_get_name(itemSource));
obs_data_set_int(data, "id",
obs_sceneitem_get_id(item));
obs_data_set_string(data, "type",
obs_source_get_id(itemSource));
obs_data_set_double(data, "volume",
obs_source_get_volume(itemSource));
obs_data_set_double(data, "x", pos.x);
obs_data_set_double(data, "y", pos.y);
obs_data_set_int(data, "source_cx", (int)item_width);
obs_data_set_int(data, "source_cy", (int)item_height);
obs_data_set_bool(data, "muted", obs_source_muted(itemSource));
obs_data_set_int(data, "alignment", (int)obs_sceneitem_get_alignment(item));
obs_data_set_double(data, "cx", item_width * scale.x);
obs_data_set_double(data, "cy", item_height * scale.y);
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
obs_data_set_bool(data, "locked", obs_sceneitem_locked(item));
obs_scene_t* parent = obs_sceneitem_get_scene(item);
if (parent) {
OBSSource parentSource = obs_scene_get_source(parent);
QString parentKind = obs_source_get_id(parentSource);
if (parentKind == "group") {
obs_data_set_string(data, "parentGroupName", obs_source_get_name(parentSource));
}
}
if (obs_sceneitem_is_group(item)) {
OBSDataArrayAutoRelease children = obs_data_array_create();
obs_sceneitem_group_enum_items(item, [](obs_scene_t*, obs_sceneitem_t* currentItem, void* param) {
obs_data_array_t* items = reinterpret_cast<obs_data_array_t*>(param);
OBSDataAutoRelease itemData = GetSceneItemData(currentItem);
obs_data_array_push_back(items, itemData);
return true;
}, children);
obs_data_set_array(data, "groupChildren", children);
}
return data;
}
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_scene_t* scene, QString name) {
if (!scene) {
return nullptr;
}
struct current_search {
QString query;
obs_sceneitem_t* result;
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
};
current_search search;
search.query = name;
search.result = nullptr;
search.enumCallback = [](
obs_scene_t* scene,
obs_sceneitem_t* currentItem,
void* param)
{
current_search* search = reinterpret_cast<current_search*>(param);
if (obs_sceneitem_is_group(currentItem)) {
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, search);
if (search->result) {
return false;
}
}
QString currentItemName =
obs_source_get_name(obs_sceneitem_get_source(currentItem));
if (currentItemName == search->query) {
search->result = currentItem;
obs_sceneitem_addref(search->result);
return false;
}
return true;
};
obs_scene_enum_items(scene, search.enumCallback, &search);
return search.result;
}
obs_sceneitem_t* Utils::GetSceneItemFromId(obs_scene_t* scene, int64_t id) {
if (!scene) {
return nullptr;
}
struct current_search {
int query;
obs_sceneitem_t* result;
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
};
current_search search;
search.query = id;
search.result = nullptr;
search.enumCallback = [](
obs_scene_t* scene,
obs_sceneitem_t* currentItem,
void* param)
{
current_search* search = reinterpret_cast<current_search*>(param);
if (obs_sceneitem_is_group(currentItem)) {
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, search);
if (search->result) {
return false;
}
}
if (obs_sceneitem_get_id(currentItem) == search->query) {
search->result = currentItem;
obs_sceneitem_addref(search->result);
return false;
}
return true;
};
obs_scene_enum_items(scene, search.enumCallback, &search);
return search.result;
}
obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* itemInfo) {
if (!scene) {
return nullptr;
}
OBSDataItemAutoRelease idInfoItem = obs_data_item_byname(itemInfo, "id");
int id = obs_data_item_get_int(idInfoItem);
OBSDataItemAutoRelease nameInfoItem = obs_data_item_byname(itemInfo, "name");
const char* name = obs_data_item_get_string(nameInfoItem);
if (idInfoItem) {
obs_sceneitem_t* sceneItem = GetSceneItemFromId(scene, id);
obs_source_t* sceneItemSource = obs_sceneitem_get_source(sceneItem);
QString sceneItemName = obs_source_get_name(sceneItemSource);
if (nameInfoItem && (QString(name) != sceneItemName)) {
return nullptr;
}
return sceneItem;
} else if (nameInfoItem) {
return GetSceneItemFromName(scene, name);
}
return nullptr;
}
obs_sceneitem_t* Utils::GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem)
{
enum obs_data_type dataType = obs_data_item_gettype(dataItem);
if (dataType == OBS_DATA_OBJECT) {
OBSDataAutoRelease itemData = obs_data_item_get_obj(dataItem);
return GetSceneItemFromItem(scene, itemData);
} else if (dataType == OBS_DATA_STRING) {
QString name = obs_data_item_get_string(dataItem);
return GetSceneItemFromName(scene, name);
}
return nullptr;
}
bool Utils::IsValidAlignment(const uint32_t alignment) {
switch (alignment) {
case OBS_ALIGN_CENTER:
case OBS_ALIGN_LEFT:
case OBS_ALIGN_RIGHT:
case OBS_ALIGN_TOP:
case OBS_ALIGN_BOTTOM:
case OBS_ALIGN_TOP | OBS_ALIGN_LEFT:
case OBS_ALIGN_TOP | OBS_ALIGN_RIGHT:
case OBS_ALIGN_BOTTOM | OBS_ALIGN_LEFT:
case OBS_ALIGN_BOTTOM | OBS_ALIGN_RIGHT: {
return true;
}
}
return false;
}
obs_source_t* Utils::GetTransitionFromName(QString searchName) {
obs_source_t* foundTransition = nullptr;
obs_frontend_source_list transition_list = {};
obs_frontend_get_transitions(&transition_list);
for (size_t i = 0; i < transition_list.sources.num; i++) {
obs_source_t* transition = transition_list.sources.array[i];
QString transitionName = obs_source_get_name(transition);
if (transitionName == searchName) {
foundTransition = transition;
obs_source_addref(foundTransition);
break;
}
}
obs_frontend_source_list_free(&transition_list);
return foundTransition;
}
obs_scene_t* Utils::GetSceneFromNameOrCurrent(QString sceneName) {
// Both obs_frontend_get_current_scene() and obs_get_source_by_name()
// increase the returned source's refcount
OBSSourceAutoRelease sceneSource = nullptr;
if (sceneName.isEmpty() || sceneName.isNull()) {
sceneSource = obs_frontend_get_current_scene();
}
else {
sceneSource = obs_get_source_by_name(sceneName.toUtf8());
}
return obs_scene_from_source(sceneSource);
}
obs_data_array_t* Utils::GetScenes() {
obs_frontend_source_list sceneList = {};
obs_frontend_get_scenes(&sceneList);
obs_data_array_t* scenes = obs_data_array_create();
for (size_t i = 0; i < sceneList.sources.num; i++) {
obs_source_t* scene = sceneList.sources.array[i];
OBSDataAutoRelease sceneData = GetSceneData(scene);
obs_data_array_push_back(scenes, sceneData);
}
obs_frontend_source_list_free(&sceneList);
return scenes;
}
obs_data_t* Utils::GetSceneData(obs_source_t* source) {
OBSDataArrayAutoRelease sceneItems = GetSceneItems(source);
obs_data_t* sceneData = obs_data_create();
obs_data_set_string(sceneData, "name", obs_source_get_name(source));
obs_data_set_array(sceneData, "sources", sceneItems);
return sceneData;
}
QSpinBox* Utils::GetTransitionDurationControl() {
QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window();
return window->findChild<QSpinBox*>("transitionDuration");
}
int Utils::GetTransitionDuration(obs_source_t* transition) {
if (!transition || obs_source_get_type(transition) != OBS_SOURCE_TYPE_TRANSITION) {
return -1;
}
QString transitionKind = obs_source_get_id(transition);
if (transitionKind == "cut_transition") {
// If this is a Cut transition, return 0
return 0;
}
if (obs_transition_fixed(transition)) {
// If this transition has a fixed duration (such as a Stinger),
// we don't currently have a way of retrieving that number.
// For now, return -1 to indicate that we don't know the actual duration.
return -1;
}
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene);
// Detect if transition is the global transition or a transition override.
// Fetching the duration is different depending on the case.
obs_data_item_t* transitionDurationItem = obs_data_item_byname(destinationSettings, "transition_duration");
int duration = (
transitionDurationItem
? obs_data_item_get_int(transitionDurationItem)
: obs_frontend_get_transition_duration()
);
return duration;
}
bool Utils::SetTransitionByName(QString transitionName) {
OBSSourceAutoRelease transition = GetTransitionFromName(transitionName);
if (transition) {
obs_frontend_set_current_transition(transition);
return true;
} else {
return false;
}
}
obs_data_t* Utils::GetTransitionData(obs_source_t* transition) {
int duration = Utils::GetTransitionDuration(transition);
if (duration < 0) {
blog(LOG_WARNING, "GetTransitionData: duration is negative !");
}
OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A);
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
obs_data_t* transitionData = obs_data_create();
obs_data_set_string(transitionData, "name", obs_source_get_name(transition));
obs_data_set_string(transitionData, "type", obs_source_get_id(transition));
obs_data_set_int(transitionData, "duration", duration);
// When a transition starts and while it is running, SOURCE_A is the source scene
// and SOURCE_B is the destination scene.
// Before the transition_end event is triggered on a transition, the destination scene
// goes into SOURCE_A and SOURCE_B becomes null. This means that, in transition_stop
// we don't know what was the source scene
// TODO fix this in libobs
bool isTransitionEndEvent = (sourceScene == destinationScene);
if (!isTransitionEndEvent) {
obs_data_set_string(transitionData, "from-scene", obs_source_get_name(sourceScene));
}
obs_data_set_string(transitionData, "to-scene", obs_source_get_name(destinationScene));
return transitionData;
}
QString Utils::OBSVersionString() {
uint32_t version = obs_get_version();
uint8_t major, minor, patch;
major = (version >> 24) & 0xFF;
minor = (version >> 16) & 0xFF;
patch = version & 0xFF;
QString result = QString("%1.%2.%3")
.arg(major).arg(minor).arg(patch);
return result;
}
QSystemTrayIcon* Utils::GetTrayIcon() {
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
if (!main) return nullptr;
QList<QSystemTrayIcon*> trays = main->findChildren<QSystemTrayIcon*>();
return trays.isEmpty() ? nullptr : trays.first();
}
void Utils::SysTrayNotify(QString text,
QSystemTrayIcon::MessageIcon icon, QString title) {
if (!GetConfig()->AlertsEnabled ||
!QSystemTrayIcon::isSystemTrayAvailable() ||
!QSystemTrayIcon::supportsMessages())
{
return;
}
QSystemTrayIcon* trayIcon = GetTrayIcon();
if (trayIcon)
trayIcon->showMessage(title, text, icon);
}
const char* Utils::GetRecordingFolder() {
config_t* profile = obs_frontend_get_profile_config();
QString outputMode = config_get_string(profile, "Output", "Mode");
if (outputMode == "Advanced") {
// Advanced mode
return config_get_string(profile, "AdvOut", "RecFilePath");
} else {
// Simple mode
return config_get_string(profile, "SimpleOutput", "FilePath");
}
}
bool Utils::SetRecordingFolder(const char* path) {
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(".");
}
config_t* profile = obs_frontend_get_profile_config();
config_set_string(profile, "AdvOut", "RecFilePath", path);
config_set_string(profile, "SimpleOutput", "FilePath", path);
config_save(profile);
return true;
}
QString Utils::ParseDataToQueryString(obs_data_t* data) {
if (!data)
return QString();
QString query;
obs_data_item_t* item = obs_data_first(data);
if (item) {
bool isFirst = true;
do {
if (!obs_data_item_has_user_value(item))
continue;
if (!isFirst)
query += "&";
else
isFirst = false;
QString attrName = obs_data_item_get_name(item);
query += (attrName + "=");
switch (obs_data_item_gettype(item)) {
case OBS_DATA_BOOLEAN:
query += (obs_data_item_get_bool(item) ? "true" : "false");
break;
case OBS_DATA_NUMBER:
switch (obs_data_item_numtype(item)) {
case OBS_DATA_NUM_DOUBLE:
query +=
QString::number(obs_data_item_get_double(item));
break;
case OBS_DATA_NUM_INT:
query +=
QString::number(obs_data_item_get_int(item));
break;
case OBS_DATA_NUM_INVALID:
break;
}
break;
case OBS_DATA_STRING:
query +=
QUrl::toPercentEncoding(
QString(obs_data_item_get_string(item)));
break;
default:
//other types are not supported
break;
}
} while (obs_data_item_next(&item));
}
return query;
}
obs_hotkey_t* Utils::FindHotkeyByName(QString name) {
struct current_search {
QString query;
obs_hotkey_t* result;
};
current_search search;
search.query = name;
search.result = nullptr;
obs_enum_hotkeys([](void* data, obs_hotkey_id id, obs_hotkey_t* hotkey) {
current_search* search = reinterpret_cast<current_search*>(data);
const char* hk_name = obs_hotkey_get_name(hotkey);
if (hk_name == search->query) {
search->result = hotkey;
return false;
}
return true;
}, &search);
return search.result;
}
bool Utils::ReplayBufferEnabled() {
config_t* profile = obs_frontend_get_profile_config();
QString outputMode = config_get_string(profile, "Output", "Mode");
if (outputMode == "Simple") {
return config_get_bool(profile, "SimpleOutput", "RecRB");
}
else if (outputMode == "Advanced") {
return config_get_bool(profile, "AdvOut", "RecRB");
}
return false;
}
void Utils::StartReplayBuffer() {
if (obs_frontend_replay_buffer_active())
return;
if (!IsRPHotkeySet()) {
obs_output_t* rpOutput = obs_frontend_get_replay_buffer_output();
OBSData outputHotkeys = obs_hotkeys_save_output(rpOutput);
OBSDataAutoRelease dummyBinding = obs_data_create();
obs_data_set_bool(dummyBinding, "control", true);
obs_data_set_bool(dummyBinding, "alt", true);
obs_data_set_bool(dummyBinding, "shift", true);
obs_data_set_bool(dummyBinding, "command", true);
obs_data_set_string(dummyBinding, "key", "OBS_KEY_0");
OBSDataArray rpSaveHotkey = obs_data_get_array(
outputHotkeys, "ReplayBuffer.Save");
obs_data_array_push_back(rpSaveHotkey, dummyBinding);
obs_hotkeys_load_output(rpOutput, outputHotkeys);
obs_frontend_replay_buffer_start();
obs_output_release(rpOutput);
}
else {
obs_frontend_replay_buffer_start();
}
}
bool Utils::IsRPHotkeySet() {
OBSOutputAutoRelease rpOutput = obs_frontend_get_replay_buffer_output();
OBSDataAutoRelease hotkeys = obs_hotkeys_save_output(rpOutput);
OBSDataArrayAutoRelease bindings = obs_data_get_array(hotkeys,
"ReplayBuffer.Save");
size_t count = obs_data_array_count(bindings);
return (count > 0);
}
const char* Utils::GetFilenameFormatting() {
config_t* profile = obs_frontend_get_profile_config();
return config_get_string(profile, "Output", "FilenameFormatting");
}
bool Utils::SetFilenameFormatting(const char* filenameFormatting) {
config_t* profile = obs_frontend_get_profile_config();
config_set_string(profile, "Output", "FilenameFormatting", filenameFormatting);
config_save(profile);
return true;
}
// Transform properties copy-pasted from WSRequestHandler_SceneItems.cpp because typedefs can't be extended yet
/**
* @typedef {Object} `SceneItemTransform`
* @property {int} `position.x` The x position of the scene item from the left.
* @property {int} `position.y` The y position of the scene item from the top.
* @property {int} `position.alignment` The point on the scene item that the item is manipulated from.
* @property {double} `rotation` The clockwise rotation of the scene item in degrees around the point of alignment.
* @property {double} `scale.x` The x-scale factor of the scene item.
* @property {double} `scale.y` The y-scale factor of the scene item.
* @property {int} `crop.top` The number of pixels cropped off the top of the scene item before scaling.
* @property {int} `crop.right` The number of pixels cropped off the right of the scene item before scaling.
* @property {int} `crop.bottom` The number of pixels cropped off the bottom of the scene item before scaling.
* @property {int} `crop.left` The number of pixels cropped off the left of the scene item before scaling.
* @property {bool} `visible` If the scene item is visible.
* @property {bool} `locked` If the scene item is locked in position.
* @property {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
* @property {int} `bounds.alignment` Alignment of the bounding box.
* @property {double} `bounds.x` Width of the bounding box.
* @property {double} `bounds.y` Height of the bounding box.
* @property {int} `sourceWidth` Base width (without scaling) of the source
* @property {int} `sourceHeight` Base source (without scaling) of the source
* @property {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
* @property {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
* @property {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
*/
obs_data_t* Utils::GetSceneItemPropertiesData(obs_sceneitem_t* sceneItem) {
if (!sceneItem) {
return nullptr;
}
OBSSource source = obs_sceneitem_get_source(sceneItem);
uint32_t baseSourceWidth = obs_source_get_width(source);
uint32_t baseSourceHeight = obs_source_get_height(source);
vec2 pos, scale, bounds;
obs_sceneitem_crop crop;
obs_sceneitem_get_pos(sceneItem, &pos);
obs_sceneitem_get_scale(sceneItem, &scale);
obs_sceneitem_get_crop(sceneItem, &crop);
obs_sceneitem_get_bounds(sceneItem, &bounds);
uint32_t alignment = obs_sceneitem_get_alignment(sceneItem);
float rotation = obs_sceneitem_get_rot(sceneItem);
bool isVisible = obs_sceneitem_visible(sceneItem);
bool isLocked = obs_sceneitem_locked(sceneItem);
obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem);
uint32_t boundsAlignment = obs_sceneitem_get_bounds_alignment(sceneItem);
QString boundsTypeName = getBoundsNameFromType(boundsType);
OBSDataAutoRelease posData = obs_data_create();
obs_data_set_double(posData, "x", pos.x);
obs_data_set_double(posData, "y", pos.y);
obs_data_set_int(posData, "alignment", alignment);
OBSDataAutoRelease scaleData = obs_data_create();
obs_data_set_double(scaleData, "x", scale.x);
obs_data_set_double(scaleData, "y", scale.y);
OBSDataAutoRelease cropData = obs_data_create();
obs_data_set_int(cropData, "left", crop.left);
obs_data_set_int(cropData, "top", crop.top);
obs_data_set_int(cropData, "right", crop.right);
obs_data_set_int(cropData, "bottom", crop.bottom);
OBSDataAutoRelease boundsData = obs_data_create();
obs_data_set_string(boundsData, "type", boundsTypeName.toUtf8());
obs_data_set_int(boundsData, "alignment", boundsAlignment);
obs_data_set_double(boundsData, "x", bounds.x);
obs_data_set_double(boundsData, "y", bounds.y);
obs_data_t* data = obs_data_create();
obs_data_set_obj(data, "position", posData);
obs_data_set_double(data, "rotation", rotation);
obs_data_set_obj(data, "scale", scaleData);
obs_data_set_obj(data, "crop", cropData);
obs_data_set_bool(data, "visible", isVisible);
obs_data_set_bool(data, "locked", isLocked);
obs_data_set_obj(data, "bounds", boundsData);
obs_data_set_int(data, "sourceWidth", baseSourceWidth);
obs_data_set_int(data, "sourceHeight", baseSourceHeight);
obs_data_set_double(data, "width", baseSourceWidth * scale.x);
obs_data_set_double(data, "height", baseSourceHeight * scale.y);
obs_scene_t* parent = obs_sceneitem_get_scene(sceneItem);
if (parent) {
OBSSource parentSource = obs_scene_get_source(parent);
QString parentKind = obs_source_get_id(parentSource);
if (parentKind == "group") {
obs_data_set_string(data, "parentGroupName", obs_source_get_name(parentSource));
}
}
if (obs_sceneitem_is_group(sceneItem)) {
OBSDataArrayAutoRelease children = obs_data_array_create();
obs_sceneitem_group_enum_items(sceneItem, [](obs_scene_t*, obs_sceneitem_t* subItem, void* param) {
obs_data_array_t* items = reinterpret_cast<obs_data_array_t*>(param);
OBSDataAutoRelease itemData = GetSceneItemPropertiesData(subItem);
obs_data_array_push_back(items, itemData);
return true;
}, children);
obs_data_set_array(data, "groupChildren", children);
}
return data;
}
obs_data_t* Utils::GetSourceFilterInfo(obs_source_t* filter, bool includeSettings)
{
obs_data_t* data = obs_data_create();
obs_data_set_bool(data, "enabled", obs_source_enabled(filter));
obs_data_set_string(data, "type", obs_source_get_id(filter));
obs_data_set_string(data, "name", obs_source_get_name(filter));
if (includeSettings) {
OBSDataAutoRelease settings = obs_source_get_settings(filter);
obs_data_set_obj(data, "settings", settings);
}
return data;
}
obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool includeSettings)
{
struct enum_params {
obs_data_array_t* filters;
bool includeSettings;
};
if (!source) {
return nullptr;
}
struct enum_params enumParams;
enumParams.filters = obs_data_array_create();
enumParams.includeSettings = includeSettings;
obs_source_enum_filters(source, [](obs_source_t* parent, obs_source_t* child, void* param)
{
auto enumParams = reinterpret_cast<struct enum_params*>(param);
OBSDataAutoRelease filterData = Utils::GetSourceFilterInfo(child, enumParams->includeSettings);
obs_data_array_push_back(enumParams->filters, filterData);
}, &enumParams);
return enumParams.filters;
}
void getPauseRecordingFunctions(RecordingPausedFunction* recPausedFuncPtr, PauseRecordingFunction* pauseRecFuncPtr)
{
void* frontendApi = os_dlopen("obs-frontend-api");
if (recPausedFuncPtr) {
*recPausedFuncPtr = (RecordingPausedFunction)os_dlsym(frontendApi, "obs_frontend_recording_paused");
}
if (pauseRecFuncPtr) {
*pauseRecFuncPtr = (PauseRecordingFunction)os_dlsym(frontendApi, "obs_frontend_recording_pause");
}
}
QString Utils::nsToTimestamp(uint64_t ns)
{
uint64_t ms = ns / 1000000ULL;
uint64_t secs = ms / 1000ULL;
uint64_t minutes = secs / 60ULL;
uint64_t hoursPart = minutes / 60ULL;
uint64_t minutesPart = minutes % 60ULL;
uint64_t secsPart = secs % 60ULL;
uint64_t msPart = ms % 1000ULL;
return QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart);
}

88
src/Utils.h Normal file
View File

@ -0,0 +1,88 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#pragma once
#include <stdio.h>
#include <QtCore/QString>
#include <QtWidgets/QSpinBox>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QLayout>
#include <QtWidgets/QListWidget>
#include <QtWidgets/QSystemTrayIcon>
#include <obs.hpp>
#include <obs-module.h>
#include <util/config-file.h>
typedef void(*PauseRecordingFunction)(bool);
typedef bool(*RecordingPausedFunction)();
namespace Utils {
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);
// These functions support nested lookup into groups
obs_sceneitem_t* GetSceneItemFromName(obs_scene_t* scene, QString name);
obs_sceneitem_t* GetSceneItemFromId(obs_scene_t* scene, int64_t id);
obs_sceneitem_t* GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* item);
obs_sceneitem_t* GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem);
obs_scene_t* GetSceneFromNameOrCurrent(QString sceneName);
obs_data_t* GetSceneItemPropertiesData(obs_sceneitem_t* item);
obs_data_t* GetSourceFilterInfo(obs_source_t* filter, bool includeSettings);
obs_data_array_t* GetSourceFiltersList(obs_source_t* source, bool includeSettings);
bool IsValidAlignment(const uint32_t alignment);
obs_data_array_t* GetScenes();
obs_data_t* GetSceneData(obs_source_t* source);
// TODO contribute a proper frontend API method for this to OBS and remove this hack
QSpinBox* GetTransitionDurationControl();
int GetTransitionDuration(obs_source_t* transition);
obs_source_t* GetTransitionFromName(QString transitionName);
bool SetTransitionByName(QString transitionName);
obs_data_t* GetTransitionData(obs_source_t* transition);
QString OBSVersionString();
QSystemTrayIcon* GetTrayIcon();
void SysTrayNotify(
QString text,
QSystemTrayIcon::MessageIcon n,
QString title = QString("obs-websocket"));
const char* GetRecordingFolder();
bool SetRecordingFolder(const char* path);
QString ParseDataToQueryString(obs_data_t* data);
obs_hotkey_t* FindHotkeyByName(QString name);
bool ReplayBufferEnabled();
void StartReplayBuffer();
bool IsRPHotkeySet();
const char* GetFilenameFormatting();
bool SetFilenameFormatting(const char* filenameFormatting);
QString nsToTimestamp(uint64_t ns);
};

1739
src/WSEvents.cpp Normal file

File diff suppressed because it is too large Load Diff

145
src/WSEvents.h Normal file
View File

@ -0,0 +1,145 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#pragma once
#include <obs.hpp>
#include <obs-frontend-api.h>
#include <util/platform.h>
#include <QtWidgets/QListWidgetItem>
#include <QtCore/QSharedPointer>
#include <QtCore/QTimer>
#include "WSServer.h"
class WSEvents : public QObject
{
Q_OBJECT
public:
explicit WSEvents(WSServerPtr srv);
~WSEvents();
void connectSourceSignals(obs_source_t* source);
void disconnectSourceSignals(obs_source_t* source);
void connectFilterSignals(obs_source_t* filter);
void disconnectFilterSignals(obs_source_t* filter);
void hookTransitionPlaybackEvents();
void unhookTransitionPlaybackEvents();
uint64_t getStreamingTime();
uint64_t getRecordingTime();
QString getStreamingTimecode();
QString getRecordingTimecode();
obs_data_t* GetStats();
void OnBroadcastCustomMessage(QString realm, obs_data_t* data);
bool HeartbeatIsActive;
private slots:
void StreamStatus();
void Heartbeat();
void TransitionDurationChanged(int ms);
private:
WSServerPtr _srv;
QTimer streamStatusTimer;
QTimer heartbeatTimer;
os_cpu_usage_info_t* cpuUsageInfo;
bool pulse;
uint64_t _streamStarttime;
uint64_t _lastBytesSent;
uint64_t _lastBytesSentTime;
void broadcastUpdate(const char* updateType,
obs_data_t* additionalFields);
void OnSceneChange();
void OnSceneListChange();
void OnSceneCollectionChange();
void OnSceneCollectionListChange();
void OnTransitionChange();
void OnTransitionListChange();
void OnProfileChange();
void OnProfileListChange();
void OnStreamStarting();
void OnStreamStarted();
void OnStreamStopping();
void OnStreamStopped();
void OnRecordingStarting();
void OnRecordingStarted();
void OnRecordingStopping();
void OnRecordingStopped();
void OnRecordingPaused();
void OnRecordingResumed();
void OnReplayStarting();
void OnReplayStarted();
void OnReplayStopping();
void OnReplayStopped();
void OnStudioModeSwitched(bool enabled);
void OnPreviewSceneChanged();
void OnExit();
static void FrontendEventHandler(
enum obs_frontend_event event, void* privateData);
static void OnTransitionBegin(void* param, calldata_t* data);
static void OnTransitionEnd(void* param, calldata_t* data);
static void OnTransitionVideoEnd(void* param, calldata_t* data);
static void OnSourceCreate(void* param, calldata_t* data);
static void OnSourceDestroy(void* param, calldata_t* data);
static void OnSourceVolumeChange(void* param, calldata_t* data);
static void OnSourceMuteStateChange(void* param, calldata_t* data);
static void OnSourceAudioSyncOffsetChanged(void* param, calldata_t* data);
static void OnSourceAudioMixersChanged(void* param, calldata_t* data);
static void OnSourceRename(void* param, calldata_t* data);
static void OnSourceFilterAdded(void* param, calldata_t* data);
static void OnSourceFilterRemoved(void* param, calldata_t* data);
static void OnSourceFilterVisibilityChanged(void* param, calldata_t* data);
static void OnSourceFilterOrderChanged(void* param, calldata_t* data);
static void OnSceneReordered(void* param, calldata_t* data);
static void OnSceneItemAdd(void* param, calldata_t* data);
static void OnSceneItemDelete(void* param, calldata_t* data);
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
static void OnSceneItemLockChanged(void* param, calldata_t* data);
static void OnSceneItemTransform(void* param, calldata_t* data);
static void OnSceneItemSelected(void* param, calldata_t* data);
static void OnSceneItemDeselected(void* param, calldata_t* data);
};

181
src/WSRequestHandler.cpp Normal file
View File

@ -0,0 +1,181 @@
/**
* obs-websocket
* Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
* Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <functional>
#include <obs-data.h>
#include "Config.h"
#include "Utils.h"
#include "WSRequestHandler.h"
using namespace std::placeholders;
const QHash<QString, RpcMethodHandler> WSRequestHandler::messageMap {
{ "GetVersion", &WSRequestHandler::GetVersion },
{ "GetAuthRequired", &WSRequestHandler::GetAuthRequired },
{ "Authenticate", &WSRequestHandler::Authenticate },
{ "GetStats", &WSRequestHandler::GetStats },
{ "SetHeartbeat", &WSRequestHandler::SetHeartbeat },
{ "GetVideoInfo", &WSRequestHandler::GetVideoInfo },
{ "OpenProjector", &WSRequestHandler::OpenProjector },
{ "SetFilenameFormatting", &WSRequestHandler::SetFilenameFormatting },
{ "GetFilenameFormatting", &WSRequestHandler::GetFilenameFormatting },
{ "BroadcastCustomMessage", &WSRequestHandler::BroadcastCustomMessage },
{ "SetCurrentScene", &WSRequestHandler::SetCurrentScene },
{ "GetCurrentScene", &WSRequestHandler::GetCurrentScene },
{ "GetSceneList", &WSRequestHandler::GetSceneList },
{ "SetSceneTransitionOverride", &WSRequestHandler::SetSceneTransitionOverride },
{ "RemoveSceneTransitionOverride", &WSRequestHandler::RemoveSceneTransitionOverride },
{ "GetSceneTransitionOverride", &WSRequestHandler::GetSceneTransitionOverride },
{ "SetSourceRender", &WSRequestHandler::SetSceneItemRender }, // Retrocompat
{ "SetSceneItemRender", &WSRequestHandler::SetSceneItemRender },
{ "SetSceneItemPosition", &WSRequestHandler::SetSceneItemPosition },
{ "SetSceneItemTransform", &WSRequestHandler::SetSceneItemTransform },
{ "SetSceneItemCrop", &WSRequestHandler::SetSceneItemCrop },
{ "GetSceneItemProperties", &WSRequestHandler::GetSceneItemProperties },
{ "SetSceneItemProperties", &WSRequestHandler::SetSceneItemProperties },
{ "ResetSceneItem", &WSRequestHandler::ResetSceneItem },
{ "DeleteSceneItem", &WSRequestHandler::DeleteSceneItem },
{ "DuplicateSceneItem", &WSRequestHandler::DuplicateSceneItem },
{ "ReorderSceneItems", &WSRequestHandler::ReorderSceneItems },
{ "GetStreamingStatus", &WSRequestHandler::GetStreamingStatus },
{ "StartStopStreaming", &WSRequestHandler::StartStopStreaming },
{ "StartStopRecording", &WSRequestHandler::StartStopRecording },
{ "StartStreaming", &WSRequestHandler::StartStreaming },
{ "StopStreaming", &WSRequestHandler::StopStreaming },
{ "StartRecording", &WSRequestHandler::StartRecording },
{ "StopRecording", &WSRequestHandler::StopRecording },
{ "PauseRecording", &WSRequestHandler::PauseRecording },
{ "ResumeRecording", &WSRequestHandler::ResumeRecording },
{ "StartStopReplayBuffer", &WSRequestHandler::StartStopReplayBuffer },
{ "StartReplayBuffer", &WSRequestHandler::StartReplayBuffer },
{ "StopReplayBuffer", &WSRequestHandler::StopReplayBuffer },
{ "SaveReplayBuffer", &WSRequestHandler::SaveReplayBuffer },
{ "SetRecordingFolder", &WSRequestHandler::SetRecordingFolder },
{ "GetRecordingFolder", &WSRequestHandler::GetRecordingFolder },
{ "GetTransitionList", &WSRequestHandler::GetTransitionList },
{ "GetCurrentTransition", &WSRequestHandler::GetCurrentTransition },
{ "SetCurrentTransition", &WSRequestHandler::SetCurrentTransition },
{ "SetTransitionDuration", &WSRequestHandler::SetTransitionDuration },
{ "GetTransitionDuration", &WSRequestHandler::GetTransitionDuration },
{ "GetTransitionPosition", &WSRequestHandler::GetTransitionPosition },
{ "SetVolume", &WSRequestHandler::SetVolume },
{ "GetVolume", &WSRequestHandler::GetVolume },
{ "ToggleMute", &WSRequestHandler::ToggleMute },
{ "SetMute", &WSRequestHandler::SetMute },
{ "GetMute", &WSRequestHandler::GetMute },
{ "SetSourceName", &WSRequestHandler::SetSourceName },
{ "SetSyncOffset", &WSRequestHandler::SetSyncOffset },
{ "GetSyncOffset", &WSRequestHandler::GetSyncOffset },
{ "GetSpecialSources", &WSRequestHandler::GetSpecialSources },
{ "GetSourcesList", &WSRequestHandler::GetSourcesList },
{ "GetSourceTypesList", &WSRequestHandler::GetSourceTypesList },
{ "GetSourceSettings", &WSRequestHandler::GetSourceSettings },
{ "SetSourceSettings", &WSRequestHandler::SetSourceSettings },
{ "GetAudioMonitorType", &WSRequestHandler::GetAudioMonitorType },
{ "SetAudioMonitorType", &WSRequestHandler::SetAudioMonitorType },
{ "TakeSourceScreenshot", &WSRequestHandler::TakeSourceScreenshot },
{ "GetSourceFilters", &WSRequestHandler::GetSourceFilters },
{ "GetSourceFilterInfo", &WSRequestHandler::GetSourceFilterInfo },
{ "AddFilterToSource", &WSRequestHandler::AddFilterToSource },
{ "RemoveFilterFromSource", &WSRequestHandler::RemoveFilterFromSource },
{ "ReorderSourceFilter", &WSRequestHandler::ReorderSourceFilter },
{ "MoveSourceFilter", &WSRequestHandler::MoveSourceFilter },
{ "SetSourceFilterSettings", &WSRequestHandler::SetSourceFilterSettings },
{ "SetSourceFilterVisibility", &WSRequestHandler::SetSourceFilterVisibility },
{ "SetCurrentSceneCollection", &WSRequestHandler::SetCurrentSceneCollection },
{ "GetCurrentSceneCollection", &WSRequestHandler::GetCurrentSceneCollection },
{ "ListSceneCollections", &WSRequestHandler::ListSceneCollections },
{ "SetCurrentProfile", &WSRequestHandler::SetCurrentProfile },
{ "GetCurrentProfile", &WSRequestHandler::GetCurrentProfile },
{ "ListProfiles", &WSRequestHandler::ListProfiles },
{ "SetStreamSettings", &WSRequestHandler::SetStreamSettings },
{ "GetStreamSettings", &WSRequestHandler::GetStreamSettings },
{ "SaveStreamSettings", &WSRequestHandler::SaveStreamSettings },
#if BUILD_CAPTIONS
{ "SendCaptions", &WSRequestHandler::SendCaptions },
#endif
{ "GetStudioModeStatus", &WSRequestHandler::GetStudioModeStatus },
{ "GetPreviewScene", &WSRequestHandler::GetPreviewScene },
{ "SetPreviewScene", &WSRequestHandler::SetPreviewScene },
{ "TransitionToProgram", &WSRequestHandler::TransitionToProgram },
{ "EnableStudioMode", &WSRequestHandler::EnableStudioMode },
{ "DisableStudioMode", &WSRequestHandler::DisableStudioMode },
{ "ToggleStudioMode", &WSRequestHandler::ToggleStudioMode },
{ "SetTextGDIPlusProperties", &WSRequestHandler::SetTextGDIPlusProperties },
{ "GetTextGDIPlusProperties", &WSRequestHandler::GetTextGDIPlusProperties },
{ "SetTextFreetype2Properties", &WSRequestHandler::SetTextFreetype2Properties },
{ "GetTextFreetype2Properties", &WSRequestHandler::GetTextFreetype2Properties },
{ "GetBrowserSourceProperties", &WSRequestHandler::GetBrowserSourceProperties },
{ "SetBrowserSourceProperties", &WSRequestHandler::SetBrowserSourceProperties },
{ "ListOutputs", &WSRequestHandler::ListOutputs },
{ "GetOutputInfo", &WSRequestHandler::GetOutputInfo },
{ "StartOutput", &WSRequestHandler::StartOutput },
{ "StopOutput", &WSRequestHandler::StopOutput }
};
const QSet<QString> WSRequestHandler::authNotRequired {
"GetVersion",
"GetAuthRequired",
"Authenticate"
};
WSRequestHandler::WSRequestHandler(ConnectionProperties& connProperties) :
_connProperties(connProperties)
{
}
RpcResponse WSRequestHandler::processRequest(const RpcRequest& request){
if (GetConfig()->AuthRequired
&& (!authNotRequired.contains(request.methodName()))
&& (!_connProperties.isAuthenticated()))
{
return RpcResponse::fail(request, "Not Authenticated");
}
RpcMethodHandler handlerFunc = messageMap[request.methodName()];
if (!handlerFunc) {
return RpcResponse::fail(request, "invalid request type");
}
return std::bind(handlerFunc, this, _1)(request);
}

171
src/WSRequestHandler.h Normal file
View File

@ -0,0 +1,171 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#pragma once
#include <QtCore/QString>
#include <QtCore/QHash>
#include <QtCore/QSet>
#include <obs.hpp>
#include <obs-frontend-api.h>
#include "ConnectionProperties.h"
#include "rpc/RpcRequest.h"
#include "rpc/RpcResponse.h"
#include "obs-websocket.h"
class WSRequestHandler;
typedef RpcResponse(WSRequestHandler::*RpcMethodHandler)(const RpcRequest&);
class WSRequestHandler {
public:
explicit WSRequestHandler(ConnectionProperties& connProperties);
RpcResponse processRequest(const RpcRequest& textMessage);
private:
ConnectionProperties& _connProperties;
static const QHash<QString, RpcMethodHandler> messageMap;
static const QSet<QString> authNotRequired;
RpcResponse GetVersion(const RpcRequest&);
RpcResponse GetAuthRequired(const RpcRequest&);
RpcResponse Authenticate(const RpcRequest&);
RpcResponse GetStats(const RpcRequest&);
RpcResponse SetHeartbeat(const RpcRequest&);
RpcResponse GetVideoInfo(const RpcRequest&);
RpcResponse OpenProjector(const RpcRequest&);
RpcResponse SetFilenameFormatting(const RpcRequest&);
RpcResponse GetFilenameFormatting(const RpcRequest&);
RpcResponse BroadcastCustomMessage(const RpcRequest&);
RpcResponse SetCurrentScene(const RpcRequest&);
RpcResponse GetCurrentScene(const RpcRequest&);
RpcResponse GetSceneList(const RpcRequest&);
RpcResponse SetSceneTransitionOverride(const RpcRequest&);
RpcResponse RemoveSceneTransitionOverride(const RpcRequest&);
RpcResponse GetSceneTransitionOverride(const RpcRequest&);
RpcResponse SetSceneItemRender(const RpcRequest&);
RpcResponse SetSceneItemPosition(const RpcRequest&);
RpcResponse SetSceneItemTransform(const RpcRequest&);
RpcResponse SetSceneItemCrop(const RpcRequest&);
RpcResponse GetSceneItemProperties(const RpcRequest&);
RpcResponse SetSceneItemProperties(const RpcRequest&);
RpcResponse ResetSceneItem(const RpcRequest&);
RpcResponse DuplicateSceneItem(const RpcRequest&);
RpcResponse DeleteSceneItem(const RpcRequest&);
RpcResponse ReorderSceneItems(const RpcRequest&);
RpcResponse GetStreamingStatus(const RpcRequest&);
RpcResponse StartStopStreaming(const RpcRequest&);
RpcResponse StartStopRecording(const RpcRequest&);
RpcResponse StartStreaming(const RpcRequest&);
RpcResponse StopStreaming(const RpcRequest&);
RpcResponse StartRecording(const RpcRequest&);
RpcResponse StopRecording(const RpcRequest&);
RpcResponse PauseRecording(const RpcRequest&);
RpcResponse ResumeRecording(const RpcRequest&);
RpcResponse StartStopReplayBuffer(const RpcRequest&);
RpcResponse StartReplayBuffer(const RpcRequest&);
RpcResponse StopReplayBuffer(const RpcRequest&);
RpcResponse SaveReplayBuffer(const RpcRequest&);
RpcResponse SetRecordingFolder(const RpcRequest&);
RpcResponse GetRecordingFolder(const RpcRequest&);
RpcResponse GetTransitionList(const RpcRequest&);
RpcResponse GetCurrentTransition(const RpcRequest&);
RpcResponse SetCurrentTransition(const RpcRequest&);
RpcResponse SetTransitionDuration(const RpcRequest&);
RpcResponse GetTransitionDuration(const RpcRequest&);
RpcResponse GetTransitionPosition(const RpcRequest&);
RpcResponse SetVolume(const RpcRequest&);
RpcResponse GetVolume(const RpcRequest&);
RpcResponse ToggleMute(const RpcRequest&);
RpcResponse SetMute(const RpcRequest&);
RpcResponse GetMute(const RpcRequest&);
RpcResponse SetSourceName(const RpcRequest&);
RpcResponse SetSyncOffset(const RpcRequest&);
RpcResponse GetSyncOffset(const RpcRequest&);
RpcResponse GetSpecialSources(const RpcRequest&);
RpcResponse GetSourcesList(const RpcRequest&);
RpcResponse GetSourceTypesList(const RpcRequest&);
RpcResponse GetSourceSettings(const RpcRequest&);
RpcResponse SetSourceSettings(const RpcRequest&);
RpcResponse GetAudioMonitorType(const RpcRequest&);
RpcResponse SetAudioMonitorType(const RpcRequest&);
RpcResponse TakeSourceScreenshot(const RpcRequest&);
RpcResponse GetSourceFilters(const RpcRequest&);
RpcResponse GetSourceFilterInfo(const RpcRequest&);
RpcResponse AddFilterToSource(const RpcRequest&);
RpcResponse RemoveFilterFromSource(const RpcRequest&);
RpcResponse ReorderSourceFilter(const RpcRequest&);
RpcResponse MoveSourceFilter(const RpcRequest&);
RpcResponse SetSourceFilterSettings(const RpcRequest&);
RpcResponse SetSourceFilterVisibility(const RpcRequest&);
RpcResponse SetCurrentSceneCollection(const RpcRequest&);
RpcResponse GetCurrentSceneCollection(const RpcRequest&);
RpcResponse ListSceneCollections(const RpcRequest&);
RpcResponse SetCurrentProfile(const RpcRequest&);
RpcResponse GetCurrentProfile(const RpcRequest&);
RpcResponse ListProfiles(const RpcRequest&);
RpcResponse SetStreamSettings(const RpcRequest&);
RpcResponse GetStreamSettings(const RpcRequest&);
RpcResponse SaveStreamSettings(const RpcRequest&);
#if BUILD_CAPTIONS
RpcResponse SendCaptions(const RpcRequest&);
#endif
RpcResponse GetStudioModeStatus(const RpcRequest&);
RpcResponse GetPreviewScene(const RpcRequest&);
RpcResponse SetPreviewScene(const RpcRequest&);
RpcResponse TransitionToProgram(const RpcRequest&);
RpcResponse EnableStudioMode(const RpcRequest&);
RpcResponse DisableStudioMode(const RpcRequest&);
RpcResponse ToggleStudioMode(const RpcRequest&);
RpcResponse SetTextGDIPlusProperties(const RpcRequest&);
RpcResponse GetTextGDIPlusProperties(const RpcRequest&);
RpcResponse SetTextFreetype2Properties(const RpcRequest&);
RpcResponse GetTextFreetype2Properties(const RpcRequest&);
RpcResponse SetBrowserSourceProperties(const RpcRequest&);
RpcResponse GetBrowserSourceProperties(const RpcRequest&);
RpcResponse ListOutputs(const RpcRequest&);
RpcResponse GetOutputInfo(const RpcRequest&);
RpcResponse StartOutput(const RpcRequest&);
RpcResponse StopOutput(const RpcRequest&);
};

View File

@ -0,0 +1,346 @@
#include "WSRequestHandler.h"
#include <QtCore/QByteArray>
#include <QtGui/QImageWriter>
#include "obs-websocket.h"
#include "Config.h"
#include "Utils.h"
#include "WSEvents.h"
#define CASE(x) case x: return #x;
const char *describe_output_format(int format) {
switch (format) {
default:
CASE(VIDEO_FORMAT_NONE)
CASE(VIDEO_FORMAT_I420)
CASE(VIDEO_FORMAT_NV12)
CASE(VIDEO_FORMAT_YVYU)
CASE(VIDEO_FORMAT_YUY2)
CASE(VIDEO_FORMAT_UYVY)
CASE(VIDEO_FORMAT_RGBA)
CASE(VIDEO_FORMAT_BGRA)
CASE(VIDEO_FORMAT_BGRX)
CASE(VIDEO_FORMAT_Y800)
CASE(VIDEO_FORMAT_I444)
}
}
const char *describe_color_space(int cs) {
switch (cs) {
default:
CASE(VIDEO_CS_DEFAULT)
CASE(VIDEO_CS_601)
CASE(VIDEO_CS_709)
}
}
const char *describe_color_range(int range) {
switch (range) {
default:
CASE(VIDEO_RANGE_DEFAULT)
CASE(VIDEO_RANGE_PARTIAL)
CASE(VIDEO_RANGE_FULL)
}
}
const char *describe_scale_type(int scale) {
switch (scale) {
default:
CASE(VIDEO_SCALE_DEFAULT)
CASE(VIDEO_SCALE_POINT)
CASE(VIDEO_SCALE_FAST_BILINEAR)
CASE(VIDEO_SCALE_BILINEAR)
CASE(VIDEO_SCALE_BICUBIC)
}
}
#undef CASE
/**
* Returns the latest version of the plugin and the API.
*
* @return {double} `version` OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility.
* @return {String} `obs-websocket-version` obs-websocket plugin version.
* @return {String} `obs-studio-version` OBS Studio program version.
* @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3").
* @return {String} `supported-image-export-formats` List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string
*
* @api requests
* @name GetVersion
* @category general
* @since 0.3
*/
RpcResponse WSRequestHandler::GetVersion(const RpcRequest& request) {
QString obsVersion = Utils::OBSVersionString();
QList<QString> names = messageMap.keys();
QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats();
// (Palakis) OBS' data arrays only support object arrays, so I improvised.
QString requests;
names.sort(Qt::CaseInsensitive);
requests += names.takeFirst();
for (const QString& reqName : names) {
requests += ("," + reqName);
}
QString supportedImageExportFormats;
supportedImageExportFormats += QString::fromUtf8(imageWriterFormats.takeFirst());
for (const QByteArray& format : imageWriterFormats) {
supportedImageExportFormats += ("," + QString::fromUtf8(format));
}
OBSDataAutoRelease data = obs_data_create();
obs_data_set_double(data, "version", 1.1);
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8());
obs_data_set_string(data, "available-requests", requests.toUtf8());
obs_data_set_string(data, "supported-image-export-formats", supportedImageExportFormats.toUtf8());
return request.success(data);
}
/**
* Tells the client if authentication is required. If so, returns authentication parameters `challenge`
* and `salt` (see "Authentication" for more information).
*
* @return {boolean} `authRequired` Indicates whether authentication is required.
* @return {String (optional)} `challenge`
* @return {String (optional)} `salt`
*
* @api requests
* @name GetAuthRequired
* @category general
* @since 0.3
*/
RpcResponse WSRequestHandler::GetAuthRequired(const RpcRequest& request) {
bool authRequired = GetConfig()->AuthRequired;
OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "authRequired", authRequired);
if (authRequired) {
auto config = GetConfig();
obs_data_set_string(data, "challenge",
config->SessionChallenge.toUtf8());
obs_data_set_string(data, "salt",
config->Salt.toUtf8());
}
return request.success(data);
}
/**
* Attempt to authenticate the client to the server.
*
* @param {String} `auth` Response to the auth challenge (see "Authentication" for more information).
*
* @api requests
* @name Authenticate
* @category general
* @since 0.3
*/
RpcResponse WSRequestHandler::Authenticate(const RpcRequest& request) {
if (!request.hasField("auth")) {
return request.failed("missing request parameters");
}
if (_connProperties.isAuthenticated()) {
return request.failed("already authenticated");
}
QString auth = obs_data_get_string(request.parameters(), "auth");
if (auth.isEmpty()) {
return request.failed("auth not specified!");
}
if (GetConfig()->CheckAuth(auth) == false) {
return request.failed("Authentication Failed.");
}
_connProperties.setAuthenticated(true);
return request.success();
}
/**
* Enable/disable sending of the Heartbeat event
*
* @param {boolean} `enable` Starts/Stops emitting heartbeat messages
*
* @api requests
* @name SetHeartbeat
* @category general
* @since 4.3.0
*/
RpcResponse WSRequestHandler::SetHeartbeat(const RpcRequest& request) {
if (!request.hasField("enable")) {
return request.failed("Heartbeat <enable> parameter missing");
}
auto events = GetEventsSystem();
events->HeartbeatIsActive = obs_data_get_bool(request.parameters(), "enable");
OBSDataAutoRelease response = obs_data_create();
obs_data_set_bool(response, "enable", events->HeartbeatIsActive);
return request.success(response);
}
/**
* Set the filename formatting string
*
* @param {String} `filename-formatting` Filename formatting string to set.
*
* @api requests
* @name SetFilenameFormatting
* @category general
* @since 4.3.0
*/
RpcResponse WSRequestHandler::SetFilenameFormatting(const RpcRequest& request) {
if (!request.hasField("filename-formatting")) {
return request.failed("<filename-formatting> parameter missing");
}
QString filenameFormatting = obs_data_get_string(request.parameters(), "filename-formatting");
if (filenameFormatting.isEmpty()) {
return request.failed("invalid request parameters");
}
Utils::SetFilenameFormatting(filenameFormatting.toUtf8());
return request.success();
}
/**
* Get the filename formatting string
*
* @return {String} `filename-formatting` Current filename formatting string.
*
* @api requests
* @name GetFilenameFormatting
* @category general
* @since 4.3.0
*/
RpcResponse WSRequestHandler::GetFilenameFormatting(const RpcRequest& request) {
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "filename-formatting", Utils::GetFilenameFormatting());
return request.success(response);
}
/**
* Get OBS stats (almost the same info as provided in OBS' stats window)
*
* @return {OBSStats} `stats` OBS stats
*
* @api requests
* @name GetStats
* @category general
* @since 4.6.0
*/
RpcResponse WSRequestHandler::GetStats(const RpcRequest& request) {
OBSDataAutoRelease stats = GetEventsSystem()->GetStats();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_obj(response, "stats", stats);
return request.success(response);
}
/**
* Broadcast custom message to all connected WebSocket clients
*
* @param {String} `realm` Identifier to be choosen by the client
* @param {Object} `data` User-defined data
*
* @api requests
* @name BroadcastCustomMessage
* @category general
* @since 4.7.0
*/
RpcResponse WSRequestHandler::BroadcastCustomMessage(const RpcRequest& request) {
if (!request.hasField("realm") || !request.hasField("data")) {
return request.failed("missing request parameters");
}
QString realm = obs_data_get_string(request.parameters(), "realm");
OBSDataAutoRelease data = obs_data_get_obj(request.parameters(), "data");
if (realm.isEmpty()) {
return request.failed("realm not specified!");
}
if (!data) {
return request.failed("data not specified!");
}
auto events = GetEventsSystem();
events->OnBroadcastCustomMessage(realm, data);
return request.success();
}
/**
* Get basic OBS video information
*
* @return {int} `baseWidth` Base (canvas) width
* @return {int} `baseHeight` Base (canvas) height
* @return {int} `outputWidth` Output width
* @return {int} `outputHeight` Output height
* @return {String} `scaleType` Scaling method used if output size differs from base size
* @return {double} `fps` Frames rendered per second
* @return {String} `videoFormat` Video color format
* @return {String} `colorSpace` Color space for YUV
* @return {String} `colorRange` Color range (full or partial)
*
* @api requests
* @name GetVideoInfo
* @category general
* @since 4.6.0
*/
RpcResponse WSRequestHandler::GetVideoInfo(const RpcRequest& request) {
obs_video_info ovi;
obs_get_video_info(&ovi);
OBSDataAutoRelease response = obs_data_create();
obs_data_set_int(response, "baseWidth", ovi.base_width);
obs_data_set_int(response, "baseHeight", ovi.base_height);
obs_data_set_int(response, "outputWidth", ovi.output_width);
obs_data_set_int(response, "outputHeight", ovi.output_height);
obs_data_set_double(response, "fps", (double)ovi.fps_num / ovi.fps_den);
obs_data_set_string(response, "videoFormat", describe_output_format(ovi.output_format));
obs_data_set_string(response, "colorSpace", describe_color_space(ovi.colorspace));
obs_data_set_string(response, "colorRange", describe_color_range(ovi.range));
obs_data_set_string(response, "scaleType", describe_scale_type(ovi.scale_type));
return request.success(response);
}
/**
* Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.
*
* @param {String (Optional)} `type` Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive).
* @param {int (Optional)} `monitor` Monitor to open the projector on. If -1 or omitted, opens a window.
* @param {String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors.
* @param {String (Optional)} `name` Name of the source or scene to be displayed (ignored for other projector types).
*
* @api requests
* @name OpenProjector
* @category general
* @since 4.8.0
*/
RpcResponse WSRequestHandler::OpenProjector(const RpcRequest& request) {
const char* type = obs_data_get_string(request.parameters(), "type");
int monitor = -1;
if (request.hasField("monitor")) {
monitor = obs_data_get_int(request.parameters(), "monitor");
}
const char* geometry = obs_data_get_string(request.parameters(), "geometry");
const char* name = obs_data_get_string(request.parameters(), "name");
obs_frontend_open_projector(type, monitor, geometry, name);
return request.success();
}

View File

@ -0,0 +1,184 @@
#include <functional>
#include "WSRequestHandler.h"
/**
* @typedef {Object} `Output`
* @property {String} `name` Output name
* @property {String} `type` Output type/kind
* @property {int} `width` Video output width
* @property {int} `height` Video output height
* @property {Object} `flags` Output flags
* @property {int} `flags.rawValue` Raw flags value
* @property {boolean} `flags.audio` Output uses audio
* @property {boolean} `flags.video` Output uses video
* @property {boolean} `flags.encoded` Output is encoded
* @property {boolean} `flags.multiTrack` Output uses several audio tracks
* @property {boolean} `flags.service` Output uses a service
* @property {Object} `settings` Output name
* @property {boolean} `active` Output status (active or not)
* @property {boolean} `reconnecting` Output reconnection status (reconnecting or not)
* @property {double} `congestion` Output congestion
* @property {int} `totalFrames` Number of frames sent
* @property {int} `droppedFrames` Number of frames dropped
* @property {int} `totalBytes` Total bytes sent
*/
obs_data_t* getOutputInfo(obs_output_t* output)
{
if (!output) {
return nullptr;
}
OBSDataAutoRelease settings = obs_output_get_settings(output);
uint32_t rawFlags = obs_output_get_flags(output);
OBSDataAutoRelease flags = obs_data_create();
obs_data_set_int(flags, "rawValue", rawFlags);
obs_data_set_bool(flags, "audio", rawFlags & OBS_OUTPUT_AUDIO);
obs_data_set_bool(flags, "video", rawFlags & OBS_OUTPUT_VIDEO);
obs_data_set_bool(flags, "encoded", rawFlags & OBS_OUTPUT_ENCODED);
obs_data_set_bool(flags, "multiTrack", rawFlags & OBS_OUTPUT_MULTI_TRACK);
obs_data_set_bool(flags, "service", rawFlags & OBS_OUTPUT_SERVICE);
obs_data_t* data = obs_data_create();
obs_data_set_string(data, "name", obs_output_get_name(output));
obs_data_set_string(data, "type", obs_output_get_id(output));
obs_data_set_int(data, "width", obs_output_get_width(output));
obs_data_set_int(data, "height", obs_output_get_height(output));
obs_data_set_obj(data, "flags", flags);
obs_data_set_obj(data, "settings", settings);
obs_data_set_bool(data, "active", obs_output_active(output));
obs_data_set_bool(data, "reconnecting", obs_output_reconnecting(output));
obs_data_set_double(data, "congestion", obs_output_get_congestion(output));
obs_data_set_int(data, "totalFrames", obs_output_get_total_frames(output));
obs_data_set_int(data, "droppedFrames", obs_output_get_frames_dropped(output));
obs_data_set_int(data, "totalBytes", obs_output_get_total_bytes(output));
return data;
}
RpcResponse findOutputOrFail(const RpcRequest& request, std::function<RpcResponse (obs_output_t*)> callback)
{
if (!request.hasField("outputName")) {
return request.failed("missing request parameters");
}
const char* outputName = obs_data_get_string(request.parameters(), "outputName");
OBSOutputAutoRelease output = obs_get_output_by_name(outputName);
if (!output) {
return request.failed("specified output doesn't exist");
}
return callback(output);
}
/**
* List existing outputs
*
* @return {Array<Output>} `outputs` Outputs list
*
* @api requests
* @name ListOutputs
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::ListOutputs(const RpcRequest& request)
{
OBSDataArrayAutoRelease outputs = obs_data_array_create();
obs_enum_outputs([](void* param, obs_output_t* output) {
obs_data_array_t* outputs = reinterpret_cast<obs_data_array_t*>(param);
OBSDataAutoRelease outputInfo = getOutputInfo(output);
obs_data_array_push_back(outputs, outputInfo);
return true;
}, outputs);
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_array(fields, "outputs", outputs);
return request.success(fields);
}
/**
* Get information about a single output
*
* @param {String} `outputName` Output name
*
* @return {Output} `outputInfo` Output info
*
* @api requests
* @name GetOutputInfo
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::GetOutputInfo(const RpcRequest& request)
{
return findOutputOrFail(request, [request](obs_output_t* output) {
OBSDataAutoRelease outputInfo = getOutputInfo(output);
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_obj(fields, "outputInfo", outputInfo);
return request.success(fields);
});
}
/**
* Start an output
*
* @param {String} `outputName` Output name
*
* @api requests
* @name StartOutput
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::StartOutput(const RpcRequest& request)
{
return findOutputOrFail(request, [request](obs_output_t* output) {
if (obs_output_active(output)) {
return request.failed("output already active");
}
bool success = obs_output_start(output);
if (!success) {
QString lastError = obs_output_get_last_error(output);
QString errorMessage = QString("output start failed: %1").arg(lastError);
return request.failed(errorMessage);
}
return request.success();
});
}
/**
* Stop an output
*
* @param {String} `outputName` Output name
* @param {boolean (optional)} `force` Force stop (default: false)
*
* @api requests
* @name StopOutput
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::StopOutput(const RpcRequest& request)
{
return findOutputOrFail(request, [request](obs_output_t* output) {
if (!obs_output_active(output)) {
return request.failed("output not active");
}
bool forceStop = obs_data_get_bool(request.parameters(), "force");
if (forceStop) {
obs_output_force_stop(output);
} else {
obs_output_stop(output);
}
return request.success();
});
}

View File

@ -0,0 +1,67 @@
#include "Utils.h"
#include "WSRequestHandler.h"
/**
* Set the currently active profile.
*
* @param {String} `profile-name` Name of the desired profile.
*
* @api requests
* @name SetCurrentProfile
* @category profiles
* @since 4.0.0
*/
RpcResponse WSRequestHandler::SetCurrentProfile(const RpcRequest& request) {
if (!request.hasField("profile-name")) {
return request.failed("missing request parameters");
}
QString profileName = obs_data_get_string(request.parameters(), "profile-name");
if (profileName.isEmpty()) {
return request.failed("invalid request parameters");
}
// TODO : check if profile exists
obs_frontend_set_current_profile(profileName.toUtf8());
return request.success();
}
/**
* Get the name of the current profile.
*
* @return {String} `profile-name` Name of the currently active profile.
*
* @api requests
* @name GetCurrentProfile
* @category profiles
* @since 4.0.0
*/
RpcResponse WSRequestHandler::GetCurrentProfile(const RpcRequest& request) {
OBSDataAutoRelease response = obs_data_create();
char* currentProfile = obs_frontend_get_current_profile();
obs_data_set_string(response, "profile-name", currentProfile);
bfree(currentProfile);
return request.success(response);
}
/**
* Get a list of available profiles.
*
* @return {Array<Object>} `profiles` List of available profiles.
*
* @api requests
* @name ListProfiles
* @category profiles
* @since 4.0.0
*/
RpcResponse WSRequestHandler::ListProfiles(const RpcRequest& request) {
char** profiles = obs_frontend_get_profiles();
OBSDataArrayAutoRelease list = Utils::StringListToArray(profiles, "profile-name");
bfree(profiles);
OBSDataAutoRelease response = obs_data_create();
obs_data_set_array(response, "profiles", list);
return request.success(response);
}

View File

@ -0,0 +1,151 @@
#include "WSRequestHandler.h"
#include <functional>
#include <util/platform.h>
#include "Utils.h"
RpcResponse ifCanPause(const RpcRequest& request, std::function<RpcResponse()> callback)
{
if (!obs_frontend_recording_active()) {
return request.failed("recording is not active");
}
return callback();
}
/**
* Toggle recording on or off.
*
* @api requests
* @name StartStopRecording
* @category recording
* @since 0.3
*/
RpcResponse WSRequestHandler::StartStopRecording(const RpcRequest& request) {
(obs_frontend_recording_active() ? obs_frontend_recording_stop() : obs_frontend_recording_start());
return request.success();
}
/**
* Start recording.
* Will return an `error` if recording is already active.
*
* @api requests
* @name StartRecording
* @category recording
* @since 4.1.0
*/
RpcResponse WSRequestHandler::StartRecording(const RpcRequest& request) {
if (obs_frontend_recording_active()) {
return request.failed("recording already active");
}
obs_frontend_recording_start();
return request.success();
}
/**
* Stop recording.
* Will return an `error` if recording is not active.
*
* @api requests
* @name StopRecording
* @category recording
* @since 4.1.0
*/
RpcResponse WSRequestHandler::StopRecording(const RpcRequest& request) {
if (!obs_frontend_recording_active()) {
return request.failed("recording not active");
}
obs_frontend_recording_stop();
return request.success();
}
/**
* Pause the current recording.
* Returns an error if recording is not active or already paused.
*
* @api requests
* @name PauseRecording
* @category recording
* @since 4.7.0
*/
RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) {
return ifCanPause(request, [request]() {
if (obs_frontend_recording_paused()) {
return request.failed("recording already paused");
}
obs_frontend_recording_pause(true);
return request.success();
});
}
/**
* Resume/unpause the current recording (if paused).
* Returns an error if recording is not active or not paused.
*
* @api requests
* @name ResumeRecording
* @category recording
* @since 4.7.0
*/
RpcResponse WSRequestHandler::ResumeRecording(const RpcRequest& request) {
return ifCanPause(request, [request]() {
if (!obs_frontend_recording_paused()) {
return request.failed("recording is not paused");
}
obs_frontend_recording_pause(false);
return request.success();
});
}
/**
* In the current profile, sets the recording folder of the Simple and Advanced
* output modes to the specified value.
*
* Please note: if `SetRecordingFolder` is called while a recording is
* in progress, the change won't be applied immediately and will be
* effective on the next recording.
*
* @param {String} `rec-folder` Path of the recording folder.
*
* @api requests
* @name SetRecordingFolder
* @category recording
* @since 4.1.0
*/
RpcResponse WSRequestHandler::SetRecordingFolder(const RpcRequest& request) {
if (!request.hasField("rec-folder")) {
return request.failed("missing request parameters");
}
const char* newRecFolder = obs_data_get_string(request.parameters(), "rec-folder");
bool success = Utils::SetRecordingFolder(newRecFolder);
if (!success) {
return request.failed("invalid request parameters");
}
return request.success();
}
/**
* Get the path of the current recording folder.
*
* @return {String} `rec-folder` Path of the recording folder.
*
* @api requests
* @name GetRecordingFolder
* @category recording
* @since 4.1.0
*/
RpcResponse WSRequestHandler::GetRecordingFolder(const RpcRequest& request) {
const char* recFolder = Utils::GetRecordingFolder();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "rec-folder", recFolder);
return request.success(response);
}

View File

@ -0,0 +1,88 @@
#include "Utils.h"
#include "WSRequestHandler.h"
/**
* Toggle the Replay Buffer on/off.
*
* @api requests
* @name StartStopReplayBuffer
* @category replay buffer
* @since 4.2.0
*/
RpcResponse WSRequestHandler::StartStopReplayBuffer(const RpcRequest& request) {
if (obs_frontend_replay_buffer_active()) {
obs_frontend_replay_buffer_stop();
} else {
Utils::StartReplayBuffer();
}
return request.success();
}
/**
* Start recording into the Replay Buffer.
* Will return an `error` if the Replay Buffer is already active or if the
* "Save Replay Buffer" hotkey is not set in OBS' settings.
* Setting this hotkey is mandatory, even when triggering saves only
* through obs-websocket.
*
* @api requests
* @name StartReplayBuffer
* @category replay buffer
* @since 4.2.0
*/
RpcResponse WSRequestHandler::StartReplayBuffer(const RpcRequest& request) {
if (!Utils::ReplayBufferEnabled()) {
return request.failed("replay buffer disabled in settings");
}
if (obs_frontend_replay_buffer_active() == true) {
return request.failed("replay buffer already active");
}
Utils::StartReplayBuffer();
return request.success();
}
/**
* Stop recording into the Replay Buffer.
* Will return an `error` if the Replay Buffer is not active.
*
* @api requests
* @name StopReplayBuffer
* @category replay buffer
* @since 4.2.0
*/
RpcResponse WSRequestHandler::StopReplayBuffer(const RpcRequest& request) {
if (obs_frontend_replay_buffer_active() == true) {
obs_frontend_replay_buffer_stop();
return request.success();
} else {
return request.failed("replay buffer not active");
}
}
/**
* Flush and save the contents of the Replay Buffer to disk. This is
* basically the same as triggering the "Save Replay Buffer" hotkey.
* Will return an `error` if the Replay Buffer is not active.
*
* @api requests
* @name SaveReplayBuffer
* @category replay buffer
* @since 4.2.0
*/
RpcResponse WSRequestHandler::SaveReplayBuffer(const RpcRequest& request) {
if (!obs_frontend_replay_buffer_active()) {
return request.failed("replay buffer not active");
}
OBSOutputAutoRelease replayOutput = obs_frontend_get_replay_buffer_output();
calldata_t cd = { 0 };
proc_handler_t* ph = obs_output_get_proc_handler(replayOutput);
proc_handler_call(ph, "save", &cd);
calldata_free(&cd);
return request.success();
}

View File

@ -0,0 +1,70 @@
#include "Utils.h"
#include "WSRequestHandler.h"
/**
* Change the active scene collection.
*
* @param {String} `sc-name` Name of the desired scene collection.
*
* @api requests
* @name SetCurrentSceneCollection
* @category scene collections
* @since 4.0.0
*/
RpcResponse WSRequestHandler::SetCurrentSceneCollection(const RpcRequest& request) {
if (!request.hasField("sc-name")) {
return request.failed("missing request parameters");
}
QString sceneCollection = obs_data_get_string(request.parameters(), "sc-name");
if (sceneCollection.isEmpty()) {
return request.failed("invalid request parameters");
}
// TODO : Check if specified profile exists and if changing is allowed
obs_frontend_set_current_scene_collection(sceneCollection.toUtf8());
return request.success();
}
/**
* Get the name of the current scene collection.
*
* @return {String} `sc-name` Name of the currently active scene collection.
*
* @api requests
* @name GetCurrentSceneCollection
* @category scene collections
* @since 4.0.0
*/
RpcResponse WSRequestHandler::GetCurrentSceneCollection(const RpcRequest& request) {
OBSDataAutoRelease response = obs_data_create();
char* sceneCollection = obs_frontend_get_current_scene_collection();
obs_data_set_string(response, "sc-name", sceneCollection);
bfree(sceneCollection);
return request.success(response);
}
/**
* List available scene collections
*
* @return {Array<String>} `scene-collections` Scene collections list
*
* @api requests
* @name ListSceneCollections
* @category scene collections
* @since 4.0.0
*/
RpcResponse WSRequestHandler::ListSceneCollections(const RpcRequest& request) {
char** sceneCollections = obs_frontend_get_scene_collections();
OBSDataArrayAutoRelease list =
Utils::StringListToArray(sceneCollections, "sc-name");
bfree(sceneCollections);
OBSDataAutoRelease response = obs_data_create();
obs_data_set_array(response, "scene-collections", list);
return request.success(response);
}

View File

@ -0,0 +1,621 @@
#include "Utils.h"
#include "WSRequestHandler.h"
/**
* Gets the scene specific properties of the specified source item.
* Coordinates are relative to the item's parent (the scene or group it belongs to).
*
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
*
* @return {String} `name` Scene Item name.
* @return {int} `itemId` Scene Item ID.
* @return {int} `position.x` The x position of the source from the left.
* @return {int} `position.y` The y position of the source from the top.
* @return {int} `position.alignment` The point on the source that the item is manipulated from.
* @return {double} `rotation` The clockwise rotation of the item in degrees around the point of alignment.
* @return {double} `scale.x` The x-scale factor of the source.
* @return {double} `scale.y` The y-scale factor of the source.
* @return {int} `crop.top` The number of pixels cropped off the top of the source before scaling.
* @return {int} `crop.right` The number of pixels cropped off the right of the source before scaling.
* @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.
* @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling.
* @return {bool} `visible` If the source is visible.
* @return {bool} `muted` If the source is muted.
* @return {bool} `locked` If the source's transform is locked.
* @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
* @return {int} `bounds.alignment` Alignment of the bounding box.
* @return {double} `bounds.x` Width of the bounding box.
* @return {double} `bounds.y` Height of the bounding box.
* @return {int} `sourceWidth` Base width (without scaling) of the source
* @return {int} `sourceHeight` Base source (without scaling) of the source
* @return {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
* @return {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
* @return {int} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
* @return {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
* @return {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
*
* @api requests
* @name GetSceneItemProperties
* @category scene items
* @since 4.3.0
*/
RpcResponse WSRequestHandler::GetSceneItemProperties(const RpcRequest& request) {
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
OBSData params = request.parameters();
QString sceneName = obs_data_get_string(params, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return request.failed("requested scene doesn't exist");
}
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
if (!sceneItem) {
return request.failed("specified scene item doesn't exist");
}
OBSDataAutoRelease data = Utils::GetSceneItemPropertiesData(sceneItem);
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
obs_data_set_string(data, "name", obs_source_get_name(sceneItemSource));
obs_data_set_int(data, "itemId", obs_sceneitem_get_id(sceneItem));
return request.success(data);
}
/**
* Sets the scene specific properties of a source. Unspecified properties will remain unchanged.
* Coordinates are relative to the item's parent (the scene or group it belongs to).
*
* @param {String (optional)} `scene-name` Name of the scene the source item belongs to. Defaults to the current scene.
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
* @param {int (optional)} `position.x` The new x position of the source.
* @param {int (optional)} `position.y` The new y position of the source.
* @param {int (optional)} `position.alignment` The new alignment of the source.
* @param {double (optional)} `rotation` The new clockwise rotation of the item in degrees.
* @param {double (optional)} `scale.x` The new x scale of the item.
* @param {double (optional)} `scale.y` The new y scale of the item.
* @param {int (optional)} `crop.top` The new amount of pixels cropped off the top of the source before scaling.
* @param {int (optional)} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling.
* @param {int (optional)} `crop.left` The new amount of pixels cropped off the left of the source before scaling.
* @param {int (optional)} `crop.right` The new amount of pixels cropped off the right of the source before scaling.
* @param {bool (optional)} `visible` The new visibility of the source. 'true' shows source, 'false' hides source.
* @param {bool (optional)} `locked` The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement.
* @param {String (optional)} `bounds.type` The new bounds type of the source. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
* @param {int (optional)} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10)
* @param {double (optional)} `bounds.x` The new width of the bounding box.
* @param {double (optional)} `bounds.y` The new height of the bounding box.
*
* @api requests
* @name SetSceneItemProperties
* @category scene items
* @since 4.3.0
*/
RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) {
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
OBSData params = request.parameters();
QString sceneName = obs_data_get_string(params, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return request.failed("requested scene doesn't exist");
}
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
if (!sceneItem) {
return request.failed("specified scene item doesn't exist");
}
bool badRequest = false;
OBSDataAutoRelease errorData = obs_data_create();
obs_sceneitem_defer_update_begin(sceneItem);
if (request.hasField("position")) {
vec2 oldPosition;
OBSDataAutoRelease positionError = obs_data_create();
obs_sceneitem_get_pos(sceneItem, &oldPosition);
OBSDataAutoRelease reqPosition = obs_data_get_obj(params, "position");
vec2 newPosition = oldPosition;
if (obs_data_has_user_value(reqPosition, "x")) {
newPosition.x = obs_data_get_int(reqPosition, "x");
}
if (obs_data_has_user_value(reqPosition, "y")) {
newPosition.y = obs_data_get_int(reqPosition, "y");
}
if (obs_data_has_user_value(reqPosition, "alignment")) {
const uint32_t alignment = obs_data_get_int(reqPosition, "alignment");
if (Utils::IsValidAlignment(alignment)) {
obs_sceneitem_set_alignment(sceneItem, alignment);
} else {
badRequest = true;
obs_data_set_string(positionError, "alignment", "invalid");
obs_data_set_obj(errorData, "position", positionError);
}
}
obs_sceneitem_set_pos(sceneItem, &newPosition);
}
if (request.hasField("rotation")) {
obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(params, "rotation"));
}
if (request.hasField("scale")) {
vec2 oldScale;
obs_sceneitem_get_scale(sceneItem, &oldScale);
vec2 newScale = oldScale;
OBSDataAutoRelease reqScale = obs_data_get_obj(params, "scale");
if (obs_data_has_user_value(reqScale, "x")) {
newScale.x = obs_data_get_double(reqScale, "x");
}
if (obs_data_has_user_value(reqScale, "y")) {
newScale.y = obs_data_get_double(reqScale, "y");
}
obs_sceneitem_set_scale(sceneItem, &newScale);
}
if (request.hasField("crop")) {
obs_sceneitem_crop oldCrop;
obs_sceneitem_get_crop(sceneItem, &oldCrop);
OBSDataAutoRelease reqCrop = obs_data_get_obj(params, "crop");
obs_sceneitem_crop newCrop = oldCrop;
if (obs_data_has_user_value(reqCrop, "top")) {
newCrop.top = obs_data_get_int(reqCrop, "top");
}
if (obs_data_has_user_value(reqCrop, "right")) {
newCrop.right = obs_data_get_int(reqCrop, "right");
}
if (obs_data_has_user_value(reqCrop, "bottom")) {
newCrop.bottom = obs_data_get_int(reqCrop, "bottom");
}
if (obs_data_has_user_value(reqCrop, "left")) {
newCrop.left = obs_data_get_int(reqCrop, "left");
}
obs_sceneitem_set_crop(sceneItem, &newCrop);
}
if (request.hasField("visible")) {
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(params, "visible"));
}
if (request.hasField("locked")) {
obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(params, "locked"));
}
if (request.hasField("bounds")) {
bool badBounds = false;
OBSDataAutoRelease boundsError = obs_data_create();
OBSDataAutoRelease reqBounds = obs_data_get_obj(params, "bounds");
if (obs_data_has_user_value(reqBounds, "type")) {
QString newBoundsType = obs_data_get_string(reqBounds, "type");
if (newBoundsType == "OBS_BOUNDS_NONE") {
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_NONE);
}
else if (newBoundsType == "OBS_BOUNDS_STRETCH") {
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_STRETCH);
}
else if (newBoundsType == "OBS_BOUNDS_SCALE_INNER") {
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_INNER);
}
else if (newBoundsType == "OBS_BOUNDS_SCALE_OUTER") {
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_OUTER);
}
else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_WIDTH") {
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_WIDTH);
}
else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_HEIGHT") {
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_HEIGHT);
}
else if (newBoundsType == "OBS_BOUNDS_MAX_ONLY") {
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_MAX_ONLY);
}
else {
badRequest = badBounds = true;
obs_data_set_string(boundsError, "type", "invalid");
}
}
vec2 oldBounds;
obs_sceneitem_get_bounds(sceneItem, &oldBounds);
vec2 newBounds = oldBounds;
if (obs_data_has_user_value(reqBounds, "x")) {
newBounds.x = obs_data_get_double(reqBounds, "x");
}
if (obs_data_has_user_value(reqBounds, "y")) {
newBounds.y = obs_data_get_double(reqBounds, "y");
}
obs_sceneitem_set_bounds(sceneItem, &newBounds);
if (obs_data_has_user_value(reqBounds, "alignment")) {
const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment");
if (Utils::IsValidAlignment(bounds_alignment)) {
obs_sceneitem_set_bounds_alignment(sceneItem, bounds_alignment);
}
else {
badRequest = badBounds = true;
obs_data_set_string(boundsError, "alignment", "invalid");
}
}
if (badBounds) {
obs_data_set_obj(errorData, "bounds", boundsError);
}
}
obs_sceneitem_defer_update_end(sceneItem);
if (badRequest) {
return request.failed("error", errorData);
}
return request.success();
}
/**
* Reset a scene item.
*
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
*
* @api requests
* @name ResetSceneItem
* @category scene items
* @since 4.2.0
*/
RpcResponse WSRequestHandler::ResetSceneItem(const RpcRequest& request) {
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
OBSData params = request.parameters();
const char* sceneName = obs_data_get_string(params, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return request.failed("requested scene doesn't exist");
}
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
if (!sceneItem) {
return request.failed("specified scene item doesn't exist");
}
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource);
obs_source_update(sceneItemSource, settings);
return request.success();
}
/**
* Show or hide a specified source item in a specified scene.
*
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the currently active scene.
* @param {String} `source` Scene Item name.
* @param {boolean} `render` true = shown ; false = hidden
*
* @api requests
* @name SetSceneItemRender
* @category scene items
* @since 0.3
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/
RpcResponse WSRequestHandler::SetSceneItemRender(const RpcRequest& request) {
if (!request.hasField("source") ||
!request.hasField("render"))
{
return request.failed("missing request parameters");
}
const char* itemName = obs_data_get_string(request.parameters(), "source");
bool isVisible = obs_data_get_bool(request.parameters(), "render");
if (!itemName) {
return request.failed("invalid request parameters");
}
const char* sceneName = obs_data_get_string(request.parameters(), "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return request.failed("requested scene doesn't exist");
}
OBSSceneItemAutoRelease sceneItem =
Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) {
return request.failed("specified scene item doesn't exist");
}
obs_sceneitem_set_visible(sceneItem, isVisible);
return request.success();
}
/**
* Sets the coordinates of a specified source item.
*
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {String} `item` Scene Item name.
* @param {double} `x` X coordinate.
* @param {double} `y` Y coordinate.
*
* @api requests
* @name SetSceneItemPosition
* @category scene items
* @since 4.0.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/
RpcResponse WSRequestHandler::SetSceneItemPosition(const RpcRequest& request) {
if (!request.hasField("item") ||
!request.hasField("x") || !request.hasField("y")) {
return request.failed("missing request parameters");
}
QString itemName = obs_data_get_string(request.parameters(), "item");
if (itemName.isEmpty()) {
return request.failed("invalid request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return request.failed("requested scene could not be found");
}
OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) {
return request.failed("specified scene item doesn't exist");
}
vec2 item_position = { 0 };
item_position.x = obs_data_get_double(request.parameters(), "x");
item_position.y = obs_data_get_double(request.parameters(), "y");
obs_sceneitem_set_pos(sceneItem, &item_position);
return request.success();
}
/**
* Set the transform of the specified source item.
*
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {String} `item` Scene Item name.
* @param {double} `x-scale` Width scale factor.
* @param {double} `y-scale` Height scale factor.
* @param {double} `rotation` Source item rotation (in degrees).
*
* @api requests
* @name SetSceneItemTransform
* @category scene items
* @since 4.0.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/
RpcResponse WSRequestHandler::SetSceneItemTransform(const RpcRequest& request) {
if (!request.hasField("item") ||
!request.hasField("x-scale") ||
!request.hasField("y-scale") ||
!request.hasField("rotation"))
{
return request.failed("missing request parameters");
}
QString itemName = obs_data_get_string(request.parameters(), "item");
if (itemName.isEmpty()) {
return request.failed("invalid request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return request.failed("requested scene doesn't exist");
}
vec2 scale;
scale.x = obs_data_get_double(request.parameters(), "x-scale");
scale.y = obs_data_get_double(request.parameters(), "y-scale");
float rotation = obs_data_get_double(request.parameters(), "rotation");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) {
return request.failed("specified scene item doesn't exist");
}
obs_sceneitem_defer_update_begin(sceneItem);
obs_sceneitem_set_scale(sceneItem, &scale);
obs_sceneitem_set_rot(sceneItem, rotation);
obs_sceneitem_defer_update_end(sceneItem);
return request.success();
}
/**
* Sets the crop coordinates of the specified source item.
*
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {String} `item` Scene Item name.
* @param {int} `top` Pixel position of the top of the source item.
* @param {int} `bottom` Pixel position of the bottom of the source item.
* @param {int} `left` Pixel position of the left of the source item.
* @param {int} `right` Pixel position of the right of the source item.
*
* @api requests
* @name SetSceneItemCrop
* @category scene items
* @since 4.1.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/
RpcResponse WSRequestHandler::SetSceneItemCrop(const RpcRequest& request) {
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
QString itemName = obs_data_get_string(request.parameters(), "item");
if (itemName.isEmpty()) {
return request.failed("invalid request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return request.failed("requested scene doesn't exist");
}
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) {
return request.failed("specified scene item doesn't exist");
}
struct obs_sceneitem_crop crop = { 0 };
crop.top = obs_data_get_int(request.parameters(), "top");
crop.bottom = obs_data_get_int(request.parameters(), "bottom");
crop.left = obs_data_get_int(request.parameters(), "left");
crop.right = obs_data_get_int(request.parameters(), "right");
obs_sceneitem_set_crop(sceneItem, &crop);
return request.success();
}
/**
* Deletes a scene item.
*
* @param {String (optional)} `scene` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {Object} `item` Scene item to delete (required)
* @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable).
* @param {int} `item.id` Scene Item ID.
*
* @api requests
* @name DeleteSceneItem
* @category scene items
* @since 4.5.0
*/
RpcResponse WSRequestHandler::DeleteSceneItem(const RpcRequest& request) {
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
const char* sceneName = obs_data_get_string(request.parameters(), "scene");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return request.failed("requested scene doesn't exist");
}
OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
if (!sceneItem) {
return request.failed("item with id/name combination not found in specified scene");
}
obs_sceneitem_remove(sceneItem);
return request.success();
}
/**
* Duplicates a scene item.
*
* @param {String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene.
* @param {String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene.
* @param {Object} `item` Scene Item to duplicate from the source scene (required)
* @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable).
* @param {int} `item.id` Scene Item ID.
*
* @return {String} `scene` Name of the scene where the new item was created
* @return {Object} `item` New item info
* @return {int} `item.id` New item ID
* @return {String} `item.name` New item name
*
* @api requests
* @name DuplicateSceneItem
* @category scene items
* @since 4.5.0
*/
RpcResponse WSRequestHandler::DuplicateSceneItem(const RpcRequest& request) {
struct DuplicateSceneItemData {
obs_sceneitem_t *referenceItem;
obs_source_t *fromSource;
obs_sceneitem_t *newItem;
};
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
const char* fromSceneName = obs_data_get_string(request.parameters(), "fromScene");
OBSScene fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName);
if (!fromScene) {
return request.failed("requested fromScene doesn't exist");
}
const char* toSceneName = obs_data_get_string(request.parameters(), "toScene");
OBSScene toScene = Utils::GetSceneFromNameOrCurrent(toSceneName);
if (!toScene) {
return request.failed("requested toScene doesn't exist");
}
OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item");
OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromRequestField(fromScene, itemField);
if (!referenceItem) {
return request.failed("item with id/name combination not found in specified scene");
}
DuplicateSceneItemData data;
data.fromSource = obs_sceneitem_get_source(referenceItem);
data.referenceItem = referenceItem;
obs_enter_graphics();
obs_scene_atomic_update(toScene, [](void *_data, obs_scene_t *scene) {
auto data = reinterpret_cast<DuplicateSceneItemData*>(_data);
data->newItem = obs_scene_add(scene, data->fromSource);
obs_sceneitem_set_visible(data->newItem, obs_sceneitem_visible(data->referenceItem));
}, &data);
obs_leave_graphics();
obs_sceneitem_t *newItem = data.newItem;
if (!newItem) {
return request.failed("Error duplicating scene item");
}
OBSDataAutoRelease itemData = obs_data_create();
obs_data_set_int(itemData, "id", obs_sceneitem_get_id(newItem));
obs_data_set_string(itemData, "name", obs_source_get_name(obs_sceneitem_get_source(newItem)));
OBSDataAutoRelease responseData = obs_data_create();
obs_data_set_obj(responseData, "item", itemData);
obs_data_set_string(responseData, "scene", obs_source_get_name(obs_scene_get_source(toScene)));
return request.success(responseData);
}

View File

@ -0,0 +1,275 @@
#include "Utils.h"
#include "WSRequestHandler.h"
/**
* @typedef {Object} `Scene`
* @property {String} `name` Name of the currently active scene.
* @property {Array<SceneItem>} `sources` Ordered list of the current scene's source items.
*/
/**
* Switch to the specified scene.
*
* @param {String} `scene-name` Name of the scene to switch to.
*
* @api requests
* @name SetCurrentScene
* @category scenes
* @since 0.3
*/
RpcResponse WSRequestHandler::SetCurrentScene(const RpcRequest& request) {
if (!request.hasField("scene-name")) {
return request.failed("missing request parameters");
}
const char* sceneName = obs_data_get_string(request.parameters(), "scene-name");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName);
if (source) {
obs_frontend_set_current_scene(source);
return request.success();
} else {
return request.failed("requested scene does not exist");
}
}
/**
* Get the current scene's name and source items.
*
* @return {String} `name` Name of the currently active scene.
* @return {Array<SceneItem>} `sources` Ordered list of the current scene's source items.
*
* @api requests
* @name GetCurrentScene
* @category scenes
* @since 0.3
*/
RpcResponse WSRequestHandler::GetCurrentScene(const RpcRequest& request) {
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene);
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "name", obs_source_get_name(currentScene));
obs_data_set_array(data, "sources", sceneItems);
return request.success(data);
}
/**
* Get a list of scenes in the currently active profile.
*
* @return {String} `current-scene` Name of the currently active scene.
* @return {Array<Scene>} `scenes` Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information).
*
* @api requests
* @name GetSceneList
* @category scenes
* @since 0.3
*/
RpcResponse WSRequestHandler::GetSceneList(const RpcRequest& request) {
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
OBSDataArrayAutoRelease scenes = Utils::GetScenes();
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "current-scene",
obs_source_get_name(currentScene));
obs_data_set_array(data, "scenes", scenes);
return request.success(data);
}
/**
* Changes the order of scene items in the requested scene.
*
* @param {String (optional)} `scene` Name of the scene to reorder (defaults to current).
* @param {Array<Scene>} `items` Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene
* @param {int (optional)} `items[].id` Id of a specific scene item. Unique on a scene by scene basis.
* @param {String (optional)} `items[].name` Name of a scene item. Sufficiently unique if no scene items share sources within the scene.
*
* @api requests
* @name ReorderSceneItems
* @category scenes
* @since 4.5.0
*/
RpcResponse WSRequestHandler::ReorderSceneItems(const RpcRequest& request) {
QString sceneName = obs_data_get_string(request.parameters(), "scene");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return request.failed("requested scene doesn't exist");
}
OBSDataArrayAutoRelease items = obs_data_get_array(request.parameters(), "items");
if (!items) {
return request.failed("sceneItem order not specified");
}
struct reorder_context {
obs_data_array_t* items;
bool success;
QString errorMessage;
};
struct reorder_context ctx;
ctx.success = false;
ctx.items = items;
obs_scene_atomic_update(scene, [](void* param, obs_scene_t* scene) {
auto ctx = reinterpret_cast<struct reorder_context*>(param);
QVector<struct obs_sceneitem_order_info> orderList;
struct obs_sceneitem_order_info info;
size_t itemCount = obs_data_array_count(ctx->items);
for (uint i = 0; i < itemCount; i++) {
OBSDataAutoRelease item = obs_data_array_item(ctx->items, i);
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
if (!sceneItem) {
ctx->success = false;
ctx->errorMessage = "Invalid sceneItem id or name specified";
return;
}
info.group = nullptr;
info.item = sceneItem;
orderList.insert(0, info);
}
ctx->success = obs_scene_reorder_items2(scene, orderList.data(), orderList.size());
if (!ctx->success) {
ctx->errorMessage = "Invalid sceneItem order";
}
}, &ctx);
if (!ctx.success) {
return request.failed(ctx.errorMessage);
}
return request.success();
}
/**
* Set a scene to use a specific transition override.
*
* @param {String} `sceneName` Name of the scene to switch to.
* @param {String} `transitionName` Name of the transition to use.
* @param {int (Optional)} `transitionDuration` Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given.
*
* @api requests
* @name SetSceneTransitionOverride
* @category scenes
* @since 4.9.0
*/
RpcResponse WSRequestHandler::SetSceneTransitionOverride(const RpcRequest& request) {
if (!request.hasField("sceneName") || !request.hasField("transitionName")) {
return request.failed("missing request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
if (!source) {
return request.failed("requested scene does not exist");
}
enum obs_source_type sourceType = obs_source_get_type(source);
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
return request.failed("requested scene is invalid");
}
QString transitionName = obs_data_get_string(request.parameters(), "transitionName");
if (!Utils::GetTransitionFromName(transitionName)) {
return request.failed("requested transition does not exist");
}
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
obs_data_set_string(sourceData, "transition", transitionName.toUtf8().constData());
if (request.hasField("transitionDuration")) {
int transitionOverrideDuration = obs_data_get_int(request.parameters(), "transitionDuration");
obs_data_set_int(sourceData, "transition_duration", transitionOverrideDuration);
} else if(!obs_data_has_user_value(sourceData, "transition_duration")) {
obs_data_set_int(sourceData, "transition_duration",
obs_frontend_get_transition_duration()
);
}
return request.success();
}
/**
* Remove any transition override on a scene.
*
* @param {String} `sceneName` Name of the scene to switch to.
*
* @api requests
* @name RemoveSceneTransitionOverride
* @category scenes
* @since 4.9.0
*/
RpcResponse WSRequestHandler::RemoveSceneTransitionOverride(const RpcRequest& request) {
if (!request.hasField("sceneName")) {
return request.failed("missing request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
if (!source) {
return request.failed("requested scene does not exist");
}
enum obs_source_type sourceType = obs_source_get_type(source);
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
return request.failed("requested scene is invalid");
}
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
obs_data_erase(sourceData, "transition");
obs_data_erase(sourceData, "transition_duration");
return request.success();
}
/**
* Get the current scene transition override.
*
* @param {String} `sceneName` Name of the scene to switch to.
*
* @return {String} `transitionName` Name of the current overriding transition. Empty string if no override is set.
* @return {int} `transitionDuration` Transition duration. `-1` if no override is set.
*
* @api requests
* @name GetSceneTransitionOverride
* @category scenes
* @since 4.9.0
*/
RpcResponse WSRequestHandler::GetSceneTransitionOverride(const RpcRequest& request) {
if (!request.hasField("sceneName")) {
return request.failed("missing request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
if (!source) {
return request.failed("requested scene does not exist");
}
enum obs_source_type sourceType = obs_source_get_type(source);
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
return request.failed("requested scene is invalid");
}
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
const char* transitionOverrideName = obs_data_get_string(sourceData, "transition");
bool hasDurationOverride = obs_data_has_user_value(sourceData, "transition_duration");
int transitionOverrideDuration = obs_data_get_int(sourceData, "transition_duration");
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "transitionName", transitionOverrideName);
obs_data_set_int(fields, "transitionDuration",
(hasDurationOverride ? transitionOverrideDuration : -1)
);
return request.success(fields);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,320 @@
#include "obs-websocket.h"
#include "Utils.h"
#include "WSEvents.h"
#include "WSRequestHandler.h"
#define STREAM_SERVICE_ID "websocket_custom_service"
/**
* Get current streaming and recording status.
*
* @return {boolean} `streaming` Current streaming status.
* @return {boolean} `recording` Current recording status.
* @return {String (optional)} `stream-timecode` Time elapsed since streaming started (only present if currently streaming).
* @return {String (optional)} `rec-timecode` Time elapsed since recording started (only present if currently recording).
* @return {boolean} `preview-only` Always false. Retrocompatibility with OBSRemote.
*
* @api requests
* @name GetStreamingStatus
* @category streaming
* @since 0.3
*/
RpcResponse WSRequestHandler::GetStreamingStatus(const RpcRequest& request) {
auto events = GetEventsSystem();
OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
obs_data_set_bool(data, "recording", obs_frontend_recording_active());
obs_data_set_bool(data, "recording-paused", obs_frontend_recording_paused());
obs_data_set_bool(data, "preview-only", false);
if (obs_frontend_streaming_active()) {
QString streamingTimecode = events->getStreamingTimecode();
obs_data_set_string(data, "stream-timecode", streamingTimecode.toUtf8().constData());
}
if (obs_frontend_recording_active()) {
QString recordingTimecode = events->getRecordingTimecode();
obs_data_set_string(data, "rec-timecode", recordingTimecode.toUtf8().constData());
}
return request.success(data);
}
/**
* Toggle streaming on or off.
*
* @api requests
* @name StartStopStreaming
* @category streaming
* @since 0.3
*/
RpcResponse WSRequestHandler::StartStopStreaming(const RpcRequest& request) {
if (obs_frontend_streaming_active())
return StopStreaming(request);
else
return StartStreaming(request);
}
/**
* Start streaming.
* Will return an `error` if streaming is already active.
*
* @param {Object (optional)} `stream` Special stream configuration. Please note: these won't be saved to OBS' configuration.
* @param {String (optional)} `stream.type` If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or 'rtmp_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the `settings` object or an error will occur when starting the stream.
* @param {Object (optional)} `stream.metadata` Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the streaming. May be any String, Numeric, or Boolean field.
* @param {Object (optional)} `stream.settings` Settings for the stream.
* @param {String (optional)} `stream.settings.server` The publish URL.
* @param {String (optional)} `stream.settings.key` The publish key of the stream.
* @param {boolean (optional)} `stream.settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.
* @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`.
* @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`.
*
* @api requests
* @name StartStreaming
* @category streaming
* @since 4.1.0
*/
RpcResponse WSRequestHandler::StartStreaming(const RpcRequest& request) {
if (obs_frontend_streaming_active() == false) {
OBSService configuredService = obs_frontend_get_streaming_service();
OBSService newService = nullptr;
// TODO: fix service memory leak
if (request.hasField("stream")) {
OBSDataAutoRelease streamData = obs_data_get_obj(request.parameters(), "stream");
OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings");
OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata");
OBSDataAutoRelease csHotkeys =
obs_hotkeys_save_service(configuredService);
QString currentType = obs_service_get_type(configuredService);
QString newType = obs_data_get_string(streamData, "type");
if (newType.isEmpty() || newType.isNull()) {
newType = currentType;
}
//Supporting adding metadata parameters to key query string
QString query = Utils::ParseDataToQueryString(newMetadata);
if (!query.isEmpty()
&& obs_data_has_user_value(newSettings, "key"))
{
const char* key = obs_data_get_string(newSettings, "key");
size_t keylen = strlen(key);
bool hasQuestionMark = false;
for (size_t i = 0; i < keylen; i++) {
if (key[i] == '?') {
hasQuestionMark = true;
break;
}
}
if (hasQuestionMark) {
query.prepend('&');
} else {
query.prepend('?');
}
query.prepend(key);
obs_data_set_string(newSettings, "key", query.toUtf8());
}
if (newType == currentType) {
// Service type doesn't change: apply settings to current service
// By doing this, you can send a request to the websocket
// that only contains settings you want to change, instead of
// having to do a get and then change them
OBSDataAutoRelease currentSettings = obs_service_get_settings(configuredService);
OBSDataAutoRelease updatedSettings = obs_data_create();
obs_data_apply(updatedSettings, currentSettings); //first apply the existing settings
obs_data_apply(updatedSettings, newSettings); //then apply the settings from the request should they exist
newService = obs_service_create(
newType.toUtf8(), STREAM_SERVICE_ID,
updatedSettings, csHotkeys);
}
else {
// Service type changed: override service settings
newService = obs_service_create(
newType.toUtf8(), STREAM_SERVICE_ID,
newSettings, csHotkeys);
}
obs_frontend_set_streaming_service(newService);
}
obs_frontend_streaming_start();
// Stream settings provided in StartStreaming are not persisted to disk
if (newService != nullptr) {
obs_frontend_set_streaming_service(configuredService);
}
return request.success();
} else {
return request.failed("streaming already active");
}
}
/**
* Stop streaming.
* Will return an `error` if streaming is not active.
*
* @api requests
* @name StopStreaming
* @category streaming
* @since 4.1.0
*/
RpcResponse WSRequestHandler::StopStreaming(const RpcRequest& request) {
if (obs_frontend_streaming_active() == true) {
obs_frontend_streaming_stop();
return request.success();
} else {
return request.failed("streaming not active");
}
}
/**
* Sets one or more attributes of the current streaming server settings. Any options not passed will remain unchanged. Returns the updated settings in response. If 'type' is different than the current streaming service type, all settings are required. Returns the full settings of the stream (the same as GetStreamSettings).
*
* @param {String} `type` The type of streaming service configuration, usually `rtmp_custom` or `rtmp_common`.
* @param {Object} `settings` The actual settings of the stream.
* @param {String (optional)} `settings.server` The publish URL.
* @param {String (optional)} `settings.key` The publish key.
* @param {boolean (optional)} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.
* @param {String (optional)} `settings.username` The username for the streaming service.
* @param {String (optional)} `settings.password` The password for the streaming service.
* @param {boolean} `save` Persist the settings to disk.
*
* @api requests
* @name SetStreamSettings
* @category streaming
* @since 4.1.0
*/
RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) {
OBSService service = obs_frontend_get_streaming_service();
OBSDataAutoRelease requestSettings = obs_data_get_obj(request.parameters(), "settings");
if (!requestSettings) {
return request.failed("'settings' are required'");
}
QString serviceType = obs_service_get_type(service);
QString requestedType = obs_data_get_string(request.parameters(), "type");
if (requestedType != nullptr && requestedType != serviceType) {
OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service);
service = obs_service_create(
requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys);
obs_frontend_set_streaming_service(service);
} else {
// If type isn't changing, we should overlay the settings we got
// to the existing settings. By doing so, you can send a request that
// only contains the settings you want to change, instead of having to
// do a get and then change them
OBSDataAutoRelease existingSettings = obs_service_get_settings(service);
OBSDataAutoRelease newSettings = obs_data_create();
// Apply existing settings
obs_data_apply(newSettings, existingSettings);
// Then apply the settings from the request
obs_data_apply(newSettings, requestSettings);
obs_service_update(service, newSettings);
}
//if save is specified we should immediately save the streaming service
if (obs_data_get_bool(request.parameters(), "save")) {
obs_frontend_save_streaming_service();
}
OBSService responseService = obs_frontend_get_streaming_service();
OBSDataAutoRelease serviceSettings = obs_service_get_settings(responseService);
const char* responseType = obs_service_get_type(responseService);
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "type", responseType);
obs_data_set_obj(response, "settings", serviceSettings);
return request.success(response);
}
/**
* Get the current streaming server settings.
*
* @return {String} `type` The type of streaming service configuration. Possible values: 'rtmp_custom' or 'rtmp_common'.
* @return {Object} `settings` Stream settings object.
* @return {String} `settings.server` The publish URL.
* @return {String} `settings.key` The publish key of the stream.
* @return {boolean} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.
* @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use_auth` is `true`.
* @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`.
*
* @api requests
* @name GetStreamSettings
* @category streaming
* @since 4.1.0
*/
RpcResponse WSRequestHandler::GetStreamSettings(const RpcRequest& request) {
OBSService service = obs_frontend_get_streaming_service();
const char* serviceType = obs_service_get_type(service);
OBSDataAutoRelease settings = obs_service_get_settings(service);
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "type", serviceType);
obs_data_set_obj(response, "settings", settings);
return request.success(response);
}
/**
* Save the current streaming server settings to disk.
*
* @api requests
* @name SaveStreamSettings
* @category streaming
* @since 4.1.0
*/
RpcResponse WSRequestHandler::SaveStreamSettings(const RpcRequest& request) {
obs_frontend_save_streaming_service();
return request.success();
}
/**
* Send the provided text as embedded CEA-608 caption data.
* As of OBS Studio 23.1, captions are not yet available on Linux.
*
* @param {String} `text` Captions text
*
* @api requests
* @name SendCaptions
* @category streaming
* @since 4.6.0
*/
#if BUILD_CAPTIONS
RpcResponse WSRequestHandler::SendCaptions(const RpcRequest& request) {
if (!request.hasField("text")) {
return request.failed("missing request parameters");
}
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
if (output) {
const char* caption = obs_data_get_string(request.parameters(), "text");
// Send caption text with immediately (0 second delay)
obs_output_output_caption_text2(output, caption, 0.0);
}
return request.success();
}
#endif

View File

@ -0,0 +1,185 @@
#include "Utils.h"
#include "WSRequestHandler.h"
/**
* Indicates if Studio Mode is currently enabled.
*
* @return {boolean} `studio-mode` Indicates if Studio Mode is enabled.
*
* @api requests
* @name GetStudioModeStatus
* @category studio mode
* @since 4.1.0
*/
RpcResponse WSRequestHandler::GetStudioModeStatus(const RpcRequest& request) {
bool previewActive = obs_frontend_preview_program_mode_active();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_bool(response, "studio-mode", previewActive);
return request.success(response);
}
/**
* Get the name of the currently previewed scene and its list of sources.
* Will return an `error` if Studio Mode is not enabled.
*
* @return {String} `name` The name of the active preview scene.
* @return {Array<SceneItem>} `sources`
*
* @api requests
* @name GetPreviewScene
* @category studio mode
* @since 4.1.0
*/
RpcResponse WSRequestHandler::GetPreviewScene(const RpcRequest& request) {
if (!obs_frontend_preview_program_mode_active()) {
return request.failed("studio mode not enabled");
}
OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene();
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(scene);
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "name", obs_source_get_name(scene));
obs_data_set_array(data, "sources", sceneItems);
return request.success(data);
}
/**
* Set the active preview scene.
* Will return an `error` if Studio Mode is not enabled.
*
* @param {String} `scene-name` The name of the scene to preview.
*
* @api requests
* @name SetPreviewScene
* @category studio mode
* @since 4.1.0
*/
RpcResponse WSRequestHandler::SetPreviewScene(const RpcRequest& request) {
if (!obs_frontend_preview_program_mode_active()) {
return request.failed("studio mode not enabled");
}
if (!request.hasField("scene-name")) {
return request.failed("missing request parameters");
}
const char* scene_name = obs_data_get_string(request.parameters(), "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(scene_name);
if (!scene) {
return request.failed("specified scene doesn't exist");
}
obs_frontend_set_current_preview_scene(obs_scene_get_source(scene));
return request.success();
}
/**
* Transitions the currently previewed scene to the main output.
* Will return an `error` if Studio Mode is not enabled.
*
* @param {Object (optional)} `with-transition` Change the active transition before switching scenes. Defaults to the active transition.
* @param {String} `with-transition.name` Name of the transition.
* @param {int (optional)} `with-transition.duration` Transition duration (in milliseconds).
*
* @api requests
* @name TransitionToProgram
* @category studio mode
* @since 4.1.0
*/
RpcResponse WSRequestHandler::TransitionToProgram(const RpcRequest& request) {
if (!obs_frontend_preview_program_mode_active()) {
return request.failed("studio mode not enabled");
}
if (request.hasField("with-transition")) {
OBSDataAutoRelease transitionInfo =
obs_data_get_obj(request.parameters(), "with-transition");
if (obs_data_has_user_value(transitionInfo, "name")) {
QString transitionName =
obs_data_get_string(transitionInfo, "name");
if (transitionName.isEmpty()) {
return request.failed("invalid request parameters");
}
bool success = Utils::SetTransitionByName(transitionName);
if (!success) {
return request.failed("specified transition doesn't exist");
}
}
if (obs_data_has_user_value(transitionInfo, "duration")) {
int transitionDuration =
obs_data_get_int(transitionInfo, "duration");
obs_frontend_set_transition_duration(transitionDuration);
}
}
obs_frontend_preview_program_trigger_transition();
return request.success();
}
/**
* Enables Studio Mode.
*
* @api requests
* @name EnableStudioMode
* @category studio mode
* @since 4.1.0
*/
RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) {
if (obs_frontend_preview_program_mode_active()) {
return request.failed("studio mode already active");
}
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_preview_program_mode(true);
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success();
}
/**
* Disables Studio Mode.
*
* @api requests
* @name DisableStudioMode
* @category studio mode
* @since 4.1.0
*/
RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) {
if (!obs_frontend_preview_program_mode_active()) {
return request.failed("studio mode not active");
}
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_preview_program_mode(false);
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success();
}
/**
* Toggles Studio Mode.
*
* @api requests
* @name ToggleStudioMode
* @category studio mode
* @since 4.1.0
*/
RpcResponse WSRequestHandler::ToggleStudioMode(const RpcRequest& request) {
obs_queue_task(OBS_TASK_UI, [](void* param) {
bool previewProgramMode = obs_frontend_preview_program_mode_active();
obs_frontend_set_preview_program_mode(!previewProgramMode);
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success();
}

View File

@ -0,0 +1,141 @@
#include "Utils.h"
#include "WSRequestHandler.h"
/**
* List of all transitions available in the frontend's dropdown menu.
*
* @return {String} `current-transition` Name of the currently active transition.
* @return {Array<Object>} `transitions` List of transitions.
* @return {String} `transitions.*.name` Name of the transition.
*
* @api requests
* @name GetTransitionList
* @category transitions
* @since 4.1.0
*/
RpcResponse WSRequestHandler::GetTransitionList(const RpcRequest& request) {
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
obs_frontend_source_list transitionList = {};
obs_frontend_get_transitions(&transitionList);
OBSDataArrayAutoRelease transitions = obs_data_array_create();
for (size_t i = 0; i < transitionList.sources.num; i++) {
OBSSource transition = transitionList.sources.array[i];
OBSDataAutoRelease obj = obs_data_create();
obs_data_set_string(obj, "name", obs_source_get_name(transition));
obs_data_array_push_back(transitions, obj);
}
obs_frontend_source_list_free(&transitionList);
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "current-transition",
obs_source_get_name(currentTransition));
obs_data_set_array(response, "transitions", transitions);
return request.success(response);
}
/**
* Get the name of the currently selected transition in the frontend's dropdown menu.
*
* @return {String} `name` Name of the selected transition.
* @return {int (optional)} `duration` Transition duration (in milliseconds) if supported by the transition.
*
* @api requests
* @name GetCurrentTransition
* @category transitions
* @since 0.3
*/
RpcResponse WSRequestHandler::GetCurrentTransition(const RpcRequest& request) {
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "name",
obs_source_get_name(currentTransition));
if (!obs_transition_fixed(currentTransition))
obs_data_set_int(response, "duration", obs_frontend_get_transition_duration());
return request.success(response);
}
/**
* Set the active transition.
*
* @param {String} `transition-name` The name of the transition.
*
* @api requests
* @name SetCurrentTransition
* @category transitions
* @since 0.3
*/
RpcResponse WSRequestHandler::SetCurrentTransition(const RpcRequest& request) {
if (!request.hasField("transition-name")) {
return request.failed("missing request parameters");
}
QString name = obs_data_get_string(request.parameters(), "transition-name");
bool success = Utils::SetTransitionByName(name);
if (!success) {
return request.failed("requested transition does not exist");
}
return request.success();
}
/**
* Set the duration of the currently selected transition if supported.
*
* @param {int} `duration` Desired duration of the transition (in milliseconds).
*
* @api requests
* @name SetTransitionDuration
* @category transitions
* @since 4.0.0
*/
RpcResponse WSRequestHandler::SetTransitionDuration(const RpcRequest& request) {
if (!request.hasField("duration")) {
return request.failed("missing request parameters");
}
int ms = obs_data_get_int(request.parameters(), "duration");
obs_frontend_set_transition_duration(ms);
return request.success();
}
/**
* Get the duration of the currently selected transition if supported.
*
* @return {int} `transition-duration` Duration of the current transition (in milliseconds).
*
* @api requests
* @name GetTransitionDuration
* @category transitions
* @since 4.1.0
*/
RpcResponse WSRequestHandler::GetTransitionDuration(const RpcRequest& request) {
OBSDataAutoRelease response = obs_data_create();
obs_data_set_int(response, "transition-duration", obs_frontend_get_transition_duration());
return request.success(response);
}
/**
* Get the position of the current transition.
*
* @return {double} `position` current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active.
*
* @api requests
* @name GetTransitionPosition
* @category transitions
* @since 4.8.0
*/
RpcResponse WSRequestHandler::GetTransitionPosition(const RpcRequest& request) {
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_double(response, "position", obs_transition_get_time(currentTransition));
return request.success(response);
}

247
src/WSServer.cpp Normal file
View File

@ -0,0 +1,247 @@
/*
obs-websocket
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <chrono>
#include <thread>
#include <QtCore/QThread>
#include <QtCore/QByteArray>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMessageBox>
#include <QtConcurrent/QtConcurrent>
#include <obs-frontend-api.h>
#include <util/platform.h>
#include "WSServer.h"
#include "obs-websocket.h"
#include "Config.h"
#include "Utils.h"
#include "protocol/OBSRemoteProtocol.h"
QT_USE_NAMESPACE
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
WSServer::WSServer()
: QObject(nullptr),
_connections(),
_clMutex(QMutex::Recursive)
{
_server.get_alog().clear_channels(websocketpp::log::alevel::frame_header | websocketpp::log::alevel::frame_payload | websocketpp::log::alevel::control);
_server.init_asio();
#ifndef _WIN32
_server.set_reuse_addr(true);
#endif
_server.set_open_handler(bind(&WSServer::onOpen, this, ::_1));
_server.set_close_handler(bind(&WSServer::onClose, this, ::_1));
_server.set_message_handler(bind(&WSServer::onMessage, this, ::_1, ::_2));
}
WSServer::~WSServer()
{
stop();
}
void WSServer::start(quint16 port)
{
if (_server.is_listening() && port == _serverPort) {
blog(LOG_INFO, "WSServer::start: server already on this port. no restart needed");
return;
}
if (_server.is_listening()) {
stop();
}
_server.reset();
_serverPort = port;
websocketpp::lib::error_code errorCode;
_server.listen(_serverPort, errorCode);
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "server: listen failed: %s", errorCodeMessage.c_str());
obs_frontend_push_ui_translation(obs_module_get_string);
QString errorTitle = tr("OBSWebsocket.Server.StartFailed.Title");
QString errorMessage = tr("OBSWebsocket.Server.StartFailed.Message").arg(_serverPort).arg(errorCodeMessage.c_str());
obs_frontend_pop_ui_translation();
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
QMessageBox::warning(mainWindow, errorTitle, errorMessage);
return;
}
_server.start_accept();
QtConcurrent::run([=]() {
blog(LOG_INFO, "io thread started");
_server.run();
blog(LOG_INFO, "io thread exited");
});
blog(LOG_INFO, "server started successfully on port %d", _serverPort);
}
void WSServer::stop()
{
if (!_server.is_listening()) {
return;
}
_server.stop_listening();
for (connection_hdl hdl : _connections) {
_server.close(hdl, websocketpp::close::status::going_away, "Server stopping");
}
_connections.clear();
_connectionProperties.clear();
_threadPool.waitForDone();
while (!_server.stopped()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
blog(LOG_INFO, "server stopped successfully");
}
void WSServer::broadcast(const RpcEvent& event)
{
OBSRemoteProtocol protocol;
std::string message = protocol.encodeEvent(event);
if (GetConfig()->DebugEnabled) {
blog(LOG_INFO, "Update << '%s'", message.c_str());
}
QMutexLocker locker(&_clMutex);
for (connection_hdl hdl : _connections) {
if (GetConfig()->AuthRequired) {
bool authenticated = _connectionProperties[hdl].isAuthenticated();
if (!authenticated) {
continue;
}
}
websocketpp::lib::error_code errorCode;
_server.send(hdl, message, websocketpp::frame::opcode::text, errorCode);
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "server(broadcast): send failed: %s",
errorCodeMessage.c_str());
}
}
}
void WSServer::onOpen(connection_hdl hdl)
{
QMutexLocker locker(&_clMutex);
_connections.insert(hdl);
locker.unlock();
QString clientIp = getRemoteEndpoint(hdl);
notifyConnection(clientIp);
blog(LOG_INFO, "new client connection from %s", clientIp.toUtf8().constData());
}
void WSServer::onMessage(connection_hdl hdl, server::message_ptr message)
{
auto opcode = message->get_opcode();
if (opcode != websocketpp::frame::opcode::text) {
return;
}
QtConcurrent::run(&_threadPool, [=]() {
std::string payload = message->get_payload();
QMutexLocker locker(&_clMutex);
ConnectionProperties& connProperties = _connectionProperties[hdl];
locker.unlock();
if (GetConfig()->DebugEnabled) {
blog(LOG_INFO, "Request >> '%s'", payload.c_str());
}
WSRequestHandler requestHandler(connProperties);
OBSRemoteProtocol protocol;
std::string response = protocol.processMessage(requestHandler, payload);
if (GetConfig()->DebugEnabled) {
blog(LOG_INFO, "Response << '%s'", response.c_str());
}
websocketpp::lib::error_code errorCode;
_server.send(hdl, response, websocketpp::frame::opcode::text, errorCode);
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "server(response): send failed: %s",
errorCodeMessage.c_str());
}
});
}
void WSServer::onClose(connection_hdl hdl)
{
QMutexLocker locker(&_clMutex);
_connections.erase(hdl);
_connectionProperties.erase(hdl);
locker.unlock();
auto conn = _server.get_con_from_hdl(hdl);
auto localCloseCode = conn->get_local_close_code();
if (localCloseCode != websocketpp::close::status::going_away) {
QString clientIp = getRemoteEndpoint(hdl);
notifyDisconnection(clientIp);
blog(LOG_INFO, "client %s disconnected", clientIp.toUtf8().constData());
}
}
QString WSServer::getRemoteEndpoint(connection_hdl hdl)
{
auto conn = _server.get_con_from_hdl(hdl);
return QString::fromStdString(conn->get_remote_endpoint());
}
void WSServer::notifyConnection(QString clientIp)
{
obs_frontend_push_ui_translation(obs_module_get_string);
QString title = tr("OBSWebsocket.NotifyConnect.Title");
QString msg = tr("OBSWebsocket.NotifyConnect.Message").arg(clientIp);
obs_frontend_pop_ui_translation();
Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title);
}
void WSServer::notifyDisconnection(QString clientIp)
{
obs_frontend_push_ui_translation(obs_module_get_string);
QString title = tr("OBSWebsocket.NotifyDisconnect.Title");
QString msg = tr("OBSWebsocket.NotifyDisconnect.Message").arg(clientIp);
obs_frontend_pop_ui_translation();
Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title);
}

69
src/WSServer.h Normal file
View File

@ -0,0 +1,69 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#pragma once
#include <map>
#include <set>
#include <QtCore/QObject>
#include <QtCore/QMutex>
#include <QtCore/QSharedPointer>
#include <QtCore/QVariantHash>
#include <QtCore/QThreadPool>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include "ConnectionProperties.h"
#include "WSRequestHandler.h"
#include "rpc/RpcEvent.h"
using websocketpp::connection_hdl;
typedef websocketpp::server<websocketpp::config::asio> server;
class WSServer : public QObject
{
Q_OBJECT
public:
explicit WSServer();
virtual ~WSServer();
void start(quint16 port);
void stop();
void broadcast(const RpcEvent& event);
QThreadPool* threadPool() {
return &_threadPool;
}
private:
void onOpen(connection_hdl hdl);
void onMessage(connection_hdl hdl, server::message_ptr message);
void onClose(connection_hdl hdl);
QString getRemoteEndpoint(connection_hdl hdl);
void notifyConnection(QString clientIp);
void notifyDisconnection(QString clientIp);
server _server;
quint16 _serverPort;
std::set<connection_hdl, std::owner_less<connection_hdl>> _connections;
std::map<connection_hdl, ConnectionProperties, std::owner_less<connection_hdl>> _connectionProperties;
QMutex _clMutex;
QThreadPool _threadPool;
};

View File

@ -0,0 +1,106 @@
/*
obs-websocket
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <obs-frontend-api.h>
#include "../obs-websocket.h"
#include "../Config.h"
#include "../WSServer.h"
#include "settings-dialog.h"
#define CHANGE_ME "changeme"
SettingsDialog::SettingsDialog(QWidget* parent) :
QDialog(parent, Qt::Dialog),
ui(new Ui::SettingsDialog)
{
ui->setupUi(this);
connect(ui->authRequired, &QCheckBox::stateChanged,
this, &SettingsDialog::AuthCheckboxChanged);
connect(ui->buttonBox, &QDialogButtonBox::accepted,
this, &SettingsDialog::FormAccepted);
AuthCheckboxChanged();
}
void SettingsDialog::showEvent(QShowEvent* event) {
auto conf = GetConfig();
ui->serverEnabled->setChecked(conf->ServerEnabled);
ui->serverPort->setValue(conf->ServerPort);
ui->debugEnabled->setChecked(conf->DebugEnabled);
ui->alertsEnabled->setChecked(conf->AlertsEnabled);
ui->authRequired->setChecked(conf->AuthRequired);
ui->password->setText(CHANGE_ME);
}
void SettingsDialog::ToggleShowHide() {
if (!isVisible())
setVisible(true);
else
setVisible(false);
}
void SettingsDialog::AuthCheckboxChanged() {
if (ui->authRequired->isChecked())
ui->password->setEnabled(true);
else
ui->password->setEnabled(false);
}
void SettingsDialog::FormAccepted() {
auto conf = GetConfig();
conf->ServerEnabled = ui->serverEnabled->isChecked();
conf->ServerPort = ui->serverPort->value();
conf->DebugEnabled = ui->debugEnabled->isChecked();
conf->AlertsEnabled = ui->alertsEnabled->isChecked();
if (ui->authRequired->isChecked()) {
if (ui->password->text() != CHANGE_ME) {
conf->SetPassword(ui->password->text());
}
if (!GetConfig()->Secret.isEmpty())
conf->AuthRequired = true;
else
conf->AuthRequired = false;
}
else
{
conf->AuthRequired = false;
}
conf->Save();
auto server = GetServer();
if (conf->ServerEnabled) {
server->start(conf->ServerPort);
} else {
server->stop();
}
}
SettingsDialog::~SettingsDialog() {
delete ui;
}

View File

@ -1,6 +1,6 @@
/*
obs-websocket
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -16,23 +16,20 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifndef SETTINGSDIALOG_H
#define SETTINGSDIALOG_H
#pragma once
#include <QDialog>
#include <QtWidgets/QDialog>
namespace Ui {
class SettingsDialog;
}
#include "ui_settings-dialog.h"
class SettingsDialog : public QDialog
{
Q_OBJECT
public:
explicit SettingsDialog(QWidget *parent = 0);
explicit SettingsDialog(QWidget* parent = 0);
~SettingsDialog();
void showEvent(QShowEvent *event);
void showEvent(QShowEvent* event);
void ToggleShowHide();
private Q_SLOTS:
@ -40,7 +37,5 @@ private Q_SLOTS:
void FormAccepted();
private:
Ui::SettingsDialog *ui;
Ui::SettingsDialog* ui;
};
#endif // SETTINGSDIALOG_H

View File

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDialog</class>
<widget class="QDialog" name="SettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>407</width>
<height>195</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>OBSWebsocket.Settings.DialogTitle</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="3" column="1">
<widget class="QCheckBox" name="authRequired">
<property name="text">
<string>OBSWebsocket.Settings.AuthRequired</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lbl_password">
<property name="text">
<string>OBSWebsocket.Settings.Password</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="password">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="serverEnabled">
<property name="text">
<string>OBSWebsocket.Settings.ServerEnable</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lbl_serverPort">
<property name="text">
<string>OBSWebsocket.Settings.ServerPort</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="serverPort">
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>4444</number>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="alertsEnabled">
<property name="text">
<string>OBSWebsocket.Settings.AlertsEnable</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="debugEnabled">
<property name="text">
<string>OBSWebsocket.Settings.DebugEnable</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>294</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>314</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>300</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>314</y>
</hint>
</hints>
</connection>
</connections>
</ui>

Some files were not shown because too many files have changed in this diff Show More