OBSBasic_SceneCollections.cpp 326 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923
  1. /******************************************************************************
  2. Copyright (C) 2023 by Lain Bailey <[email protected]>
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ******************************************************************************/
  14. #include <filesystem>
  15. #include <string>
  16. #include <obs.hpp>
  17. #include <util/util.hpp>
  18. #include <QMessageBox>
  19. #include <QVariant>
  20. #include <QFileDialog>
  21. #include <QStandardPaths>
  22. #include <qt-wrappers.hpp>
  23. #include "item-widget-helpers.hpp"
  24. #include "window-basic-main.hpp"
  25. #include "window-importer.hpp"
  26. #include "window-namedialog.hpp"
  27. // MARK: Constant Expressions
  28. constexpr std::string_view OBSSceneCollectionPath = "/obs-studio/basic/scenes/";
  29. // MARK: - Anonymous Namespace
  30. namespace {
  31. QList<QString> sortedSceneCollections{};
  32. void updateSortedSceneCollections(const OBSSceneCollectionCache &collections)
  33. {
  34. const QLocale locale = QLocale::system();
  35. QList<QString> newList{};
  36. for (auto [collectionName, _] : collections) {
  37. QString entry = QString::fromStdString(collectionName);
  38. newList.append(entry);
  39. }
  40. std::sort(newList.begin(), newList.end(), [&locale](const QString &lhs, const QString &rhs) -> bool {
  41. int result = QString::localeAwareCompare(locale.toLower(lhs), locale.toLower(rhs));
  42. return (result < 0);
  43. });
  44. sortedSceneCollections.swap(newList);
  45. }
  46. void cleanBackupCollision(const OBSSceneCollection &collection)
  47. {
  48. std::filesystem::path backupFilePath = collection.collectionFile;
  49. backupFilePath.replace_extension(".json.bak");
  50. if (std::filesystem::exists(backupFilePath)) {
  51. try {
  52. std::filesystem::remove(backupFilePath);
  53. } catch (std::filesystem::filesystem_error &) {
  54. throw std::logic_error("Failed to remove pre-existing scene collection backup file: " +
  55. backupFilePath.u8string());
  56. }
  57. }
  58. }
  59. } // namespace
  60. // MARK: - Main Scene Collection Management Functions
  61. void OBSBasic::SetupNewSceneCollection(const std::string &collectionName)
  62. {
  63. const OBSSceneCollection &newCollection = CreateSceneCollection(collectionName);
  64. OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
  65. cleanBackupCollision(newCollection);
  66. ActivateSceneCollection(newCollection);
  67. blog(LOG_INFO, "Created scene collection '%s' (clean, %s)", newCollection.name.c_str(),
  68. newCollection.fileName.c_str());
  69. blog(LOG_INFO, "------------------------------------------------");
  70. }
  71. void OBSBasic::SetupDuplicateSceneCollection(const std::string &collectionName)
  72. {
  73. const OBSSceneCollection &newCollection = CreateSceneCollection(collectionName);
  74. const OBSSceneCollection &currentCollection = GetCurrentSceneCollection();
  75. SaveProjectNow();
  76. const auto copyOptions = std::filesystem::copy_options::overwrite_existing;
  77. try {
  78. std::filesystem::copy(currentCollection.collectionFile, newCollection.collectionFile, copyOptions);
  79. } catch (const std::filesystem::filesystem_error &error) {
  80. blog(LOG_DEBUG, "%s", error.what());
  81. throw std::logic_error("Failed to copy file for cloned scene collection: " + newCollection.name);
  82. }
  83. OBSDataAutoRelease collection = obs_data_create_from_json_file(newCollection.collectionFile.u8string().c_str());
  84. obs_data_set_string(collection, "name", newCollection.name.c_str());
  85. OBSDataArrayAutoRelease sources = obs_data_get_array(collection, "sources");
  86. if (sources) {
  87. obs_data_erase(collection, "sources");
  88. obs_data_array_enum(
  89. sources,
  90. [](obs_data_t *data, void *) -> void {
  91. const char *uuid = os_generate_uuid();
  92. obs_data_set_string(data, "uuid", uuid);
  93. bfree((void *)uuid);
  94. },
  95. nullptr);
  96. obs_data_set_array(collection, "sources", sources);
  97. }
  98. obs_data_save_json_safe(collection, newCollection.collectionFile.u8string().c_str(), "tmp", nullptr);
  99. cleanBackupCollision(newCollection);
  100. ActivateSceneCollection(newCollection);
  101. blog(LOG_INFO, "Created scene collection '%s' (duplicate, %s)", newCollection.name.c_str(),
  102. newCollection.fileName.c_str());
  103. blog(LOG_INFO, "------------------------------------------------");
  104. }
  105. void OBSBasic::SetupRenameSceneCollection(const std::string &collectionName)
  106. {
  107. const OBSSceneCollection &newCollection = CreateSceneCollection(collectionName);
  108. const OBSSceneCollection currentCollection = GetCurrentSceneCollection();
  109. SaveProjectNow();
  110. const auto copyOptions = std::filesystem::copy_options::overwrite_existing;
  111. try {
  112. std::filesystem::copy(currentCollection.collectionFile, newCollection.collectionFile, copyOptions);
  113. } catch (const std::filesystem::filesystem_error &error) {
  114. blog(LOG_DEBUG, "%s", error.what());
  115. throw std::logic_error("Failed to copy file for scene collection: " + currentCollection.name);
  116. }
  117. collections.erase(currentCollection.name);
  118. OBSDataAutoRelease collection = obs_data_create_from_json_file(newCollection.collectionFile.u8string().c_str());
  119. obs_data_set_string(collection, "name", newCollection.name.c_str());
  120. obs_data_save_json_safe(collection, newCollection.collectionFile.u8string().c_str(), "tmp", nullptr);
  121. cleanBackupCollision(newCollection);
  122. ActivateSceneCollection(newCollection);
  123. RemoveSceneCollection(currentCollection);
  124. blog(LOG_INFO, "Renamed scene collection '%s' to '%s' (%s)", currentCollection.name.c_str(),
  125. newCollection.name.c_str(), newCollection.fileName.c_str());
  126. blog(LOG_INFO, "------------------------------------------------");
  127. OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_RENAMED);
  128. }
  129. // MARK: - Scene Collection File Management Functions
  130. const OBSSceneCollection &OBSBasic::CreateSceneCollection(const std::string &collectionName)
  131. {
  132. if (const auto &foundCollection = GetSceneCollectionByName(collectionName)) {
  133. throw std::invalid_argument("Scene collection already exists: " + collectionName);
  134. }
  135. std::string fileName;
  136. if (!GetFileSafeName(collectionName.c_str(), fileName)) {
  137. throw std::invalid_argument("Failed to create safe directory for new scene collection: " +
  138. collectionName);
  139. }
  140. std::string collectionFile;
  141. collectionFile.reserve(App()->userScenesLocation.u8string().size() + OBSSceneCollectionPath.size() +
  142. fileName.size());
  143. collectionFile.append(App()->userScenesLocation.u8string()).append(OBSSceneCollectionPath).append(fileName);
  144. if (!GetClosestUnusedFileName(collectionFile, "json")) {
  145. throw std::invalid_argument("Failed to get closest file name for new scene collection: " + fileName);
  146. }
  147. const std::filesystem::path collectionFilePath = std::filesystem::u8path(collectionFile);
  148. auto [iterator, success] = collections.try_emplace(
  149. collectionName,
  150. OBSSceneCollection{collectionName, collectionFilePath.filename().u8string(), collectionFilePath});
  151. return iterator->second;
  152. }
  153. void OBSBasic::RemoveSceneCollection(OBSSceneCollection collection)
  154. {
  155. try {
  156. std::filesystem::remove(collection.collectionFile);
  157. } catch (const std::filesystem::filesystem_error &error) {
  158. blog(LOG_DEBUG, "%s", error.what());
  159. throw std::logic_error("Failed to remove scene collection file: " + collection.fileName);
  160. }
  161. blog(LOG_INFO, "Removed scene collection '%s' (%s)", collection.name.c_str(), collection.fileName.c_str());
  162. blog(LOG_INFO, "------------------------------------------------");
  163. }
  164. // MARK: - Scene Collection UI Handling Functions
  165. bool OBSBasic::CreateNewSceneCollection(const QString &name)
  166. {
  167. try {
  168. SetupNewSceneCollection(name.toStdString());
  169. return true;
  170. } catch (const std::invalid_argument &error) {
  171. blog(LOG_ERROR, "%s", error.what());
  172. return false;
  173. } catch (const std::logic_error &error) {
  174. blog(LOG_ERROR, "%s", error.what());
  175. return false;
  176. }
  177. }
  178. bool OBSBasic::CreateDuplicateSceneCollection(const QString &name)
  179. {
  180. try {
  181. SetupDuplicateSceneCollection(name.toStdString());
  182. return true;
  183. } catch (const std::invalid_argument &error) {
  184. blog(LOG_ERROR, "%s", error.what());
  185. return false;
  186. } catch (const std::logic_error &error) {
  187. blog(LOG_ERROR, "%s", error.what());
  188. return false;
  189. }
  190. }
  191. void OBSBasic::DeleteSceneCollection(const QString &name)
  192. {
  193. const std::string_view currentCollectionName{
  194. config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
  195. if (currentCollectionName == name.toStdString()) {
  196. on_actionRemoveSceneCollection_triggered();
  197. return;
  198. }
  199. OBSSceneCollection currentCollection = GetCurrentSceneCollection();
  200. RemoveSceneCollection(currentCollection);
  201. collections.erase(name.toStdString());
  202. RefreshSceneCollections();
  203. OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED);
  204. }
  205. void OBSBasic::ChangeSceneCollection()
  206. {
  207. QAction *action = reinterpret_cast<QAction *>(sender());
  208. if (!action) {
  209. return;
  210. }
  211. const std::string_view currentCollectionName{
  212. config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
  213. const QVariant qCollectionName = action->property("collection_name");
  214. const std::string selectedCollectionName{qCollectionName.toString().toStdString()};
  215. if (currentCollectionName == selectedCollectionName) {
  216. action->setChecked(true);
  217. return;
  218. }
  219. const std::optional<OBSSceneCollection> foundCollection = GetSceneCollectionByName(selectedCollectionName);
  220. if (!foundCollection) {
  221. const std::string errorMessage{"Selected scene collection not found: "};
  222. throw std::invalid_argument(errorMessage + currentCollectionName.data());
  223. }
  224. const OBSSceneCollection &selectedCollection = foundCollection.value();
  225. OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
  226. ActivateSceneCollection(selectedCollection);
  227. blog(LOG_INFO, "Switched to scene collection '%s' (%s)", selectedCollection.name.c_str(),
  228. selectedCollection.fileName.c_str());
  229. blog(LOG_INFO, "------------------------------------------------");
  230. }
  231. void OBSBasic::RefreshSceneCollections(bool refreshCache)
  232. {
  233. std::string_view currentCollectionName{config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
  234. QList<QAction *> menuActions = ui->sceneCollectionMenu->actions();
  235. for (auto &action : menuActions) {
  236. QVariant variant = action->property("file_name");
  237. if (variant.typeName() != nullptr) {
  238. delete action;
  239. }
  240. }
  241. if (refreshCache) {
  242. RefreshSceneCollectionCache();
  243. }
  244. updateSortedSceneCollections(collections);
  245. size_t numAddedCollections = 0;
  246. for (auto &name : sortedSceneCollections) {
  247. const std::string collectionName = name.toStdString();
  248. try {
  249. const OBSSceneCollection &collection = collections.at(collectionName);
  250. const QString qCollectionName = QString().fromStdString(collectionName);
  251. QAction *action = new QAction(qCollectionName, this);
  252. action->setProperty("collection_name", qCollectionName);
  253. action->setProperty("file_name", QString().fromStdString(collection.fileName));
  254. connect(action, &QAction::triggered, this, &OBSBasic::ChangeSceneCollection);
  255. action->setCheckable(true);
  256. action->setChecked(collectionName == currentCollectionName);
  257. ui->sceneCollectionMenu->addAction(action);
  258. numAddedCollections += 1;
  259. } catch (const std::out_of_range &error) {
  260. blog(LOG_ERROR, "No scene collection with name %s found in scene collection cache.\n%s",
  261. collectionName.c_str(), error.what());
  262. }
  263. }
  264. ui->actionRemoveSceneCollection->setEnabled(numAddedCollections > 1);
  265. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  266. main->ui->actionPasteFilters->setEnabled(false);
  267. main->ui->actionPasteRef->setEnabled(false);
  268. main->ui->actionPasteDup->setEnabled(false);
  269. }
  270. // MARK: - Scene Collection Cache Functions
  271. void OBSBasic::RefreshSceneCollectionCache()
  272. {
  273. OBSSceneCollectionCache foundCollections{};
  274. const std::filesystem::path collectionsPath =
  275. App()->userScenesLocation / std::filesystem::u8path(OBSSceneCollectionPath.substr(1));
  276. if (!std::filesystem::exists(collectionsPath)) {
  277. blog(LOG_WARNING, "Failed to get scene collections config path");
  278. return;
  279. }
  280. for (const auto &entry : std::filesystem::directory_iterator(collectionsPath)) {
  281. if (entry.is_directory()) {
  282. continue;
  283. }
  284. if (entry.path().extension().u8string() != ".json") {
  285. continue;
  286. }
  287. OBSDataAutoRelease collectionData =
  288. obs_data_create_from_json_file_safe(entry.path().u8string().c_str(), "bak");
  289. std::string candidateName;
  290. const char *collectionName = obs_data_get_string(collectionData, "name");
  291. if (!collectionName) {
  292. candidateName = entry.path().filename().u8string();
  293. } else {
  294. candidateName = collectionName;
  295. }
  296. foundCollections.try_emplace(candidateName,
  297. OBSSceneCollection{candidateName, entry.path().filename().u8string(),
  298. entry.path()});
  299. }
  300. collections.swap(foundCollections);
  301. }
  302. const OBSSceneCollection &OBSBasic::GetCurrentSceneCollection() const
  303. {
  304. std::string currentCollectionName{config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
  305. if (currentCollectionName.empty()) {
  306. throw std::invalid_argument("No valid scene collection name in configuration Basic->SceneCollection");
  307. }
  308. const auto &foundCollection = collections.find(currentCollectionName);
  309. if (foundCollection != collections.end()) {
  310. return foundCollection->second;
  311. } else {
  312. throw std::invalid_argument("Scene collection not found in collection list: " + currentCollectionName);
  313. }
  314. }
  315. std::optional<OBSSceneCollection> OBSBasic::GetSceneCollectionByName(const std::string &collectionName) const
  316. {
  317. auto foundCollection = collections.find(collectionName);
  318. if (foundCollection == collections.end()) {
  319. return {};
  320. } else {
  321. return foundCollection->second;
  322. }
  323. }
  324. std::optional<OBSSceneCollection> OBSBasic::GetSceneCollectionByFileName(const std::string &fileName) const
  325. {
  326. for (auto &[iterator, collection] : collections) {
  327. if (collection.fileName == fileName) {
  328. return collection;
  329. }
  330. }
  331. return {};
  332. }
  333. // MARK: - Qt Slot Functions
  334. void OBSBasic::on_actionNewSceneCollection_triggered()
  335. {
  336. const OBSPromptCallback sceneCollectionCallback = [this](const OBSPromptResult &result) {
  337. if (GetSceneCollectionByName(result.promptValue)) {
  338. return false;
  339. }
  340. return true;
  341. };
  342. const OBSPromptRequest request{Str("Basic.Main.AddSceneCollection.Title"),
  343. Str("Basic.Main.AddSceneCollection.Text")};
  344. OBSPromptResult result = PromptForName(request, sceneCollectionCallback);
  345. if (!result.success) {
  346. return;
  347. }
  348. try {
  349. SetupNewSceneCollection(result.promptValue);
  350. } catch (const std::invalid_argument &error) {
  351. blog(LOG_ERROR, "%s", error.what());
  352. } catch (const std::logic_error &error) {
  353. blog(LOG_ERROR, "%s", error.what());
  354. }
  355. }
  356. void OBSBasic::on_actionDupSceneCollection_triggered()
  357. {
  358. const OBSPromptCallback sceneCollectionCallback = [this](const OBSPromptResult &result) {
  359. if (GetSceneCollectionByName(result.promptValue)) {
  360. return false;
  361. }
  362. return true;
  363. };
  364. const OBSPromptRequest request{Str("Basic.Main.AddSceneCollection.Title"),
  365. Str("Basic.Main.AddSceneCollection.Text")};
  366. OBSPromptResult result = PromptForName(request, sceneCollectionCallback);
  367. if (!result.success) {
  368. return;
  369. }
  370. try {
  371. SetupDuplicateSceneCollection(result.promptValue);
  372. } catch (const std::invalid_argument &error) {
  373. blog(LOG_ERROR, "%s", error.what());
  374. } catch (const std::logic_error &error) {
  375. blog(LOG_ERROR, "%s", error.what());
  376. }
  377. }
  378. void OBSBasic::on_actionRenameSceneCollection_triggered()
  379. {
  380. const OBSSceneCollection &currentCollection = GetCurrentSceneCollection();
  381. const OBSPromptCallback sceneCollectionCallback = [this](const OBSPromptResult &result) {
  382. if (GetSceneCollectionByName(result.promptValue)) {
  383. return false;
  384. }
  385. return true;
  386. };
  387. const OBSPromptRequest request{Str("Basic.Main.RenameSceneCollection.Title"),
  388. Str("Basic.Main.AddSceneCollection.Text"), currentCollection.name};
  389. OBSPromptResult result = PromptForName(request, sceneCollectionCallback);
  390. if (!result.success) {
  391. return;
  392. }
  393. try {
  394. SetupRenameSceneCollection(result.promptValue);
  395. } catch (const std::invalid_argument &error) {
  396. blog(LOG_ERROR, "%s", error.what());
  397. } catch (const std::logic_error &error) {
  398. blog(LOG_ERROR, "%s", error.what());
  399. }
  400. }
  401. void OBSBasic::on_actionRemoveSceneCollection_triggered(bool skipConfirmation)
  402. {
  403. if (collections.size() < 2) {
  404. return;
  405. }
  406. OBSSceneCollection currentCollection;
  407. try {
  408. currentCollection = GetCurrentSceneCollection();
  409. if (!skipConfirmation) {
  410. const QString confirmationText =
  411. QTStr("ConfirmRemove.Text").arg(QString::fromStdString(currentCollection.name));
  412. const QMessageBox::StandardButton button =
  413. OBSMessageBox::question(this, QTStr("ConfirmRemove.Title"), confirmationText);
  414. if (button == QMessageBox::No) {
  415. return;
  416. }
  417. }
  418. OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
  419. collections.erase(currentCollection.name);
  420. } catch (const std::invalid_argument &error) {
  421. blog(LOG_ERROR, "%s", error.what());
  422. } catch (const std::logic_error &error) {
  423. blog(LOG_ERROR, "%s", error.what());
  424. }
  425. const OBSSceneCollection &newCollection = collections.begin()->second;
  426. ActivateSceneCollection(newCollection);
  427. RemoveSceneCollection(currentCollection);
  428. blog(LOG_INFO, "Switched to scene collection '%s' (%s)", newCollection.name.c_str(),
  429. newCollection.fileName.c_str());
  430. blog(LOG_INFO, "------------------------------------------------");
  431. }
  432. void OBSBasic::on_actionImportSceneCollection_triggered()
  433. {
  434. OBSImporter imp(this);
  435. imp.exec();
  436. RefreshSceneCollections(true);
  437. }
  438. void OBSBasic::on_actionExportSceneCollection_triggered()
  439. {
  440. SaveProjectNow();
  441. const OBSSceneCollection &currentCollection = GetCurrentSceneCollection();
  442. const QString home = QDir::homePath();
  443. const QString destinationFileName = SaveFile(this, QTStr("Basic.MainMenu.SceneCollection.Export"),
  444. home + "/" + currentCollection.fileName.c_str(),
  445. "JSON Files (*.json)");
  446. if (!destinationFileName.isEmpty() && !destinationFileName.isNull()) {
  447. const std::filesystem::path sourceFile = currentCollection.collectionFile;
  448. const std::filesystem::path destinationFile =
  449. std::filesystem::u8path(destinationFileName.toStdString());
  450. OBSDataAutoRelease collection = obs_data_create_from_json_file(sourceFile.u8string().c_str());
  451. OBSDataArrayAutoRelease sources = obs_data_get_array(collection, "sources");
  452. if (!sources) {
  453. blog(LOG_WARNING, "No sources in exported scene collection");
  454. return;
  455. }
  456. obs_data_erase(collection, "sources");
  457. using OBSDataVector = std::vector<OBSData>;
  458. OBSDataVector sourceItems;
  459. obs_data_array_enum(
  460. sources,
  461. [](obs_data_t *data, void *vector) -> void {
  462. OBSDataVector &sourceItems{*static_cast<OBSDataVector *>(vector)};
  463. sourceItems.push_back(data);
  464. },
  465. &sourceItems);
  466. std::sort(sourceItems.begin(), sourceItems.end(), [](const OBSData &a, const OBSData &b) {
  467. return astrcmpi(obs_data_get_string(a, "name"), obs_data_get_string(b, "name")) < 0;
  468. });
  469. OBSDataArrayAutoRelease newSources = obs_data_array_create();
  470. for (auto &item : sourceItems) {
  471. obs_data_array_push_back(newSources, item);
  472. }
  473. obs_data_set_array(collection, "sources", newSources);
  474. obs_data_save_json_pretty_safe(collection, destinationFile.u8string().c_str(), "tmp", "bak");
  475. }
  476. }
  477. void OBSBasic::on_actionRemigrateSceneCollection_triggered()
  478. {
  479. if (Active()) {
  480. OBSMessageBox::warning(this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
  481. QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.Active"));
  482. return;
  483. }
  484. OBSDataAutoRelease priv = obs_get_private_data();
  485. if (!usingAbsoluteCoordinates && !migrationBaseResolution) {
  486. OBSMessageBox::warning(
  487. this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
  488. QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.UnknownBaseResolution"));
  489. return;
  490. }
  491. obs_video_info ovi;
  492. obs_get_video_info(&ovi);
  493. if (!usingAbsoluteCoordinates && migrationBaseResolution->first == ovi.base_width &&
  494. migrationBaseResolution->second == ovi.base_height) {
  495. OBSMessageBox::warning(
  496. this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
  497. QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.BaseResolutionMatches"));
  498. return;
  499. }
  500. const OBSSceneCollection &currentCollection = GetCurrentSceneCollection();
  501. QString name = QString::fromStdString(currentCollection.name);
  502. QString message =
  503. QTStr("Basic.Main.RemigrateSceneCollection.Text").arg(name).arg(ovi.base_width).arg(ovi.base_height);
  504. auto answer = OBSMessageBox::question(this, QTStr("Basic.Main.RemigrateSceneCollection.Title"), message);
  505. if (answer == QMessageBox::No)
  506. return;
  507. lastOutputResolution = {ovi.base_width, ovi.base_height};
  508. if (!usingAbsoluteCoordinates) {
  509. /* Temporarily change resolution to migration resolution */
  510. ovi.base_width = migrationBaseResolution->first;
  511. ovi.base_height = migrationBaseResolution->second;
  512. if (obs_reset_video(&ovi) != OBS_VIDEO_SUCCESS) {
  513. OBSMessageBox::critical(
  514. this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
  515. QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.FailedVideoReset"));
  516. return;
  517. }
  518. }
  519. OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
  520. /* Save and immediately reload to (re-)run migrations. */
  521. SaveProjectNow();
  522. /* Reset video if we potentially changed to a temporary resolution */
  523. if (!usingAbsoluteCoordinates) {
  524. ResetVideo();
  525. }
  526. ActivateSceneCollection(currentCollection);
  527. }
  528. // MARK: - Scene Collection Management Helper Functions
  529. void OBSBasic::ActivateSceneCollection(const OBSSceneCollection &collection)
  530. {
  531. const std::string currentCollectionName{config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
  532. if (auto foundCollection = GetSceneCollectionByName(currentCollectionName)) {
  533. if (collection.name != foundCollection.value().name) {
  534. SaveProjectNow();
  535. }
  536. }
  537. config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", collection.name.c_str());
  538. config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", collection.fileName.c_str());
  539. Load(collection.collectionFile.u8string().c_str());
  540. RefreshSceneCollections();
  541. UpdateTitleBar();
  542. OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED);
  543. OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED);
  544. }
  545. /******************************************************************************
  546. Copyright (C) 2023 by Lain Bailey <[email protected]>
  547. Zachary Lund <[email protected]>
  548. Philippe Groarke <[email protected]>
  549. This program is free software: you can redistribute it and/or modify
  550. it under the terms of the GNU General Public License as published by
  551. the Free Software Foundation, either version 2 of the License, or
  552. (at your option) any later version.
  553. This program is distributed in the hope that it will be useful,
  554. but WITHOUT ANY WARRANTY; without even the implied warranty of
  555. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  556. GNU General Public License for more details.
  557. You should have received a copy of the GNU General Public License
  558. along with this program. If not, see <http://www.gnu.org/licenses/>.
  559. ******************************************************************************/
  560. #include "ui-config.h"
  561. #include <cstddef>
  562. #include <ctime>
  563. #include <functional>
  564. #include <unordered_set>
  565. #include <obs-data.h>
  566. #include <obs.h>
  567. #include <obs.hpp>
  568. #include <QGuiApplication>
  569. #include <QMessageBox>
  570. #include <QShowEvent>
  571. #include <QDesktopServices>
  572. #include <QFileDialog>
  573. #include <QScreen>
  574. #include <QColorDialog>
  575. #include <QSizePolicy>
  576. #include <QScrollBar>
  577. #include <QTextStream>
  578. #include <QActionGroup>
  579. #include <qt-wrappers.hpp>
  580. #include <util/dstr.h>
  581. #include <util/util.hpp>
  582. #include <util/platform.h>
  583. #include <util/profiler.hpp>
  584. #include <util/dstr.hpp>
  585. #include "obs-app.hpp"
  586. #include "platform.hpp"
  587. #include "visibility-item-widget.hpp"
  588. #include "item-widget-helpers.hpp"
  589. #include "basic-controls.hpp"
  590. #include "window-basic-settings.hpp"
  591. #include "window-namedialog.hpp"
  592. #include "window-basic-auto-config.hpp"
  593. #include "window-basic-source-select.hpp"
  594. #include "window-basic-main.hpp"
  595. #include "window-basic-stats.hpp"
  596. #include "window-basic-main-outputs.hpp"
  597. #include "window-basic-vcam-config.hpp"
  598. #include "window-log-reply.hpp"
  599. #ifdef __APPLE__
  600. #include "window-permissions.hpp"
  601. #endif
  602. #include "window-projector.hpp"
  603. #include "window-remux.hpp"
  604. #ifdef YOUTUBE_ENABLED
  605. #include "auth-youtube.hpp"
  606. #include "window-youtube-actions.hpp"
  607. #include "youtube-api-wrappers.hpp"
  608. #endif
  609. #include "window-whats-new.hpp"
  610. #include "context-bar-controls.hpp"
  611. #include "obs-proxy-style.hpp"
  612. #include "display-helpers.hpp"
  613. #include "volume-control.hpp"
  614. #include "remote-text.hpp"
  615. #include "ui-validation.hpp"
  616. #include "media-controls.hpp"
  617. #include "undo-stack-obs.hpp"
  618. #include <fstream>
  619. #include <sstream>
  620. #ifdef _WIN32
  621. #include "update/win-update.hpp"
  622. #include "update/shared-update.hpp"
  623. #include "windows.h"
  624. #endif
  625. #ifdef WHATSNEW_ENABLED
  626. #include "update/models/whatsnew.hpp"
  627. #endif
  628. #if !defined(_WIN32) && defined(WHATSNEW_ENABLED)
  629. #include "update/shared-update.hpp"
  630. #endif
  631. #ifdef ENABLE_SPARKLE_UPDATER
  632. #include "update/mac-update.hpp"
  633. #endif
  634. #include "ui_OBSBasic.h"
  635. #include "ui_ColorSelect.h"
  636. #include <QWindow>
  637. #ifdef ENABLE_WAYLAND
  638. #include <obs-nix-platform.h>
  639. #endif
  640. using namespace std;
  641. #ifdef BROWSER_AVAILABLE
  642. #include <browser-panel.hpp>
  643. #endif
  644. #include "ui-config.h"
  645. struct QCef;
  646. struct QCefCookieManager;
  647. QCef *cef = nullptr;
  648. QCefCookieManager *panel_cookies = nullptr;
  649. bool cef_js_avail = false;
  650. extern std::string opt_starting_profile;
  651. extern std::string opt_starting_collection;
  652. void DestroyPanelCookieManager();
  653. namespace {
  654. QPointer<OBSWhatsNew> obsWhatsNew;
  655. template<typename OBSRef> struct SignalContainer {
  656. OBSRef ref;
  657. vector<shared_ptr<OBSSignal>> handlers;
  658. };
  659. } // namespace
  660. extern volatile long insideEventLoop;
  661. Q_DECLARE_METATYPE(OBSScene);
  662. Q_DECLARE_METATYPE(OBSSceneItem);
  663. Q_DECLARE_METATYPE(OBSSource);
  664. Q_DECLARE_METATYPE(obs_order_movement);
  665. Q_DECLARE_METATYPE(SignalContainer<OBSScene>);
  666. QDataStream &operator<<(QDataStream &out, const SignalContainer<OBSScene> &v)
  667. {
  668. out << v.ref;
  669. return out;
  670. }
  671. QDataStream &operator>>(QDataStream &in, SignalContainer<OBSScene> &v)
  672. {
  673. in >> v.ref;
  674. return in;
  675. }
  676. template<typename T> static T GetOBSRef(QListWidgetItem *item)
  677. {
  678. return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
  679. }
  680. template<typename T> static void SetOBSRef(QListWidgetItem *item, T &&val)
  681. {
  682. item->setData(static_cast<int>(QtDataRole::OBSRef), QVariant::fromValue(val));
  683. }
  684. static void AddExtraModulePaths()
  685. {
  686. string plugins_path, plugins_data_path;
  687. char *s;
  688. s = getenv("OBS_PLUGINS_PATH");
  689. if (s)
  690. plugins_path = s;
  691. s = getenv("OBS_PLUGINS_DATA_PATH");
  692. if (s)
  693. plugins_data_path = s;
  694. if (!plugins_path.empty() && !plugins_data_path.empty()) {
  695. #if defined(__APPLE__)
  696. plugins_path += "/%module%.plugin/Contents/MacOS";
  697. plugins_data_path += "/%module%.plugin/Contents/Resources";
  698. obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str());
  699. #else
  700. string data_path_with_module_suffix;
  701. data_path_with_module_suffix += plugins_data_path;
  702. data_path_with_module_suffix += "/%module%";
  703. obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str());
  704. #endif
  705. }
  706. if (portable_mode)
  707. return;
  708. char base_module_dir[512];
  709. #if defined(_WIN32)
  710. int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%");
  711. #elif defined(__APPLE__)
  712. int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin");
  713. #else
  714. int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%");
  715. #endif
  716. if (ret <= 0)
  717. return;
  718. string path = base_module_dir;
  719. #if defined(__APPLE__)
  720. /* User Application Support Search Path */
  721. obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str());
  722. #ifndef __aarch64__
  723. /* Legacy System Library Search Path */
  724. char system_legacy_module_dir[PATH_MAX];
  725. GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%");
  726. std::string path_system_legacy = system_legacy_module_dir;
  727. obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str());
  728. /* Legacy User Application Support Search Path */
  729. char user_legacy_module_dir[PATH_MAX];
  730. GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%");
  731. std::string path_user_legacy = user_legacy_module_dir;
  732. obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str());
  733. #endif
  734. #else
  735. #if ARCH_BITS == 64
  736. obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str());
  737. #else
  738. obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str());
  739. #endif
  740. #endif
  741. }
  742. /* First-party modules considered to be potentially unsafe to load in Safe Mode
  743. * due to them allowing external code (e.g. scripts) to modify OBS's state. */
  744. static const unordered_set<string> unsafe_modules = {
  745. "frontend-tools", // Scripting
  746. "obs-websocket", // Allows outside modifications
  747. };
  748. static void SetSafeModuleNames()
  749. {
  750. #ifndef SAFE_MODULES
  751. return;
  752. #else
  753. string module;
  754. stringstream modules(SAFE_MODULES);
  755. while (getline(modules, module, '|')) {
  756. /* When only disallowing third-party plugins, still add
  757. * "unsafe" bundled modules to the safe list. */
  758. if (disable_3p_plugins || !unsafe_modules.count(module))
  759. obs_add_safe_module(module.c_str());
  760. }
  761. #endif
  762. }
  763. extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main);
  764. void assignDockToggle(QDockWidget *dock, QAction *action)
  765. {
  766. auto handleWindowToggle = [action](bool vis) {
  767. action->blockSignals(true);
  768. action->setChecked(vis);
  769. action->blockSignals(false);
  770. };
  771. auto handleMenuToggle = [dock](bool check) {
  772. dock->blockSignals(true);
  773. dock->setVisible(check);
  774. dock->blockSignals(false);
  775. };
  776. dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle);
  777. dock->connect(action, &QAction::toggled, handleMenuToggle);
  778. }
  779. void setupDockAction(QDockWidget *dock)
  780. {
  781. QAction *action = dock->toggleViewAction();
  782. auto neverDisable = [action]() {
  783. QSignalBlocker block(action);
  784. action->setEnabled(true);
  785. };
  786. auto newToggleView = [dock](bool check) {
  787. QSignalBlocker block(dock);
  788. dock->setVisible(check);
  789. };
  790. // Replace the slot connected by default
  791. QObject::disconnect(action, &QAction::triggered, nullptr, 0);
  792. dock->connect(action, &QAction::triggered, newToggleView);
  793. // Make the action unable to be disabled
  794. action->connect(action, &QAction::enabledChanged, neverDisable);
  795. }
  796. extern void RegisterTwitchAuth();
  797. extern void RegisterRestreamAuth();
  798. #ifdef YOUTUBE_ENABLED
  799. extern void RegisterYoutubeAuth();
  800. #endif
  801. OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic)
  802. {
  803. setAttribute(Qt::WA_NativeWindow);
  804. #ifdef TWITCH_ENABLED
  805. RegisterTwitchAuth();
  806. #endif
  807. #ifdef RESTREAM_ENABLED
  808. RegisterRestreamAuth();
  809. #endif
  810. #ifdef YOUTUBE_ENABLED
  811. RegisterYoutubeAuth();
  812. #endif
  813. setAcceptDrops(true);
  814. setContextMenuPolicy(Qt::CustomContextMenu);
  815. QEvent::registerEventType(QEvent::User + QEvent::Close);
  816. api = InitializeAPIInterface(this);
  817. ui->setupUi(this);
  818. ui->previewDisabledWidget->setVisible(false);
  819. /* Set up streaming connections */
  820. connect(
  821. this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; },
  822. Qt::DirectConnection);
  823. connect(
  824. this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; },
  825. Qt::DirectConnection);
  826. connect(
  827. this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; },
  828. Qt::DirectConnection);
  829. /* Set up recording connections */
  830. connect(
  831. this, &OBSBasic::RecordingStarted, this,
  832. [this]() {
  833. this->recordingStarted = true;
  834. this->recordingPaused = false;
  835. },
  836. Qt::DirectConnection);
  837. connect(
  838. this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; },
  839. Qt::DirectConnection);
  840. connect(
  841. this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; },
  842. Qt::DirectConnection);
  843. connect(
  844. this, &OBSBasic::RecordingStopped, this,
  845. [this]() {
  846. this->recordingStarted = false;
  847. this->recordingPaused = false;
  848. },
  849. Qt::DirectConnection);
  850. /* Add controls dock */
  851. OBSBasicControls *controls = new OBSBasicControls(this);
  852. controlsDock = new OBSDock(this);
  853. controlsDock->setObjectName(QString::fromUtf8("controlsDock"));
  854. controlsDock->setWindowTitle(QTStr("Basic.Main.Controls"));
  855. /* Parenting is done there so controls will be deleted alongside controlsDock */
  856. controlsDock->setWidget(controls);
  857. addDockWidget(Qt::BottomDockWidgetArea, controlsDock);
  858. connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered);
  859. connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming);
  860. connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming);
  861. connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming);
  862. connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked);
  863. connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered);
  864. connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled);
  865. connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered);
  866. connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave);
  867. connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered);
  868. connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig);
  869. connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode);
  870. connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered);
  871. connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close);
  872. startingDockLayout = saveState();
  873. statsDock = new OBSDock();
  874. statsDock->setObjectName(QStringLiteral("statsDock"));
  875. statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable |
  876. QDockWidget::DockWidgetFloatable);
  877. statsDock->setWindowTitle(QTStr("Basic.Stats"));
  878. addDockWidget(Qt::BottomDockWidgetArea, statsDock);
  879. statsDock->setVisible(false);
  880. statsDock->setFloating(true);
  881. statsDock->resize(700, 200);
  882. copyActionsDynamicProperties();
  883. qRegisterMetaType<int64_t>("int64_t");
  884. qRegisterMetaType<uint32_t>("uint32_t");
  885. qRegisterMetaType<OBSScene>("OBSScene");
  886. qRegisterMetaType<OBSSceneItem>("OBSSceneItem");
  887. qRegisterMetaType<OBSSource>("OBSSource");
  888. qRegisterMetaType<obs_hotkey_id>("obs_hotkey_id");
  889. qRegisterMetaType<SavedProjectorInfo *>("SavedProjectorInfo *");
  890. ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false);
  891. ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false);
  892. bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode");
  893. ui->scenes->SetGridMode(sceneGrid);
  894. if (sceneGrid)
  895. ui->actionSceneGridMode->setChecked(true);
  896. else
  897. ui->actionSceneListMode->setChecked(true);
  898. ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes));
  899. auto displayResize = [this]() {
  900. struct obs_video_info ovi;
  901. if (obs_get_video_info(&ovi))
  902. ResizePreview(ovi.base_width, ovi.base_height);
  903. UpdateContextBarVisibility();
  904. UpdatePreviewScrollbars();
  905. dpi = devicePixelRatioF();
  906. };
  907. dpi = devicePixelRatioF();
  908. connect(windowHandle(), &QWindow::screenChanged, displayResize);
  909. connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize);
  910. /* TODO: Move these into window-basic-preview */
  911. /* Preview Scaling label */
  912. connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent,
  913. &OBSPreviewScalingLabel::PreviewScaleChanged);
  914. /* Preview Scaling dropdown */
  915. connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode,
  916. &OBSPreviewScalingComboBox::PreviewScaleChanged);
  917. connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode,
  918. &OBSPreviewScalingComboBox::PreviewFixedScalingChanged);
  919. connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this,
  920. &OBSBasic::PreviewScalingModeChanged);
  921. connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized);
  922. connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized);
  923. delete shortcutFilter;
  924. shortcutFilter = CreateShortcutFilter();
  925. installEventFilter(shortcutFilter);
  926. stringstream name;
  927. name << "OBS " << App()->GetVersionString();
  928. blog(LOG_INFO, "%s", name.str().c_str());
  929. blog(LOG_INFO, "---------------------------------");
  930. UpdateTitleBar();
  931. connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited);
  932. cpuUsageInfo = os_cpu_usage_info_start();
  933. cpuUsageTimer = new QTimer(this);
  934. connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage);
  935. cpuUsageTimer->start(3000);
  936. diskFullTimer = new QTimer(this);
  937. connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining);
  938. renameScene = new QAction(QTStr("Rename"), ui->scenesDock);
  939. renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  940. connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName);
  941. ui->scenesDock->addAction(renameScene);
  942. renameSource = new QAction(QTStr("Rename"), ui->sourcesDock);
  943. renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  944. connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName);
  945. ui->sourcesDock->addAction(renameSource);
  946. #ifdef __APPLE__
  947. renameScene->setShortcut({Qt::Key_Return});
  948. renameSource->setShortcut({Qt::Key_Return});
  949. ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace});
  950. ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace});
  951. ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole);
  952. ui->action_Settings->setMenuRole(QAction::PreferencesRole);
  953. ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole);
  954. ui->actionE_xit->setMenuRole(QAction::QuitRole);
  955. #else
  956. renameScene->setShortcut({Qt::Key_F2});
  957. renameSource->setShortcut({Qt::Key_F2});
  958. #endif
  959. #ifdef __linux__
  960. ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q);
  961. #endif
  962. auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) {
  963. QAction *nudge = new QAction(ui->preview);
  964. nudge->setShortcut(seq);
  965. nudge->setShortcutContext(Qt::WidgetShortcut);
  966. ui->preview->addAction(nudge);
  967. connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); });
  968. };
  969. addNudge(Qt::Key_Up, MoveDir::Up, 1);
  970. addNudge(Qt::Key_Down, MoveDir::Down, 1);
  971. addNudge(Qt::Key_Left, MoveDir::Left, 1);
  972. addNudge(Qt::Key_Right, MoveDir::Right, 1);
  973. addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10);
  974. addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10);
  975. addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10);
  976. addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10);
  977. /* Setup dock toggle action
  978. * And hide all docks before restoring parent geometry */
  979. #define SETUP_DOCK(dock) \
  980. setupDockAction(dock); \
  981. ui->menuDocks->addAction(dock->toggleViewAction()); \
  982. dock->setVisible(false);
  983. SETUP_DOCK(ui->scenesDock);
  984. SETUP_DOCK(ui->sourcesDock);
  985. SETUP_DOCK(ui->mixerDock);
  986. SETUP_DOCK(ui->transitionsDock);
  987. SETUP_DOCK(controlsDock);
  988. SETUP_DOCK(statsDock);
  989. #undef SETUP_DOCK
  990. // Register shortcuts for Undo/Redo
  991. ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z);
  992. QList<QKeySequence> shrt;
  993. shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y);
  994. ui->actionMainRedo->setShortcuts(shrt);
  995. ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut);
  996. ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut);
  997. QPoint curPos;
  998. //restore parent window geometry
  999. const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry");
  1000. if (geometry != NULL) {
  1001. QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry));
  1002. restoreGeometry(byteArray);
  1003. QRect windowGeometry = normalGeometry();
  1004. if (!WindowPositionValid(windowGeometry)) {
  1005. QRect rect = QGuiApplication::primaryScreen()->geometry();
  1006. setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect));
  1007. }
  1008. curPos = pos();
  1009. } else {
  1010. QRect desktopRect = QGuiApplication::primaryScreen()->geometry();
  1011. QSize adjSize = desktopRect.size() / 2 - size() / 2;
  1012. curPos = QPoint(adjSize.width(), adjSize.height());
  1013. }
  1014. QPoint curSize(width(), height());
  1015. QPoint statsDockSize(statsDock->width(), statsDock->height());
  1016. QPoint statsDockPos = curSize / 2 - statsDockSize / 2;
  1017. QPoint newPos = curPos + statsDockPos;
  1018. statsDock->move(newPos);
  1019. #ifdef HAVE_OBSCONFIG_H
  1020. ui->actionReleaseNotes->setVisible(true);
  1021. #endif
  1022. ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu);
  1023. connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview);
  1024. connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); });
  1025. connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); });
  1026. QActionGroup *actionGroup = new QActionGroup(this);
  1027. actionGroup->addAction(ui->actionSceneListMode);
  1028. actionGroup->addAction(ui->actionSceneGridMode);
  1029. UpdatePreviewSafeAreas();
  1030. UpdatePreviewSpacingHelpers();
  1031. UpdatePreviewOverflowSettings();
  1032. }
  1033. static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector<OBSSource> &audioSources)
  1034. {
  1035. OBSSourceAutoRelease source = obs_get_output_source(channel);
  1036. if (!source)
  1037. return;
  1038. audioSources.push_back(source.Get());
  1039. OBSDataAutoRelease data = obs_save_source(source);
  1040. obs_data_set_obj(parent, name, data);
  1041. }
  1042. static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData,
  1043. int transitionDuration, obs_data_array_t *transitions, OBSScene &scene,
  1044. OBSSource &curProgramScene, obs_data_array_t *savedProjectorList)
  1045. {
  1046. obs_data_t *saveData = obs_data_create();
  1047. vector<OBSSource> audioSources;
  1048. audioSources.reserve(6);
  1049. SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources);
  1050. SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources);
  1051. SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources);
  1052. SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources);
  1053. SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources);
  1054. SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources);
  1055. /* -------------------------------- */
  1056. /* save non-group sources */
  1057. auto FilterAudioSources = [&](obs_source_t *source) {
  1058. if (obs_source_is_group(source))
  1059. return false;
  1060. return find(begin(audioSources), end(audioSources), source) == end(audioSources);
  1061. };
  1062. using FilterAudioSources_t = decltype(FilterAudioSources);
  1063. obs_data_array_t *sourcesArray = obs_save_sources_filtered(
  1064. [](void *data, obs_source_t *source) {
  1065. auto &func = *static_cast<FilterAudioSources_t *>(data);
  1066. return func(source);
  1067. },
  1068. static_cast<void *>(&FilterAudioSources));
  1069. /* -------------------------------- */
  1070. /* save group sources separately */
  1071. /* saving separately ensures they won't be loaded in older versions */
  1072. obs_data_array_t *groupsArray = obs_save_sources_filtered(
  1073. [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr);
  1074. /* -------------------------------- */
  1075. OBSSourceAutoRelease transition = obs_get_output_source(0);
  1076. obs_source_t *currentScene = obs_scene_get_source(scene);
  1077. const char *sceneName = obs_source_get_name(currentScene);
  1078. const char *programName = obs_source_get_name(curProgramScene);
  1079. const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection");
  1080. obs_data_set_string(saveData, "current_scene", sceneName);
  1081. obs_data_set_string(saveData, "current_program_scene", programName);
  1082. obs_data_set_array(saveData, "scene_order", sceneOrder);
  1083. obs_data_set_string(saveData, "name", sceneCollection);
  1084. obs_data_set_array(saveData, "sources", sourcesArray);
  1085. obs_data_set_array(saveData, "groups", groupsArray);
  1086. obs_data_set_array(saveData, "quick_transitions", quickTransitionData);
  1087. obs_data_set_array(saveData, "transitions", transitions);
  1088. obs_data_set_array(saveData, "saved_projectors", savedProjectorList);
  1089. obs_data_array_release(sourcesArray);
  1090. obs_data_array_release(groupsArray);
  1091. obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition));
  1092. obs_data_set_int(saveData, "transition_duration", transitionDuration);
  1093. return saveData;
  1094. }
  1095. void OBSBasic::copyActionsDynamicProperties()
  1096. {
  1097. // Themes need the QAction dynamic properties
  1098. for (QAction *x : ui->scenesToolbar->actions()) {
  1099. QWidget *temp = ui->scenesToolbar->widgetForAction(x);
  1100. if (!temp)
  1101. continue;
  1102. for (QByteArray &y : x->dynamicPropertyNames()) {
  1103. temp->setProperty(y, x->property(y));
  1104. }
  1105. }
  1106. for (QAction *x : ui->sourcesToolbar->actions()) {
  1107. QWidget *temp = ui->sourcesToolbar->widgetForAction(x);
  1108. if (!temp)
  1109. continue;
  1110. for (QByteArray &y : x->dynamicPropertyNames()) {
  1111. temp->setProperty(y, x->property(y));
  1112. }
  1113. }
  1114. for (QAction *x : ui->mixerToolbar->actions()) {
  1115. QWidget *temp = ui->mixerToolbar->widgetForAction(x);
  1116. if (!temp)
  1117. continue;
  1118. for (QByteArray &y : x->dynamicPropertyNames()) {
  1119. temp->setProperty(y, x->property(y));
  1120. }
  1121. }
  1122. }
  1123. void OBSBasic::UpdateVolumeControlsDecayRate()
  1124. {
  1125. double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate");
  1126. for (size_t i = 0; i < volumes.size(); i++) {
  1127. volumes[i]->SetMeterDecayRate(meterDecayRate);
  1128. }
  1129. }
  1130. void OBSBasic::UpdateVolumeControlsPeakMeterType()
  1131. {
  1132. uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType");
  1133. enum obs_peak_meter_type peakMeterType;
  1134. switch (peakMeterTypeIdx) {
  1135. case 0:
  1136. peakMeterType = SAMPLE_PEAK_METER;
  1137. break;
  1138. case 1:
  1139. peakMeterType = TRUE_PEAK_METER;
  1140. break;
  1141. default:
  1142. peakMeterType = SAMPLE_PEAK_METER;
  1143. break;
  1144. }
  1145. for (size_t i = 0; i < volumes.size(); i++) {
  1146. volumes[i]->setPeakMeterType(peakMeterType);
  1147. }
  1148. }
  1149. void OBSBasic::ClearVolumeControls()
  1150. {
  1151. for (VolControl *vol : volumes)
  1152. delete vol;
  1153. volumes.clear();
  1154. }
  1155. void OBSBasic::RefreshVolumeColors()
  1156. {
  1157. for (VolControl *vol : volumes) {
  1158. vol->refreshColors();
  1159. }
  1160. }
  1161. obs_data_array_t *OBSBasic::SaveSceneListOrder()
  1162. {
  1163. obs_data_array_t *sceneOrder = obs_data_array_create();
  1164. for (int i = 0; i < ui->scenes->count(); i++) {
  1165. OBSDataAutoRelease data = obs_data_create();
  1166. obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text()));
  1167. obs_data_array_push_back(sceneOrder, data);
  1168. }
  1169. return sceneOrder;
  1170. }
  1171. obs_data_array_t *OBSBasic::SaveProjectors()
  1172. {
  1173. obs_data_array_t *savedProjectors = obs_data_array_create();
  1174. auto saveProjector = [savedProjectors](OBSProjector *projector) {
  1175. if (!projector)
  1176. return;
  1177. OBSDataAutoRelease data = obs_data_create();
  1178. ProjectorType type = projector->GetProjectorType();
  1179. switch (type) {
  1180. case ProjectorType::Scene:
  1181. case ProjectorType::Source: {
  1182. OBSSource source = projector->GetSource();
  1183. const char *name = obs_source_get_name(source);
  1184. obs_data_set_string(data, "name", name);
  1185. break;
  1186. }
  1187. default:
  1188. break;
  1189. }
  1190. obs_data_set_int(data, "monitor", projector->GetMonitor());
  1191. obs_data_set_int(data, "type", static_cast<int>(type));
  1192. obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData());
  1193. if (projector->IsAlwaysOnTopOverridden())
  1194. obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop());
  1195. obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden());
  1196. obs_data_array_push_back(savedProjectors, data);
  1197. };
  1198. for (size_t i = 0; i < projectors.size(); i++)
  1199. saveProjector(static_cast<OBSProjector *>(projectors[i]));
  1200. return savedProjectors;
  1201. }
  1202. void OBSBasic::Save(const char *file)
  1203. {
  1204. OBSScene scene = GetCurrentScene();
  1205. OBSSource curProgramScene = OBSGetStrongRef(programScene);
  1206. if (!curProgramScene)
  1207. curProgramScene = obs_scene_get_source(scene);
  1208. OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder();
  1209. OBSDataArrayAutoRelease transitions = SaveTransitions();
  1210. OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions();
  1211. OBSDataArrayAutoRelease savedProjectorList = SaveProjectors();
  1212. OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(),
  1213. transitions, scene, curProgramScene, savedProjectorList);
  1214. obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked());
  1215. obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling());
  1216. obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel());
  1217. obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX());
  1218. obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY());
  1219. if (vcamEnabled) {
  1220. OBSDataAutoRelease obj = obs_data_create();
  1221. obs_data_set_int(obj, "type2", (int)vcamConfig.type);
  1222. switch (vcamConfig.type) {
  1223. case VCamOutputType::Invalid:
  1224. case VCamOutputType::ProgramView:
  1225. case VCamOutputType::PreviewOutput:
  1226. break;
  1227. case VCamOutputType::SceneOutput:
  1228. obs_data_set_string(obj, "scene", vcamConfig.scene.c_str());
  1229. break;
  1230. case VCamOutputType::SourceOutput:
  1231. obs_data_set_string(obj, "source", vcamConfig.source.c_str());
  1232. break;
  1233. }
  1234. obs_data_set_obj(saveData, "virtual-camera", obj);
  1235. }
  1236. if (api) {
  1237. if (!collectionModuleData)
  1238. collectionModuleData = obs_data_create();
  1239. api->on_save(collectionModuleData);
  1240. obs_data_set_obj(saveData, "modules", collectionModuleData);
  1241. }
  1242. if (lastOutputResolution) {
  1243. OBSDataAutoRelease res = obs_data_create();
  1244. obs_data_set_int(res, "x", lastOutputResolution->first);
  1245. obs_data_set_int(res, "y", lastOutputResolution->second);
  1246. obs_data_set_obj(saveData, "resolution", res);
  1247. }
  1248. obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2);
  1249. if (migrationBaseResolution && !usingAbsoluteCoordinates) {
  1250. OBSDataAutoRelease res = obs_data_create();
  1251. obs_data_set_int(res, "x", migrationBaseResolution->first);
  1252. obs_data_set_int(res, "y", migrationBaseResolution->second);
  1253. obs_data_set_obj(saveData, "migration_resolution", res);
  1254. }
  1255. if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak"))
  1256. blog(LOG_ERROR, "Could not save scene data to %s", file);
  1257. }
  1258. void OBSBasic::DeferSaveBegin()
  1259. {
  1260. os_atomic_inc_long(&disableSaving);
  1261. }
  1262. void OBSBasic::DeferSaveEnd()
  1263. {
  1264. long result = os_atomic_dec_long(&disableSaving);
  1265. if (result == 0) {
  1266. SaveProject();
  1267. }
  1268. }
  1269. static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val);
  1270. static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent)
  1271. {
  1272. OBSDataAutoRelease data = obs_data_get_obj(parent, name);
  1273. if (!data)
  1274. return;
  1275. OBSSourceAutoRelease source = obs_load_source(data);
  1276. if (!source)
  1277. return;
  1278. obs_set_output_source(channel, source);
  1279. const char *source_name = obs_source_get_name(source);
  1280. blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name);
  1281. obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1);
  1282. obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source);
  1283. if (monitoring_type != OBS_MONITORING_TYPE_NONE) {
  1284. const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only"
  1285. : "monitor and output";
  1286. blog(LOG_INFO, " - monitoring: %s", type);
  1287. }
  1288. }
  1289. static inline bool HasAudioDevices(const char *source_id)
  1290. {
  1291. const char *output_id = source_id;
  1292. obs_properties_t *props = obs_get_source_properties(output_id);
  1293. size_t count = 0;
  1294. if (!props)
  1295. return false;
  1296. obs_property_t *devices = obs_properties_get(props, "device_id");
  1297. if (devices)
  1298. count = obs_property_list_item_count(devices);
  1299. obs_properties_destroy(props);
  1300. return count != 0;
  1301. }
  1302. void OBSBasic::CreateFirstRunSources()
  1303. {
  1304. bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource());
  1305. bool hasInputAudio = HasAudioDevices(App()->InputAudioSource());
  1306. #ifdef __APPLE__
  1307. /* On macOS 13 and above, the SCK based audio capture provides a
  1308. * better alternative to the device-based audio capture. */
  1309. if (__builtin_available(macOS 13.0, *)) {
  1310. hasDesktopAudio = false;
  1311. }
  1312. #endif
  1313. if (hasDesktopAudio)
  1314. ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1);
  1315. if (hasInputAudio)
  1316. ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3);
  1317. }
  1318. void OBSBasic::DisableRelativeCoordinates(bool enable)
  1319. {
  1320. /* Allow disabling relative positioning to allow loading collections
  1321. * that cannot yet be migrated. */
  1322. OBSDataAutoRelease priv = obs_get_private_data();
  1323. obs_data_set_bool(priv, "AbsoluteCoordinates", enable);
  1324. usingAbsoluteCoordinates = enable;
  1325. ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate")
  1326. : QTStr("Basic.MainMenu.SceneCollection.Remigrate"));
  1327. ui->actionRemigrateSceneCollection->setEnabled(enable);
  1328. }
  1329. void OBSBasic::CreateDefaultScene(bool firstStart)
  1330. {
  1331. disableSaving++;
  1332. ClearSceneData();
  1333. InitDefaultTransitions();
  1334. CreateDefaultQuickTransitions();
  1335. ui->transitionDuration->setValue(300);
  1336. SetTransition(fadeTransition);
  1337. DisableRelativeCoordinates(false);
  1338. OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene"));
  1339. if (firstStart)
  1340. CreateFirstRunSources();
  1341. SetCurrentScene(scene, true);
  1342. disableSaving--;
  1343. }
  1344. static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex)
  1345. {
  1346. for (int i = 0; i < lw->count(); i++) {
  1347. QListWidgetItem *item = lw->item(i);
  1348. if (strcmp(name, QT_TO_UTF8(item->text())) == 0) {
  1349. if (newIndex != i) {
  1350. item = lw->takeItem(i);
  1351. lw->insertItem(newIndex, item);
  1352. }
  1353. break;
  1354. }
  1355. }
  1356. }
  1357. void OBSBasic::LoadSceneListOrder(obs_data_array_t *array)
  1358. {
  1359. size_t num = obs_data_array_count(array);
  1360. for (size_t i = 0; i < num; i++) {
  1361. OBSDataAutoRelease data = obs_data_array_item(array, i);
  1362. const char *name = obs_data_get_string(data, "name");
  1363. ReorderItemByName(ui->scenes, name, (int)i);
  1364. }
  1365. }
  1366. void OBSBasic::LoadSavedProjectors(obs_data_array_t *array)
  1367. {
  1368. for (SavedProjectorInfo *info : savedProjectorsArray) {
  1369. delete info;
  1370. }
  1371. savedProjectorsArray.clear();
  1372. size_t num = obs_data_array_count(array);
  1373. for (size_t i = 0; i < num; i++) {
  1374. OBSDataAutoRelease data = obs_data_array_item(array, i);
  1375. SavedProjectorInfo *info = new SavedProjectorInfo();
  1376. info->monitor = obs_data_get_int(data, "monitor");
  1377. info->type = static_cast<ProjectorType>(obs_data_get_int(data, "type"));
  1378. info->geometry = std::string(obs_data_get_string(data, "geometry"));
  1379. info->name = std::string(obs_data_get_string(data, "name"));
  1380. info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop");
  1381. info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden");
  1382. savedProjectorsArray.emplace_back(info);
  1383. }
  1384. }
  1385. static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val)
  1386. {
  1387. const char *name = obs_source_get_name(filter);
  1388. const char *id = obs_source_get_id(filter);
  1389. int val = (int)(intptr_t)v_val;
  1390. string indent;
  1391. for (int i = 0; i < val; i++)
  1392. indent += " ";
  1393. blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id);
  1394. }
  1395. static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val)
  1396. {
  1397. obs_source_t *source = obs_sceneitem_get_source(item);
  1398. const char *name = obs_source_get_name(source);
  1399. const char *id = obs_source_get_id(source);
  1400. int indent_count = (int)(intptr_t)v_val;
  1401. string indent;
  1402. for (int i = 0; i < indent_count; i++)
  1403. indent += " ";
  1404. blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id);
  1405. obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source);
  1406. if (monitoring_type != OBS_MONITORING_TYPE_NONE) {
  1407. const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only"
  1408. : "monitor and output";
  1409. blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type);
  1410. }
  1411. int child_indent = 1 + indent_count;
  1412. obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent);
  1413. obs_source_t *show_tn = obs_sceneitem_get_transition(item, true);
  1414. obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false);
  1415. if (show_tn)
  1416. blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn),
  1417. obs_source_get_id(show_tn));
  1418. if (hide_tn)
  1419. blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn),
  1420. obs_source_get_id(hide_tn));
  1421. if (obs_sceneitem_is_group(item))
  1422. obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent);
  1423. return true;
  1424. }
  1425. void OBSBasic::LogScenes()
  1426. {
  1427. blog(LOG_INFO, "------------------------------------------------");
  1428. blog(LOG_INFO, "Loaded scenes:");
  1429. for (int i = 0; i < ui->scenes->count(); i++) {
  1430. QListWidgetItem *item = ui->scenes->item(i);
  1431. OBSScene scene = GetOBSRef<OBSScene>(item);
  1432. obs_source_t *source = obs_scene_get_source(scene);
  1433. const char *name = obs_source_get_name(source);
  1434. blog(LOG_INFO, "- scene '%s':", name);
  1435. obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1);
  1436. obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1);
  1437. }
  1438. blog(LOG_INFO, "------------------------------------------------");
  1439. }
  1440. void OBSBasic::Load(const char *file, bool remigrate)
  1441. {
  1442. disableSaving++;
  1443. lastOutputResolution.reset();
  1444. migrationBaseResolution.reset();
  1445. obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak");
  1446. if (!data) {
  1447. disableSaving--;
  1448. const auto path = filesystem::u8path(file);
  1449. const string name = path.stem().u8string();
  1450. /* Check if file exists but failed to load. */
  1451. if (filesystem::exists(path)) {
  1452. /* Assume the file is corrupt and rename it to allow
  1453. * for manual recovery if possible. */
  1454. auto newPath = path;
  1455. newPath.concat(".invalid");
  1456. blog(LOG_WARNING,
  1457. "File exists but appears to be corrupt, renaming "
  1458. "to \"%s\" before continuing.",
  1459. newPath.filename().u8string().c_str());
  1460. error_code ec;
  1461. filesystem::rename(path, newPath, ec);
  1462. if (ec) {
  1463. blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value());
  1464. }
  1465. }
  1466. blog(LOG_INFO, "No scene file found, creating default scene");
  1467. bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun");
  1468. CreateDefaultScene(!hasFirstRun);
  1469. SaveProject();
  1470. return;
  1471. }
  1472. LoadData(data, file, remigrate);
  1473. }
  1474. static inline void AddMissingFiles(void *data, obs_source_t *source)
  1475. {
  1476. obs_missing_files_t *f = (obs_missing_files_t *)data;
  1477. obs_missing_files_t *sf = obs_source_get_missing_files(source);
  1478. obs_missing_files_append(f, sf);
  1479. obs_missing_files_destroy(sf);
  1480. }
  1481. static void ClearRelativePosCb(obs_data_t *data, void *)
  1482. {
  1483. const string_view id = obs_data_get_string(data, "id");
  1484. if (id != "scene" && id != "group")
  1485. return;
  1486. OBSDataAutoRelease settings = obs_data_get_obj(data, "settings");
  1487. OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items");
  1488. obs_data_array_enum(
  1489. items,
  1490. [](obs_data_t *data, void *) {
  1491. obs_data_unset_user_value(data, "pos_rel");
  1492. obs_data_unset_user_value(data, "scale_rel");
  1493. obs_data_unset_user_value(data, "scale_ref");
  1494. obs_data_unset_user_value(data, "bounds_rel");
  1495. },
  1496. nullptr);
  1497. }
  1498. void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate)
  1499. {
  1500. ClearSceneData();
  1501. ClearContextBar();
  1502. /* Exit OBS if clearing scene data failed for some reason. */
  1503. if (clearingFailed) {
  1504. OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text"));
  1505. close();
  1506. return;
  1507. }
  1508. InitDefaultTransitions();
  1509. if (devicePropertiesThread && devicePropertiesThread->isRunning()) {
  1510. devicePropertiesThread->wait();
  1511. devicePropertiesThread.reset();
  1512. }
  1513. QApplication::sendPostedEvents(nullptr);
  1514. OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules");
  1515. if (api)
  1516. api->on_preload(modulesObj);
  1517. /* Keep a reference to "modules" data so plugins that are not loaded do
  1518. * not have their collection specific data lost. */
  1519. collectionModuleData = obs_data_get_obj(data, "modules");
  1520. OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order");
  1521. OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources");
  1522. OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups");
  1523. OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions");
  1524. const char *sceneName = obs_data_get_string(data, "current_scene");
  1525. const char *programSceneName = obs_data_get_string(data, "current_program_scene");
  1526. const char *transitionName = obs_data_get_string(data, "current_transition");
  1527. if (!opt_starting_scene.empty()) {
  1528. programSceneName = opt_starting_scene.c_str();
  1529. if (!IsPreviewProgramMode())
  1530. sceneName = opt_starting_scene.c_str();
  1531. }
  1532. int newDuration = obs_data_get_int(data, "transition_duration");
  1533. if (!newDuration)
  1534. newDuration = 300;
  1535. if (!transitionName)
  1536. transitionName = obs_source_get_name(fadeTransition);
  1537. const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection");
  1538. obs_data_set_default_string(data, "name", curSceneCollection);
  1539. const char *name = obs_data_get_string(data, "name");
  1540. OBSSourceAutoRelease curScene;
  1541. OBSSourceAutoRelease curProgramScene;
  1542. obs_source_t *curTransition;
  1543. if (!name || !*name)
  1544. name = curSceneCollection;
  1545. LoadAudioDevice(DESKTOP_AUDIO_1, 1, data);
  1546. LoadAudioDevice(DESKTOP_AUDIO_2, 2, data);
  1547. LoadAudioDevice(AUX_AUDIO_1, 3, data);
  1548. LoadAudioDevice(AUX_AUDIO_2, 4, data);
  1549. LoadAudioDevice(AUX_AUDIO_3, 5, data);
  1550. LoadAudioDevice(AUX_AUDIO_4, 6, data);
  1551. if (!sources) {
  1552. sources = std::move(groups);
  1553. } else {
  1554. obs_data_array_push_back_array(sources, groups);
  1555. }
  1556. /* Reset relative coordinate data if forcefully remigrating. */
  1557. if (remigrate) {
  1558. obs_data_set_int(data, "version", 1);
  1559. obs_data_array_enum(sources, ClearRelativePosCb, nullptr);
  1560. }
  1561. bool resetVideo = false;
  1562. bool disableRelativeCoords = false;
  1563. obs_video_info ovi;
  1564. int64_t version = obs_data_get_int(data, "version");
  1565. OBSDataAutoRelease res = obs_data_get_obj(data, "resolution");
  1566. if (res) {
  1567. lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")};
  1568. }
  1569. /* Only migrate legacy collection if resolution is saved. */
  1570. if (version < 2 && lastOutputResolution) {
  1571. obs_get_video_info(&ovi);
  1572. uint32_t width = obs_data_get_int(res, "x");
  1573. uint32_t height = obs_data_get_int(res, "y");
  1574. migrationBaseResolution = {width, height};
  1575. if (ovi.base_height != height || ovi.base_width != width) {
  1576. ovi.base_width = width;
  1577. ovi.base_height = height;
  1578. /* Attempt to reset to last known canvas resolution for migration. */
  1579. resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS;
  1580. disableRelativeCoords = !resetVideo;
  1581. }
  1582. /* If migration is possible, and it wasn't forced, back up the original file. */
  1583. if (!disableRelativeCoords && !remigrate) {
  1584. auto path = filesystem::u8path(file);
  1585. auto backupPath = path.concat(".v1");
  1586. if (!filesystem::exists(backupPath)) {
  1587. if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) {
  1588. blog(LOG_WARNING,
  1589. "Failed to create a backup of existing scene collection data!");
  1590. }
  1591. }
  1592. }
  1593. } else if (version < 2) {
  1594. disableRelativeCoords = true;
  1595. } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) {
  1596. migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")};
  1597. }
  1598. DisableRelativeCoordinates(disableRelativeCoords);
  1599. obs_missing_files_t *files = obs_missing_files_create();
  1600. obs_load_sources(sources, AddMissingFiles, files);
  1601. if (resetVideo)
  1602. ResetVideo();
  1603. if (transitions)
  1604. LoadTransitions(transitions, AddMissingFiles, files);
  1605. if (sceneOrder)
  1606. LoadSceneListOrder(sceneOrder);
  1607. curTransition = FindTransition(transitionName);
  1608. if (!curTransition)
  1609. curTransition = fadeTransition;
  1610. ui->transitionDuration->setValue(newDuration);
  1611. SetTransition(curTransition);
  1612. retryScene:
  1613. curScene = obs_get_source_by_name(sceneName);
  1614. curProgramScene = obs_get_source_by_name(programSceneName);
  1615. /* if the starting scene command line parameter is bad at all,
  1616. * fall back to original settings */
  1617. if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) {
  1618. sceneName = obs_data_get_string(data, "current_scene");
  1619. programSceneName = obs_data_get_string(data, "current_program_scene");
  1620. opt_starting_scene.clear();
  1621. goto retryScene;
  1622. }
  1623. if (!curScene) {
  1624. auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) {
  1625. *static_cast<OBSSourceAutoRelease *>(source_ptr) = obs_source_get_ref(scene);
  1626. return false;
  1627. };
  1628. obs_enum_scenes(find_scene_cb, &curScene);
  1629. }
  1630. SetCurrentScene(curScene.Get(), true);
  1631. if (!curProgramScene)
  1632. curProgramScene = std::move(curScene);
  1633. if (IsPreviewProgramMode())
  1634. TransitionToScene(curProgramScene.Get(), true);
  1635. /* ------------------- */
  1636. bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors");
  1637. if (projectorSave) {
  1638. OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors");
  1639. if (savedProjectors) {
  1640. LoadSavedProjectors(savedProjectors);
  1641. OpenSavedProjectors();
  1642. activateWindow();
  1643. }
  1644. }
  1645. /* ------------------- */
  1646. std::string file_base = strrchr(file, '/') + 1;
  1647. file_base.erase(file_base.size() - 5, 5);
  1648. config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name);
  1649. config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str());
  1650. OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions");
  1651. LoadQuickTransitions(quickTransitionData);
  1652. RefreshQuickTransitions();
  1653. bool previewLocked = obs_data_get_bool(data, "preview_locked");
  1654. ui->preview->SetLocked(previewLocked);
  1655. ui->actionLockPreview->setChecked(previewLocked);
  1656. /* ---------------------- */
  1657. bool fixedScaling = obs_data_get_bool(data, "scaling_enabled");
  1658. int scalingLevel = (int)obs_data_get_int(data, "scaling_level");
  1659. float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x");
  1660. float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y");
  1661. if (fixedScaling) {
  1662. ui->preview->SetScalingLevel(scalingLevel);
  1663. ui->preview->SetScrollingOffset(scrollOffX, scrollOffY);
  1664. }
  1665. ui->preview->SetFixedScaling(fixedScaling);
  1666. emit ui->preview->DisplayResized();
  1667. if (vcamEnabled) {
  1668. OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera");
  1669. vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2");
  1670. if (vcamConfig.type == VCamOutputType::Invalid)
  1671. vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type");
  1672. if (vcamConfig.type == VCamOutputType::Invalid) {
  1673. VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal");
  1674. switch (internal) {
  1675. case VCamInternalType::Default:
  1676. vcamConfig.type = VCamOutputType::ProgramView;
  1677. break;
  1678. case VCamInternalType::Preview:
  1679. vcamConfig.type = VCamOutputType::PreviewOutput;
  1680. break;
  1681. }
  1682. }
  1683. vcamConfig.scene = obs_data_get_string(obj, "scene");
  1684. vcamConfig.source = obs_data_get_string(obj, "source");
  1685. }
  1686. if (obs_data_has_user_value(data, "resolution")) {
  1687. OBSDataAutoRelease res = obs_data_get_obj(data, "resolution");
  1688. if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) {
  1689. lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")};
  1690. }
  1691. }
  1692. /* ---------------------- */
  1693. if (api)
  1694. api->on_load(modulesObj);
  1695. obs_data_release(data);
  1696. if (!opt_starting_scene.empty())
  1697. opt_starting_scene.clear();
  1698. if (opt_start_streaming && !safe_mode) {
  1699. blog(LOG_INFO, "Starting stream due to command line parameter");
  1700. QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection);
  1701. opt_start_streaming = false;
  1702. }
  1703. if (opt_start_recording && !safe_mode) {
  1704. blog(LOG_INFO, "Starting recording due to command line parameter");
  1705. QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection);
  1706. opt_start_recording = false;
  1707. }
  1708. if (opt_start_replaybuffer && !safe_mode) {
  1709. QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection);
  1710. opt_start_replaybuffer = false;
  1711. }
  1712. if (opt_start_virtualcam && !safe_mode) {
  1713. QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection);
  1714. opt_start_virtualcam = false;
  1715. }
  1716. LogScenes();
  1717. if (!App()->IsMissingFilesCheckDisabled())
  1718. ShowMissingFilesDialog(files);
  1719. disableSaving--;
  1720. if (vcamEnabled)
  1721. outputHandler->UpdateVirtualCamOutputSource();
  1722. OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED);
  1723. OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);
  1724. }
  1725. constexpr std::string_view OBSServiceFileName = "service.json";
  1726. void OBSBasic::SaveService()
  1727. {
  1728. if (!service)
  1729. return;
  1730. const OBSProfile &currentProfile = GetCurrentProfile();
  1731. const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName);
  1732. OBSDataAutoRelease data = obs_data_create();
  1733. OBSDataAutoRelease settings = obs_service_get_settings(service);
  1734. obs_data_set_string(data, "type", obs_service_get_type(service));
  1735. obs_data_set_obj(data, "settings", settings);
  1736. if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) {
  1737. blog(LOG_WARNING, "Failed to save service");
  1738. }
  1739. }
  1740. bool OBSBasic::LoadService()
  1741. {
  1742. OBSDataAutoRelease data;
  1743. try {
  1744. const OBSProfile &currentProfile = GetCurrentProfile();
  1745. const std::filesystem::path jsonFilePath =
  1746. currentProfile.path / std::filesystem::u8path(OBSServiceFileName);
  1747. data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak");
  1748. if (!data) {
  1749. return false;
  1750. }
  1751. } catch (const std::invalid_argument &error) {
  1752. blog(LOG_ERROR, "%s", error.what());
  1753. return false;
  1754. }
  1755. const char *type;
  1756. obs_data_set_default_string(data, "type", "rtmp_common");
  1757. type = obs_data_get_string(data, "type");
  1758. OBSDataAutoRelease settings = obs_data_get_obj(data, "settings");
  1759. OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys");
  1760. service = obs_service_create(type, "default_service", settings, hotkey_data);
  1761. obs_service_release(service);
  1762. if (!service)
  1763. return false;
  1764. /* Enforce Opus on WHIP if needed */
  1765. if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) {
  1766. const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder");
  1767. if (strcmp(option, "opus") != 0)
  1768. config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus");
  1769. option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder");
  1770. const char *encoder_codec = obs_get_encoder_codec(option);
  1771. if (!encoder_codec || strcmp(encoder_codec, "opus") != 0)
  1772. config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus");
  1773. }
  1774. return true;
  1775. }
  1776. bool OBSBasic::InitService()
  1777. {
  1778. ProfileScope("OBSBasic::InitService");
  1779. if (LoadService())
  1780. return true;
  1781. service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr);
  1782. if (!service)
  1783. return false;
  1784. obs_service_release(service);
  1785. return true;
  1786. }
  1787. static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0};
  1788. extern void CheckExistingCookieId();
  1789. #ifdef __APPLE__
  1790. #define DEFAULT_CONTAINER "fragmented_mov"
  1791. #elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0
  1792. #define DEFAULT_CONTAINER "mkv"
  1793. #else
  1794. #define DEFAULT_CONTAINER "hybrid_mp4"
  1795. #endif
  1796. bool OBSBasic::InitBasicConfigDefaults()
  1797. {
  1798. QList<QScreen *> screens = QGuiApplication::screens();
  1799. if (!screens.size()) {
  1800. OBSErrorBox(NULL, "There appears to be no monitors. Er, this "
  1801. "technically shouldn't be possible.");
  1802. return false;
  1803. }
  1804. QScreen *primaryScreen = QGuiApplication::primaryScreen();
  1805. uint32_t cx = primaryScreen->size().width();
  1806. uint32_t cy = primaryScreen->size().height();
  1807. cx *= devicePixelRatioF();
  1808. cy *= devicePixelRatioF();
  1809. bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults");
  1810. /* use 1920x1080 for new default base res if main monitor is above
  1811. * 1920x1080, but don't apply for people from older builds -- only to
  1812. * new users */
  1813. if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) {
  1814. cx = 1920;
  1815. cy = 1080;
  1816. }
  1817. bool changed = false;
  1818. /* ----------------------------------------------------- */
  1819. /* move over old FFmpeg track settings */
  1820. if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") &&
  1821. !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) {
  1822. int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack");
  1823. config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1));
  1824. config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true);
  1825. changed = true;
  1826. }
  1827. /* ----------------------------------------------------- */
  1828. /* move over mixer values in advanced if older config */
  1829. if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") &&
  1830. !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) {
  1831. uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex");
  1832. track = 1ULL << (track - 1);
  1833. config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track);
  1834. config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex");
  1835. changed = true;
  1836. }
  1837. /* ----------------------------------------------------- */
  1838. /* set twitch chat extensions to "both" if prev version */
  1839. /* is under 24.1 */
  1840. if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") &&
  1841. !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) {
  1842. config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3);
  1843. changed = true;
  1844. }
  1845. /* ----------------------------------------------------- */
  1846. /* move bitrate enforcement setting to new value */
  1847. if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") &&
  1848. !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") &&
  1849. !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) {
  1850. bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate");
  1851. config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce);
  1852. config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true);
  1853. changed = true;
  1854. }
  1855. /* ----------------------------------------------------- */
  1856. /* enforce minimum retry delay of 1 second prior to 27.1 */
  1857. if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) {
  1858. int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay");
  1859. if (retryDelay < 1) {
  1860. config_set_uint(activeConfiguration, "Output", "RetryDelay", 1);
  1861. changed = true;
  1862. }
  1863. }
  1864. /* ----------------------------------------------------- */
  1865. /* Migrate old container selection (if any) to new key. */
  1866. auto MigrateFormat = [&](const char *section) {
  1867. bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat");
  1868. bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2");
  1869. if (!has_new_key && !has_old_key)
  1870. return;
  1871. string old_format =
  1872. config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat");
  1873. string new_format = old_format;
  1874. if (old_format == "ts")
  1875. new_format = "mpegts";
  1876. else if (old_format == "m3u8")
  1877. new_format = "hls";
  1878. else if (old_format == "fmp4")
  1879. new_format = "fragmented_mp4";
  1880. else if (old_format == "fmov")
  1881. new_format = "fragmented_mov";
  1882. if (new_format != old_format || !has_new_key) {
  1883. config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str());
  1884. changed = true;
  1885. }
  1886. };
  1887. MigrateFormat("AdvOut");
  1888. MigrateFormat("SimpleOutput");
  1889. /* ----------------------------------------------------- */
  1890. /* Migrate output scale setting to GPU scaling options. */
  1891. if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") &&
  1892. !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) {
  1893. config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR);
  1894. }
  1895. if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") &&
  1896. !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) {
  1897. config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR);
  1898. }
  1899. /* ----------------------------------------------------- */
  1900. if (changed) {
  1901. activeConfiguration.SaveSafe("tmp");
  1902. }
  1903. /* ----------------------------------------------------- */
  1904. config_set_default_string(activeConfiguration, "Output", "Mode", "Simple");
  1905. config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false);
  1906. config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false);
  1907. config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true);
  1908. config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true);
  1909. config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str());
  1910. config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER);
  1911. config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500);
  1912. config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160);
  1913. config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false);
  1914. config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast");
  1915. config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5");
  1916. config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream");
  1917. config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false);
  1918. config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20);
  1919. config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512);
  1920. config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay");
  1921. config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac");
  1922. config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac");
  1923. config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0));
  1924. config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true);
  1925. config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false);
  1926. config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1);
  1927. config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2);
  1928. config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264");
  1929. config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard");
  1930. config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str());
  1931. config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER);
  1932. config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false);
  1933. config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0));
  1934. config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none");
  1935. config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1);
  1936. config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1);
  1937. config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true);
  1938. config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str());
  1939. config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4");
  1940. config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500);
  1941. config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250);
  1942. config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false);
  1943. config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false);
  1944. config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160);
  1945. config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1);
  1946. config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160);
  1947. config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160);
  1948. config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160);
  1949. config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160);
  1950. config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160);
  1951. config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160);
  1952. config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15);
  1953. config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048);
  1954. config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false);
  1955. config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20);
  1956. config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512);
  1957. config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx);
  1958. config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy);
  1959. /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */
  1960. if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") ||
  1961. !config_has_user_value(activeConfiguration, "Video", "BaseCY")) {
  1962. config_set_uint(activeConfiguration, "Video", "BaseCX", cx);
  1963. config_set_uint(activeConfiguration, "Video", "BaseCY", cy);
  1964. config_save_safe(activeConfiguration, "tmp", nullptr);
  1965. }
  1966. config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss");
  1967. config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false);
  1968. config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20);
  1969. config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true);
  1970. config_set_default_bool(activeConfiguration, "Output", "Reconnect", true);
  1971. config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2);
  1972. config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25);
  1973. config_set_default_string(activeConfiguration, "Output", "BindIP", "default");
  1974. config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6");
  1975. config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false);
  1976. config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false);
  1977. int i = 0;
  1978. uint32_t scale_cx = cx;
  1979. uint32_t scale_cy = cy;
  1980. /* use a default scaled resolution that has a pixel count no higher
  1981. * than 1280x720 */
  1982. while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) {
  1983. double scale = scaled_vals[i++];
  1984. scale_cx = uint32_t(double(cx) / scale);
  1985. scale_cy = uint32_t(double(cy) / scale);
  1986. }
  1987. config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx);
  1988. config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy);
  1989. /* don't allow OutputCX/OutputCY to be susceptible to defaults
  1990. * changing */
  1991. if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") ||
  1992. !config_has_user_value(activeConfiguration, "Video", "OutputCY")) {
  1993. config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx);
  1994. config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy);
  1995. config_save_safe(activeConfiguration, "tmp", nullptr);
  1996. }
  1997. config_set_default_uint(activeConfiguration, "Video", "FPSType", 0);
  1998. config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30");
  1999. config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30);
  2000. config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30);
  2001. config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1);
  2002. config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic");
  2003. config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12");
  2004. config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709");
  2005. config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial");
  2006. config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300);
  2007. config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000);
  2008. config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default");
  2009. config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName",
  2010. Str("Basic.Settings.Advanced.Audio.MonitoringDevice"
  2011. ".Default"));
  2012. config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000);
  2013. config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo");
  2014. config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST);
  2015. config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0);
  2016. CheckExistingCookieId();
  2017. return true;
  2018. }
  2019. extern bool EncoderAvailable(const char *encoder);
  2020. void OBSBasic::InitBasicConfigDefaults2()
  2021. {
  2022. bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults");
  2023. bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults;
  2024. config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder",
  2025. useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264);
  2026. config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder",
  2027. useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264);
  2028. const char *aac_default = "ffmpeg_aac";
  2029. if (EncoderAvailable("CoreAudio_AAC"))
  2030. aac_default = "CoreAudio_AAC";
  2031. else if (EncoderAvailable("libfdk_aac"))
  2032. aac_default = "libfdk_aac";
  2033. config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default);
  2034. config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default);
  2035. }
  2036. bool OBSBasic::InitBasicConfig()
  2037. {
  2038. ProfileScope("OBSBasic::InitBasicConfig");
  2039. RefreshProfiles(true);
  2040. const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")};
  2041. const std::optional<OBSProfile> currentProfile = GetProfileByName(currentProfileName);
  2042. const std::optional<OBSProfile> foundProfile = GetProfileByName(opt_starting_profile);
  2043. try {
  2044. if (foundProfile) {
  2045. ActivateProfile(foundProfile.value());
  2046. } else if (currentProfile) {
  2047. ActivateProfile(currentProfile.value());
  2048. } else {
  2049. const OBSProfile &newProfile = CreateProfile(currentProfileName);
  2050. ActivateProfile(newProfile);
  2051. }
  2052. } catch (const std::logic_error &) {
  2053. OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1);
  2054. return false;
  2055. }
  2056. return true;
  2057. }
  2058. void OBSBasic::InitOBSCallbacks()
  2059. {
  2060. ProfileScope("OBSBasic::InitOBSCallbacks");
  2061. signalHandlers.reserve(signalHandlers.size() + 9);
  2062. signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this);
  2063. signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this);
  2064. signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this);
  2065. signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this);
  2066. signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated,
  2067. this);
  2068. signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate",
  2069. OBSBasic::SourceAudioDeactivated, this);
  2070. signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this);
  2071. signalHandlers.emplace_back(
  2072. obs_get_signal_handler(), "source_filter_add",
  2073. [](void *data, calldata_t *) {
  2074. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "UpdateEditMenu",
  2075. Qt::QueuedConnection);
  2076. },
  2077. this);
  2078. signalHandlers.emplace_back(
  2079. obs_get_signal_handler(), "source_filter_remove",
  2080. [](void *data, calldata_t *) {
  2081. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "UpdateEditMenu",
  2082. Qt::QueuedConnection);
  2083. },
  2084. this);
  2085. }
  2086. void OBSBasic::InitPrimitives()
  2087. {
  2088. ProfileScope("OBSBasic::InitPrimitives");
  2089. obs_enter_graphics();
  2090. gs_render_start(true);
  2091. gs_vertex2f(0.0f, 0.0f);
  2092. gs_vertex2f(0.0f, 1.0f);
  2093. gs_vertex2f(1.0f, 0.0f);
  2094. gs_vertex2f(1.0f, 1.0f);
  2095. box = gs_render_save();
  2096. gs_render_start(true);
  2097. gs_vertex2f(0.0f, 0.0f);
  2098. gs_vertex2f(0.0f, 1.0f);
  2099. boxLeft = gs_render_save();
  2100. gs_render_start(true);
  2101. gs_vertex2f(0.0f, 0.0f);
  2102. gs_vertex2f(1.0f, 0.0f);
  2103. boxTop = gs_render_save();
  2104. gs_render_start(true);
  2105. gs_vertex2f(1.0f, 0.0f);
  2106. gs_vertex2f(1.0f, 1.0f);
  2107. boxRight = gs_render_save();
  2108. gs_render_start(true);
  2109. gs_vertex2f(0.0f, 1.0f);
  2110. gs_vertex2f(1.0f, 1.0f);
  2111. boxBottom = gs_render_save();
  2112. gs_render_start(true);
  2113. for (int i = 0; i <= 360; i += (360 / 20)) {
  2114. float pos = RAD(float(i));
  2115. gs_vertex2f(cosf(pos), sinf(pos));
  2116. }
  2117. circle = gs_render_save();
  2118. InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine);
  2119. obs_leave_graphics();
  2120. }
  2121. void OBSBasic::ReplayBufferActionTriggered()
  2122. {
  2123. if (outputHandler->ReplayBufferActive())
  2124. StopReplayBuffer();
  2125. else
  2126. StartReplayBuffer();
  2127. };
  2128. void OBSBasic::ResetOutputs()
  2129. {
  2130. ProfileScope("OBSBasic::ResetOutputs");
  2131. const char *mode = config_get_string(activeConfiguration, "Output", "Mode");
  2132. bool advOut = astrcmpi(mode, "Advanced") == 0;
  2133. if ((!outputHandler || !outputHandler->Active()) &&
  2134. (!setupStreamingGuard.valid() ||
  2135. setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) {
  2136. outputHandler.reset();
  2137. outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this));
  2138. emit ReplayBufEnabled(outputHandler->replayBuffer);
  2139. if (sysTrayReplayBuffer)
  2140. sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer);
  2141. UpdateIsRecordingPausable();
  2142. } else {
  2143. outputHandler->Update();
  2144. }
  2145. }
  2146. #define STARTUP_SEPARATOR "==== Startup complete ==============================================="
  2147. #define SHUTDOWN_SEPARATOR "==== Shutting down =================================================="
  2148. #define UNSUPPORTED_ERROR \
  2149. "Failed to initialize video:\n\nRequired graphics API functionality " \
  2150. "not found. Your GPU may not be supported."
  2151. #define UNKNOWN_ERROR \
  2152. "Failed to initialize video. Your GPU may not be supported, " \
  2153. "or your graphics drivers may need to be updated."
  2154. static inline void LogEncoders()
  2155. {
  2156. constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL;
  2157. auto list_encoders = [](obs_encoder_type type) {
  2158. size_t idx = 0;
  2159. const char *encoder_type;
  2160. while (obs_enum_encoder_types(idx++, &encoder_type)) {
  2161. if (obs_get_encoder_caps(encoder_type) & hide_flags ||
  2162. obs_get_encoder_type(encoder_type) != type) {
  2163. continue;
  2164. }
  2165. blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type));
  2166. }
  2167. };
  2168. blog(LOG_INFO, "---------------------------------");
  2169. blog(LOG_INFO, "Available Encoders:");
  2170. blog(LOG_INFO, " Video Encoders:");
  2171. list_encoders(OBS_ENCODER_VIDEO);
  2172. blog(LOG_INFO, " Audio Encoders:");
  2173. list_encoders(OBS_ENCODER_AUDIO);
  2174. }
  2175. void OBSBasic::OBSInit()
  2176. {
  2177. ProfileScope("OBSBasic::OBSInit");
  2178. if (!InitBasicConfig())
  2179. throw "Failed to load basic.ini";
  2180. if (!ResetAudio())
  2181. throw "Failed to initialize audio";
  2182. int ret = 0;
  2183. ret = ResetVideo();
  2184. switch (ret) {
  2185. case OBS_VIDEO_MODULE_NOT_FOUND:
  2186. throw "Failed to initialize video: Graphics module not found";
  2187. case OBS_VIDEO_NOT_SUPPORTED:
  2188. throw UNSUPPORTED_ERROR;
  2189. case OBS_VIDEO_INVALID_PARAM:
  2190. throw "Failed to initialize video: Invalid parameters";
  2191. default:
  2192. if (ret != OBS_VIDEO_SUCCESS)
  2193. throw UNKNOWN_ERROR;
  2194. }
  2195. /* load audio monitoring */
  2196. if (obs_audio_monitoring_available()) {
  2197. const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName");
  2198. const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId");
  2199. obs_set_audio_monitoring_device(device_name, device_id);
  2200. blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id);
  2201. }
  2202. InitOBSCallbacks();
  2203. InitHotkeys();
  2204. ui->preview->Init();
  2205. /* hack to prevent elgato from loading its own QtNetwork that it tries
  2206. * to ship with */
  2207. #if defined(_WIN32) && !defined(_DEBUG)
  2208. LoadLibraryW(L"Qt6Network");
  2209. #endif
  2210. struct obs_module_failure_info mfi;
  2211. /* Safe Mode disables third-party plugins so we don't need to add earch
  2212. * paths outside the OBS bundle/installation. */
  2213. if (safe_mode || disable_3p_plugins) {
  2214. SetSafeModuleNames();
  2215. } else {
  2216. AddExtraModulePaths();
  2217. }
  2218. /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code.
  2219. Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information.
  2220. */
  2221. RefreshSceneCollections(true);
  2222. blog(LOG_INFO, "---------------------------------");
  2223. obs_load_all_modules2(&mfi);
  2224. blog(LOG_INFO, "---------------------------------");
  2225. obs_log_loaded_modules();
  2226. blog(LOG_INFO, "---------------------------------");
  2227. obs_post_load_modules();
  2228. BPtr<char *> failed_modules = mfi.failed_modules;
  2229. #ifdef BROWSER_AVAILABLE
  2230. cef = obs_browser_init_panel();
  2231. cef_js_avail = cef && obs_browser_qcef_version() >= 3;
  2232. #endif
  2233. vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0;
  2234. if (vcamEnabled) {
  2235. emit VirtualCamEnabled();
  2236. }
  2237. UpdateProfileEncoders();
  2238. LogEncoders();
  2239. blog(LOG_INFO, STARTUP_SEPARATOR);
  2240. if (!InitService())
  2241. throw "Failed to initialize service";
  2242. ResetOutputs();
  2243. CreateHotkeys();
  2244. InitPrimitives();
  2245. sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode");
  2246. swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode");
  2247. editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode");
  2248. if (!opt_studio_mode) {
  2249. SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode"));
  2250. } else {
  2251. SetPreviewProgramMode(true);
  2252. opt_studio_mode = false;
  2253. }
  2254. #define SET_VISIBILITY(name, control) \
  2255. do { \
  2256. if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \
  2257. bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \
  2258. ui->control->setChecked(visible); \
  2259. } \
  2260. } while (false)
  2261. SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars);
  2262. SET_VISIBILITY("ShowStatusBar", toggleStatusBar);
  2263. #undef SET_VISIBILITY
  2264. bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons");
  2265. ui->toggleSourceIcons->setChecked(sourceIconsVisible);
  2266. bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars");
  2267. ui->toggleContextBar->setChecked(contextVisible);
  2268. ui->contextContainer->setVisible(contextVisible);
  2269. if (contextVisible)
  2270. UpdateContextBar(true);
  2271. UpdateEditMenu();
  2272. {
  2273. ProfileScope("OBSBasic::Load");
  2274. const std::string sceneCollectionName{
  2275. config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
  2276. const std::optional<OBSSceneCollection> configuredCollection =
  2277. GetSceneCollectionByName(sceneCollectionName);
  2278. const std::optional<OBSSceneCollection> foundCollection =
  2279. GetSceneCollectionByName(opt_starting_collection);
  2280. if (foundCollection) {
  2281. ActivateSceneCollection(foundCollection.value());
  2282. } else if (configuredCollection) {
  2283. ActivateSceneCollection(configuredCollection.value());
  2284. } else {
  2285. disableSaving--;
  2286. SetupNewSceneCollection(sceneCollectionName);
  2287. disableSaving++;
  2288. }
  2289. disableSaving--;
  2290. if (foundCollection || configuredCollection) {
  2291. OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED);
  2292. OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED);
  2293. }
  2294. OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED);
  2295. OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);
  2296. disableSaving++;
  2297. }
  2298. loaded = true;
  2299. previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled");
  2300. if (!previewEnabled && !IsPreviewProgramMode())
  2301. QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection,
  2302. Q_ARG(bool, previewEnabled));
  2303. else if (!previewEnabled && IsPreviewProgramMode())
  2304. QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true));
  2305. disableSaving--;
  2306. auto addDisplay = [this](OBSQTDisplay *window) {
  2307. obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this);
  2308. struct obs_video_info ovi;
  2309. if (obs_get_video_info(&ovi))
  2310. ResizePreview(ovi.base_width, ovi.base_height);
  2311. };
  2312. connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay);
  2313. /* Show the main window, unless the tray icon isn't available
  2314. * or neither the setting nor flag for starting minimized is set. */
  2315. bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled");
  2316. bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted");
  2317. bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled &&
  2318. (opt_minimize_tray || sysTrayWhenStarted);
  2319. #ifdef _WIN32
  2320. SetWin32DropStyle(this);
  2321. if (!hideWindowOnStart)
  2322. show();
  2323. #endif
  2324. bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop");
  2325. #ifdef ENABLE_WAYLAND
  2326. bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND;
  2327. #else
  2328. bool isWayland = false;
  2329. #endif
  2330. if (!isWayland && (alwaysOnTop || opt_always_on_top)) {
  2331. SetAlwaysOnTop(this, true);
  2332. ui->actionAlwaysOnTop->setChecked(true);
  2333. } else if (isWayland) {
  2334. if (opt_always_on_top)
  2335. blog(LOG_INFO, "Always On Top not available on Wayland, ignoring.");
  2336. ui->actionAlwaysOnTop->setEnabled(false);
  2337. ui->actionAlwaysOnTop->setVisible(false);
  2338. }
  2339. #ifndef _WIN32
  2340. if (!hideWindowOnStart)
  2341. show();
  2342. #endif
  2343. /* setup stats dock */
  2344. OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false);
  2345. statsDock->setWidget(statsDlg);
  2346. /* ----------------------------- */
  2347. /* add custom browser docks */
  2348. #if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED)
  2349. YouTubeAppDock::CleanupYouTubeUrls();
  2350. #endif
  2351. #ifdef BROWSER_AVAILABLE
  2352. if (cef) {
  2353. QAction *action = new QAction(QTStr("Basic.MainMenu.Docks."
  2354. "CustomBrowserDocks"),
  2355. this);
  2356. ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action);
  2357. connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks);
  2358. ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction());
  2359. LoadExtraBrowserDocks();
  2360. }
  2361. #endif
  2362. #ifdef YOUTUBE_ENABLED
  2363. /* setup YouTube app dock */
  2364. if (YouTubeAppDock::IsYTServiceSelected())
  2365. NewYouTubeAppDock();
  2366. #endif
  2367. const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState");
  2368. if (!dockStateStr) {
  2369. on_resetDocks_triggered(true);
  2370. } else {
  2371. QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr));
  2372. if (!restoreState(dockState))
  2373. on_resetDocks_triggered(true);
  2374. }
  2375. bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults");
  2376. if (pre23Defaults) {
  2377. bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23");
  2378. if (!resetDockLock23) {
  2379. config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true);
  2380. config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked");
  2381. config_save_safe(App()->GetUserConfig(), "tmp", nullptr);
  2382. }
  2383. }
  2384. bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked");
  2385. on_lockDocks_toggled(docksLocked);
  2386. ui->lockDocks->blockSignals(true);
  2387. ui->lockDocks->setChecked(docksLocked);
  2388. ui->lockDocks->blockSignals(false);
  2389. bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks");
  2390. on_sideDocks_toggled(sideDocks);
  2391. ui->sideDocks->blockSignals(true);
  2392. ui->sideDocks->setChecked(sideDocks);
  2393. ui->sideDocks->blockSignals(false);
  2394. SystemTray(true);
  2395. TaskbarOverlayInit();
  2396. #ifdef __APPLE__
  2397. disableColorSpaceConversion(this);
  2398. #endif
  2399. bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion");
  2400. bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun");
  2401. if (!first_run) {
  2402. config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true);
  2403. config_save_safe(App()->GetUserConfig(), "tmp", nullptr);
  2404. }
  2405. if (!first_run && !has_last_version && !Active())
  2406. QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection);
  2407. #if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0)
  2408. /* Automatically set branch to "beta" the first time a pre-release build is run. */
  2409. if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) {
  2410. config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta");
  2411. config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true);
  2412. config_save_safe(App()->GetAppConfig(), "tmp", nullptr);
  2413. }
  2414. #endif
  2415. TimedCheckForUpdates();
  2416. ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"));
  2417. if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup"))
  2418. on_stats_triggered();
  2419. OBSBasicStats::InitializeValues();
  2420. /* ----------------------- */
  2421. /* Add multiview menu */
  2422. ui->viewMenu->addSeparator();
  2423. AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector);
  2424. connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu);
  2425. ui->sources->UpdateIcons();
  2426. #if !defined(_WIN32)
  2427. delete ui->actionShowCrashLogs;
  2428. delete ui->actionUploadLastCrashLog;
  2429. delete ui->menuCrashLogs;
  2430. delete ui->actionRepair;
  2431. ui->actionShowCrashLogs = nullptr;
  2432. ui->actionUploadLastCrashLog = nullptr;
  2433. ui->menuCrashLogs = nullptr;
  2434. ui->actionRepair = nullptr;
  2435. #if !defined(__APPLE__)
  2436. delete ui->actionCheckForUpdates;
  2437. ui->actionCheckForUpdates = nullptr;
  2438. #endif
  2439. #endif
  2440. #ifdef __APPLE__
  2441. /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */
  2442. delete ui->actionFullscreenInterface;
  2443. ui->actionFullscreenInterface = nullptr;
  2444. #else
  2445. /* Don't show menu to raise macOS-only permissions dialog */
  2446. delete ui->actionShowMacPermissions;
  2447. ui->actionShowMacPermissions = nullptr;
  2448. #endif
  2449. #if defined(_WIN32) || defined(__APPLE__)
  2450. if (App()->IsUpdaterDisabled()) {
  2451. ui->actionCheckForUpdates->setEnabled(false);
  2452. #if defined(_WIN32)
  2453. ui->actionRepair->setEnabled(false);
  2454. #endif
  2455. }
  2456. #endif
  2457. #ifndef WHATSNEW_ENABLED
  2458. delete ui->actionShowWhatsNew;
  2459. ui->actionShowWhatsNew = nullptr;
  2460. #endif
  2461. if (safe_mode) {
  2462. ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal"));
  2463. }
  2464. UpdatePreviewProgramIndicators();
  2465. OnFirstLoad();
  2466. if (!hideWindowOnStart)
  2467. activateWindow();
  2468. /* ------------------------------------------- */
  2469. /* display warning message for failed modules */
  2470. if (mfi.count) {
  2471. QString failed_plugins;
  2472. char **plugin = mfi.failed_modules;
  2473. while (*plugin) {
  2474. failed_plugins += *plugin;
  2475. failed_plugins += "\n";
  2476. plugin++;
  2477. }
  2478. QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins);
  2479. OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg);
  2480. }
  2481. }
  2482. void OBSBasic::OnFirstLoad()
  2483. {
  2484. OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING);
  2485. #ifdef WHATSNEW_ENABLED
  2486. /* Attempt to load init screen if available */
  2487. if (cef) {
  2488. WhatsNewInfoThread *wnit = new WhatsNewInfoThread();
  2489. connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection);
  2490. introCheckThread.reset(wnit);
  2491. introCheckThread->start();
  2492. }
  2493. #endif
  2494. Auth::Load();
  2495. bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup");
  2496. if (showLogViewerOnStartup)
  2497. on_actionViewCurrentLog_triggered();
  2498. }
  2499. /* shows a "what's new" page on startup of new versions using CEF */
  2500. void OBSBasic::ReceivedIntroJson(const QString &text)
  2501. {
  2502. #ifdef WHATSNEW_ENABLED
  2503. if (closing)
  2504. return;
  2505. WhatsNewList items;
  2506. try {
  2507. nlohmann::json json = nlohmann::json::parse(text.toStdString());
  2508. items = json.get<WhatsNewList>();
  2509. } catch (nlohmann::json::exception &e) {
  2510. blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what());
  2511. return;
  2512. }
  2513. std::string info_url;
  2514. int info_increment = -1;
  2515. /* check to see if there's an info page for this version */
  2516. for (const WhatsNewItem &item : items) {
  2517. if (item.os) {
  2518. WhatsNewPlatforms platforms = *item.os;
  2519. #ifdef _WIN32
  2520. if (!platforms.windows)
  2521. continue;
  2522. #elif defined(__APPLE__)
  2523. if (!platforms.macos)
  2524. continue;
  2525. #else
  2526. if (!platforms.linux)
  2527. continue;
  2528. #endif
  2529. }
  2530. int major = 0;
  2531. int minor = 0;
  2532. sscanf(item.version.c_str(), "%d.%d", &major, &minor);
  2533. if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER &&
  2534. item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) {
  2535. info_url = item.url;
  2536. info_increment = item.increment;
  2537. }
  2538. }
  2539. /* this version was not found, or no info for this version */
  2540. if (info_increment == -1) {
  2541. return;
  2542. }
  2543. #if OBS_RELEASE_CANDIDATE > 0
  2544. constexpr const char *lastInfoVersion = "InfoLastRCVersion";
  2545. #elif OBS_BETA > 0
  2546. constexpr const char *lastInfoVersion = "InfoLastBetaVersion";
  2547. #else
  2548. constexpr const char *lastInfoVersion = "InfoLastVersion";
  2549. #endif
  2550. constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL |
  2551. OBS_BETA;
  2552. uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion);
  2553. int current_version_increment = -1;
  2554. if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) {
  2555. config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1);
  2556. config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion);
  2557. } else {
  2558. current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement");
  2559. }
  2560. if (info_increment <= current_version_increment) {
  2561. return;
  2562. }
  2563. config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment);
  2564. config_save_safe(App()->GetAppConfig(), "tmp", nullptr);
  2565. cef->init_browser();
  2566. WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str()));
  2567. connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection);
  2568. whatsNewInitThread.reset(wnbit);
  2569. whatsNewInitThread->start();
  2570. #else
  2571. UNUSED_PARAMETER(text);
  2572. #endif
  2573. }
  2574. void OBSBasic::ShowWhatsNew(const QString &url)
  2575. {
  2576. #ifdef BROWSER_AVAILABLE
  2577. if (closing)
  2578. return;
  2579. if (obsWhatsNew) {
  2580. obsWhatsNew->close();
  2581. }
  2582. obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url));
  2583. #else
  2584. UNUSED_PARAMETER(url);
  2585. #endif
  2586. }
  2587. void OBSBasic::UpdateMultiviewProjectorMenu()
  2588. {
  2589. ui->multiviewProjectorMenu->clear();
  2590. AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector);
  2591. }
  2592. void OBSBasic::InitHotkeys()
  2593. {
  2594. ProfileScope("OBSBasic::InitHotkeys");
  2595. struct obs_hotkeys_translations t = {};
  2596. t.insert = Str("Hotkeys.Insert");
  2597. t.del = Str("Hotkeys.Delete");
  2598. t.home = Str("Hotkeys.Home");
  2599. t.end = Str("Hotkeys.End");
  2600. t.page_up = Str("Hotkeys.PageUp");
  2601. t.page_down = Str("Hotkeys.PageDown");
  2602. t.num_lock = Str("Hotkeys.NumLock");
  2603. t.scroll_lock = Str("Hotkeys.ScrollLock");
  2604. t.caps_lock = Str("Hotkeys.CapsLock");
  2605. t.backspace = Str("Hotkeys.Backspace");
  2606. t.tab = Str("Hotkeys.Tab");
  2607. t.print = Str("Hotkeys.Print");
  2608. t.pause = Str("Hotkeys.Pause");
  2609. t.left = Str("Hotkeys.Left");
  2610. t.right = Str("Hotkeys.Right");
  2611. t.up = Str("Hotkeys.Up");
  2612. t.down = Str("Hotkeys.Down");
  2613. #ifdef _WIN32
  2614. t.meta = Str("Hotkeys.Windows");
  2615. #else
  2616. t.meta = Str("Hotkeys.Super");
  2617. #endif
  2618. t.menu = Str("Hotkeys.Menu");
  2619. t.space = Str("Hotkeys.Space");
  2620. t.numpad_num = Str("Hotkeys.NumpadNum");
  2621. t.numpad_multiply = Str("Hotkeys.NumpadMultiply");
  2622. t.numpad_divide = Str("Hotkeys.NumpadDivide");
  2623. t.numpad_plus = Str("Hotkeys.NumpadAdd");
  2624. t.numpad_minus = Str("Hotkeys.NumpadSubtract");
  2625. t.numpad_decimal = Str("Hotkeys.NumpadDecimal");
  2626. t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum");
  2627. t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply");
  2628. t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide");
  2629. t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd");
  2630. t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract");
  2631. t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal");
  2632. t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual");
  2633. t.mouse_num = Str("Hotkeys.MouseButton");
  2634. t.escape = Str("Hotkeys.Escape");
  2635. obs_hotkeys_set_translations(&t);
  2636. obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"),
  2637. Str("Push-to-talk"));
  2638. obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide"));
  2639. obs_hotkey_enable_callback_rerouting(true);
  2640. obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this);
  2641. }
  2642. void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed)
  2643. {
  2644. obs_hotkey_trigger_routed_callback(id, pressed);
  2645. }
  2646. void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed)
  2647. {
  2648. OBSBasic &basic = *static_cast<OBSBasic *>(data);
  2649. QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed));
  2650. }
  2651. void OBSBasic::CreateHotkeys()
  2652. {
  2653. ProfileScope("OBSBasic::CreateHotkeys");
  2654. auto LoadHotkeyData = [&](const char *name) -> OBSData {
  2655. const char *info = config_get_string(activeConfiguration, "Hotkeys", name);
  2656. if (!info)
  2657. return {};
  2658. OBSDataAutoRelease data = obs_data_create_from_json(info);
  2659. if (!data)
  2660. return {};
  2661. return data.Get();
  2662. };
  2663. auto LoadHotkey = [&](obs_hotkey_id id, const char *name) {
  2664. OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings");
  2665. obs_hotkey_load(id, array);
  2666. };
  2667. auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1,
  2668. const char *oldName = NULL) {
  2669. if (oldName) {
  2670. const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName);
  2671. if (info) {
  2672. config_set_string(activeConfiguration, "Hotkeys", name0, info);
  2673. config_set_string(activeConfiguration, "Hotkeys", name1, info);
  2674. config_remove_value(activeConfiguration, "Hotkeys", oldName);
  2675. activeConfiguration.Save();
  2676. }
  2677. }
  2678. OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings");
  2679. OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings");
  2680. obs_hotkey_pair_load(id, array0, array1);
  2681. };
  2682. #define MAKE_CALLBACK(pred, method, log_action) \
  2683. [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \
  2684. OBSBasic &basic = *static_cast<OBSBasic *>(data); \
  2685. if ((pred) && pressed) { \
  2686. blog(LOG_INFO, log_action " due to hotkey"); \
  2687. method(); \
  2688. return true; \
  2689. } \
  2690. return false; \
  2691. }
  2692. streamingHotkeys = obs_hotkey_pair_register_frontend(
  2693. "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming",
  2694. Str("Basic.Main.StopStreaming"),
  2695. MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming,
  2696. "Starting stream"),
  2697. MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming,
  2698. "Stopping stream"),
  2699. this, this);
  2700. LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming");
  2701. auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
  2702. OBSBasic &basic = *static_cast<OBSBasic *>(data);
  2703. if (basic.outputHandler->StreamingActive() && pressed) {
  2704. basic.ForceStopStreaming();
  2705. }
  2706. };
  2707. forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming",
  2708. Str("Basic.Main.ForceStopStreaming"), cb, this);
  2709. LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming");
  2710. recordingHotkeys = obs_hotkey_pair_register_frontend(
  2711. "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording",
  2712. Str("Basic.Main.StopRecording"),
  2713. MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording,
  2714. "Starting recording"),
  2715. MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording,
  2716. "Stopping recording"),
  2717. this, this);
  2718. LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording");
  2719. pauseHotkeys =
  2720. obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"),
  2721. "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"),
  2722. MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused,
  2723. basic.PauseRecording, "Pausing recording"),
  2724. MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused,
  2725. basic.UnpauseRecording, "Unpausing recording"),
  2726. this, this);
  2727. LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording");
  2728. splitFileHotkey = obs_hotkey_register_frontend(
  2729. "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"),
  2730. [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
  2731. if (pressed)
  2732. obs_frontend_recording_split_file();
  2733. },
  2734. this);
  2735. LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile");
  2736. addChapterHotkey = obs_hotkey_register_frontend(
  2737. "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"),
  2738. [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
  2739. if (pressed)
  2740. obs_frontend_recording_add_chapter(nullptr);
  2741. },
  2742. this);
  2743. LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker");
  2744. replayBufHotkeys =
  2745. obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"),
  2746. "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"),
  2747. MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(),
  2748. basic.StartReplayBuffer, "Starting replay buffer"),
  2749. MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(),
  2750. basic.StopReplayBuffer, "Stopping replay buffer"),
  2751. this, this);
  2752. LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer");
  2753. if (vcamEnabled) {
  2754. vcamHotkeys = obs_hotkey_pair_register_frontend(
  2755. "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam",
  2756. Str("Basic.Main.StopVirtualCam"),
  2757. MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam,
  2758. "Starting virtual camera"),
  2759. MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam,
  2760. "Stopping virtual camera"),
  2761. this, this);
  2762. LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam");
  2763. }
  2764. togglePreviewHotkeys = obs_hotkey_pair_register_frontend(
  2765. "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview",
  2766. Str("Basic.Main.Preview.Disable"),
  2767. MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"),
  2768. MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this);
  2769. LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview");
  2770. togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend(
  2771. "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"),
  2772. "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"),
  2773. MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"),
  2774. MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"),
  2775. this, this);
  2776. LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram",
  2777. "OBSBasic.TogglePreviewProgram");
  2778. contextBarHotkeys = obs_hotkey_pair_register_frontend(
  2779. "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar",
  2780. Str("Basic.Main.HideContextBar"),
  2781. MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"),
  2782. MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"),
  2783. this, this);
  2784. LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar");
  2785. #undef MAKE_CALLBACK
  2786. auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
  2787. if (pressed)
  2788. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "TransitionClicked",
  2789. Qt::QueuedConnection);
  2790. };
  2791. transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this);
  2792. LoadHotkey(transitionHotkey, "OBSBasic.Transition");
  2793. auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
  2794. if (pressed)
  2795. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "ResetStatsHotkey",
  2796. Qt::QueuedConnection);
  2797. };
  2798. statsHotkey =
  2799. obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this);
  2800. LoadHotkey(statsHotkey, "OBSBasic.ResetStats");
  2801. auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
  2802. if (pressed)
  2803. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "Screenshot", Qt::QueuedConnection);
  2804. };
  2805. screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this);
  2806. LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot");
  2807. auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
  2808. if (pressed)
  2809. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "ScreenshotSelectedSource",
  2810. Qt::QueuedConnection);
  2811. };
  2812. sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot",
  2813. Str("Screenshot.SourceHotkey"), screenshotSource, this);
  2814. LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot");
  2815. }
  2816. void OBSBasic::ClearHotkeys()
  2817. {
  2818. obs_hotkey_pair_unregister(streamingHotkeys);
  2819. obs_hotkey_pair_unregister(recordingHotkeys);
  2820. obs_hotkey_pair_unregister(pauseHotkeys);
  2821. obs_hotkey_unregister(splitFileHotkey);
  2822. obs_hotkey_unregister(addChapterHotkey);
  2823. obs_hotkey_pair_unregister(replayBufHotkeys);
  2824. obs_hotkey_pair_unregister(vcamHotkeys);
  2825. obs_hotkey_pair_unregister(togglePreviewHotkeys);
  2826. obs_hotkey_pair_unregister(contextBarHotkeys);
  2827. obs_hotkey_pair_unregister(togglePreviewProgramHotkeys);
  2828. obs_hotkey_unregister(forceStreamingStopHotkey);
  2829. obs_hotkey_unregister(transitionHotkey);
  2830. obs_hotkey_unregister(statsHotkey);
  2831. obs_hotkey_unregister(screenshotHotkey);
  2832. obs_hotkey_unregister(sourceScreenshotHotkey);
  2833. }
  2834. OBSBasic::~OBSBasic()
  2835. {
  2836. /* clear out UI event queue */
  2837. QApplication::sendPostedEvents(nullptr);
  2838. QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
  2839. if (updateCheckThread && updateCheckThread->isRunning())
  2840. updateCheckThread->wait();
  2841. if (patronJsonThread && patronJsonThread->isRunning())
  2842. patronJsonThread->wait();
  2843. delete screenshotData;
  2844. delete previewProjector;
  2845. delete studioProgramProjector;
  2846. delete previewProjectorSource;
  2847. delete previewProjectorMain;
  2848. delete sourceProjector;
  2849. delete sceneProjectorMenu;
  2850. delete scaleFilteringMenu;
  2851. delete blendingModeMenu;
  2852. delete colorMenu;
  2853. delete colorWidgetAction;
  2854. delete colorSelect;
  2855. delete deinterlaceMenu;
  2856. delete perSceneTransitionMenu;
  2857. delete shortcutFilter;
  2858. delete trayMenu;
  2859. delete programOptions;
  2860. delete program;
  2861. /* XXX: any obs data must be released before calling obs_shutdown.
  2862. * currently, we can't automate this with C++ RAII because of the
  2863. * delicate nature of obs_shutdown needing to be freed before the UI
  2864. * can be freed, and we have no control over the destruction order of
  2865. * the Qt UI stuff, so we have to manually clear any references to
  2866. * libobs. */
  2867. delete cpuUsageTimer;
  2868. os_cpu_usage_info_destroy(cpuUsageInfo);
  2869. obs_hotkey_set_callback_routing_func(nullptr, nullptr);
  2870. ClearHotkeys();
  2871. service = nullptr;
  2872. outputHandler.reset();
  2873. delete interaction;
  2874. delete properties;
  2875. delete filters;
  2876. delete transformWindow;
  2877. delete advAudioWindow;
  2878. delete about;
  2879. delete remux;
  2880. obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this);
  2881. obs_enter_graphics();
  2882. gs_vertexbuffer_destroy(box);
  2883. gs_vertexbuffer_destroy(boxLeft);
  2884. gs_vertexbuffer_destroy(boxTop);
  2885. gs_vertexbuffer_destroy(boxRight);
  2886. gs_vertexbuffer_destroy(boxBottom);
  2887. gs_vertexbuffer_destroy(circle);
  2888. gs_vertexbuffer_destroy(actionSafeMargin);
  2889. gs_vertexbuffer_destroy(graphicsSafeMargin);
  2890. gs_vertexbuffer_destroy(fourByThreeSafeMargin);
  2891. gs_vertexbuffer_destroy(leftLine);
  2892. gs_vertexbuffer_destroy(topLine);
  2893. gs_vertexbuffer_destroy(rightLine);
  2894. obs_leave_graphics();
  2895. /* When shutting down, sometimes source references can get in to the
  2896. * event queue, and if we don't forcibly process those events they
  2897. * won't get processed until after obs_shutdown has been called. I
  2898. * really wish there were a more elegant way to deal with this via C++,
  2899. * but Qt doesn't use C++ in a normal way, so you can't really rely on
  2900. * normal C++ behavior for your data to be freed in the order that you
  2901. * expect or want it to. */
  2902. QApplication::sendPostedEvents(nullptr);
  2903. config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER);
  2904. config_save_safe(App()->GetAppConfig(), "tmp", nullptr);
  2905. config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled);
  2906. config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked());
  2907. config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode);
  2908. config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode);
  2909. config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode);
  2910. config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode());
  2911. config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked());
  2912. config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked());
  2913. config_save_safe(App()->GetUserConfig(), "tmp", nullptr);
  2914. #ifdef BROWSER_AVAILABLE
  2915. DestroyPanelCookieManager();
  2916. delete cef;
  2917. cef = nullptr;
  2918. #endif
  2919. }
  2920. void OBSBasic::SaveProjectNow()
  2921. {
  2922. if (disableSaving)
  2923. return;
  2924. projectChanged = true;
  2925. SaveProjectDeferred();
  2926. }
  2927. void OBSBasic::SaveProject()
  2928. {
  2929. if (disableSaving)
  2930. return;
  2931. projectChanged = true;
  2932. QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection);
  2933. }
  2934. void OBSBasic::SaveProjectDeferred()
  2935. {
  2936. if (disableSaving)
  2937. return;
  2938. if (!projectChanged)
  2939. return;
  2940. projectChanged = false;
  2941. try {
  2942. const OBSSceneCollection &currentCollection = GetCurrentSceneCollection();
  2943. Save(currentCollection.collectionFile.u8string().c_str());
  2944. } catch (const std::invalid_argument &error) {
  2945. blog(LOG_ERROR, "%s", error.what());
  2946. }
  2947. }
  2948. OBSSource OBSBasic::GetProgramSource()
  2949. {
  2950. return OBSGetStrongRef(programScene);
  2951. }
  2952. OBSScene OBSBasic::GetCurrentScene()
  2953. {
  2954. return currentScene.load();
  2955. }
  2956. OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item)
  2957. {
  2958. return item ? GetOBSRef<OBSSceneItem>(item) : nullptr;
  2959. }
  2960. OBSSceneItem OBSBasic::GetCurrentSceneItem()
  2961. {
  2962. return ui->sources->Get(GetTopSelectedSourceItem());
  2963. }
  2964. void OBSBasic::UpdatePreviewScalingMenu()
  2965. {
  2966. bool fixedScaling = ui->preview->IsFixedScaling();
  2967. float scalingAmount = ui->preview->GetScalingAmount();
  2968. if (!fixedScaling) {
  2969. ui->actionScaleWindow->setChecked(true);
  2970. ui->actionScaleCanvas->setChecked(false);
  2971. ui->actionScaleOutput->setChecked(false);
  2972. return;
  2973. }
  2974. obs_video_info ovi;
  2975. obs_get_video_info(&ovi);
  2976. ui->actionScaleWindow->setChecked(false);
  2977. ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f);
  2978. ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width));
  2979. }
  2980. void OBSBasic::CreateInteractionWindow(obs_source_t *source)
  2981. {
  2982. bool closed = true;
  2983. if (interaction)
  2984. closed = interaction->close();
  2985. if (!closed)
  2986. return;
  2987. interaction = new OBSBasicInteraction(this, source);
  2988. interaction->Init();
  2989. interaction->setAttribute(Qt::WA_DeleteOnClose, true);
  2990. }
  2991. void OBSBasic::CreatePropertiesWindow(obs_source_t *source)
  2992. {
  2993. bool closed = true;
  2994. if (properties)
  2995. closed = properties->close();
  2996. if (!closed)
  2997. return;
  2998. properties = new OBSBasicProperties(this, source);
  2999. properties->Init();
  3000. properties->setAttribute(Qt::WA_DeleteOnClose, true);
  3001. }
  3002. void OBSBasic::CreateFiltersWindow(obs_source_t *source)
  3003. {
  3004. bool closed = true;
  3005. if (filters)
  3006. closed = filters->close();
  3007. if (!closed)
  3008. return;
  3009. filters = new OBSBasicFilters(this, source);
  3010. filters->Init();
  3011. filters->setAttribute(Qt::WA_DeleteOnClose, true);
  3012. }
  3013. /* Qt callbacks for invokeMethod */
  3014. void OBSBasic::AddScene(OBSSource source)
  3015. {
  3016. const char *name = obs_source_get_name(source);
  3017. obs_scene_t *scene = obs_scene_from_source(source);
  3018. QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name));
  3019. SetOBSRef(item, OBSScene(scene));
  3020. ui->scenes->insertItem(ui->scenes->currentRow() + 1, item);
  3021. obs_hotkey_register_source(
  3022. source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"),
  3023. [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
  3024. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  3025. auto potential_source = static_cast<obs_source_t *>(data);
  3026. OBSSourceAutoRelease source = obs_source_get_ref(potential_source);
  3027. if (source && pressed)
  3028. main->SetCurrentScene(source.Get());
  3029. },
  3030. static_cast<obs_source_t *>(source));
  3031. signal_handler_t *handler = obs_source_get_signal_handler(source);
  3032. SignalContainer<OBSScene> container;
  3033. container.ref = scene;
  3034. container.handlers.assign({
  3035. std::make_shared<OBSSignal>(handler, "item_add", OBSBasic::SceneItemAdded, this),
  3036. std::make_shared<OBSSignal>(handler, "reorder", OBSBasic::SceneReordered, this),
  3037. std::make_shared<OBSSignal>(handler, "refresh", OBSBasic::SceneRefreshed, this),
  3038. });
  3039. item->setData(static_cast<int>(QtDataRole::OBSSignals), QVariant::fromValue(container));
  3040. /* if the scene already has items (a duplicated scene) add them */
  3041. auto addSceneItem = [this](obs_sceneitem_t *item) {
  3042. AddSceneItem(item);
  3043. };
  3044. using addSceneItem_t = decltype(addSceneItem);
  3045. obs_scene_enum_items(
  3046. scene,
  3047. [](obs_scene_t *, obs_sceneitem_t *item, void *param) {
  3048. addSceneItem_t *func;
  3049. func = reinterpret_cast<addSceneItem_t *>(param);
  3050. (*func)(item);
  3051. return true;
  3052. },
  3053. &addSceneItem);
  3054. SaveProject();
  3055. if (!disableSaving) {
  3056. obs_source_t *source = obs_scene_get_source(scene);
  3057. blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source));
  3058. OBSProjector::UpdateMultiviewProjectors();
  3059. }
  3060. OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
  3061. }
  3062. void OBSBasic::RemoveScene(OBSSource source)
  3063. {
  3064. obs_scene_t *scene = obs_scene_from_source(source);
  3065. QListWidgetItem *sel = nullptr;
  3066. int count = ui->scenes->count();
  3067. for (int i = 0; i < count; i++) {
  3068. auto item = ui->scenes->item(i);
  3069. auto cur_scene = GetOBSRef<OBSScene>(item);
  3070. if (cur_scene != scene)
  3071. continue;
  3072. sel = item;
  3073. break;
  3074. }
  3075. if (sel != nullptr) {
  3076. if (sel == ui->scenes->currentItem())
  3077. ui->sources->Clear();
  3078. delete sel;
  3079. }
  3080. SaveProject();
  3081. if (!disableSaving) {
  3082. blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source));
  3083. OBSProjector::UpdateMultiviewProjectors();
  3084. }
  3085. OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
  3086. }
  3087. static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  3088. {
  3089. obs_sceneitem_t *selectedItem = reinterpret_cast<obs_sceneitem_t *>(param);
  3090. if (obs_sceneitem_is_group(item))
  3091. obs_sceneitem_group_enum_items(item, select_one, param);
  3092. obs_sceneitem_select(item, (selectedItem == item));
  3093. return true;
  3094. }
  3095. void OBSBasic::AddSceneItem(OBSSceneItem item)
  3096. {
  3097. obs_scene_t *scene = obs_sceneitem_get_scene(item);
  3098. if (GetCurrentScene() == scene)
  3099. ui->sources->Add(item);
  3100. SaveProject();
  3101. if (!disableSaving) {
  3102. obs_source_t *sceneSource = obs_scene_get_source(scene);
  3103. obs_source_t *itemSource = obs_sceneitem_get_source(item);
  3104. blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource),
  3105. obs_source_get_id(itemSource), obs_source_get_name(sceneSource));
  3106. obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item);
  3107. }
  3108. }
  3109. static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName)
  3110. {
  3111. QList<QListWidgetItem *> items = listWidget->findItems(prevName, Qt::MatchExactly);
  3112. for (int i = 0; i < items.count(); i++)
  3113. items[i]->setText(newName);
  3114. }
  3115. void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName)
  3116. {
  3117. RenameListValues(ui->scenes, newName, prevName);
  3118. if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source))
  3119. vcamConfig.source = newName.toStdString();
  3120. if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene))
  3121. vcamConfig.scene = newName.toStdString();
  3122. SaveProject();
  3123. obs_scene_t *scene = obs_scene_from_source(source);
  3124. if (scene)
  3125. OBSProjector::UpdateMultiviewProjectors();
  3126. UpdateContextBar();
  3127. UpdatePreviewProgramIndicators();
  3128. }
  3129. void OBSBasic::ClearContextBar()
  3130. {
  3131. QLayoutItem *la = ui->emptySpace->layout()->itemAt(0);
  3132. if (la) {
  3133. delete la->widget();
  3134. ui->emptySpace->layout()->removeItem(la);
  3135. }
  3136. }
  3137. void OBSBasic::UpdateContextBarVisibility()
  3138. {
  3139. int width = ui->centralwidget->size().width();
  3140. ContextBarSize contextBarSizeNew;
  3141. if (width >= 740) {
  3142. contextBarSizeNew = ContextBarSize_Normal;
  3143. } else if (width >= 600) {
  3144. contextBarSizeNew = ContextBarSize_Reduced;
  3145. } else {
  3146. contextBarSizeNew = ContextBarSize_Minimized;
  3147. }
  3148. if (contextBarSize == contextBarSizeNew)
  3149. return;
  3150. contextBarSize = contextBarSizeNew;
  3151. UpdateContextBarDeferred();
  3152. }
  3153. static bool is_network_media_source(obs_source_t *source, const char *id)
  3154. {
  3155. if (strcmp(id, "ffmpeg_source") != 0)
  3156. return false;
  3157. OBSDataAutoRelease s = obs_source_get_settings(source);
  3158. bool is_local_file = obs_data_get_bool(s, "is_local_file");
  3159. return !is_local_file;
  3160. }
  3161. void OBSBasic::UpdateContextBarDeferred(bool force)
  3162. {
  3163. QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force));
  3164. }
  3165. void OBSBasic::SourceToolBarActionsSetEnabled()
  3166. {
  3167. bool enable = false;
  3168. bool disableProps = false;
  3169. OBSSceneItem item = GetCurrentSceneItem();
  3170. if (item) {
  3171. OBSSource source = obs_sceneitem_get_source(item);
  3172. disableProps = !obs_source_configurable(source);
  3173. enable = true;
  3174. }
  3175. if (disableProps)
  3176. ui->actionSourceProperties->setEnabled(false);
  3177. else
  3178. ui->actionSourceProperties->setEnabled(enable);
  3179. ui->actionRemoveSource->setEnabled(enable);
  3180. ui->actionSourceUp->setEnabled(enable);
  3181. ui->actionSourceDown->setEnabled(enable);
  3182. RefreshToolBarStyling(ui->sourcesToolbar);
  3183. }
  3184. void OBSBasic::UpdateContextBar(bool force)
  3185. {
  3186. SourceToolBarActionsSetEnabled();
  3187. if (!ui->contextContainer->isVisible() && !force)
  3188. return;
  3189. OBSSceneItem item = GetCurrentSceneItem();
  3190. if (item) {
  3191. obs_source_t *source = obs_sceneitem_get_source(item);
  3192. bool updateNeeded = true;
  3193. QLayoutItem *la = ui->emptySpace->layout()->itemAt(0);
  3194. if (la) {
  3195. if (SourceToolbar *toolbar = dynamic_cast<SourceToolbar *>(la->widget())) {
  3196. if (toolbar->GetSource() == source)
  3197. updateNeeded = false;
  3198. } else if (MediaControls *toolbar = dynamic_cast<MediaControls *>(la->widget())) {
  3199. if (toolbar->GetSource() == source)
  3200. updateNeeded = false;
  3201. }
  3202. }
  3203. const char *id = obs_source_get_unversioned_id(source);
  3204. uint32_t flags = obs_source_get_output_flags(source);
  3205. ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION);
  3206. if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) {
  3207. ClearContextBar();
  3208. if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) {
  3209. if (!is_network_media_source(source, id)) {
  3210. MediaControls *mediaControls = new MediaControls(ui->emptySpace);
  3211. mediaControls->SetSource(source);
  3212. ui->emptySpace->layout()->addWidget(mediaControls);
  3213. }
  3214. } else if (strcmp(id, "browser_source") == 0) {
  3215. BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source);
  3216. ui->emptySpace->layout()->addWidget(c);
  3217. } else if (strcmp(id, "wasapi_input_capture") == 0 ||
  3218. strcmp(id, "wasapi_output_capture") == 0 ||
  3219. strcmp(id, "coreaudio_input_capture") == 0 ||
  3220. strcmp(id, "coreaudio_output_capture") == 0 ||
  3221. strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 ||
  3222. strcmp(id, "alsa_input_capture") == 0) {
  3223. AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source);
  3224. c->Init();
  3225. ui->emptySpace->layout()->addWidget(c);
  3226. } else if (strcmp(id, "wasapi_process_output_capture") == 0) {
  3227. ApplicationAudioCaptureToolbar *c =
  3228. new ApplicationAudioCaptureToolbar(ui->emptySpace, source);
  3229. c->Init();
  3230. ui->emptySpace->layout()->addWidget(c);
  3231. } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) {
  3232. WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source);
  3233. c->Init();
  3234. ui->emptySpace->layout()->addWidget(c);
  3235. } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 ||
  3236. strcmp(id, "xshm_input") == 0) {
  3237. DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source);
  3238. c->Init();
  3239. ui->emptySpace->layout()->addWidget(c);
  3240. } else if (strcmp(id, "dshow_input") == 0) {
  3241. DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source);
  3242. ui->emptySpace->layout()->addWidget(c);
  3243. } else if (strcmp(id, "game_capture") == 0) {
  3244. GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source);
  3245. ui->emptySpace->layout()->addWidget(c);
  3246. } else if (strcmp(id, "image_source") == 0) {
  3247. ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source);
  3248. ui->emptySpace->layout()->addWidget(c);
  3249. } else if (strcmp(id, "color_source") == 0) {
  3250. ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source);
  3251. ui->emptySpace->layout()->addWidget(c);
  3252. } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) {
  3253. TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source);
  3254. ui->emptySpace->layout()->addWidget(c);
  3255. }
  3256. } else if (contextBarSize == ContextBarSize_Minimized) {
  3257. ClearContextBar();
  3258. }
  3259. QIcon icon;
  3260. if (strcmp(id, "scene") == 0)
  3261. icon = GetSceneIcon();
  3262. else if (strcmp(id, "group") == 0)
  3263. icon = GetGroupIcon();
  3264. else
  3265. icon = GetSourceIcon(id);
  3266. QPixmap pixmap = icon.pixmap(QSize(16, 16));
  3267. ui->contextSourceIcon->setPixmap(pixmap);
  3268. ui->contextSourceIconSpacer->hide();
  3269. ui->contextSourceIcon->show();
  3270. const char *name = obs_source_get_name(source);
  3271. ui->contextSourceLabel->setText(name);
  3272. ui->sourceFiltersButton->setEnabled(true);
  3273. ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source));
  3274. } else {
  3275. ClearContextBar();
  3276. ui->contextSourceIcon->hide();
  3277. ui->contextSourceIconSpacer->show();
  3278. ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource"));
  3279. ui->sourceFiltersButton->setEnabled(false);
  3280. ui->sourcePropertiesButton->setEnabled(false);
  3281. ui->sourceInteractButton->setVisible(false);
  3282. }
  3283. if (contextBarSize == ContextBarSize_Normal) {
  3284. ui->sourcePropertiesButton->setText(QTStr("Properties"));
  3285. ui->sourceFiltersButton->setText(QTStr("Filters"));
  3286. ui->sourceInteractButton->setText(QTStr("Interact"));
  3287. } else {
  3288. ui->sourcePropertiesButton->setText("");
  3289. ui->sourceFiltersButton->setText("");
  3290. ui->sourceInteractButton->setText("");
  3291. }
  3292. }
  3293. static inline bool SourceMixerHidden(obs_source_t *source)
  3294. {
  3295. OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source);
  3296. bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden");
  3297. return hidden;
  3298. }
  3299. static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden)
  3300. {
  3301. OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source);
  3302. obs_data_set_bool(priv_settings, "mixer_hidden", hidden);
  3303. }
  3304. void OBSBasic::GetAudioSourceFilters()
  3305. {
  3306. QAction *action = reinterpret_cast<QAction *>(sender());
  3307. VolControl *vol = action->property("volControl").value<VolControl *>();
  3308. obs_source_t *source = vol->GetSource();
  3309. CreateFiltersWindow(source);
  3310. }
  3311. void OBSBasic::GetAudioSourceProperties()
  3312. {
  3313. QAction *action = reinterpret_cast<QAction *>(sender());
  3314. VolControl *vol = action->property("volControl").value<VolControl *>();
  3315. obs_source_t *source = vol->GetSource();
  3316. CreatePropertiesWindow(source);
  3317. }
  3318. void OBSBasic::HideAudioControl()
  3319. {
  3320. QAction *action = reinterpret_cast<QAction *>(sender());
  3321. VolControl *vol = action->property("volControl").value<VolControl *>();
  3322. obs_source_t *source = vol->GetSource();
  3323. if (!SourceMixerHidden(source)) {
  3324. SetSourceMixerHidden(source, true);
  3325. DeactivateAudioSource(source);
  3326. }
  3327. }
  3328. void OBSBasic::UnhideAllAudioControls()
  3329. {
  3330. auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */
  3331. {
  3332. if (!obs_source_active(source))
  3333. return true;
  3334. if (!SourceMixerHidden(source))
  3335. return true;
  3336. SetSourceMixerHidden(source, false);
  3337. ActivateAudioSource(source);
  3338. return true;
  3339. };
  3340. using UnhideAudioMixer_t = decltype(UnhideAudioMixer);
  3341. auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */
  3342. {
  3343. return (*reinterpret_cast<UnhideAudioMixer_t *>(data))(source);
  3344. };
  3345. obs_enum_sources(PreEnum, &UnhideAudioMixer);
  3346. }
  3347. void OBSBasic::ToggleHideMixer()
  3348. {
  3349. OBSSceneItem item = GetCurrentSceneItem();
  3350. OBSSource source = obs_sceneitem_get_source(item);
  3351. if (!SourceMixerHidden(source)) {
  3352. SetSourceMixerHidden(source, true);
  3353. DeactivateAudioSource(source);
  3354. } else {
  3355. SetSourceMixerHidden(source, false);
  3356. ActivateAudioSource(source);
  3357. }
  3358. }
  3359. void OBSBasic::MixerRenameSource()
  3360. {
  3361. QAction *action = reinterpret_cast<QAction *>(sender());
  3362. VolControl *vol = action->property("volControl").value<VolControl *>();
  3363. OBSSource source = vol->GetSource();
  3364. const char *prevName = obs_source_get_name(source);
  3365. for (;;) {
  3366. string name;
  3367. bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"),
  3368. QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName));
  3369. if (!accepted)
  3370. return;
  3371. if (name.empty()) {
  3372. OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  3373. continue;
  3374. }
  3375. OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str());
  3376. if (sourceTest) {
  3377. OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  3378. continue;
  3379. }
  3380. obs_source_set_name(source, name.c_str());
  3381. break;
  3382. }
  3383. }
  3384. static inline bool SourceVolumeLocked(obs_source_t *source)
  3385. {
  3386. OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source);
  3387. bool lock = obs_data_get_bool(priv_settings, "volume_locked");
  3388. return lock;
  3389. }
  3390. void OBSBasic::LockVolumeControl(bool lock)
  3391. {
  3392. QAction *action = reinterpret_cast<QAction *>(sender());
  3393. VolControl *vol = action->property("volControl").value<VolControl *>();
  3394. obs_source_t *source = vol->GetSource();
  3395. OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source);
  3396. obs_data_set_bool(priv_settings, "volume_locked", lock);
  3397. vol->EnableSlider(!lock);
  3398. }
  3399. void OBSBasic::VolControlContextMenu()
  3400. {
  3401. VolControl *vol = reinterpret_cast<VolControl *>(sender());
  3402. /* ------------------- */
  3403. QAction lockAction(QTStr("LockVolume"), this);
  3404. lockAction.setCheckable(true);
  3405. lockAction.setChecked(SourceVolumeLocked(vol->GetSource()));
  3406. QAction hideAction(QTStr("Hide"), this);
  3407. QAction unhideAllAction(QTStr("UnhideAll"), this);
  3408. QAction mixerRenameAction(QTStr("Rename"), this);
  3409. QAction copyFiltersAction(QTStr("Copy.Filters"), this);
  3410. QAction pasteFiltersAction(QTStr("Paste.Filters"), this);
  3411. QAction filtersAction(QTStr("Filters"), this);
  3412. QAction propertiesAction(QTStr("Properties"), this);
  3413. QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this);
  3414. QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this);
  3415. toggleControlLayoutAction.setCheckable(true);
  3416. toggleControlLayoutAction.setChecked(
  3417. config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"));
  3418. /* ------------------- */
  3419. connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection);
  3420. connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection);
  3421. connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection);
  3422. connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection);
  3423. connect(&copyFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection);
  3424. connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters,
  3425. Qt::DirectConnection);
  3426. connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection);
  3427. connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties,
  3428. Qt::DirectConnection);
  3429. connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered,
  3430. Qt::DirectConnection);
  3431. /* ------------------- */
  3432. connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout,
  3433. Qt::DirectConnection);
  3434. /* ------------------- */
  3435. hideAction.setProperty("volControl", QVariant::fromValue<VolControl *>(vol));
  3436. lockAction.setProperty("volControl", QVariant::fromValue<VolControl *>(vol));
  3437. mixerRenameAction.setProperty("volControl", QVariant::fromValue<VolControl *>(vol));
  3438. copyFiltersAction.setProperty("volControl", QVariant::fromValue<VolControl *>(vol));
  3439. pasteFiltersAction.setProperty("volControl", QVariant::fromValue<VolControl *>(vol));
  3440. filtersAction.setProperty("volControl", QVariant::fromValue<VolControl *>(vol));
  3441. propertiesAction.setProperty("volControl", QVariant::fromValue<VolControl *>(vol));
  3442. /* ------------------- */
  3443. copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0);
  3444. pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource));
  3445. QMenu popup;
  3446. vol->SetContextMenu(&popup);
  3447. popup.addAction(&lockAction);
  3448. popup.addSeparator();
  3449. popup.addAction(&unhideAllAction);
  3450. popup.addAction(&hideAction);
  3451. popup.addAction(&mixerRenameAction);
  3452. popup.addSeparator();
  3453. popup.addAction(&copyFiltersAction);
  3454. popup.addAction(&pasteFiltersAction);
  3455. popup.addSeparator();
  3456. popup.addAction(&toggleControlLayoutAction);
  3457. popup.addSeparator();
  3458. popup.addAction(&filtersAction);
  3459. popup.addAction(&propertiesAction);
  3460. popup.addAction(&advPropAction);
  3461. // toggleControlLayoutAction deletes and re-creates the volume controls
  3462. // meaning that "vol" would be pointing to freed memory.
  3463. if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction)
  3464. vol->SetContextMenu(nullptr);
  3465. }
  3466. void OBSBasic::on_hMixerScrollArea_customContextMenuRequested()
  3467. {
  3468. StackedMixerAreaContextMenuRequested();
  3469. }
  3470. void OBSBasic::on_vMixerScrollArea_customContextMenuRequested()
  3471. {
  3472. StackedMixerAreaContextMenuRequested();
  3473. }
  3474. void OBSBasic::StackedMixerAreaContextMenuRequested()
  3475. {
  3476. QAction unhideAllAction(QTStr("UnhideAll"), this);
  3477. QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this);
  3478. QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this);
  3479. toggleControlLayoutAction.setCheckable(true);
  3480. toggleControlLayoutAction.setChecked(
  3481. config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"));
  3482. /* ------------------- */
  3483. connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection);
  3484. connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered,
  3485. Qt::DirectConnection);
  3486. /* ------------------- */
  3487. connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout,
  3488. Qt::DirectConnection);
  3489. /* ------------------- */
  3490. QMenu popup;
  3491. popup.addAction(&unhideAllAction);
  3492. popup.addSeparator();
  3493. popup.addAction(&toggleControlLayoutAction);
  3494. popup.addSeparator();
  3495. popup.addAction(&advPropAction);
  3496. popup.exec(QCursor::pos());
  3497. }
  3498. void OBSBasic::ToggleMixerLayout(bool vertical)
  3499. {
  3500. if (vertical) {
  3501. ui->stackedMixerArea->setMinimumSize(180, 220);
  3502. ui->stackedMixerArea->setCurrentIndex(1);
  3503. } else {
  3504. ui->stackedMixerArea->setMinimumSize(220, 0);
  3505. ui->stackedMixerArea->setCurrentIndex(0);
  3506. }
  3507. }
  3508. void OBSBasic::ToggleVolControlLayout()
  3509. {
  3510. bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl");
  3511. config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical);
  3512. ToggleMixerLayout(vertical);
  3513. // We need to store it so we can delete current and then add
  3514. // at the right order
  3515. vector<OBSSource> sources;
  3516. for (size_t i = 0; i != volumes.size(); i++)
  3517. sources.emplace_back(volumes[i]->GetSource());
  3518. ClearVolumeControls();
  3519. for (const auto &source : sources)
  3520. ActivateAudioSource(source);
  3521. }
  3522. void OBSBasic::ActivateAudioSource(OBSSource source)
  3523. {
  3524. if (SourceMixerHidden(source))
  3525. return;
  3526. if (!obs_source_active(source))
  3527. return;
  3528. if (!obs_source_audio_active(source))
  3529. return;
  3530. bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl");
  3531. VolControl *vol = new VolControl(source, true, vertical);
  3532. vol->EnableSlider(!SourceVolumeLocked(source));
  3533. double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate");
  3534. vol->SetMeterDecayRate(meterDecayRate);
  3535. uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType");
  3536. enum obs_peak_meter_type peakMeterType;
  3537. switch (peakMeterTypeIdx) {
  3538. case 0:
  3539. peakMeterType = SAMPLE_PEAK_METER;
  3540. break;
  3541. case 1:
  3542. peakMeterType = TRUE_PEAK_METER;
  3543. break;
  3544. default:
  3545. peakMeterType = SAMPLE_PEAK_METER;
  3546. break;
  3547. }
  3548. vol->setPeakMeterType(peakMeterType);
  3549. vol->setContextMenuPolicy(Qt::CustomContextMenu);
  3550. connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu);
  3551. connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu);
  3552. InsertQObjectByName(volumes, vol);
  3553. for (auto volume : volumes) {
  3554. if (vertical)
  3555. ui->vVolControlLayout->addWidget(volume);
  3556. else
  3557. ui->hVolControlLayout->addWidget(volume);
  3558. }
  3559. }
  3560. void OBSBasic::DeactivateAudioSource(OBSSource source)
  3561. {
  3562. for (size_t i = 0; i < volumes.size(); i++) {
  3563. if (volumes[i]->GetSource() == source) {
  3564. delete volumes[i];
  3565. volumes.erase(volumes.begin() + i);
  3566. break;
  3567. }
  3568. }
  3569. }
  3570. bool OBSBasic::QueryRemoveSource(obs_source_t *source)
  3571. {
  3572. if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) {
  3573. int count = ui->scenes->count();
  3574. if (count == 1) {
  3575. OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text"));
  3576. return false;
  3577. }
  3578. }
  3579. const char *name = obs_source_get_name(source);
  3580. QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name));
  3581. QMessageBox remove_source(this);
  3582. remove_source.setText(text);
  3583. QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole);
  3584. remove_source.setDefaultButton(Yes);
  3585. remove_source.addButton(QTStr("No"), QMessageBox::NoRole);
  3586. remove_source.setIcon(QMessageBox::Question);
  3587. remove_source.setWindowTitle(QTStr("ConfirmRemove.Title"));
  3588. remove_source.exec();
  3589. return Yes == remove_source.clickedButton();
  3590. }
  3591. #define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */
  3592. void OBSBasic::TimedCheckForUpdates()
  3593. {
  3594. if (App()->IsUpdaterDisabled())
  3595. return;
  3596. if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates"))
  3597. return;
  3598. #if defined(ENABLE_SPARKLE_UPDATER)
  3599. CheckForUpdates(false);
  3600. #elif _WIN32
  3601. long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck");
  3602. uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion");
  3603. if (lastVersion < LIBOBS_API_VER) {
  3604. lastUpdate = 0;
  3605. config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0);
  3606. }
  3607. long long t = (long long)time(nullptr);
  3608. long long secs = t - lastUpdate;
  3609. if (secs > UPDATE_CHECK_INTERVAL)
  3610. CheckForUpdates(false);
  3611. #endif
  3612. }
  3613. void OBSBasic::CheckForUpdates(bool manualUpdate)
  3614. {
  3615. #if _WIN32
  3616. ui->actionCheckForUpdates->setEnabled(false);
  3617. ui->actionRepair->setEnabled(false);
  3618. if (updateCheckThread && updateCheckThread->isRunning())
  3619. return;
  3620. updateCheckThread.reset(new AutoUpdateThread(manualUpdate));
  3621. updateCheckThread->start();
  3622. #elif defined(ENABLE_SPARKLE_UPDATER)
  3623. ui->actionCheckForUpdates->setEnabled(false);
  3624. if (updateCheckThread && updateCheckThread->isRunning())
  3625. return;
  3626. MacUpdateThread *mut = new MacUpdateThread(manualUpdate);
  3627. connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection);
  3628. updateCheckThread.reset(mut);
  3629. updateCheckThread->start();
  3630. #else
  3631. UNUSED_PARAMETER(manualUpdate);
  3632. #endif
  3633. }
  3634. void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate)
  3635. {
  3636. #ifdef ENABLE_SPARKLE_UPDATER
  3637. static OBSSparkle *updater;
  3638. if (!updater) {
  3639. updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates);
  3640. return;
  3641. }
  3642. updater->setBranch(QT_TO_UTF8(branch));
  3643. updater->checkForUpdates(manualUpdate);
  3644. #else
  3645. UNUSED_PARAMETER(branch);
  3646. UNUSED_PARAMETER(manualUpdate);
  3647. #endif
  3648. }
  3649. void OBSBasic::updateCheckFinished()
  3650. {
  3651. ui->actionCheckForUpdates->setEnabled(true);
  3652. ui->actionRepair->setEnabled(true);
  3653. }
  3654. void OBSBasic::DuplicateSelectedScene()
  3655. {
  3656. OBSScene curScene = GetCurrentScene();
  3657. if (!curScene)
  3658. return;
  3659. OBSSource curSceneSource = obs_scene_get_source(curScene);
  3660. QString format{obs_source_get_name(curSceneSource)};
  3661. format += " %1";
  3662. int i = 2;
  3663. QString placeHolderText = format.arg(i);
  3664. OBSSourceAutoRelease source = nullptr;
  3665. while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
  3666. placeHolderText = format.arg(++i);
  3667. }
  3668. for (;;) {
  3669. string name;
  3670. bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"),
  3671. QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText);
  3672. if (!accepted)
  3673. return;
  3674. if (name.empty()) {
  3675. OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  3676. continue;
  3677. }
  3678. obs_source_t *source = obs_get_source_by_name(name.c_str());
  3679. if (source) {
  3680. OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  3681. obs_source_release(source);
  3682. continue;
  3683. }
  3684. OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS);
  3685. source = obs_scene_get_source(scene);
  3686. SetCurrentScene(source, true);
  3687. auto undo = [](const std::string &data) {
  3688. OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str());
  3689. obs_source_remove(source);
  3690. };
  3691. auto redo = [this, name](const std::string &data) {
  3692. OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str());
  3693. obs_scene_t *scene = obs_scene_from_source(source);
  3694. scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS);
  3695. source = obs_scene_get_source(scene);
  3696. SetCurrentScene(source.Get(), true);
  3697. };
  3698. undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo,
  3699. obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene)));
  3700. break;
  3701. }
  3702. }
  3703. static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p)
  3704. {
  3705. obs_source_t *source = obs_sceneitem_get_source(item);
  3706. if (obs_obj_is_private(source) && !obs_source_removed(source))
  3707. return true;
  3708. obs_data_array_t *array = (obs_data_array_t *)p;
  3709. /* check if the source is already stored in the array */
  3710. const char *name = obs_source_get_name(source);
  3711. const size_t count = obs_data_array_count(array);
  3712. for (size_t i = 0; i < count; i++) {
  3713. OBSDataAutoRelease sourceData = obs_data_array_item(array, i);
  3714. if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0)
  3715. return true;
  3716. }
  3717. if (obs_source_is_group(source))
  3718. obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p);
  3719. OBSDataAutoRelease source_data = obs_save_source(source);
  3720. obs_data_array_push_back(array, source_data);
  3721. return true;
  3722. }
  3723. static inline void RemoveSceneAndReleaseNested(obs_source_t *source)
  3724. {
  3725. obs_source_remove(source);
  3726. auto cb = [](void *, obs_source_t *source) {
  3727. if (strcmp(obs_source_get_id(source), "scene") == 0)
  3728. obs_scene_prune_sources(obs_scene_from_source(source));
  3729. return true;
  3730. };
  3731. obs_enum_scenes(cb, NULL);
  3732. }
  3733. void OBSBasic::RemoveSelectedScene()
  3734. {
  3735. OBSScene scene = GetCurrentScene();
  3736. obs_source_t *source = obs_scene_get_source(scene);
  3737. if (!source || !QueryRemoveSource(source)) {
  3738. return;
  3739. }
  3740. /* ------------------------------ */
  3741. /* save all sources in scene */
  3742. OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create();
  3743. obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene);
  3744. OBSDataAutoRelease scene_data = obs_save_source(source);
  3745. obs_data_array_push_back(sources_in_deleted_scene, scene_data);
  3746. /* ----------------------------------------------- */
  3747. /* save all scenes and groups the scene is used in */
  3748. OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create();
  3749. struct other_scenes_cb_data {
  3750. obs_source_t *oldScene;
  3751. obs_data_array_t *scene_used_in_other_scenes;
  3752. } other_scenes_cb_data;
  3753. other_scenes_cb_data.oldScene = source;
  3754. other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes;
  3755. auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) {
  3756. struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr;
  3757. if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0)
  3758. return true;
  3759. obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene),
  3760. obs_source_get_name(data->oldScene));
  3761. if (item) {
  3762. OBSDataAutoRelease scene_data =
  3763. obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item)));
  3764. obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data);
  3765. }
  3766. return true;
  3767. };
  3768. obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data);
  3769. /* --------------------------- */
  3770. /* undo/redo */
  3771. auto undo = [this](const std::string &json) {
  3772. OBSDataAutoRelease base = obs_data_create_from_json(json.c_str());
  3773. OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene");
  3774. OBSDataArrayAutoRelease scene_used_in_other_scenes =
  3775. obs_data_get_array(base, "scene_used_in_other_scenes");
  3776. int savedIndex = (int)obs_data_get_int(base, "index");
  3777. std::vector<OBSSource> sources;
  3778. /* create missing sources */
  3779. size_t count = obs_data_array_count(sources_in_deleted_scene);
  3780. sources.reserve(count);
  3781. for (size_t i = 0; i < count; i++) {
  3782. OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i);
  3783. const char *name = obs_data_get_string(data, "name");
  3784. OBSSourceAutoRelease source = obs_get_source_by_name(name);
  3785. if (!source) {
  3786. source = obs_load_source(data);
  3787. sources.push_back(source.Get());
  3788. }
  3789. }
  3790. /* actually load sources now */
  3791. for (obs_source_t *source : sources)
  3792. obs_source_load2(source);
  3793. /* Add scene to scenes and groups it was nested in */
  3794. for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) {
  3795. OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i);
  3796. const char *name = obs_data_get_string(data, "name");
  3797. OBSSourceAutoRelease source = obs_get_source_by_name(name);
  3798. OBSDataAutoRelease settings = obs_data_get_obj(data, "settings");
  3799. OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items");
  3800. /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */
  3801. std::vector<OBSSource> existing_sources;
  3802. auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) {
  3803. std::vector<OBSSource> *existing = (std::vector<OBSSource> *)data;
  3804. OBSSource source = obs_sceneitem_get_source(item);
  3805. obs_sceneitem_remove(item);
  3806. existing->push_back(source);
  3807. return true;
  3808. };
  3809. obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources);
  3810. /* Re-add sources to the scene */
  3811. obs_sceneitems_add(obs_group_or_scene_from_source(source), items);
  3812. }
  3813. obs_source_t *scene_source = sources.back();
  3814. OBSScene scene = obs_scene_from_source(scene_source);
  3815. SetCurrentScene(scene, true);
  3816. /* set original index in list box */
  3817. ui->scenes->blockSignals(true);
  3818. int curIndex = ui->scenes->currentRow();
  3819. QListWidgetItem *item = ui->scenes->takeItem(curIndex);
  3820. ui->scenes->insertItem(savedIndex, item);
  3821. ui->scenes->setCurrentRow(savedIndex);
  3822. currentScene = scene.Get();
  3823. ui->scenes->blockSignals(false);
  3824. };
  3825. auto redo = [](const std::string &name) {
  3826. OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str());
  3827. RemoveSceneAndReleaseNested(source);
  3828. };
  3829. OBSDataAutoRelease data = obs_data_create();
  3830. obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene);
  3831. obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes);
  3832. obs_data_set_int(data, "index", ui->scenes->currentRow());
  3833. const char *scene_name = obs_source_get_name(source);
  3834. undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name);
  3835. /* --------------------------- */
  3836. /* remove */
  3837. RemoveSceneAndReleaseNested(source);
  3838. OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
  3839. }
  3840. void OBSBasic::ReorderSources(OBSScene scene)
  3841. {
  3842. if (scene != GetCurrentScene() || ui->sources->IgnoreReorder())
  3843. return;
  3844. ui->sources->ReorderItems();
  3845. SaveProject();
  3846. }
  3847. void OBSBasic::RefreshSources(OBSScene scene)
  3848. {
  3849. if (scene != GetCurrentScene() || ui->sources->IgnoreReorder())
  3850. return;
  3851. ui->sources->RefreshItems();
  3852. SaveProject();
  3853. }
  3854. /* OBS Callbacks */
  3855. void OBSBasic::SceneReordered(void *data, calldata_t *params)
  3856. {
  3857. OBSBasic *window = static_cast<OBSBasic *>(data);
  3858. obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene");
  3859. QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene)));
  3860. }
  3861. void OBSBasic::SceneRefreshed(void *data, calldata_t *params)
  3862. {
  3863. OBSBasic *window = static_cast<OBSBasic *>(data);
  3864. obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene");
  3865. QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene)));
  3866. }
  3867. void OBSBasic::SceneItemAdded(void *data, calldata_t *params)
  3868. {
  3869. OBSBasic *window = static_cast<OBSBasic *>(data);
  3870. obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item");
  3871. QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item)));
  3872. }
  3873. void OBSBasic::SourceCreated(void *data, calldata_t *params)
  3874. {
  3875. obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
  3876. if (obs_scene_from_source(source) != NULL)
  3877. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "AddScene", WaitConnection(),
  3878. Q_ARG(OBSSource, OBSSource(source)));
  3879. }
  3880. void OBSBasic::SourceRemoved(void *data, calldata_t *params)
  3881. {
  3882. obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
  3883. if (obs_scene_from_source(source) != NULL)
  3884. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "RemoveScene",
  3885. Q_ARG(OBSSource, OBSSource(source)));
  3886. }
  3887. void OBSBasic::SourceActivated(void *data, calldata_t *params)
  3888. {
  3889. obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
  3890. uint32_t flags = obs_source_get_output_flags(source);
  3891. if (flags & OBS_SOURCE_AUDIO)
  3892. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "ActivateAudioSource",
  3893. Q_ARG(OBSSource, OBSSource(source)));
  3894. }
  3895. void OBSBasic::SourceDeactivated(void *data, calldata_t *params)
  3896. {
  3897. obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
  3898. uint32_t flags = obs_source_get_output_flags(source);
  3899. if (flags & OBS_SOURCE_AUDIO)
  3900. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "DeactivateAudioSource",
  3901. Q_ARG(OBSSource, OBSSource(source)));
  3902. }
  3903. void OBSBasic::SourceAudioActivated(void *data, calldata_t *params)
  3904. {
  3905. obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
  3906. if (obs_source_active(source))
  3907. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "ActivateAudioSource",
  3908. Q_ARG(OBSSource, OBSSource(source)));
  3909. }
  3910. void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params)
  3911. {
  3912. obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
  3913. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "DeactivateAudioSource",
  3914. Q_ARG(OBSSource, OBSSource(source)));
  3915. }
  3916. void OBSBasic::SourceRenamed(void *data, calldata_t *params)
  3917. {
  3918. obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
  3919. const char *newName = calldata_string(params, "new_name");
  3920. const char *prevName = calldata_string(params, "prev_name");
  3921. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "RenameSources", Q_ARG(OBSSource, source),
  3922. Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName)));
  3923. blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName);
  3924. }
  3925. void OBSBasic::DrawBackdrop(float cx, float cy)
  3926. {
  3927. if (!box)
  3928. return;
  3929. GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop");
  3930. gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
  3931. gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color");
  3932. gs_technique_t *tech = gs_effect_get_technique(solid, "Solid");
  3933. vec4 colorVal;
  3934. vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f);
  3935. gs_effect_set_vec4(color, &colorVal);
  3936. gs_technique_begin(tech);
  3937. gs_technique_begin_pass(tech, 0);
  3938. gs_matrix_push();
  3939. gs_matrix_identity();
  3940. gs_matrix_scale3f(float(cx), float(cy), 1.0f);
  3941. gs_load_vertexbuffer(box);
  3942. gs_draw(GS_TRISTRIP, 0, 0);
  3943. gs_matrix_pop();
  3944. gs_technique_end_pass(tech);
  3945. gs_technique_end(tech);
  3946. gs_load_vertexbuffer(nullptr);
  3947. GS_DEBUG_MARKER_END();
  3948. }
  3949. void OBSBasic::RenderMain(void *data, uint32_t, uint32_t)
  3950. {
  3951. GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain");
  3952. OBSBasic *window = static_cast<OBSBasic *>(data);
  3953. obs_video_info ovi;
  3954. obs_get_video_info(&ovi);
  3955. window->previewCX = int(window->previewScale * float(ovi.base_width));
  3956. window->previewCY = int(window->previewScale * float(ovi.base_height));
  3957. gs_viewport_push();
  3958. gs_projection_push();
  3959. obs_display_t *display = window->ui->preview->GetDisplay();
  3960. uint32_t width, height;
  3961. obs_display_size(display, &width, &height);
  3962. float right = float(width) - window->previewX;
  3963. float bottom = float(height) - window->previewY;
  3964. gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f);
  3965. window->ui->preview->DrawOverflow();
  3966. /* --------------------------------------- */
  3967. gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f);
  3968. gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY);
  3969. if (window->IsPreviewProgramMode()) {
  3970. window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));
  3971. OBSScene scene = window->GetCurrentScene();
  3972. obs_source_t *source = obs_scene_get_source(scene);
  3973. if (source)
  3974. obs_source_video_render(source);
  3975. } else {
  3976. obs_render_main_texture_src_color_only();
  3977. }
  3978. gs_load_vertexbuffer(nullptr);
  3979. /* --------------------------------------- */
  3980. gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f);
  3981. gs_reset_viewport();
  3982. uint32_t targetCX = window->previewCX;
  3983. uint32_t targetCY = window->previewCY;
  3984. if (window->drawSafeAreas) {
  3985. RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY);
  3986. RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY);
  3987. RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY);
  3988. RenderSafeAreas(window->leftLine, targetCX, targetCY);
  3989. RenderSafeAreas(window->topLine, targetCX, targetCY);
  3990. RenderSafeAreas(window->rightLine, targetCX, targetCY);
  3991. }
  3992. window->ui->preview->DrawSceneEditing();
  3993. if (window->drawSpacingHelpers)
  3994. window->ui->preview->DrawSpacingHelpers();
  3995. /* --------------------------------------- */
  3996. gs_projection_pop();
  3997. gs_viewport_pop();
  3998. GS_DEBUG_MARKER_END();
  3999. }
  4000. /* Main class functions */
  4001. obs_service_t *OBSBasic::GetService()
  4002. {
  4003. if (!service) {
  4004. service = obs_service_create("rtmp_common", NULL, NULL, nullptr);
  4005. obs_service_release(service);
  4006. }
  4007. return service;
  4008. }
  4009. void OBSBasic::SetService(obs_service_t *newService)
  4010. {
  4011. if (newService) {
  4012. service = newService;
  4013. }
  4014. }
  4015. int OBSBasic::GetTransitionDuration()
  4016. {
  4017. return ui->transitionDuration->value();
  4018. }
  4019. bool OBSBasic::Active() const
  4020. {
  4021. if (!outputHandler)
  4022. return false;
  4023. return outputHandler->Active();
  4024. }
  4025. #ifdef _WIN32
  4026. #define IS_WIN32 1
  4027. #else
  4028. #define IS_WIN32 0
  4029. #endif
  4030. static inline int AttemptToResetVideo(struct obs_video_info *ovi)
  4031. {
  4032. return obs_reset_video(ovi);
  4033. }
  4034. static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration)
  4035. {
  4036. const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType");
  4037. if (astrcmpi(scaleTypeStr, "bilinear") == 0)
  4038. return OBS_SCALE_BILINEAR;
  4039. else if (astrcmpi(scaleTypeStr, "lanczos") == 0)
  4040. return OBS_SCALE_LANCZOS;
  4041. else if (astrcmpi(scaleTypeStr, "area") == 0)
  4042. return OBS_SCALE_AREA;
  4043. else
  4044. return OBS_SCALE_BICUBIC;
  4045. }
  4046. static inline enum video_format GetVideoFormatFromName(const char *name)
  4047. {
  4048. if (astrcmpi(name, "I420") == 0)
  4049. return VIDEO_FORMAT_I420;
  4050. else if (astrcmpi(name, "NV12") == 0)
  4051. return VIDEO_FORMAT_NV12;
  4052. else if (astrcmpi(name, "I444") == 0)
  4053. return VIDEO_FORMAT_I444;
  4054. else if (astrcmpi(name, "I010") == 0)
  4055. return VIDEO_FORMAT_I010;
  4056. else if (astrcmpi(name, "P010") == 0)
  4057. return VIDEO_FORMAT_P010;
  4058. else if (astrcmpi(name, "P216") == 0)
  4059. return VIDEO_FORMAT_P216;
  4060. else if (astrcmpi(name, "P416") == 0)
  4061. return VIDEO_FORMAT_P416;
  4062. #if 0 //currently unsupported
  4063. else if (astrcmpi(name, "YVYU") == 0)
  4064. return VIDEO_FORMAT_YVYU;
  4065. else if (astrcmpi(name, "YUY2") == 0)
  4066. return VIDEO_FORMAT_YUY2;
  4067. else if (astrcmpi(name, "UYVY") == 0)
  4068. return VIDEO_FORMAT_UYVY;
  4069. #endif
  4070. else
  4071. return VIDEO_FORMAT_BGRA;
  4072. }
  4073. static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name)
  4074. {
  4075. enum video_colorspace colorspace = VIDEO_CS_SRGB;
  4076. if (strcmp(name, "601") == 0)
  4077. colorspace = VIDEO_CS_601;
  4078. else if (strcmp(name, "709") == 0)
  4079. colorspace = VIDEO_CS_709;
  4080. else if (strcmp(name, "2100PQ") == 0)
  4081. colorspace = VIDEO_CS_2100_PQ;
  4082. else if (strcmp(name, "2100HLG") == 0)
  4083. colorspace = VIDEO_CS_2100_HLG;
  4084. return colorspace;
  4085. }
  4086. void OBSBasic::ResetUI()
  4087. {
  4088. bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout");
  4089. if (studioPortraitLayout)
  4090. ui->previewLayout->setDirection(QBoxLayout::BottomToTop);
  4091. else
  4092. ui->previewLayout->setDirection(QBoxLayout::LeftToRight);
  4093. UpdatePreviewProgramIndicators();
  4094. }
  4095. int OBSBasic::ResetVideo()
  4096. {
  4097. if (outputHandler && outputHandler->Active())
  4098. return OBS_VIDEO_CURRENTLY_ACTIVE;
  4099. ProfileScope("OBSBasic::ResetVideo");
  4100. struct obs_video_info ovi;
  4101. int ret;
  4102. GetConfigFPS(ovi.fps_num, ovi.fps_den);
  4103. const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat");
  4104. const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace");
  4105. const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange");
  4106. ovi.graphics_module = App()->GetRenderModule();
  4107. ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX");
  4108. ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY");
  4109. ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX");
  4110. ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY");
  4111. ovi.output_format = GetVideoFormatFromName(colorFormat);
  4112. ovi.colorspace = GetVideoColorSpaceFromName(colorSpace);
  4113. ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL;
  4114. ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx");
  4115. ovi.gpu_conversion = true;
  4116. ovi.scale_type = GetScaleType(activeConfiguration);
  4117. if (ovi.base_width < 32 || ovi.base_height < 32) {
  4118. ovi.base_width = 1920;
  4119. ovi.base_height = 1080;
  4120. config_set_uint(activeConfiguration, "Video", "BaseCX", 1920);
  4121. config_set_uint(activeConfiguration, "Video", "BaseCY", 1080);
  4122. }
  4123. if (ovi.output_width < 32 || ovi.output_height < 32) {
  4124. ovi.output_width = ovi.base_width;
  4125. ovi.output_height = ovi.base_height;
  4126. config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width);
  4127. config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height);
  4128. }
  4129. ret = AttemptToResetVideo(&ovi);
  4130. if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) {
  4131. blog(LOG_WARNING, "Tried to reset when already active");
  4132. return ret;
  4133. }
  4134. if (ret == OBS_VIDEO_SUCCESS) {
  4135. ResizePreview(ovi.base_width, ovi.base_height);
  4136. if (program)
  4137. ResizeProgram(ovi.base_width, ovi.base_height);
  4138. const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel");
  4139. const float hdr_nominal_peak_level =
  4140. (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel");
  4141. obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level);
  4142. OBSBasicStats::InitializeValues();
  4143. OBSProjector::UpdateMultiviewProjectors();
  4144. bool canMigrate = usingAbsoluteCoordinates ||
  4145. (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width ||
  4146. migrationBaseResolution->second != ovi.base_height));
  4147. ui->actionRemigrateSceneCollection->setEnabled(canMigrate);
  4148. emit CanvasResized(ovi.base_width, ovi.base_height);
  4149. emit OutputResized(ovi.output_width, ovi.output_height);
  4150. }
  4151. return ret;
  4152. }
  4153. bool OBSBasic::ResetAudio()
  4154. {
  4155. ProfileScope("OBSBasic::ResetAudio");
  4156. struct obs_audio_info2 ai = {};
  4157. ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate");
  4158. const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup");
  4159. if (strcmp(channelSetupStr, "Mono") == 0)
  4160. ai.speakers = SPEAKERS_MONO;
  4161. else if (strcmp(channelSetupStr, "2.1") == 0)
  4162. ai.speakers = SPEAKERS_2POINT1;
  4163. else if (strcmp(channelSetupStr, "4.0") == 0)
  4164. ai.speakers = SPEAKERS_4POINT0;
  4165. else if (strcmp(channelSetupStr, "4.1") == 0)
  4166. ai.speakers = SPEAKERS_4POINT1;
  4167. else if (strcmp(channelSetupStr, "5.1") == 0)
  4168. ai.speakers = SPEAKERS_5POINT1;
  4169. else if (strcmp(channelSetupStr, "7.1") == 0)
  4170. ai.speakers = SPEAKERS_7POINT1;
  4171. else
  4172. ai.speakers = SPEAKERS_STEREO;
  4173. bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering");
  4174. if (lowLatencyAudioBuffering) {
  4175. ai.max_buffering_ms = 20;
  4176. ai.fixed_buffering = true;
  4177. }
  4178. return obs_reset_audio2(&ai);
  4179. }
  4180. extern char *get_new_source_name(const char *name, const char *format);
  4181. void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel)
  4182. {
  4183. bool disable = deviceId && strcmp(deviceId, "disabled") == 0;
  4184. OBSSourceAutoRelease source;
  4185. OBSDataAutoRelease settings;
  4186. source = obs_get_output_source(channel);
  4187. if (source) {
  4188. if (disable) {
  4189. obs_set_output_source(channel, nullptr);
  4190. } else {
  4191. settings = obs_source_get_settings(source);
  4192. const char *oldId = obs_data_get_string(settings, "device_id");
  4193. if (strcmp(oldId, deviceId) != 0) {
  4194. obs_data_set_string(settings, "device_id", deviceId);
  4195. obs_source_update(source, settings);
  4196. }
  4197. }
  4198. } else if (!disable) {
  4199. BPtr<char> name = get_new_source_name(deviceDesc, "%s (%d)");
  4200. settings = obs_data_create();
  4201. obs_data_set_string(settings, "device_id", deviceId);
  4202. source = obs_source_create(sourceId, name, settings, nullptr);
  4203. obs_set_output_source(channel, source);
  4204. }
  4205. }
  4206. void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
  4207. {
  4208. QSize targetSize;
  4209. bool isFixedScaling;
  4210. obs_video_info ovi;
  4211. /* resize preview panel to fix to the top section of the window */
  4212. targetSize = GetPixelSize(ui->preview);
  4213. isFixedScaling = ui->preview->IsFixedScaling();
  4214. obs_get_video_info(&ovi);
  4215. if (isFixedScaling) {
  4216. previewScale = ui->preview->GetScalingAmount();
  4217. ui->preview->ClampScrollingOffsets();
  4218. GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2,
  4219. targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY,
  4220. previewScale);
  4221. previewX += ui->preview->GetScrollX();
  4222. previewY += ui->preview->GetScrollY();
  4223. } else {
  4224. GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2,
  4225. targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale);
  4226. }
  4227. ui->preview->SetScalingAmount(previewScale);
  4228. previewX += float(PREVIEW_EDGE_SIZE);
  4229. previewY += float(PREVIEW_EDGE_SIZE);
  4230. }
  4231. void OBSBasic::CloseDialogs()
  4232. {
  4233. QList<QDialog *> childDialogs = this->findChildren<QDialog *>();
  4234. if (!childDialogs.isEmpty()) {
  4235. for (int i = 0; i < childDialogs.size(); ++i) {
  4236. childDialogs.at(i)->close();
  4237. }
  4238. }
  4239. if (!stats.isNull())
  4240. stats->close(); //call close to save Stats geometry
  4241. if (!remux.isNull())
  4242. remux->close();
  4243. }
  4244. void OBSBasic::EnumDialogs()
  4245. {
  4246. visDialogs.clear();
  4247. modalDialogs.clear();
  4248. visMsgBoxes.clear();
  4249. /* fill list of Visible dialogs and Modal dialogs */
  4250. QList<QDialog *> dialogs = findChildren<QDialog *>();
  4251. for (QDialog *dialog : dialogs) {
  4252. if (dialog->isVisible())
  4253. visDialogs.append(dialog);
  4254. if (dialog->isModal())
  4255. modalDialogs.append(dialog);
  4256. }
  4257. /* fill list of Visible message boxes */
  4258. QList<QMessageBox *> msgBoxes = findChildren<QMessageBox *>();
  4259. for (QMessageBox *msgbox : msgBoxes) {
  4260. if (msgbox->isVisible())
  4261. visMsgBoxes.append(msgbox);
  4262. }
  4263. }
  4264. void OBSBasic::ClearProjectors()
  4265. {
  4266. for (size_t i = 0; i < projectors.size(); i++) {
  4267. if (projectors[i])
  4268. delete projectors[i];
  4269. }
  4270. projectors.clear();
  4271. }
  4272. void OBSBasic::ClearSceneData()
  4273. {
  4274. disableSaving++;
  4275. setCursor(Qt::WaitCursor);
  4276. CloseDialogs();
  4277. ClearVolumeControls();
  4278. ClearListItems(ui->scenes);
  4279. ui->sources->Clear();
  4280. ClearQuickTransitions();
  4281. ui->transitions->clear();
  4282. ClearProjectors();
  4283. for (int i = 0; i < MAX_CHANNELS; i++)
  4284. obs_set_output_source(i, nullptr);
  4285. /* Reset VCam to default to clear its private scene and any references
  4286. * it holds. It will be reconfigured during loading. */
  4287. if (vcamEnabled) {
  4288. vcamConfig.type = VCamOutputType::ProgramView;
  4289. outputHandler->UpdateVirtualCamOutputSource();
  4290. }
  4291. collectionModuleData = nullptr;
  4292. lastScene = nullptr;
  4293. swapScene = nullptr;
  4294. programScene = nullptr;
  4295. prevFTBSource = nullptr;
  4296. clipboard.clear();
  4297. copyFiltersSource = nullptr;
  4298. copyFilter = nullptr;
  4299. auto cb = [](void *, obs_source_t *source) {
  4300. obs_source_remove(source);
  4301. return true;
  4302. };
  4303. obs_enum_scenes(cb, nullptr);
  4304. obs_enum_sources(cb, nullptr);
  4305. OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP);
  4306. undo_s.clear();
  4307. /* using QEvent::DeferredDelete explicitly is the only way to ensure
  4308. * that deleteLater events are processed at this point */
  4309. QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
  4310. do {
  4311. QApplication::sendPostedEvents(nullptr);
  4312. } while (obs_wait_for_destroy_queue());
  4313. /* Pump Qt events one final time to give remaining signals time to be
  4314. * processed (since this happens after the destroy thread finishes and
  4315. * the audio/video threads have processed their tasks). */
  4316. QApplication::sendPostedEvents(nullptr);
  4317. unsetCursor();
  4318. /* If scene data wasn't actually cleared, e.g. faulty plugin holding a
  4319. * reference, they will still be in the hash table, enumerate them and
  4320. * store the names for logging purposes. */
  4321. auto cb2 = [](void *param, obs_source_t *source) {
  4322. auto orphans = static_cast<vector<string> *>(param);
  4323. orphans->push_back(obs_source_get_name(source));
  4324. return true;
  4325. };
  4326. vector<string> orphan_sources;
  4327. obs_enum_sources(cb2, &orphan_sources);
  4328. if (!orphan_sources.empty()) {
  4329. /* Avoid logging list twice in case it gets called after
  4330. * setting the flag the first time. */
  4331. if (!clearingFailed) {
  4332. /* This ugly mess exists to join a vector of strings
  4333. * with a user-defined delimiter. */
  4334. string orphan_names =
  4335. std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""),
  4336. [](string a, string b) { return std::move(a) + "\n- " + b; });
  4337. blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n",
  4338. orphan_names.c_str());
  4339. }
  4340. /* We do not decrement disableSaving here to avoid OBS
  4341. * overwriting user data with garbage. */
  4342. clearingFailed = true;
  4343. } else {
  4344. disableSaving--;
  4345. blog(LOG_INFO, "All scene data cleared");
  4346. blog(LOG_INFO, "------------------------------------------------");
  4347. }
  4348. }
  4349. void OBSBasic::closeEvent(QCloseEvent *event)
  4350. {
  4351. /* Wait for multitrack video stream to start/finish processing in the background */
  4352. if (setupStreamingGuard.valid() &&
  4353. setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) {
  4354. QTimer::singleShot(1000, this, &OBSBasic::close);
  4355. event->ignore();
  4356. return;
  4357. }
  4358. /* Do not close window if inside of a temporary event loop because we
  4359. * could be inside of an Auth::LoadUI call. Keep trying once per
  4360. * second until we've exit any known sub-loops. */
  4361. if (os_atomic_load_long(&insideEventLoop) != 0) {
  4362. QTimer::singleShot(1000, this, &OBSBasic::close);
  4363. event->ignore();
  4364. return;
  4365. }
  4366. #ifdef YOUTUBE_ENABLED
  4367. /* Also don't close the window if the youtube stream check is active */
  4368. if (youtubeStreamCheckThread) {
  4369. QTimer::singleShot(1000, this, &OBSBasic::close);
  4370. event->ignore();
  4371. return;
  4372. }
  4373. #endif
  4374. if (isVisible())
  4375. config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry",
  4376. saveGeometry().toBase64().constData());
  4377. bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit");
  4378. if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) {
  4379. SetShowing(true);
  4380. QMessageBox::StandardButton button =
  4381. OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text"));
  4382. if (button == QMessageBox::No) {
  4383. event->ignore();
  4384. restart = false;
  4385. return;
  4386. }
  4387. }
  4388. if (remux && !remux->close()) {
  4389. event->ignore();
  4390. restart = false;
  4391. return;
  4392. }
  4393. QWidget::closeEvent(event);
  4394. if (!event->isAccepted())
  4395. return;
  4396. blog(LOG_INFO, SHUTDOWN_SEPARATOR);
  4397. closing = true;
  4398. /* While closing, a resize event to OBSQTDisplay could be triggered.
  4399. * The graphics thread on macOS dispatches a lambda function to be
  4400. * executed asynchronously in the main thread. However, the display is
  4401. * sometimes deleted before the lambda function is actually executed.
  4402. * To avoid such a case, destroy displays earlier than others such as
  4403. * deleting browser docks. */
  4404. ui->preview->DestroyDisplay();
  4405. if (program)
  4406. program->DestroyDisplay();
  4407. if (outputHandler->VirtualCamActive())
  4408. outputHandler->StopVirtualCam();
  4409. if (introCheckThread)
  4410. introCheckThread->wait();
  4411. if (whatsNewInitThread)
  4412. whatsNewInitThread->wait();
  4413. if (updateCheckThread)
  4414. updateCheckThread->wait();
  4415. if (logUploadThread)
  4416. logUploadThread->wait();
  4417. if (devicePropertiesThread && devicePropertiesThread->isRunning()) {
  4418. devicePropertiesThread->wait();
  4419. devicePropertiesThread.reset();
  4420. }
  4421. QApplication::sendPostedEvents(nullptr);
  4422. signalHandlers.clear();
  4423. Auth::Save();
  4424. SaveProjectNow();
  4425. auth.reset();
  4426. delete extraBrowsers;
  4427. config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData());
  4428. #ifdef BROWSER_AVAILABLE
  4429. if (cef)
  4430. SaveExtraBrowserDocks();
  4431. ClearExtraBrowserDocks();
  4432. #endif
  4433. OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN);
  4434. disableSaving++;
  4435. /* Clear all scene data (dialogs, widgets, widget sub-items, scenes,
  4436. * sources, etc) so that all references are released before shutdown */
  4437. ClearSceneData();
  4438. OnEvent(OBS_FRONTEND_EVENT_EXIT);
  4439. // Destroys the frontend API so plugins can't continue calling it
  4440. obs_frontend_set_callbacks_internal(nullptr);
  4441. api = nullptr;
  4442. QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection);
  4443. }
  4444. bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *)
  4445. {
  4446. #ifdef _WIN32
  4447. const MSG &msg = *static_cast<MSG *>(message);
  4448. switch (msg.message) {
  4449. case WM_MOVE:
  4450. for (OBSQTDisplay *const display : findChildren<OBSQTDisplay *>()) {
  4451. display->OnMove();
  4452. }
  4453. break;
  4454. case WM_DISPLAYCHANGE:
  4455. for (OBSQTDisplay *const display : findChildren<OBSQTDisplay *>()) {
  4456. display->OnDisplayChange();
  4457. }
  4458. }
  4459. #else
  4460. UNUSED_PARAMETER(message);
  4461. #endif
  4462. return false;
  4463. }
  4464. void OBSBasic::changeEvent(QEvent *event)
  4465. {
  4466. if (event->type() == QEvent::WindowStateChange) {
  4467. QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event;
  4468. if (isMinimized()) {
  4469. if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) {
  4470. ToggleShowHide();
  4471. return;
  4472. }
  4473. if (previewEnabled)
  4474. EnablePreviewDisplay(false);
  4475. } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) {
  4476. if (previewEnabled)
  4477. EnablePreviewDisplay(true);
  4478. }
  4479. }
  4480. }
  4481. void OBSBasic::on_actionShow_Recordings_triggered()
  4482. {
  4483. const char *mode = config_get_string(activeConfiguration, "Output", "Mode");
  4484. const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType");
  4485. const char *adv_path = strcmp(type, "Standard")
  4486. ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath")
  4487. : config_get_string(activeConfiguration, "AdvOut", "RecFilePath");
  4488. const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath")
  4489. : adv_path;
  4490. QDesktopServices::openUrl(QUrl::fromLocalFile(path));
  4491. }
  4492. void OBSBasic::on_actionRemux_triggered()
  4493. {
  4494. if (!remux.isNull()) {
  4495. remux->show();
  4496. remux->raise();
  4497. return;
  4498. }
  4499. const char *mode = config_get_string(activeConfiguration, "Output", "Mode");
  4500. const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath")
  4501. : config_get_string(activeConfiguration, "AdvOut", "RecFilePath");
  4502. OBSRemux *remuxDlg;
  4503. remuxDlg = new OBSRemux(path, this);
  4504. remuxDlg->show();
  4505. remux = remuxDlg;
  4506. }
  4507. void OBSBasic::on_action_Settings_triggered()
  4508. {
  4509. static bool settings_already_executing = false;
  4510. /* Do not load settings window if inside of a temporary event loop
  4511. * because we could be inside of an Auth::LoadUI call. Keep trying
  4512. * once per second until we've exit any known sub-loops. */
  4513. if (os_atomic_load_long(&insideEventLoop) != 0) {
  4514. QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered);
  4515. return;
  4516. }
  4517. if (settings_already_executing) {
  4518. return;
  4519. }
  4520. settings_already_executing = true;
  4521. {
  4522. OBSBasicSettings settings(this);
  4523. settings.exec();
  4524. }
  4525. settings_already_executing = false;
  4526. if (restart) {
  4527. QMessageBox::StandardButton button =
  4528. OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart"));
  4529. if (button == QMessageBox::Yes)
  4530. close();
  4531. else
  4532. restart = false;
  4533. }
  4534. }
  4535. void OBSBasic::on_actionShowMacPermissions_triggered()
  4536. {
  4537. #ifdef __APPLE__
  4538. OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess),
  4539. CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility));
  4540. check.exec();
  4541. #endif
  4542. }
  4543. void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files)
  4544. {
  4545. if (obs_missing_files_count(files) > 0) {
  4546. /* When loading the missing files dialog on launch, the
  4547. * window hasn't fully initialized by this point on macOS,
  4548. * so put this at the end of the current task queue. Fixes
  4549. * a bug where the window is behind OBS on startup. */
  4550. QTimer::singleShot(0, [this, files] {
  4551. missDialog = new OBSMissingFiles(files, this);
  4552. missDialog->setAttribute(Qt::WA_DeleteOnClose, true);
  4553. missDialog->show();
  4554. missDialog->raise();
  4555. });
  4556. } else {
  4557. obs_missing_files_destroy(files);
  4558. /* Only raise dialog if triggered manually */
  4559. if (!disableSaving)
  4560. OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"),
  4561. QTStr("MissingFiles.NoMissing.Text"));
  4562. }
  4563. }
  4564. void OBSBasic::on_actionShowMissingFiles_triggered()
  4565. {
  4566. obs_missing_files_t *files = obs_missing_files_create();
  4567. auto cb_sources = [](void *data, obs_source_t *source) {
  4568. AddMissingFiles(data, source);
  4569. return true;
  4570. };
  4571. obs_enum_all_sources(cb_sources, files);
  4572. ShowMissingFilesDialog(files);
  4573. }
  4574. void OBSBasic::on_actionAdvAudioProperties_triggered()
  4575. {
  4576. if (advAudioWindow != nullptr) {
  4577. advAudioWindow->raise();
  4578. return;
  4579. }
  4580. bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons");
  4581. advAudioWindow = new OBSBasicAdvAudio(this);
  4582. advAudioWindow->show();
  4583. advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true);
  4584. advAudioWindow->SetIconsVisible(iconsVisible);
  4585. }
  4586. void OBSBasic::on_actionMixerToolbarAdvAudio_triggered()
  4587. {
  4588. on_actionAdvAudioProperties_triggered();
  4589. }
  4590. void OBSBasic::on_actionMixerToolbarMenu_triggered()
  4591. {
  4592. QAction unhideAllAction(QTStr("UnhideAll"), this);
  4593. connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection);
  4594. QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this);
  4595. toggleControlLayoutAction.setCheckable(true);
  4596. toggleControlLayoutAction.setChecked(
  4597. config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"));
  4598. connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout,
  4599. Qt::DirectConnection);
  4600. QMenu popup;
  4601. popup.addAction(&unhideAllAction);
  4602. popup.addSeparator();
  4603. popup.addAction(&toggleControlLayoutAction);
  4604. popup.exec(QCursor::pos());
  4605. }
  4606. void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *)
  4607. {
  4608. OBSSource source;
  4609. if (current) {
  4610. OBSScene scene = GetOBSRef<OBSScene>(current);
  4611. source = obs_scene_get_source(scene);
  4612. currentScene = scene;
  4613. } else {
  4614. currentScene = NULL;
  4615. }
  4616. SetCurrentScene(source);
  4617. if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput)
  4618. outputHandler->UpdateVirtualCamOutputSource();
  4619. OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);
  4620. UpdateContextBar();
  4621. }
  4622. void OBSBasic::EditSceneName()
  4623. {
  4624. ui->scenesDock->removeAction(renameScene);
  4625. QListWidgetItem *item = ui->scenes->currentItem();
  4626. Qt::ItemFlags flags = item->flags();
  4627. item->setFlags(flags | Qt::ItemIsEditable);
  4628. ui->scenes->editItem(item);
  4629. item->setFlags(flags);
  4630. }
  4631. QList<QString> OBSBasic::GetProjectorMenuMonitorsFormatted()
  4632. {
  4633. QList<QString> projectorsFormatted;
  4634. QList<QScreen *> screens = QGuiApplication::screens();
  4635. for (int i = 0; i < screens.size(); i++) {
  4636. QScreen *screen = screens[i];
  4637. QRect screenGeometry = screen->geometry();
  4638. qreal ratio = screen->devicePixelRatio();
  4639. QString name = "";
  4640. #if defined(__APPLE__) || defined(_WIN32)
  4641. name = screen->name();
  4642. #else
  4643. name = screen->model().simplified();
  4644. if (name.length() > 1 && name.endsWith("-"))
  4645. name.chop(1);
  4646. #endif
  4647. name = name.simplified();
  4648. if (name.length() == 0) {
  4649. name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1));
  4650. }
  4651. QString str = QString("%1: %2x%3 @ %4,%5")
  4652. .arg(name, QString::number(screenGeometry.width() * ratio),
  4653. QString::number(screenGeometry.height() * ratio),
  4654. QString::number(screenGeometry.x()), QString::number(screenGeometry.y()));
  4655. projectorsFormatted.push_back(str);
  4656. }
  4657. return projectorsFormatted;
  4658. }
  4659. void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
  4660. {
  4661. QListWidgetItem *item = ui->scenes->itemAt(pos);
  4662. QMenu popup(this);
  4663. QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this);
  4664. popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered);
  4665. if (item) {
  4666. QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this);
  4667. copyFilters->setEnabled(false);
  4668. connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters);
  4669. QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this);
  4670. pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource));
  4671. connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters);
  4672. popup.addSeparator();
  4673. popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene);
  4674. popup.addAction(copyFilters);
  4675. popup.addAction(pasteFilters);
  4676. popup.addSeparator();
  4677. popup.addAction(renameScene);
  4678. popup.addAction(ui->actionRemoveScene);
  4679. popup.addSeparator();
  4680. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered);
  4681. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this,
  4682. &OBSBasic::on_actionSceneDown_triggered);
  4683. order.addSeparator();
  4684. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop);
  4685. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom);
  4686. popup.addMenu(&order);
  4687. popup.addSeparator();
  4688. delete sceneProjectorMenu;
  4689. sceneProjectorMenu = new QMenu(QTStr("SceneProjector"));
  4690. AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector);
  4691. popup.addMenu(sceneProjectorMenu);
  4692. QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow);
  4693. popup.addAction(sceneWindow);
  4694. popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene);
  4695. popup.addSeparator();
  4696. popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters);
  4697. popup.addSeparator();
  4698. delete perSceneTransitionMenu;
  4699. perSceneTransitionMenu = CreatePerSceneTransitionMenu();
  4700. popup.addMenu(perSceneTransitionMenu);
  4701. /* ---------------------- */
  4702. QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview"));
  4703. OBSSource source = GetCurrentSceneSource();
  4704. OBSDataAutoRelease data = obs_source_get_private_settings(source);
  4705. obs_data_set_default_bool(data, "show_in_multiview", true);
  4706. bool show = obs_data_get_bool(data, "show_in_multiview");
  4707. multiviewAction->setCheckable(true);
  4708. multiviewAction->setChecked(show);
  4709. auto showInMultiview = [](OBSData data) {
  4710. bool show = obs_data_get_bool(data, "show_in_multiview");
  4711. obs_data_set_bool(data, "show_in_multiview", !show);
  4712. OBSProjector::UpdateMultiviewProjectors();
  4713. };
  4714. connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get()));
  4715. copyFilters->setEnabled(obs_source_filter_count(source) > 0);
  4716. }
  4717. popup.addSeparator();
  4718. bool grid = ui->scenes->GetGridMode();
  4719. QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this);
  4720. connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked);
  4721. popup.addAction(gridAction);
  4722. popup.exec(QCursor::pos());
  4723. }
  4724. void OBSBasic::on_actionSceneListMode_triggered()
  4725. {
  4726. ui->scenes->SetGridMode(false);
  4727. config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false);
  4728. }
  4729. void OBSBasic::on_actionSceneGridMode_triggered()
  4730. {
  4731. ui->scenes->SetGridMode(true);
  4732. config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true);
  4733. }
  4734. void OBSBasic::GridActionClicked()
  4735. {
  4736. bool gridMode = !ui->scenes->GetGridMode();
  4737. ui->scenes->SetGridMode(gridMode);
  4738. if (gridMode)
  4739. ui->actionSceneGridMode->setChecked(true);
  4740. else
  4741. ui->actionSceneListMode->setChecked(true);
  4742. config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode);
  4743. }
  4744. void OBSBasic::on_actionAddScene_triggered()
  4745. {
  4746. string name;
  4747. QString format{QTStr("Basic.Main.DefaultSceneName.Text")};
  4748. int i = 2;
  4749. QString placeHolderText = format.arg(i);
  4750. OBSSourceAutoRelease source = nullptr;
  4751. while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
  4752. placeHolderText = format.arg(++i);
  4753. }
  4754. bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"),
  4755. QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText);
  4756. if (accepted) {
  4757. if (name.empty()) {
  4758. OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  4759. on_actionAddScene_triggered();
  4760. return;
  4761. }
  4762. OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str());
  4763. if (source) {
  4764. OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  4765. on_actionAddScene_triggered();
  4766. return;
  4767. }
  4768. auto undo_fn = [](const std::string &data) {
  4769. obs_source_t *t = obs_get_source_by_name(data.c_str());
  4770. if (t) {
  4771. obs_source_remove(t);
  4772. obs_source_release(t);
  4773. }
  4774. };
  4775. auto redo_fn = [this](const std::string &data) {
  4776. OBSSceneAutoRelease scene = obs_scene_create(data.c_str());
  4777. obs_source_t *source = obs_scene_get_source(scene);
  4778. SetCurrentScene(source, true);
  4779. };
  4780. undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name);
  4781. OBSSceneAutoRelease scene = obs_scene_create(name.c_str());
  4782. obs_source_t *scene_source = obs_scene_get_source(scene);
  4783. SetCurrentScene(scene_source);
  4784. }
  4785. }
  4786. void OBSBasic::on_actionRemoveScene_triggered()
  4787. {
  4788. RemoveSelectedScene();
  4789. }
  4790. void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx)
  4791. {
  4792. int idx = ui->scenes->currentRow();
  4793. if (idx == -1 || idx == invalidIdx)
  4794. return;
  4795. ui->scenes->blockSignals(true);
  4796. QListWidgetItem *item = ui->scenes->takeItem(idx);
  4797. if (!relative)
  4798. idx = 0;
  4799. ui->scenes->insertItem(idx + offset, item);
  4800. ui->scenes->setCurrentRow(idx + offset);
  4801. item->setSelected(true);
  4802. currentScene = GetOBSRef<OBSScene>(item).Get();
  4803. ui->scenes->blockSignals(false);
  4804. OBSProjector::UpdateMultiviewProjectors();
  4805. }
  4806. void OBSBasic::on_actionSceneUp_triggered()
  4807. {
  4808. ChangeSceneIndex(true, -1, 0);
  4809. }
  4810. void OBSBasic::on_actionSceneDown_triggered()
  4811. {
  4812. ChangeSceneIndex(true, 1, ui->scenes->count() - 1);
  4813. }
  4814. void OBSBasic::MoveSceneToTop()
  4815. {
  4816. ChangeSceneIndex(false, 0, 0);
  4817. }
  4818. void OBSBasic::MoveSceneToBottom()
  4819. {
  4820. ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1);
  4821. }
  4822. void OBSBasic::EditSceneItemName()
  4823. {
  4824. int idx = GetTopSelectedSourceItem();
  4825. ui->sources->Edit(idx);
  4826. }
  4827. void OBSBasic::SetDeinterlacingMode()
  4828. {
  4829. QAction *action = reinterpret_cast<QAction *>(sender());
  4830. obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt();
  4831. OBSSceneItem sceneItem = GetCurrentSceneItem();
  4832. obs_source_t *source = obs_sceneitem_get_source(sceneItem);
  4833. obs_source_set_deinterlace_mode(source, mode);
  4834. }
  4835. void OBSBasic::SetDeinterlacingOrder()
  4836. {
  4837. QAction *action = reinterpret_cast<QAction *>(sender());
  4838. obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt();
  4839. OBSSceneItem sceneItem = GetCurrentSceneItem();
  4840. obs_source_t *source = obs_sceneitem_get_source(sceneItem);
  4841. obs_source_set_deinterlace_field_order(source, order);
  4842. }
  4843. QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source)
  4844. {
  4845. obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source);
  4846. obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source);
  4847. QAction *action;
  4848. #define ADD_MODE(name, mode) \
  4849. action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \
  4850. action->setProperty("mode", (int)mode); \
  4851. action->setCheckable(true); \
  4852. action->setChecked(deinterlaceMode == mode);
  4853. ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE);
  4854. ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD);
  4855. ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO);
  4856. ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND);
  4857. ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X);
  4858. ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR);
  4859. ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X);
  4860. ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF);
  4861. ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X);
  4862. #undef ADD_MODE
  4863. menu->addSeparator();
  4864. #define ADD_ORDER(name, order) \
  4865. action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \
  4866. action->setProperty("order", (int)order); \
  4867. action->setCheckable(true); \
  4868. action->setChecked(deinterlaceOrder == order);
  4869. ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP);
  4870. ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM);
  4871. #undef ADD_ORDER
  4872. return menu;
  4873. }
  4874. void OBSBasic::SetScaleFilter()
  4875. {
  4876. QAction *action = reinterpret_cast<QAction *>(sender());
  4877. obs_scale_type mode = (obs_scale_type)action->property("mode").toInt();
  4878. OBSSceneItem sceneItem = GetCurrentSceneItem();
  4879. obs_sceneitem_set_scale_filter(sceneItem, mode);
  4880. }
  4881. QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item)
  4882. {
  4883. obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item);
  4884. QAction *action;
  4885. #define ADD_MODE(name, mode) \
  4886. action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \
  4887. action->setProperty("mode", (int)mode); \
  4888. action->setCheckable(true); \
  4889. action->setChecked(scaleFilter == mode);
  4890. ADD_MODE("Disable", OBS_SCALE_DISABLE);
  4891. ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT);
  4892. ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR);
  4893. ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC);
  4894. ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS);
  4895. ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA);
  4896. #undef ADD_MODE
  4897. return menu;
  4898. }
  4899. void OBSBasic::SetBlendingMethod()
  4900. {
  4901. QAction *action = reinterpret_cast<QAction *>(sender());
  4902. obs_blending_method method = (obs_blending_method)action->property("method").toInt();
  4903. OBSSceneItem sceneItem = GetCurrentSceneItem();
  4904. obs_sceneitem_set_blending_method(sceneItem, method);
  4905. }
  4906. QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item)
  4907. {
  4908. obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item);
  4909. QAction *action;
  4910. #define ADD_MODE(name, method) \
  4911. action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \
  4912. action->setProperty("method", (int)method); \
  4913. action->setCheckable(true); \
  4914. action->setChecked(blendingMethod == method);
  4915. ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT);
  4916. ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF);
  4917. #undef ADD_MODE
  4918. return menu;
  4919. }
  4920. void OBSBasic::SetBlendingMode()
  4921. {
  4922. QAction *action = reinterpret_cast<QAction *>(sender());
  4923. obs_blending_type mode = (obs_blending_type)action->property("mode").toInt();
  4924. OBSSceneItem sceneItem = GetCurrentSceneItem();
  4925. obs_sceneitem_set_blending_mode(sceneItem, mode);
  4926. }
  4927. QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item)
  4928. {
  4929. obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item);
  4930. QAction *action;
  4931. #define ADD_MODE(name, mode) \
  4932. action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \
  4933. action->setProperty("mode", (int)mode); \
  4934. action->setCheckable(true); \
  4935. action->setChecked(blendingMode == mode);
  4936. ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL);
  4937. ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE);
  4938. ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT);
  4939. ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN);
  4940. ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY);
  4941. ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN);
  4942. ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN);
  4943. #undef ADD_MODE
  4944. return menu;
  4945. }
  4946. QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select,
  4947. obs_sceneitem_t *item)
  4948. {
  4949. QAction *action;
  4950. menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}"
  4951. "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}"
  4952. "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}"
  4953. "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}"
  4954. "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}"
  4955. "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}"
  4956. "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}"
  4957. "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}"));
  4958. obs_data_t *privData = obs_sceneitem_get_private_settings(item);
  4959. obs_data_release(privData);
  4960. obs_data_set_default_int(privData, "color-preset", 0);
  4961. int preset = obs_data_get_int(privData, "color-preset");
  4962. action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange);
  4963. action->setCheckable(true);
  4964. action->setProperty("bgColor", 0);
  4965. action->setChecked(preset == 0);
  4966. action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange);
  4967. action->setCheckable(true);
  4968. action->setProperty("bgColor", 1);
  4969. action->setChecked(preset == 1);
  4970. menu->addSeparator();
  4971. widgetAction->setDefaultWidget(select);
  4972. for (int i = 1; i < 9; i++) {
  4973. stringstream button;
  4974. button << "preset" << i;
  4975. QPushButton *colorButton = select->findChild<QPushButton *>(button.str().c_str());
  4976. if (preset == i + 1)
  4977. colorButton->setStyleSheet("border: 2px solid black");
  4978. colorButton->setProperty("bgColor", i);
  4979. select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange);
  4980. }
  4981. menu->addAction(widgetAction);
  4982. return menu;
  4983. }
  4984. ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect)
  4985. {
  4986. ui->setupUi(this);
  4987. }
  4988. void OBSBasic::CreateSourcePopupMenu(int idx, bool preview)
  4989. {
  4990. QMenu popup(this);
  4991. delete previewProjectorSource;
  4992. delete sourceProjector;
  4993. delete scaleFilteringMenu;
  4994. delete blendingMethodMenu;
  4995. delete blendingModeMenu;
  4996. delete colorMenu;
  4997. delete colorWidgetAction;
  4998. delete colorSelect;
  4999. delete deinterlaceMenu;
  5000. if (preview) {
  5001. QAction *action =
  5002. popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview);
  5003. action->setCheckable(true);
  5004. action->setChecked(obs_display_enabled(ui->preview->GetDisplay()));
  5005. if (IsPreviewProgramMode())
  5006. action->setEnabled(false);
  5007. popup.addAction(ui->actionLockPreview);
  5008. popup.addMenu(ui->scalingMenu);
  5009. previewProjectorSource = new QMenu(QTStr("PreviewProjector"));
  5010. AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector);
  5011. popup.addMenu(previewProjectorSource);
  5012. QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow);
  5013. popup.addAction(previewWindow);
  5014. popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene);
  5015. popup.addSeparator();
  5016. }
  5017. QPointer<QMenu> addSourceMenu = CreateAddSourcePopupMenu();
  5018. if (addSourceMenu)
  5019. popup.addMenu(addSourceMenu);
  5020. if (ui->sources->MultipleBaseSelected()) {
  5021. popup.addSeparator();
  5022. popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems);
  5023. } else if (ui->sources->GroupsSelected()) {
  5024. popup.addSeparator();
  5025. popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups);
  5026. }
  5027. popup.addSeparator();
  5028. popup.addAction(ui->actionCopySource);
  5029. popup.addAction(ui->actionPasteRef);
  5030. popup.addAction(ui->actionPasteDup);
  5031. popup.addSeparator();
  5032. popup.addSeparator();
  5033. popup.addAction(ui->actionCopyFilters);
  5034. popup.addAction(ui->actionPasteFilters);
  5035. popup.addSeparator();
  5036. if (idx != -1) {
  5037. if (addSourceMenu)
  5038. popup.addSeparator();
  5039. OBSSceneItem sceneItem = ui->sources->Get(idx);
  5040. obs_source_t *source = obs_sceneitem_get_source(sceneItem);
  5041. uint32_t flags = obs_source_get_output_flags(source);
  5042. bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO;
  5043. bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO;
  5044. bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO;
  5045. colorMenu = new QMenu(QTStr("ChangeBG"));
  5046. colorWidgetAction = new QWidgetAction(colorMenu);
  5047. colorSelect = new ColorSelect(colorMenu);
  5048. popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem));
  5049. popup.addAction(renameSource);
  5050. popup.addAction(ui->actionRemoveSource);
  5051. popup.addSeparator();
  5052. popup.addMenu(ui->orderMenu);
  5053. if (hasVideo)
  5054. popup.addMenu(ui->transformMenu);
  5055. popup.addSeparator();
  5056. if (hasAudio) {
  5057. QAction *actionHideMixer =
  5058. popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer);
  5059. actionHideMixer->setCheckable(true);
  5060. actionHideMixer->setChecked(SourceMixerHidden(source));
  5061. popup.addSeparator();
  5062. }
  5063. if (hasVideo) {
  5064. QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this,
  5065. &OBSBasic::ResizeOutputSizeOfSource);
  5066. int width = obs_source_get_width(source);
  5067. int height = obs_source_get_height(source);
  5068. resizeOutput->setEnabled(!obs_video_active());
  5069. if (width < 32 || height < 32)
  5070. resizeOutput->setEnabled(false);
  5071. scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering"));
  5072. popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem));
  5073. blendingModeMenu = new QMenu(QTStr("BlendingMode"));
  5074. popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem));
  5075. blendingMethodMenu = new QMenu(QTStr("BlendingMethod"));
  5076. popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem));
  5077. if (isAsyncVideo) {
  5078. deinterlaceMenu = new QMenu(QTStr("Deinterlacing"));
  5079. popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source));
  5080. }
  5081. popup.addSeparator();
  5082. popup.addMenu(CreateVisibilityTransitionMenu(true));
  5083. popup.addMenu(CreateVisibilityTransitionMenu(false));
  5084. popup.addSeparator();
  5085. sourceProjector = new QMenu(QTStr("SourceProjector"));
  5086. AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector);
  5087. popup.addMenu(sourceProjector);
  5088. popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow);
  5089. popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource);
  5090. }
  5091. popup.addSeparator();
  5092. if (flags & OBS_SOURCE_INTERACTION)
  5093. popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered);
  5094. popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); });
  5095. QAction *action =
  5096. popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered);
  5097. action->setEnabled(obs_source_configurable(source));
  5098. }
  5099. popup.exec(QCursor::pos());
  5100. }
  5101. void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
  5102. {
  5103. if (ui->scenes->count()) {
  5104. QModelIndex idx = ui->sources->indexAt(pos);
  5105. CreateSourcePopupMenu(idx.row(), false);
  5106. }
  5107. }
  5108. void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem)
  5109. {
  5110. if (!witem)
  5111. return;
  5112. if (IsPreviewProgramMode()) {
  5113. bool doubleClickSwitch =
  5114. config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick");
  5115. if (doubleClickSwitch)
  5116. TransitionClicked();
  5117. }
  5118. }
  5119. static inline bool should_show_properties(obs_source_t *source, const char *id)
  5120. {
  5121. if (!source)
  5122. return false;
  5123. if (strcmp(id, "group") == 0)
  5124. return false;
  5125. if (!obs_source_configurable(source))
  5126. return false;
  5127. uint32_t caps = obs_source_get_output_flags(source);
  5128. if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0)
  5129. return false;
  5130. return true;
  5131. }
  5132. void OBSBasic::AddSource(const char *id)
  5133. {
  5134. if (id && *id) {
  5135. OBSBasicSourceSelect sourceSelect(this, id, undo_s);
  5136. sourceSelect.exec();
  5137. if (should_show_properties(sourceSelect.newSource, id)) {
  5138. CreatePropertiesWindow(sourceSelect.newSource);
  5139. }
  5140. }
  5141. }
  5142. QMenu *OBSBasic::CreateAddSourcePopupMenu()
  5143. {
  5144. const char *unversioned_type;
  5145. const char *type;
  5146. bool foundValues = false;
  5147. bool foundDeprecated = false;
  5148. size_t idx = 0;
  5149. QMenu *popup = new QMenu(QTStr("Add"), this);
  5150. QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup);
  5151. auto getActionAfter = [](QMenu *menu, const QString &name) {
  5152. QList<QAction *> actions = menu->actions();
  5153. for (QAction *menuAction : actions) {
  5154. if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0)
  5155. return menuAction;
  5156. }
  5157. return (QAction *)nullptr;
  5158. };
  5159. auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) {
  5160. QString qname = QT_UTF8(name);
  5161. QAction *popupItem = new QAction(qname, this);
  5162. connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); });
  5163. QIcon icon;
  5164. if (strcmp(type, "scene") == 0)
  5165. icon = GetSceneIcon();
  5166. else
  5167. icon = GetSourceIcon(type);
  5168. popupItem->setIcon(icon);
  5169. QAction *after = getActionAfter(popup, qname);
  5170. popup->insertAction(after, popupItem);
  5171. };
  5172. while (obs_enum_input_types2(idx++, &type, &unversioned_type)) {
  5173. const char *name = obs_source_get_display_name(type);
  5174. uint32_t caps = obs_get_source_output_flags(type);
  5175. if ((caps & OBS_SOURCE_CAP_DISABLED) != 0)
  5176. continue;
  5177. if ((caps & OBS_SOURCE_DEPRECATED) == 0) {
  5178. addSource(popup, unversioned_type, name);
  5179. } else {
  5180. addSource(deprecated, unversioned_type, name);
  5181. foundDeprecated = true;
  5182. }
  5183. foundValues = true;
  5184. }
  5185. addSource(popup, "scene", Str("Basic.Scene"));
  5186. popup->addSeparator();
  5187. QAction *addGroup = new QAction(QTStr("Group"), this);
  5188. addGroup->setIcon(GetGroupIcon());
  5189. connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); });
  5190. popup->addAction(addGroup);
  5191. if (!foundDeprecated) {
  5192. delete deprecated;
  5193. deprecated = nullptr;
  5194. }
  5195. if (!foundValues) {
  5196. delete popup;
  5197. popup = nullptr;
  5198. } else if (foundDeprecated) {
  5199. popup->addSeparator();
  5200. popup->addMenu(deprecated);
  5201. }
  5202. return popup;
  5203. }
  5204. void OBSBasic::AddSourcePopupMenu(const QPoint &pos)
  5205. {
  5206. if (!GetCurrentScene()) {
  5207. // Tell the user he needs a scene first (help beginners).
  5208. OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"),
  5209. QTStr("Basic.Main.AddSourceHelp.Text"));
  5210. return;
  5211. }
  5212. QScopedPointer<QMenu> popup(CreateAddSourcePopupMenu());
  5213. if (popup)
  5214. popup->exec(pos);
  5215. }
  5216. void OBSBasic::on_actionAddSource_triggered()
  5217. {
  5218. AddSourcePopupMenu(QCursor::pos());
  5219. }
  5220. static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param)
  5221. {
  5222. vector<OBSSceneItem> &items = *reinterpret_cast<vector<OBSSceneItem> *>(param);
  5223. if (obs_sceneitem_selected(item)) {
  5224. items.emplace_back(item);
  5225. } else if (obs_sceneitem_is_group(item)) {
  5226. obs_sceneitem_group_enum_items(item, remove_items, &items);
  5227. }
  5228. return true;
  5229. };
  5230. OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector<obs_source_t *> *sources)
  5231. {
  5232. OBSDataArrayAutoRelease undo_array = obs_data_array_create();
  5233. if (!sources) {
  5234. obs_scene_enum_items(scene, save_undo_source_enum, undo_array);
  5235. } else {
  5236. for (obs_source_t *source : *sources) {
  5237. obs_data_t *source_data = obs_save_source(source);
  5238. obs_data_array_push_back(undo_array, source_data);
  5239. obs_data_release(source_data);
  5240. }
  5241. }
  5242. OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene));
  5243. obs_data_array_push_back(undo_array, scene_data);
  5244. OBSDataAutoRelease data = obs_data_create();
  5245. obs_data_set_array(data, "array", undo_array);
  5246. obs_data_get_json(data);
  5247. return data.Get();
  5248. }
  5249. static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p)
  5250. {
  5251. auto sources = static_cast<std::vector<OBSSource> *>(p);
  5252. sources->push_back(obs_sceneitem_get_source(item));
  5253. return true;
  5254. }
  5255. void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data)
  5256. {
  5257. auto undo_redo = [this](const std::string &json) {
  5258. OBSDataAutoRelease base = obs_data_create_from_json(json.c_str());
  5259. OBSDataArrayAutoRelease array = obs_data_get_array(base, "array");
  5260. std::vector<OBSSource> sources;
  5261. std::vector<OBSSource> old_sources;
  5262. /* create missing sources */
  5263. const size_t count = obs_data_array_count(array);
  5264. sources.reserve(count);
  5265. for (size_t i = 0; i < count; i++) {
  5266. OBSDataAutoRelease data = obs_data_array_item(array, i);
  5267. const char *name = obs_data_get_string(data, "name");
  5268. OBSSourceAutoRelease source = obs_get_source_by_name(name);
  5269. if (!source)
  5270. source = obs_load_source(data);
  5271. sources.push_back(source.Get());
  5272. /* update scene/group settings to restore their
  5273. * contents to their saved settings */
  5274. obs_scene_t *scene = obs_group_or_scene_from_source(source);
  5275. if (scene) {
  5276. obs_scene_enum_items(scene, add_source_enum, &old_sources);
  5277. OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings");
  5278. obs_source_update(source, scene_settings);
  5279. }
  5280. }
  5281. /* actually load sources now */
  5282. for (obs_source_t *source : sources)
  5283. obs_source_load2(source);
  5284. ui->sources->RefreshItems();
  5285. };
  5286. const char *undo_json = obs_data_get_last_json(undo_data);
  5287. const char *redo_json = obs_data_get_last_json(redo_data);
  5288. undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json);
  5289. }
  5290. void OBSBasic::on_actionRemoveSource_triggered()
  5291. {
  5292. vector<OBSSceneItem> items;
  5293. OBSScene scene = GetCurrentScene();
  5294. obs_source_t *scene_source = obs_scene_get_source(scene);
  5295. obs_scene_enum_items(scene, remove_items, &items);
  5296. if (!items.size())
  5297. return;
  5298. /* ------------------------------------- */
  5299. /* confirm action with user */
  5300. bool confirmed = false;
  5301. if (items.size() > 1) {
  5302. QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size()));
  5303. QMessageBox remove_items(this);
  5304. remove_items.setText(text);
  5305. QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole);
  5306. remove_items.setDefaultButton(Yes);
  5307. remove_items.addButton(QTStr("No"), QMessageBox::NoRole);
  5308. remove_items.setIcon(QMessageBox::Question);
  5309. remove_items.setWindowTitle(QTStr("ConfirmRemove.Title"));
  5310. remove_items.exec();
  5311. confirmed = Yes == remove_items.clickedButton();
  5312. } else {
  5313. OBSSceneItem &item = items[0];
  5314. obs_source_t *source = obs_sceneitem_get_source(item);
  5315. if (source && QueryRemoveSource(source))
  5316. confirmed = true;
  5317. }
  5318. if (!confirmed)
  5319. return;
  5320. /* ----------------------------------------------- */
  5321. /* save undo data */
  5322. OBSData undo_data = BackupScene(scene_source);
  5323. /* ----------------------------------------------- */
  5324. /* remove items */
  5325. for (auto &item : items)
  5326. obs_sceneitem_remove(item);
  5327. /* ----------------------------------------------- */
  5328. /* save redo data */
  5329. OBSData redo_data = BackupScene(scene_source);
  5330. /* ----------------------------------------------- */
  5331. /* add undo/redo action */
  5332. QString action_name;
  5333. if (items.size() > 1) {
  5334. action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size()));
  5335. } else {
  5336. QString str = QTStr("Undo.Delete");
  5337. action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0])));
  5338. }
  5339. CreateSceneUndoRedoAction(action_name, undo_data, redo_data);
  5340. }
  5341. void OBSBasic::on_actionInteract_triggered()
  5342. {
  5343. OBSSceneItem item = GetCurrentSceneItem();
  5344. OBSSource source = obs_sceneitem_get_source(item);
  5345. if (source)
  5346. CreateInteractionWindow(source);
  5347. }
  5348. void OBSBasic::on_actionSourceProperties_triggered()
  5349. {
  5350. OBSSceneItem item = GetCurrentSceneItem();
  5351. OBSSource source = obs_sceneitem_get_source(item);
  5352. if (source)
  5353. CreatePropertiesWindow(source);
  5354. }
  5355. void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name)
  5356. {
  5357. OBSSceneItem item = GetCurrentSceneItem();
  5358. obs_source_t *source = obs_sceneitem_get_source(item);
  5359. if (!source)
  5360. return;
  5361. OBSScene scene = GetCurrentScene();
  5362. std::vector<obs_source_t *> sources;
  5363. if (scene != obs_sceneitem_get_scene(item))
  5364. sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item)));
  5365. OBSData undo_data = BackupScene(scene, &sources);
  5366. obs_sceneitem_set_order(item, movement);
  5367. const char *source_name = obs_source_get_name(source);
  5368. const char *scene_name = obs_source_get_name(obs_scene_get_source(scene));
  5369. OBSData redo_data = BackupScene(scene, &sources);
  5370. CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data);
  5371. }
  5372. void OBSBasic::on_actionSourceUp_triggered()
  5373. {
  5374. MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp"));
  5375. }
  5376. void OBSBasic::on_actionSourceDown_triggered()
  5377. {
  5378. MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown"));
  5379. }
  5380. void OBSBasic::on_actionMoveUp_triggered()
  5381. {
  5382. MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp"));
  5383. }
  5384. void OBSBasic::on_actionMoveDown_triggered()
  5385. {
  5386. MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown"));
  5387. }
  5388. void OBSBasic::on_actionMoveToTop_triggered()
  5389. {
  5390. MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop"));
  5391. }
  5392. void OBSBasic::on_actionMoveToBottom_triggered()
  5393. {
  5394. MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom"));
  5395. }
  5396. static BPtr<char> ReadLogFile(const char *subdir, const char *log)
  5397. {
  5398. char logDir[512];
  5399. if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0)
  5400. return nullptr;
  5401. string path = logDir;
  5402. path += "/";
  5403. path += log;
  5404. BPtr<char> file = os_quick_read_utf8_file(path.c_str());
  5405. if (!file)
  5406. blog(LOG_WARNING, "Failed to read log file %s", path.c_str());
  5407. return file;
  5408. }
  5409. void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash)
  5410. {
  5411. BPtr<char> fileString{ReadLogFile(subdir, file)};
  5412. if (!fileString)
  5413. return;
  5414. if (!*fileString)
  5415. return;
  5416. ui->menuLogFiles->setEnabled(false);
  5417. #if defined(_WIN32)
  5418. ui->menuCrashLogs->setEnabled(false);
  5419. #endif
  5420. stringstream ss;
  5421. ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n"
  5422. << fileString;
  5423. if (logUploadThread) {
  5424. logUploadThread->wait();
  5425. }
  5426. RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str());
  5427. logUploadThread.reset(thread);
  5428. if (crash) {
  5429. connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished);
  5430. } else {
  5431. connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished);
  5432. }
  5433. logUploadThread->start();
  5434. }
  5435. void OBSBasic::on_actionShowLogs_triggered()
  5436. {
  5437. char logDir[512];
  5438. if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
  5439. return;
  5440. QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir));
  5441. QDesktopServices::openUrl(url);
  5442. }
  5443. void OBSBasic::on_actionUploadCurrentLog_triggered()
  5444. {
  5445. UploadLog("obs-studio/logs", App()->GetCurrentLog(), false);
  5446. }
  5447. void OBSBasic::on_actionUploadLastLog_triggered()
  5448. {
  5449. UploadLog("obs-studio/logs", App()->GetLastLog(), false);
  5450. }
  5451. void OBSBasic::on_actionViewCurrentLog_triggered()
  5452. {
  5453. if (!logView)
  5454. logView = new OBSLogViewer();
  5455. logView->show();
  5456. logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
  5457. logView->activateWindow();
  5458. logView->raise();
  5459. }
  5460. void OBSBasic::on_actionShowCrashLogs_triggered()
  5461. {
  5462. char logDir[512];
  5463. if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0)
  5464. return;
  5465. QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir));
  5466. QDesktopServices::openUrl(url);
  5467. }
  5468. void OBSBasic::on_actionUploadLastCrashLog_triggered()
  5469. {
  5470. UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true);
  5471. }
  5472. void OBSBasic::on_actionCheckForUpdates_triggered()
  5473. {
  5474. CheckForUpdates(true);
  5475. }
  5476. void OBSBasic::on_actionRepair_triggered()
  5477. {
  5478. #if defined(_WIN32)
  5479. ui->actionCheckForUpdates->setEnabled(false);
  5480. ui->actionRepair->setEnabled(false);
  5481. if (updateCheckThread && updateCheckThread->isRunning())
  5482. return;
  5483. updateCheckThread.reset(new AutoUpdateThread(false, true));
  5484. updateCheckThread->start();
  5485. #endif
  5486. }
  5487. void OBSBasic::on_actionRestartSafe_triggered()
  5488. {
  5489. QMessageBox::StandardButton button = OBSMessageBox::question(
  5490. this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart"));
  5491. if (button == QMessageBox::Yes) {
  5492. restart = safe_mode;
  5493. restart_safe = !safe_mode;
  5494. close();
  5495. }
  5496. }
  5497. void OBSBasic::logUploadFinished(const QString &text, const QString &error)
  5498. {
  5499. ui->menuLogFiles->setEnabled(true);
  5500. #if defined(_WIN32)
  5501. ui->menuCrashLogs->setEnabled(true);
  5502. #endif
  5503. if (text.isEmpty()) {
  5504. OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error);
  5505. return;
  5506. }
  5507. openLogDialog(text, false);
  5508. }
  5509. void OBSBasic::crashUploadFinished(const QString &text, const QString &error)
  5510. {
  5511. ui->menuLogFiles->setEnabled(true);
  5512. #if defined(_WIN32)
  5513. ui->menuCrashLogs->setEnabled(true);
  5514. #endif
  5515. if (text.isEmpty()) {
  5516. OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error);
  5517. return;
  5518. }
  5519. openLogDialog(text, true);
  5520. }
  5521. void OBSBasic::openLogDialog(const QString &text, const bool crash)
  5522. {
  5523. OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text));
  5524. string resURL = obs_data_get_string(returnData, "url");
  5525. QString logURL = resURL.c_str();
  5526. OBSLogReply logDialog(this, logURL, crash);
  5527. logDialog.exec();
  5528. }
  5529. static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name)
  5530. {
  5531. const char *prevName = obs_source_get_name(source);
  5532. if (name == prevName)
  5533. return;
  5534. OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str());
  5535. QListWidgetItem *listItem = listWidget->currentItem();
  5536. if (foundSource || name.empty()) {
  5537. listItem->setText(QT_UTF8(prevName));
  5538. if (foundSource) {
  5539. OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  5540. } else if (name.empty()) {
  5541. OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  5542. }
  5543. } else {
  5544. auto undo = [prev = std::string(prevName)](const std::string &data) {
  5545. OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
  5546. obs_source_set_name(source, prev.c_str());
  5547. };
  5548. auto redo = [name](const std::string &data) {
  5549. OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
  5550. obs_source_set_name(source, name.c_str());
  5551. };
  5552. std::string source_uuid(obs_source_get_uuid(source));
  5553. parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid);
  5554. listItem->setText(QT_UTF8(name.c_str()));
  5555. obs_source_set_name(source, name.c_str());
  5556. }
  5557. }
  5558. void OBSBasic::SceneNameEdited(QWidget *editor)
  5559. {
  5560. OBSScene scene = GetCurrentScene();
  5561. QLineEdit *edit = qobject_cast<QLineEdit *>(editor);
  5562. string text = QT_TO_UTF8(edit->text().trimmed());
  5563. if (!scene)
  5564. return;
  5565. obs_source_t *source = obs_scene_get_source(scene);
  5566. RenameListItem(this, ui->scenes, source, text);
  5567. ui->scenesDock->addAction(renameScene);
  5568. OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
  5569. }
  5570. void OBSBasic::OpenFilters(OBSSource source)
  5571. {
  5572. if (source == nullptr) {
  5573. OBSSceneItem item = GetCurrentSceneItem();
  5574. source = obs_sceneitem_get_source(item);
  5575. }
  5576. CreateFiltersWindow(source);
  5577. }
  5578. void OBSBasic::OpenProperties(OBSSource source)
  5579. {
  5580. if (source == nullptr) {
  5581. OBSSceneItem item = GetCurrentSceneItem();
  5582. source = obs_sceneitem_get_source(item);
  5583. }
  5584. CreatePropertiesWindow(source);
  5585. }
  5586. void OBSBasic::OpenInteraction(OBSSource source)
  5587. {
  5588. if (source == nullptr) {
  5589. OBSSceneItem item = GetCurrentSceneItem();
  5590. source = obs_sceneitem_get_source(item);
  5591. }
  5592. CreateInteractionWindow(source);
  5593. }
  5594. void OBSBasic::OpenEditTransform(OBSSceneItem item)
  5595. {
  5596. if (!item)
  5597. item = GetCurrentSceneItem();
  5598. if (!item)
  5599. return;
  5600. CreateEditTransformWindow(item);
  5601. }
  5602. void OBSBasic::OpenSceneFilters()
  5603. {
  5604. OBSScene scene = GetCurrentScene();
  5605. OBSSource source = obs_scene_get_source(scene);
  5606. CreateFiltersWindow(source);
  5607. }
  5608. #define RECORDING_START "==== Recording Start ==============================================="
  5609. #define RECORDING_STOP "==== Recording Stop ================================================"
  5610. #define REPLAY_BUFFER_START "==== Replay Buffer Start ==========================================="
  5611. #define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================"
  5612. #define STREAMING_START "==== Streaming Start ==============================================="
  5613. #define STREAMING_STOP "==== Streaming Stop ================================================"
  5614. #define VIRTUAL_CAM_START "==== Virtual Camera Start =========================================="
  5615. #define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ==========================================="
  5616. void OBSBasic::DisplayStreamStartError()
  5617. {
  5618. QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str())
  5619. : QTStr("Output.StartFailedGeneric");
  5620. emit StreamingStopped();
  5621. if (sysTrayStream) {
  5622. sysTrayStream->setText(QTStr("Basic.Main.StartStreaming"));
  5623. sysTrayStream->setEnabled(true);
  5624. }
  5625. QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message);
  5626. }
  5627. #ifdef YOUTUBE_ENABLED
  5628. void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key,
  5629. bool autostart, bool autostop, bool start_now)
  5630. {
  5631. //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key));
  5632. obs_service_t *service_obj = GetService();
  5633. OBSDataAutoRelease settings = obs_service_get_settings(service_obj);
  5634. const std::string a_key = QT_TO_UTF8(key);
  5635. obs_data_set_string(settings, "key", a_key.c_str());
  5636. const std::string b_id = QT_TO_UTF8(broadcast_id);
  5637. obs_data_set_string(settings, "broadcast_id", b_id.c_str());
  5638. const std::string s_id = QT_TO_UTF8(stream_id);
  5639. obs_data_set_string(settings, "stream_id", s_id.c_str());
  5640. obs_service_update(service_obj, settings);
  5641. autoStartBroadcast = autostart;
  5642. autoStopBroadcast = autostop;
  5643. broadcastReady = true;
  5644. emit BroadcastStreamReady(broadcastReady);
  5645. if (start_now)
  5646. QMetaObject::invokeMethod(this, "StartStreaming");
  5647. }
  5648. void OBSBasic::YoutubeStreamCheck(const std::string &key)
  5649. {
  5650. YoutubeApiWrappers *apiYouTube(dynamic_cast<YoutubeApiWrappers *>(GetAuth()));
  5651. if (!apiYouTube) {
  5652. /* technically we should never get here -Lain */
  5653. QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection);
  5654. youtubeStreamCheckThread->deleteLater();
  5655. blog(LOG_ERROR, "==========================================");
  5656. blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__);
  5657. blog(LOG_ERROR, "==========================================");
  5658. return;
  5659. }
  5660. int timeout = 0;
  5661. json11::Json json;
  5662. QString id = key.c_str();
  5663. while (StreamingActive()) {
  5664. if (timeout == 14) {
  5665. QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection);
  5666. break;
  5667. }
  5668. if (!apiYouTube->FindStream(id, json)) {
  5669. QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection);
  5670. QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection);
  5671. break;
  5672. }
  5673. auto item = json["items"][0];
  5674. auto status = item["status"]["streamStatus"].string_value();
  5675. if (status == "active") {
  5676. emit BroadcastStreamActive();
  5677. break;
  5678. } else {
  5679. QThread::sleep(1);
  5680. timeout++;
  5681. }
  5682. }
  5683. youtubeStreamCheckThread->deleteLater();
  5684. }
  5685. void OBSBasic::ShowYouTubeAutoStartWarning()
  5686. {
  5687. auto msgBox = []() {
  5688. QMessageBox msgbox(App()->GetMainWindow());
  5689. msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title"));
  5690. msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning"));
  5691. msgbox.setIcon(QMessageBox::Icon::Information);
  5692. msgbox.addButton(QMessageBox::Ok);
  5693. QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain"));
  5694. msgbox.setCheckBox(cb);
  5695. msgbox.exec();
  5696. if (cb->isChecked()) {
  5697. config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true);
  5698. config_save_safe(App()->GetUserConfig(), "tmp", nullptr);
  5699. }
  5700. };
  5701. bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart");
  5702. if (!warned) {
  5703. QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox));
  5704. }
  5705. }
  5706. #endif
  5707. void OBSBasic::StartStreaming()
  5708. {
  5709. if (outputHandler->StreamingActive())
  5710. return;
  5711. if (disableOutputsRef)
  5712. return;
  5713. if (auth && auth->broadcastFlow()) {
  5714. if (!broadcastActive && !broadcastReady) {
  5715. QMessageBox no_broadcast(this);
  5716. no_broadcast.setText(QTStr("Output.NoBroadcast.Text"));
  5717. QPushButton *SetupBroadcast =
  5718. no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole);
  5719. no_broadcast.setDefaultButton(SetupBroadcast);
  5720. no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole);
  5721. no_broadcast.setIcon(QMessageBox::Information);
  5722. no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title"));
  5723. no_broadcast.exec();
  5724. if (no_broadcast.clickedButton() == SetupBroadcast)
  5725. QMetaObject::invokeMethod(this, "SetupBroadcast");
  5726. return;
  5727. }
  5728. }
  5729. emit StreamingPreparing();
  5730. if (sysTrayStream) {
  5731. sysTrayStream->setEnabled(false);
  5732. sysTrayStream->setText("Basic.Main.PreparingStream");
  5733. }
  5734. auto finish_stream_setup = [&](bool setupStreamingResult) {
  5735. if (!setupStreamingResult) {
  5736. DisplayStreamStartError();
  5737. return;
  5738. }
  5739. OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING);
  5740. SaveProject();
  5741. emit StreamingStarting(autoStartBroadcast);
  5742. if (sysTrayStream)
  5743. sysTrayStream->setText("Basic.Main.Connecting");
  5744. if (!outputHandler->StartStreaming(service)) {
  5745. DisplayStreamStartError();
  5746. return;
  5747. }
  5748. if (autoStartBroadcast) {
  5749. emit BroadcastStreamStarted(autoStopBroadcast);
  5750. broadcastActive = true;
  5751. }
  5752. bool recordWhenStreaming =
  5753. config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming");
  5754. if (recordWhenStreaming)
  5755. StartRecording();
  5756. bool replayBufferWhileStreaming =
  5757. config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
  5758. if (replayBufferWhileStreaming)
  5759. StartReplayBuffer();
  5760. #ifdef YOUTUBE_ENABLED
  5761. if (!autoStartBroadcast)
  5762. OBSBasic::ShowYouTubeAutoStartWarning();
  5763. #endif
  5764. };
  5765. setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup);
  5766. }
  5767. void OBSBasic::BroadcastButtonClicked()
  5768. {
  5769. if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) {
  5770. SetupBroadcast();
  5771. return;
  5772. }
  5773. if (!autoStartBroadcast) {
  5774. #ifdef YOUTUBE_ENABLED
  5775. std::shared_ptr<YoutubeApiWrappers> ytAuth = dynamic_pointer_cast<YoutubeApiWrappers>(auth);
  5776. if (ytAuth.get()) {
  5777. if (!ytAuth->StartLatestBroadcast()) {
  5778. auto last_error = ytAuth->GetLastError();
  5779. if (last_error.isEmpty())
  5780. last_error = QTStr("YouTube.Actions.Error.YouTubeApi");
  5781. if (!ytAuth->GetTranslatedError(last_error))
  5782. last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed")
  5783. .arg(last_error, ytAuth->GetBroadcastId());
  5784. OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true);
  5785. return;
  5786. }
  5787. }
  5788. #endif
  5789. broadcastActive = true;
  5790. autoStartBroadcast = true; // and clear the flag
  5791. emit BroadcastStreamStarted(autoStopBroadcast);
  5792. } else if (!autoStopBroadcast) {
  5793. #ifdef YOUTUBE_ENABLED
  5794. bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream");
  5795. if (confirm && isVisible()) {
  5796. QMessageBox::StandardButton button = OBSMessageBox::question(
  5797. this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"),
  5798. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  5799. if (button == QMessageBox::No)
  5800. return;
  5801. }
  5802. std::shared_ptr<YoutubeApiWrappers> ytAuth = dynamic_pointer_cast<YoutubeApiWrappers>(auth);
  5803. if (ytAuth.get()) {
  5804. if (!ytAuth->StopLatestBroadcast()) {
  5805. auto last_error = ytAuth->GetLastError();
  5806. if (last_error.isEmpty())
  5807. last_error = QTStr("YouTube.Actions.Error.YouTubeApi");
  5808. if (!ytAuth->GetTranslatedError(last_error))
  5809. last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed")
  5810. .arg(last_error, ytAuth->GetBroadcastId());
  5811. OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true);
  5812. }
  5813. }
  5814. #endif
  5815. broadcastActive = false;
  5816. broadcastReady = false;
  5817. autoStopBroadcast = true;
  5818. QMetaObject::invokeMethod(this, "StopStreaming");
  5819. emit BroadcastStreamReady(broadcastReady);
  5820. SetBroadcastFlowEnabled(true);
  5821. }
  5822. }
  5823. void OBSBasic::SetBroadcastFlowEnabled(bool enabled)
  5824. {
  5825. emit BroadcastFlowEnabled(enabled);
  5826. }
  5827. void OBSBasic::SetupBroadcast()
  5828. {
  5829. #ifdef YOUTUBE_ENABLED
  5830. Auth *const auth = GetAuth();
  5831. if (IsYouTubeService(auth->service())) {
  5832. OBSYoutubeActions dialog(this, auth, broadcastReady);
  5833. connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk);
  5834. dialog.exec();
  5835. }
  5836. #endif
  5837. }
  5838. #ifdef _WIN32
  5839. static inline void UpdateProcessPriority()
  5840. {
  5841. const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority");
  5842. if (priority && strcmp(priority, "Normal") != 0)
  5843. SetProcessPriority(priority);
  5844. }
  5845. static inline void ClearProcessPriority()
  5846. {
  5847. const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority");
  5848. if (priority && strcmp(priority, "Normal") != 0)
  5849. SetProcessPriority("Normal");
  5850. }
  5851. #else
  5852. #define UpdateProcessPriority() \
  5853. do { \
  5854. } while (false)
  5855. #define ClearProcessPriority() \
  5856. do { \
  5857. } while (false)
  5858. #endif
  5859. inline void OBSBasic::OnActivate(bool force)
  5860. {
  5861. if (ui->profileMenu->isEnabled() || force) {
  5862. ui->profileMenu->setEnabled(false);
  5863. ui->autoConfigure->setEnabled(false);
  5864. App()->IncrementSleepInhibition();
  5865. UpdateProcessPriority();
  5866. struct obs_video_info ovi;
  5867. obs_get_video_info(&ovi);
  5868. lastOutputResolution = {ovi.base_width, ovi.base_height};
  5869. TaskbarOverlaySetStatus(TaskbarOverlayStatusActive);
  5870. if (trayIcon && trayIcon->isVisible()) {
  5871. #ifdef __APPLE__
  5872. QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg");
  5873. trayMask.setIsMask(true);
  5874. trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask));
  5875. #else
  5876. trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png")));
  5877. #endif
  5878. }
  5879. }
  5880. }
  5881. extern volatile bool recording_paused;
  5882. extern volatile bool replaybuf_active;
  5883. inline void OBSBasic::OnDeactivate()
  5884. {
  5885. if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
  5886. ui->profileMenu->setEnabled(true);
  5887. ui->autoConfigure->setEnabled(true);
  5888. App()->DecrementSleepInhibition();
  5889. ClearProcessPriority();
  5890. TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive);
  5891. if (trayIcon && trayIcon->isVisible()) {
  5892. #ifdef __APPLE__
  5893. QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg");
  5894. trayIconFile.setIsMask(true);
  5895. #else
  5896. QIcon trayIconFile = QIcon(":/res/images/obs.png");
  5897. #endif
  5898. trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile));
  5899. }
  5900. } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) {
  5901. if (os_atomic_load_bool(&recording_paused)) {
  5902. #ifdef __APPLE__
  5903. QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg");
  5904. trayIconFile.setIsMask(true);
  5905. #else
  5906. QIcon trayIconFile = QIcon(":/res/images/obs_paused.png");
  5907. #endif
  5908. trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile));
  5909. TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused);
  5910. } else {
  5911. #ifdef __APPLE__
  5912. QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg");
  5913. trayIconFile.setIsMask(true);
  5914. #else
  5915. QIcon trayIconFile = QIcon(":/res/images/tray_active.png");
  5916. #endif
  5917. trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile));
  5918. TaskbarOverlaySetStatus(TaskbarOverlayStatusActive);
  5919. }
  5920. }
  5921. }
  5922. void OBSBasic::StopStreaming()
  5923. {
  5924. SaveProject();
  5925. if (outputHandler->StreamingActive())
  5926. outputHandler->StopStreaming(streamingStopping);
  5927. // special case: force reset broadcast state if
  5928. // no autostart and no autostop selected
  5929. if (!autoStartBroadcast && !broadcastActive) {
  5930. broadcastActive = false;
  5931. autoStartBroadcast = true;
  5932. autoStopBroadcast = true;
  5933. broadcastReady = false;
  5934. }
  5935. if (autoStopBroadcast) {
  5936. broadcastActive = false;
  5937. broadcastReady = false;
  5938. }
  5939. emit BroadcastStreamReady(broadcastReady);
  5940. OnDeactivate();
  5941. bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming");
  5942. bool keepRecordingWhenStreamStops =
  5943. config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops");
  5944. if (recordWhenStreaming && !keepRecordingWhenStreamStops)
  5945. StopRecording();
  5946. bool replayBufferWhileStreaming =
  5947. config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
  5948. bool keepReplayBufferStreamStops =
  5949. config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops");
  5950. if (replayBufferWhileStreaming && !keepReplayBufferStreamStops)
  5951. StopReplayBuffer();
  5952. }
  5953. void OBSBasic::ForceStopStreaming()
  5954. {
  5955. SaveProject();
  5956. if (outputHandler->StreamingActive())
  5957. outputHandler->StopStreaming(true);
  5958. // special case: force reset broadcast state if
  5959. // no autostart and no autostop selected
  5960. if (!autoStartBroadcast && !broadcastActive) {
  5961. broadcastActive = false;
  5962. autoStartBroadcast = true;
  5963. autoStopBroadcast = true;
  5964. broadcastReady = false;
  5965. }
  5966. if (autoStopBroadcast) {
  5967. broadcastActive = false;
  5968. broadcastReady = false;
  5969. }
  5970. emit BroadcastStreamReady(broadcastReady);
  5971. OnDeactivate();
  5972. bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming");
  5973. bool keepRecordingWhenStreamStops =
  5974. config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops");
  5975. if (recordWhenStreaming && !keepRecordingWhenStreamStops)
  5976. StopRecording();
  5977. bool replayBufferWhileStreaming =
  5978. config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
  5979. bool keepReplayBufferStreamStops =
  5980. config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops");
  5981. if (replayBufferWhileStreaming && !keepReplayBufferStreamStops)
  5982. StopReplayBuffer();
  5983. }
  5984. void OBSBasic::StreamDelayStarting(int sec)
  5985. {
  5986. emit StreamingStarted(true);
  5987. if (sysTrayStream) {
  5988. sysTrayStream->setText(QTStr("Basic.Main.StopStreaming"));
  5989. sysTrayStream->setEnabled(true);
  5990. }
  5991. ui->statusbar->StreamDelayStarting(sec);
  5992. OnActivate();
  5993. }
  5994. void OBSBasic::StreamDelayStopping(int sec)
  5995. {
  5996. emit StreamingStopped(true);
  5997. if (sysTrayStream) {
  5998. sysTrayStream->setText(QTStr("Basic.Main.StartStreaming"));
  5999. sysTrayStream->setEnabled(true);
  6000. }
  6001. ui->statusbar->StreamDelayStopping(sec);
  6002. OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING);
  6003. }
  6004. void OBSBasic::StreamingStart()
  6005. {
  6006. emit StreamingStarted();
  6007. OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
  6008. ui->statusbar->StreamStarted(output);
  6009. if (sysTrayStream) {
  6010. sysTrayStream->setText(QTStr("Basic.Main.StopStreaming"));
  6011. sysTrayStream->setEnabled(true);
  6012. }
  6013. #ifdef YOUTUBE_ENABLED
  6014. if (!autoStartBroadcast) {
  6015. // get a current stream key
  6016. obs_service_t *service_obj = GetService();
  6017. OBSDataAutoRelease settings = obs_service_get_settings(service_obj);
  6018. std::string key = obs_data_get_string(settings, "stream_id");
  6019. if (!key.empty() && !youtubeStreamCheckThread) {
  6020. youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); });
  6021. youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread");
  6022. youtubeStreamCheckThread->start();
  6023. }
  6024. }
  6025. #endif
  6026. OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED);
  6027. OnActivate();
  6028. #ifdef YOUTUBE_ENABLED
  6029. if (YouTubeAppDock::IsYTServiceSelected())
  6030. youtubeAppDock->IngestionStarted();
  6031. #endif
  6032. blog(LOG_INFO, STREAMING_START);
  6033. }
  6034. void OBSBasic::StreamStopping()
  6035. {
  6036. emit StreamingStopping();
  6037. if (sysTrayStream)
  6038. sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming"));
  6039. streamingStopping = true;
  6040. OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING);
  6041. }
  6042. void OBSBasic::StreamingStop(int code, QString last_error)
  6043. {
  6044. const char *errorDescription = "";
  6045. DStr errorMessage;
  6046. bool use_last_error = false;
  6047. bool encode_error = false;
  6048. switch (code) {
  6049. case OBS_OUTPUT_BAD_PATH:
  6050. errorDescription = Str("Output.ConnectFail.BadPath");
  6051. break;
  6052. case OBS_OUTPUT_CONNECT_FAILED:
  6053. use_last_error = true;
  6054. errorDescription = Str("Output.ConnectFail.ConnectFailed");
  6055. break;
  6056. case OBS_OUTPUT_INVALID_STREAM:
  6057. errorDescription = Str("Output.ConnectFail.InvalidStream");
  6058. break;
  6059. case OBS_OUTPUT_ENCODE_ERROR:
  6060. encode_error = true;
  6061. break;
  6062. case OBS_OUTPUT_HDR_DISABLED:
  6063. errorDescription = Str("Output.ConnectFail.HdrDisabled");
  6064. break;
  6065. default:
  6066. case OBS_OUTPUT_ERROR:
  6067. use_last_error = true;
  6068. errorDescription = Str("Output.ConnectFail.Error");
  6069. break;
  6070. case OBS_OUTPUT_DISCONNECTED:
  6071. /* doesn't happen if output is set to reconnect. note that
  6072. * reconnects are handled in the output, not in the UI */
  6073. use_last_error = true;
  6074. errorDescription = Str("Output.ConnectFail.Disconnected");
  6075. }
  6076. if (use_last_error && !last_error.isEmpty())
  6077. dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error));
  6078. else
  6079. dstr_copy(errorMessage, errorDescription);
  6080. ui->statusbar->StreamStopped();
  6081. emit StreamingStopped();
  6082. if (sysTrayStream) {
  6083. sysTrayStream->setText(QTStr("Basic.Main.StartStreaming"));
  6084. sysTrayStream->setEnabled(true);
  6085. }
  6086. streamingStopping = false;
  6087. OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED);
  6088. OnDeactivate();
  6089. #ifdef YOUTUBE_ENABLED
  6090. if (YouTubeAppDock::IsYTServiceSelected())
  6091. youtubeAppDock->IngestionStopped();
  6092. #endif
  6093. blog(LOG_INFO, STREAMING_STOP);
  6094. if (encode_error) {
  6095. QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg")
  6096. : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error);
  6097. OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg);
  6098. } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
  6099. OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage));
  6100. } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
  6101. SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning);
  6102. }
  6103. // Reset broadcast button state/text
  6104. if (!broadcastActive)
  6105. SetBroadcastFlowEnabled(auth && auth->broadcastFlow());
  6106. }
  6107. void OBSBasic::AutoRemux(QString input, bool no_show)
  6108. {
  6109. auto config = Config();
  6110. bool autoRemux = config_get_bool(config, "Video", "AutoRemux");
  6111. if (!autoRemux)
  6112. return;
  6113. bool isSimpleMode = false;
  6114. const char *mode = config_get_string(config, "Output", "Mode");
  6115. if (!mode) {
  6116. isSimpleMode = true;
  6117. } else {
  6118. isSimpleMode = strcmp(mode, "Simple") == 0;
  6119. }
  6120. if (!isSimpleMode) {
  6121. const char *recType = config_get_string(config, "AdvOut", "RecType");
  6122. bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0;
  6123. if (ffmpegOutput)
  6124. return;
  6125. }
  6126. if (input.isEmpty())
  6127. return;
  6128. QFileInfo fi(input);
  6129. QString suffix = fi.suffix();
  6130. /* do not remux if lossless */
  6131. if (suffix.compare("avi", Qt::CaseInsensitive) == 0) {
  6132. return;
  6133. }
  6134. QString path = fi.path();
  6135. QString output = input;
  6136. output.resize(output.size() - suffix.size());
  6137. const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput);
  6138. const char *vCodecName = obs_encoder_get_codec(videoEncoder);
  6139. const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2");
  6140. /* Retain original container for fMP4/fMOV */
  6141. if (strncmp(format, "fragmented", 10) == 0) {
  6142. output += "remuxed." + suffix;
  6143. } else if (strcmp(vCodecName, "prores") == 0) {
  6144. output += "mov";
  6145. } else {
  6146. output += "mp4";
  6147. }
  6148. OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true);
  6149. if (!no_show)
  6150. remux->show();
  6151. remux->AutoRemux(input, output);
  6152. }
  6153. void OBSBasic::StartRecording()
  6154. {
  6155. if (outputHandler->RecordingActive())
  6156. return;
  6157. if (disableOutputsRef)
  6158. return;
  6159. if (!OutputPathValid()) {
  6160. OutputPathInvalidMessage();
  6161. return;
  6162. }
  6163. if (!IsFFmpegOutputToURL() && LowDiskSpace()) {
  6164. DiskSpaceMessage();
  6165. return;
  6166. }
  6167. OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING);
  6168. SaveProject();
  6169. outputHandler->StartRecording();
  6170. }
  6171. void OBSBasic::RecordStopping()
  6172. {
  6173. emit RecordingStopping();
  6174. if (sysTrayRecord)
  6175. sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording"));
  6176. recordingStopping = true;
  6177. OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING);
  6178. }
  6179. void OBSBasic::StopRecording()
  6180. {
  6181. SaveProject();
  6182. if (outputHandler->RecordingActive())
  6183. outputHandler->StopRecording(recordingStopping);
  6184. OnDeactivate();
  6185. }
  6186. void OBSBasic::RecordingStart()
  6187. {
  6188. ui->statusbar->RecordingStarted(outputHandler->fileOutput);
  6189. emit RecordingStarted(isRecordingPausable);
  6190. if (sysTrayRecord)
  6191. sysTrayRecord->setText(QTStr("Basic.Main.StopRecording"));
  6192. recordingStopping = false;
  6193. OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED);
  6194. if (!diskFullTimer->isActive())
  6195. diskFullTimer->start(1000);
  6196. OnActivate();
  6197. blog(LOG_INFO, RECORDING_START);
  6198. }
  6199. void OBSBasic::RecordingStop(int code, QString last_error)
  6200. {
  6201. ui->statusbar->RecordingStopped();
  6202. emit RecordingStopped();
  6203. if (sysTrayRecord)
  6204. sysTrayRecord->setText(QTStr("Basic.Main.StartRecording"));
  6205. blog(LOG_INFO, RECORDING_STOP);
  6206. if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) {
  6207. OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported"));
  6208. } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) {
  6209. QString msg = last_error.isEmpty()
  6210. ? QTStr("Output.RecordError.EncodeErrorMsg")
  6211. : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error);
  6212. OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg);
  6213. } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) {
  6214. OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg"));
  6215. } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
  6216. const char *errorDescription;
  6217. DStr errorMessage;
  6218. bool use_last_error = true;
  6219. errorDescription = Str("Output.RecordError.Msg");
  6220. if (use_last_error && !last_error.isEmpty())
  6221. dstr_printf(errorMessage, "%s<br><br>%s", errorDescription, QT_TO_UTF8(last_error));
  6222. else
  6223. dstr_copy(errorMessage, errorDescription);
  6224. OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage));
  6225. } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) {
  6226. SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning);
  6227. } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) {
  6228. SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning);
  6229. } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
  6230. SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning);
  6231. } else if (code == OBS_OUTPUT_SUCCESS) {
  6232. if (outputHandler) {
  6233. std::string path = outputHandler->lastRecordingPath;
  6234. QString str = QTStr("Basic.StatusBar.RecordingSavedTo");
  6235. ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str())));
  6236. }
  6237. }
  6238. OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED);
  6239. if (diskFullTimer->isActive())
  6240. diskFullTimer->stop();
  6241. AutoRemux(outputHandler->lastRecordingPath.c_str());
  6242. OnDeactivate();
  6243. }
  6244. void OBSBasic::RecordingFileChanged(QString lastRecordingPath)
  6245. {
  6246. QString str = QTStr("Basic.StatusBar.RecordingSavedTo");
  6247. ShowStatusBarMessage(str.arg(lastRecordingPath));
  6248. AutoRemux(lastRecordingPath, true);
  6249. }
  6250. void OBSBasic::ShowReplayBufferPauseWarning()
  6251. {
  6252. auto msgBox = []() {
  6253. QMessageBox msgbox(App()->GetMainWindow());
  6254. msgbox.setWindowTitle(QTStr("Output.ReplayBuffer."
  6255. "PauseWarning.Title"));
  6256. msgbox.setText(QTStr("Output.ReplayBuffer."
  6257. "PauseWarning.Text"));
  6258. msgbox.setIcon(QMessageBox::Icon::Information);
  6259. msgbox.addButton(QMessageBox::Ok);
  6260. QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain"));
  6261. msgbox.setCheckBox(cb);
  6262. msgbox.exec();
  6263. if (cb->isChecked()) {
  6264. config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true);
  6265. config_save_safe(App()->GetUserConfig(), "tmp", nullptr);
  6266. }
  6267. };
  6268. bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing");
  6269. if (!warned) {
  6270. QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox));
  6271. }
  6272. }
  6273. void OBSBasic::StartReplayBuffer()
  6274. {
  6275. if (!outputHandler || !outputHandler->replayBuffer)
  6276. return;
  6277. if (outputHandler->ReplayBufferActive())
  6278. return;
  6279. if (disableOutputsRef)
  6280. return;
  6281. if (!UIValidation::NoSourcesConfirmation(this))
  6282. return;
  6283. if (!OutputPathValid()) {
  6284. OutputPathInvalidMessage();
  6285. return;
  6286. }
  6287. if (LowDiskSpace()) {
  6288. DiskSpaceMessage();
  6289. return;
  6290. }
  6291. OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING);
  6292. SaveProject();
  6293. if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) {
  6294. ShowReplayBufferPauseWarning();
  6295. }
  6296. }
  6297. void OBSBasic::ReplayBufferStopping()
  6298. {
  6299. if (!outputHandler || !outputHandler->replayBuffer)
  6300. return;
  6301. emit ReplayBufStopping();
  6302. if (sysTrayReplayBuffer)
  6303. sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer"));
  6304. replayBufferStopping = true;
  6305. OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING);
  6306. }
  6307. void OBSBasic::StopReplayBuffer()
  6308. {
  6309. if (!outputHandler || !outputHandler->replayBuffer)
  6310. return;
  6311. SaveProject();
  6312. if (outputHandler->ReplayBufferActive())
  6313. outputHandler->StopReplayBuffer(replayBufferStopping);
  6314. OnDeactivate();
  6315. }
  6316. void OBSBasic::ReplayBufferStart()
  6317. {
  6318. if (!outputHandler || !outputHandler->replayBuffer)
  6319. return;
  6320. emit ReplayBufStarted();
  6321. if (sysTrayReplayBuffer)
  6322. sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer"));
  6323. replayBufferStopping = false;
  6324. OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED);
  6325. OnActivate();
  6326. blog(LOG_INFO, REPLAY_BUFFER_START);
  6327. }
  6328. void OBSBasic::ReplayBufferSave()
  6329. {
  6330. if (!outputHandler || !outputHandler->replayBuffer)
  6331. return;
  6332. if (!outputHandler->ReplayBufferActive())
  6333. return;
  6334. calldata_t cd = {0};
  6335. proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer);
  6336. proc_handler_call(ph, "save", &cd);
  6337. calldata_free(&cd);
  6338. }
  6339. void OBSBasic::ReplayBufferSaved()
  6340. {
  6341. if (!outputHandler || !outputHandler->replayBuffer)
  6342. return;
  6343. if (!outputHandler->ReplayBufferActive())
  6344. return;
  6345. calldata_t cd = {0};
  6346. proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer);
  6347. proc_handler_call(ph, "get_last_replay", &cd);
  6348. std::string path = calldata_string(&cd, "path");
  6349. QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str()));
  6350. ShowStatusBarMessage(msg);
  6351. lastReplay = path;
  6352. calldata_free(&cd);
  6353. OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED);
  6354. AutoRemux(QT_UTF8(path.c_str()));
  6355. }
  6356. void OBSBasic::ReplayBufferStop(int code)
  6357. {
  6358. if (!outputHandler || !outputHandler->replayBuffer)
  6359. return;
  6360. emit ReplayBufStopped();
  6361. if (sysTrayReplayBuffer)
  6362. sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer"));
  6363. blog(LOG_INFO, REPLAY_BUFFER_STOP);
  6364. if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) {
  6365. OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported"));
  6366. } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) {
  6367. OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg"));
  6368. } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
  6369. OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg"));
  6370. } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) {
  6371. SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning);
  6372. } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) {
  6373. SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning);
  6374. } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
  6375. SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning);
  6376. }
  6377. OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED);
  6378. OnDeactivate();
  6379. }
  6380. void OBSBasic::StartVirtualCam()
  6381. {
  6382. if (!outputHandler || !outputHandler->virtualCam)
  6383. return;
  6384. if (outputHandler->VirtualCamActive())
  6385. return;
  6386. if (disableOutputsRef)
  6387. return;
  6388. SaveProject();
  6389. outputHandler->StartVirtualCam();
  6390. }
  6391. void OBSBasic::StopVirtualCam()
  6392. {
  6393. if (!outputHandler || !outputHandler->virtualCam)
  6394. return;
  6395. SaveProject();
  6396. if (outputHandler->VirtualCamActive())
  6397. outputHandler->StopVirtualCam();
  6398. OnDeactivate();
  6399. }
  6400. void OBSBasic::OnVirtualCamStart()
  6401. {
  6402. if (!outputHandler || !outputHandler->virtualCam)
  6403. return;
  6404. emit VirtualCamStarted();
  6405. if (sysTrayVirtualCam)
  6406. sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam"));
  6407. OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED);
  6408. OnActivate();
  6409. blog(LOG_INFO, VIRTUAL_CAM_START);
  6410. }
  6411. void OBSBasic::OnVirtualCamStop(int)
  6412. {
  6413. if (!outputHandler || !outputHandler->virtualCam)
  6414. return;
  6415. emit VirtualCamStopped();
  6416. if (sysTrayVirtualCam)
  6417. sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam"));
  6418. OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED);
  6419. blog(LOG_INFO, VIRTUAL_CAM_STOP);
  6420. OnDeactivate();
  6421. if (!restartingVCam)
  6422. return;
  6423. /* Restarting needs to be delayed to make sure that the virtual camera
  6424. * implementation is stopped and avoid race condition. */
  6425. QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam);
  6426. }
  6427. void OBSBasic::StreamActionTriggered()
  6428. {
  6429. if (outputHandler->StreamingActive()) {
  6430. bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream");
  6431. #ifdef YOUTUBE_ENABLED
  6432. if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) {
  6433. QMessageBox::StandardButton button = OBSMessageBox::question(
  6434. this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"),
  6435. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  6436. if (button == QMessageBox::No)
  6437. return;
  6438. confirm = false;
  6439. }
  6440. #endif
  6441. if (confirm && isVisible()) {
  6442. QMessageBox::StandardButton button =
  6443. OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"),
  6444. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  6445. if (button == QMessageBox::No)
  6446. return;
  6447. }
  6448. StopStreaming();
  6449. } else {
  6450. if (!UIValidation::NoSourcesConfirmation(this))
  6451. return;
  6452. Auth *auth = GetAuth();
  6453. auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream
  6454. : UIValidation::StreamSettingsConfirmation(this, service);
  6455. switch (action) {
  6456. case StreamSettingsAction::ContinueStream:
  6457. break;
  6458. case StreamSettingsAction::OpenSettings:
  6459. on_action_Settings_triggered();
  6460. return;
  6461. case StreamSettingsAction::Cancel:
  6462. return;
  6463. }
  6464. bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream");
  6465. bool bwtest = false;
  6466. if (this->auth) {
  6467. OBSDataAutoRelease settings = obs_service_get_settings(service);
  6468. bwtest = obs_data_get_bool(settings, "bwtest");
  6469. // Disable confirmation if this is going to open broadcast setup
  6470. if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive)
  6471. confirm = false;
  6472. }
  6473. if (bwtest && isVisible()) {
  6474. QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"),
  6475. QTStr("ConfirmBWTest.Text"));
  6476. if (button == QMessageBox::No)
  6477. return;
  6478. } else if (confirm && isVisible()) {
  6479. QMessageBox::StandardButton button =
  6480. OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"),
  6481. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  6482. if (button == QMessageBox::No)
  6483. return;
  6484. }
  6485. StartStreaming();
  6486. }
  6487. }
  6488. void OBSBasic::RecordActionTriggered()
  6489. {
  6490. if (outputHandler->RecordingActive()) {
  6491. bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord");
  6492. if (confirm && isVisible()) {
  6493. QMessageBox::StandardButton button = OBSMessageBox::question(
  6494. this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"),
  6495. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  6496. if (button == QMessageBox::No)
  6497. return;
  6498. }
  6499. StopRecording();
  6500. } else {
  6501. if (!UIValidation::NoSourcesConfirmation(this))
  6502. return;
  6503. StartRecording();
  6504. }
  6505. }
  6506. void OBSBasic::VirtualCamActionTriggered()
  6507. {
  6508. if (outputHandler->VirtualCamActive()) {
  6509. StopVirtualCam();
  6510. } else {
  6511. if (!UIValidation::NoSourcesConfirmation(this))
  6512. return;
  6513. StartVirtualCam();
  6514. }
  6515. }
  6516. void OBSBasic::OpenVirtualCamConfig()
  6517. {
  6518. OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this);
  6519. connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig);
  6520. connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam);
  6521. dialog.exec();
  6522. }
  6523. void log_vcam_changed(const VCamConfig &config, bool starting)
  6524. {
  6525. const char *action = starting ? "Starting" : "Changing";
  6526. switch (config.type) {
  6527. case VCamOutputType::Invalid:
  6528. break;
  6529. case VCamOutputType::ProgramView:
  6530. blog(LOG_INFO, "%s Virtual Camera output to Program", action);
  6531. break;
  6532. case VCamOutputType::PreviewOutput:
  6533. blog(LOG_INFO, "%s Virtual Camera output to Preview", action);
  6534. break;
  6535. case VCamOutputType::SceneOutput:
  6536. blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str());
  6537. break;
  6538. case VCamOutputType::SourceOutput:
  6539. blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str());
  6540. break;
  6541. }
  6542. }
  6543. void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config)
  6544. {
  6545. vcamConfig = config;
  6546. outputHandler->UpdateVirtualCamOutputSource();
  6547. log_vcam_changed(config, false);
  6548. }
  6549. void OBSBasic::RestartVirtualCam(const VCamConfig &config)
  6550. {
  6551. restartingVCam = true;
  6552. StopVirtualCam();
  6553. vcamConfig = config;
  6554. }
  6555. void OBSBasic::RestartingVirtualCam()
  6556. {
  6557. if (!restartingVCam)
  6558. return;
  6559. outputHandler->UpdateVirtualCamOutputSource();
  6560. StartVirtualCam();
  6561. restartingVCam = false;
  6562. }
  6563. void OBSBasic::on_actionHelpPortal_triggered()
  6564. {
  6565. QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode);
  6566. QDesktopServices::openUrl(url);
  6567. }
  6568. void OBSBasic::on_actionWebsite_triggered()
  6569. {
  6570. QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode);
  6571. QDesktopServices::openUrl(url);
  6572. }
  6573. void OBSBasic::on_actionDiscord_triggered()
  6574. {
  6575. QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode);
  6576. QDesktopServices::openUrl(url);
  6577. }
  6578. void OBSBasic::on_actionShowWhatsNew_triggered()
  6579. {
  6580. #ifdef WHATSNEW_ENABLED
  6581. if (introCheckThread && introCheckThread->isRunning())
  6582. return;
  6583. if (!cef)
  6584. return;
  6585. config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1);
  6586. WhatsNewInfoThread *wnit = new WhatsNewInfoThread();
  6587. connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection);
  6588. introCheckThread.reset(wnit);
  6589. introCheckThread->start();
  6590. #endif
  6591. }
  6592. void OBSBasic::on_actionReleaseNotes_triggered()
  6593. {
  6594. QString addr("https://github.com/obsproject/obs-studio/releases");
  6595. QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode);
  6596. QDesktopServices::openUrl(url);
  6597. }
  6598. void OBSBasic::on_actionShowSettingsFolder_triggered()
  6599. {
  6600. const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio";
  6601. const QString userConfigLocation = QString::fromStdString(userConfigPath);
  6602. QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation));
  6603. }
  6604. void OBSBasic::on_actionShowProfileFolder_triggered()
  6605. {
  6606. try {
  6607. const OBSProfile &currentProfile = GetCurrentProfile();
  6608. QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string());
  6609. QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation));
  6610. } catch (const std::invalid_argument &error) {
  6611. blog(LOG_ERROR, "%s", error.what());
  6612. }
  6613. }
  6614. int OBSBasic::GetTopSelectedSourceItem()
  6615. {
  6616. QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes();
  6617. return selectedItems.count() ? selectedItems[0].row() : -1;
  6618. }
  6619. QModelIndexList OBSBasic::GetAllSelectedSourceItems()
  6620. {
  6621. return ui->sources->selectionModel()->selectedIndexes();
  6622. }
  6623. void OBSBasic::on_preview_customContextMenuRequested()
  6624. {
  6625. CreateSourcePopupMenu(GetTopSelectedSourceItem(), true);
  6626. }
  6627. void OBSBasic::ProgramViewContextMenuRequested()
  6628. {
  6629. QMenu popup(this);
  6630. QPointer<QMenu> studioProgramProjector;
  6631. studioProgramProjector = new QMenu(QTStr("StudioProgramProjector"));
  6632. AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector);
  6633. popup.addMenu(studioProgramProjector);
  6634. popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow);
  6635. popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram);
  6636. popup.exec(QCursor::pos());
  6637. }
  6638. void OBSBasic::on_previewDisabledWidget_customContextMenuRequested()
  6639. {
  6640. QMenu popup(this);
  6641. delete previewProjectorMain;
  6642. QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview);
  6643. action->setCheckable(true);
  6644. action->setChecked(obs_display_enabled(ui->preview->GetDisplay()));
  6645. previewProjectorMain = new QMenu(QTStr("PreviewProjector"));
  6646. AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector);
  6647. QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow);
  6648. popup.addMenu(previewProjectorMain);
  6649. popup.addAction(previewWindow);
  6650. popup.exec(QCursor::pos());
  6651. }
  6652. void OBSBasic::on_actionAlwaysOnTop_triggered()
  6653. {
  6654. #ifndef _WIN32
  6655. /* Make sure all dialogs are safely and successfully closed before
  6656. * switching the always on top mode due to the fact that windows all
  6657. * have to be recreated, so queue the actual toggle to happen after
  6658. * all events related to closing the dialogs have finished */
  6659. CloseDialogs();
  6660. #endif
  6661. QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection);
  6662. }
  6663. void OBSBasic::ToggleAlwaysOnTop()
  6664. {
  6665. bool isAlwaysOnTop = IsAlwaysOnTop(this);
  6666. ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop);
  6667. SetAlwaysOnTop(this, !isAlwaysOnTop);
  6668. show();
  6669. }
  6670. void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const
  6671. {
  6672. const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon");
  6673. if (strcmp(val, "10") == 0) {
  6674. num = 10;
  6675. den = 1;
  6676. } else if (strcmp(val, "20") == 0) {
  6677. num = 20;
  6678. den = 1;
  6679. } else if (strcmp(val, "24 NTSC") == 0) {
  6680. num = 24000;
  6681. den = 1001;
  6682. } else if (strcmp(val, "25 PAL") == 0) {
  6683. num = 25;
  6684. den = 1;
  6685. } else if (strcmp(val, "29.97") == 0) {
  6686. num = 30000;
  6687. den = 1001;
  6688. } else if (strcmp(val, "48") == 0) {
  6689. num = 48;
  6690. den = 1;
  6691. } else if (strcmp(val, "50 PAL") == 0) {
  6692. num = 50;
  6693. den = 1;
  6694. } else if (strcmp(val, "59.94") == 0) {
  6695. num = 60000;
  6696. den = 1001;
  6697. } else if (strcmp(val, "60") == 0) {
  6698. num = 60;
  6699. den = 1;
  6700. } else {
  6701. num = 30;
  6702. den = 1;
  6703. }
  6704. }
  6705. void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const
  6706. {
  6707. num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt");
  6708. den = 1;
  6709. }
  6710. void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const
  6711. {
  6712. num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum");
  6713. den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen");
  6714. }
  6715. void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const
  6716. {
  6717. num = 1000000000;
  6718. den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS");
  6719. }
  6720. void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const
  6721. {
  6722. uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType");
  6723. if (type == 1) //"Integer"
  6724. GetFPSInteger(num, den);
  6725. else if (type == 2) //"Fraction"
  6726. GetFPSFraction(num, den);
  6727. /*
  6728. * else if (false) //"Nanoseconds", currently not implemented
  6729. * GetFPSNanoseconds(num, den);
  6730. */
  6731. else
  6732. GetFPSCommon(num, den);
  6733. }
  6734. config_t *OBSBasic::Config() const
  6735. {
  6736. return activeConfiguration;
  6737. }
  6738. #ifdef YOUTUBE_ENABLED
  6739. YouTubeAppDock *OBSBasic::GetYouTubeAppDock()
  6740. {
  6741. return youtubeAppDock;
  6742. }
  6743. #ifndef SEC_TO_NSEC
  6744. #define SEC_TO_NSEC 1000000000
  6745. #endif
  6746. void OBSBasic::NewYouTubeAppDock()
  6747. {
  6748. if (!cef_js_avail)
  6749. return;
  6750. /* make sure that the youtube app dock can't be immediately recreated.
  6751. * dumb hack. blame chromium. or this particular dock. or both. if CEF
  6752. * creates/destroys/creates a widget too quickly it can lead to a
  6753. * crash. */
  6754. uint64_t ts = os_gettime_ns();
  6755. if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC))
  6756. return;
  6757. lastYouTubeAppDockCreationTime = ts;
  6758. if (youtubeAppDock)
  6759. RemoveDockWidget(youtubeAppDock->objectName());
  6760. youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel");
  6761. }
  6762. void OBSBasic::DeleteYouTubeAppDock()
  6763. {
  6764. if (!cef_js_avail)
  6765. return;
  6766. if (youtubeAppDock)
  6767. RemoveDockWidget(youtubeAppDock->objectName());
  6768. youtubeAppDock = nullptr;
  6769. }
  6770. #endif
  6771. void OBSBasic::UpdateEditMenu()
  6772. {
  6773. QModelIndexList items = GetAllSelectedSourceItems();
  6774. int totalCount = items.count();
  6775. size_t filter_count = 0;
  6776. if (totalCount == 1) {
  6777. OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem());
  6778. OBSSource source = obs_sceneitem_get_source(sceneItem);
  6779. filter_count = obs_source_filter_count(source);
  6780. }
  6781. bool allowPastingDuplicate = !!clipboard.size();
  6782. for (size_t i = clipboard.size(); i > 0; i--) {
  6783. const size_t idx = i - 1;
  6784. OBSWeakSource &weak = clipboard[idx].weak_source;
  6785. if (obs_weak_source_expired(weak)) {
  6786. clipboard.erase(clipboard.begin() + idx);
  6787. continue;
  6788. }
  6789. OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get());
  6790. if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE)
  6791. allowPastingDuplicate = false;
  6792. }
  6793. int videoCount = 0;
  6794. bool canTransformMultiple = false;
  6795. for (int i = 0; i < totalCount; i++) {
  6796. OBSSceneItem item = ui->sources->Get(items.value(i).row());
  6797. OBSSource source = obs_sceneitem_get_source(item);
  6798. const uint32_t flags = obs_source_get_output_flags(source);
  6799. const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0;
  6800. if (hasVideo && !obs_sceneitem_locked(item))
  6801. canTransformMultiple = true;
  6802. if (hasVideo)
  6803. videoCount++;
  6804. }
  6805. const bool canTransformSingle = videoCount == 1 && totalCount == 1;
  6806. OBSSceneItem curItem = GetCurrentSceneItem();
  6807. bool locked = curItem && obs_sceneitem_locked(curItem);
  6808. ui->actionCopySource->setEnabled(totalCount > 0);
  6809. ui->actionEditTransform->setEnabled(canTransformSingle && !locked);
  6810. ui->actionCopyTransform->setEnabled(canTransformSingle);
  6811. ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0);
  6812. ui->actionCopyFilters->setEnabled(filter_count > 0);
  6813. ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0);
  6814. ui->actionPasteRef->setEnabled(!!clipboard.size());
  6815. ui->actionPasteDup->setEnabled(allowPastingDuplicate);
  6816. ui->actionMoveUp->setEnabled(totalCount > 0);
  6817. ui->actionMoveDown->setEnabled(totalCount > 0);
  6818. ui->actionMoveToTop->setEnabled(totalCount > 0);
  6819. ui->actionMoveToBottom->setEnabled(totalCount > 0);
  6820. ui->actionResetTransform->setEnabled(canTransformMultiple);
  6821. ui->actionRotate90CW->setEnabled(canTransformMultiple);
  6822. ui->actionRotate90CCW->setEnabled(canTransformMultiple);
  6823. ui->actionRotate180->setEnabled(canTransformMultiple);
  6824. ui->actionFlipHorizontal->setEnabled(canTransformMultiple);
  6825. ui->actionFlipVertical->setEnabled(canTransformMultiple);
  6826. ui->actionFitToScreen->setEnabled(canTransformMultiple);
  6827. ui->actionStretchToScreen->setEnabled(canTransformMultiple);
  6828. ui->actionCenterToScreen->setEnabled(canTransformMultiple);
  6829. ui->actionVerticalCenter->setEnabled(canTransformMultiple);
  6830. ui->actionHorizontalCenter->setEnabled(canTransformMultiple);
  6831. }
  6832. void OBSBasic::on_actionEditTransform_triggered()
  6833. {
  6834. const auto item = GetCurrentSceneItem();
  6835. if (!item)
  6836. return;
  6837. CreateEditTransformWindow(item);
  6838. }
  6839. void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item)
  6840. {
  6841. if (transformWindow)
  6842. transformWindow->close();
  6843. transformWindow = new OBSBasicTransform(item, this);
  6844. connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged);
  6845. transformWindow->show();
  6846. transformWindow->setAttribute(Qt::WA_DeleteOnClose, true);
  6847. }
  6848. void OBSBasic::on_actionCopyTransform_triggered()
  6849. {
  6850. OBSSceneItem item = GetCurrentSceneItem();
  6851. obs_sceneitem_get_info2(item, &copiedTransformInfo);
  6852. obs_sceneitem_get_crop(item, &copiedCropInfo);
  6853. ui->actionPasteTransform->setEnabled(true);
  6854. hasCopiedTransform = true;
  6855. }
  6856. void undo_redo(const std::string &data)
  6857. {
  6858. OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str());
  6859. OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid"));
  6860. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())->SetCurrentScene(source.Get(), true);
  6861. obs_scene_load_transform_states(data.c_str());
  6862. }
  6863. void OBSBasic::on_actionPasteTransform_triggered()
  6864. {
  6865. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  6866. auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) {
  6867. if (!obs_sceneitem_selected(item))
  6868. return true;
  6869. if (obs_sceneitem_locked(item))
  6870. return true;
  6871. OBSBasic *main = reinterpret_cast<OBSBasic *>(data);
  6872. obs_sceneitem_defer_update_begin(item);
  6873. obs_sceneitem_set_info2(item, &main->copiedTransformInfo);
  6874. obs_sceneitem_set_crop(item, &main->copiedCropInfo);
  6875. obs_sceneitem_defer_update_end(item);
  6876. return true;
  6877. };
  6878. obs_scene_enum_items(GetCurrentScene(), func, this);
  6879. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  6880. std::string undo_data(obs_data_get_json(wrapper));
  6881. std::string redo_data(obs_data_get_json(rwrapper));
  6882. undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo,
  6883. undo_redo, undo_data, redo_data);
  6884. }
  6885. static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *)
  6886. {
  6887. if (obs_sceneitem_is_group(item))
  6888. obs_sceneitem_group_enum_items(item, reset_tr, nullptr);
  6889. if (!obs_sceneitem_selected(item))
  6890. return true;
  6891. if (obs_sceneitem_locked(item))
  6892. return true;
  6893. obs_sceneitem_defer_update_begin(item);
  6894. obs_transform_info info;
  6895. vec2_set(&info.pos, 0.0f, 0.0f);
  6896. vec2_set(&info.scale, 1.0f, 1.0f);
  6897. info.rot = 0.0f;
  6898. info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
  6899. info.bounds_type = OBS_BOUNDS_NONE;
  6900. info.bounds_alignment = OBS_ALIGN_CENTER;
  6901. info.crop_to_bounds = false;
  6902. vec2_set(&info.bounds, 0.0f, 0.0f);
  6903. obs_sceneitem_set_info2(item, &info);
  6904. obs_sceneitem_crop crop = {};
  6905. obs_sceneitem_set_crop(item, &crop);
  6906. obs_sceneitem_defer_update_end(item);
  6907. return true;
  6908. }
  6909. void OBSBasic::on_actionResetTransform_triggered()
  6910. {
  6911. OBSScene scene = GetCurrentScene();
  6912. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false);
  6913. obs_scene_enum_items(scene, reset_tr, nullptr);
  6914. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false);
  6915. std::string undo_data(obs_data_get_json(wrapper));
  6916. std::string redo_data(obs_data_get_json(rwrapper));
  6917. undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))),
  6918. undo_redo, undo_redo, undo_data, redo_data);
  6919. obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr);
  6920. }
  6921. static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br)
  6922. {
  6923. matrix4 boxTransform;
  6924. obs_sceneitem_get_box_transform(item, &boxTransform);
  6925. vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f);
  6926. vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f);
  6927. auto GetMinPos = [&](float x, float y) {
  6928. vec3 pos;
  6929. vec3_set(&pos, x, y, 0.0f);
  6930. vec3_transform(&pos, &pos, &boxTransform);
  6931. vec3_min(&tl, &tl, &pos);
  6932. vec3_max(&br, &br, &pos);
  6933. };
  6934. GetMinPos(0.0f, 0.0f);
  6935. GetMinPos(1.0f, 0.0f);
  6936. GetMinPos(0.0f, 1.0f);
  6937. GetMinPos(1.0f, 1.0f);
  6938. }
  6939. static vec3 GetItemTL(obs_sceneitem_t *item)
  6940. {
  6941. vec3 tl, br;
  6942. GetItemBox(item, tl, br);
  6943. return tl;
  6944. }
  6945. static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl)
  6946. {
  6947. vec3 newTL;
  6948. vec2 pos;
  6949. obs_sceneitem_get_pos(item, &pos);
  6950. newTL = GetItemTL(item);
  6951. pos.x += tl.x - newTL.x;
  6952. pos.y += tl.y - newTL.y;
  6953. obs_sceneitem_set_pos(item, &pos);
  6954. }
  6955. static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  6956. {
  6957. if (obs_sceneitem_is_group(item))
  6958. obs_sceneitem_group_enum_items(item, RotateSelectedSources, param);
  6959. if (!obs_sceneitem_selected(item))
  6960. return true;
  6961. if (obs_sceneitem_locked(item))
  6962. return true;
  6963. float rot = *reinterpret_cast<float *>(param);
  6964. vec3 tl = GetItemTL(item);
  6965. rot += obs_sceneitem_get_rot(item);
  6966. if (rot >= 360.0f)
  6967. rot -= 360.0f;
  6968. else if (rot <= -360.0f)
  6969. rot += 360.0f;
  6970. obs_sceneitem_set_rot(item, rot);
  6971. obs_sceneitem_force_update_transform(item);
  6972. SetItemTL(item, tl);
  6973. return true;
  6974. };
  6975. void OBSBasic::on_actionRotate90CW_triggered()
  6976. {
  6977. float f90CW = 90.0f;
  6978. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  6979. obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW);
  6980. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  6981. std::string undo_data(obs_data_get_json(wrapper));
  6982. std::string redo_data(obs_data_get_json(rwrapper));
  6983. undo_s.add_action(
  6984. QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  6985. undo_redo, undo_redo, undo_data, redo_data);
  6986. }
  6987. void OBSBasic::on_actionRotate90CCW_triggered()
  6988. {
  6989. float f90CCW = -90.0f;
  6990. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  6991. obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW);
  6992. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  6993. std::string undo_data(obs_data_get_json(wrapper));
  6994. std::string redo_data(obs_data_get_json(rwrapper));
  6995. undo_s.add_action(
  6996. QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  6997. undo_redo, undo_redo, undo_data, redo_data);
  6998. }
  6999. void OBSBasic::on_actionRotate180_triggered()
  7000. {
  7001. float f180 = 180.0f;
  7002. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7003. obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180);
  7004. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7005. std::string undo_data(obs_data_get_json(wrapper));
  7006. std::string redo_data(obs_data_get_json(rwrapper));
  7007. undo_s.add_action(
  7008. QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  7009. undo_redo, undo_redo, undo_data, redo_data);
  7010. }
  7011. static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  7012. {
  7013. vec2 &mul = *reinterpret_cast<vec2 *>(param);
  7014. if (obs_sceneitem_is_group(item))
  7015. obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param);
  7016. if (!obs_sceneitem_selected(item))
  7017. return true;
  7018. if (obs_sceneitem_locked(item))
  7019. return true;
  7020. vec3 tl = GetItemTL(item);
  7021. vec2 scale;
  7022. obs_sceneitem_get_scale(item, &scale);
  7023. vec2_mul(&scale, &scale, &mul);
  7024. obs_sceneitem_set_scale(item, &scale);
  7025. obs_sceneitem_force_update_transform(item);
  7026. SetItemTL(item, tl);
  7027. return true;
  7028. }
  7029. void OBSBasic::on_actionFlipHorizontal_triggered()
  7030. {
  7031. vec2 scale;
  7032. vec2_set(&scale, -1.0f, 1.0f);
  7033. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7034. obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale);
  7035. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7036. std::string undo_data(obs_data_get_json(wrapper));
  7037. std::string redo_data(obs_data_get_json(rwrapper));
  7038. undo_s.add_action(
  7039. QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  7040. undo_redo, undo_redo, undo_data, redo_data);
  7041. }
  7042. void OBSBasic::on_actionFlipVertical_triggered()
  7043. {
  7044. vec2 scale;
  7045. vec2_set(&scale, 1.0f, -1.0f);
  7046. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7047. obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale);
  7048. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7049. std::string undo_data(obs_data_get_json(wrapper));
  7050. std::string redo_data(obs_data_get_json(rwrapper));
  7051. undo_s.add_action(
  7052. QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  7053. undo_redo, undo_redo, undo_data, redo_data);
  7054. }
  7055. static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  7056. {
  7057. obs_bounds_type boundsType = *reinterpret_cast<obs_bounds_type *>(param);
  7058. if (obs_sceneitem_is_group(item))
  7059. obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param);
  7060. if (!obs_sceneitem_selected(item))
  7061. return true;
  7062. if (obs_sceneitem_locked(item))
  7063. return true;
  7064. obs_video_info ovi;
  7065. obs_get_video_info(&ovi);
  7066. obs_transform_info itemInfo;
  7067. vec2_set(&itemInfo.pos, 0.0f, 0.0f);
  7068. vec2_set(&itemInfo.scale, 1.0f, 1.0f);
  7069. itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP;
  7070. itemInfo.rot = 0.0f;
  7071. vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height));
  7072. itemInfo.bounds_type = boundsType;
  7073. itemInfo.bounds_alignment = OBS_ALIGN_CENTER;
  7074. itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item);
  7075. obs_sceneitem_set_info2(item, &itemInfo);
  7076. return true;
  7077. }
  7078. void OBSBasic::on_actionFitToScreen_triggered()
  7079. {
  7080. obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER;
  7081. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7082. obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType);
  7083. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7084. std::string undo_data(obs_data_get_json(wrapper));
  7085. std::string redo_data(obs_data_get_json(rwrapper));
  7086. undo_s.add_action(
  7087. QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  7088. undo_redo, undo_redo, undo_data, redo_data);
  7089. }
  7090. void OBSBasic::on_actionStretchToScreen_triggered()
  7091. {
  7092. obs_bounds_type boundsType = OBS_BOUNDS_STRETCH;
  7093. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7094. obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType);
  7095. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7096. std::string undo_data(obs_data_get_json(wrapper));
  7097. std::string redo_data(obs_data_get_json(rwrapper));
  7098. undo_s.add_action(QTStr("Undo.Transform.StretchToScreen")
  7099. .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  7100. undo_redo, undo_redo, undo_data, redo_data);
  7101. }
  7102. void OBSBasic::CenterSelectedSceneItems(const CenterType &centerType)
  7103. {
  7104. QModelIndexList selectedItems = GetAllSelectedSourceItems();
  7105. if (!selectedItems.count())
  7106. return;
  7107. vector<OBSSceneItem> items;
  7108. // Filter out items that have no size
  7109. for (int x = 0; x < selectedItems.count(); x++) {
  7110. OBSSceneItem item = ui->sources->Get(selectedItems[x].row());
  7111. obs_transform_info oti;
  7112. obs_sceneitem_get_info2(item, &oti);
  7113. obs_source_t *source = obs_sceneitem_get_source(item);
  7114. float width = float(obs_source_get_width(source)) * oti.scale.x;
  7115. float height = float(obs_source_get_height(source)) * oti.scale.y;
  7116. if (width == 0.0f || height == 0.0f)
  7117. continue;
  7118. items.emplace_back(item);
  7119. }
  7120. if (!items.size())
  7121. return;
  7122. // Get center x, y coordinates of items
  7123. vec3 center;
  7124. float top = M_INFINITE;
  7125. float left = M_INFINITE;
  7126. float right = 0.0f;
  7127. float bottom = 0.0f;
  7128. for (auto &item : items) {
  7129. vec3 tl, br;
  7130. GetItemBox(item, tl, br);
  7131. left = std::min(tl.x, left);
  7132. top = std::min(tl.y, top);
  7133. right = std::max(br.x, right);
  7134. bottom = std::max(br.y, bottom);
  7135. }
  7136. center.x = (right + left) / 2.0f;
  7137. center.y = (top + bottom) / 2.0f;
  7138. center.z = 0.0f;
  7139. // Get coordinates of screen center
  7140. obs_video_info ovi;
  7141. obs_get_video_info(&ovi);
  7142. vec3 screenCenter;
  7143. vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f);
  7144. vec3_mulf(&screenCenter, &screenCenter, 0.5f);
  7145. // Calculate difference between screen center and item center
  7146. vec3 offset;
  7147. vec3_sub(&offset, &screenCenter, &center);
  7148. // Shift items by offset
  7149. for (auto &item : items) {
  7150. vec3 tl, br;
  7151. GetItemBox(item, tl, br);
  7152. vec3_add(&tl, &tl, &offset);
  7153. vec3 itemTL = GetItemTL(item);
  7154. if (centerType == CenterType::Vertical)
  7155. tl.x = itemTL.x;
  7156. else if (centerType == CenterType::Horizontal)
  7157. tl.y = itemTL.y;
  7158. SetItemTL(item, tl);
  7159. }
  7160. }
  7161. void OBSBasic::on_actionCenterToScreen_triggered()
  7162. {
  7163. CenterType centerType = CenterType::Scene;
  7164. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7165. CenterSelectedSceneItems(centerType);
  7166. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7167. std::string undo_data(obs_data_get_json(wrapper));
  7168. std::string redo_data(obs_data_get_json(rwrapper));
  7169. undo_s.add_action(
  7170. QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  7171. undo_redo, undo_redo, undo_data, redo_data);
  7172. }
  7173. void OBSBasic::on_actionVerticalCenter_triggered()
  7174. {
  7175. CenterType centerType = CenterType::Vertical;
  7176. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7177. CenterSelectedSceneItems(centerType);
  7178. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7179. std::string undo_data(obs_data_get_json(wrapper));
  7180. std::string redo_data(obs_data_get_json(rwrapper));
  7181. undo_s.add_action(
  7182. QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  7183. undo_redo, undo_redo, undo_data, redo_data);
  7184. }
  7185. void OBSBasic::on_actionHorizontalCenter_triggered()
  7186. {
  7187. CenterType centerType = CenterType::Horizontal;
  7188. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7189. CenterSelectedSceneItems(centerType);
  7190. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  7191. std::string undo_data(obs_data_get_json(wrapper));
  7192. std::string redo_data(obs_data_get_json(rwrapper));
  7193. undo_s.add_action(
  7194. QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  7195. undo_redo, undo_redo, undo_data, redo_data);
  7196. }
  7197. void OBSBasic::EnablePreviewDisplay(bool enable)
  7198. {
  7199. obs_display_set_enabled(ui->preview->GetDisplay(), enable);
  7200. ui->previewContainer->setVisible(enable);
  7201. ui->previewDisabledWidget->setVisible(!enable);
  7202. }
  7203. void OBSBasic::TogglePreview()
  7204. {
  7205. previewEnabled = !previewEnabled;
  7206. EnablePreviewDisplay(previewEnabled);
  7207. }
  7208. void OBSBasic::EnablePreview()
  7209. {
  7210. if (previewProgramMode)
  7211. return;
  7212. previewEnabled = true;
  7213. EnablePreviewDisplay(true);
  7214. }
  7215. void OBSBasic::DisablePreview()
  7216. {
  7217. if (previewProgramMode)
  7218. return;
  7219. previewEnabled = false;
  7220. EnablePreviewDisplay(false);
  7221. }
  7222. void OBSBasic::EnablePreviewProgram()
  7223. {
  7224. SetPreviewProgramMode(true);
  7225. }
  7226. void OBSBasic::DisablePreviewProgram()
  7227. {
  7228. SetPreviewProgramMode(false);
  7229. }
  7230. static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param)
  7231. {
  7232. if (obs_sceneitem_locked(item))
  7233. return true;
  7234. struct vec2 &offset = *reinterpret_cast<struct vec2 *>(param);
  7235. struct vec2 pos;
  7236. if (!obs_sceneitem_selected(item)) {
  7237. if (obs_sceneitem_is_group(item)) {
  7238. struct vec3 offset3;
  7239. vec3_set(&offset3, offset.x, offset.y, 0.0f);
  7240. struct matrix4 matrix;
  7241. obs_sceneitem_get_draw_transform(item, &matrix);
  7242. vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f);
  7243. matrix4_inv(&matrix, &matrix);
  7244. vec3_transform(&offset3, &offset3, &matrix);
  7245. struct vec2 new_offset;
  7246. vec2_set(&new_offset, offset3.x, offset3.y);
  7247. obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset);
  7248. }
  7249. return true;
  7250. }
  7251. obs_sceneitem_get_pos(item, &pos);
  7252. vec2_add(&pos, &pos, &offset);
  7253. obs_sceneitem_set_pos(item, &pos);
  7254. return true;
  7255. }
  7256. void OBSBasic::Nudge(int dist, MoveDir dir)
  7257. {
  7258. if (ui->preview->Locked())
  7259. return;
  7260. struct vec2 offset;
  7261. vec2_set(&offset, 0.0f, 0.0f);
  7262. switch (dir) {
  7263. case MoveDir::Up:
  7264. offset.y = (float)-dist;
  7265. break;
  7266. case MoveDir::Down:
  7267. offset.y = (float)dist;
  7268. break;
  7269. case MoveDir::Left:
  7270. offset.x = (float)-dist;
  7271. break;
  7272. case MoveDir::Right:
  7273. offset.x = (float)dist;
  7274. break;
  7275. }
  7276. if (!recent_nudge) {
  7277. recent_nudge = true;
  7278. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true);
  7279. std::string undo_data(obs_data_get_json(wrapper));
  7280. nudge_timer = new QTimer;
  7281. QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() {
  7282. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true);
  7283. std::string redo_data(obs_data_get_json(rwrapper));
  7284. undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())),
  7285. undo_redo, undo_redo, undo_data, redo_data);
  7286. recent_nudge = false;
  7287. });
  7288. connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater);
  7289. nudge_timer->setSingleShot(true);
  7290. }
  7291. if (nudge_timer) {
  7292. nudge_timer->stop();
  7293. nudge_timer->start(1000);
  7294. } else {
  7295. blog(LOG_ERROR, "No nudge timer!");
  7296. }
  7297. obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset);
  7298. }
  7299. void OBSBasic::DeleteProjector(OBSProjector *projector)
  7300. {
  7301. for (size_t i = 0; i < projectors.size(); i++) {
  7302. if (projectors[i] == projector) {
  7303. projectors[i]->deleteLater();
  7304. projectors.erase(projectors.begin() + i);
  7305. break;
  7306. }
  7307. }
  7308. }
  7309. OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type)
  7310. {
  7311. /* seriously? 10 monitors? */
  7312. if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1)
  7313. return nullptr;
  7314. bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors");
  7315. if (closeProjectors && monitor > -1) {
  7316. for (size_t i = projectors.size(); i > 0; i--) {
  7317. size_t idx = i - 1;
  7318. if (projectors[idx]->GetMonitor() == monitor)
  7319. DeleteProjector(projectors[idx]);
  7320. }
  7321. }
  7322. OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type);
  7323. projectors.emplace_back(projector);
  7324. return projector;
  7325. }
  7326. void OBSBasic::OpenStudioProgramProjector()
  7327. {
  7328. int monitor = sender()->property("monitor").toInt();
  7329. OpenProjector(nullptr, monitor, ProjectorType::StudioProgram);
  7330. }
  7331. void OBSBasic::OpenPreviewProjector()
  7332. {
  7333. int monitor = sender()->property("monitor").toInt();
  7334. OpenProjector(nullptr, monitor, ProjectorType::Preview);
  7335. }
  7336. void OBSBasic::OpenSourceProjector()
  7337. {
  7338. int monitor = sender()->property("monitor").toInt();
  7339. OBSSceneItem item = GetCurrentSceneItem();
  7340. if (!item)
  7341. return;
  7342. OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source);
  7343. }
  7344. void OBSBasic::OpenMultiviewProjector()
  7345. {
  7346. int monitor = sender()->property("monitor").toInt();
  7347. OpenProjector(nullptr, monitor, ProjectorType::Multiview);
  7348. }
  7349. void OBSBasic::OpenSceneProjector()
  7350. {
  7351. int monitor = sender()->property("monitor").toInt();
  7352. OBSScene scene = GetCurrentScene();
  7353. if (!scene)
  7354. return;
  7355. OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene);
  7356. }
  7357. void OBSBasic::OpenStudioProgramWindow()
  7358. {
  7359. OpenProjector(nullptr, -1, ProjectorType::StudioProgram);
  7360. }
  7361. void OBSBasic::OpenPreviewWindow()
  7362. {
  7363. OpenProjector(nullptr, -1, ProjectorType::Preview);
  7364. }
  7365. void OBSBasic::OpenSourceWindow()
  7366. {
  7367. OBSSceneItem item = GetCurrentSceneItem();
  7368. if (!item)
  7369. return;
  7370. OBSSource source = obs_sceneitem_get_source(item);
  7371. OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source);
  7372. }
  7373. void OBSBasic::OpenSceneWindow()
  7374. {
  7375. OBSScene scene = GetCurrentScene();
  7376. if (!scene)
  7377. return;
  7378. OBSSource source = obs_scene_get_source(scene);
  7379. OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene);
  7380. }
  7381. void OBSBasic::OpenSavedProjectors()
  7382. {
  7383. for (SavedProjectorInfo *info : savedProjectorsArray) {
  7384. OpenSavedProjector(info);
  7385. }
  7386. }
  7387. void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info)
  7388. {
  7389. if (info) {
  7390. OBSProjector *projector = nullptr;
  7391. switch (info->type) {
  7392. case ProjectorType::Source:
  7393. case ProjectorType::Scene: {
  7394. OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str());
  7395. if (!source)
  7396. return;
  7397. projector = OpenProjector(source, info->monitor, info->type);
  7398. break;
  7399. }
  7400. default: {
  7401. projector = OpenProjector(nullptr, info->monitor, info->type);
  7402. break;
  7403. }
  7404. }
  7405. if (projector && !info->geometry.empty() && info->monitor < 0) {
  7406. QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str()));
  7407. projector->restoreGeometry(byteArray);
  7408. if (!WindowPositionValid(projector->normalGeometry())) {
  7409. QRect rect = QGuiApplication::primaryScreen()->geometry();
  7410. projector->setGeometry(
  7411. QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect));
  7412. }
  7413. if (info->alwaysOnTopOverridden)
  7414. projector->SetIsAlwaysOnTop(info->alwaysOnTop, true);
  7415. }
  7416. }
  7417. }
  7418. void OBSBasic::on_actionFullscreenInterface_triggered()
  7419. {
  7420. if (!isFullScreen())
  7421. showFullScreen();
  7422. else
  7423. showNormal();
  7424. }
  7425. void OBSBasic::UpdateTitleBar()
  7426. {
  7427. stringstream name;
  7428. const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile");
  7429. const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection");
  7430. name << "OBS ";
  7431. if (previewProgramMode)
  7432. name << "Studio ";
  7433. name << App()->GetVersionString(false);
  7434. if (safe_mode)
  7435. name << " (" << Str("TitleBar.SafeMode") << ")";
  7436. if (App()->IsPortableMode())
  7437. name << " - " << Str("TitleBar.PortableMode");
  7438. name << " - " << Str("TitleBar.Profile") << ": " << profile;
  7439. name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection;
  7440. setWindowTitle(QT_UTF8(name.str().c_str()));
  7441. }
  7442. int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const
  7443. {
  7444. char profiles_path[512];
  7445. const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir");
  7446. int ret;
  7447. if (!profile)
  7448. return -1;
  7449. if (!path)
  7450. return -1;
  7451. if (!file)
  7452. file = "";
  7453. ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles");
  7454. if (ret <= 0)
  7455. return ret;
  7456. if (!*file)
  7457. return snprintf(path, size, "%s/%s", profiles_path, profile);
  7458. return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file);
  7459. }
  7460. void OBSBasic::on_resetDocks_triggered(bool force)
  7461. {
  7462. /* prune deleted extra docks */
  7463. for (int i = oldExtraDocks.size() - 1; i >= 0; i--) {
  7464. if (!oldExtraDocks[i]) {
  7465. oldExtraDocks.removeAt(i);
  7466. oldExtraDockNames.removeAt(i);
  7467. }
  7468. }
  7469. #ifdef BROWSER_AVAILABLE
  7470. if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) &&
  7471. !force)
  7472. #else
  7473. if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force)
  7474. #endif
  7475. {
  7476. QMessageBox::StandardButton button =
  7477. OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text"));
  7478. if (button == QMessageBox::No)
  7479. return;
  7480. }
  7481. /* undock/hide/center extra docks */
  7482. for (int i = oldExtraDocks.size() - 1; i >= 0; i--) {
  7483. if (oldExtraDocks[i]) {
  7484. oldExtraDocks[i]->setVisible(true);
  7485. oldExtraDocks[i]->setFloating(true);
  7486. oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() -
  7487. oldExtraDocks[i]->rect().center());
  7488. oldExtraDocks[i]->setVisible(false);
  7489. }
  7490. }
  7491. #define RESET_DOCKLIST(dockList) \
  7492. for (int i = dockList.size() - 1; i >= 0; i--) { \
  7493. dockList[i]->setVisible(true); \
  7494. dockList[i]->setFloating(true); \
  7495. dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \
  7496. dockList[i]->setVisible(false); \
  7497. }
  7498. RESET_DOCKLIST(extraDocks)
  7499. RESET_DOCKLIST(extraCustomDocks)
  7500. #ifdef BROWSER_AVAILABLE
  7501. RESET_DOCKLIST(extraBrowserDocks)
  7502. #endif
  7503. #undef RESET_DOCKLIST
  7504. restoreState(startingDockLayout);
  7505. int cx = width();
  7506. int cy = height();
  7507. int cx22_5 = cx * 225 / 1000;
  7508. int cx5 = cx * 5 / 100;
  7509. int cx21 = cx * 21 / 100;
  7510. cy = cy * 225 / 1000;
  7511. int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21);
  7512. QList<QDockWidget *> docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock};
  7513. QList<int> sizes{cx22_5, cx22_5, mixerSize, cx5, cx21};
  7514. ui->scenesDock->setVisible(true);
  7515. ui->sourcesDock->setVisible(true);
  7516. ui->mixerDock->setVisible(true);
  7517. ui->transitionsDock->setVisible(true);
  7518. controlsDock->setVisible(true);
  7519. statsDock->setVisible(false);
  7520. statsDock->setFloating(true);
  7521. resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical);
  7522. resizeDocks(docks, sizes, Qt::Horizontal);
  7523. activateWindow();
  7524. }
  7525. void OBSBasic::on_lockDocks_toggled(bool lock)
  7526. {
  7527. QDockWidget::DockWidgetFeatures features =
  7528. lock ? QDockWidget::NoDockWidgetFeatures
  7529. : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable |
  7530. QDockWidget::DockWidgetFloatable);
  7531. QDockWidget::DockWidgetFeatures mainFeatures = features;
  7532. mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable;
  7533. ui->scenesDock->setFeatures(mainFeatures);
  7534. ui->sourcesDock->setFeatures(mainFeatures);
  7535. ui->mixerDock->setFeatures(mainFeatures);
  7536. ui->transitionsDock->setFeatures(mainFeatures);
  7537. controlsDock->setFeatures(mainFeatures);
  7538. statsDock->setFeatures(features);
  7539. for (int i = extraDocks.size() - 1; i >= 0; i--)
  7540. extraDocks[i]->setFeatures(features);
  7541. for (int i = extraCustomDocks.size() - 1; i >= 0; i--)
  7542. extraCustomDocks[i]->setFeatures(features);
  7543. #ifdef BROWSER_AVAILABLE
  7544. for (int i = extraBrowserDocks.size() - 1; i >= 0; i--)
  7545. extraBrowserDocks[i]->setFeatures(features);
  7546. #endif
  7547. for (int i = oldExtraDocks.size() - 1; i >= 0; i--) {
  7548. if (!oldExtraDocks[i]) {
  7549. oldExtraDocks.removeAt(i);
  7550. oldExtraDockNames.removeAt(i);
  7551. } else {
  7552. oldExtraDocks[i]->setFeatures(features);
  7553. }
  7554. }
  7555. }
  7556. void OBSBasic::on_sideDocks_toggled(bool side)
  7557. {
  7558. if (side) {
  7559. setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
  7560. setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
  7561. setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
  7562. setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
  7563. } else {
  7564. setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea);
  7565. setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea);
  7566. setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea);
  7567. setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea);
  7568. }
  7569. }
  7570. void OBSBasic::on_resetUI_triggered()
  7571. {
  7572. on_resetDocks_triggered();
  7573. ui->toggleListboxToolbars->setChecked(true);
  7574. ui->toggleContextBar->setChecked(true);
  7575. ui->toggleSourceIcons->setChecked(true);
  7576. ui->toggleStatusBar->setChecked(true);
  7577. ui->scenes->SetGridMode(false);
  7578. ui->actionSceneListMode->setChecked(true);
  7579. config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false);
  7580. }
  7581. void OBSBasic::on_multiviewProjectorWindowed_triggered()
  7582. {
  7583. OpenProjector(nullptr, -1, ProjectorType::Multiview);
  7584. }
  7585. void OBSBasic::on_toggleListboxToolbars_toggled(bool visible)
  7586. {
  7587. ui->sourcesToolbar->setVisible(visible);
  7588. ui->scenesToolbar->setVisible(visible);
  7589. ui->mixerToolbar->setVisible(visible);
  7590. config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible);
  7591. }
  7592. void OBSBasic::ShowContextBar()
  7593. {
  7594. on_toggleContextBar_toggled(true);
  7595. ui->toggleContextBar->setChecked(true);
  7596. }
  7597. void OBSBasic::HideContextBar()
  7598. {
  7599. on_toggleContextBar_toggled(false);
  7600. ui->toggleContextBar->setChecked(false);
  7601. }
  7602. void OBSBasic::on_toggleContextBar_toggled(bool visible)
  7603. {
  7604. config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible);
  7605. this->ui->contextContainer->setVisible(visible);
  7606. UpdateContextBar(true);
  7607. }
  7608. void OBSBasic::on_toggleStatusBar_toggled(bool visible)
  7609. {
  7610. ui->statusbar->setVisible(visible);
  7611. config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible);
  7612. }
  7613. void OBSBasic::on_toggleSourceIcons_toggled(bool visible)
  7614. {
  7615. ui->sources->SetIconsVisible(visible);
  7616. if (advAudioWindow != nullptr)
  7617. advAudioWindow->SetIconsVisible(visible);
  7618. config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible);
  7619. }
  7620. void OBSBasic::on_actionLockPreview_triggered()
  7621. {
  7622. ui->preview->ToggleLocked();
  7623. ui->actionLockPreview->setChecked(ui->preview->Locked());
  7624. }
  7625. void OBSBasic::on_scalingMenu_aboutToShow()
  7626. {
  7627. obs_video_info ovi;
  7628. obs_get_video_info(&ovi);
  7629. QAction *action = ui->actionScaleCanvas;
  7630. QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas");
  7631. text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height));
  7632. action->setText(text);
  7633. action = ui->actionScaleOutput;
  7634. text = QTStr("Basic.MainMenu.Edit.Scale.Output");
  7635. text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height));
  7636. action->setText(text);
  7637. action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height));
  7638. UpdatePreviewScalingMenu();
  7639. }
  7640. void OBSBasic::on_actionScaleWindow_triggered()
  7641. {
  7642. ui->preview->SetFixedScaling(false);
  7643. ui->preview->ResetScrollingOffset();
  7644. emit ui->preview->DisplayResized();
  7645. }
  7646. void OBSBasic::on_actionScaleCanvas_triggered()
  7647. {
  7648. ui->preview->SetFixedScaling(true);
  7649. ui->preview->SetScalingLevel(0);
  7650. emit ui->preview->DisplayResized();
  7651. }
  7652. void OBSBasic::on_actionScaleOutput_triggered()
  7653. {
  7654. obs_video_info ovi;
  7655. obs_get_video_info(&ovi);
  7656. ui->preview->SetFixedScaling(true);
  7657. float scalingAmount = float(ovi.output_width) / float(ovi.base_width);
  7658. // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY)
  7659. int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY)));
  7660. ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount);
  7661. emit ui->preview->DisplayResized();
  7662. }
  7663. void OBSBasic::SetShowing(bool showing)
  7664. {
  7665. if (!showing && isVisible()) {
  7666. config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry",
  7667. saveGeometry().toBase64().constData());
  7668. /* hide all visible child dialogs */
  7669. visDlgPositions.clear();
  7670. if (!visDialogs.isEmpty()) {
  7671. for (QDialog *dlg : visDialogs) {
  7672. visDlgPositions.append(dlg->pos());
  7673. dlg->hide();
  7674. }
  7675. }
  7676. if (showHide)
  7677. showHide->setText(QTStr("Basic.SystemTray.Show"));
  7678. QTimer::singleShot(0, this, &OBSBasic::hide);
  7679. if (previewEnabled)
  7680. EnablePreviewDisplay(false);
  7681. #ifdef __APPLE__
  7682. EnableOSXDockIcon(false);
  7683. #endif
  7684. } else if (showing && !isVisible()) {
  7685. if (showHide)
  7686. showHide->setText(QTStr("Basic.SystemTray.Hide"));
  7687. QTimer::singleShot(0, this, &OBSBasic::show);
  7688. if (previewEnabled)
  7689. EnablePreviewDisplay(true);
  7690. #ifdef __APPLE__
  7691. EnableOSXDockIcon(true);
  7692. #endif
  7693. /* raise and activate window to ensure it is on top */
  7694. raise();
  7695. activateWindow();
  7696. /* show all child dialogs that was visible earlier */
  7697. if (!visDialogs.isEmpty()) {
  7698. for (int i = 0; i < visDialogs.size(); ++i) {
  7699. QDialog *dlg = visDialogs[i];
  7700. dlg->move(visDlgPositions[i]);
  7701. dlg->show();
  7702. }
  7703. }
  7704. /* Unminimize window if it was hidden to tray instead of task
  7705. * bar. */
  7706. if (sysTrayMinimizeToTray()) {
  7707. Qt::WindowStates state;
  7708. state = windowState() & ~Qt::WindowMinimized;
  7709. state |= Qt::WindowActive;
  7710. setWindowState(state);
  7711. }
  7712. }
  7713. }
  7714. void OBSBasic::ToggleShowHide()
  7715. {
  7716. bool showing = isVisible();
  7717. if (showing) {
  7718. /* check for modal dialogs */
  7719. EnumDialogs();
  7720. if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty())
  7721. return;
  7722. }
  7723. SetShowing(!showing);
  7724. }
  7725. void OBSBasic::SystemTrayInit()
  7726. {
  7727. #ifdef __APPLE__
  7728. QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg");
  7729. trayIconFile.setIsMask(true);
  7730. #else
  7731. QIcon trayIconFile = QIcon(":/res/images/obs.png");
  7732. #endif
  7733. trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this));
  7734. trayIcon->setToolTip("OBS Studio");
  7735. showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data());
  7736. sysTrayStream =
  7737. new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"),
  7738. trayIcon.data());
  7739. sysTrayRecord =
  7740. new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"),
  7741. trayIcon.data());
  7742. sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer")
  7743. : QTStr("Basic.Main.StartReplayBuffer"),
  7744. trayIcon.data());
  7745. sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam")
  7746. : QTStr("Basic.Main.StartVirtualCam"),
  7747. trayIcon.data());
  7748. exit = new QAction(QTStr("Exit"), trayIcon.data());
  7749. trayMenu = new QMenu;
  7750. previewProjector = new QMenu(QTStr("PreviewProjector"));
  7751. studioProgramProjector = new QMenu(QTStr("StudioProgramProjector"));
  7752. AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector);
  7753. AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector);
  7754. trayMenu->addAction(showHide);
  7755. trayMenu->addSeparator();
  7756. trayMenu->addMenu(previewProjector);
  7757. trayMenu->addMenu(studioProgramProjector);
  7758. trayMenu->addSeparator();
  7759. trayMenu->addAction(sysTrayStream);
  7760. trayMenu->addAction(sysTrayRecord);
  7761. trayMenu->addAction(sysTrayReplayBuffer);
  7762. trayMenu->addAction(sysTrayVirtualCam);
  7763. trayMenu->addSeparator();
  7764. trayMenu->addAction(exit);
  7765. trayIcon->setContextMenu(trayMenu);
  7766. trayIcon->show();
  7767. if (outputHandler && !outputHandler->replayBuffer)
  7768. sysTrayReplayBuffer->setEnabled(false);
  7769. sysTrayVirtualCam->setEnabled(vcamEnabled);
  7770. if (Active())
  7771. OnActivate(true);
  7772. connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated);
  7773. connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide);
  7774. connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered);
  7775. connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered);
  7776. connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered);
  7777. connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered);
  7778. connect(exit, &QAction::triggered, this, &OBSBasic::close);
  7779. }
  7780. void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason)
  7781. {
  7782. // Refresh projector list
  7783. previewProjector->clear();
  7784. studioProgramProjector->clear();
  7785. AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector);
  7786. AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector);
  7787. #ifdef __APPLE__
  7788. UNUSED_PARAMETER(reason);
  7789. #else
  7790. if (reason == QSystemTrayIcon::Trigger) {
  7791. EnablePreviewDisplay(previewEnabled && !isVisible());
  7792. ToggleShowHide();
  7793. }
  7794. #endif
  7795. }
  7796. void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n)
  7797. {
  7798. if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) {
  7799. QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n);
  7800. trayIcon->showMessage("OBS Studio", text, icon, 10000);
  7801. }
  7802. }
  7803. void OBSBasic::SystemTray(bool firstStarted)
  7804. {
  7805. if (!QSystemTrayIcon::isSystemTrayAvailable())
  7806. return;
  7807. if (!trayIcon && !firstStarted)
  7808. return;
  7809. bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted");
  7810. bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled");
  7811. if (firstStarted)
  7812. SystemTrayInit();
  7813. if (!sysTrayEnabled) {
  7814. trayIcon->hide();
  7815. } else {
  7816. trayIcon->show();
  7817. if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) {
  7818. EnablePreviewDisplay(false);
  7819. #ifdef __APPLE__
  7820. EnableOSXDockIcon(false);
  7821. #endif
  7822. opt_minimize_tray = false;
  7823. }
  7824. }
  7825. if (isVisible())
  7826. showHide->setText(QTStr("Basic.SystemTray.Hide"));
  7827. else
  7828. showHide->setText(QTStr("Basic.SystemTray.Show"));
  7829. }
  7830. bool OBSBasic::sysTrayMinimizeToTray()
  7831. {
  7832. return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray");
  7833. }
  7834. void OBSBasic::on_actionMainUndo_triggered()
  7835. {
  7836. undo_s.undo();
  7837. }
  7838. void OBSBasic::on_actionMainRedo_triggered()
  7839. {
  7840. undo_s.redo();
  7841. }
  7842. void OBSBasic::on_actionCopySource_triggered()
  7843. {
  7844. clipboard.clear();
  7845. for (auto &selectedSource : GetAllSelectedSourceItems()) {
  7846. OBSSceneItem item = ui->sources->Get(selectedSource.row());
  7847. if (!item)
  7848. continue;
  7849. OBSSource source = obs_sceneitem_get_source(item);
  7850. SourceCopyInfo copyInfo;
  7851. copyInfo.weak_source = OBSGetWeakRef(source);
  7852. obs_sceneitem_get_info2(item, &copyInfo.transform);
  7853. obs_sceneitem_get_crop(item, &copyInfo.crop);
  7854. copyInfo.blend_method = obs_sceneitem_get_blending_method(item);
  7855. copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item);
  7856. copyInfo.visible = obs_sceneitem_visible(item);
  7857. clipboard.push_back(copyInfo);
  7858. }
  7859. UpdateEditMenu();
  7860. }
  7861. void OBSBasic::on_actionPasteRef_triggered()
  7862. {
  7863. OBSSource scene_source = GetCurrentSceneSource();
  7864. OBSData undo_data = BackupScene(scene_source);
  7865. OBSScene scene = GetCurrentScene();
  7866. undo_s.push_disabled();
  7867. for (size_t i = clipboard.size(); i > 0; i--) {
  7868. SourceCopyInfo &copyInfo = clipboard[i - 1];
  7869. OBSSource source = OBSGetStrongRef(copyInfo.weak_source);
  7870. if (!source)
  7871. continue;
  7872. const char *name = obs_source_get_name(source);
  7873. /* do not allow duplicate refs of the same group in the same
  7874. * scene */
  7875. if (!!obs_scene_get_group(scene, name)) {
  7876. continue;
  7877. }
  7878. OBSBasicSourceSelect::SourcePaste(copyInfo, false);
  7879. }
  7880. undo_s.pop_disabled();
  7881. QString action_name = QTStr("Undo.PasteSourceRef");
  7882. const char *scene_name = obs_source_get_name(scene_source);
  7883. OBSData redo_data = BackupScene(scene_source);
  7884. CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data);
  7885. }
  7886. void OBSBasic::on_actionPasteDup_triggered()
  7887. {
  7888. OBSSource scene_source = GetCurrentSceneSource();
  7889. OBSData undo_data = BackupScene(scene_source);
  7890. undo_s.push_disabled();
  7891. for (size_t i = clipboard.size(); i > 0; i--) {
  7892. SourceCopyInfo &copyInfo = clipboard[i - 1];
  7893. OBSBasicSourceSelect::SourcePaste(copyInfo, true);
  7894. }
  7895. undo_s.pop_disabled();
  7896. QString action_name = QTStr("Undo.PasteSource");
  7897. const char *scene_name = obs_source_get_name(scene_source);
  7898. OBSData redo_data = BackupScene(scene_source);
  7899. CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data);
  7900. }
  7901. void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource)
  7902. {
  7903. if (source == dstSource)
  7904. return;
  7905. OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource);
  7906. obs_source_copy_filters(dstSource, source);
  7907. OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource);
  7908. const char *srcName = obs_source_get_name(source);
  7909. const char *dstName = obs_source_get_name(dstSource);
  7910. QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName);
  7911. CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array);
  7912. }
  7913. void OBSBasic::AudioMixerCopyFilters()
  7914. {
  7915. QAction *action = reinterpret_cast<QAction *>(sender());
  7916. VolControl *vol = action->property("volControl").value<VolControl *>();
  7917. obs_source_t *source = vol->GetSource();
  7918. copyFiltersSource = obs_source_get_weak_source(source);
  7919. ui->actionPasteFilters->setEnabled(true);
  7920. }
  7921. void OBSBasic::AudioMixerPasteFilters()
  7922. {
  7923. QAction *action = reinterpret_cast<QAction *>(sender());
  7924. VolControl *vol = action->property("volControl").value<VolControl *>();
  7925. obs_source_t *dstSource = vol->GetSource();
  7926. OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource);
  7927. SourcePasteFilters(source.Get(), dstSource);
  7928. }
  7929. void OBSBasic::SceneCopyFilters()
  7930. {
  7931. copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource());
  7932. ui->actionPasteFilters->setEnabled(true);
  7933. }
  7934. void OBSBasic::ScenePasteFilters()
  7935. {
  7936. OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource);
  7937. OBSSource dstSource = GetCurrentSceneSource();
  7938. SourcePasteFilters(source.Get(), dstSource);
  7939. }
  7940. void OBSBasic::on_actionCopyFilters_triggered()
  7941. {
  7942. OBSSceneItem item = GetCurrentSceneItem();
  7943. if (!item)
  7944. return;
  7945. OBSSource source = obs_sceneitem_get_source(item);
  7946. copyFiltersSource = obs_source_get_weak_source(source);
  7947. ui->actionPasteFilters->setEnabled(true);
  7948. }
  7949. void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array,
  7950. obs_data_array_t *redo_array)
  7951. {
  7952. auto undo_redo = [this](const std::string &json) {
  7953. OBSDataAutoRelease data = obs_data_create_from_json(json.c_str());
  7954. OBSDataArrayAutoRelease array = obs_data_get_array(data, "array");
  7955. OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid"));
  7956. obs_source_restore_filters(source, array);
  7957. if (filters)
  7958. filters->UpdateSource(source);
  7959. };
  7960. const char *uuid = obs_source_get_uuid(source);
  7961. OBSDataAutoRelease undo_data = obs_data_create();
  7962. OBSDataAutoRelease redo_data = obs_data_create();
  7963. obs_data_set_array(undo_data, "array", undo_array);
  7964. obs_data_set_array(redo_data, "array", redo_array);
  7965. obs_data_set_string(undo_data, "uuid", uuid);
  7966. obs_data_set_string(redo_data, "uuid", uuid);
  7967. undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data));
  7968. }
  7969. void OBSBasic::on_actionPasteFilters_triggered()
  7970. {
  7971. OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource);
  7972. OBSSceneItem sceneItem = GetCurrentSceneItem();
  7973. OBSSource dstSource = obs_sceneitem_get_source(sceneItem);
  7974. SourcePasteFilters(source.Get(), dstSource);
  7975. }
  7976. static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems)
  7977. {
  7978. for (int x = 0; x < selectedItems.count(); x++) {
  7979. SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row());
  7980. treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb));
  7981. treeItem->style()->unpolish(treeItem);
  7982. treeItem->style()->polish(treeItem);
  7983. OBSSceneItem sceneItem = sources->Get(selectedItems[x].row());
  7984. OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem);
  7985. obs_data_set_int(privData, "color-preset", 1);
  7986. obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb)));
  7987. }
  7988. }
  7989. void OBSBasic::ColorChange()
  7990. {
  7991. QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes();
  7992. QAction *action = qobject_cast<QAction *>(sender());
  7993. QPushButton *colorButton = qobject_cast<QPushButton *>(sender());
  7994. if (selectedItems.count() == 0)
  7995. return;
  7996. if (colorButton) {
  7997. int preset = colorButton->property("bgColor").value<int>();
  7998. for (int x = 0; x < selectedItems.count(); x++) {
  7999. SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row());
  8000. treeItem->setStyleSheet("");
  8001. treeItem->setProperty("bgColor", preset);
  8002. treeItem->style()->unpolish(treeItem);
  8003. treeItem->style()->polish(treeItem);
  8004. OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row());
  8005. OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem);
  8006. obs_data_set_int(privData, "color-preset", preset + 1);
  8007. obs_data_set_string(privData, "color", "");
  8008. }
  8009. for (int i = 1; i < 9; i++) {
  8010. stringstream button;
  8011. button << "preset" << i;
  8012. QPushButton *cButton =
  8013. colorButton->parentWidget()->findChild<QPushButton *>(button.str().c_str());
  8014. cButton->setStyleSheet("border: 1px solid black");
  8015. }
  8016. colorButton->setStyleSheet("border: 2px solid black");
  8017. } else if (action) {
  8018. int preset = action->property("bgColor").value<int>();
  8019. if (preset == 1) {
  8020. OBSSceneItem curSceneItem = GetCurrentSceneItem();
  8021. SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem);
  8022. OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem);
  8023. int oldPreset = obs_data_get_int(curPrivData, "color-preset");
  8024. const QString oldSheet = curTreeItem->styleSheet();
  8025. auto liveChangeColor = [=](const QColor &color) {
  8026. if (color.isValid()) {
  8027. curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb));
  8028. }
  8029. };
  8030. auto changedColor = [=](const QColor &color) {
  8031. if (color.isValid()) {
  8032. ConfirmColor(ui->sources, color, selectedItems);
  8033. }
  8034. };
  8035. auto rejected = [=]() {
  8036. if (oldPreset == 1) {
  8037. curTreeItem->setStyleSheet(oldSheet);
  8038. curTreeItem->setProperty("bgColor", 0);
  8039. } else if (oldPreset == 0) {
  8040. curTreeItem->setStyleSheet("background: none");
  8041. curTreeItem->setProperty("bgColor", 0);
  8042. } else {
  8043. curTreeItem->setStyleSheet("");
  8044. curTreeItem->setProperty("bgColor", oldPreset - 1);
  8045. }
  8046. curTreeItem->style()->unpolish(curTreeItem);
  8047. curTreeItem->style()->polish(curTreeItem);
  8048. };
  8049. QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel;
  8050. const char *oldColor = obs_data_get_string(curPrivData, "color");
  8051. const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000";
  8052. #ifdef __linux__
  8053. // TODO: Revisit hang on Ubuntu with native dialog
  8054. options |= QColorDialog::DontUseNativeDialog;
  8055. #endif
  8056. QColorDialog *colorDialog = new QColorDialog(this);
  8057. colorDialog->setOptions(options);
  8058. colorDialog->setCurrentColor(QColor(customColor));
  8059. connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor);
  8060. connect(colorDialog, &QColorDialog::colorSelected, changedColor);
  8061. connect(colorDialog, &QColorDialog::rejected, rejected);
  8062. colorDialog->open();
  8063. } else {
  8064. for (int x = 0; x < selectedItems.count(); x++) {
  8065. SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row());
  8066. treeItem->setStyleSheet("background: none");
  8067. treeItem->setProperty("bgColor", preset);
  8068. treeItem->style()->unpolish(treeItem);
  8069. treeItem->style()->polish(treeItem);
  8070. OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row());
  8071. OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem);
  8072. obs_data_set_int(privData, "color-preset", preset);
  8073. obs_data_set_string(privData, "color", "");
  8074. }
  8075. }
  8076. }
  8077. }
  8078. SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem)
  8079. {
  8080. int i = 0;
  8081. SourceTreeItem *treeItem = ui->sources->GetItemWidget(i);
  8082. OBSSceneItem item = ui->sources->Get(i);
  8083. int64_t id = obs_sceneitem_get_id(sceneItem);
  8084. while (treeItem && obs_sceneitem_get_id(item) != id) {
  8085. i++;
  8086. treeItem = ui->sources->GetItemWidget(i);
  8087. item = ui->sources->Get(i);
  8088. }
  8089. if (treeItem)
  8090. return treeItem;
  8091. return nullptr;
  8092. }
  8093. void OBSBasic::on_autoConfigure_triggered()
  8094. {
  8095. AutoConfig test(this);
  8096. test.setModal(true);
  8097. test.show();
  8098. test.exec();
  8099. }
  8100. void OBSBasic::on_stats_triggered()
  8101. {
  8102. if (!stats.isNull()) {
  8103. stats->show();
  8104. stats->raise();
  8105. return;
  8106. }
  8107. OBSBasicStats *statsDlg;
  8108. statsDlg = new OBSBasicStats(nullptr);
  8109. statsDlg->show();
  8110. stats = statsDlg;
  8111. }
  8112. void OBSBasic::on_actionShowAbout_triggered()
  8113. {
  8114. if (about)
  8115. about->close();
  8116. about = new OBSAbout(this);
  8117. about->show();
  8118. about->setAttribute(Qt::WA_DeleteOnClose, true);
  8119. }
  8120. void OBSBasic::ResizeOutputSizeOfSource()
  8121. {
  8122. if (obs_video_active())
  8123. return;
  8124. QMessageBox resize_output(this);
  8125. resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" +
  8126. QTStr("ResizeOutputSizeOfSource.Continue"));
  8127. QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole);
  8128. resize_output.addButton(QTStr("No"), QMessageBox::NoRole);
  8129. resize_output.setIcon(QMessageBox::Warning);
  8130. resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource"));
  8131. resize_output.exec();
  8132. if (resize_output.clickedButton() != Yes)
  8133. return;
  8134. OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem());
  8135. int width = obs_source_get_width(source);
  8136. int height = obs_source_get_height(source);
  8137. config_set_uint(activeConfiguration, "Video", "BaseCX", width);
  8138. config_set_uint(activeConfiguration, "Video", "BaseCY", height);
  8139. config_set_uint(activeConfiguration, "Video", "OutputCX", width);
  8140. config_set_uint(activeConfiguration, "Video", "OutputCY", height);
  8141. ResetVideo();
  8142. ResetOutputs();
  8143. activeConfiguration.SaveSafe("tmp");
  8144. on_actionFitToScreen_triggered();
  8145. }
  8146. QAction *OBSBasic::AddDockWidget(QDockWidget *dock)
  8147. {
  8148. // Prevent the object name from being changed
  8149. connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName);
  8150. #ifdef BROWSER_AVAILABLE
  8151. QAction *action = new QAction(dock->windowTitle(), ui->menuDocks);
  8152. if (!extraBrowserMenuDocksSeparator.isNull())
  8153. ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action);
  8154. else
  8155. ui->menuDocks->addAction(action);
  8156. #else
  8157. QAction *action = ui->menuDocks->addAction(dock->windowTitle());
  8158. #endif
  8159. action->setCheckable(true);
  8160. assignDockToggle(dock, action);
  8161. oldExtraDocks.push_back(dock);
  8162. oldExtraDockNames.push_back(dock->objectName());
  8163. bool lock = ui->lockDocks->isChecked();
  8164. QDockWidget::DockWidgetFeatures features =
  8165. lock ? QDockWidget::NoDockWidgetFeatures
  8166. : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable |
  8167. QDockWidget::DockWidgetFloatable);
  8168. dock->setFeatures(features);
  8169. /* prune deleted docks */
  8170. for (int i = oldExtraDocks.size() - 1; i >= 0; i--) {
  8171. if (!oldExtraDocks[i]) {
  8172. oldExtraDocks.removeAt(i);
  8173. oldExtraDockNames.removeAt(i);
  8174. }
  8175. }
  8176. return action;
  8177. }
  8178. void OBSBasic::RepairOldExtraDockName()
  8179. {
  8180. QDockWidget *dock = reinterpret_cast<QDockWidget *>(sender());
  8181. int idx = oldExtraDocks.indexOf(dock);
  8182. QSignalBlocker block(dock);
  8183. if (idx == -1) {
  8184. blog(LOG_WARNING, "A dock got its object name changed");
  8185. return;
  8186. }
  8187. blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx]));
  8188. dock->setObjectName(oldExtraDockNames[idx]);
  8189. }
  8190. void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser)
  8191. {
  8192. if (dock->objectName().isEmpty())
  8193. return;
  8194. bool lock = ui->lockDocks->isChecked();
  8195. QDockWidget::DockWidgetFeatures features =
  8196. lock ? QDockWidget::NoDockWidgetFeatures
  8197. : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable |
  8198. QDockWidget::DockWidgetFloatable);
  8199. setupDockAction(dock);
  8200. dock->setFeatures(features);
  8201. addDockWidget(area, dock);
  8202. #ifdef BROWSER_AVAILABLE
  8203. if (extraBrowser && extraBrowserMenuDocksSeparator.isNull())
  8204. extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator();
  8205. if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull())
  8206. ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction());
  8207. else
  8208. ui->menuDocks->addAction(dock->toggleViewAction());
  8209. if (extraBrowser)
  8210. return;
  8211. #else
  8212. UNUSED_PARAMETER(extraBrowser);
  8213. ui->menuDocks->addAction(dock->toggleViewAction());
  8214. #endif
  8215. extraDockNames.push_back(dock->objectName());
  8216. extraDocks.push_back(std::shared_ptr<QDockWidget>(dock));
  8217. }
  8218. void OBSBasic::RemoveDockWidget(const QString &name)
  8219. {
  8220. if (extraDockNames.contains(name)) {
  8221. int idx = extraDockNames.indexOf(name);
  8222. extraDockNames.removeAt(idx);
  8223. extraDocks[idx].reset();
  8224. extraDocks.removeAt(idx);
  8225. } else if (extraCustomDockNames.contains(name)) {
  8226. int idx = extraCustomDockNames.indexOf(name);
  8227. extraCustomDockNames.removeAt(idx);
  8228. removeDockWidget(extraCustomDocks[idx]);
  8229. extraCustomDocks.removeAt(idx);
  8230. }
  8231. }
  8232. bool OBSBasic::IsDockObjectNameUsed(const QString &name)
  8233. {
  8234. QStringList list;
  8235. list << "scenesDock"
  8236. << "sourcesDock"
  8237. << "mixerDock"
  8238. << "transitionsDock"
  8239. << "controlsDock"
  8240. << "statsDock";
  8241. list << oldExtraDockNames;
  8242. list << extraDockNames;
  8243. list << extraCustomDockNames;
  8244. return list.contains(name);
  8245. }
  8246. void OBSBasic::AddCustomDockWidget(QDockWidget *dock)
  8247. {
  8248. // Prevent the object name from being changed
  8249. connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName);
  8250. bool lock = ui->lockDocks->isChecked();
  8251. QDockWidget::DockWidgetFeatures features =
  8252. lock ? QDockWidget::NoDockWidgetFeatures
  8253. : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable |
  8254. QDockWidget::DockWidgetFloatable);
  8255. dock->setFeatures(features);
  8256. addDockWidget(Qt::RightDockWidgetArea, dock);
  8257. extraCustomDockNames.push_back(dock->objectName());
  8258. extraCustomDocks.push_back(dock);
  8259. }
  8260. void OBSBasic::RepairCustomExtraDockName()
  8261. {
  8262. QDockWidget *dock = reinterpret_cast<QDockWidget *>(sender());
  8263. int idx = extraCustomDocks.indexOf(dock);
  8264. QSignalBlocker block(dock);
  8265. if (idx == -1) {
  8266. blog(LOG_WARNING, "A custom dock got its object name changed");
  8267. return;
  8268. }
  8269. blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx]));
  8270. dock->setObjectName(extraCustomDockNames[idx]);
  8271. }
  8272. OBSBasic *OBSBasic::Get()
  8273. {
  8274. return reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  8275. }
  8276. bool OBSBasic::StreamingActive()
  8277. {
  8278. if (!outputHandler)
  8279. return false;
  8280. return outputHandler->StreamingActive();
  8281. }
  8282. bool OBSBasic::RecordingActive()
  8283. {
  8284. if (!outputHandler)
  8285. return false;
  8286. return outputHandler->RecordingActive();
  8287. }
  8288. bool OBSBasic::ReplayBufferActive()
  8289. {
  8290. if (!outputHandler)
  8291. return false;
  8292. return outputHandler->ReplayBufferActive();
  8293. }
  8294. bool OBSBasic::VirtualCamActive()
  8295. {
  8296. if (!outputHandler)
  8297. return false;
  8298. return outputHandler->VirtualCamActive();
  8299. }
  8300. SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {}
  8301. void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
  8302. {
  8303. QStyledItemDelegate::setEditorData(editor, index);
  8304. QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
  8305. if (lineEdit)
  8306. lineEdit->selectAll();
  8307. }
  8308. bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event)
  8309. {
  8310. if (event->type() == QEvent::KeyPress) {
  8311. QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
  8312. switch (keyEvent->key()) {
  8313. case Qt::Key_Escape: {
  8314. QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
  8315. if (lineEdit)
  8316. lineEdit->undo();
  8317. break;
  8318. }
  8319. case Qt::Key_Tab:
  8320. case Qt::Key_Backtab:
  8321. return false;
  8322. }
  8323. }
  8324. return QStyledItemDelegate::eventFilter(editor, event);
  8325. }
  8326. void OBSBasic::UpdatePatronJson(const QString &text, const QString &error)
  8327. {
  8328. if (!error.isEmpty())
  8329. return;
  8330. patronJson = QT_TO_UTF8(text);
  8331. }
  8332. void OBSBasic::PauseRecording()
  8333. {
  8334. if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput ||
  8335. os_atomic_load_bool(&recording_paused))
  8336. return;
  8337. obs_output_t *output = outputHandler->fileOutput;
  8338. if (obs_output_pause(output, true)) {
  8339. os_atomic_set_bool(&recording_paused, true);
  8340. emit RecordingPaused();
  8341. ui->statusbar->RecordingPaused();
  8342. TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused);
  8343. if (trayIcon && trayIcon->isVisible()) {
  8344. #ifdef __APPLE__
  8345. QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg");
  8346. trayIconFile.setIsMask(true);
  8347. #else
  8348. QIcon trayIconFile = QIcon(":/res/images/obs_paused.png");
  8349. #endif
  8350. trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile));
  8351. }
  8352. OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED);
  8353. if (os_atomic_load_bool(&replaybuf_active))
  8354. ShowReplayBufferPauseWarning();
  8355. }
  8356. }
  8357. void OBSBasic::UnpauseRecording()
  8358. {
  8359. if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput ||
  8360. !os_atomic_load_bool(&recording_paused))
  8361. return;
  8362. obs_output_t *output = outputHandler->fileOutput;
  8363. if (obs_output_pause(output, false)) {
  8364. os_atomic_set_bool(&recording_paused, false);
  8365. emit RecordingUnpaused();
  8366. ui->statusbar->RecordingUnpaused();
  8367. TaskbarOverlaySetStatus(TaskbarOverlayStatusActive);
  8368. if (trayIcon && trayIcon->isVisible()) {
  8369. #ifdef __APPLE__
  8370. QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg");
  8371. trayIconFile.setIsMask(true);
  8372. #else
  8373. QIcon trayIconFile = QIcon(":/res/images/tray_active.png");
  8374. #endif
  8375. trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile));
  8376. }
  8377. OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED);
  8378. }
  8379. }
  8380. void OBSBasic::RecordPauseToggled()
  8381. {
  8382. if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput)
  8383. return;
  8384. obs_output_t *output = outputHandler->fileOutput;
  8385. bool enable = !obs_output_paused(output);
  8386. if (enable)
  8387. PauseRecording();
  8388. else
  8389. UnpauseRecording();
  8390. }
  8391. void OBSBasic::UpdateIsRecordingPausable()
  8392. {
  8393. const char *mode = config_get_string(activeConfiguration, "Output", "Mode");
  8394. bool adv = astrcmpi(mode, "Advanced") == 0;
  8395. bool shared = true;
  8396. if (adv) {
  8397. const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType");
  8398. if (astrcmpi(recType, "FFmpeg") == 0) {
  8399. shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile");
  8400. } else {
  8401. const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder");
  8402. shared = astrcmpi(recordEncoder, "none") == 0;
  8403. }
  8404. } else {
  8405. const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality");
  8406. shared = strcmp(quality, "Stream") == 0;
  8407. }
  8408. isRecordingPausable = !shared;
  8409. }
  8410. #define MBYTE (1024ULL * 1024ULL)
  8411. #define MBYTES_LEFT_STOP_REC 50ULL
  8412. #define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE)
  8413. const char *OBSBasic::GetCurrentOutputPath()
  8414. {
  8415. const char *path = nullptr;
  8416. const char *mode = config_get_string(Config(), "Output", "Mode");
  8417. if (strcmp(mode, "Advanced") == 0) {
  8418. const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType");
  8419. if (strcmp(advanced_mode, "FFmpeg") == 0) {
  8420. path = config_get_string(Config(), "AdvOut", "FFFilePath");
  8421. } else {
  8422. path = config_get_string(Config(), "AdvOut", "RecFilePath");
  8423. }
  8424. } else {
  8425. path = config_get_string(Config(), "SimpleOutput", "FilePath");
  8426. }
  8427. return path;
  8428. }
  8429. void OBSBasic::OutputPathInvalidMessage()
  8430. {
  8431. blog(LOG_ERROR, "Recording stopped because of bad output path");
  8432. OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text"));
  8433. }
  8434. bool OBSBasic::IsFFmpegOutputToURL() const
  8435. {
  8436. const char *mode = config_get_string(Config(), "Output", "Mode");
  8437. if (strcmp(mode, "Advanced") == 0) {
  8438. const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType");
  8439. if (strcmp(advanced_mode, "FFmpeg") == 0) {
  8440. bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile");
  8441. if (!is_local)
  8442. return true;
  8443. }
  8444. }
  8445. return false;
  8446. }
  8447. bool OBSBasic::OutputPathValid()
  8448. {
  8449. if (IsFFmpegOutputToURL())
  8450. return true;
  8451. const char *path = GetCurrentOutputPath();
  8452. return path && *path && QDir(path).exists();
  8453. }
  8454. void OBSBasic::DiskSpaceMessage()
  8455. {
  8456. blog(LOG_ERROR, "Recording stopped because of low disk space");
  8457. OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg"));
  8458. }
  8459. bool OBSBasic::LowDiskSpace()
  8460. {
  8461. const char *path;
  8462. path = GetCurrentOutputPath();
  8463. if (!path)
  8464. return false;
  8465. uint64_t num_bytes = os_get_free_disk_space(path);
  8466. if (num_bytes < (MAX_BYTES_LEFT))
  8467. return true;
  8468. else
  8469. return false;
  8470. }
  8471. void OBSBasic::CheckDiskSpaceRemaining()
  8472. {
  8473. if (LowDiskSpace()) {
  8474. StopRecording();
  8475. StopReplayBuffer();
  8476. DiskSpaceMessage();
  8477. }
  8478. }
  8479. void OBSBasic::ResetStatsHotkey()
  8480. {
  8481. const QList<OBSBasicStats *> list = findChildren<OBSBasicStats *>();
  8482. for (OBSBasicStats *s : list) {
  8483. s->Reset();
  8484. }
  8485. }
  8486. void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos)
  8487. {
  8488. QWidget *widget = childAt(pos);
  8489. const char *className = nullptr;
  8490. QString objName;
  8491. if (widget != nullptr) {
  8492. className = widget->metaObject()->className();
  8493. objName = widget->objectName();
  8494. }
  8495. QPoint globalPos = mapToGlobal(pos);
  8496. if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) {
  8497. if (objName.compare("scenesDock") == 0) {
  8498. ui->scenes->customContextMenuRequested(globalPos);
  8499. } else if (objName.compare("sourcesDock") == 0) {
  8500. ui->sources->customContextMenuRequested(globalPos);
  8501. } else if (objName.compare("mixerDock") == 0) {
  8502. StackedMixerAreaContextMenuRequested();
  8503. }
  8504. } else if (!className) {
  8505. ui->menuDocks->exec(globalPos);
  8506. }
  8507. }
  8508. void OBSBasic::UpdateProjectorHideCursor()
  8509. {
  8510. for (size_t i = 0; i < projectors.size(); i++)
  8511. projectors[i]->SetHideCursor();
  8512. }
  8513. void OBSBasic::UpdateProjectorAlwaysOnTop(bool top)
  8514. {
  8515. for (size_t i = 0; i < projectors.size(); i++)
  8516. SetAlwaysOnTop(projectors[i], top);
  8517. }
  8518. void OBSBasic::ResetProjectors()
  8519. {
  8520. OBSDataArrayAutoRelease savedProjectorList = SaveProjectors();
  8521. ClearProjectors();
  8522. LoadSavedProjectors(savedProjectorList);
  8523. OpenSavedProjectors();
  8524. }
  8525. void OBSBasic::on_sourcePropertiesButton_clicked()
  8526. {
  8527. on_actionSourceProperties_triggered();
  8528. }
  8529. void OBSBasic::on_sourceFiltersButton_clicked()
  8530. {
  8531. OpenFilters();
  8532. }
  8533. void OBSBasic::on_actionSceneFilters_triggered()
  8534. {
  8535. OBSSource sceneSource = GetCurrentSceneSource();
  8536. if (sceneSource)
  8537. OpenFilters(sceneSource);
  8538. }
  8539. void OBSBasic::on_sourceInteractButton_clicked()
  8540. {
  8541. on_actionInteract_triggered();
  8542. }
  8543. void OBSBasic::ShowStatusBarMessage(const QString &message)
  8544. {
  8545. ui->statusbar->clearMessage();
  8546. ui->statusbar->showMessage(message, 10000);
  8547. }
  8548. void OBSBasic::UpdatePreviewSafeAreas()
  8549. {
  8550. drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas");
  8551. }
  8552. void OBSBasic::UpdatePreviewOverflowSettings()
  8553. {
  8554. bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden");
  8555. bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden");
  8556. bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible");
  8557. ui->preview->SetOverflowHidden(hidden);
  8558. ui->preview->SetOverflowSelectionHidden(select);
  8559. ui->preview->SetOverflowAlwaysVisible(always);
  8560. }
  8561. void OBSBasic::SetDisplayAffinity(QWindow *window)
  8562. {
  8563. if (!SetDisplayAffinitySupported())
  8564. return;
  8565. bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture");
  8566. // Don't hide projectors, those are designed to be visible / captured
  8567. if (window->property("isOBSProjectorWindow") == true)
  8568. return;
  8569. #ifdef _WIN32
  8570. HWND hwnd = (HWND)window->winId();
  8571. DWORD curAffinity;
  8572. if (GetWindowDisplayAffinity(hwnd, &curAffinity)) {
  8573. if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE)
  8574. SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE);
  8575. else if (!hideFromCapture && curAffinity != WDA_NONE)
  8576. SetWindowDisplayAffinity(hwnd, WDA_NONE);
  8577. }
  8578. #else
  8579. // TODO: Implement for other platforms if possible. Don't forget to
  8580. // implement SetDisplayAffinitySupported too!
  8581. UNUSED_PARAMETER(hideFromCapture);
  8582. #endif
  8583. }
  8584. static inline QColor color_from_int(long long val)
  8585. {
  8586. return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff);
  8587. }
  8588. QColor OBSBasic::GetSelectionColor() const
  8589. {
  8590. if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) {
  8591. return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed"));
  8592. } else {
  8593. return QColor::fromRgb(255, 0, 0);
  8594. }
  8595. }
  8596. QColor OBSBasic::GetCropColor() const
  8597. {
  8598. if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) {
  8599. return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen"));
  8600. } else {
  8601. return QColor::fromRgb(0, 255, 0);
  8602. }
  8603. }
  8604. QColor OBSBasic::GetHoverColor() const
  8605. {
  8606. if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) {
  8607. return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue"));
  8608. } else {
  8609. return QColor::fromRgb(0, 127, 255);
  8610. }
  8611. }
  8612. void OBSBasic::UpdatePreviewSpacingHelpers()
  8613. {
  8614. drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled");
  8615. }
  8616. float OBSBasic::GetDevicePixelRatio()
  8617. {
  8618. return dpi;
  8619. }
  8620. void OBSBasic::OnEvent(enum obs_frontend_event event)
  8621. {
  8622. if (api)
  8623. api->on_event(event);
  8624. }
  8625. void OBSBasic::UpdatePreviewScrollbars()
  8626. {
  8627. if (!ui->preview->IsFixedScaling()) {
  8628. ui->previewXScrollBar->setRange(0, 0);
  8629. ui->previewYScrollBar->setRange(0, 0);
  8630. }
  8631. }
  8632. void OBSBasic::on_previewXScrollBar_valueChanged(int value)
  8633. {
  8634. emit PreviewXScrollBarMoved(value);
  8635. }
  8636. void OBSBasic::on_previewYScrollBar_valueChanged(int value)
  8637. {
  8638. emit PreviewYScrollBarMoved(value);
  8639. }
  8640. void OBSBasic::PreviewScalingModeChanged(int value)
  8641. {
  8642. switch (value) {
  8643. case 0:
  8644. on_actionScaleWindow_triggered();
  8645. break;
  8646. case 1:
  8647. on_actionScaleCanvas_triggered();
  8648. break;
  8649. case 2:
  8650. on_actionScaleOutput_triggered();
  8651. break;
  8652. };
  8653. }
  8654. // MARK: - Generic UI Helper Functions
  8655. OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback)
  8656. {
  8657. OBSPromptResult result;
  8658. for (;;) {
  8659. result.success = false;
  8660. if (request.withOption && !request.optionPrompt.empty()) {
  8661. result.optionValue = request.optionValue;
  8662. result.success = NameDialog::AskForNameWithOption(
  8663. this, request.title.c_str(), request.prompt.c_str(), result.promptValue,
  8664. request.optionPrompt.c_str(), result.optionValue,
  8665. (request.promptValue.empty() ? nullptr : request.promptValue.c_str()));
  8666. } else {
  8667. result.success = NameDialog::AskForName(
  8668. this, request.title.c_str(), request.prompt.c_str(), result.promptValue,
  8669. (request.promptValue.empty() ? nullptr : request.promptValue.c_str()));
  8670. }
  8671. if (!result.success) {
  8672. break;
  8673. }
  8674. if (result.promptValue.empty()) {
  8675. OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  8676. continue;
  8677. }
  8678. if (!callback(result)) {
  8679. OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  8680. continue;
  8681. }
  8682. break;
  8683. }
  8684. return result;
  8685. }