Compare commits

...

332 Commits
4.3.0 ... 4.6.0

Author SHA1 Message Date
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
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
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
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
73 changed files with 8841 additions and 3710 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

9
.gitmodules vendored
View File

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

View File

@ -32,14 +32,6 @@ matrix:
after_success: after_success:
- docker exec -it xenial /root/obs-websocket/CI/package-xenial.sh - docker exec -it xenial /root/obs-websocket/CI/package-xenial.sh
- os: osx
env: _macos_build
osx_image: xcode8.3
before_install: "./CI/install-dependencies-macos.sh"
script: "./CI/build-macos.sh"
after_success:
- ./CI/package-macos.sh
deploy: deploy:
- provider: s3 - provider: s3
region: eu-central-1 region: eu-central-1
@ -55,15 +47,3 @@ deploy:
- "$TRAVIS_OS_NAME = linux" - "$TRAVIS_OS_NAME = linux"
- "-d /home/travis/package" - "-d /home/travis/package"
all_branches: true all_branches: true
- provider: s3
region: eu-central-1
bucket: obs-websocket-osx-builds
access_key_id: "$AWS_ID"
secret_access_key: "$AWS_SECRET"
local_dir: release
skip_cleanup: true
acl: public_read
on:
repo: Palakis/obs-websocket
condition: "$TRAVIS_OS_NAME = osx"
all_branches: true

View File

@ -1,18 +1,26 @@
# Compiling obs-websocket # Compiling obs-websocket
## Prerequisites ## Prerequisites
You'll need [QT 5.9.0](https://download.qt.io/official_releases/qt/5.7/5.7.0/), CMake, and a working development environment for OBS Studio installed on your computer.
You'll need [Qt 5.10.x](https://download.qt.io/official_releases/qt/5.10/),
[CMake](https://cmake.org/download/), [Boost](https://www.boost.org/) and a working [OBS Studio development environment](https://obsproject.com/wiki/install-instructions) installed on your
computer.
## Windows ## Windows
In cmake-gui, you'll have to set the following variables : In cmake-gui, you'll have to set the following variables :
- **QTDIR** (path) : location of the Qt environment suited for your compiler and architecture - **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_INCLUDE_DIR** (path) : location of the libobs subfolder in the source code of OBS Studio
- **LIBOBS_LIB** (filepath) : location of the obs.lib file - **LIBOBS_LIB** (filepath) : location of the obs.lib file
- **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file - **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file
## Linux ## Linux
On Debian/Ubuntu : On Debian/Ubuntu :
```
sudo apt-get install libqt5websockets5-dev ```shell
sudo apt-get install libboost-all-dev
git clone --recursive https://github.com/Palakis/obs-websocket.git git clone --recursive https://github.com/Palakis/obs-websocket.git
cd obs-websocket cd obs-websocket
mkdir build && cd build mkdir build && cd build
@ -22,17 +30,33 @@ sudo make install
``` ```
## OS X ## OS X
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-dependencies-macos.sh` will install it for you.
Of course, you're encouraged to dig through the contents of these scripts to look for issues or specificities. 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 git clone --recursive https://github.com/Palakis/obs-websocket.git
cd obs-websocket cd obs-websocket
./CI/install-dependencies-macos.sh ./CI/install-dependencies-macos.sh
./CI/install-build-obs-macos.sh
./CI/build-macos.sh ./CI/build-macos.sh
./CI/package-macos.sh ./CI/package-macos.sh
``` ```
This will result in a ready-to-use `obs-websocket.pkg` installer in the `release` subfolder. This will result in a ready-to-use `obs-websocket.pkg` installer in the `release` subfolder.
## Automated Builds ## 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 & OS X : [![Automated Build status for Linux & OS X](https://travis-ci.org/Palakis/obs-websocket.svg?branch=master)](https://travis-ci.org/Palakis/obs-websocket) - 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)

View File

@ -1,13 +1,28 @@
#!/bin/sh #!/bin/sh
set -ex
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)" #export QT_PREFIX="$(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
mkdir build && cd build echo "[obs-websocket] Building 'obs-websocket' for macOS."
mkdir -p build && cd build
cmake .. \ cmake .. \
-DQTDIR=/usr/local/opt/qt \ -DQTDIR=/usr/local/opt/qt \
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \ -DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
-DLIBOBS_LIB=../../obs-studio/libobs \ -DLIBOBS_LIB=../../obs-studio/libobs \
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \ -DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
-DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=/usr \
&& make -j4 && make -j4

View File

@ -4,5 +4,5 @@ set -ex
cd /root/obs-websocket cd /root/obs-websocket
mkdir build && cd build mkdir build && cd build
cmake -DLIBOBS_INCLUDE_DIR="../../obs-studio/libobs" -DCMAKE_INSTALL_PREFIX=/usr .. cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make -j4 make -j4

View File

@ -11,13 +11,13 @@ npm run build
echo "-- Documentation successfully generated." echo "-- Documentation successfully generated."
if git diff --quiet; then if git diff --quiet; then
echo "-- No documentation changes to commit." echo "-- No documentation changes to commit."
exit 0 exit 0
fi fi
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "master" ]; then if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "4.x-current" ]; then
echo "-- Skipping documentation deployment because this is either a pull request or a non-master branch." echo "-- Skipping documentation deployment because this is either a pull request or a non-master branch."
exit 0 exit 0
fi fi
REMOTE_URL="$(git config remote.origin.url)" REMOTE_URL="$(git config remote.origin.url)"

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

@ -0,0 +1,43 @@
#!/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 .. \
-DBUILD_CAPTIONS=true \
-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

@ -19,52 +19,63 @@ set "BuildOBS="
REM Check the last tag successfully built by CI. REM Check the last tag successfully built by CI.
if exist C:\projects\obs-studio-last-tag-built.txt ( if exist C:\projects\obs-studio-last-tag-built.txt (
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
) else ( ) else (
set OBSLastTagBuilt=0 set OBSLastTagBuilt=0
) )
REM If obs-studio directory exists, run git pull and get the latest tag number. REM If obs-studio directory exists, run git pull and get the latest tag number.
if exist C:\projects\obs-studio\ ( if exist C:\projects\obs-studio\ (
echo obs-studio directory exists echo obs-studio directory exists
echo Updating tag info echo Updating tag info
cd C:\projects\obs-studio\ cd C:\projects\obs-studio\
git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-pre-pull.txt git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-pre-pull.txt
set /p OBSLatestTagPrePull=<C:\projects\latest-obs-studio-tag-pre-pull.txt set /p OBSLatestTagPrePull=<C:\projects\latest-obs-studio-tag-pre-pull.txt
git checkout master git checkout master
git pull git pull
git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-post-pull.txt git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-post-pull.txt
set /p OBSLatestTagPostPull=<C:\projects\latest-obs-studio-tag-post-pull.txt set /p OBSLatestTagPostPull=<C:\projects\latest-obs-studio-tag-post-pull.txt
set /p OBSLatestTag=<C:\projects\latest-obs-studio-tag-post-pull.txt set /p OBSLatestTag=<C:\projects\latest-obs-studio-tag-post-pull.txt
echo %OBSLatestTagPostPull%> C:\projects\latest-obs-studio-tag.txt echo %OBSLatestTagPostPull%> C:\projects\latest-obs-studio-tag.txt
) )
REM Check the obs-studio tags for mismatches. REM Check the obs-studio tags for mismatches.
REM If a new tag was pulled, set the build flag. REM If a new tag was pulled, set the build flag.
if not %OBSLatestTagPrePull%==%OBSLatestTagPostPull% ( if not %OBSLatestTagPrePull%==%OBSLatestTagPostPull% (
echo Latest tag pre-pull: %OBSLatestTagPrePull% echo Latest tag pre-pull: %OBSLatestTagPrePull%
echo Latest tag post-pull: %OBSLatestTagPostPull% echo Latest tag post-pull: %OBSLatestTagPostPull%
echo Tags do not match. Need to rebuild OBS. echo Tags do not match. Need to rebuild OBS.
set BuildOBS=true set BuildOBS=true
) )
REM If the latest git tag doesn't match the last built tag, set the build flag. REM If the latest git tag doesn't match the last built tag, set the build flag.
if not %OBSLatestTagPostPull%==%OBSLastTagBuilt% ( if not %OBSLatestTagPostPull%==%OBSLastTagBuilt% (
echo Last built OBS tag: %OBSLastTagBuilt% echo Last built OBS tag: %OBSLastTagBuilt%
echo Latest tag post-pull: %OBSLatestTagPostPull% echo Latest tag post-pull: %OBSLatestTagPostPull%
echo Tags do not match. Need to rebuild OBS. echo Tags do not match. Need to rebuild OBS.
set BuildOBS=true set BuildOBS=true
) )
REM If obs-studio directory does not exist, clone the git repo, get the latest REM If obs-studio directory does not exist, clone the git repo, get the latest
REM tag number, and set the build flag. REM tag number, and set the build flag.
if not exist C:\projects\obs-studio ( if not exist C:\projects\obs-studio (
echo obs-studio directory does not exist echo obs-studio directory does not exist
git clone --recursive https://github.com/jp9000/obs-studio git clone https://github.com/obsproject/obs-studio
cd C:\projects\obs-studio\ cd C:\projects\obs-studio\
git describe --tags --abbrev=0 > C:\projects\obs-studio-latest-tag.txt git describe --tags --abbrev=0 > C:\projects\obs-studio-latest-tag.txt
set /p OBSLatestTag=<C:\projects\obs-studio-latest-tag.txt set /p OBSLatestTag=<C:\projects\obs-studio-latest-tag.txt
set BuildOBS=true set BuildOBS=true
)
REM If the needed obs-studio libs for this build_config do not exist,
REM set the build flag.
if not exist C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib (
echo obs-studio\build32\libobs\%build_config%\obs.lib does not exist
set BuildOBS=true
)
if not exist C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib (
echo obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib does not exist
set BuildOBS=true
) )
REM Some debug info REM Some debug info
@ -75,44 +86,44 @@ echo Latest tag: %OBSLatestTag%
echo Last built OBS tag: %OBSLastTagBuilt% echo Last built OBS tag: %OBSLastTagBuilt%
if defined BuildOBS ( if defined BuildOBS (
echo BuildOBS: true echo BuildOBS: true
) else ( ) else (
echo BuildOBS: false echo BuildOBS: false
) )
echo: echo:
REM If the build flag is set, build obs-studio. REM If the build flag is set, build obs-studio.
if defined BuildOBS ( if defined BuildOBS (
echo Building obs-studio... echo Building obs-studio...
echo git checkout %OBSLatestTag% echo git checkout %OBSLatestTag%
git checkout %OBSLatestTag% git checkout %OBSLatestTag%
echo: echo:
echo Removing previous build dirs... echo Removing previous build dirs...
if exist build rmdir /s /q C:\projects\obs-studio\build if exist build rmdir /s /q C:\projects\obs-studio\build
if exist build32 rmdir /s /q C:\projects\obs-studio\build32 if exist build32 rmdir /s /q C:\projects\obs-studio\build32
if exist build64 rmdir /s /q C:\projects\obs-studio\build64 if exist build64 rmdir /s /q C:\projects\obs-studio\build64
echo Making new build dirs... echo Making new build dirs...
mkdir build mkdir build
mkdir build32 mkdir build32
mkdir build64 mkdir build64
echo Running cmake for obs-studio %OBSLatestTag% 32-bit... echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
cd ./build32 cd ./build32
cmake -G "Visual Studio 12 2013" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. cmake -G "Visual Studio 14 2015" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo: echo:
echo: echo:
echo Running cmake for obs-studio %OBSLatestTag% 64-bit... echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
cd ../build64 cd ../build64
cmake -G "Visual Studio 12 2013 Win64" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. cmake -G "Visual Studio 14 2015 Win64" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo: echo:
echo: echo:
echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)... echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)...
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)... echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)...
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
cd .. cd ..
git describe --tags --abbrev=0 > C:\projects\obs-studio-last-tag-built.txt git describe --tags --abbrev=0 > C:\projects\obs-studio-last-tag-built.txt
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
) else ( ) else (
echo Last OBS tag built is: %OBSLastTagBuilt% echo Last OBS tag built is: %OBSLastTagBuilt%
echo No need to rebuild OBS. echo No need to rebuild OBS.
) )

View File

@ -1,32 +1,61 @@
#!/bin/sh #!/bin/sh
set -ex
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 # OBS Studio deps
brew update echo "[obs-websocket] Updating Homebrew.."
brew install ffmpeg brew update >/dev/null
brew install libav 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 # qtwebsockets deps
# qt latest echo "[obs-websocket] Installing obs-websocket dependency 'QT 5.10.1'.."
#brew install qt5 # =!= 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 =!=
# qt 5.9.2 brew install https://gist.githubusercontent.com/DDRBoxman/b3956fab6073335a4bf151db0dcbd4ad/raw/ed1342a8a86793ea8c10d8b4d712a654da121ace/qt.rb
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/2b121c9a96e58a5da14228630cb71d5bead7137e/Formula/qt.rb
#echo "Qt path: $(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)" # Pin this version of QT5 to avoid `brew upgrade`
# upgrading it to incompatible version
brew pin qt
# Build obs-studio # Fetch and install Packages app
cd .. # =!= NOTICE =!=
git clone --recursive https://github.com/jp9000/obs-studio # Installs a LaunchDaemon under /Library/LaunchDaemons/fr.whitebox.packages.build.dispatcher.plist
cd obs-studio # =!= NOTICE =!=
git checkout 20.1.0
mkdir build && cd build
cmake .. \
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \
&& make -j4
# Packages app HAS_PACKAGES=$(type packagesbuild 2>/dev/null)
cd ..
curl -L -O http://s.sudre.free.fr/Software/files/Packages.dmg -f --retry 5 -C - if [ "${HAS_PACKAGES}" = "" ]; then
hdiutil attach ./Packages.dmg echo "[obs-websocket] Installing Packaging app (might require password due to 'sudo').."
sudo installer -pkg /Volumes/Packages\ 1.2.2/packages/Packages.pkg -target / 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

@ -1,57 +1,19 @@
#!/bin/sh #!/bin/sh
set -ex set -ex
# OBS Studio deps add-apt-repository -y ppa:obsproject/obs-studio
apt-get -qq update apt-get -qq update
apt-get install -y \
libc-dev-bin libc6-dev \
git \
build-essential
apt-get install -y \ apt-get install -y \
build-essential \ libc-dev-bin \
checkinstall \ libc6-dev git \
cmake \ build-essential \
libasound2-dev \ checkinstall \
libavcodec-dev \ cmake \
libavdevice-dev \ obs-studio \
libavfilter-dev \ qtbase5-dev
libavformat-dev \
libavutil-dev \
libcurl4-openssl-dev \
libfontconfig-dev \
libfreetype6-dev \
libgl1-mesa-dev \
libjack-jackd2-dev \
libjansson-dev \
libpulse-dev \
libqt5x11extras5-dev \
libspeexdsp-dev \
libswresample-dev \
libswscale-dev \
libudev-dev \
libv4l-dev \
libvlc-dev \
libx11-dev \
libx264-dev \
libxcb-shm0-dev \
libxcb-xinerama0-dev \
libxcomposite-dev \
libxinerama-dev \
pkg-config \
qtbase5-dev
# obs-websocket deps # Dirty hack
apt-get install -y libqt5websockets5-dev wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/master/UI/obs-frontend-api/obs-frontend-api.h
# Build obs-studio
cd /root
git clone https://github.com/jp9000/obs-studio ./obs-studio
cd obs-studio
git checkout 20.1.0
mkdir build && cd build
cmake -DUNIX_STRUCTURE=1 -DCMAKE_INSTALL_PREFIX=/usr ..
make -j4
make install
ldconfig ldconfig

View File

@ -1,20 +1,6 @@
@echo off @echo off
REM Set default values to use AppVeyor's built-in Qt. REM Set default values to use AppVeyor's built-in Qt.
set QTDIR32=C:\Qt\5.7\msvc2013 set QTDIR32=C:\Qt\5.10.1\msvc2015
set QTDIR64=C:\Qt\5.7\msvc2013_64 set QTDIR64=C:\Qt\5.10.1\msvc2015_64
set QTCompileVersion=5.7.1 set QTCompileVersion=5.10.1
REM If the AppVeyor cache couldn't recover qt570.7z,
REM try to fetch Qt 5.7.0 from slepin.fr.
if not exist qt570.7z (
curl -kLO https://www.slepin.fr/obs-plugins/deps/qt570.7z -f --retry 5 -C -
)
REM If qt570.7z exists now, use that instead of AppVeyor's built-in Qt.
if exist qt570.7z (
7z x qt570.7z -o"Qt5.7.0"
set QTDIR32=%CD%\Qt5.7.0\msvc2013
set QTDIR64=%CD%\Qt5.7.0\msvc2013_64
set QTCompileVersion=5.7.0
)

View File

@ -12,123 +12,6 @@
<dict> <dict>
<key>CHILDREN</key> <key>CHILDREN</key>
<array> <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/QtNetwork</string>
<key>PATH_TYPE</key>
<integer>1</integer>
<key>PERMISSIONS</key>
<integer>292</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../build/QtWebSockets</string>
<key>PATH_TYPE</key>
<integer>1</integer>
<key>PERMISSIONS</key>
<integer>292</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>509</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>Resources</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</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>Contents</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</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.app</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</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>Applications</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict> <dict>
<key>CHILDREN</key> <key>CHILDREN</key>
<array> <array>
@ -635,7 +518,7 @@
<key>OVERWRITE_PERMISSIONS</key> <key>OVERWRITE_PERMISSIONS</key>
<false/> <false/>
<key>VERSION</key> <key>VERSION</key>
<string>4.3.0</string> <string>4.6.0</string>
</dict> </dict>
<key>PROJECT_COMMENTS</key> <key>PROJECT_COMMENTS</key>
<dict> <dict>

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

View File

@ -2,65 +2,39 @@
set -e set -e
echo "-- Preparing package build" OSTYPE=$(uname)
export QT_CELLAR_PREFIX="$(find /usr/local/Cellar/qt -d 1 | tail -n 1)"
export WS_LIB="/usr/local/opt/qt/lib/QtWebSockets.framework/QtWebSockets" if [ "${OSTYPE}" != "Darwin" ]; then
export NET_LIB="/usr/local/opt/qt/lib/QtNetwork.framework/QtNetwork" 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)"
export GIT_HASH=$(git rev-parse --short HEAD) export GIT_HASH=$(git rev-parse --short HEAD)
export GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}')
export VERSION="$GIT_HASH-$TRAVIS_BRANCH" export VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
export LATEST_VERSION="$TRAVIS_BRANCH" export LATEST_VERSION="$GIT_BRANCH_OR_TAG"
if [ -n "${TRAVIS_TAG}" ]; then
export VERSION="$TRAVIS_TAG"
export LATEST_VERSION="$TRAVIS_TAG"
fi
export FILENAME="obs-websocket-$VERSION.pkg" export FILENAME="obs-websocket-$VERSION.pkg"
export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg" export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg"
echo "-- Copying Qt dependencies" echo "[obs-websocket] Modifying obs-websocket.so"
cp $WS_LIB ./build
cp $NET_LIB ./build
chmod +rw ./build/QtWebSockets ./build/QtNetwork
echo "-- Modifying QtNetwork"
install_name_tool \ install_name_tool \
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
-change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
./build/QtNetwork
echo "-- Modifying QtWebSockets"
install_name_tool \
-change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
-change $QT_CELLAR_PREFIX/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
-change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
./build/QtWebSockets
echo "-- Modifying obs-websocket.so"
install_name_tool \
-change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \ -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \ -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \ -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
./build/obs-websocket.so ./build/obs-websocket.so
# Check if replacement worked # Check if replacement worked
echo "-- Dependencies for QtNetwork" echo "[obs-websocket] Dependencies for obs-websocket"
otool -L ./build/QtNetwork
echo "-- Dependencies for QtWebSockets"
otool -L ./build/QtWebSockets
echo "-- Dependencies for obs-websocket"
otool -L ./build/obs-websocket.so otool -L ./build/obs-websocket.so
chmod -w ./build/QtWebSockets ./build/QtNetwork echo "[obs-websocket] Actual package build"
echo "-- Actual package build"
packagesbuild ./CI/macos/obs-websocket.pkgproj packagesbuild ./CI/macos/obs-websocket.pkgproj
echo "-- Renaming obs-websocket.pkg to $FILENAME" echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
mv ./release/obs-websocket.pkg ./release/$FILENAME mv ./release/obs-websocket.pkg ./release/$FILENAME
cp ./release/$FILENAME ./release/$LATEST_FILENAME cp ./release/$FILENAME ./release/$LATEST_FILENAME

View File

@ -17,7 +17,7 @@ PAGER=cat checkinstall -y --type=debian --fstrans=no --nodoc \
--backup=no --deldoc=yes --install=no \ --backup=no --deldoc=yes --install=no \
--pkgname=obs-websocket --pkgversion="$PKG_VERSION" \ --pkgname=obs-websocket --pkgversion="$PKG_VERSION" \
--pkglicense="GPLv2.0" --maintainer="contact@slepin.fr" \ --pkglicense="GPLv2.0" --maintainer="contact@slepin.fr" \
--requires="libqt5websockets5" --pkggroup="video" \ --pkggroup="video" \
--pkgsource="https://github.com/Palakis/obs-websocket" \ --pkgsource="https://github.com/Palakis/obs-websocket" \
--pakdir="/package" --pakdir="/package"

View File

@ -6,19 +6,30 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOUIC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (WIN32 OR APPLE)
include(external/FindLibObs.cmake) include(external/FindLibObs.cmake)
endif()
add_definitions(-DASIO_STANDALONE)
if (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endif()
if (WIN32 OR APPLE)
include(external/FindLibObs.cmake)
endif()
find_package(LibObs REQUIRED) find_package(LibObs REQUIRED)
find_package(Qt5Core REQUIRED) find_package(Qt5Core REQUIRED)
find_package(Qt5WebSockets REQUIRED)
find_package(Qt5Widgets REQUIRED) find_package(Qt5Widgets REQUIRED)
add_subdirectory(deps/mbedtls EXCLUDE_FROM_ALL)
set(ENABLE_PROGRAMS false)
set(obs-websocket_SOURCES set(obs-websocket_SOURCES
src/obs-websocket.cpp src/obs-websocket.cpp
src/WSServer.cpp src/WSServer.cpp
src/ConnectionProperties.cpp
src/WSRequestHandler.cpp src/WSRequestHandler.cpp
src/WSRequestHandler_General.cpp src/WSRequestHandler_General.cpp
src/WSRequestHandler_Profiles.cpp src/WSRequestHandler_Profiles.cpp
@ -39,6 +50,7 @@ set(obs-websocket_SOURCES
set(obs-websocket_HEADERS set(obs-websocket_HEADERS
src/obs-websocket.h src/obs-websocket.h
src/WSServer.h src/WSServer.h
src/ConnectionProperties.h
src/WSRequestHandler.h src/WSRequestHandler.h
src/WSEvents.h src/WSEvents.h
src/Config.h src/Config.h
@ -50,22 +62,17 @@ add_library(obs-websocket MODULE
${obs-websocket_SOURCES} ${obs-websocket_SOURCES}
${obs-websocket_HEADERS}) ${obs-websocket_HEADERS})
add_dependencies(obs-websocket mbedcrypto)
include_directories( include_directories(
"${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api" "${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api"
${Qt5Core_INCLUDES} ${Qt5Core_INCLUDES}
${Qt5WebSockets_INCLUDES}
${Qt5Widgets_INCLUDES} ${Qt5Widgets_INCLUDES}
${mbedcrypto_INCLUDES} "${CMAKE_SOURCE_DIR}/deps/asio/asio/include"
"${CMAKE_SOURCE_DIR}/deps/mbedtls/include") "${CMAKE_SOURCE_DIR}/deps/websocketpp")
target_link_libraries(obs-websocket target_link_libraries(obs-websocket
libobs libobs
Qt5::Core Qt5::Core
Qt5::WebSockets Qt5::Widgets)
Qt5::Widgets
mbedcrypto)
# --- End of section --- # --- End of section ---
@ -73,9 +80,11 @@ target_link_libraries(obs-websocket
if(WIN32) if(WIN32)
if(NOT DEFINED OBS_FRONTEND_LIB) if(NOT DEFINED OBS_FRONTEND_LIB)
set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library") set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library")
message(FATAL_ERROR "Could not find OBS Frontend API\'s library !") message(FATAL_ERROR "Could not find OBS Frontend API's library !")
endif() endif()
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
if(CMAKE_SIZEOF_VOID_P EQUAL 8) if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(ARCH_NAME "64bit") set(ARCH_NAME "64bit")
set(OBS_BUILDDIR_ARCH "build64") set(OBS_BUILDDIR_ARCH "build64")
@ -91,23 +100,12 @@ if(WIN32)
target_link_libraries(obs-websocket target_link_libraries(obs-websocket
"${OBS_FRONTEND_LIB}") "${OBS_FRONTEND_LIB}")
add_custom_command(TARGET obs-websocket POST_BUILD
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
"${QTDIR}/bin/Qt5WebSockets.dll"
"${QTDIR}/bin/Qt5Network.dll"
"${CMAKE_BINARY_DIR}/$<CONFIG>")
COMMAND if $<CONFIG:Debug>==1 ("${CMAKE_COMMAND}" -E copy
"${QTDIR}/bin/Qt5WebSocketsd.dll"
"${QTDIR}/bin/Qt5Networkd.dll"
"${CMAKE_BINARY_DIR}/$<CONFIG>")
)
# --- Release package helper --- # --- Release package helper ---
# The "release" folder has a structure similar OBS' one on Windows # The "release" folder has a structure similar OBS' one on Windows
set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release") set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release")
add_custom_command(TARGET obs-websocket POST_BUILD add_custom_command(TARGET obs-websocket POST_BUILD
# If config is Release, package release files
COMMAND if $<CONFIG:Release>==1 ( COMMAND if $<CONFIG:Release>==1 (
"${CMAKE_COMMAND}" -E make_directory "${CMAKE_COMMAND}" -E make_directory
"${RELEASE_DIR}/data/obs-plugins/obs-websocket" "${RELEASE_DIR}/data/obs-plugins/obs-websocket"
@ -119,16 +117,35 @@ if(WIN32)
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:obs-websocket>" "$<TARGET_FILE:obs-websocket>"
"${QTDIR}/bin/Qt5WebSockets.dll" "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
"${QTDIR}/bin/Qt5Network.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}") "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
# Copy to obs-studio dev environment for immediate testing # Copy to obs-studio dev environment for immediate testing
COMMAND if $<CONFIG:Debug>==1 ( COMMAND if $<CONFIG:Debug>==1 (
"${CMAKE_COMMAND}" -E copy "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:obs-websocket>" "$<TARGET_FILE:obs-websocket>"
"${QTDIR}/bin/Qt5WebSocketsd.dll" "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
"${QTDIR}/bin/Qt5Networkd.dll"
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}") "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
COMMAND if $<CONFIG:Debug>==1 ( COMMAND if $<CONFIG:Debug>==1 (
@ -147,17 +164,18 @@ endif()
# --- Linux-specific build settings and tasks --- # --- Linux-specific build settings and tasks ---
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
target_compile_options(mbedcrypto PRIVATE -fPIC)
set_target_properties(obs-websocket PROPERTIES PREFIX "") set_target_properties(obs-websocket PROPERTIES PREFIX "")
target_link_libraries(obs-websocket target_link_libraries(obs-websocket obs-frontend-api)
obs-frontend-api)
file(GLOB locale_files data/locale/*.ini) file(GLOB locale_files data/locale/*.ini)
execute_process(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE UNAME_MACHINE)
install(TARGETS obs-websocket install(TARGETS obs-websocket
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/obs-plugins") LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/obs-plugins")
# Dirty fix for Ubuntu
install(TARGETS obs-websocket
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/${UNAME_MACHINE}-linux-gnu/obs-plugins")
install(FILES ${locale_files} install(FILES ${locale_files}
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale") DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
endif() endif()
@ -167,6 +185,7 @@ endif()
if(APPLE) if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default")
set(CMAKE_SKIP_RPATH TRUE)
set_target_properties(obs-websocket PROPERTIES PREFIX "") set_target_properties(obs-websocket PROPERTIES PREFIX "")
target_link_libraries(obs-websocket "${OBS_FRONTEND_LIB}") target_link_libraries(obs-websocket "${OBS_FRONTEND_LIB}")
endif() endif()

100
README.md
View File

@ -2,9 +2,9 @@ obs-websocket
============== ==============
Remote control of OBS Studio made easy. Remote control of OBS Studio made easy.
Follow the project on Twitter for news & updates : [@obswebsocket](https://twitter.com/obswebsocket) Follow the main author on Twitter for news & updates : [@LePalakis](https://twitter.com/LePalakis)
[![Gitter chat](https://badges.gitter.im/obs-websocket/obs-websocket.png)](https://gitter.im/obs-websocket/obs-websocket) [![Build Status - Windows](https://ci.appveyor.com/api/projects/status/github/Palakis/obs-websocket)](https://ci.appveyor.com/project/Palakis/obs-websocket/history) [![Build Status - Linux & OS X](https://travis-ci.org/Palakis/obs-websocket.svg?branch=master)](https://travis-ci.org/Palakis/obs-websocket) [![Build Status - Windows](https://ci.appveyor.com/api/projects/status/github/Palakis/obs-websocket)](https://ci.appveyor.com/project/Palakis/obs-websocket/history) [![Build Status - Linux & OS X](https://travis-ci.org/Palakis/obs-websocket.svg?branch=master)](https://travis-ci.org/Palakis/obs-websocket)
## Downloads ## Downloads
Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section. Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
@ -20,6 +20,7 @@ It is **highly recommended** to protect obs-websocket with a password against un
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...) - Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
### For developers ### 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 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). The protocol understood by the server is documented in [PROTOCOL.md](docs/generated/protocol.md).
@ -29,37 +30,92 @@ Here's a list of available language APIs for obs-websocket :
- Python 2 and 3: [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi - 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.5+ with asyncio: [obs-ws-rc](https://github.com/KirillMysnik/obs-ws-rc) by Kirill Mysnik
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 `contact at slepin dot fr` ! 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 ## Compiling obs-websocket
See the [build instructions](BUILDING.md). See the [build instructions](BUILDING.md).
## Contributing
### Branches
The two main development branches are:
- `4.x-current`: actively-maintained codebase for 4.x releases. Backwards-compatible (unless stated otherwise) with existing clients until 5.0.
- `5.x`: upcoming 5.0 version
**New features and fixes must be based off and contributed to `4.x-current`**, as obs-websocket 5.0 is not in active development yet.
### Pull Requests
Pull Requests must never be based off your fork's main branch (in our case, `4.x-current` or `5.x`). 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, tag it with the `work in progress` label. You can also use the `help needed` label if you have questions, need a hand or want to ask for input.
### 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 using to automatically generate the [protocol specification](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");
}
```
is better like this:
```cpp
if (!success) {
return req->SendErrorResponse("something went wrong");
}
return req->SendOKResponse();
```
## Translations ## Translations
**We need your help on translations**. Please join the localization project on Crowdin: https://crowdin.com/project/obs-websocket
**Your help is welcome on translations**. Please join the localization project on Crowdin: https://crowdin.com/project/obs-websocket
## Special thanks ## Special thanks
In order of appearance:
- [Brendan H.](https://github.com/haganbmj) : Code contributions and gooder English in the Protocol specification In (almost) order of appearance:
- [Mikhail Swift](https://github.com/mikhailswift) : Code contributions
- [Tobias Frahmer](https://github.com/Frahmer) : German localization - [Brendan H.](https://github.com/haganbmj): Code contributions and gooder English in the Protocol specification
- [Genture](https://github.com/Genteure) : Simplified Chinese and Traditional Chinese localizations - [Mikhail Swift](https://github.com/mikhailswift): Code contributions
- [Larissa Gabilan](https://github.com/laris151) : Portuguese localization - [Tobias Frahmer](https://github.com/Frahmer): Initial German localization
- [Andy Asquelt](https://github.com/asquelt) : Polish localization - [Genture](https://github.com/Genteure): Initial Simplified Chinese and Traditional Chinese localizations
- [Marcel Haazen](https://github.com/inpothet) : Dutch localization - [Larissa Gabilan](https://github.com/laris151): Initial Portuguese localization
- [Peter Antonvich](https://github.com/pantonvich) : Code contributions - [Andy Asquelt](https://github.com/asquelt): Initial Polish localization
- [yinzara](https://github.com/yinzara) : Code contributions - [Marcel Haazen](https://github.com/nekocentral): Initial Dutch localization
- [Chris Angelico](https://github.com/Rosuav) : Code contributions - [Peter Antonvich](https://github.com/pantonvich): Code contributions
- [Guillaume "Elektordi" Genty](https://github.com/Elektordi) : Code contributions - [yinzara](https://github.com/yinzara): Code contributions
- [Marwin M](https://github.com/dragonbane0) : Code contributions - [Chris Angelico](https://github.com/Rosuav): Code contributions
- [Logan S.](https://github.com/lsdaniel) : Code contributions - [Guillaume "Elektordi" Genty](https://github.com/Elektordi): Code contributions
- [RainbowEK](https://github.com/RainbowEK) : Code contributions - [Marwin M](https://github.com/dragonbane0): Code contributions
- [RytoEX](https://github.com/RytoEX) : CI script and code contributions - [Logan S.](https://github.com/lsdaniel): Code contributions
- [Theodore Stoddard](https://github.com/TStod) : Code contributions - [RainbowEK](https://github.com/RainbowEK): Code contributions
- [Philip Loche](https://github.com/PicoCentauri) : 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! And also: special thanks to supporters of the project!
## Supporters ## Supporters
They have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them! They have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them!
--- ---

View File

@ -4,20 +4,20 @@ environment:
install: install:
- git submodule update --init --recursive - git submodule update --init --recursive
- cd C:\projects\ - cd C:\projects\
- if not exist dependencies2013.zip curl -kLO https://obsproject.com/downloads/dependencies2013.zip -f --retry 5 -C - - if not exist dependencies2015.zip curl -kLO https://obsproject.com/downloads/dependencies2015.zip -f --retry 5 -C -
- 7z x dependencies2013.zip -odependencies2013 - 7z x dependencies2015.zip -odependencies2015
- set DepsPath32=%CD%\dependencies2013\win32 - set DepsPath32=%CD%\dependencies2015\win32
- set DepsPath64=%CD%\dependencies2013\win64 - set DepsPath64=%CD%\dependencies2015\win64
- call C:\projects\obs-websocket\CI\install-setup-qt.cmd - call C:\projects\obs-websocket\CI\install-setup-qt.cmd
- set build_config=Release - set build_config=RelWithDebInfo
- call C:\projects\obs-websocket\CI\install-build-obs.cmd - call C:\projects\obs-websocket\CI\install-build-obs.cmd
- cd C:\projects\obs-websocket\ - cd C:\projects\obs-websocket\
- mkdir build32 - mkdir build32
- mkdir build64 - mkdir build64
- cd ./build32 - cd ./build32
- cmake -G "Visual Studio 12 2013" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. - cmake -G "Visual Studio 14 2015" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
- cd ../build64 - cd ../build64
- cmake -G "Visual Studio 12 2013 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. - cmake -G "Visual Studio 14 2015 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
build_script: build_script:
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
@ -25,14 +25,16 @@ build_script:
before_deploy: before_deploy:
- 7z a "C:\projects\obs-websocket\build.zip" C:\projects\obs-websocket\release\* - 7z a "C:\projects\obs-websocket\build.zip" C:\projects\obs-websocket\release\*
- set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5"
- iscc "C:\projects\obs-websocket\installer\installer.iss"
deploy_script: deploy_script:
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1).zip" - ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows.zip"
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\installer\Output\obs-websocket-Windows-Installer.exe" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows-Installer.exe"
test: off test: off
cache: cache:
- C:\projects\dependencies2013.zip - C:\projects\dependencies2015.zip
- C:\projects\qt570.7z
- C:\projects\obs-studio-last-tag-built.txt - C:\projects\obs-studio-last-tag-built.txt
- C:\projects\obs-studio\ - C:\projects\obs-studio\

23
azure-pipelines.yml Normal file
View File

@ -0,0 +1,23 @@
pool:
vmImage: 'macOS-10.13'
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'
- script: ./CI/package-macos.sh
displayName: 'Package'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: './release'
artifactName: 'build'

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

View File

@ -1,14 +1,16 @@
OBSWebsocket.Menu.SettingsItem="Websocket-Server Einstellungen" OBSWebsocket.Settings.DialogTitle="WebSockets-Servereinstellungen"
OBSWebsocket.Settings.DialogTitle="Websocket-Server Einstellungen" OBSWebsocket.Settings.ServerEnable="WebSockets-Server aktivieren"
OBSWebsocket.Settings.ServerEnable="Websocket-Server aktivieren" OBSWebsocket.Settings.ServerPort="Server-Port"
OBSWebsocket.Settings.ServerPort="Server Port" OBSWebsocket.Settings.AuthRequired="Authentifizierung aktivieren"
OBSWebsocket.Settings.AuthRequired="Authentifizierung erforderlich"
OBSWebsocket.Settings.Password="Passwort" OBSWebsocket.Settings.Password="Passwort"
OBSWebsocket.Settings.DebugEnable="Debug-Protokollierung aktivieren" OBSWebsocket.Settings.DebugEnable="Debug-Protokollierung aktivieren"
OBSWebsocket.Settings.AlertsEnable="Infobereich-Benachrichtigungen aktivieren" OBSWebsocket.Settings.AlertsEnable="Infobereichbenachrichtigungen aktivieren"
OBSWebsocket.NotifyConnect.Title="Neue WebSocket Verbindung" OBSWebsocket.NotifyConnect.Title="Neue Websocket-Verbindung"
OBSWebsocket.NotifyConnect.Message="Client %1 verbunden" OBSWebsocket.NotifyConnect.Message="Client %1 verbunden"
OBSWebsocket.NotifyDisconnect.Title="WebSocket-Client getrennt" OBSWebsocket.NotifyDisconnect.Title="Websocket-Client getrennt"
OBSWebsocket.NotifyDisconnect.Message="Client %1 getrennt" OBSWebsocket.NotifyDisconnect.Message="Client %1 getrennt"
OBSWebsocket.Server.StartFailed.Title="WebSocket-Server Fehler" OBSWebsocket.Server.StartFailed.Title="Websocket-Serverfehler"
OBSWebsocket.Server.StartFailed.Message="Der WebSocket-Server konnte nicht gestartet werden, mögliche Gründe:\n - TCP Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den WebSocket-Server Einstellungen zu setzten oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Ein unbekannter Netzwerkfehler ist aufgetreten. Versuchen Sie es erneut mit anderen Einstellungen, einem OBS neustart oder einem System neustart." 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,6 +1,5 @@
OBSWebsocket.Menu.SettingsItem="Websocket server settings" OBSWebsocket.Settings.DialogTitle="WebSockets Server Settings"
OBSWebsocket.Settings.DialogTitle="obs-websocket" OBSWebsocket.Settings.ServerEnable="Enable WebSockets server"
OBSWebsocket.Settings.ServerEnable="Enable Websocket server"
OBSWebsocket.Settings.ServerPort="Server Port" OBSWebsocket.Settings.ServerPort="Server Port"
OBSWebsocket.Settings.AuthRequired="Enable authentication" OBSWebsocket.Settings.AuthRequired="Enable authentication"
OBSWebsocket.Settings.Password="Password" OBSWebsocket.Settings.Password="Password"
@ -10,5 +9,8 @@ OBSWebsocket.NotifyConnect.Title="New WebSocket connection"
OBSWebsocket.NotifyConnect.Message="Client %1 connected" OBSWebsocket.NotifyConnect.Message="Client %1 connected"
OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected" OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected"
OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected" OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected"
OBSWebsocket.Server.StartFailed.Title="WebSocket Server failure" OBSWebsocket.Server.StartFailed.Title="WebSockets Server failure"
OBSWebsocket.Server.StartFailed.Message="The obs-websocket 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 - An unknown network error happened on your system. Try again by changing settings, restarting OBS or restarting your system." 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 - An unknown network error happened on your system. Try again by changing settings, restarting OBS or restarting your system."
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."

View File

@ -1,14 +1,12 @@
OBSWebsocket.Menu.SettingsItem="Configuración del servidor obs-websocket" OBSWebsocket.Settings.ServerEnable="Habilitar el servidor WebSockets"
OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Habilitar el servidor Websocket"
OBSWebsocket.Settings.ServerPort="Puerto del Servidor" OBSWebsocket.Settings.ServerPort="Puerto del Servidor"
OBSWebsocket.Settings.AuthRequired="Habilitar autenticación" OBSWebsocket.Settings.AuthRequired="Habilitar autenticación"
OBSWebsocket.Settings.Password="Contraseña" OBSWebsocket.Settings.Password="Contraseña"
OBSWebsocket.Settings.DebugEnable="Habilitar registro de depuración" OBSWebsocket.Settings.DebugEnable="Habilitar registro de depuración"
OBSWebsocket.Settings.AlertsEnable="Habilitar alertas de la bandeja de sistema" OBSWebsocket.Settings.AlertsEnable="Habilitar alertas en la bandeja de sistema"
OBSWebsocket.NotifyConnect.Title="Nueva conexión WebSocket" OBSWebsocket.NotifyConnect.Title="Nueva conexión WebSocket"
OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado" OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
OBSWebsocket.NotifyDisconnect.Title="Cliente WebSocket desconectado" OBSWebsocket.NotifyDisconnect.Title="Cliente WebSocket desconectado"
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado" OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
OBSWebsocket.Server.StartFailed.Title="Fallo del servidor WebSocket" 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 en uso en 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 ocurrido en su sistema. Inténtalo de nuevo cambiando la configuración, reiniciando OBS o reiniciando su sistema." 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,4 @@
OBSWebsocket.Menu.SettingsItem="Paramètres du serveur Websocket" OBSWebsocket.Settings.DialogTitle="Paramètres du serveur WebSockets"
OBSWebsocket.Settings.ServerEnable="Activer le serveur WebSocket" OBSWebsocket.Settings.ServerEnable="Activer le serveur WebSocket"
OBSWebsocket.Settings.ServerPort="Port du serveur" OBSWebsocket.Settings.ServerPort="Port du serveur"
OBSWebsocket.Settings.AuthRequired="Activer l'authentification" OBSWebsocket.Settings.AuthRequired="Activer l'authentification"
@ -9,5 +9,8 @@ OBSWebsocket.NotifyConnect.Title="Nouvelle connexion WebSocket"
OBSWebsocket.NotifyConnect.Message="Le client %1 s'est connecté" OBSWebsocket.NotifyConnect.Message="Le client %1 s'est connecté"
OBSWebsocket.NotifyDisconnect.Title="Déconnexion WebSocket" OBSWebsocket.NotifyDisconnect.Title="Déconnexion WebSocket"
OBSWebsocket.NotifyDisconnect.Message="Le client %1 s'est déconnecté" OBSWebsocket.NotifyDisconnect.Message="Le client %1 s'est déconnecté"
OBSWebsocket.Server.StartFailed.Title="Impossible de démarrer le serveur WebSocket" OBSWebsocket.Server.StartFailed.Title="Impossible de démarrer le serveur WebSockets"
OBSWebsocket.Server.StartFailed.Message="Le serveur WebSocket 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.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

View File

@ -1,6 +1,4 @@
OBSWebsocket.Menu.SettingsItem="Impostazioni del server di WebSocket" OBSWebsocket.Settings.ServerEnable="Abilitare il server WebSockets"
OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Abilitare il server Websocket"
OBSWebsocket.Settings.ServerPort="Porta del server" OBSWebsocket.Settings.ServerPort="Porta del server"
OBSWebsocket.Settings.AuthRequired="Abilitare l'autenticazione" OBSWebsocket.Settings.AuthRequired="Abilitare l'autenticazione"
OBSWebsocket.Settings.Password="Password" OBSWebsocket.Settings.Password="Password"
@ -11,4 +9,4 @@ OBSWebsocket.NotifyConnect.Message="%1 cliente collegato"
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente disconnesso" OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente disconnesso"
OBSWebsocket.NotifyDisconnect.Message="%1 cliente disconnesso" OBSWebsocket.NotifyDisconnect.Message="%1 cliente disconnesso"
OBSWebsocket.Server.StartFailed.Title="Errore del WebSocket Server" OBSWebsocket.Server.StartFailed.Title="Errore del WebSocket Server"
OBSWebsocket.Server.StartFailed.Message="Impossibile avviare, forse perché il server di obs-websocket: \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 WebSocket, 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." 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."

View File

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

View File

@ -1,14 +1,9 @@
OBSWebsocket.Menu.SettingsItem="Websocket server instellingen"
OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Activeer Websocket server"
OBSWebsocket.Settings.ServerPort="Serverpoort" OBSWebsocket.Settings.ServerPort="Serverpoort"
OBSWebsocket.Settings.AuthRequired="Activeer authenticatie"
OBSWebsocket.Settings.Password="Wachtwoord"
OBSWebsocket.Settings.DebugEnable="Activeer debug logs" OBSWebsocket.Settings.DebugEnable="Activeer debug logs"
OBSWebsocket.Settings.AlertsEnable="Systemvak waarschuwingen inschakelen" OBSWebsocket.Settings.AlertsEnable="Systemvak waarschuwingen inschakelen"
OBSWebsocket.NotifyConnect.Title="Nieuwe WebSocket verbinding" OBSWebsocket.NotifyConnect.Title="Nieuwe WebSocket verbinding"
OBSWebsocket.NotifyConnect.Message="Client %1 verbonden" OBSWebsocket.NotifyConnect.Message="Client %1 verbonden"
OBSWebsocket.NotifyDisconnect.Title="WebSocket client connectie verbroken" OBSWebsocket.NotifyDisconnect.Title="WebSocket client connectie verbroken"
OBSWebsocket.NotifyDisconnect.Message="Client %1 losgekoppeld" OBSWebsocket.NotifyDisconnect.Message="Client %1 losgekoppeld"
OBSWebsocket.Server.StartFailed.Title="WebSocket Server mislukt" 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 wordt gebruikt elders op dit systeem, eventueel door een andere toepassing. Probeer een andere TCP-poort instellen in de WebSocket Server-instellingen of stoppen van elke toepassing die deze poort zouden kunnen gebruiken.\n Een onbekende netwerkfout gebeurde op uw systeem. Probeer het opnieuw door de instellingen wijzigen, OBS herstarten of opnieuw opstarten van uw systeem." 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."

View File

@ -1,14 +1,10 @@
OBSWebsocket.Menu.SettingsItem="Ustawienia serwera zdalnego sterowania" OBSWebsocket.Settings.ServerEnable="Włącz serwer WebSockets"
OBSWebsocket.Settings.DialogTitle="Serwer zdalnego sterowania" OBSWebsocket.Settings.AuthRequired="Wymagaj uwierzytelniania"
OBSWebsocket.Settings.ServerEnable="Włącz serwer zdalnego sterowania (Websocket)"
OBSWebsocket.Settings.ServerPort="Port serwera"
OBSWebsocket.Settings.AuthRequired="Wymagaj hasła"
OBSWebsocket.Settings.Password="Hasło"
OBSWebsocket.Settings.DebugEnable="Włącz rejestrowanie debugowania" OBSWebsocket.Settings.DebugEnable="Włącz rejestrowanie debugowania"
OBSWebsocket.Settings.AlertsEnable="Włącz powiadomienia o zasobniku systemowym" OBSWebsocket.Settings.AlertsEnable="Włącz powiadomienia w zasobniku systemowym"
OBSWebsocket.NotifyConnect.Title="Nowe połączenie WebSocket" OBSWebsocket.NotifyConnect.Title="Nowe połączenie WebSocket"
OBSWebsocket.NotifyConnect.Message="Klient %1 połączony" OBSWebsocket.NotifyConnect.Message="Klient %1 połączony"
OBSWebsocket.NotifyDisconnect.Title="Klient WebSocket odłączony" OBSWebsocket.NotifyDisconnect.Title="Klient WebSocket odłączony"
OBSWebsocket.NotifyDisconnect.Message="Klient %1 połączony" OBSWebsocket.NotifyDisconnect.Message="Klient %1 rozłączony"
OBSWebsocket.Server.StartFailed.Title="Awaria serwera WebSocket" OBSWebsocket.Server.StartFailed.Title="Awaria serwera WebSockets"
OBSWebsocket.Server.StartFailed.Message="Nie udało się uruchomić serwera obs-websocket, 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 WebSocket, 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." 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."

View File

@ -1,5 +0,0 @@
OBSWebsocket.Menu.SettingsItem="Configuraçes do Servidor Websocket"
OBSWebsocket.Settings.ServerEnable="Habilitar o Servidor Websocket"
OBSWebsocket.Settings.ServerPort="Porta do Servidor"
OBSWebsocket.Settings.AuthRequired="Autenticação Requerida"
OBSWebsocket.Settings.Password="Senha"

View File

@ -1,5 +1,3 @@
OBSWebsocket.Menu.SettingsItem="Configurações do servidor Websocket"
OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Habilitar servidor de WebSockets" OBSWebsocket.Settings.ServerEnable="Habilitar servidor de WebSockets"
OBSWebsocket.Settings.ServerPort="Porta do Servidor" OBSWebsocket.Settings.ServerPort="Porta do Servidor"
OBSWebsocket.Settings.AuthRequired="Activar autenticação" OBSWebsocket.Settings.AuthRequired="Activar autenticação"
@ -11,4 +9,4 @@ OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente desconectado" OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente desconectado"
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado" OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
OBSWebsocket.Server.StartFailed.Title="Falha do servidor de WebSocket" OBSWebsocket.Server.StartFailed.Title="Falha do servidor de WebSocket"
OBSWebsocket.Server.StartFailed.Message="O servidor obs-websocket 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 WebSocket, 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." 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."

View File

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

View File

@ -1,6 +1,12 @@
OBSWebsocket.Menu.SettingsItem="Websocket 服务器设置" OBSWebsocket.Settings.ServerEnable="启用 WebSockets 服务器"
OBSWebsocket.Settings.DialogTitle="obs-websocket 设置"
OBSWebsocket.Settings.ServerEnable="启用 Websocket 服务器"
OBSWebsocket.Settings.ServerPort="服务器端口" OBSWebsocket.Settings.ServerPort="服务器端口"
OBSWebsocket.Settings.AuthRequired="启用密码认证" OBSWebsocket.Settings.AuthRequired="启用身份验证"
OBSWebsocket.Settings.Password="密码" 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 或重新启动系统。"

View File

@ -1,6 +1,9 @@
OBSWebsocket.Menu.SettingsItem="Websocket 伺服器設定" OBSWebsocket.Settings.ServerPort="伺服器連接埠"
OBSWebsocket.Settings.DialogTitle="obs-websocket 設定" OBSWebsocket.Settings.DebugEnable="啟用除錯日誌"
OBSWebsocket.Settings.ServerEnable="啟用 Websocket 伺服器" OBSWebsocket.Settings.AlertsEnable="啟用系統列通知"
OBSWebsocket.Settings.ServerPort="伺服器端口" OBSWebsocket.NotifyConnect.Title="新的 WebSocket 連線"
OBSWebsocket.Settings.AuthRequired="啟用密碼認證" OBSWebsocket.NotifyConnect.Message="客戶端 %1 已連線"
OBSWebsocket.Settings.Password="密碼" 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

View File

@ -30,6 +30,14 @@ const processComments = comments => {
let errors = []; let errors = [];
comments.forEach(comment => { 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; if (typeof comment.api === 'undefined') return;
let validationFailures = validateComment(comment); let validationFailures = validateComment(comment);
@ -84,9 +92,7 @@ const validateComment = comment => {
fullContext: Object.assign({}, comment) fullContext: Object.assign({}, comment)
}; };
} }
};
return;
}
const files = glob.sync(config.srcGlob); const files = glob.sync(config.srcGlob);
const comments = processComments(parseFiles(files)); const comments = processComments(parseFiles(files));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,11 @@
# obs-websocket 4.2.1 protocol reference # obs-websocket 4.6.0 protocol reference
**This is the reference for the unreleased obs-websocket 4.2.1. See the list below for older versions.**
- [4.2.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.2.0/docs/generated/protocol.md)
- [4.1.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.1.0/PROTOCOL.md)
- [4.0.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.0.0/PROTOCOL.md)
# General Introduction # General Introduction
Messages are exchanged between the client and the server as JSON objects. 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. This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
# Authentication # Authentication
OBSWebSocket uses SHA256 to transmit credentials. `obs-websocket` uses SHA256 to transmit credentials.
A request for [`GetAuthRequired`](#getauthrequired) returns two elements: A request for [`GetAuthRequired`](#getauthrequired) returns two elements:
- A `challenge`: a random string that will be used to generate the auth response. - A `challenge`: a random string that will be used to generate the auth response.

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.

View File

@ -7,6 +7,19 @@
{{#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}} {{#read "partials/eventsHeader.md"}}{{/read}}
{{#each events}} {{#each events}}

View File

@ -2,8 +2,8 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "obs-websocket" #define MyAppName "obs-websocket"
#define MyAppVersion "4.3.0" #define MyAppVersion "4.6.0"
#define MyAppPublisher "St<EFBFBD>phane Lepin" #define MyAppPublisher "Stephane Lepin"
#define MyAppURL "http://github.com/Palakis/obs-websocket" #define MyAppURL "http://github.com/Palakis/obs-websocket"
[Setup] [Setup]
@ -20,17 +20,16 @@ AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL} AppUpdatesURL={#MyAppURL}
DefaultDirName={code:GetDirName} DefaultDirName={code:GetDirName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}
OutputBaseFilename=obs-websocket-{#MyAppVersion}-Windows-Installer OutputBaseFilename=obs-websocket-Windows-Installer
Compression=lzma Compression=lzma
SolidCompression=yes SolidCompression=yes
LicenseFile=..\LICENSE
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
[Files] [Files]
Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\LICENSE"; Flags: dontcopy
; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons] [Icons]
@ -38,17 +37,32 @@ Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
[Code] [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 : // 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 // 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; function GetDirName(Value: string): string;
var var
InstallPath: string; InstallPath: string;
begin begin
// initialize default path, which will be returned when the following registry // initialize default path, which will be returned when the following registry
// key queries fail due to missing keys or for some different reason // key queries fail due to missing keys or for some different reason
Result := '{pf}\obs-studio'; Result := '{pf}\obs-studio';
// query the first registry value; if this succeeds, return the obtained value // query the first registry value; if this succeeds, return the obtained value
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
Result := InstallPath Result := InstallPath
end; end;

View File

@ -16,11 +16,11 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> 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 <obs-frontend-api.h>
#include <util/config-file.h>
#include <string> #include <QtCore/QCryptographicHash>
#include <QtCore/QTime>
#include <QtWidgets/QSystemTrayIcon>
#define SECTION_NAME "WebsocketAPI" #define SECTION_NAME "WebsocketAPI"
#define PARAM_ENABLE "ServerEnabled" #define PARAM_ENABLE "ServerEnabled"
@ -31,164 +31,256 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define PARAM_SECRET "AuthSecret" #define PARAM_SECRET "AuthSecret"
#define PARAM_SALT "AuthSalt" #define PARAM_SALT "AuthSalt"
#include "Config.h"
#include "Utils.h" #include "Utils.h"
#include "WSServer.h"
Config* Config::_instance = new Config(); #include "Config.h"
#define QT_TO_UTF8(str) str.toUtf8().constData()
Config::Config() : Config::Config() :
ServerEnabled(true), ServerEnabled(true),
ServerPort(4444), ServerPort(4444),
DebugEnabled(false), DebugEnabled(false),
AlertsEnabled(true), AlertsEnabled(true),
AuthRequired(false), AuthRequired(false),
Secret(""), Secret(""),
Salt(""), Salt(""),
SettingsLoaded(false) SettingsLoaded(false)
{ {
// OBS Config defaults qsrand(QTime::currentTime().msec());
config_t* obsConfig = obs_frontend_get_global_config();
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, SetDefaults();
SECTION_NAME, PARAM_DEBUG, DebugEnabled); SessionChallenge = GenerateSalt();
config_set_default_bool(obsConfig,
SECTION_NAME, PARAM_ALERT, AlertsEnabled);
config_set_default_bool(obsConfig, obs_frontend_add_event_callback(OnFrontendEvent, this);
SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_default_string(obsConfig,
SECTION_NAME, PARAM_SECRET, qstring_data_copy(Secret));
config_set_default_string(obsConfig,
SECTION_NAME, PARAM_SALT, qstring_data_copy(Salt));
}
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&rng);
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
SessionChallenge = GenerateSalt();
} }
Config::~Config() { Config::~Config()
mbedtls_ctr_drbg_free(&rng); {
mbedtls_entropy_free(&entropy); obs_frontend_remove_event_callback(OnFrontendEvent, this);
} }
void Config::Load() { void Config::Load()
config_t* obsConfig = obs_frontend_get_global_config(); {
config_t* obsConfig = GetConfigStore();
ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE); ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE);
ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT); ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT);
DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG); DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG);
AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT); AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT);
AuthRequired = config_get_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED); AuthRequired = config_get_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED);
Secret = config_get_string(obsConfig, SECTION_NAME, PARAM_SECRET); Secret = config_get_string(obsConfig, SECTION_NAME, PARAM_SECRET);
Salt = config_get_string(obsConfig, SECTION_NAME, PARAM_SALT); Salt = config_get_string(obsConfig, SECTION_NAME, PARAM_SALT);
} }
void Config::Save() { void Config::Save()
config_t* obsConfig = obs_frontend_get_global_config(); {
config_t* obsConfig = GetConfigStore();
config_set_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled); config_set_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
config_set_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort); 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_DEBUG, DebugEnabled);
config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled); config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled);
config_set_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired); config_set_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_string(obsConfig, SECTION_NAME, PARAM_SECRET, config_set_string(obsConfig, SECTION_NAME, PARAM_SECRET,
qstring_data_copy(Secret)); QT_TO_UTF8(Secret));
config_set_string(obsConfig, SECTION_NAME, PARAM_SALT, config_set_string(obsConfig, SECTION_NAME, PARAM_SALT,
qstring_data_copy(Salt)); QT_TO_UTF8(Salt));
config_save(obsConfig); config_save(obsConfig);
} }
QString Config::GenerateSalt() { void Config::SetDefaults()
// Generate 32 random chars {
unsigned char* randomChars = (unsigned char*)bzalloc(32); // OBS Config defaults
mbedtls_ctr_drbg_random(&rng, randomChars, 32); 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);
// Convert the 32 random chars to a base64 string config_set_default_bool(obsConfig,
char* salt = (char*)bzalloc(64); SECTION_NAME, PARAM_DEBUG, DebugEnabled);
size_t saltBytes; config_set_default_bool(obsConfig,
mbedtls_base64_encode( SECTION_NAME, PARAM_ALERT, AlertsEnabled);
(unsigned char*)salt, 64, &saltBytes,
randomChars, 32);
bfree(randomChars); config_set_default_bool(obsConfig,
return salt; 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));
}
} }
QString Config::GenerateSecret(QString password, QString salt) { config_t* Config::GetConfigStore()
// Concatenate the password and the salt {
QString passAndSalt = ""; return obs_frontend_get_profile_config();
passAndSalt += password;
passAndSalt += salt;
// Generate a SHA256 hash of the password
unsigned char* challengeHash = (unsigned char*)bzalloc(32);
mbedtls_sha256(
(unsigned char*)passAndSalt.toUtf8().constData(), passAndSalt.length(),
challengeHash, 0);
// Encode SHA256 hash to Base64
char* challenge = (char*)bzalloc(64);
size_t challengeBytes = 0;
mbedtls_base64_encode(
(unsigned char*)challenge, 64, &challengeBytes,
challengeHash, 32);
bfree(challengeHash);
return challenge;
} }
void Config::SetPassword(QString password) { QString Config::GenerateSalt()
QString newSalt = GenerateSalt(); {
QString newChallenge = GenerateSecret(password, newSalt); // Generate 32 random chars
const size_t randomCount = 32;
QByteArray randomChars;
for (size_t i = 0; i < randomCount; i++) {
randomChars.append((char)qrand());
}
this->Salt = newSalt; // Convert the 32 random chars to a base64 string
this->Secret = newChallenge; QString salt = randomChars.toBase64();
return salt;
} }
bool Config::CheckAuth(QString response) { QString Config::GenerateSecret(QString password, QString salt)
// Concatenate auth secret with the challenge sent to the user {
QString challengeAndResponse = ""; // Concatenate the password and the salt
challengeAndResponse += Secret; QString passAndSalt = "";
challengeAndResponse += SessionChallenge; passAndSalt += password;
passAndSalt += salt;
// Generate a SHA256 hash of challengeAndResponse // Generate a SHA256 hash of the password and salt
unsigned char* hash = (unsigned char*)bzalloc(32); auto challengeHash = QCryptographicHash::hash(
mbedtls_sha256( passAndSalt.toUtf8(),
(unsigned char*)challengeAndResponse.toUtf8().constData(), QCryptographicHash::Algorithm::Sha256
challengeAndResponse.length(), );
hash, 0);
// Encode the SHA256 hash to Base64 // Encode SHA256 hash to Base64
char* expectedResponse = (char*)bzalloc(64); QString challenge = challengeHash.toBase64();
size_t base64_size = 0;
mbedtls_base64_encode(
(unsigned char*)expectedResponse, 64, &base64_size,
hash, 32);
bool authSuccess = false; return challenge;
if (response == QString(expectedResponse)) {
SessionChallenge = GenerateSalt();
authSuccess = true;
}
bfree(hash);
bfree(expectedResponse);
return authSuccess;
} }
Config* Config::Current() { void Config::SetPassword(QString password)
return _instance; {
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 obs-websocket
Copyright (C) 2016-2017 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 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 it under the terms of the GNU General Public License as published by
@ -16,45 +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/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#ifndef CONFIG_H #pragma once
#define CONFIG_H
#include <QString> #include <obs-frontend-api.h>
#include <util/config-file.h>
#include <mbedtls/entropy.h> #include <QtCore/QString>
#include <mbedtls/ctr_drbg.h> #include <QtCore/QSharedPointer>
class Config { class Config {
public: public:
Config(); Config();
~Config(); ~Config();
void Load(); void Load();
void Save(); void Save();
void SetDefaults();
config_t* GetConfigStore();
void SetPassword(QString password); void MigrateFromGlobalSettings();
bool CheckAuth(QString userChallenge);
QString GenerateSalt();
static QString GenerateSecret(
QString password, QString salt);
bool ServerEnabled; void SetPassword(QString password);
uint64_t ServerPort; bool CheckAuth(QString userChallenge);
QString GenerateSalt();
static QString GenerateSecret(
QString password, QString salt);
bool DebugEnabled; bool ServerEnabled;
bool AlertsEnabled; uint64_t ServerPort;
bool AuthRequired; bool DebugEnabled;
QString Secret; bool AlertsEnabled;
QString Salt;
QString SessionChallenge;
bool SettingsLoaded;
static Config* Current(); bool AuthRequired;
QString Secret;
QString Salt;
QString SessionChallenge;
bool SettingsLoaded;
private: private:
static Config* _instance; static void OnFrontendEvent(enum obs_frontend_event event, void* param);
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context rng;
}; };
#endif // CONFIG_H

View File

@ -0,0 +1,34 @@
/*
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/>
*/
#include "ConnectionProperties.h"
ConnectionProperties::ConnectionProperties()
: _authenticated(false)
{
}
bool ConnectionProperties::isAuthenticated()
{
return _authenticated.load();
}
void ConnectionProperties::setAuthenticated(bool authenticated)
{
_authenticated.store(authenticated);
}

View File

@ -0,0 +1,31 @@
/*
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 <atomic>
class ConnectionProperties
{
public:
explicit ConnectionProperties();
bool isAuthenticated();
void setAuthenticated(bool authenticated);
private:
std::atomic<bool> _authenticated;
};

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2017 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 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 it under the terms of the GNU General Public License as published by
@ -16,74 +16,70 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#ifndef UTILS_H #pragma once
#define UTILS_H
#include <stdio.h> #include <stdio.h>
#include <QSpinBox> #include <QtCore/QString>
#include <QPushButton> #include <QtWidgets/QSpinBox>
#include <QLayout> #include <QtWidgets/QPushButton>
#include <QListWidget> #include <QtWidgets/QLayout>
#include <QSystemTrayIcon> #include <QtWidgets/QListWidget>
#include <QHostAddress> #include <QtWidgets/QSystemTrayIcon>
#include <obs.hpp> #include <obs.hpp>
#include <obs-module.h> #include <obs-module.h>
#include <util/config-file.h> #include <util/config-file.h>
const char* qstring_data_copy(QString value);
class Utils { class Utils {
public: public:
static obs_data_array_t* StringListToArray(char** strings, char* key); static obs_data_array_t* StringListToArray(char** strings, const char* key);
static obs_data_array_t* GetSceneItems(obs_source_t* source); static obs_data_array_t* GetSceneItems(obs_source_t* source);
static obs_data_t* GetSceneItemData(obs_sceneitem_t* item); static obs_data_t* GetSceneItemData(obs_sceneitem_t* item);
static obs_sceneitem_t* GetSceneItemFromName( static obs_sceneitem_t* GetSceneItemFromName(
obs_source_t* source, QString name); obs_source_t* source, QString name);
static obs_source_t* GetTransitionFromName(QString transitionName); static obs_sceneitem_t* GetSceneItemFromId(obs_source_t* source, size_t id);
static obs_source_t* GetSceneFromNameOrCurrent(QString sceneName); static obs_sceneitem_t* GetSceneItemFromItem(obs_source_t* source, obs_data_t* item);
static obs_source_t* GetTransitionFromName(QString transitionName);
static obs_source_t* GetSceneFromNameOrCurrent(QString sceneName);
static obs_data_t* GetSceneItemPropertiesData(obs_sceneitem_t* item);
static bool IsValidAlignment(const uint32_t alignment); static obs_data_array_t* GetSourceFiltersList(obs_source_t* source, bool includeSettings);
static obs_data_array_t* GetScenes(); static bool IsValidAlignment(const uint32_t alignment);
static obs_data_t* GetSceneData(obs_source_t* source);
static obs_data_array_t* GetProfiles(); static obs_data_array_t* GetScenes();
static obs_data_t* GetSceneData(obs_source_t* source);
static QSpinBox* GetTransitionDurationControl(); // TODO contribute a proper frontend API method for this to OBS and remove this hack
static int GetTransitionDuration(); static QSpinBox* GetTransitionDurationControl();
static void SetTransitionDuration(int ms); static int GetTransitionDuration();
static void SetTransitionDuration(int ms);
static bool SetTransitionByName(QString transitionName); static bool SetTransitionByName(QString transitionName);
static QPushButton* GetPreviewModeButtonControl(); static QPushButton* GetPreviewModeButtonControl();
static QLayout* GetPreviewLayout(); static QLayout* GetPreviewLayout();
static QListWidget* GetSceneListControl();
static obs_scene_t* SceneListItemToScene(QListWidgetItem* item);
static void TransitionToProgram(); // TODO contribute a proper frontend API method for this to OBS and remove this hack
static void TransitionToProgram();
static QString OBSVersionString(); static QString OBSVersionString();
static QSystemTrayIcon* GetTrayIcon(); static QSystemTrayIcon* GetTrayIcon();
static void SysTrayNotify( static void SysTrayNotify(
QString &text, QString text,
QSystemTrayIcon::MessageIcon n, QSystemTrayIcon::MessageIcon n,
QString title = QString("obs-websocket")); QString title = QString("obs-websocket"));
static QString FormatIPAddress(QHostAddress &addr); static const char* GetRecordingFolder();
static bool SetRecordingFolder(const char* path);
static const char* GetRecordingFolder(); static QString ParseDataToQueryString(obs_data_t* data);
static bool SetRecordingFolder(const char* path); static obs_hotkey_t* FindHotkeyByName(QString name);
static bool ReplayBufferEnabled();
static QString ParseDataToQueryString(obs_data_t* data); static void StartReplayBuffer();
static obs_hotkey_t* FindHotkeyByName(QString name); static bool IsRPHotkeySet();
static bool ReplayBufferEnabled(); static const char* GetFilenameFormatting();
static void StartReplayBuffer(); static bool SetFilenameFormatting(const char* filenameFormatting);
static bool IsRPHotkeySet();
static const char* GetFilenameFormatting();
static bool SetFilenameFormatting(const char* filenameFormatting);
}; };
#endif // UTILS_H

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj> Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
@ -17,95 +17,114 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#ifndef WSEVENTS_H #pragma once
#define WSEVENTS_H
#include <obs.hpp> #include <obs.hpp>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <QListWidgetItem> #include <util/platform.h>
#include <QtWidgets/QListWidgetItem>
#include <QtCore/QSharedPointer>
#include <QtCore/QTimer>
#include "WSServer.h" #include "WSServer.h"
class WSEvents : public QObject { class WSEvents : public QObject
Q_OBJECT {
public: Q_OBJECT
explicit WSEvents(WSServer* srv);
~WSEvents();
static void FrontendEventHandler(
enum obs_frontend_event event, void* privateData);
static WSEvents* Instance;
void connectTransitionSignals(obs_source_t* transition);
void connectSceneSignals(obs_source_t* scene);
uint64_t GetStreamingTime(); public:
const char* GetStreamingTimecode(); explicit WSEvents(WSServerPtr srv);
uint64_t GetRecordingTime(); ~WSEvents();
const char* GetRecordingTimecode();
bool HeartbeatIsActive; void connectSourceSignals(obs_source_t* source);
void disconnectSourceSignals(obs_source_t* source);
private slots: uint64_t GetStreamingTime();
void deferredInitOperations(); const char* GetStreamingTimecode();
void StreamStatus(); uint64_t GetRecordingTime();
void Heartbeat(); const char* GetRecordingTimecode();
void TransitionDurationChanged(int ms); obs_data_t* GetStats();
void SelectedSceneChanged(
QListWidgetItem* current, QListWidgetItem* prev);
private: bool HeartbeatIsActive;
WSServer* _srv;
signal_handler_t* transitionHandler;
signal_handler_t* sceneHandler;
bool pulse; private slots:
void StreamStatus();
void Heartbeat();
void TransitionDurationChanged(int ms);
bool _streamingActive; private:
bool _recordingActive; WSServerPtr _srv;
QTimer streamStatusTimer;
QTimer heartbeatTimer;
os_cpu_usage_info_t* cpuUsageInfo;
uint64_t _streamStarttime; bool pulse;
uint64_t _recStarttime;
uint64_t _lastBytesSent; uint64_t _streamStarttime;
uint64_t _lastBytesSentTime; uint64_t _recStarttime;
void broadcastUpdate(const char* updateType, uint64_t _lastBytesSent;
obs_data_t* additionalFields); uint64_t _lastBytesSentTime;
void OnSceneChange(); void broadcastUpdate(const char* updateType,
void OnSceneListChange(); obs_data_t* additionalFields);
void OnSceneCollectionChange();
void OnSceneCollectionListChange();
void OnTransitionChange(); void OnSceneChange();
void OnTransitionListChange(); void OnSceneListChange();
void OnSceneCollectionChange();
void OnSceneCollectionListChange();
void OnProfileChange(); void OnTransitionChange();
void OnProfileListChange(); void OnTransitionListChange();
void OnStreamStarting(); void OnProfileChange();
void OnStreamStarted(); void OnProfileListChange();
void OnStreamStopping();
void OnStreamStopped();
void OnRecordingStarting(); void OnStreamStarting();
void OnRecordingStarted(); void OnStreamStarted();
void OnRecordingStopping(); void OnStreamStopping();
void OnRecordingStopped(); void OnStreamStopped();
void OnReplayStarting(); void OnRecordingStarting();
void OnReplayStarted(); void OnRecordingStarted();
void OnReplayStopping(); void OnRecordingStopping();
void OnReplayStopped(); void OnRecordingStopped();
void OnStudioModeSwitched(bool enabled); void OnReplayStarting();
void OnReplayStarted();
void OnReplayStopping();
void OnReplayStopped();
void OnExit(); void OnStudioModeSwitched(bool enabled);
void OnPreviewSceneChanged();
static void OnTransitionBegin(void* param, calldata_t* data); void OnExit();
static void OnSceneReordered(void* param, calldata_t* data); static void FrontendEventHandler(
static void OnSceneItemAdd(void* param, calldata_t* data); enum obs_frontend_event event, void* privateData);
static void OnSceneItemDelete(void* param, calldata_t* data);
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data); static void OnTransitionBegin(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 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 OnSceneItemTransform(void* param, calldata_t* data);
static void OnSceneItemSelected(void* param, calldata_t* data);
static void OnSceneItemDeselected(void* param, calldata_t* data);
}; };
#endif // WSEVENTS_H

View File

@ -24,194 +24,204 @@
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
QHash<QString, void(*)(WSRequestHandler*)> WSRequestHandler::messageMap { QHash<QString, HandlerResponse(*)(WSRequestHandler*)> WSRequestHandler::messageMap {
{ "GetVersion", WSRequestHandler::HandleGetVersion }, { "GetVersion", WSRequestHandler::HandleGetVersion },
{ "GetAuthRequired", WSRequestHandler::HandleGetAuthRequired }, { "GetAuthRequired", WSRequestHandler::HandleGetAuthRequired },
{ "Authenticate", WSRequestHandler::HandleAuthenticate }, { "Authenticate", WSRequestHandler::HandleAuthenticate },
{ "SetHeartbeat", WSRequestHandler::HandleSetHeartbeat }, { "GetStats", WSRequestHandler::HandleGetStats },
{ "SetHeartbeat", WSRequestHandler::HandleSetHeartbeat },
{ "GetVideoInfo", WSRequestHandler::HandleGetVideoInfo },
{ "SetFilenameFormatting", WSRequestHandler::HandleSetFilenameFormatting }, { "SetFilenameFormatting", WSRequestHandler::HandleSetFilenameFormatting },
{ "GetFilenameFormatting", WSRequestHandler::HandleGetFilenameFormatting }, { "GetFilenameFormatting", WSRequestHandler::HandleGetFilenameFormatting },
{ "SetCurrentScene", WSRequestHandler::HandleSetCurrentScene }, { "SetCurrentScene", WSRequestHandler::HandleSetCurrentScene },
{ "GetCurrentScene", WSRequestHandler::HandleGetCurrentScene }, { "GetCurrentScene", WSRequestHandler::HandleGetCurrentScene },
{ "GetSceneList", WSRequestHandler::HandleGetSceneList }, { "GetSceneList", WSRequestHandler::HandleGetSceneList },
{ "SetSourceRender", WSRequestHandler::HandleSetSceneItemRender }, // Retrocompat { "SetSourceRender", WSRequestHandler::HandleSetSceneItemRender }, // Retrocompat
{ "SetSceneItemRender", WSRequestHandler::HandleSetSceneItemRender }, { "SetSceneItemRender", WSRequestHandler::HandleSetSceneItemRender },
{ "SetSceneItemPosition", WSRequestHandler::HandleSetSceneItemPosition }, { "SetSceneItemPosition", WSRequestHandler::HandleSetSceneItemPosition },
{ "SetSceneItemTransform", WSRequestHandler::HandleSetSceneItemTransform }, { "SetSceneItemTransform", WSRequestHandler::HandleSetSceneItemTransform },
{ "SetSceneItemCrop", WSRequestHandler::HandleSetSceneItemCrop }, { "SetSceneItemCrop", WSRequestHandler::HandleSetSceneItemCrop },
{ "GetSceneItemProperties", WSRequestHandler::HandleGetSceneItemProperties }, { "GetSceneItemProperties", WSRequestHandler::HandleGetSceneItemProperties },
{ "SetSceneItemProperties", WSRequestHandler::HandleSetSceneItemProperties }, { "SetSceneItemProperties", WSRequestHandler::HandleSetSceneItemProperties },
{ "ResetSceneItem", WSRequestHandler::HandleResetSceneItem }, { "ResetSceneItem", WSRequestHandler::HandleResetSceneItem },
{ "DeleteSceneItem", WSRequestHandler::HandleDeleteSceneItem },
{ "DuplicateSceneItem", WSRequestHandler::HandleDuplicateSceneItem },
{ "ReorderSceneItems", WSRequestHandler::HandleReorderSceneItems },
{ "GetStreamingStatus", WSRequestHandler::HandleGetStreamingStatus }, { "GetStreamingStatus", WSRequestHandler::HandleGetStreamingStatus },
{ "StartStopStreaming", WSRequestHandler::HandleStartStopStreaming }, { "StartStopStreaming", WSRequestHandler::HandleStartStopStreaming },
{ "StartStopRecording", WSRequestHandler::HandleStartStopRecording }, { "StartStopRecording", WSRequestHandler::HandleStartStopRecording },
{ "StartStreaming", WSRequestHandler::HandleStartStreaming }, { "StartStreaming", WSRequestHandler::HandleStartStreaming },
{ "StopStreaming", WSRequestHandler::HandleStopStreaming }, { "StopStreaming", WSRequestHandler::HandleStopStreaming },
{ "StartRecording", WSRequestHandler::HandleStartRecording }, { "StartRecording", WSRequestHandler::HandleStartRecording },
{ "StopRecording", WSRequestHandler::HandleStopRecording }, { "StopRecording", WSRequestHandler::HandleStopRecording },
{ "StartStopReplayBuffer", WSRequestHandler::HandleStartStopReplayBuffer }, { "StartStopReplayBuffer", WSRequestHandler::HandleStartStopReplayBuffer },
{ "StartReplayBuffer", WSRequestHandler::HandleStartReplayBuffer }, { "StartReplayBuffer", WSRequestHandler::HandleStartReplayBuffer },
{ "StopReplayBuffer", WSRequestHandler::HandleStopReplayBuffer }, { "StopReplayBuffer", WSRequestHandler::HandleStopReplayBuffer },
{ "SaveReplayBuffer", WSRequestHandler::HandleSaveReplayBuffer }, { "SaveReplayBuffer", WSRequestHandler::HandleSaveReplayBuffer },
{ "SetRecordingFolder", WSRequestHandler::HandleSetRecordingFolder }, { "SetRecordingFolder", WSRequestHandler::HandleSetRecordingFolder },
{ "GetRecordingFolder", WSRequestHandler::HandleGetRecordingFolder }, { "GetRecordingFolder", WSRequestHandler::HandleGetRecordingFolder },
{ "GetTransitionList", WSRequestHandler::HandleGetTransitionList }, { "GetTransitionList", WSRequestHandler::HandleGetTransitionList },
{ "GetCurrentTransition", WSRequestHandler::HandleGetCurrentTransition }, { "GetCurrentTransition", WSRequestHandler::HandleGetCurrentTransition },
{ "SetCurrentTransition", WSRequestHandler::HandleSetCurrentTransition }, { "SetCurrentTransition", WSRequestHandler::HandleSetCurrentTransition },
{ "SetTransitionDuration", WSRequestHandler::HandleSetTransitionDuration }, { "SetTransitionDuration", WSRequestHandler::HandleSetTransitionDuration },
{ "GetTransitionDuration", WSRequestHandler::HandleGetTransitionDuration }, { "GetTransitionDuration", WSRequestHandler::HandleGetTransitionDuration },
{ "SetVolume", WSRequestHandler::HandleSetVolume }, { "SetVolume", WSRequestHandler::HandleSetVolume },
{ "GetVolume", WSRequestHandler::HandleGetVolume }, { "GetVolume", WSRequestHandler::HandleGetVolume },
{ "ToggleMute", WSRequestHandler::HandleToggleMute }, { "ToggleMute", WSRequestHandler::HandleToggleMute },
{ "SetMute", WSRequestHandler::HandleSetMute }, { "SetMute", WSRequestHandler::HandleSetMute },
{ "GetMute", WSRequestHandler::HandleGetMute }, { "GetMute", WSRequestHandler::HandleGetMute },
{ "SetSyncOffset", WSRequestHandler::HandleSetSyncOffset }, { "SetSyncOffset", WSRequestHandler::HandleSetSyncOffset },
{ "GetSyncOffset", WSRequestHandler::HandleGetSyncOffset }, { "GetSyncOffset", WSRequestHandler::HandleGetSyncOffset },
{ "GetSpecialSources", WSRequestHandler::HandleGetSpecialSources }, { "GetSpecialSources", WSRequestHandler::HandleGetSpecialSources },
{ "GetSourcesList", WSRequestHandler::HandleGetSourcesList }, { "GetSourcesList", WSRequestHandler::HandleGetSourcesList },
{ "GetSourceTypesList", WSRequestHandler::HandleGetSourceTypesList }, { "GetSourceTypesList", WSRequestHandler::HandleGetSourceTypesList },
{ "GetSourceSettings", WSRequestHandler::HandleGetSourceSettings }, { "GetSourceSettings", WSRequestHandler::HandleGetSourceSettings },
{ "SetSourceSettings", WSRequestHandler::HandleSetSourceSettings }, { "SetSourceSettings", WSRequestHandler::HandleSetSourceSettings },
{ "TakeSourceScreenshot", WSRequestHandler::HandleTakeSourceScreenshot },
{ "SetCurrentSceneCollection", WSRequestHandler::HandleSetCurrentSceneCollection }, { "GetSourceFilters", WSRequestHandler::HandleGetSourceFilters },
{ "GetCurrentSceneCollection", WSRequestHandler::HandleGetCurrentSceneCollection }, { "AddFilterToSource", WSRequestHandler::HandleAddFilterToSource },
{ "ListSceneCollections", WSRequestHandler::HandleListSceneCollections }, { "RemoveFilterFromSource", WSRequestHandler::HandleRemoveFilterFromSource },
{ "ReorderSourceFilter", WSRequestHandler::HandleReorderSourceFilter },
{ "MoveSourceFilter", WSRequestHandler::HandleMoveSourceFilter },
{ "SetSourceFilterSettings", WSRequestHandler::HandleSetSourceFilterSettings },
{ "SetCurrentProfile", WSRequestHandler::HandleSetCurrentProfile }, { "SetCurrentSceneCollection", WSRequestHandler::HandleSetCurrentSceneCollection },
{ "GetCurrentProfile", WSRequestHandler::HandleGetCurrentProfile }, { "GetCurrentSceneCollection", WSRequestHandler::HandleGetCurrentSceneCollection },
{ "ListProfiles", WSRequestHandler::HandleListProfiles }, { "ListSceneCollections", WSRequestHandler::HandleListSceneCollections },
{ "SetStreamSettings", WSRequestHandler::HandleSetStreamSettings }, { "SetCurrentProfile", WSRequestHandler::HandleSetCurrentProfile },
{ "GetStreamSettings", WSRequestHandler::HandleGetStreamSettings }, { "GetCurrentProfile", WSRequestHandler::HandleGetCurrentProfile },
{ "SaveStreamSettings", WSRequestHandler::HandleSaveStreamSettings }, { "ListProfiles", WSRequestHandler::HandleListProfiles },
{ "GetStudioModeStatus", WSRequestHandler::HandleGetStudioModeStatus }, { "SetStreamSettings", WSRequestHandler::HandleSetStreamSettings },
{ "GetPreviewScene", WSRequestHandler::HandleGetPreviewScene }, { "GetStreamSettings", WSRequestHandler::HandleGetStreamSettings },
{ "SetPreviewScene", WSRequestHandler::HandleSetPreviewScene }, { "SaveStreamSettings", WSRequestHandler::HandleSaveStreamSettings },
{ "TransitionToProgram", WSRequestHandler::HandleTransitionToProgram }, #if BUILD_CAPTIONS
{ "EnableStudioMode", WSRequestHandler::HandleEnableStudioMode }, { "SendCaptions", WSRequestHandler::HandleSendCaptions },
{ "DisableStudioMode", WSRequestHandler::HandleDisableStudioMode }, #endif
{ "ToggleStudioMode", WSRequestHandler::HandleToggleStudioMode },
{ "SetTextGDIPlusProperties", WSRequestHandler::HandleSetTextGDIPlusProperties }, { "GetStudioModeStatus", WSRequestHandler::HandleGetStudioModeStatus },
{ "GetTextGDIPlusProperties", WSRequestHandler::HandleGetTextGDIPlusProperties }, { "GetPreviewScene", WSRequestHandler::HandleGetPreviewScene },
{ "SetPreviewScene", WSRequestHandler::HandleSetPreviewScene },
{ "TransitionToProgram", WSRequestHandler::HandleTransitionToProgram },
{ "EnableStudioMode", WSRequestHandler::HandleEnableStudioMode },
{ "DisableStudioMode", WSRequestHandler::HandleDisableStudioMode },
{ "ToggleStudioMode", WSRequestHandler::HandleToggleStudioMode },
{ "GetBrowserSourceProperties", WSRequestHandler::HandleGetBrowserSourceProperties }, { "SetTextGDIPlusProperties", WSRequestHandler::HandleSetTextGDIPlusProperties },
{ "SetBrowserSourceProperties", WSRequestHandler::HandleSetBrowserSourceProperties } { "GetTextGDIPlusProperties", WSRequestHandler::HandleGetTextGDIPlusProperties },
{ "SetTextFreetype2Properties", WSRequestHandler::HandleSetTextFreetype2Properties },
{ "GetTextFreetype2Properties", WSRequestHandler::HandleGetTextFreetype2Properties },
{ "GetBrowserSourceProperties", WSRequestHandler::HandleGetBrowserSourceProperties },
{ "SetBrowserSourceProperties", WSRequestHandler::HandleSetBrowserSourceProperties }
}; };
QSet<QString> WSRequestHandler::authNotRequired { QSet<QString> WSRequestHandler::authNotRequired {
"GetVersion", "GetVersion",
"GetAuthRequired", "GetAuthRequired",
"Authenticate" "Authenticate"
}; };
WSRequestHandler::WSRequestHandler(QWebSocket* client) : WSRequestHandler::WSRequestHandler(ConnectionProperties& connProperties) :
_messageId(0), _messageId(0),
_requestType(""), _requestType(""),
data(nullptr), data(nullptr),
_client(client) _connProperties(connProperties)
{ {
} }
void WSRequestHandler::processIncomingMessage(QString textMessage) { std::string WSRequestHandler::processIncomingMessage(std::string& textMessage) {
QByteArray msgData = textMessage.toUtf8(); if (GetConfig()->DebugEnabled) {
const char* msg = msgData.constData(); blog(LOG_INFO, "Request >> '%s'", textMessage.c_str());
}
data = obs_data_create_from_json(msg); OBSDataAutoRelease responseData = processRequest(textMessage);
if (!data) { std::string response = obs_data_get_json(responseData);
if (!msg)
msg = "<null pointer>";
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg); if (GetConfig()->DebugEnabled) {
SendErrorResponse("invalid JSON payload"); blog(LOG_INFO, "Response << '%s'", response.c_str());
return; }
}
if (Config::Current()->DebugEnabled) { return response;
blog(LOG_DEBUG, "Request >> '%s'", msg); }
}
if (!hasField("request-type") HandlerResponse WSRequestHandler::processRequest(std::string& textMessage){
|| !hasField("message-id")) std::string msgContainer(textMessage);
{ const char* msg = msgContainer.c_str();
SendErrorResponse("missing request parameters");
return;
}
_requestType = obs_data_get_string(data, "request-type"); data = obs_data_create_from_json(msg);
_messageId = obs_data_get_string(data, "message-id"); if (!data) {
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
return SendErrorResponse("invalid JSON payload");
}
if (Config::Current()->AuthRequired if (!hasField("request-type") || !hasField("message-id")) {
&& (_client->property(PROP_AUTHENTICATED).toBool() == false) return SendErrorResponse("missing request parameters");
&& (authNotRequired.find(_requestType) == authNotRequired.end())) }
{
SendErrorResponse("Not Authenticated");
return;
}
void (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]); _requestType = obs_data_get_string(data, "request-type");
_messageId = obs_data_get_string(data, "message-id");
if (handlerFunc != nullptr) if (GetConfig()->AuthRequired
handlerFunc(this); && (!authNotRequired.contains(_requestType))
else && (!_connProperties.isAuthenticated()))
SendErrorResponse("invalid request type"); {
return SendErrorResponse("Not Authenticated");
}
HandlerResponse (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]);
if (!handlerFunc) {
return SendErrorResponse("invalid request type");
}
return handlerFunc(this);
} }
WSRequestHandler::~WSRequestHandler() { WSRequestHandler::~WSRequestHandler() {
} }
void WSRequestHandler::SendOKResponse(obs_data_t* additionalFields) { HandlerResponse WSRequestHandler::SendOKResponse(obs_data_t* additionalFields) {
OBSDataAutoRelease response = obs_data_create(); return SendResponse("ok", additionalFields);
obs_data_set_string(response, "status", "ok");
obs_data_set_string(response, "message-id", _messageId);
if (additionalFields)
obs_data_apply(response, additionalFields);
SendResponse(response);
} }
void WSRequestHandler::SendErrorResponse(const char* errorMessage) { HandlerResponse WSRequestHandler::SendErrorResponse(const char* errorMessage) {
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(response, "status", "error"); obs_data_set_string(fields, "error", errorMessage);
obs_data_set_string(response, "error", errorMessage);
obs_data_set_string(response, "message-id", _messageId);
SendResponse(response); return SendResponse("error", fields);
} }
void WSRequestHandler::SendErrorResponse(obs_data_t* additionalFields) { HandlerResponse WSRequestHandler::SendErrorResponse(obs_data_t* additionalFields) {
OBSDataAutoRelease response = obs_data_create(); return SendResponse("error", additionalFields);
obs_data_set_string(response, "status", "error");
obs_data_set_string(response, "message-id", _messageId);
if (additionalFields)
obs_data_set_obj(response, "error", additionalFields);
SendResponse(response);
} }
void WSRequestHandler::SendResponse(obs_data_t* response) { HandlerResponse WSRequestHandler::SendResponse(const char* status, obs_data_t* fields) {
QString json = obs_data_get_json(response); obs_data_t* response = obs_data_create();
_client->sendTextMessage(json); obs_data_set_string(response, "message-id", _messageId);
obs_data_set_string(response, "status", status);
if (Config::Current()->DebugEnabled) if (fields) {
blog(LOG_DEBUG, "Response << '%s'", json.toUtf8().constData()); obs_data_apply(response, fields);
}
return response;
} }
bool WSRequestHandler::hasField(QString name) { bool WSRequestHandler::hasField(QString name) {
if (!data || name.isEmpty() || name.isNull()) if (!data || name.isEmpty() || name.isNull())
return false; return false;
return obs_data_has_user_value(data, name.toUtf8()); return obs_data_has_user_value(data, name.toUtf8());
} }

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift> Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
@ -17,123 +17,147 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#ifndef WSREQUESTHANDLER_H #pragma once
#define WSREQUESTHANDLER_H
#include <QHash> #include <QtCore/QHash>
#include <QSet> #include <QtCore/QSet>
#include <QWebSocket> #include <QtCore/QVariantHash>
#include <QWebSocketServer> #include <QtCore/QString>
#include <QtCore/QSharedPointer>
#include <obs.hpp> #include <obs.hpp>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include "ConnectionProperties.h"
#include "obs-websocket.h" #include "obs-websocket.h"
typedef obs_data_t* HandlerResponse;
class WSRequestHandler : public QObject { class WSRequestHandler : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit WSRequestHandler(QWebSocket* client); explicit WSRequestHandler(ConnectionProperties& connProperties);
~WSRequestHandler(); ~WSRequestHandler();
void processIncomingMessage(QString textMessage); std::string processIncomingMessage(std::string& textMessage);
bool hasField(QString name); bool hasField(QString name);
private: private:
QWebSocket* _client; const char* _messageId;
const char* _messageId; const char* _requestType;
const char* _requestType; ConnectionProperties& _connProperties;
OBSDataAutoRelease data; OBSDataAutoRelease data;
void SendOKResponse(obs_data_t* additionalFields = NULL); HandlerResponse processRequest(std::string& textMessage);
void SendErrorResponse(const char* errorMessage);
void SendErrorResponse(obs_data_t* additionalFields = NULL);
void SendResponse(obs_data_t* response);
static QHash<QString, void(*)(WSRequestHandler*)> messageMap; HandlerResponse SendOKResponse(obs_data_t* additionalFields = nullptr);
static QSet<QString> authNotRequired; HandlerResponse SendErrorResponse(const char* errorMessage);
HandlerResponse SendErrorResponse(obs_data_t* additionalFields = nullptr);
HandlerResponse SendResponse(const char* status, obs_data_t* additionalFields = nullptr);
static void HandleGetVersion(WSRequestHandler* req); static QHash<QString, HandlerResponse(*)(WSRequestHandler*)> messageMap;
static void HandleGetAuthRequired(WSRequestHandler* req); static QSet<QString> authNotRequired;
static void HandleAuthenticate(WSRequestHandler* req);
static void HandleSetHeartbeat(WSRequestHandler* req); static HandlerResponse HandleGetVersion(WSRequestHandler* req);
static HandlerResponse HandleGetAuthRequired(WSRequestHandler* req);
static HandlerResponse HandleAuthenticate(WSRequestHandler* req);
static void HandleSetFilenameFormatting(WSRequestHandler* req); static HandlerResponse HandleGetStats(WSRequestHandler* req);
static void HandleGetFilenameFormatting(WSRequestHandler* req); static HandlerResponse HandleSetHeartbeat(WSRequestHandler* req);
static HandlerResponse HandleGetVideoInfo(WSRequestHandler* req);
static void HandleSetCurrentScene(WSRequestHandler* req); static HandlerResponse HandleSetFilenameFormatting(WSRequestHandler* req);
static void HandleGetCurrentScene(WSRequestHandler* req); static HandlerResponse HandleGetFilenameFormatting(WSRequestHandler* req);
static void HandleGetSceneList(WSRequestHandler* req);
static void HandleSetSceneItemRender(WSRequestHandler* req); static HandlerResponse HandleSetCurrentScene(WSRequestHandler* req);
static void HandleSetSceneItemPosition(WSRequestHandler* req); static HandlerResponse HandleGetCurrentScene(WSRequestHandler* req);
static void HandleSetSceneItemTransform(WSRequestHandler* req); static HandlerResponse HandleGetSceneList(WSRequestHandler* req);
static void HandleSetSceneItemCrop(WSRequestHandler* req);
static void HandleGetSceneItemProperties(WSRequestHandler* req);
static void HandleSetSceneItemProperties(WSRequestHandler* req);
static void HandleResetSceneItem(WSRequestHandler* req);
static void HandleGetStreamingStatus(WSRequestHandler* req); static HandlerResponse HandleSetSceneItemRender(WSRequestHandler* req);
static void HandleStartStopStreaming(WSRequestHandler* req); static HandlerResponse HandleSetSceneItemPosition(WSRequestHandler* req);
static void HandleStartStopRecording(WSRequestHandler* req); static HandlerResponse HandleSetSceneItemTransform(WSRequestHandler* req);
static void HandleStartStreaming(WSRequestHandler* req); static HandlerResponse HandleSetSceneItemCrop(WSRequestHandler* req);
static void HandleStopStreaming(WSRequestHandler* req); static HandlerResponse HandleGetSceneItemProperties(WSRequestHandler* req);
static void HandleStartRecording(WSRequestHandler* req); static HandlerResponse HandleSetSceneItemProperties(WSRequestHandler* req);
static void HandleStopRecording(WSRequestHandler* req); static HandlerResponse HandleResetSceneItem(WSRequestHandler* req);
static HandlerResponse HandleDuplicateSceneItem(WSRequestHandler* req);
static HandlerResponse HandleDeleteSceneItem(WSRequestHandler* req);
static HandlerResponse HandleReorderSceneItems(WSRequestHandler* req);
static void HandleStartStopReplayBuffer(WSRequestHandler* req); static HandlerResponse HandleGetStreamingStatus(WSRequestHandler* req);
static void HandleStartReplayBuffer(WSRequestHandler* req); static HandlerResponse HandleStartStopStreaming(WSRequestHandler* req);
static void HandleStopReplayBuffer(WSRequestHandler* req); static HandlerResponse HandleStartStopRecording(WSRequestHandler* req);
static void HandleSaveReplayBuffer(WSRequestHandler* req); static HandlerResponse HandleStartStreaming(WSRequestHandler* req);
static HandlerResponse HandleStopStreaming(WSRequestHandler* req);
static HandlerResponse HandleStartRecording(WSRequestHandler* req);
static HandlerResponse HandleStopRecording(WSRequestHandler* req);
static void HandleSetRecordingFolder(WSRequestHandler* req); static HandlerResponse HandleStartStopReplayBuffer(WSRequestHandler* req);
static void HandleGetRecordingFolder(WSRequestHandler* req); static HandlerResponse HandleStartReplayBuffer(WSRequestHandler* req);
static HandlerResponse HandleStopReplayBuffer(WSRequestHandler* req);
static HandlerResponse HandleSaveReplayBuffer(WSRequestHandler* req);
static void HandleGetTransitionList(WSRequestHandler* req); static HandlerResponse HandleSetRecordingFolder(WSRequestHandler* req);
static void HandleGetCurrentTransition(WSRequestHandler* req); static HandlerResponse HandleGetRecordingFolder(WSRequestHandler* req);
static void HandleSetCurrentTransition(WSRequestHandler* req);
static void HandleSetVolume(WSRequestHandler* req); static HandlerResponse HandleGetTransitionList(WSRequestHandler* req);
static void HandleGetVolume(WSRequestHandler* req); static HandlerResponse HandleGetCurrentTransition(WSRequestHandler* req);
static void HandleToggleMute(WSRequestHandler* req); static HandlerResponse HandleSetCurrentTransition(WSRequestHandler* req);
static void HandleSetMute(WSRequestHandler* req);
static void HandleGetMute(WSRequestHandler* req);
static void HandleSetSyncOffset(WSRequestHandler* req);
static void HandleGetSyncOffset(WSRequestHandler* req);
static void HandleGetSpecialSources(WSRequestHandler* req);
static void HandleGetSourcesList(WSRequestHandler* req);
static void HandleGetSourceTypesList(WSRequestHandler* req);
static void HandleGetSourceSettings(WSRequestHandler* req);
static void HandleSetSourceSettings(WSRequestHandler* req);
static void HandleSetCurrentSceneCollection(WSRequestHandler* req); static HandlerResponse HandleSetVolume(WSRequestHandler* req);
static void HandleGetCurrentSceneCollection(WSRequestHandler* req); static HandlerResponse HandleGetVolume(WSRequestHandler* req);
static void HandleListSceneCollections(WSRequestHandler* req); static HandlerResponse HandleToggleMute(WSRequestHandler* req);
static HandlerResponse HandleSetMute(WSRequestHandler* req);
static HandlerResponse HandleGetMute(WSRequestHandler* req);
static HandlerResponse HandleSetSyncOffset(WSRequestHandler* req);
static HandlerResponse HandleGetSyncOffset(WSRequestHandler* req);
static HandlerResponse HandleGetSpecialSources(WSRequestHandler* req);
static HandlerResponse HandleGetSourcesList(WSRequestHandler* req);
static HandlerResponse HandleGetSourceTypesList(WSRequestHandler* req);
static HandlerResponse HandleGetSourceSettings(WSRequestHandler* req);
static HandlerResponse HandleSetSourceSettings(WSRequestHandler* req);
static HandlerResponse HandleTakeSourceScreenshot(WSRequestHandler* req);
static void HandleSetCurrentProfile(WSRequestHandler* req); static HandlerResponse HandleGetSourceFilters(WSRequestHandler* req);
static void HandleGetCurrentProfile(WSRequestHandler* req); static HandlerResponse HandleAddFilterToSource(WSRequestHandler* req);
static void HandleListProfiles(WSRequestHandler* req); static HandlerResponse HandleRemoveFilterFromSource(WSRequestHandler* req);
static HandlerResponse HandleReorderSourceFilter(WSRequestHandler* req);
static HandlerResponse HandleMoveSourceFilter(WSRequestHandler* req);
static HandlerResponse HandleSetSourceFilterSettings(WSRequestHandler* req);
static void HandleSetStreamSettings(WSRequestHandler* req); static HandlerResponse HandleSetCurrentSceneCollection(WSRequestHandler* req);
static void HandleGetStreamSettings(WSRequestHandler* req); static HandlerResponse HandleGetCurrentSceneCollection(WSRequestHandler* req);
static void HandleSaveStreamSettings(WSRequestHandler* req); static HandlerResponse HandleListSceneCollections(WSRequestHandler* req);
static void HandleSetTransitionDuration(WSRequestHandler* req); static HandlerResponse HandleSetCurrentProfile(WSRequestHandler* req);
static void HandleGetTransitionDuration(WSRequestHandler* req); static HandlerResponse HandleGetCurrentProfile(WSRequestHandler* req);
static HandlerResponse HandleListProfiles(WSRequestHandler* req);
static void HandleGetStudioModeStatus(WSRequestHandler* req); static HandlerResponse HandleSetStreamSettings(WSRequestHandler* req);
static void HandleGetPreviewScene(WSRequestHandler* req); static HandlerResponse HandleGetStreamSettings(WSRequestHandler* req);
static void HandleSetPreviewScene(WSRequestHandler* req); static HandlerResponse HandleSaveStreamSettings(WSRequestHandler* req);
static void HandleTransitionToProgram(WSRequestHandler* req); #if BUILD_CAPTIONS
static void HandleEnableStudioMode(WSRequestHandler* req); static HandlerResponse HandleSendCaptions(WSRequestHandler * req);
static void HandleDisableStudioMode(WSRequestHandler* req); #endif
static void HandleToggleStudioMode(WSRequestHandler* req);
static void HandleSetTextGDIPlusProperties(WSRequestHandler* req); static HandlerResponse HandleSetTransitionDuration(WSRequestHandler* req);
static void HandleGetTextGDIPlusProperties(WSRequestHandler* req); static HandlerResponse HandleGetTransitionDuration(WSRequestHandler* req);
static void HandleSetBrowserSourceProperties(WSRequestHandler* req);
static void HandleGetBrowserSourceProperties(WSRequestHandler* req); static HandlerResponse HandleGetStudioModeStatus(WSRequestHandler* req);
static HandlerResponse HandleGetPreviewScene(WSRequestHandler* req);
static HandlerResponse HandleSetPreviewScene(WSRequestHandler* req);
static HandlerResponse HandleTransitionToProgram(WSRequestHandler* req);
static HandlerResponse HandleEnableStudioMode(WSRequestHandler* req);
static HandlerResponse HandleDisableStudioMode(WSRequestHandler* req);
static HandlerResponse HandleToggleStudioMode(WSRequestHandler* req);
static HandlerResponse HandleSetTextGDIPlusProperties(WSRequestHandler* req);
static HandlerResponse HandleGetTextGDIPlusProperties(WSRequestHandler* req);
static HandlerResponse HandleSetTextFreetype2Properties(WSRequestHandler* req);
static HandlerResponse HandleGetTextFreetype2Properties(WSRequestHandler* req);
static HandlerResponse HandleSetBrowserSourceProperties(WSRequestHandler* req);
static HandlerResponse HandleGetBrowserSourceProperties(WSRequestHandler* req);
}; };
#endif // WSPROTOCOL_H

View File

@ -1,11 +1,58 @@
#include <QString> #include "obs-websocket.h"
#include "Config.h" #include "Config.h"
#include "Utils.h" #include "Utils.h"
#include "WSEvents.h" #include "WSEvents.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.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. * Returns the latest version of the plugin and the API.
* *
@ -19,25 +66,26 @@
* @category general * @category general
* @since 0.3 * @since 0.3
*/ */
void WSRequestHandler::HandleGetVersion(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetVersion(WSRequestHandler* req) {
QString obsVersion = Utils::OBSVersionString(); QString obsVersion = Utils::OBSVersionString();
QList<QString> names = req->messageMap.keys(); QList<QString> names = req->messageMap.keys();
names.sort(Qt::CaseInsensitive); names.sort(Qt::CaseInsensitive);
// (Palakis) OBS' data arrays only support object arrays, so I improvised. // (Palakis) OBS' data arrays only support object arrays, so I improvised.
QString requests; QString requests;
requests += names.takeFirst(); requests += names.takeFirst();
for (QString reqName : names) { for (QString reqName : names) {
requests += ("," + reqName); requests += ("," + reqName);
} }
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION); obs_data_set_double(data, "version", 1.1);
obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8()); obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
obs_data_set_string(data, "available-requests", requests.toUtf8()); obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8());
obs_data_set_string(data, "available-requests", requests.toUtf8());
req->SendOKResponse(data); return req->SendOKResponse(data);
} }
/** /**
@ -53,20 +101,21 @@
* @category general * @category general
* @since 0.3 * @since 0.3
*/ */
void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
bool authRequired = Config::Current()->AuthRequired; bool authRequired = GetConfig()->AuthRequired;
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "authRequired", authRequired); obs_data_set_bool(data, "authRequired", authRequired);
if (authRequired) { if (authRequired) {
obs_data_set_string(data, "challenge", auto config = GetConfig();
Config::Current()->SessionChallenge.toUtf8()); obs_data_set_string(data, "challenge",
obs_data_set_string(data, "salt", config->SessionChallenge.toUtf8());
Config::Current()->Salt.toUtf8()); obs_data_set_string(data, "salt",
} config->Salt.toUtf8());
}
req->SendOKResponse(data); return req->SendOKResponse(data);
} }
/** /**
@ -79,26 +128,26 @@ void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
* @category general * @category general
* @since 0.3 * @since 0.3
*/ */
void WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
if (!req->hasField("auth")) { if (!req->hasField("auth")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return; }
}
QString auth = obs_data_get_string(req->data, "auth"); if (req->_connProperties.isAuthenticated()) {
if (auth.isEmpty()) { return req->SendErrorResponse("already authenticated");
req->SendErrorResponse("auth not specified!"); }
return;
}
if ((req->_client->property(PROP_AUTHENTICATED).toBool() == false) QString auth = obs_data_get_string(req->data, "auth");
&& Config::Current()->CheckAuth(auth)) if (auth.isEmpty()) {
{ return req->SendErrorResponse("auth not specified!");
req->_client->setProperty(PROP_AUTHENTICATED, true); }
req->SendOKResponse();
} else { if (GetConfig()->CheckAuth(auth) == false) {
req->SendErrorResponse("Authentication Failed."); return req->SendErrorResponse("Authentication Failed.");
} }
req->_connProperties.setAuthenticated(true);
return req->SendOKResponse();
} }
/** /**
@ -111,19 +160,17 @@ void WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
* @category general * @category general
* @since 4.3.0 * @since 4.3.0
*/ */
void WSRequestHandler::HandleSetHeartbeat(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetHeartbeat(WSRequestHandler* req) {
if (!req->hasField("enable")) { if (!req->hasField("enable")) {
req->SendErrorResponse("Heartbeat <enable> parameter missing"); return req->SendErrorResponse("Heartbeat <enable> parameter missing");
return; }
}
WSEvents::Instance->HeartbeatIsActive = auto events = GetEventsSystem();
obs_data_get_bool(req->data, "enable"); events->HeartbeatIsActive = obs_data_get_bool(req->data, "enable");
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_bool(response, "enable", obs_data_set_bool(response, "enable", events->HeartbeatIsActive);
WSEvents::Instance->HeartbeatIsActive); return req->SendOKResponse(response);
req->SendOKResponse(response);
} }
/** /**
@ -136,19 +183,18 @@ void WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
* @category general * @category general
* @since 4.3.0 * @since 4.3.0
*/ */
void WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler* req) {
if (!req->hasField("filename-formatting")) { if (!req->hasField("filename-formatting")) {
req->SendErrorResponse("<filename-formatting> parameter missing"); return req->SendErrorResponse("<filename-formatting> parameter missing");
return; }
}
QString filenameFormatting = obs_data_get_string(req->data, "filename-formatting"); QString filenameFormatting = obs_data_get_string(req->data, "filename-formatting");
if (!filenameFormatting.isEmpty()) { if (filenameFormatting.isEmpty()) {
Utils::SetFilenameFormatting(filenameFormatting.toUtf8()); return req->SendErrorResponse("invalid request parameters");
req->SendOKResponse(); }
} else {
req->SendErrorResponse("invalid request parameters"); Utils::SetFilenameFormatting(filenameFormatting.toUtf8());
} return req->SendOKResponse();
} }
/** /**
@ -161,8 +207,60 @@ void WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler* req) {
* @category general * @category general
* @since 4.3.0 * @since 4.3.0
*/ */
void WSRequestHandler::HandleGetFilenameFormatting(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetFilenameFormatting(WSRequestHandler* req) {
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "filename-formatting", Utils::GetFilenameFormatting()); obs_data_set_string(response, "filename-formatting", Utils::GetFilenameFormatting());
req->SendOKResponse(response); return req->SendOKResponse(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
*/
HandlerResponse WSRequestHandler::HandleGetStats(WSRequestHandler* req) {
OBSDataAutoRelease stats = GetEventsSystem()->GetStats();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_obj(response, "stats", stats);
return req->SendOKResponse(response);
}
/**
* 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
*/
HandlerResponse WSRequestHandler::HandleGetVideoInfo(WSRequestHandler* req) {
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 req->SendOKResponse(response);
} }

View File

@ -1,4 +1,3 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -13,20 +12,19 @@
* @category profiles * @category profiles
* @since 4.0.0 * @since 4.0.0
*/ */
void WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler* req) {
if (!req->hasField("profile-name")) { if (!req->hasField("profile-name")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return; }
}
QString profileName = obs_data_get_string(req->data, "profile-name"); QString profileName = obs_data_get_string(req->data, "profile-name");
if (!profileName.isEmpty()) { if (profileName.isEmpty()) {
// TODO : check if profile exists return req->SendErrorResponse("invalid request parameters");
obs_frontend_set_current_profile(profileName.toUtf8()); }
req->SendOKResponse();
} else { // TODO : check if profile exists
req->SendErrorResponse("invalid request parameters"); obs_frontend_set_current_profile(profileName.toUtf8());
} return req->SendOKResponse();
} }
/** /**
@ -39,32 +37,29 @@
* @category profiles * @category profiles
* @since 4.0.0 * @since 4.0.0
*/ */
void WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req) {
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "profile-name", obs_data_set_string(response, "profile-name", obs_frontend_get_current_profile());
obs_frontend_get_current_profile()); return req->SendOKResponse(response);
req->SendOKResponse(response);
} }
/** /**
* Get a list of available profiles. * Get a list of available profiles.
* *
* @return {Object|Array} `profiles` List of available profiles. * @return {Array<Object>} `profiles` List of available profiles.
* *
* @api requests * @api requests
* @name ListProfiles * @name ListProfiles
* @category profiles * @category profiles
* @since 4.0.0 * @since 4.0.0
*/ */
void WSRequestHandler::HandleListProfiles(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleListProfiles(WSRequestHandler* req) {
char** profiles = obs_frontend_get_profiles(); char** profiles = obs_frontend_get_profiles();
OBSDataArrayAutoRelease list = OBSDataArrayAutoRelease list = Utils::StringListToArray(profiles, "profile-name");
Utils::StringListToArray(profiles, "profile-name"); bfree(profiles);
bfree(profiles);
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_array(response, "profiles", list); obs_data_set_array(response, "profiles", list);
req->SendOKResponse(response); return req->SendOKResponse(response);
} }

View File

@ -1,4 +1,3 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -11,13 +10,13 @@
* @category recording * @category recording
* @since 0.3 * @since 0.3
*/ */
void WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req) {
if (obs_frontend_recording_active()) if (obs_frontend_recording_active())
obs_frontend_recording_stop(); obs_frontend_recording_stop();
else else
obs_frontend_recording_start(); obs_frontend_recording_start();
req->SendOKResponse(); return req->SendOKResponse();
} }
/** /**
@ -29,13 +28,13 @@
* @category recording * @category recording
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleStartRecording(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
if (obs_frontend_recording_active() == false) { if (obs_frontend_recording_active() == false) {
obs_frontend_recording_start(); obs_frontend_recording_start();
req->SendOKResponse(); return req->SendOKResponse();
} else { } else {
req->SendErrorResponse("recording already active"); return req->SendErrorResponse("recording already active");
} }
} }
/** /**
@ -47,17 +46,23 @@
* @category recording * @category recording
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleStopRecording(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleStopRecording(WSRequestHandler* req) {
if (obs_frontend_recording_active() == true) { if (obs_frontend_recording_active() == true) {
obs_frontend_recording_stop(); obs_frontend_recording_stop();
req->SendOKResponse(); return req->SendOKResponse();
} else { } else {
req->SendErrorResponse("recording not active"); return req->SendErrorResponse("recording not active");
} }
} }
/** /**
* Change the current recording folder. * 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. * @param {String} `rec-folder` Path of the recording folder.
* *
@ -66,18 +71,18 @@ void WSRequestHandler::HandleStopRecording(WSRequestHandler* req) {
* @category recording * @category recording
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleSetRecordingFolder(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetRecordingFolder(WSRequestHandler* req) {
if (!req->hasField("rec-folder")) { if (!req->hasField("rec-folder")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return; }
}
const char* newRecFolder = obs_data_get_string(req->data, "rec-folder"); const char* newRecFolder = obs_data_get_string(req->data, "rec-folder");
bool success = Utils::SetRecordingFolder(newRecFolder); bool success = Utils::SetRecordingFolder(newRecFolder);
if (success) if (!success) {
req->SendOKResponse(); return req->SendErrorResponse("invalid request parameters");
else }
req->SendErrorResponse("invalid request parameters");
return req->SendOKResponse();
} }
/** /**
@ -90,11 +95,11 @@ void WSRequestHandler::HandleStopRecording(WSRequestHandler* req) {
* @category recording * @category recording
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleGetRecordingFolder(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetRecordingFolder(WSRequestHandler* req) {
const char* recFolder = Utils::GetRecordingFolder(); const char* recFolder = Utils::GetRecordingFolder();
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "rec-folder", recFolder); obs_data_set_string(response, "rec-folder", recFolder);
req->SendOKResponse(response); return req->SendOKResponse(response);
} }

View File

@ -1,4 +1,3 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -11,13 +10,13 @@
* @category replay buffer * @category replay buffer
* @since 4.2.0 * @since 4.2.0
*/ */
void WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler* req) {
if (obs_frontend_replay_buffer_active()) { if (obs_frontend_replay_buffer_active()) {
obs_frontend_replay_buffer_stop(); obs_frontend_replay_buffer_stop();
} else { } else {
Utils::StartReplayBuffer(); Utils::StartReplayBuffer();
} }
req->SendOKResponse(); return req->SendOKResponse();
} }
/** /**
@ -32,19 +31,17 @@ void WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler* req) {
* @category replay buffer * @category replay buffer
* @since 4.2.0 * @since 4.2.0
*/ */
void WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req) {
if (!Utils::ReplayBufferEnabled()) { if (!Utils::ReplayBufferEnabled()) {
req->SendErrorResponse("replay buffer disabled in settings"); return req->SendErrorResponse("replay buffer disabled in settings");
return; }
}
if (obs_frontend_replay_buffer_active() == true) { if (obs_frontend_replay_buffer_active() == true) {
req->SendErrorResponse("replay buffer already active"); return req->SendErrorResponse("replay buffer already active");
return; }
}
Utils::StartReplayBuffer(); Utils::StartReplayBuffer();
req->SendOKResponse(); return req->SendOKResponse();
} }
/** /**
@ -56,13 +53,13 @@ void WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req) {
* @category replay buffer * @category replay buffer
* @since 4.2.0 * @since 4.2.0
*/ */
void WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req) {
if (obs_frontend_replay_buffer_active() == true) { if (obs_frontend_replay_buffer_active() == true) {
obs_frontend_replay_buffer_stop(); obs_frontend_replay_buffer_stop();
req->SendOKResponse(); return req->SendOKResponse();
} else { } else {
req->SendErrorResponse("replay buffer not active"); return req->SendErrorResponse("replay buffer not active");
} }
} }
/** /**
@ -75,18 +72,17 @@ void WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req) {
* @category replay buffer * @category replay buffer
* @since 4.2.0 * @since 4.2.0
*/ */
void WSRequestHandler::HandleSaveReplayBuffer(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSaveReplayBuffer(WSRequestHandler* req) {
if (!obs_frontend_replay_buffer_active()) { if (!obs_frontend_replay_buffer_active()) {
req->SendErrorResponse("replay buffer not active"); return req->SendErrorResponse("replay buffer not active");
return; }
}
OBSOutputAutoRelease replayOutput = obs_frontend_get_replay_buffer_output(); OBSOutputAutoRelease replayOutput = obs_frontend_get_replay_buffer_output();
calldata_t cd = { 0 }; calldata_t cd = { 0 };
proc_handler_t* ph = obs_output_get_proc_handler(replayOutput); proc_handler_t* ph = obs_output_get_proc_handler(replayOutput);
proc_handler_call(ph, "save", &cd); proc_handler_call(ph, "save", &cd);
calldata_free(&cd); calldata_free(&cd);
req->SendOKResponse(); return req->SendOKResponse();
} }

View File

@ -1,4 +1,3 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -13,20 +12,19 @@
* @category scene collections * @category scene collections
* @since 4.0.0 * @since 4.0.0
*/ */
void WSRequestHandler::HandleSetCurrentSceneCollection(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetCurrentSceneCollection(WSRequestHandler* req) {
if (!req->hasField("sc-name")) { if (!req->hasField("sc-name")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return; }
}
QString sceneCollection = obs_data_get_string(req->data, "sc-name"); QString sceneCollection = obs_data_get_string(req->data, "sc-name");
if (!sceneCollection.isEmpty()) { if (sceneCollection.isEmpty()) {
// TODO : Check if specified profile exists and if changing is allowed return req->SendErrorResponse("invalid request parameters");
obs_frontend_set_current_scene_collection(sceneCollection.toUtf8()); }
req->SendOKResponse();
} else { // TODO : Check if specified profile exists and if changing is allowed
req->SendErrorResponse("invalid request parameters"); obs_frontend_set_current_scene_collection(sceneCollection.toUtf8());
} return req->SendOKResponse();
} }
/** /**
@ -39,33 +37,32 @@
* @category scene collections * @category scene collections
* @since 4.0.0 * @since 4.0.0
*/ */
void WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandler* req) {
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "sc-name", obs_data_set_string(response, "sc-name",
obs_frontend_get_current_scene_collection()); obs_frontend_get_current_scene_collection());
req->SendOKResponse(response); return req->SendOKResponse(response);
} }
/** /**
* List available scene collections * List available scene collections
* *
* @return {Object|Array} `scene-collections` Scene collections list * @return {Array<String>} `scene-collections` Scene collections list
* @return {String} `scene-collections.*.`
* *
* @api requests * @api requests
* @name ListSceneCollections * @name ListSceneCollections
* @category scene collections * @category scene collections
* @since 4.0.0 * @since 4.0.0
*/ */
void WSRequestHandler::HandleListSceneCollections(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleListSceneCollections(WSRequestHandler* req) {
char** sceneCollections = obs_frontend_get_scene_collections(); char** sceneCollections = obs_frontend_get_scene_collections();
OBSDataArrayAutoRelease list = OBSDataArrayAutoRelease list =
Utils::StringListToArray(sceneCollections, "sc-name"); Utils::StringListToArray(sceneCollections, "sc-name");
bfree(sceneCollections); bfree(sceneCollections);
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_array(response, "scene-collections", list); obs_data_set_array(response, "scene-collections", list);
req->SendOKResponse(response); return req->SendOKResponse(response);
} }

View File

@ -1,10 +1,10 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
/** /**
* Gets the scene specific properties of the specified source item. * 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` the name of the scene that the source item belongs to. Defaults to the current scene. * @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String} `item` The name of the source. * @param {String} `item` The name of the source.
@ -21,171 +21,106 @@
* @return {int} `crop.bottom` The number of pixels cropped off the bottom 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 {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} `visible` If the source is visible.
* @return {String} `bounds.type` Type of bounding box. * @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 {int} `bounds.alignment` Alignment of the bounding box.
* @return {double} `bounds.x` Width of the bounding box. * @return {double} `bounds.x` Width of the bounding box.
* @return {double} `bounds.y` Height 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)
* @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)
* *
* @api requests * @api requests
* @name GetSceneItemProperties * @name GetSceneItemProperties
* @category scene items * @category scene items
* @since 4.3.0 * @since 4.3.0
*/ */
void WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* req) {
if (!req->hasField("item")) { if (!req->hasField("item")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return;
} }
QString itemName = obs_data_get_string(req->data, "item"); QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) { if (itemName.isEmpty()) {
req->SendErrorResponse("invalid request parameters"); return req->SendErrorResponse("invalid request parameters");
return;
} }
QString sceneName = obs_data_get_string(req->data, "scene-name"); QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
req->SendErrorResponse("requested scene doesn't exist"); return req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSSceneItemAutoRelease sceneItem = OBSSceneItemAutoRelease sceneItem =
Utils::GetSceneItemFromName(scene, itemName); Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) { if (!sceneItem) {
req->SendErrorResponse("specified scene item doesn't exist"); return req->SendErrorResponse("specified scene item doesn't exist");
return;
} }
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = Utils::GetSceneItemPropertiesData(sceneItem);
obs_data_set_string(data, "name", itemName.toUtf8()); obs_data_set_string(data, "name", itemName.toUtf8());
OBSDataAutoRelease posData = obs_data_create(); return req->SendOKResponse(data);
vec2 pos;
obs_sceneitem_get_pos(sceneItem, &pos);
obs_data_set_double(posData, "x", pos.x);
obs_data_set_double(posData, "y", pos.y);
obs_data_set_int(posData, "alignment", obs_sceneitem_get_alignment(sceneItem));
obs_data_set_obj(data, "position", posData);
obs_data_set_double(data, "rotation", obs_sceneitem_get_rot(sceneItem));
OBSDataAutoRelease scaleData = obs_data_create();
vec2 scale;
obs_sceneitem_get_scale(sceneItem, &scale);
obs_data_set_double(scaleData, "x", scale.x);
obs_data_set_double(scaleData, "y", scale.y);
obs_data_set_obj(data, "scale", scaleData);
OBSDataAutoRelease cropData = obs_data_create();
obs_sceneitem_crop crop;
obs_sceneitem_get_crop(sceneItem, &crop);
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);
obs_data_set_obj(data, "crop", cropData);
obs_data_set_bool(data, "visible", obs_sceneitem_visible(sceneItem));
OBSDataAutoRelease boundsData = obs_data_create();
obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem);
if (boundsType == OBS_BOUNDS_NONE) {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_NONE");
}
else {
switch (boundsType) {
case OBS_BOUNDS_STRETCH: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_STRETCH");
break;
}
case OBS_BOUNDS_SCALE_INNER: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_INNER");
break;
}
case OBS_BOUNDS_SCALE_OUTER: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_OUTER");
break;
}
case OBS_BOUNDS_SCALE_TO_WIDTH: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_WIDTH");
break;
}
case OBS_BOUNDS_SCALE_TO_HEIGHT: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_HEIGHT");
break;
}
case OBS_BOUNDS_MAX_ONLY: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_MAX_ONLY");
break;
}
}
obs_data_set_int(boundsData, "alignment", obs_sceneitem_get_bounds_alignment(sceneItem));
vec2 bounds;
obs_sceneitem_get_bounds(sceneItem, &bounds);
obs_data_set_double(boundsData, "x", bounds.x);
obs_data_set_double(boundsData, "y", bounds.y);
}
obs_data_set_obj(data, "bounds", boundsData);
req->SendOKResponse(data);
} }
/** /**
* Sets the scene specific properties of a source. Unspecified properties will remain unchanged. * 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` the name of the scene that the source item belongs to. Defaults to the current scene. * @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String} `item` The name of the source. * @param {String} `item` The name of the source.
* @param {int} `position.x` The new x position of the source. * @param {int (optional)} `position.x` The new x position of the source.
* @param {int} `position.y` The new y position of the source. * @param {int (optional)} `position.y` The new y position of the source.
* @param {int} `position.alignment` The new alignment of the source. * @param {int (optional)} `position.alignment` The new alignment of the source.
* @param {double} `rotation` The new clockwise rotation of the item in degrees. * @param {double (optional)} `rotation` The new clockwise rotation of the item in degrees.
* @param {double} `scale.x` The new x scale of the item. * @param {double (optional)} `scale.x` The new x scale of the item.
* @param {double} `scale.y` The new y scale of the item. * @param {double (optional)} `scale.y` The new y scale of the item.
* @param {int} `crop.top` The new amount of pixels cropped off the top of the source before scaling. * @param {int (optional)} `crop.top` The new amount of pixels cropped off the top of the source before scaling.
* @param {int} `crop.bottom` The new amount of pixels cropped off the bottom 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} `crop.left` The new amount of pixels cropped off the left 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} `crop.right` The new amount of pixels cropped off the right 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} `visible` The new visibility of the source. 'true' shows source, 'false' hides source. * @param {bool (optional)} `visible` The new visibility of the source. 'true' shows source, 'false' hides source.
* @param {String} `bounds.type` The new bounds type of the source. * @param {bool (optional)} `locked` The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement.
* @param {int} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10) * @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 {double} `bounds.x` The new width of the bounding box. * @param {int (optional)} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10)
* @param {double} `bounds.y` The new height of the bounding box. * @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 * @api requests
* @name SetSceneItemProperties * @name SetSceneItemProperties
* @category scene items * @category scene items
* @since 4.3.0 * @since 4.3.0
*/ */
void WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) {
if (!req->hasField("item")) { if (!req->hasField("item")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return;
} }
QString itemName = obs_data_get_string(req->data, "item"); QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) { if (itemName.isEmpty()) {
req->SendErrorResponse("invalid request parameters"); return req->SendErrorResponse("invalid request parameters");
return;
} }
QString sceneName = obs_data_get_string(req->data, "scene-name"); QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
req->SendErrorResponse("requested scene doesn't exist"); return req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSSceneItemAutoRelease sceneItem = OBSSceneItemAutoRelease sceneItem =
Utils::GetSceneItemFromName(scene, itemName); Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) { if (!sceneItem) {
req->SendErrorResponse("specified scene item doesn't exist"); return req->SendErrorResponse("specified scene item doesn't exist");
return;
} }
bool badRequest = false; bool badRequest = false;
OBSDataAutoRelease errorMessage = obs_data_create(); OBSDataAutoRelease errorMessage = obs_data_create();
obs_sceneitem_defer_update_begin(sceneItem);
if (req->hasField("position")) { if (req->hasField("position")) {
vec2 oldPosition; vec2 oldPosition;
OBSDataAutoRelease positionError = obs_data_create(); OBSDataAutoRelease positionError = obs_data_create();
@ -254,12 +189,16 @@ void WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) {
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(req->data, "visible")); obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(req->data, "visible"));
} }
if (req->hasField("locked")) {
obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(req->data, "locked"));
}
if (req->hasField("bounds")) { if (req->hasField("bounds")) {
bool badBounds = false; bool badBounds = false;
OBSDataAutoRelease boundsError = obs_data_create(); OBSDataAutoRelease boundsError = obs_data_create();
OBSDataAutoRelease reqBounds = obs_data_get_obj(req->data, "bounds"); OBSDataAutoRelease reqBounds = obs_data_get_obj(req->data, "bounds");
if (obs_data_has_user_value(reqBounds, "type")) { if (obs_data_has_user_value(reqBounds, "type")) {
const char* newBoundsType = obs_data_get_string(reqBounds, "type"); QString newBoundsType = obs_data_get_string(reqBounds, "type");
if (newBoundsType == "OBS_BOUNDS_NONE") { if (newBoundsType == "OBS_BOUNDS_NONE") {
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_NONE); obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_NONE);
} }
@ -311,18 +250,19 @@ void WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) {
} }
} }
obs_sceneitem_defer_update_end(sceneItem);
if (badRequest) { if (badRequest) {
req->SendErrorResponse(errorMessage); return req->SendErrorResponse(errorMessage);
}
else {
req->SendOKResponse();
} }
return req->SendOKResponse();
} }
/** /**
* Reset a scene item. * Reset a scene item.
* *
* @param {String (optional)} `scene-name` Name of the scene the source belogns to. Defaults to the current scene. * @param {String (optional)} `scene-name` Name of the scene the source belongs to. Defaults to the current scene.
* @param {String} `item` Name of the source item. * @param {String} `item` Name of the source item.
* *
* @api requests * @api requests
@ -330,39 +270,35 @@ void WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) {
* @category scene items * @category scene items
* @since 4.2.0 * @since 4.2.0
*/ */
void WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
// TODO: remove this request, or refactor it to ResetSource // TODO: remove this request, or refactor it to ResetSource
if (!req->hasField("item")) { if (!req->hasField("item")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return;
} }
const char* itemName = obs_data_get_string(req->data, "item"); const char* itemName = obs_data_get_string(req->data, "item");
if (!itemName) { if (!itemName) {
req->SendErrorResponse("invalid request parameters"); return req->SendErrorResponse("invalid request parameters");
return;
} }
const char* sceneName = obs_data_get_string(req->data, "scene-name"); const char* sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
req->SendErrorResponse("requested scene doesn't exist"); return req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (sceneItem) { if (!sceneItem) {
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem); return req->SendErrorResponse("specified scene item doesn't exist");
OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource);
obs_source_update(sceneItemSource, settings);
req->SendOKResponse();
}
else {
req->SendErrorResponse("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 req->SendOKResponse();
} }
/** /**
@ -378,38 +314,34 @@ void WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
* @since 0.3 * @since 0.3
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties. * @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/ */
void WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req) {
if (!req->hasField("source") || if (!req->hasField("source") ||
!req->hasField("render")) !req->hasField("render"))
{ {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return;
} }
const char* itemName = obs_data_get_string(req->data, "source"); const char* itemName = obs_data_get_string(req->data, "source");
bool isVisible = obs_data_get_bool(req->data, "render"); bool isVisible = obs_data_get_bool(req->data, "render");
if (!itemName) { if (!itemName) {
req->SendErrorResponse("invalid request parameters"); return req->SendErrorResponse("invalid request parameters");
return;
} }
const char* sceneName = obs_data_get_string(req->data, "scene-name"); const char* sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
req->SendErrorResponse("requested scene doesn't exist"); return req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSSceneItemAutoRelease sceneItem = OBSSceneItemAutoRelease sceneItem =
Utils::GetSceneItemFromName(scene, itemName); Utils::GetSceneItemFromName(scene, itemName);
if (sceneItem) { if (!sceneItem) {
obs_sceneitem_set_visible(sceneItem, isVisible); return req->SendErrorResponse("specified scene item doesn't exist");
req->SendOKResponse();
}
else {
req->SendErrorResponse("specified scene item doesn't exist");
} }
obs_sceneitem_set_visible(sceneItem, isVisible);
return req->SendOKResponse();
} }
/** /**
@ -427,38 +359,34 @@ void WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req) {
* @since 4.0.0 * @since 4.0.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties. * @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/ */
void WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* req) {
if (!req->hasField("item") || if (!req->hasField("item") ||
!req->hasField("x") || !req->hasField("y")) { !req->hasField("x") || !req->hasField("y")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return;
} }
QString itemName = obs_data_get_string(req->data, "item"); QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) { if (itemName.isEmpty()) {
req->SendErrorResponse("invalid request parameters"); return req->SendErrorResponse("invalid request parameters");
return;
} }
QString sceneName = obs_data_get_string(req->data, "scene-name"); QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
req->SendErrorResponse("requested scene could not be found"); return req->SendErrorResponse("requested scene could not be found");
return;
} }
OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName); OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (sceneItem) { if (!sceneItem) {
vec2 item_position = { 0 }; return req->SendErrorResponse("specified scene item doesn't exist");
item_position.x = obs_data_get_double(req->data, "x"); }
item_position.y = obs_data_get_double(req->data, "y");
obs_sceneitem_set_pos(sceneItem, &item_position);
req->SendOKResponse(); vec2 item_position = { 0 };
} item_position.x = obs_data_get_double(req->data, "x");
else { item_position.y = obs_data_get_double(req->data, "y");
req->SendErrorResponse("specified scene item doesn't exist"); obs_sceneitem_set_pos(sceneItem, &item_position);
}
return req->SendOKResponse();
} }
/** /**
@ -476,27 +404,24 @@ void WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* req) {
* @since 4.0.0 * @since 4.0.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties. * @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/ */
void WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) {
if (!req->hasField("item") || if (!req->hasField("item") ||
!req->hasField("x-scale") || !req->hasField("x-scale") ||
!req->hasField("y-scale") || !req->hasField("y-scale") ||
!req->hasField("rotation")) !req->hasField("rotation"))
{ {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return;
} }
QString itemName = obs_data_get_string(req->data, "item"); QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) { if (itemName.isEmpty()) {
req->SendErrorResponse("invalid request parameters"); return req->SendErrorResponse("invalid request parameters");
return;
} }
QString sceneName = obs_data_get_string(req->data, "scene-name"); QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
req->SendErrorResponse("requested scene doesn't exist"); return req->SendErrorResponse("requested scene doesn't exist");
return;
} }
vec2 scale; vec2 scale;
@ -505,14 +430,18 @@ void WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) {
float rotation = obs_data_get_double(req->data, "rotation"); float rotation = obs_data_get_double(req->data, "rotation");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (sceneItem) { if (!sceneItem) {
obs_sceneitem_set_scale(sceneItem, &scale); return req->SendErrorResponse("specified scene item doesn't exist");
obs_sceneitem_set_rot(sceneItem, rotation);
req->SendOKResponse();
}
else {
req->SendErrorResponse("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 req->SendOKResponse();
} }
/** /**
@ -531,38 +460,147 @@ void WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) {
* @since 4.1.0 * @since 4.1.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties. * @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/ */
void WSRequestHandler::HandleSetSceneItemCrop(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetSceneItemCrop(WSRequestHandler* req) {
if (!req->hasField("item")) { if (!req->hasField("item")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return;
} }
QString itemName = obs_data_get_string(req->data, "item"); QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) { if (itemName.isEmpty()) {
req->SendErrorResponse("invalid request parameters"); return req->SendErrorResponse("invalid request parameters");
return;
} }
QString sceneName = obs_data_get_string(req->data, "scene-name"); QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
req->SendErrorResponse("requested scene doesn't exist"); return req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (sceneItem) { if (!sceneItem) {
struct obs_sceneitem_crop crop = { 0 }; return req->SendErrorResponse("specified scene item doesn't exist");
crop.top = obs_data_get_int(req->data, "top");
crop.bottom = obs_data_get_int(req->data, "bottom");
crop.left = obs_data_get_int(req->data, "left");
crop.right = obs_data_get_int(req->data, "right");
obs_sceneitem_set_crop(sceneItem, &crop);
req->SendOKResponse();
}
else {
req->SendErrorResponse("specified scene item doesn't exist");
} }
struct obs_sceneitem_crop crop = { 0 };
crop.top = obs_data_get_int(req->data, "top");
crop.bottom = obs_data_get_int(req->data, "bottom");
crop.left = obs_data_get_int(req->data, "left");
crop.right = obs_data_get_int(req->data, "right");
obs_sceneitem_set_crop(sceneItem, &crop);
return req->SendOKResponse();
}
/**
* Deletes a scene item.
*
* @param {String (optional)} `scene` Name of the scene the source belongs to. Defaults to the current scene.
* @param {Object} `item` item to delete (required)
* @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable).
* @param {int} `item.id` id of the scene item.
*
* @api requests
* @name DeleteSceneItem
* @category scene items
* @since 4.5.0
*/
HandlerResponse WSRequestHandler::HandleDeleteSceneItem(WSRequestHandler* req) {
if (!req->hasField("item")) {
return req->SendErrorResponse("missing request parameters");
}
const char* sceneName = obs_data_get_string(req->data, "scene");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return req->SendErrorResponse("requested scene doesn't exist");
}
OBSDataAutoRelease item = obs_data_get_obj(req->data, "item");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
if (!sceneItem) {
return req->SendErrorResponse("item with id/name combination not found in specified scene");
}
obs_sceneitem_remove(sceneItem);
return req->SendOKResponse();
}
struct DuplicateSceneItemData {
obs_sceneitem_t *referenceItem;
obs_source_t *fromSource;
obs_sceneitem_t *newItem;
};
static void DuplicateSceneItem(void *_data, obs_scene_t *scene) {
DuplicateSceneItemData *data = (DuplicateSceneItemData *)_data;
data->newItem = obs_scene_add(scene, data->fromSource);
obs_sceneitem_set_visible(data->newItem, obs_sceneitem_visible(data->referenceItem));
}
/**
* 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` item to duplicate (required)
* @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable).
* @param {int} `item.id` id of the scene item.
*
* @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
*/
HandlerResponse WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req) {
if (!req->hasField("item")) {
return req->SendErrorResponse("missing request parameters");
}
const char* fromSceneName = obs_data_get_string(req->data, "fromScene");
OBSSourceAutoRelease fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName);
if (!fromScene) {
return req->SendErrorResponse("requested fromScene doesn't exist");
}
const char* toSceneName = obs_data_get_string(req->data, "toScene");
OBSSourceAutoRelease toScene = Utils::GetSceneFromNameOrCurrent(toSceneName);
if (!toScene) {
return req->SendErrorResponse("requested toScene doesn't exist");
}
OBSDataAutoRelease item = obs_data_get_obj(req->data, "item");
OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromItem(fromScene, item);
if (!referenceItem) {
return req->SendErrorResponse("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(obs_scene_from_source(toScene), DuplicateSceneItem, &data);
obs_leave_graphics();
obs_sceneitem_t *newItem = data.newItem;
if (!newItem) {
return req->SendErrorResponse("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(toScene));
return req->SendOKResponse(responseData);
} }

View File

@ -1,8 +1,13 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.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. * Switch to the specified scene.
* *
@ -13,64 +18,124 @@
* @category scenes * @category scenes
* @since 0.3 * @since 0.3
*/ */
void WSRequestHandler::HandleSetCurrentScene(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetCurrentScene(WSRequestHandler* req) {
if (!req->hasField("scene-name")) { if (!req->hasField("scene-name")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return; }
}
const char* sceneName = obs_data_get_string(req->data, "scene-name"); const char* sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName); OBSSourceAutoRelease source = obs_get_source_by_name(sceneName);
if (source) { if (source) {
obs_frontend_set_current_scene(source); obs_frontend_set_current_scene(source);
req->SendOKResponse(); return req->SendOKResponse();
} else { } else {
req->SendErrorResponse("requested scene does not exist"); return req->SendErrorResponse("requested scene does not exist");
} }
} }
/** /**
* Get the current scene's name and source items. * Get the current scene's name and source items.
* *
* @return {String} `name` Name of the currently active scene. * @return {String} `name` Name of the currently active scene.
* @return {Source|Array} `sources` Ordered list of the current scene's source items. * @return {Array<SceneItem>} `sources` Ordered list of the current scene's source items.
* *
* @api requests * @api requests
* @name GetCurrentScene * @name GetCurrentScene
* @category scenes * @category scenes
* @since 0.3 * @since 0.3
*/ */
void WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) {
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene(); OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene); OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene);
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "name", obs_source_get_name(currentScene)); obs_data_set_string(data, "name", obs_source_get_name(currentScene));
obs_data_set_array(data, "sources", sceneItems); obs_data_set_array(data, "sources", sceneItems);
req->SendOKResponse(data); return req->SendOKResponse(data);
} }
/** /**
* Get a list of scenes in the currently active profile. * Get a list of scenes in the currently active profile.
* *
* @return {String} `current-scene` Name of the currently active scene. * @return {String} `current-scene` Name of the currently active scene.
* @return {Scene|Array} `scenes` Ordered list of the current profile's scenes (See `[GetCurrentScene](#getcurrentscene)` for more information). * @return {Array<Scene>} `scenes` Ordered list of the current profile's scenes (See `[GetCurrentScene](#getcurrentscene)` for more information).
* *
* @api requests * @api requests
* @name GetSceneList * @name GetSceneList
* @category scenes * @category scenes
* @since 0.3 * @since 0.3
*/ */
void WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene(); OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
OBSDataArrayAutoRelease scenes = Utils::GetScenes(); OBSDataArrayAutoRelease scenes = Utils::GetScenes();
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "current-scene", obs_data_set_string(data, "current-scene",
obs_source_get_name(currentScene)); obs_source_get_name(currentScene));
obs_data_set_array(data, "scenes", scenes); obs_data_set_array(data, "scenes", scenes);
req->SendOKResponse(data); return req->SendOKResponse(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
*/
HandlerResponse WSRequestHandler::HandleReorderSceneItems(WSRequestHandler* req) {
QString sceneName = obs_data_get_string(req->data, "scene");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return req->SendErrorResponse("requested scene doesn't exist");
}
OBSDataArrayAutoRelease items = obs_data_get_array(req->data, "items");
if (!items) {
return req->SendErrorResponse("sceneItem order not specified");
}
size_t count = obs_data_array_count(items);
std::vector<obs_sceneitem_t*> newOrder;
newOrder.reserve(count);
for (size_t i = 0; i < count; ++i) {
OBSDataAutoRelease item = obs_data_array_item(items, i);
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
obs_sceneitem_release(sceneItem); // ref dec
if (!sceneItem) {
return req->SendErrorResponse("Invalid sceneItem id or name specified");
}
for (size_t j = 0; j <= i; ++j) {
if (sceneItem == newOrder[j]) {
return req->SendErrorResponse("Duplicate sceneItem in specified order");
}
}
newOrder.push_back(sceneItem);
}
bool success = obs_scene_reorder_items(obs_scene_from_source(scene), newOrder.data(), count);
if (!success) {
return req->SendErrorResponse("Invalid sceneItem order");
}
for (auto const& item: newOrder) {
obs_sceneitem_release(item);
}
return req->SendOKResponse();
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
#include <QString> #include "obs-websocket.h"
#include "Utils.h" #include "Utils.h"
#include "WSEvents.h" #include "WSEvents.h"
@ -20,26 +20,28 @@
* @category streaming * @category streaming
* @since 0.3 * @since 0.3
*/ */
void WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req) {
OBSDataAutoRelease data = obs_data_create(); auto events = GetEventsSystem();
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);
const char* tc = nullptr; OBSDataAutoRelease data = obs_data_create();
if (obs_frontend_streaming_active()) { obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
tc = WSEvents::Instance->GetStreamingTimecode(); obs_data_set_bool(data, "recording", obs_frontend_recording_active());
obs_data_set_string(data, "stream-timecode", tc); obs_data_set_bool(data, "preview-only", false);
bfree((void*)tc);
}
if (obs_frontend_recording_active()) { const char* tc = nullptr;
tc = WSEvents::Instance->GetRecordingTimecode(); if (obs_frontend_streaming_active()) {
obs_data_set_string(data, "rec-timecode", tc); tc = events->GetStreamingTimecode();
bfree((void*)tc); obs_data_set_string(data, "stream-timecode", tc);
} bfree((void*)tc);
}
req->SendOKResponse(data); if (obs_frontend_recording_active()) {
tc = events->GetRecordingTimecode();
obs_data_set_string(data, "rec-timecode", tc);
bfree((void*)tc);
}
return req->SendOKResponse(data);
} }
/** /**
@ -50,11 +52,11 @@
* @category streaming * @category streaming
* @since 0.3 * @since 0.3
*/ */
void WSRequestHandler::HandleStartStopStreaming(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleStartStopStreaming(WSRequestHandler* req) {
if (obs_frontend_streaming_active()) if (obs_frontend_streaming_active())
HandleStopStreaming(req); return HandleStopStreaming(req);
else else
HandleStartStreaming(req); return HandleStartStreaming(req);
} }
/** /**
@ -76,91 +78,91 @@
* @category streaming * @category streaming
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
if (obs_frontend_streaming_active() == false) { if (obs_frontend_streaming_active() == false) {
OBSService configuredService = obs_frontend_get_streaming_service(); OBSService configuredService = obs_frontend_get_streaming_service();
OBSService newService = nullptr; OBSService newService = nullptr;
// TODO: fix service memory leak // TODO: fix service memory leak
if (req->hasField("stream")) { if (req->hasField("stream")) {
OBSDataAutoRelease streamData = obs_data_get_obj(req->data, "stream"); OBSDataAutoRelease streamData = obs_data_get_obj(req->data, "stream");
OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings"); OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings");
OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata"); OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata");
OBSDataAutoRelease csHotkeys = OBSDataAutoRelease csHotkeys =
obs_hotkeys_save_service(configuredService); obs_hotkeys_save_service(configuredService);
QString currentType = obs_service_get_type(configuredService); QString currentType = obs_service_get_type(configuredService);
QString newType = obs_data_get_string(streamData, "type"); QString newType = obs_data_get_string(streamData, "type");
if (newType.isEmpty() || newType.isNull()) { if (newType.isEmpty() || newType.isNull()) {
newType = currentType; newType = currentType;
} }
//Supporting adding metadata parameters to key query string //Supporting adding metadata parameters to key query string
QString query = Utils::ParseDataToQueryString(newMetadata); QString query = Utils::ParseDataToQueryString(newMetadata);
if (!query.isEmpty() if (!query.isEmpty()
&& obs_data_has_user_value(newSettings, "key")) && obs_data_has_user_value(newSettings, "key"))
{ {
const char* key = obs_data_get_string(newSettings, "key"); const char* key = obs_data_get_string(newSettings, "key");
int keylen = strlen(key); int keylen = strlen(key);
bool hasQuestionMark = false; bool hasQuestionMark = false;
for (int i = 0; i < keylen; i++) { for (int i = 0; i < keylen; i++) {
if (key[i] == '?') { if (key[i] == '?') {
hasQuestionMark = true; hasQuestionMark = true;
break; break;
} }
} }
if (hasQuestionMark) { if (hasQuestionMark) {
query.prepend('&'); query.prepend('&');
} else { } else {
query.prepend('?'); query.prepend('?');
} }
query.prepend(key); query.prepend(key);
obs_data_set_string(newSettings, "key", query.toUtf8()); obs_data_set_string(newSettings, "key", query.toUtf8());
} }
if (newType == currentType) { if (newType == currentType) {
// Service type doesn't change: apply settings to current service // Service type doesn't change: apply settings to current service
// By doing this, you can send a request to the websocket // By doing this, you can send a request to the websocket
// that only contains settings you want to change, instead of // that only contains settings you want to change, instead of
// having to do a get and then change them // having to do a get and then change them
OBSDataAutoRelease currentSettings = obs_service_get_settings(configuredService); OBSDataAutoRelease currentSettings = obs_service_get_settings(configuredService);
OBSDataAutoRelease updatedSettings = obs_data_create(); OBSDataAutoRelease updatedSettings = obs_data_create();
obs_data_apply(updatedSettings, currentSettings); //first apply the existing settings 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 obs_data_apply(updatedSettings, newSettings); //then apply the settings from the request should they exist
newService = obs_service_create( newService = obs_service_create(
newType.toUtf8(), STREAM_SERVICE_ID, newType.toUtf8(), STREAM_SERVICE_ID,
updatedSettings, csHotkeys); updatedSettings, csHotkeys);
} }
else { else {
// Service type changed: override service settings // Service type changed: override service settings
newService = obs_service_create( newService = obs_service_create(
newType.toUtf8(), STREAM_SERVICE_ID, newType.toUtf8(), STREAM_SERVICE_ID,
newSettings, csHotkeys); newSettings, csHotkeys);
} }
obs_frontend_set_streaming_service(newService); obs_frontend_set_streaming_service(newService);
} }
obs_frontend_streaming_start(); obs_frontend_streaming_start();
// Stream settings provided in StartStreaming are not persisted to disk // Stream settings provided in StartStreaming are not persisted to disk
if (newService != nullptr) { if (newService != nullptr) {
obs_frontend_set_streaming_service(configuredService); obs_frontend_set_streaming_service(configuredService);
} }
req->SendOKResponse(); return req->SendOKResponse();
} else { } else {
req->SendErrorResponse("streaming already active"); return req->SendErrorResponse("streaming already active");
} }
} }
/** /**
@ -172,13 +174,13 @@
* @category streaming * @category streaming
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
if (obs_frontend_streaming_active() == true) { if (obs_frontend_streaming_active() == true) {
obs_frontend_streaming_stop(); obs_frontend_streaming_stop();
req->SendOKResponse(); return req->SendOKResponse();
} else { } else {
req->SendErrorResponse("streaming not active"); return req->SendErrorResponse("streaming not active");
} }
} }
/** /**
@ -198,51 +200,50 @@ void WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
* @category streaming * @category streaming
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req) {
OBSService service = obs_frontend_get_streaming_service(); OBSService service = obs_frontend_get_streaming_service();
OBSDataAutoRelease requestSettings = obs_data_get_obj(req->data, "settings"); OBSDataAutoRelease requestSettings = obs_data_get_obj(req->data, "settings");
if (!requestSettings) { if (!requestSettings) {
req->SendErrorResponse("'settings' are required'"); return req->SendErrorResponse("'settings' are required'");
return; }
}
QString serviceType = obs_service_get_type(service); QString serviceType = obs_service_get_type(service);
QString requestedType = obs_data_get_string(req->data, "type"); QString requestedType = obs_data_get_string(req->data, "type");
if (requestedType != nullptr && requestedType != serviceType) { if (requestedType != nullptr && requestedType != serviceType) {
OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service); OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service);
service = obs_service_create( service = obs_service_create(
requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys); requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys);
} else { } else {
// If type isn't changing, we should overlay the settings we got // 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 // 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 // only contains the settings you want to change, instead of having to
// do a get and then change them // do a get and then change them
OBSDataAutoRelease existingSettings = obs_service_get_settings(service); OBSDataAutoRelease existingSettings = obs_service_get_settings(service);
OBSDataAutoRelease newSettings = obs_data_create(); OBSDataAutoRelease newSettings = obs_data_create();
// Apply existing settings // Apply existing settings
obs_data_apply(newSettings, existingSettings); obs_data_apply(newSettings, existingSettings);
// Then apply the settings from the request // Then apply the settings from the request
obs_data_apply(newSettings, requestSettings); obs_data_apply(newSettings, requestSettings);
obs_service_update(service, newSettings); obs_service_update(service, newSettings);
} }
//if save is specified we should immediately save the streaming service //if save is specified we should immediately save the streaming service
if (obs_data_get_bool(req->data, "save")) { if (obs_data_get_bool(req->data, "save")) {
obs_frontend_save_streaming_service(); obs_frontend_save_streaming_service();
} }
OBSDataAutoRelease serviceSettings = obs_service_get_settings(service); OBSDataAutoRelease serviceSettings = obs_service_get_settings(service);
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "type", requestedType.toUtf8()); obs_data_set_string(response, "type", requestedType.toUtf8());
obs_data_set_obj(response, "settings", serviceSettings); obs_data_set_obj(response, "settings", serviceSettings);
req->SendOKResponse(response); return req->SendOKResponse(response);
} }
/** /**
@ -252,7 +253,7 @@ void WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
* @return {Object} `settings` Stream settings object. * @return {Object} `settings` Stream settings object.
* @return {String} `settings.server` The publish URL. * @return {String} `settings.server` The publish URL.
* @return {String} `settings.key` The publish key of the stream. * @return {String} `settings.key` The publish key of the stream.
* @return {boolean} `settings.use-auth` Indicates whether audentication should be used when connecting to the streaming server. * @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.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`. * @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use-auth` is `true`.
* *
@ -261,17 +262,17 @@ void WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
* @category streaming * @category streaming
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req) {
OBSService service = obs_frontend_get_streaming_service(); OBSService service = obs_frontend_get_streaming_service();
const char* serviceType = obs_service_get_type(service); const char* serviceType = obs_service_get_type(service);
OBSDataAutoRelease settings = obs_service_get_settings(service); OBSDataAutoRelease settings = obs_service_get_settings(service);
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "type", serviceType); obs_data_set_string(response, "type", serviceType);
obs_data_set_obj(response, "settings", settings); obs_data_set_obj(response, "settings", settings);
req->SendOKResponse(response); return req->SendOKResponse(response);
} }
/** /**
@ -282,7 +283,36 @@ void WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req) {
* @category streaming * @category streaming
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req) {
obs_frontend_save_streaming_service(); obs_frontend_save_streaming_service();
req->SendOKResponse(); return req->SendOKResponse();
} }
/**
* 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
HandlerResponse WSRequestHandler::HandleSendCaptions(WSRequestHandler* req) {
if (!req->hasField("text")) {
return req->SendErrorResponse("missing request parameters");
}
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
if (output) {
const char* caption = obs_data_get_string(req->data, "text");
obs_output_output_caption_text1(output, caption);
}
return req->SendOKResponse();
}
#endif

View File

@ -1,4 +1,3 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -13,13 +12,13 @@
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleGetStudioModeStatus(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetStudioModeStatus(WSRequestHandler* req) {
bool previewActive = obs_frontend_preview_program_mode_active(); bool previewActive = obs_frontend_preview_program_mode_active();
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_bool(response, "studio-mode", previewActive); obs_data_set_bool(response, "studio-mode", previewActive);
req->SendOKResponse(response); return req->SendOKResponse(response);
} }
/** /**
@ -27,27 +26,26 @@
* Will return an `error` if Studio Mode is not enabled. * Will return an `error` if Studio Mode is not enabled.
* *
* @return {String} `name` The name of the active preview scene. * @return {String} `name` The name of the active preview scene.
* @return {Source|Array} `sources` * @return {Array<SceneItem>} `sources`
* *
* @api requests * @api requests
* @name GetPreviewScene * @name GetPreviewScene
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
if (!obs_frontend_preview_program_mode_active()) { if (!obs_frontend_preview_program_mode_active()) {
req->SendErrorResponse("studio mode not enabled"); return req->SendErrorResponse("studio mode not enabled");
return; }
}
OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene(); OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene();
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(scene); OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(scene);
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "name", obs_source_get_name(scene)); obs_data_set_string(data, "name", obs_source_get_name(scene));
obs_data_set_array(data, "sources", sceneItems); obs_data_set_array(data, "sources", sceneItems);
req->SendOKResponse(data); return req->SendOKResponse(data);
} }
/** /**
@ -61,26 +59,23 @@ void WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) {
if (!obs_frontend_preview_program_mode_active()) { if (!obs_frontend_preview_program_mode_active()) {
req->SendErrorResponse("studio mode not enabled"); return req->SendErrorResponse("studio mode not enabled");
return; }
}
if (!req->hasField("scene-name")) { if (!req->hasField("scene-name")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return; }
}
const char* scene_name = obs_data_get_string(req->data, "scene-name"); const char* scene_name = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(scene_name); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(scene_name);
if (!scene) {
return req->SendErrorResponse("specified scene doesn't exist");
}
if (scene) { obs_frontend_set_current_preview_scene(scene);
obs_frontend_set_current_preview_scene(scene); return req->SendOKResponse();
req->SendOKResponse();
} else {
req->SendErrorResponse("specified scene doesn't exist");
}
} }
/** /**
@ -96,40 +91,37 @@ void WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) {
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* req) {
if (!obs_frontend_preview_program_mode_active()) { if (!obs_frontend_preview_program_mode_active()) {
req->SendErrorResponse("studio mode not enabled"); return req->SendErrorResponse("studio mode not enabled");
return; }
}
if (req->hasField("with-transition")) { if (req->hasField("with-transition")) {
OBSDataAutoRelease transitionInfo = OBSDataAutoRelease transitionInfo =
obs_data_get_obj(req->data, "with-transition"); obs_data_get_obj(req->data, "with-transition");
if (obs_data_has_user_value(transitionInfo, "name")) { if (obs_data_has_user_value(transitionInfo, "name")) {
QString transitionName = QString transitionName =
obs_data_get_string(transitionInfo, "name"); obs_data_get_string(transitionInfo, "name");
if (transitionName.isEmpty()) { if (transitionName.isEmpty()) {
req->SendErrorResponse("invalid request parameters"); return req->SendErrorResponse("invalid request parameters");
return; }
}
bool success = Utils::SetTransitionByName(transitionName); bool success = Utils::SetTransitionByName(transitionName);
if (!success) { if (!success) {
req->SendErrorResponse("specified transition doesn't exist"); return req->SendErrorResponse("specified transition doesn't exist");
return; }
} }
}
if (obs_data_has_user_value(transitionInfo, "duration")) { if (obs_data_has_user_value(transitionInfo, "duration")) {
int transitionDuration = int transitionDuration =
obs_data_get_int(transitionInfo, "duration"); obs_data_get_int(transitionInfo, "duration");
Utils::SetTransitionDuration(transitionDuration); Utils::SetTransitionDuration(transitionDuration);
} }
} }
Utils::TransitionToProgram(); Utils::TransitionToProgram();
req->SendOKResponse(); return req->SendOKResponse();
} }
/** /**
@ -140,9 +132,9 @@ void WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* req) {
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req) {
obs_frontend_set_preview_program_mode(true); obs_frontend_set_preview_program_mode(true);
req->SendOKResponse(); return req->SendOKResponse();
} }
/** /**
@ -153,9 +145,9 @@ void WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req) {
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req) {
obs_frontend_set_preview_program_mode(false); obs_frontend_set_preview_program_mode(false);
req->SendOKResponse(); return req->SendOKResponse();
} }
/** /**
@ -166,8 +158,8 @@ void WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req) {
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleToggleStudioMode(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleToggleStudioMode(WSRequestHandler* req) {
bool previewProgramMode = obs_frontend_preview_program_mode_active(); bool previewProgramMode = obs_frontend_preview_program_mode_active();
obs_frontend_set_preview_program_mode(!previewProgramMode); obs_frontend_set_preview_program_mode(!previewProgramMode);
req->SendOKResponse(); return req->SendOKResponse();
} }

View File

@ -1,4 +1,3 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -7,35 +6,35 @@
* List of all transitions available in the frontend's dropdown menu. * List of all transitions available in the frontend's dropdown menu.
* *
* @return {String} `current-transition` Name of the currently active transition. * @return {String} `current-transition` Name of the currently active transition.
* @return {Object|Array} `transitions` List of transitions. * @return {Array<Object>} `transitions` List of transitions.
* @return {String} `transitions[].name` Name of the transition. * @return {String} `transitions.*.name` Name of the transition.
* *
* @api requests * @api requests
* @name GetTransitionList * @name GetTransitionList
* @category transitions * @category transitions
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req) {
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition(); OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
obs_frontend_source_list transitionList = {}; obs_frontend_source_list transitionList = {};
obs_frontend_get_transitions(&transitionList); obs_frontend_get_transitions(&transitionList);
OBSDataArrayAutoRelease transitions = obs_data_array_create(); OBSDataArrayAutoRelease transitions = obs_data_array_create();
for (size_t i = 0; i < transitionList.sources.num; i++) { for (size_t i = 0; i < transitionList.sources.num; i++) {
OBSSource transition = transitionList.sources.array[i]; OBSSource transition = transitionList.sources.array[i];
OBSDataAutoRelease obj = obs_data_create(); OBSDataAutoRelease obj = obs_data_create();
obs_data_set_string(obj, "name", obs_source_get_name(transition)); obs_data_set_string(obj, "name", obs_source_get_name(transition));
obs_data_array_push_back(transitions, obj); obs_data_array_push_back(transitions, obj);
} }
obs_frontend_source_list_free(&transitionList); obs_frontend_source_list_free(&transitionList);
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "current-transition", obs_data_set_string(response, "current-transition",
obs_source_get_name(currentTransition)); obs_source_get_name(currentTransition));
obs_data_set_array(response, "transitions", transitions); obs_data_set_array(response, "transitions", transitions);
req->SendOKResponse(response); return req->SendOKResponse(response);
} }
/** /**
@ -49,17 +48,17 @@
* @category transitions * @category transitions
* @since 0.3 * @since 0.3
*/ */
void WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* req) {
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition(); OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "name", obs_data_set_string(response, "name",
obs_source_get_name(currentTransition)); obs_source_get_name(currentTransition));
if (!obs_transition_fixed(currentTransition)) if (!obs_transition_fixed(currentTransition))
obs_data_set_int(response, "duration", Utils::GetTransitionDuration()); obs_data_set_int(response, "duration", Utils::GetTransitionDuration());
req->SendOKResponse(response); return req->SendOKResponse(response);
} }
/** /**
@ -72,18 +71,18 @@ void WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* req) {
* @category transitions * @category transitions
* @since 0.3 * @since 0.3
*/ */
void WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler* req) {
if (!req->hasField("transition-name")) { if (!req->hasField("transition-name")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return; }
}
QString name = obs_data_get_string(req->data, "transition-name"); QString name = obs_data_get_string(req->data, "transition-name");
bool success = Utils::SetTransitionByName(name); bool success = Utils::SetTransitionByName(name);
if (success) if (!success) {
req->SendOKResponse(); return req->SendErrorResponse("requested transition does not exist");
else }
req->SendErrorResponse("requested transition does not exist");
return req->SendOKResponse();
} }
/** /**
@ -96,15 +95,14 @@ void WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler* req) {
* @category transitions * @category transitions
* @since 4.0.0 * @since 4.0.0
*/ */
void WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler* req) {
if (!req->hasField("duration")) { if (!req->hasField("duration")) {
req->SendErrorResponse("missing request parameters"); return req->SendErrorResponse("missing request parameters");
return; }
}
int ms = obs_data_get_int(req->data, "duration"); int ms = obs_data_get_int(req->data, "duration");
Utils::SetTransitionDuration(ms); Utils::SetTransitionDuration(ms);
req->SendOKResponse(); return req->SendOKResponse();
} }
/** /**
@ -117,10 +115,8 @@ void WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler* req) {
* @category transitions * @category transitions
* @since 4.1.0 * @since 4.1.0
*/ */
void WSRequestHandler::HandleGetTransitionDuration(WSRequestHandler* req) { HandlerResponse WSRequestHandler::HandleGetTransitionDuration(WSRequestHandler* req) {
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_int(response, "transition-duration", obs_data_set_int(response, "transition-duration", Utils::GetTransitionDuration());
Utils::GetTransitionDuration()); return req->SendOKResponse(response);
req->SendOKResponse(response);
} }

View File

@ -16,12 +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/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#include <QtWebSockets/QWebSocket> #include <chrono>
#include <thread>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <QtCore/QByteArray> #include <QtCore/QByteArray>
#include <QMainWindow> #include <QtWidgets/QMainWindow>
#include <QMessageBox> #include <QtWidgets/QMessageBox>
#include <QtConcurrent/QtConcurrent>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <util/platform.h>
#include "WSServer.h" #include "WSServer.h"
#include "obs-websocket.h" #include "obs-websocket.h"
@ -30,139 +34,181 @@ with this program. If not, see <https://www.gnu.org/licenses/>
QT_USE_NAMESPACE QT_USE_NAMESPACE
WSServer* WSServer::Instance = nullptr; using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
WSServer::WSServer(QObject* parent) WSServer::WSServer()
: QObject(parent), : QObject(nullptr),
_wsServer(Q_NULLPTR), _connections(),
_clients(), _clMutex(QMutex::Recursive)
_clMutex(QMutex::Recursive)
{ {
_wsServer = new QWebSocketServer( _server.init_asio();
QStringLiteral("obs-websocket"), #ifndef _WIN32
QWebSocketServer::NonSecureMode); _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() { WSServer::~WSServer()
Stop(); {
stop();
} }
void WSServer::Start(quint16 port) { void WSServer::start(quint16 port)
if (port == _wsServer->serverPort()) {
return; if (_server.is_listening() && port == _serverPort) {
blog(LOG_INFO, "WSServer::start: server already on this port. no restart needed");
return;
}
if(_wsServer->isListening()) if (_server.is_listening()) {
Stop(); stop();
}
bool serverStarted = _wsServer->listen(QHostAddress::Any, port); _server.reset();
if (serverStarted) {
blog(LOG_INFO, "server started successfully on TCP port %d", port);
connect(_wsServer, SIGNAL(newConnection()), _serverPort = port;
this, SLOT(onNewConnection()));
}
else {
QString errorString = _wsServer->errorString();
blog(LOG_ERROR,
"error: failed to start server on TCP port %d: %s",
port, errorString.toUtf8().constData());
QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window(); websocketpp::lib::error_code errorCode;
_server.listen(_serverPort, errorCode);
obs_frontend_push_ui_translation(obs_module_get_string); if (errorCode) {
QString title = tr("OBSWebsocket.Server.StartFailed.Title"); std::string errorCodeMessage = errorCode.message();
QString msg = tr("OBSWebsocket.Server.StartFailed.Message").arg(port); blog(LOG_INFO, "server: listen failed: %s", errorCodeMessage.c_str());
obs_frontend_pop_ui_translation();
QMessageBox::warning(mainWindow, title, msg); 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);
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() { void WSServer::stop()
QMutexLocker locker(&_clMutex); {
for(QWebSocket* pClient : _clients) { if (!_server.is_listening()) {
pClient->close(); return;
} }
locker.unlock();
_wsServer->close(); _server.stop_listening();
for (connection_hdl hdl : _connections) {
_server.close(hdl, websocketpp::close::status::going_away, "Server stopping");
}
_connections.clear();
_connectionProperties.clear();
blog(LOG_INFO, "server stopped successfully"); _threadPool.waitForDone();
while (!_server.stopped()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
blog(LOG_INFO, "server stopped successfully");
} }
void WSServer::broadcast(QString message) { void WSServer::broadcast(std::string message)
QMutexLocker locker(&_clMutex); {
for(QWebSocket* pClient : _clients) { QMutexLocker locker(&_clMutex);
if (Config::Current()->AuthRequired for (connection_hdl hdl : _connections) {
&& (pClient->property(PROP_AUTHENTICATED).toBool() == false)) { if (GetConfig()->AuthRequired) {
// Skip this client if unauthenticated bool authenticated = _connectionProperties[hdl].isAuthenticated();
continue; if (!authenticated) {
} continue;
pClient->sendTextMessage(message); }
} }
_server.send(hdl, message, websocketpp::frame::opcode::text);
}
} }
void WSServer::onNewConnection() { void WSServer::onOpen(connection_hdl hdl)
QWebSocket* pSocket = _wsServer->nextPendingConnection(); {
if (pSocket) { QMutexLocker locker(&_clMutex);
connect(pSocket, SIGNAL(textMessageReceived(const QString&)), _connections.insert(hdl);
this, SLOT(onTextMessageReceived(QString))); locker.unlock();
connect(pSocket, SIGNAL(disconnected()),
this, SLOT(onSocketDisconnected()));
pSocket->setProperty(PROP_AUTHENTICATED, false); QString clientIp = getRemoteEndpoint(hdl);
notifyConnection(clientIp);
QMutexLocker locker(&_clMutex); blog(LOG_INFO, "new client connection from %s", clientIp.toUtf8().constData());
_clients << pSocket;
locker.unlock();
QHostAddress clientAddr = pSocket->peerAddress();
QString clientIp = Utils::FormatIPAddress(clientAddr);
blog(LOG_INFO, "new client connection from %s:%d",
clientIp.toUtf8().constData(), pSocket->peerPort());
obs_frontend_push_ui_translation(obs_module_get_string);
QString title = tr("OBSWebsocket.NotifyConnect.Title");
QString msg = tr("OBSWebsocket.NotifyConnect.Message")
.arg(Utils::FormatIPAddress(clientAddr));
obs_frontend_pop_ui_translation();
Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title);
}
} }
void WSServer::onTextMessageReceived(QString message) { void WSServer::onMessage(connection_hdl hdl, server::message_ptr message)
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender()); {
if (pSocket) { auto opcode = message->get_opcode();
WSRequestHandler handler(pSocket); if (opcode != websocketpp::frame::opcode::text) {
handler.processIncomingMessage(message); return;
} }
QtConcurrent::run(&_threadPool, [=]() {
std::string payload = message->get_payload();
QMutexLocker locker(&_clMutex);
ConnectionProperties& connProperties = _connectionProperties[hdl];
locker.unlock();
WSRequestHandler handler(connProperties);
std::string response = handler.processIncomingMessage(payload);
_server.send(hdl, response, websocketpp::frame::opcode::text);
});
} }
void WSServer::onSocketDisconnected() { void WSServer::onClose(connection_hdl hdl)
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender()); {
if (pSocket) { QMutexLocker locker(&_clMutex);
pSocket->setProperty(PROP_AUTHENTICATED, false); _connections.erase(hdl);
_connectionProperties.erase(hdl);
locker.unlock();
QMutexLocker locker(&_clMutex); auto conn = _server.get_con_from_hdl(hdl);
_clients.removeAll(pSocket); auto localCloseCode = conn->get_local_close_code();
locker.unlock();
pSocket->deleteLater(); if (localCloseCode != websocketpp::close::status::going_away) {
QString clientIp = getRemoteEndpoint(hdl);
QHostAddress clientAddr = pSocket->peerAddress(); notifyDisconnection(clientIp);
QString clientIp = Utils::FormatIPAddress(clientAddr); blog(LOG_INFO, "client %s disconnected", clientIp.toUtf8().constData());
}
blog(LOG_INFO, "client %s:%d disconnected", }
clientIp.toUtf8().constData(), pSocket->peerPort());
QString WSServer::getRemoteEndpoint(connection_hdl hdl)
obs_frontend_push_ui_translation(obs_module_get_string); {
QString title = tr("OBSWebsocket.NotifyDisconnect.Title"); auto conn = _server.get_con_from_hdl(hdl);
QString msg = tr("OBSWebsocket.NotifyDisconnect.Message") return QString::fromStdString(conn->get_remote_endpoint());
.arg(Utils::FormatIPAddress(clientAddr)); }
obs_frontend_pop_ui_translation();
void WSServer::notifyConnection(QString clientIp)
Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title); {
} 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);
} }

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2017 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 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 it under the terms of the GNU General Public License as published by
@ -16,37 +16,57 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#ifndef WSSERVER_H #pragma once
#define WSSERVER_H
#include <QObject> #include <map>
#include <QList> #include <set>
#include <QMutex> #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 "WSRequestHandler.h"
QT_FORWARD_DECLARE_CLASS(QWebSocketServer) QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
QT_FORWARD_DECLARE_CLASS(QWebSocket) QT_FORWARD_DECLARE_CLASS(QWebSocket)
class WSServer : public QObject { using websocketpp::connection_hdl;
Q_OBJECT
public:
explicit WSServer(QObject* parent = Q_NULLPTR);
virtual ~WSServer();
void Start(quint16 port);
void Stop();
void broadcast(QString message);
static WSServer* Instance;
private slots: typedef websocketpp::server<websocketpp::config::asio> server;
void onNewConnection();
void onTextMessageReceived(QString message);
void onSocketDisconnected();
private: class WSServer : public QObject
QWebSocketServer* _wsServer; {
QList<QWebSocket*> _clients; Q_OBJECT
QMutex _clMutex;
public:
explicit WSServer();
virtual ~WSServer();
void start(quint16 port);
void stop();
void broadcast(std::string message);
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;
}; };
#endif // WSSERVER_H

View File

@ -22,84 +22,85 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../Config.h" #include "../Config.h"
#include "../WSServer.h" #include "../WSServer.h"
#include "settings-dialog.h" #include "settings-dialog.h"
#include "ui_settings-dialog.h"
#define CHANGE_ME "changeme" #define CHANGE_ME "changeme"
SettingsDialog::SettingsDialog(QWidget* parent) : SettingsDialog::SettingsDialog(QWidget* parent) :
QDialog(parent, Qt::Dialog), QDialog(parent, Qt::Dialog),
ui(new Ui::SettingsDialog) ui(new Ui::SettingsDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
connect(ui->authRequired, &QCheckBox::stateChanged, connect(ui->authRequired, &QCheckBox::stateChanged,
this, &SettingsDialog::AuthCheckboxChanged); this, &SettingsDialog::AuthCheckboxChanged);
connect(ui->buttonBox, &QDialogButtonBox::accepted, connect(ui->buttonBox, &QDialogButtonBox::accepted,
this, &SettingsDialog::FormAccepted); this, &SettingsDialog::FormAccepted);
AuthCheckboxChanged(); AuthCheckboxChanged();
} }
void SettingsDialog::showEvent(QShowEvent* event) { void SettingsDialog::showEvent(QShowEvent* event) {
Config* conf = Config::Current(); auto conf = GetConfig();
ui->serverEnabled->setChecked(conf->ServerEnabled); ui->serverEnabled->setChecked(conf->ServerEnabled);
ui->serverPort->setValue(conf->ServerPort); ui->serverPort->setValue(conf->ServerPort);
ui->debugEnabled->setChecked(conf->DebugEnabled); ui->debugEnabled->setChecked(conf->DebugEnabled);
ui->alertsEnabled->setChecked(conf->AlertsEnabled); ui->alertsEnabled->setChecked(conf->AlertsEnabled);
ui->authRequired->setChecked(conf->AuthRequired); ui->authRequired->setChecked(conf->AuthRequired);
ui->password->setText(CHANGE_ME); ui->password->setText(CHANGE_ME);
} }
void SettingsDialog::ToggleShowHide() { void SettingsDialog::ToggleShowHide() {
if (!isVisible()) if (!isVisible())
setVisible(true); setVisible(true);
else else
setVisible(false); setVisible(false);
} }
void SettingsDialog::AuthCheckboxChanged() { void SettingsDialog::AuthCheckboxChanged() {
if (ui->authRequired->isChecked()) if (ui->authRequired->isChecked())
ui->password->setEnabled(true); ui->password->setEnabled(true);
else else
ui->password->setEnabled(false); ui->password->setEnabled(false);
} }
void SettingsDialog::FormAccepted() { void SettingsDialog::FormAccepted() {
Config* conf = Config::Current(); auto conf = GetConfig();
conf->ServerEnabled = ui->serverEnabled->isChecked(); conf->ServerEnabled = ui->serverEnabled->isChecked();
conf->ServerPort = ui->serverPort->value(); conf->ServerPort = ui->serverPort->value();
conf->DebugEnabled = ui->debugEnabled->isChecked(); conf->DebugEnabled = ui->debugEnabled->isChecked();
conf->AlertsEnabled = ui->alertsEnabled->isChecked(); conf->AlertsEnabled = ui->alertsEnabled->isChecked();
if (ui->authRequired->isChecked()) { if (ui->authRequired->isChecked()) {
if (ui->password->text() != CHANGE_ME) { if (ui->password->text() != CHANGE_ME) {
conf->SetPassword(ui->password->text()); conf->SetPassword(ui->password->text());
} }
if (!Config::Current()->Secret.isEmpty()) if (!GetConfig()->Secret.isEmpty())
conf->AuthRequired = true; conf->AuthRequired = true;
else else
conf->AuthRequired = false; conf->AuthRequired = false;
} }
else else
{ {
conf->AuthRequired = false; conf->AuthRequired = false;
} }
conf->Save(); conf->Save();
if (conf->ServerEnabled) auto server = GetServer();
WSServer::Instance->Start(conf->ServerPort); if (conf->ServerEnabled) {
else server->start(conf->ServerPort);
WSServer::Instance->Stop(); } else {
server->stop();
}
} }
SettingsDialog::~SettingsDialog() { SettingsDialog::~SettingsDialog() {
delete ui; delete ui;
} }

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2017 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 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 it under the terms of the GNU General Public License as published by
@ -16,31 +16,26 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#ifndef SETTINGSDIALOG_H #pragma once
#define SETTINGSDIALOG_H
#include <QDialog> #include <QtWidgets/QDialog>
namespace Ui { #include "ui_settings-dialog.h"
class SettingsDialog;
}
class SettingsDialog : public QDialog class SettingsDialog : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit SettingsDialog(QWidget* parent = 0); explicit SettingsDialog(QWidget* parent = 0);
~SettingsDialog(); ~SettingsDialog();
void showEvent(QShowEvent* event); void showEvent(QShowEvent* event);
void ToggleShowHide(); void ToggleShowHide();
private Q_SLOTS: private Q_SLOTS:
void AuthCheckboxChanged(); void AuthCheckboxChanged();
void FormAccepted(); void FormAccepted();
private: private:
Ui::SettingsDialog* ui; Ui::SettingsDialog* ui;
}; };
#endif // SETTINGSDIALOG_H

View File

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

View File

@ -18,9 +18,10 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs-module.h> #include <obs-module.h>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <QAction>
#include <QMainWindow> #include <QtCore/QTimer>
#include <QTimer> #include <QtWidgets/QAction>
#include <QtWidgets/QMainWindow>
#include "obs-websocket.h" #include "obs-websocket.h"
#include "WSServer.h" #include "WSServer.h"
@ -37,44 +38,67 @@ void ___output_dummy_addref(obs_output_t*) {}
OBS_DECLARE_MODULE() OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US") OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
SettingsDialog* settings_dialog; ConfigPtr _config;
WSServerPtr _server;
WSEventsPtr _eventsSystem;
bool obs_module_load(void) { bool obs_module_load(void) {
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION); blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
blog(LOG_INFO, "qt version (compile-time): %s ; qt version (run-time): %s", blog(LOG_INFO, "qt version (compile-time): %s ; qt version (run-time): %s",
QT_VERSION_STR, qVersion()); QT_VERSION_STR, qVersion());
// Core setup // Core setup
Config* config = Config::Current(); _config = ConfigPtr(new Config());
config->Load(); _config->MigrateFromGlobalSettings(); // TODO remove this on the next minor jump
_config->Load();
WSServer::Instance = new WSServer(); _server = WSServerPtr(new WSServer());
WSEvents::Instance = new WSEvents(WSServer::Instance); _eventsSystem = WSEventsPtr(new WSEvents(_server));
if (config->ServerEnabled) if (_config->ServerEnabled) {
WSServer::Instance->Start(config->ServerPort); _server->start(_config->ServerPort);
}
// UI setup // UI setup
QAction* menu_action = (QAction*)obs_frontend_add_tools_menu_qaction( obs_frontend_push_ui_translation(obs_module_get_string);
obs_module_text("OBSWebsocket.Menu.SettingsItem")); QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window();
SettingsDialog* settingsDialog = new SettingsDialog(mainWindow);
obs_frontend_pop_ui_translation();
obs_frontend_push_ui_translation(obs_module_get_string); const char* menuActionText =
QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window(); obs_module_text("OBSWebsocket.Settings.DialogTitle");
settings_dialog = new SettingsDialog(main_window); QAction* menuAction =
obs_frontend_pop_ui_translation(); (QAction*)obs_frontend_add_tools_menu_qaction(menuActionText);
QObject::connect(menuAction, &QAction::triggered, [settingsDialog] {
// The settings dialog belongs to the main window. Should be ok
// to pass the pointer to this QAction belonging to the main window
settingsDialog->ToggleShowHide();
});
auto menu_cb = [] { // Loading finished
settings_dialog->ToggleShowHide(); blog(LOG_INFO, "module loaded!");
};
menu_action->connect(menu_action, &QAction::triggered, menu_cb);
// Loading finished return true;
blog(LOG_INFO, "module loaded!");
return true;
} }
void obs_module_unload() { void obs_module_unload() {
blog(LOG_INFO, "goodbye!"); _server->stop();
_eventsSystem.reset();
_server.reset();
_config.reset();
blog(LOG_INFO, "goodbye!");
} }
ConfigPtr GetConfig() {
return _config;
}
WSServerPtr GetServer() {
return _server;
}
WSEventsPtr GetEventsSystem() {
return _eventsSystem;
}

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2017 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 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 it under the terms of the GNU General Public License as published by
@ -16,10 +16,10 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#ifndef OBSWEBSOCKET_H #pragma once
#define OBSWEBSOCKET_H
#include <obs.hpp> #include <obs.hpp>
#include <memory>
void ___source_dummy_addref(obs_source_t*); void ___source_dummy_addref(obs_source_t*);
void ___sceneitem_dummy_addref(obs_sceneitem_t*); void ___sceneitem_dummy_addref(obs_sceneitem_t*);
@ -38,9 +38,19 @@ using OBSDataArrayAutoRelease =
using OBSOutputAutoRelease = using OBSOutputAutoRelease =
OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>; OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
#define PROP_AUTHENTICATED "wsclient_authenticated" class Config;
#define OBS_WEBSOCKET_VERSION "4.3.0" typedef std::shared_ptr<Config> ConfigPtr;
class WSServer;
typedef std::shared_ptr<WSServer> WSServerPtr;
class WSEvents;
typedef std::shared_ptr<WSEvents> WSEventsPtr;
ConfigPtr GetConfig();
WSServerPtr GetServer();
WSEventsPtr GetEventsSystem();
#define OBS_WEBSOCKET_VERSION "4.6.0"
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__) #define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
#endif // OBSWEBSOCKET_H