mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-10-05 07:26:53 +08:00
Complete implementation of auto-show/hide an item in OBS on mouse-focus in XServer
This commit is contained in:
12
Makefile
12
Makefile
@@ -60,16 +60,20 @@ $(GOPATH)/bin/pkg-config-wrapper:
|
||||
sh -c 'cd 3rdparty/amd64/windows && wget https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-29-12-53/ffmpeg-n7.0.2-19-g45ecf80f0e-win64-gpl-shared-7.0.zip && unzip ffmpeg-n7.0.2-19-g45ecf80f0e-win64-gpl-shared-7.0.zip && rm -f ffmpeg-n7.0.2-19-g45ecf80f0e-win64-gpl-shared-7.0.zip'
|
||||
|
||||
streampanel-linux-amd64: builddir
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build $(GOBUILD_FLAGS) -o build/streampanel-linux-amd64 ./cmd/streampanel
|
||||
$(eval INSTALL_DEST?=build/streampanel-linux-amd64)
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build $(GOBUILD_FLAGS) -o "$(INSTALL_DEST)" ./cmd/streampanel
|
||||
|
||||
streampanel-linux-arm64: builddir
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build $(GOBUILD_FLAGS) -o build/streampanel-linux-arm64 ./cmd/streampanel
|
||||
$(eval INSTALL_DEST?=build/streampanel-linux-arm64)
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build $(GOBUILD_FLAGS) -o "$(INSTALL_DEST)" ./cmd/streampanel
|
||||
|
||||
streampanel-macos-amd64: builddir
|
||||
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build $(GOBUILD_FLAGS) -o build/streampanel-macos-amd64 ./cmd/streampanel
|
||||
$(eval INSTALL_DEST?=build/streampanel-macos-amd64)
|
||||
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build $(GOBUILD_FLAGS) -o "$(INSTALL_DEST)" ./cmd/streampanel
|
||||
|
||||
streampanel-macos-arm64: builddir
|
||||
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build $(GOBUILD_FLAGS) -o build/streampanel-macos-arm64 ./cmd/streampanel
|
||||
$(eval INSTALL_DEST?=build/streampanel-macos-arm64)
|
||||
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build $(GOBUILD_FLAGS) -o "$(INSTALL_DEST)" ./cmd/streampanel
|
||||
|
||||
3rdparty/arm64/termux-packages:
|
||||
mkdir -p 3rdparty/arm64/
|
||||
|
@@ -196,6 +196,9 @@ func main() {
|
||||
if obsGRPCClose != nil {
|
||||
defer obsGRPCClose()
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
obs_grpc.RegisterOBSServer(grpcServer, obsGRPC)
|
||||
streamd_grpc.RegisterStreamDServer(grpcServer, streamdGRPC)
|
||||
l.Infof("started server at %s", *listenAddr)
|
||||
|
11
go.mod
11
go.mod
@@ -39,7 +39,6 @@ require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
fyne.io/systray v1.11.0 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 // indirect
|
||||
github.com/MicahParks/jwkset v0.5.20 // indirect
|
||||
github.com/MicahParks/keyfunc/v3 v3.3.5 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
@@ -59,7 +58,6 @@ require (
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/datarhei/gosrt v0.7.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fatih/color v1.10.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@@ -81,6 +79,7 @@ require (
|
||||
github.com/go-ng/sort v0.0.0-20220617173827-2cc7cd04f7c7 // indirect
|
||||
github.com/go-ng/xatomic v0.0.0-20230519181013-85c0ec87e55f // indirect
|
||||
github.com/go-ng/xsort v0.0.0-20220617174223-1d146907bccc // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
@@ -138,12 +137,15 @@ require (
|
||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/xaionaro-go/spinlock v0.0.0-20200518175509-30e6d1ce68a1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
github.com/yuin/goldmark v1.7.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/yutopp/go-amf0 v0.1.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
@@ -173,6 +175,7 @@ require (
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.5.0
|
||||
github.com/AgustinSRG/go-child-process-manager v1.0.1
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802
|
||||
github.com/DataDog/gostackparse v0.6.0
|
||||
github.com/DexterLB/mpvipc v0.0.0-20230829142118-145d6eabdc37
|
||||
github.com/adrg/libvlc-go/v3 v3.1.5
|
||||
@@ -185,6 +188,7 @@ require (
|
||||
github.com/blang/mpv v0.0.0-20160810175505-d56d7352e068
|
||||
github.com/bluenviron/gortsplib/v4 v4.11.0
|
||||
github.com/chai2010/webp v1.1.1
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/getsentry/sentry-go v0.28.1
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
@@ -198,6 +202,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/sethvargo/go-password v0.3.1
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
@@ -206,7 +211,7 @@ require (
|
||||
github.com/xaionaro-go/gorex v0.0.0-20241010205749-bcd59d639c4d
|
||||
github.com/xaionaro-go/lockmap v0.0.0-20240901172806-e17aea364748
|
||||
github.com/xaionaro-go/mediamtx v0.0.0-20241009124606-94c22c603970
|
||||
github.com/xaionaro-go/obs-grpc-proxy v0.0.0-20241009130412-03d201da4f74
|
||||
github.com/xaionaro-go/obs-grpc-proxy v0.0.0-20241018162120-5faf4e7a684a
|
||||
github.com/xaionaro-go/timeapiio v0.0.0-20240915203246-b907cf699af3
|
||||
github.com/xaionaro-go/typing v0.0.0-20221123235249-2229101d38ba
|
||||
github.com/xaionaro-go/unsafetools v0.0.0-20210722164218-75ba48cf7b3c
|
||||
|
15
go.sum
15
go.sum
@@ -232,6 +232,8 @@ github.com/go-ng/xmath v0.0.0-20230704233441-028f5ea62335 h1:N17hl+3/Zqxg3SM+33Q
|
||||
github.com/go-ng/xmath v0.0.0-20230704233441-028f5ea62335/go.mod h1:rmcKNA11zmis1auYtl0UY64vE/4+QKeS07w/htllpSE=
|
||||
github.com/go-ng/xsort v0.0.0-20220617174223-1d146907bccc h1:VNz633GRJx2/hL0SpBNoNlLid4xtyi7LSJP1kHpD2Fo=
|
||||
github.com/go-ng/xsort v0.0.0-20220617174223-1d146907bccc/go.mod h1:Pz/V4pxeXP0hjBlXIrm2ehR0GJ0l4Bon3fsOl6TmoJs=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
@@ -565,6 +567,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU=
|
||||
github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
@@ -608,6 +612,10 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/sunfish-shogi/bufseekio v0.0.0-20210207115823-a4185644b365/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
@@ -630,8 +638,8 @@ github.com/xaionaro-go/logrustash v0.0.0-20240804141650-d48034780a5f h1:mMrVrYtH
|
||||
github.com/xaionaro-go/logrustash v0.0.0-20240804141650-d48034780a5f/go.mod h1:aszOZHoPPSgKwdbJUgonps3MSODqctkNhwQDDwlw0Eg=
|
||||
github.com/xaionaro-go/mediamtx v0.0.0-20241009124606-94c22c603970 h1:QmbvVR2Jt+I2TTeGef79xhfmlnvvXl+FYEHoYpe7mUY=
|
||||
github.com/xaionaro-go/mediamtx v0.0.0-20241009124606-94c22c603970/go.mod h1:3J9s+wGt6CV4MDnoXApKEdY3kdc5sd6AYEndLJSAIYI=
|
||||
github.com/xaionaro-go/obs-grpc-proxy v0.0.0-20241009130412-03d201da4f74 h1:W3nQdfHickUpLouh1Gz/0cE9H6y1pW+vX79pnhB3G4w=
|
||||
github.com/xaionaro-go/obs-grpc-proxy v0.0.0-20241009130412-03d201da4f74/go.mod h1:exSKIlCibB0ww+ABDwH+YG/iNdqVfdzXBBg5LYxkxGw=
|
||||
github.com/xaionaro-go/obs-grpc-proxy v0.0.0-20241018162120-5faf4e7a684a h1:PyX7XpLkj+eAwrPMFMGpvZIG4zBfzAfwNhwTtbORqN0=
|
||||
github.com/xaionaro-go/obs-grpc-proxy v0.0.0-20241018162120-5faf4e7a684a/go.mod h1:exSKIlCibB0ww+ABDwH+YG/iNdqVfdzXBBg5LYxkxGw=
|
||||
github.com/xaionaro-go/spinlock v0.0.0-20190309154744-55278e21e817/go.mod h1:Nb/15eS0BMty6TMuWgRQM8WCDIUlyPZagcpchHT6c9Y=
|
||||
github.com/xaionaro-go/spinlock v0.0.0-20200518175509-30e6d1ce68a1 h1:1Kqw9dv2LnznIhJoMt3dNzc/ctSj6VHjyGh4YZHjpE4=
|
||||
github.com/xaionaro-go/spinlock v0.0.0-20200518175509-30e6d1ce68a1/go.mod h1:UwmTXX+EpoEYHuy0rSys1Rp5PW+eVTgZSjgMVLJENKg=
|
||||
@@ -656,6 +664,8 @@ github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
|
||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/yutopp/go-amf0 v0.1.0 h1:a3UeBZG7nRF0zfvmPn2iAfNo1RGzUpHz1VyJD2oGrik=
|
||||
github.com/yutopp/go-amf0 v0.1.0/go.mod h1:QzDOBr9RV6sQh6E5GFEJROZbU0iQKijORBmprkb3FIk=
|
||||
github.com/yutopp/go-flv v0.3.1 h1:4ILK6OgCJgUNm2WOjaucWM5lUHE0+sLNPdjq3L0Xtjk=
|
||||
@@ -855,6 +865,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func init() {
|
||||
serializable.RegisterType[Noop]()
|
||||
serializable.RegisterType[OBSElementShowHide]()
|
||||
serializable.RegisterType[OBSItemShowHide]()
|
||||
serializable.RegisterType[OBSWindowCaptureSetSource]()
|
||||
serializable.RegisterType[StartStream]()
|
||||
serializable.RegisterType[EndStream]()
|
||||
@@ -24,23 +24,23 @@ type Action interface {
|
||||
|
||||
type ValueExpression = expression.Expression
|
||||
|
||||
type OBSElementShowHide struct {
|
||||
ElementName *string `yaml:"element_name,omitempty" json:"element_name,omitempty"`
|
||||
ElementUUID *string `yaml:"element_uuid,omitempty" json:"element_uuid,omitempty"`
|
||||
type OBSItemShowHide struct {
|
||||
ItemName *string `yaml:"item_name,omitempty" json:"item_name,omitempty"`
|
||||
ItemUUID *string `yaml:"item_uuid,omitempty" json:"item_uuid,omitempty"`
|
||||
ValueExpression ValueExpression `yaml:"value_expression,omitempty" json:"value_expression,omitempty"`
|
||||
}
|
||||
|
||||
var _ Action = (*OBSElementShowHide)(nil)
|
||||
var _ Action = (*OBSItemShowHide)(nil)
|
||||
|
||||
func (OBSElementShowHide) isAction() {}
|
||||
func (OBSItemShowHide) isAction() {}
|
||||
|
||||
func (a OBSElementShowHide) String() string {
|
||||
func (a OBSItemShowHide) String() string {
|
||||
return string(tryJSON(a))
|
||||
}
|
||||
|
||||
type OBSWindowCaptureSetSource struct {
|
||||
ElementName *string `yaml:"element_name,omitempty" json:"element_name,omitempty"`
|
||||
ElementUUID *string `yaml:"element_uuid,omitempty" json:"element_uuid,omitempty"`
|
||||
ItemName *string `yaml:"item_name,omitempty" json:"item_name,omitempty"`
|
||||
ItemUUID *string `yaml:"item_uuid,omitempty" json:"item_uuid,omitempty"`
|
||||
ValueExpression ValueExpression `yaml:"value_expression,omitempty" json:"value_expression,omitempty"`
|
||||
}
|
||||
|
||||
|
@@ -18,13 +18,13 @@ type Event interface {
|
||||
}
|
||||
|
||||
type WindowFocusChange struct {
|
||||
WindowID *uint64 `yaml:"window_id,omitempty" json:"window_id,omitempty"`
|
||||
WindowTitle *string `yaml:"window_title,omitempty" json:"window_title,omitempty"`
|
||||
WindowTitlePartial *string `yaml:"window_title_partial,omitempty" json:"window_title_partial,omitempty"`
|
||||
UserID *uint64 `yaml:"user_id,omitempty" json:"user_id,omitempty"`
|
||||
|
||||
//lint:ignore U1000 this field is used by reflection
|
||||
uiComment struct{} `uicomment:"This action will also add field .IsFocused to the event."`
|
||||
Host *string `yaml:"host,omitempty" json:"host,omitempty"`
|
||||
WindowID *uint64 `yaml:"window_id,omitempty" json:"window_id,omitempty"`
|
||||
WindowTitle *string `yaml:"window_title,omitempty" json:"window_title,omitempty"`
|
||||
UserID *uint64 `yaml:"user_id,omitempty" json:"user_id,omitempty"`
|
||||
ProcessID *uint64 `yaml:"process_id,omitempty" json:"process_id,omitempty"`
|
||||
ProcessName *string `yaml:"process_name,omitempty" json:"process_name,omitempty"`
|
||||
IsFocused *bool `yaml:"is_focused,omitempty" json:"is_focused,omitempty"`
|
||||
}
|
||||
|
||||
func (ev *WindowFocusChange) Get() Event { return ev }
|
||||
@@ -35,17 +35,25 @@ func (ev *WindowFocusChange) Match(cmpIface Event) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if !fieldMatch(ev.Host, cmp.Host) {
|
||||
return false
|
||||
}
|
||||
if !fieldMatch(ev.WindowID, cmp.WindowID) {
|
||||
return false
|
||||
}
|
||||
if !fieldMatch(ev.WindowTitle, cmp.WindowTitle) {
|
||||
return false
|
||||
}
|
||||
if !partialFieldMatch(ev.WindowTitle, cmp.WindowTitlePartial) &&
|
||||
!partialFieldMatch(cmp.WindowTitle, ev.WindowTitlePartial) {
|
||||
if !fieldMatch(ev.UserID, cmp.UserID) {
|
||||
return false
|
||||
}
|
||||
if !fieldMatch(ev.WindowID, cmp.WindowID) {
|
||||
if !fieldMatch(ev.ProcessID, cmp.ProcessID) {
|
||||
return false
|
||||
}
|
||||
if !fieldMatch(ev.ProcessName, cmp.ProcessName) {
|
||||
return false
|
||||
}
|
||||
if !fieldMatch(ev.IsFocused, cmp.IsFocused) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,5 @@
|
||||
package event
|
||||
|
||||
import "strings"
|
||||
|
||||
func fieldMatch[T comparable](f1 *T, f2 *T) bool {
|
||||
if f1 == nil || f2 == nil {
|
||||
return true
|
||||
@@ -9,11 +7,3 @@ func fieldMatch[T comparable](f1 *T, f2 *T) bool {
|
||||
|
||||
return *f1 == *f2
|
||||
}
|
||||
|
||||
func partialFieldMatch(full *string, partial *string) bool {
|
||||
if full == nil || partial == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return strings.Contains(*full, *partial)
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/xaionaro-go/streamctl/pkg/expression"
|
||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||
@@ -37,13 +38,15 @@ func (d *StreamD) submitEvent(
|
||||
ctx context.Context,
|
||||
ev event.Event,
|
||||
) error {
|
||||
logger.Debugf(ctx, "submitEvent(ctx, %s)", spew.Sdump(ev))
|
||||
defer logger.Debugf(ctx, "/submitEvent(ctx, %#v)", spew.Sdump(ev))
|
||||
exprCtx := objToMap(ev)
|
||||
for _, rule := range d.Config.TriggerRules {
|
||||
if rule.EventQuery.Match(ev) {
|
||||
observability.Go(ctx, func() {
|
||||
err := d.doAction(ctx, rule.Action, exprCtx)
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to perform action %#+v: %w", rule.Action, err)
|
||||
logger.Errorf(ctx, "unable to perform action %s: %v", rule.Action, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -63,7 +66,7 @@ func (d *StreamD) doAction(
|
||||
return d.StartStream(ctx, a.PlatID, a.Title, a.Description, a.Profile, a.CustomArgs...)
|
||||
case *action.EndStream:
|
||||
return d.EndStream(ctx, a.PlatID)
|
||||
case *action.OBSElementShowHide:
|
||||
case *action.OBSItemShowHide:
|
||||
value, err := expression.Eval[bool](a.ValueExpression, exprCtx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to Eval() the expression '%s': %w", a.ValueExpression, err)
|
||||
@@ -71,8 +74,8 @@ func (d *StreamD) doAction(
|
||||
return d.OBSElementSetShow(
|
||||
ctx,
|
||||
SceneElementIdentifier{
|
||||
Name: a.ElementName,
|
||||
UUID: a.ElementUUID,
|
||||
Name: a.ItemName,
|
||||
UUID: a.ItemUUID,
|
||||
},
|
||||
value,
|
||||
)
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -35,16 +35,16 @@ func ActionGRPC2Go(
|
||||
}
|
||||
case *streamd_grpc.Action_ObsAction:
|
||||
switch o := a.ObsAction.OBSActionOneOf.(type) {
|
||||
case *streamd_grpc.OBSAction_ElementShowHide:
|
||||
result = &action.OBSElementShowHide{
|
||||
ElementName: o.ElementShowHide.ElementName,
|
||||
ElementUUID: o.ElementShowHide.ElementUUID,
|
||||
ValueExpression: expression.Expression(o.ElementShowHide.ValueExpression),
|
||||
case *streamd_grpc.OBSAction_ItemShowHide:
|
||||
result = &action.OBSItemShowHide{
|
||||
ItemName: o.ItemShowHide.ItemName,
|
||||
ItemUUID: o.ItemShowHide.ItemUUID,
|
||||
ValueExpression: expression.Expression(o.ItemShowHide.ValueExpression),
|
||||
}
|
||||
case *streamd_grpc.OBSAction_WindowCaptureSetSource:
|
||||
result = &action.OBSWindowCaptureSetSource{
|
||||
ElementName: o.WindowCaptureSetSource.ElementName,
|
||||
ElementUUID: o.WindowCaptureSetSource.ElementUUID,
|
||||
ItemName: o.WindowCaptureSetSource.ItemName,
|
||||
ItemUUID: o.WindowCaptureSetSource.ItemUUID,
|
||||
ValueExpression: expression.Expression(o.WindowCaptureSetSource.ValueExpression),
|
||||
}
|
||||
default:
|
||||
@@ -82,13 +82,13 @@ func ActionGo2GRPC(
|
||||
PlatID: string(a.PlatID),
|
||||
},
|
||||
}
|
||||
case *action.OBSElementShowHide:
|
||||
case *action.OBSItemShowHide:
|
||||
result.ActionOneof = &streamd_grpc.Action_ObsAction{
|
||||
ObsAction: &streamd_grpc.OBSAction{
|
||||
OBSActionOneOf: &streamd_grpc.OBSAction_ElementShowHide{
|
||||
ElementShowHide: &streamd_grpc.OBSActionElementShowHide{
|
||||
ElementName: a.ElementName,
|
||||
ElementUUID: a.ElementUUID,
|
||||
OBSActionOneOf: &streamd_grpc.OBSAction_ItemShowHide{
|
||||
ItemShowHide: &streamd_grpc.OBSActionItemShowHide{
|
||||
ItemName: a.ItemName,
|
||||
ItemUUID: a.ItemUUID,
|
||||
ValueExpression: string(a.ValueExpression),
|
||||
},
|
||||
},
|
||||
@@ -99,8 +99,8 @@ func ActionGo2GRPC(
|
||||
ObsAction: &streamd_grpc.OBSAction{
|
||||
OBSActionOneOf: &streamd_grpc.OBSAction_WindowCaptureSetSource{
|
||||
WindowCaptureSetSource: &streamd_grpc.OBSActionWindowCaptureSetSource{
|
||||
ElementName: a.ElementName,
|
||||
ElementUUID: a.ElementUUID,
|
||||
ItemName: a.ItemName,
|
||||
ItemUUID: a.ItemUUID,
|
||||
ValueExpression: string(a.ValueExpression),
|
||||
},
|
||||
},
|
||||
|
@@ -23,10 +23,13 @@ func EventGo2GRPC(in event.Event) (*streamd_grpc.Event, error) {
|
||||
|
||||
func triggerGo2GRPCWindowFocusChange(q *event.WindowFocusChange) *streamd_grpc.EventWindowFocusChange {
|
||||
return &streamd_grpc.EventWindowFocusChange{
|
||||
WindowID: q.WindowID,
|
||||
WindowTitle: q.WindowTitle,
|
||||
WindowTitlePartial: q.WindowTitlePartial,
|
||||
UserID: q.UserID,
|
||||
Host: q.Host,
|
||||
WindowID: q.WindowID,
|
||||
WindowTitle: q.WindowTitle,
|
||||
ProcessID: q.ProcessID,
|
||||
ProcessName: q.ProcessName,
|
||||
UserID: q.UserID,
|
||||
IsFocused: q.IsFocused,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +46,12 @@ func triggerGRPC2GoWindowFocusChange(
|
||||
q *streamd_grpc.EventWindowFocusChange,
|
||||
) config.Event {
|
||||
return &event.WindowFocusChange{
|
||||
WindowID: q.WindowID,
|
||||
WindowTitle: q.WindowTitle,
|
||||
WindowTitlePartial: q.WindowTitlePartial,
|
||||
UserID: q.UserID,
|
||||
Host: q.Host,
|
||||
WindowID: q.WindowID,
|
||||
WindowTitle: q.WindowTitle,
|
||||
UserID: q.UserID,
|
||||
ProcessID: q.ProcessID,
|
||||
ProcessName: q.ProcessName,
|
||||
IsFocused: q.IsFocused,
|
||||
}
|
||||
}
|
||||
|
@@ -541,21 +541,21 @@ message StreamPlayersChange {}
|
||||
|
||||
message NoopRequest {}
|
||||
|
||||
message OBSActionElementShowHide {
|
||||
optional string elementName = 1;
|
||||
optional string elementUUID = 2;
|
||||
message OBSActionItemShowHide {
|
||||
optional string itemName = 1;
|
||||
optional string itemUUID = 2;
|
||||
string valueExpression = 3;
|
||||
}
|
||||
|
||||
message OBSActionWindowCaptureSetSource {
|
||||
optional string elementName = 1;
|
||||
optional string elementUUID = 2;
|
||||
optional string itemName = 1;
|
||||
optional string itemUUID = 2;
|
||||
string valueExpression = 3;
|
||||
}
|
||||
|
||||
message OBSAction {
|
||||
oneof OBSActionOneOf {
|
||||
OBSActionElementShowHide elementShowHide = 1;
|
||||
OBSActionItemShowHide itemShowHide = 1;
|
||||
OBSActionWindowCaptureSetSource windowCaptureSetSource = 2;
|
||||
}
|
||||
}
|
||||
@@ -610,10 +610,13 @@ message EventOBSSceneChange {
|
||||
}
|
||||
|
||||
message EventWindowFocusChange {
|
||||
optional uint64 windowID = 1;
|
||||
optional string windowTitle = 2;
|
||||
optional string windowTitlePartial = 3;
|
||||
optional uint64 userID = 4;
|
||||
optional string host = 1;
|
||||
optional uint64 windowID = 2;
|
||||
optional string windowTitle = 3;
|
||||
optional uint64 processID = 4;
|
||||
optional string processName = 5;
|
||||
optional uint64 userID = 6;
|
||||
optional bool isFocused = 7;
|
||||
}
|
||||
|
||||
message EventQuery {
|
||||
|
@@ -170,5 +170,51 @@ func (d *StreamD) OBSElementSetShow(
|
||||
elID SceneElementIdentifier,
|
||||
shouldShow bool,
|
||||
) error {
|
||||
return fmt.Errorf("not implemented, yet")
|
||||
if elID.Name == nil && elID.UUID == nil {
|
||||
return fmt.Errorf("elID.Name == nil && elID.UUID == nil (which is legit, but unexpected, so we fail just in case)")
|
||||
}
|
||||
|
||||
obsServer, obsServerClose, err := d.OBS(ctx)
|
||||
if obsServerClose != nil {
|
||||
defer obsServerClose()
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get a client to OBS: %w", err)
|
||||
}
|
||||
|
||||
sceneListResp, err := obsServer.GetSceneList(ctx, &obs_grpc.GetSceneListRequest{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get the scenes list: %w", err)
|
||||
}
|
||||
|
||||
for _, scene := range sceneListResp.Scenes {
|
||||
itemList, err := obsServer.GetSceneItemList(ctx, &obs_grpc.GetSceneItemListRequest{
|
||||
SceneUUID: scene.SceneUUID,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get the list of items of scene %#+v: %w", scene, err)
|
||||
}
|
||||
|
||||
for _, item := range itemList.GetSceneItems() {
|
||||
if elID.Name != nil && *elID.Name != item.GetSourceName() {
|
||||
continue
|
||||
}
|
||||
if elID.UUID != nil && *elID.UUID != item.GetSourceUUID() {
|
||||
continue
|
||||
}
|
||||
|
||||
req := &obs_grpc.SetSceneItemEnabledRequest{
|
||||
SceneName: scene.SceneName,
|
||||
SceneUUID: scene.SceneUUID,
|
||||
SceneItemID: item.SceneItemID,
|
||||
SceneItemEnabled: shouldShow,
|
||||
}
|
||||
_, err := obsServer.SetSceneItemEnabled(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to submit %#+v: %w", shouldShow, item, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
115
pkg/streampanel/events.go
Normal file
115
pkg/streampanel/events.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package streampanel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamd/config/event"
|
||||
"github.com/xaionaro-go/streamctl/pkg/windowmanagerhandler"
|
||||
)
|
||||
|
||||
var hostname *string
|
||||
|
||||
func init() {
|
||||
if _hostname, err := os.Hostname(); err == nil {
|
||||
hostname = &_hostname
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Panel) initEventSensor(ctx context.Context) {
|
||||
es, err := newEventSensor()
|
||||
if err != nil {
|
||||
p.DisplayError(err)
|
||||
return
|
||||
}
|
||||
|
||||
observability.Go(ctx, func() {
|
||||
logger.Debugf(ctx, "eventSensor")
|
||||
defer logger.Debugf(ctx, "/eventSensor")
|
||||
es.Loop(ctx, p.StreamD)
|
||||
})
|
||||
}
|
||||
|
||||
type eventSensor struct {
|
||||
WMH *windowmanagerhandler.WindowManagerHandler
|
||||
|
||||
PreviouslyFocusedWindow *windowmanagerhandler.WindowFocusChange
|
||||
}
|
||||
|
||||
func newEventSensor() (*eventSensor, error) {
|
||||
wmh, err := windowmanagerhandler.New()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to init a window manager handler: %w", err)
|
||||
}
|
||||
|
||||
return &eventSensor{
|
||||
WMH: wmh,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type submitEventer interface {
|
||||
SubmitEvent(
|
||||
ctx context.Context,
|
||||
event event.Event,
|
||||
) error
|
||||
}
|
||||
|
||||
func (es *eventSensor) Loop(
|
||||
ctx context.Context,
|
||||
eventSubmitter submitEventer,
|
||||
) {
|
||||
windowFocusChangeChan := es.WMH.WindowFocusChangeChan(ctx)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case ev := <-windowFocusChangeChan:
|
||||
if err := es.submitEventWindowFocusChange(ctx, ev, eventSubmitter); err != nil {
|
||||
logger.Errorf(ctx, "unable to submit the WindowFocusChange event %#+v: %w", ev, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (es *eventSensor) submitEventWindowFocusChange(
|
||||
ctx context.Context,
|
||||
ev windowmanagerhandler.WindowFocusChange,
|
||||
submitEventer submitEventer,
|
||||
) error {
|
||||
logger.Debugf(ctx, "submitEventWindowFocusChange(ctx, %s)", spew.Sdump(ev))
|
||||
defer logger.Debugf(ctx, "/submitEventWindowFocusChange(ctx, %#v)", spew.Sdump(ev))
|
||||
|
||||
var err *multierror.Error
|
||||
|
||||
if es.PreviouslyFocusedWindow != nil {
|
||||
ev := es.PreviouslyFocusedWindow
|
||||
err = multierror.Append(err, submitEventer.SubmitEvent(ctx, &event.WindowFocusChange{
|
||||
Host: hostname,
|
||||
WindowID: (*uint64)(ev.WindowID),
|
||||
WindowTitle: ev.WindowTitle,
|
||||
UserID: ptr(uint64(*ev.UserID)),
|
||||
ProcessID: ptr(uint64(*ev.ProcessID)),
|
||||
ProcessName: ev.ProcessName,
|
||||
IsFocused: ptr(false),
|
||||
}))
|
||||
}
|
||||
|
||||
es.PreviouslyFocusedWindow = &ev
|
||||
err = multierror.Append(err, submitEventer.SubmitEvent(ctx, &event.WindowFocusChange{
|
||||
Host: hostname,
|
||||
WindowID: (*uint64)(ev.WindowID),
|
||||
WindowTitle: ev.WindowTitle,
|
||||
UserID: ptr(uint64(*ev.UserID)),
|
||||
ProcessID: ptr(uint64(*ev.ProcessID)),
|
||||
ProcessName: ev.ProcessName,
|
||||
IsFocused: ptr(true),
|
||||
}))
|
||||
|
||||
return err.ErrorOrNil()
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
package streampanel
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
func hideWindow(w fyne.Window) {
|
||||
for i := 0; i < 10; i++ {
|
||||
time.Sleep(time.Millisecond)
|
||||
w.Hide()
|
||||
}
|
||||
}
|
@@ -231,23 +231,6 @@ func imgFillTo(
|
||||
return img
|
||||
}
|
||||
|
||||
func imgRotateFillTo(
|
||||
ctx context.Context,
|
||||
src image.Image,
|
||||
size image.Point,
|
||||
alignX streamdconsts.AlignX,
|
||||
alignY streamdconsts.AlignY,
|
||||
) image.Image {
|
||||
return imgFillTo(
|
||||
ctx,
|
||||
src,
|
||||
size,
|
||||
size,
|
||||
image.Point{},
|
||||
alignX, alignY,
|
||||
)
|
||||
}
|
||||
|
||||
const (
|
||||
ScreenshotMaxWidth = 384
|
||||
ScreenshotMaxHeight = 216
|
||||
|
@@ -22,7 +22,6 @@ import (
|
||||
"github.com/anthonynsimon/bild/adjust"
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/lusingander/colorpicker"
|
||||
"github.com/xaionaro-go/obs-grpc-proxy/pkg/obsgrpcproxy"
|
||||
"github.com/xaionaro-go/obs-grpc-proxy/protobuf/go/obs_grpc"
|
||||
"github.com/xaionaro-go/streamctl/pkg/colorx"
|
||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||
@@ -488,50 +487,37 @@ func (p *Panel) editMonitorElementWindow(
|
||||
var audioSourceNames []string
|
||||
videoSourceNameIsSet := map[string]struct{}{}
|
||||
audioSourceNameIsSet := map[string]struct{}{}
|
||||
for _, _scene := range resp.Scenes {
|
||||
scene, err := obsgrpcproxy.FromAbstractObject[map[string]any](_scene)
|
||||
if err != nil {
|
||||
p.DisplayError(fmt.Errorf("unable to convert scene info: %w", err))
|
||||
return
|
||||
}
|
||||
logger.Debugf(ctx, "scene info: %#+v", scene)
|
||||
sceneName, _ := scene["sceneName"].(string)
|
||||
for _, scene := range resp.Scenes {
|
||||
resp, err := obsServer.GetSceneItemList(ctx, &obs_grpc.GetSceneItemListRequest{
|
||||
SceneName: &sceneName,
|
||||
SceneName: scene.SceneName,
|
||||
})
|
||||
if err != nil {
|
||||
p.DisplayError(
|
||||
fmt.Errorf("unable to get the list of items of scene '%s': %w", sceneName, err),
|
||||
fmt.Errorf("unable to get the list of items of scene '%s': %w", scene.SceneName, err),
|
||||
)
|
||||
return
|
||||
}
|
||||
for _, item := range resp.SceneItems {
|
||||
source, err := obsgrpcproxy.FromAbstractObject[map[string]any](item)
|
||||
if err != nil {
|
||||
p.DisplayError(fmt.Errorf("unable to convert source info: %w", err))
|
||||
return
|
||||
}
|
||||
logger.Debugf(ctx, "source info: %#+v", source)
|
||||
sourceName, _ := source["sourceName"].(string)
|
||||
logger.Debugf(ctx, "source info: %#+v", item)
|
||||
func() {
|
||||
if _, ok := videoSourceNameIsSet[sourceName]; ok {
|
||||
if _, ok := videoSourceNameIsSet[item.SourceName]; ok {
|
||||
return
|
||||
}
|
||||
sceneItemTransform := source["sceneItemTransform"].(map[string]any)
|
||||
sourceWidth, _ := sceneItemTransform["sourceWidth"].(float64)
|
||||
sceneItemTransform := item.SceneItemTransform
|
||||
sourceWidth := sceneItemTransform.SourceWidth
|
||||
if sourceWidth == 0 {
|
||||
return
|
||||
}
|
||||
videoSourceNameIsSet[sourceName] = struct{}{}
|
||||
videoSourceNames = append(videoSourceNames, sourceName)
|
||||
videoSourceNameIsSet[item.SourceName] = struct{}{}
|
||||
videoSourceNames = append(videoSourceNames, item.SourceName)
|
||||
}()
|
||||
func() {
|
||||
if _, ok := audioSourceNameIsSet[sourceName]; ok {
|
||||
if _, ok := audioSourceNameIsSet[item.SourceName]; ok {
|
||||
return
|
||||
}
|
||||
// TODO: filter only audio sources
|
||||
audioSourceNameIsSet[sourceName] = struct{}{}
|
||||
audioSourceNames = append(audioSourceNames, sourceName)
|
||||
audioSourceNameIsSet[item.SourceName] = struct{}{}
|
||||
audioSourceNames = append(audioSourceNames, item.SourceName)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
@@ -1,21 +0,0 @@
|
||||
package streampanel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamd/config"
|
||||
)
|
||||
|
||||
func (p *Panel) setStreamDConfig(
|
||||
ctx context.Context,
|
||||
cfg *config.Config,
|
||||
) error {
|
||||
if err := p.StreamD.SetConfig(ctx, cfg); err != nil {
|
||||
return fmt.Errorf("unable to set the config: %w", err)
|
||||
}
|
||||
if err := p.StreamD.SaveConfig(ctx); err != nil {
|
||||
return fmt.Errorf("unable to save the config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -347,6 +347,7 @@ func (p *Panel) Loop(ctx context.Context, opts ...LoopOption) error {
|
||||
}
|
||||
|
||||
p.reinitScreenshoter(ctx)
|
||||
p.initEventSensor(ctx)
|
||||
|
||||
p.initMainWindow(ctx, initCfg.StartingPage)
|
||||
if streamDRunErr != nil {
|
||||
@@ -1630,25 +1631,18 @@ func (p *Panel) getUpdatedStatus_backends_noLock(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
sceneList, err := obsServer.GetSceneList(ctx, &obs_grpc.GetSceneListRequest{})
|
||||
sceneListResp, err := obsServer.GetSceneList(ctx, &obs_grpc.GetSceneListRequest{})
|
||||
if err != nil {
|
||||
p.ReportError(fmt.Errorf("unable to get the list of scene from OBS: %w", err))
|
||||
p.ReportError(err)
|
||||
return
|
||||
}
|
||||
logger.Tracef(ctx, "OBS SceneList response: %#+v", sceneList)
|
||||
|
||||
p.obsSelectScene.Options = p.obsSelectScene.Options[:0]
|
||||
for _, scene := range sceneList.Scenes {
|
||||
sceneNameAny := scene.GetFields()["sceneName"]
|
||||
sceneName := string(sceneNameAny.GetString_())
|
||||
if sceneName == "" {
|
||||
p.ReportError(fmt.Errorf("unable to parse the scene name from %#+v", scene))
|
||||
return
|
||||
}
|
||||
p.obsSelectScene.Options = append(p.obsSelectScene.Options, sceneName)
|
||||
for _, scene := range sceneListResp.Scenes {
|
||||
p.obsSelectScene.Options = append(p.obsSelectScene.Options, *scene.SceneName)
|
||||
}
|
||||
if sceneList.CurrentProgramSceneName != p.obsSelectScene.Selected {
|
||||
p.obsSelectScene.SetSelected(sceneList.CurrentProgramSceneName)
|
||||
|
||||
if sceneListResp.CurrentProgramSceneName != p.obsSelectScene.Selected {
|
||||
p.obsSelectScene.SetSelected(sceneListResp.CurrentProgramSceneName)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
@@ -57,6 +57,10 @@ func reflectMakeFieldsFor(
|
||||
return []fyne.CanvasObject{newReflectField(v, func(i uint64) {
|
||||
setter(reflect.ValueOf(i).Convert(t))
|
||||
}, namePrefix)}
|
||||
case reflect.Bool:
|
||||
return []fyne.CanvasObject{newReflectField(v, func(i bool) {
|
||||
setter(reflect.ValueOf(i).Convert(t))
|
||||
}, namePrefix)}
|
||||
case reflect.Ptr:
|
||||
return reflectMakeFieldsFor(v.Elem(), t.Elem(), func(newValue reflect.Value) {
|
||||
if newValue.IsZero() && v.CanAddr() {
|
||||
|
5
pkg/windowmanagerhandler/utils.go
Normal file
5
pkg/windowmanagerhandler/utils.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package windowmanagerhandler
|
||||
|
||||
func ptr[T any](in T) *T {
|
||||
return &in
|
||||
}
|
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type WindowManagerHandler struct {
|
||||
*PlatformSpecificWindowManagerHandler
|
||||
PlatformSpecificWindowManagerHandler
|
||||
}
|
||||
|
||||
func New() (*WindowManagerHandler, error) {
|
||||
@@ -22,6 +22,9 @@ func (wmh *WindowManagerHandler) WindowFocusChangeChan(ctx context.Context) <-ch
|
||||
}
|
||||
|
||||
type WindowFocusChange struct {
|
||||
WindowID WindowID
|
||||
WindowTitle string
|
||||
WindowID *WindowID
|
||||
WindowTitle *string
|
||||
ProcessID *PID
|
||||
UserID *UID
|
||||
ProcessName *string
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
type WindowID uint64
|
||||
type PID int // using the same underlying type as `os` does
|
||||
type UID int // using the same underlying type as `os` does
|
||||
|
||||
type XWMOrWaylandWM interface {
|
||||
WindowFocusChangeChan(ctx context.Context) <-chan WindowFocusChange
|
||||
|
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/BurntSushi/xgbutil"
|
||||
"github.com/BurntSushi/xgbutil/ewmh"
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/shirou/gopsutil/process"
|
||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||
)
|
||||
|
||||
@@ -69,9 +70,36 @@ func (wmh *XWindowManagerHandler) WindowFocusChangeChan(ctx context.Context) <-c
|
||||
continue
|
||||
}
|
||||
|
||||
pid, err := ewmh.WmPidGet(wmh.XUtil, clientID)
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to get the PID of the active window (%d): %w", clientID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
proc, err := process.NewProcess(int32(pid))
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to get process info of the active window (%d) using PID %d: %w", clientID, pid, err)
|
||||
continue
|
||||
}
|
||||
|
||||
uids, err := proc.Uids()
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to get the UIDs of the active window (%d) using PID %d", clientID, pid, err)
|
||||
continue
|
||||
}
|
||||
|
||||
procName, err := proc.Name()
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to get the process name of the active window (%d) using PID %d", clientID, pid, err)
|
||||
continue
|
||||
}
|
||||
|
||||
ch <- WindowFocusChange{
|
||||
WindowID: WindowID(clientID),
|
||||
WindowTitle: name,
|
||||
WindowID: ptr(WindowID(clientID)),
|
||||
WindowTitle: ptr(name),
|
||||
UserID: ptr(UID(uids[0])),
|
||||
ProcessID: ptr(PID(pid)),
|
||||
ProcessName: ptr(procName),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@@ -10,6 +10,8 @@ import (
|
||||
|
||||
type PlatformSpecificWindowManagerHandler struct{}
|
||||
type WindowID struct{}
|
||||
type PID struct{}
|
||||
type UID struct{}
|
||||
|
||||
func (wmh *WindowManagerHandler) init(context.Context) error {
|
||||
return fmt.Errorf("the support of window manager handler for this platform is not implemented, yet")
|
||||
|
Reference in New Issue
Block a user