Compare commits
83 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b1f581c2fd | ||
|
4198b99b83 | ||
|
37f04b681f | ||
|
c309e94db2 | ||
|
c60fbf8342 | ||
|
671fd8ffd2 | ||
|
b8a92586d4 | ||
|
1f200f4f30 | ||
|
44d64ccdc4 | ||
|
3985458832 | ||
|
7a70feedba | ||
|
88afa4a99e | ||
|
d32c75b639 | ||
|
7789588aef | ||
|
b1ad0e958b | ||
|
9c8a578e05 | ||
|
3e15b7cecb | ||
|
11cf962bfd | ||
|
a25f0499c8 | ||
|
51769d6061 | ||
|
af317eee96 | ||
|
3aded523c2 | ||
|
d3005335b8 | ||
|
18458e3465 | ||
|
a6f03db8f6 | ||
|
bed8d6a58e | ||
|
c5f9cac444 | ||
|
30baa56881 | ||
|
13165fa9c0 | ||
|
6ca3059914 | ||
|
f4019fd2af | ||
|
8c6cda2e92 | ||
|
9c03de5d00 | ||
|
24121856ee | ||
|
e410289a13 | ||
|
2601454143 | ||
|
f47b0624fd | ||
|
b2c6a8bc2a | ||
|
d002e2811f | ||
|
0148a43946 | ||
|
e12fecf971 | ||
|
886c099dba | ||
|
f81bedb71a | ||
|
2787211f0d | ||
|
98d6451e74 | ||
|
970103ddee | ||
|
aa551db066 | ||
|
4569fa79e8 | ||
|
be2d7ab6d0 | ||
|
cf438b523c | ||
|
66cd1b9c15 | ||
|
1789bb0b25 | ||
|
c1ae2c37b6 | ||
|
a32b16c467 | ||
|
06b0ec8ead | ||
|
d0bf6091a4 | ||
|
f4be2299b9 | ||
|
111750a33b | ||
|
40e999f7d1 | ||
|
dd4700eb11 | ||
|
f2cc9ea886 | ||
|
5c9a273fcc | ||
|
38928e07b8 | ||
|
4a5964c340 | ||
|
bcc39acf5e | ||
|
3fbaa3166d | ||
|
3689d4d57c | ||
|
ea43d37589 | ||
|
eda453df53 | ||
|
375f083cbf | ||
|
a56c0956ab | ||
|
d2b1c8875e | ||
|
aa66eb37de | ||
|
0a48ff3d58 | ||
|
234b5fe043 | ||
|
16db9ab30a | ||
|
ad6c53a531 | ||
|
aaf47503fe | ||
|
5de51d5565 | ||
|
21962866c5 | ||
|
14a872fb89 | ||
|
bb38972aea | ||
|
e0274e8fd6 |
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
*.log
|
*.log
|
||||||
src/config.rs
|
src/config.rs
|
||||||
|
subprojects/*/
|
||||||
|
|
165
.gitlab-ci.yml
|
@ -9,19 +9,20 @@ stages:
|
||||||
- lint
|
- lint
|
||||||
- test
|
- test
|
||||||
- release
|
- release
|
||||||
|
- deploy
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
FDO_UPSTREAM_REPO: "dabrain34/GstPipelineStudio"
|
FDO_UPSTREAM_REPO: "dabrain34/GstPipelineStudio"
|
||||||
GNOME_RUNTIME_IMAGE: "registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:41"
|
GNOME_RUNTIME_IMAGE: "quay.io/gnome_infrastructure/gnome-runtime-images:gnome-master"
|
||||||
WINDOWS_RUST_STABLE_IMAGE: "$CI_REGISTRY_IMAGE/windows:$GST_RS_IMG_TAG-main-$GST_RS_STABLE"
|
WINDOWS_RUST_STABLE_IMAGE: "$CI_REGISTRY_IMAGE/windows:$GST_RS_WIN_IMG_TAG"
|
||||||
WINDOWS_RUST_STABLE_UPSTREAM_IMAGE: "$CI_REGISTRY/$FDO_UPSTREAM_REPO/windows:$GST_RS_IMG_TAG-main-$GST_RS_STABLE"
|
WINDOWS_RUST_STABLE_UPSTREAM_IMAGE: "$CI_REGISTRY/$FDO_UPSTREAM_REPO/windows:$GST_RS_WIN_IMG_TAG"
|
||||||
|
|
||||||
# Version and tag for our current container
|
# Version and tag for our current container
|
||||||
.fedora:
|
.fedora:
|
||||||
variables:
|
variables:
|
||||||
FDO_DISTRIBUTION_VERSION: "36"
|
FDO_DISTRIBUTION_VERSION: "38"
|
||||||
# Update this to trigger a container rebuild
|
# Update this to trigger a container rebuild
|
||||||
FDO_DISTRIBUTION_TAG: "2023-02-13.1"
|
FDO_DISTRIBUTION_TAG: $GST_RS_FDO_IMG_TAG
|
||||||
before_script:
|
before_script:
|
||||||
- source ./ci/env.sh
|
- source ./ci/env.sh
|
||||||
- mkdir .cargo && echo -e "[net]\ngit-fetch-with-cli = true" > .cargo/config
|
- mkdir .cargo && echo -e "[net]\ngit-fetch-with-cli = true" > .cargo/config
|
||||||
|
@ -55,9 +56,12 @@ build-fedora-container:
|
||||||
xorg-x11-server-Xvfb
|
xorg-x11-server-Xvfb
|
||||||
wget
|
wget
|
||||||
git
|
git
|
||||||
|
flex
|
||||||
|
bison
|
||||||
FDO_DISTRIBUTION_EXEC: >-
|
FDO_DISTRIBUTION_EXEC: >-
|
||||||
ci/install-rust.sh stable &&
|
ci/install-rust.sh stable &&
|
||||||
pip3 install meson
|
pip3 install meson &&
|
||||||
|
pip3 install pre-commit
|
||||||
|
|
||||||
.windows rust docker build:
|
.windows rust docker build:
|
||||||
stage: prepare
|
stage: prepare
|
||||||
|
@ -68,12 +72,12 @@ build-fedora-container:
|
||||||
#
|
#
|
||||||
# We also don't need a CONTEXT_DIR var as its also
|
# We also don't need a CONTEXT_DIR var as its also
|
||||||
# hardcoded to be windows-docker/
|
# hardcoded to be windows-docker/
|
||||||
DOCKERFILE: 'ci/windows-docker/Dockerfile'
|
DOCKERFILE: "ci/windows-docker/Dockerfile"
|
||||||
GST_UPSTREAM_BRANCH: '1.22'
|
GST_UPSTREAM_BRANCH: "1.22"
|
||||||
tags:
|
tags:
|
||||||
- 'windows'
|
- "windows"
|
||||||
- 'shell'
|
- "shell"
|
||||||
- '2022'
|
- "2022"
|
||||||
script:
|
script:
|
||||||
# We need to pass an array and to resolve the env vars, so we can't use a variable:
|
# We need to pass an array and to resolve the env vars, so we can't use a variable:
|
||||||
- $DOCKER_BUILD_ARGS = @("--build-arg", "DEFAULT_GST_BRANCH=$GST_UPSTREAM_BRANCH", "--build-arg", "RUST_VERSION=$RUST_VERSION")
|
- $DOCKER_BUILD_ARGS = @("--build-arg", "DEFAULT_GST_BRANCH=$GST_UPSTREAM_BRANCH", "--build-arg", "RUST_VERSION=$RUST_VERSION")
|
||||||
|
@ -85,26 +89,27 @@ build-fedora-container:
|
||||||
}
|
}
|
||||||
|
|
||||||
windows rust docker stable:
|
windows rust docker stable:
|
||||||
extends: '.windows rust docker build'
|
extends: ".windows rust docker build"
|
||||||
variables:
|
variables:
|
||||||
RUST_IMAGE: !reference [variables, "WINDOWS_RUST_STABLE_IMAGE"]
|
RUST_IMAGE: $WINDOWS_RUST_STABLE_IMAGE
|
||||||
RUST_UPSTREAM_IMAGE: !reference [variables, "WINDOWS_RUST_STABLE_UPSTREAM_IMAGE"]
|
RUST_UPSTREAM_IMAGE: $WINDOWS_RUST_STABLE_UPSTREAM_IMAGE"]
|
||||||
RUST_VERSION: !reference [variables, "GST_RS_STABLE"]
|
RUST_VERSION: $GST_RS_STABLE
|
||||||
|
|
||||||
.msvc2019 build:
|
.msvc2019 build:
|
||||||
stage: test
|
stage: test
|
||||||
tags:
|
tags:
|
||||||
- 'docker'
|
- "docker"
|
||||||
- 'windows'
|
- "windows"
|
||||||
- '2022'
|
- "2022"
|
||||||
|
|
||||||
windows installer stable:
|
windows installer stable:
|
||||||
needs:
|
needs:
|
||||||
- job: 'windows rust docker stable'
|
- job: "windows rust docker stable"
|
||||||
artifacts: false
|
artifacts: false
|
||||||
image: "$WINDOWS_RUST_STABLE_IMAGE"
|
image: "$WINDOWS_RUST_STABLE_IMAGE"
|
||||||
extends: '.msvc2019 build'
|
extends: ".msvc2019 build"
|
||||||
script:
|
script:
|
||||||
|
- rustc --version
|
||||||
- git fetch --tags
|
- git fetch --tags
|
||||||
- "& ./ci/build_gps.ps1"
|
- "& ./ci/build_gps.ps1"
|
||||||
- "& ./installer/wix/prepare_gstreamer.ps1"
|
- "& ./installer/wix/prepare_gstreamer.ps1"
|
||||||
|
@ -113,22 +118,34 @@ windows installer stable:
|
||||||
paths:
|
paths:
|
||||||
- installer/wix/*.msi
|
- installer/wix/*.msi
|
||||||
expire_in: 10 days
|
expire_in: 10 days
|
||||||
when: 'manual'
|
when: "manual"
|
||||||
|
|
||||||
rustfmt-clippy:
|
pre-commit:
|
||||||
|
stage: "lint"
|
||||||
|
extends:
|
||||||
|
- .fedora
|
||||||
|
- .fdo.distribution-image@fedora
|
||||||
|
variables:
|
||||||
|
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
|
||||||
|
cache:
|
||||||
|
paths:
|
||||||
|
- ${PRE_COMMIT_HOME}
|
||||||
|
script:
|
||||||
|
- meson setup build
|
||||||
|
- pre-commit run --all-files
|
||||||
|
|
||||||
|
clippy:
|
||||||
extends:
|
extends:
|
||||||
- .fedora
|
- .fedora
|
||||||
- .fdo.distribution-image@fedora
|
- .fdo.distribution-image@fedora
|
||||||
stage: lint
|
stage: lint
|
||||||
script:
|
script:
|
||||||
- meson build
|
- meson build
|
||||||
- cargo fmt --version
|
|
||||||
- cargo fmt -- --color=always --check
|
|
||||||
- cargo clippy --version
|
- cargo clippy --version
|
||||||
- cargo clippy --color=always --all-targets -- -D warnings
|
- cargo clippy --color=always --all-targets -- -D warnings
|
||||||
|
|
||||||
windows installer release:
|
windows installer release:
|
||||||
extends: 'windows installer stable'
|
extends: "windows installer stable"
|
||||||
stage: release
|
stage: release
|
||||||
only:
|
only:
|
||||||
- flatpak
|
- flatpak
|
||||||
|
@ -136,7 +153,23 @@ windows installer release:
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- installer/wix/*.msi
|
- installer/wix/*.msi
|
||||||
when: 'always'
|
when: "always"
|
||||||
|
|
||||||
|
linux release:
|
||||||
|
extends:
|
||||||
|
- .fedora
|
||||||
|
- .fdo.distribution-image@fedora
|
||||||
|
stage: release
|
||||||
|
only:
|
||||||
|
- flatpak
|
||||||
|
- tags
|
||||||
|
script:
|
||||||
|
- meson builddir -Dbuildtype=release
|
||||||
|
- ninja -C builddir/ dist
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- builddir/meson-dist/*
|
||||||
|
when: "always"
|
||||||
|
|
||||||
test-stable:
|
test-stable:
|
||||||
extends:
|
extends:
|
||||||
|
@ -179,19 +212,11 @@ dist-package:
|
||||||
|
|
||||||
flatpak:
|
flatpak:
|
||||||
image: $GNOME_RUNTIME_IMAGE
|
image: $GNOME_RUNTIME_IMAGE
|
||||||
stage: release
|
stage: test
|
||||||
only:
|
|
||||||
- flatpak
|
|
||||||
- tags
|
|
||||||
# Using gstreamer runner avoids an issue with 'bwrap: No permissions to creating new namespace' during flatpak builder
|
|
||||||
tags:
|
|
||||||
- gstreamer
|
|
||||||
variables:
|
variables:
|
||||||
BUNDLE: "gst-pipeline-studio-nightly.flatpak"
|
|
||||||
MANIFEST_PATH: "build-aux/org.freedesktop.dabrain34.GstPipelineStudio.Devel.json"
|
MANIFEST_PATH: "build-aux/org.freedesktop.dabrain34.GstPipelineStudio.Devel.json"
|
||||||
FLATPAK_MODULE: "gst-pipeline-studio"
|
|
||||||
APP_ID: "org.freedesktop.dabrain34.GstPipelineStudio.Devel"
|
APP_ID: "org.freedesktop.dabrain34.GstPipelineStudio.Devel"
|
||||||
RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo"
|
BUNDLE: "gst-pipeline-studio-nightly.flatpak"
|
||||||
script:
|
script:
|
||||||
- flatpak-builder app ${MANIFEST_PATH}
|
- flatpak-builder app ${MANIFEST_PATH}
|
||||||
- flatpak build-export repo app
|
- flatpak build-export repo app
|
||||||
|
@ -202,6 +227,70 @@ flatpak:
|
||||||
when: "always"
|
when: "always"
|
||||||
paths:
|
paths:
|
||||||
- "${BUNDLE}"
|
- "${BUNDLE}"
|
||||||
- ".flatpak-builder/build/${FLATPAK_MODULE}/_flatpak_build/meson-logs/meson-log.txt"
|
|
||||||
- ".flatpak-builder/build/${FLATPAK_MODULE}/_flatpak_build/meson-logs/testlog.txt"
|
|
||||||
expire_in: 14 days
|
expire_in: 14 days
|
||||||
|
cache:
|
||||||
|
key: "flatpak"
|
||||||
|
paths:
|
||||||
|
- .flatpak-builder/downloads/
|
||||||
|
- .flatpak-builder/git/
|
||||||
|
- target/
|
||||||
|
- target_test/
|
||||||
|
when: "manual"
|
||||||
|
|
||||||
|
macos installer stable:
|
||||||
|
stage: test
|
||||||
|
tags:
|
||||||
|
- gst-macos-13
|
||||||
|
before_script:
|
||||||
|
- pip3 install --upgrade pip
|
||||||
|
# Make sure meson is up to date
|
||||||
|
- pip3 install -U meson
|
||||||
|
# Need to install certificates for python
|
||||||
|
- pip3 install --upgrade certifi
|
||||||
|
# Another way to install certificates
|
||||||
|
- open /Applications/Python\ 3.8/Install\ Certificates.command
|
||||||
|
# Get ninja
|
||||||
|
- pip3 install -U ninja
|
||||||
|
script:
|
||||||
|
# rust toolchain
|
||||||
|
- curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||||
|
- source $HOME/.cargo/env
|
||||||
|
# brew install
|
||||||
|
- /bin/bash -c "./installer/macos/brew_setup.sh"
|
||||||
|
- /bin/bash -c "./installer/macos/deploy_macos.sh"
|
||||||
|
artifacts:
|
||||||
|
name: "MacOS installer"
|
||||||
|
paths:
|
||||||
|
- installer/**/GstPipelineStudio*.dmg
|
||||||
|
- installer/**/GstPipelineStudio*.tar.gz
|
||||||
|
expire_in: 14 days
|
||||||
|
when: "manual"
|
||||||
|
|
||||||
|
macos installer release:
|
||||||
|
extends: "macos installer stable"
|
||||||
|
stage: release
|
||||||
|
only:
|
||||||
|
- flatpak
|
||||||
|
- tags
|
||||||
|
artifacts:
|
||||||
|
name: "MacOS installer"
|
||||||
|
paths:
|
||||||
|
- installer/**/GstPipelineStudio*.dmg
|
||||||
|
- installer/**/GstPipelineStudio*.tar.gz
|
||||||
|
when: "always"
|
||||||
|
|
||||||
|
pages:
|
||||||
|
image: ruby:2.7
|
||||||
|
stage: deploy
|
||||||
|
script:
|
||||||
|
- gem install bundler -v 2.4.22
|
||||||
|
- bundle install
|
||||||
|
- bundle exec jekyll build -d public
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
# The folder that contains the files to be exposed at the Page URL
|
||||||
|
- public
|
||||||
|
rules:
|
||||||
|
# This ensures that only pushes to the default branch will trigger
|
||||||
|
# a pages deploy
|
||||||
|
- if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
|
||||||
|
|
29
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v3.2.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-added-large-files
|
||||||
|
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
# Ruff version.
|
||||||
|
rev: v0.1.11
|
||||||
|
hooks:
|
||||||
|
# Run the linter.
|
||||||
|
- id: ruff
|
||||||
|
# Run the formatter.
|
||||||
|
- id: ruff-format
|
||||||
|
|
||||||
|
- repo: https://github.com/crate-ci/typos
|
||||||
|
rev: v1.17.0
|
||||||
|
hooks:
|
||||||
|
- id: typos
|
||||||
|
exclude: '^$|\.svg$'
|
||||||
|
|
||||||
|
- repo: https://github.com/doublify/pre-commit-rust
|
||||||
|
rev: v1.0
|
||||||
|
hooks:
|
||||||
|
- id: fmt
|
1087
Cargo.lock
generated
17
Cargo.toml
|
@ -1,15 +1,15 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst_pipeline_studio"
|
name = "gst-pipeline-studio"
|
||||||
version = "0.3.0"
|
version = "0.3.5"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
rust-version = "1.67"
|
rust-version = "1.70.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gtk = { version = "0.6.1", package = "gtk4" }
|
gtk = { version = "0.8.2", package = "gtk4" }
|
||||||
gst = { package = "gstreamer", version = "0.20" }
|
gst = { package = "gstreamer", version = "0.22.2" }
|
||||||
gst-plugin-gtk4 = { version = "0.10", optional=true }
|
gst-plugin-gtk4 = { version = "0.12.1", optional=true }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
log = "0.4.11"
|
log = "0.4.11"
|
||||||
once_cell = "1.7.2"
|
once_cell = "1.7.2"
|
||||||
|
@ -18,6 +18,11 @@ serde = "1.0"
|
||||||
serde_any = "0.5"
|
serde_any = "0.5"
|
||||||
simplelog = "0.11.2"
|
simplelog = "0.11.2"
|
||||||
futures-channel = "0.3"
|
futures-channel = "0.3"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
chrono = "0.4"
|
||||||
|
structopt = "0.3"
|
||||||
|
async-channel = "2.0.0"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
futures-executor = "0.3"
|
futures-executor = "0.3"
|
||||||
|
|
42
ChangeLog.md
|
@ -83,3 +83,45 @@
|
||||||
### Graphview
|
### Graphview
|
||||||
|
|
||||||
- [x] Update node description on property removal
|
- [x] Update node description on property removal
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
### CI/Infra
|
||||||
|
- [x] Create a macos installer
|
||||||
|
- [x] Create a windows installer
|
||||||
|
|
||||||
|
### Graphview
|
||||||
|
- [x] set/get the file format version
|
||||||
|
|
||||||
|
### GStreamer
|
||||||
|
- [x] Display GStreamer version in the about dialog
|
||||||
|
|
||||||
|
## 0.3.1
|
||||||
|
### app
|
||||||
|
- [x] Add multiple graphviews with tabs.
|
||||||
|
- [x] handle the caps setter element
|
||||||
|
|
||||||
|
## 0.3.2
|
||||||
|
### app
|
||||||
|
- [x] check that element exists before creating it on file load.
|
||||||
|
|
||||||
|
## 0.3.3
|
||||||
|
|
||||||
|
### app
|
||||||
|
- [x] Fix MacOs GTK runtime dependencies
|
||||||
|
- [x] Fix the maximize call with MacOS
|
||||||
|
- [x] Fix the default size at GTK save/load state
|
||||||
|
|
||||||
|
## 0.3.4
|
||||||
|
|
||||||
|
### app
|
||||||
|
- [x] Fix first run when application folder has not been created, fixes #23
|
||||||
|
- [x] Fix windows installer to bring share folder and let filesrc work properly, fixes #24
|
||||||
|
|
||||||
|
## 0.3.5
|
||||||
|
|
||||||
|
### app
|
||||||
|
- [x] logs: receive multiple log sources such as GST logs and messages.
|
||||||
|
- [x] settings: add a log level selection
|
||||||
|
- [x] rename gst_pipeline_studio to gst-pipeline-studio
|
||||||
|
- [x] can open a pipeline from the command line
|
||||||
|
|
3
Gemfile
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "jekyll"
|
|
@ -1,9 +1,11 @@
|
||||||
# Important
|
|
||||||
|
# [GstPipelineStudio](https://dabrain34.pages.freedesktop.org/GstPipelineStudio): Draw your own GStreamer pipeline ...
|
||||||
|
|
||||||
|
## Important
|
||||||
|
|
||||||
Until version 1.0, this software should be considered as **unstable**.
|
Until version 1.0, this software should be considered as **unstable**.
|
||||||
The settings moreover the graph file format might change over the development phase.
|
The settings moreover the graph file format might change over the development phase.
|
||||||
|
|
||||||
# GstPipelineStudio: Draw your own GStreamer pipeline ...
|
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
@ -49,7 +51,7 @@ brew install gstreamer gst-plugins-base gst-plugins-bad
|
||||||
```sh
|
```sh
|
||||||
$ meson builddir -Dbuildtype=release
|
$ meson builddir -Dbuildtype=release
|
||||||
$ ninja -C builddir
|
$ ninja -C builddir
|
||||||
$ ./builddir/target/release/gst_pipeline_studio
|
$ ./builddir/target/release/gst-pipeline-studio
|
||||||
```
|
```
|
||||||
|
|
||||||
## Flatpak
|
## Flatpak
|
||||||
|
|
12
TODO.md
|
@ -3,40 +3,32 @@
|
||||||
### Graphview
|
### Graphview
|
||||||
|
|
||||||
- [ ] create a crate for graphview/node/port
|
- [ ] create a crate for graphview/node/port
|
||||||
- [x] set/get the file format version
|
|
||||||
|
|
||||||
### GStreamer:
|
### GStreamer:
|
||||||
|
|
||||||
- [ ] Implement pipeline unit test
|
- [ ] Implement pipeline unit test
|
||||||
- [x] Display GStreamer version in the about dialog
|
|
||||||
|
|
||||||
### app
|
### app
|
||||||
|
|
||||||
- [ ] Control the connection between element
|
- [ ] Control the connection between element
|
||||||
- [ ] unable to connect element with incompatible caps.
|
- [ ] unable to connect element with incompatible caps.
|
||||||
- [ ] Add multiple graphviews with tabs.
|
|
||||||
- [ ] Implement graph dot render/load
|
- [ ] Implement graph dot render/load
|
||||||
- [ ] Implement a command line parser to graph
|
|
||||||
- [ ] handle the caps setter element
|
|
||||||
- [ ] Add probes on each pad to monitor the pipeline
|
- [ ] Add probes on each pad to monitor the pipeline
|
||||||
- [ ] Render a media file
|
- [ ] Render a media file
|
||||||
- [ ] Offer compatible element to a pad (autorender)
|
- [ ] Offer compatible element to a pad (autorender)
|
||||||
- [ ] Display tags/meta/message detected
|
- [ ] Display tags/meta/message detected
|
||||||
- [ ] Change TreeView to ListView
|
- [ ] Change TreeView to ListView
|
||||||
- [ ] Implement zoom on the view (https://gitlab.gnome.org/World/obfuscate/-/blob/master/src/widgets/drawing_area.rs)
|
- [ ] Implement zoom on the view (https://gitlab.gnome.org/World/obfuscate/-/blob/master/src/widgets/drawing_area.rs)
|
||||||
- [ ] Settings: add a log level selection
|
|
||||||
- [ ] reopen the last log on prematured exit (crash)
|
- [ ] reopen the last log on prematured exit (crash)
|
||||||
- [ ] Play/pause should be prevented until the pipeline is ready
|
- [ ] Play/pause should be prevented until the pipeline is ready
|
||||||
- [ ] Filter the elements by class/rank etc.
|
- [ ] Filter the elements by class/rank etc.
|
||||||
- [ ] double click on node/pad open the properties
|
- [ ] double click on node/pad open the properties
|
||||||
|
|
||||||
### CI/Infra
|
|
||||||
|
|
||||||
- [ ] Create a macos/windows job
|
|
||||||
|
|
||||||
## bugs
|
## bugs
|
||||||
|
|
||||||
- [ ] check that element exists before creating it on file load.
|
|
||||||
- [ ] Combo box is not well selected if the value is not linear such as flags. See flags in playbin
|
- [ ] Combo box is not well selected if the value is not linear such as flags. See flags in playbin
|
||||||
- [ ] opening a graph file can lead a different behavior in the pipeline. See videomixer graph where the zorder
|
- [ ] opening a graph file can lead a different behavior in the pipeline. See videomixer graph where the zorder
|
||||||
on pads is not correctly set to right one.
|
on pads is not correctly set to right one.
|
||||||
|
|
1
VERSION
Normal file
|
@ -0,0 +1 @@
|
||||||
|
0.3.5
|
|
@ -7,26 +7,39 @@ import shutil
|
||||||
|
|
||||||
env = os.environ
|
env = os.environ
|
||||||
|
|
||||||
MESON_BUILD_ROOT=sys.argv[1]
|
MESON_BUILD_ROOT = sys.argv[1]
|
||||||
MESON_SOURCE_ROOT=sys.argv[2]
|
MESON_SOURCE_ROOT = sys.argv[2]
|
||||||
CARGO_TARGET_DIR = os.path.join (MESON_BUILD_ROOT, "target")
|
CARGO_TARGET_DIR = os.path.join(MESON_BUILD_ROOT, "target")
|
||||||
env["CARGO_TARGET_DIR"] = CARGO_TARGET_DIR
|
env["CARGO_TARGET_DIR"] = CARGO_TARGET_DIR
|
||||||
CARGO_HOME = os.path.join (CARGO_TARGET_DIR, "cargo-home")
|
env["CARGO_HOME"] = os.path.join(CARGO_TARGET_DIR, "cargo-home")
|
||||||
env["CARGO_HOME"] = CARGO_HOME
|
OUTPUT = sys.argv[3]
|
||||||
OUTPUT=sys.argv[3]
|
BUILDTYPE = sys.argv[4]
|
||||||
BUILDTYPE=sys.argv[4]
|
APP_BIN = sys.argv[5]
|
||||||
APP_BIN=sys.argv[5]
|
env["PKG_CONFIG_PATH"] = (
|
||||||
|
os.path.join(MESON_BUILD_ROOT, "meson-uninstalled")
|
||||||
|
+ os.pathsep
|
||||||
|
+ env.get("PKG_CONFIG_PATH", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if BUILDTYPE == "release":
|
if BUILDTYPE == "release":
|
||||||
print("RELEASE MODE")
|
print("RELEASE MODE")
|
||||||
CMD = ['cargo', 'build', '--manifest-path', os.path.join(MESON_SOURCE_ROOT, 'Cargo.toml'), '--release']
|
CMD = [
|
||||||
|
"cargo",
|
||||||
|
"build",
|
||||||
|
"--manifest-path",
|
||||||
|
os.path.join(MESON_SOURCE_ROOT, "Cargo.toml"),
|
||||||
|
"--release",
|
||||||
|
]
|
||||||
subprocess.run(CMD, env=env)
|
subprocess.run(CMD, env=env)
|
||||||
shutil.copyfile(os.path.join(CARGO_TARGET_DIR, "release", APP_BIN), OUTPUT)
|
shutil.copy2(os.path.join(CARGO_TARGET_DIR, "release", APP_BIN), OUTPUT)
|
||||||
else:
|
else:
|
||||||
print("DEBUG MODE")
|
print("DEBUG MODE")
|
||||||
CMD = ['cargo', 'build', '--manifest-path', os.path.join(MESON_SOURCE_ROOT, 'Cargo.toml')]
|
CMD = [
|
||||||
|
"cargo",
|
||||||
|
"build",
|
||||||
|
"--manifest-path",
|
||||||
|
os.path.join(MESON_SOURCE_ROOT, "Cargo.toml"),
|
||||||
|
]
|
||||||
subprocess.run(CMD, env=env)
|
subprocess.run(CMD, env=env)
|
||||||
shutil.copyfile(os.path.join(CARGO_TARGET_DIR, "debug", APP_BIN), OUTPUT)
|
shutil.copy2(os.path.join(CARGO_TARGET_DIR, "debug", APP_BIN), OUTPUT)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,19 +3,17 @@
|
||||||
from os import environ, path
|
from os import environ, path
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
|
|
||||||
prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local')
|
prefix = environ.get("MESON_INSTALL_PREFIX", "/usr/local")
|
||||||
datadir = path.join(prefix, 'share')
|
datadir = path.join(prefix, "share")
|
||||||
destdir = environ.get('DESTDIR', '')
|
destdir = environ.get("DESTDIR", "")
|
||||||
|
|
||||||
# Package managers set this so we don't need to run
|
# Package managers set this so we don't need to run
|
||||||
if not destdir:
|
if not destdir:
|
||||||
print('Updating icon cache...')
|
print("Updating icon cache...")
|
||||||
call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')])
|
call(["gtk-update-icon-cache", "-qtf", path.join(datadir, "icons", "hicolor")])
|
||||||
|
|
||||||
print('Updating desktop database...')
|
|
||||||
call(['update-desktop-database', '-q', path.join(datadir, 'applications')])
|
|
||||||
|
|
||||||
print('Compiling GSettings schemas...')
|
|
||||||
call(['glib-compile-schemas', path.join(datadir, 'glib-2.0', 'schemas')])
|
|
||||||
|
|
||||||
|
print("Updating desktop database...")
|
||||||
|
call(["update-desktop-database", "-q", path.join(datadir, "applications")])
|
||||||
|
|
||||||
|
print("Compiling GSettings schemas...")
|
||||||
|
call(["glib-compile-schemas", path.join(datadir, "glib-2.0", "schemas")])
|
||||||
|
|
|
@ -1,28 +1,56 @@
|
||||||
{
|
{
|
||||||
"app-id": "org.freedesktop.dabrain34.GstPipelineStudio.Devel",
|
"app-id": "org.freedesktop.dabrain34.GstPipelineStudio.Devel",
|
||||||
"runtime": "org.gnome.Platform",
|
"runtime": "org.gnome.Platform",
|
||||||
"runtime-version": "41",
|
"runtime-version": "master",
|
||||||
"sdk": "org.gnome.Sdk",
|
"sdk": "org.gnome.Sdk",
|
||||||
"sdk-extensions": ["org.freedesktop.Sdk.Extension.rust-stable"],
|
"sdk-extensions": [
|
||||||
"command": "gst_pipeline_studio",
|
"org.freedesktop.Sdk.Extension.rust-stable"
|
||||||
|
],
|
||||||
|
"command": "gst-pipeline-studio",
|
||||||
"finish-args": [
|
"finish-args": [
|
||||||
"--socket=fallback-x11",
|
"--socket=fallback-x11",
|
||||||
"--socket=wayland",
|
"--socket=wayland",
|
||||||
"--device=dri",
|
"--device=dri",
|
||||||
|
"--share=ipc",
|
||||||
|
"--share=network",
|
||||||
|
"--filesystem=home",
|
||||||
"--env=G_MESSAGES_DEBUG=none",
|
"--env=G_MESSAGES_DEBUG=none",
|
||||||
"--env=RUST_BACKTRACE=1"
|
"--env=RUST_BACKTRACE=1"
|
||||||
],
|
],
|
||||||
"build-options": {
|
"build-options": {
|
||||||
"append-path": "/usr/lib/sdk/rust-stable/bin",
|
"append-path": "/usr/lib/sdk/rust-stable/bin",
|
||||||
"build-args": ["--share=network"],
|
"build-args": [
|
||||||
"test-args": ["--socket=x11", "--share=network"]
|
"--share=network"
|
||||||
|
],
|
||||||
|
"test-args": [
|
||||||
|
"--socket=x11",
|
||||||
|
"--share=network"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"name": "gst_pipeline_studio",
|
"name": "gstreamer",
|
||||||
|
"buildsystem": "meson",
|
||||||
|
"builddir": true,
|
||||||
|
"config-opts": [
|
||||||
|
"-Ddoc=disabled"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"tag": "1.22",
|
||||||
|
"url": "https://gitlab.freedesktop.org/gstreamer/gstreamer.git",
|
||||||
|
"disable-submodules": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gst-pipeline-studio",
|
||||||
"buildsystem": "meson",
|
"buildsystem": "meson",
|
||||||
"run-tests": true,
|
"run-tests": true,
|
||||||
"config-opts": ["-Dprofile=development"],
|
"config-opts": [
|
||||||
|
"-Dprofile=development"
|
||||||
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "dir",
|
"type": "dir",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
$env:MESON_ARGS = "--prefix=C:\gst-install\"
|
$env:MESON_ARGS = "--prefix=C:\gst-install\ -Dbuildtype=release"
|
||||||
cmd.exe /C "C:\BuildTools\Common7\Tools\VsDevCmd.bat -host_arch=amd64 -arch=amd64 && meson _build $env:MESON_ARGS && meson compile -C _build && ninja -C _build install"
|
cmd.exe /C "C:\BuildTools\Common7\Tools\VsDevCmd.bat -host_arch=amd64 -arch=amd64 && meson _build $env:MESON_ARGS && meson compile -C _build && ninja -C _build install"
|
||||||
if (!$?) {
|
if (!$?) {
|
||||||
Write-Host "Failed to build and install GstPipelineStudio"
|
Write-Host "Failed to build and install GstPipelineStudio"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
variables:
|
variables:
|
||||||
GST_RS_IMG_TAG: '2023-04-12.1'
|
GST_RS_WIN_IMG_TAG: "2023-12-22.0"
|
||||||
GST_RS_STABLE: '1.67.0'
|
GST_RS_FDO_IMG_TAG: "2024-01-05.0"
|
||||||
GST_RS_MSRV: '1.63.0'
|
GST_RS_STABLE: "1.70.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
source ./ci/env.sh
|
source ./ci/env.sh
|
||||||
|
|
||||||
RUSTUP_VERSION=1.23.1
|
RUSTUP_VERSION=1.26.0
|
||||||
RUST_VERSION=$1
|
RUST_VERSION=$1
|
||||||
RUST_ARCH="x86_64-unknown-linux-gnu"
|
RUST_ARCH="x86_64-unknown-linux-gnu"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# escape=`
|
# escape=`
|
||||||
|
|
||||||
FROM "registry.freedesktop.org/gstreamer/gstreamer/amd64/windows:2023-03-20.0-main"
|
FROM "registry.freedesktop.org/gstreamer/gstreamer/amd64/windows:2023-08-24.0-main"
|
||||||
|
|
||||||
# Make sure any failure in PowerShell is fatal
|
# Make sure any failure in PowerShell is fatal
|
||||||
ENV ErrorActionPreference='Stop'
|
ENV ErrorActionPreference='Stop'
|
||||||
|
@ -8,7 +8,6 @@ SHELL ["powershell","-NoLogo", "-NonInteractive", "-Command"]
|
||||||
|
|
||||||
ARG DEFAULT_GST_BRANCH="1.22"
|
ARG DEFAULT_GST_BRANCH="1.22"
|
||||||
ARG DEFAULT_GTK_BRANCH="4.8.2"
|
ARG DEFAULT_GTK_BRANCH="4.8.2"
|
||||||
ARG DEFAULT_PANGO_BRANCH="1.50.14"
|
|
||||||
ARG RUST_VERSION="invalid"
|
ARG RUST_VERSION="invalid"
|
||||||
|
|
||||||
RUN choco install -y pkgconfiglite nasm llvm
|
RUN choco install -y pkgconfiglite nasm llvm
|
||||||
|
@ -17,8 +16,7 @@ RUN choco install -y pkgconfiglite nasm llvm
|
||||||
RUN setx PATH '%PATH%;C:\Program Files\NASM;C:\gst-install\bin;c:\Program Files\gettext-iconv\bin'
|
RUN setx PATH '%PATH%;C:\Program Files\NASM;C:\gst-install\bin;c:\Program Files\gettext-iconv\bin'
|
||||||
ENV PKG_CONFIG_PATH="C:\gst-install\lib\pkgconfig"
|
ENV PKG_CONFIG_PATH="C:\gst-install\lib\pkgconfig"
|
||||||
|
|
||||||
COPY install_pango.ps1 install_gst.ps1 install_gtk.ps1 C:\
|
COPY install_gst.ps1 install_gtk.ps1 C:\
|
||||||
RUN C:\install_pango.ps1
|
|
||||||
RUN C:\install_gst.ps1
|
RUN C:\install_gst.ps1
|
||||||
RUN C:\install_gtk.ps1
|
RUN C:\install_gtk.ps1
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ if (!$?) {
|
||||||
Exit 1
|
Exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
$env:MESON_ARGS = "--prefix=C:\gst-install\ " +
|
$env:MESON_ARGS = "--prefix=C:\gst-install\ -Dbuildtype=release " +
|
||||||
"-Dglib:installed_tests=false " +
|
"-Dglib:installed_tests=false " +
|
||||||
"-Dlibnice:tests=disabled " +
|
"-Dlibnice:tests=disabled " +
|
||||||
"-Dlibnice:examples=disabled " +
|
"-Dlibnice:examples=disabled " +
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
||||||
|
|
||||||
$env:MESON_ARGS = "--prefix=C:\gst-install\"
|
$env:MESON_ARGS = "--prefix=C:\gst-install\ -Dbuildtype=release"
|
||||||
|
|
||||||
# Download gtk and all its subprojects
|
# Download gtk and all its subprojects
|
||||||
git clone -b $env:DEFAULT_GTK_BRANCH --depth 1 https://gitlab.gnome.org/gnome/gtk.git C:\gtk
|
git clone -b $env:DEFAULT_GTK_BRANCH --depth 1 https://gitlab.gnome.org/gnome/gtk.git C:\gtk
|
||||||
|
|
573
data/css/style.css
Normal file
|
@ -0,0 +1,573 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
GstPipelineStudio Website
|
||||||
|
====================
|
||||||
|
|
||||||
|
shamelessly stolen CSS from Pipewire
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* GNOME Color Palette */
|
||||||
|
:root {
|
||||||
|
--rounded-corner: 12px;
|
||||||
|
--blue1: rgb(153, 193, 241);
|
||||||
|
--blue2: rgb(98, 160, 234);
|
||||||
|
--blue3: rgb(53, 132, 228);
|
||||||
|
--blue4: rgb(28, 113, 216);
|
||||||
|
--blue5: rgb(26, 95, 180);
|
||||||
|
--green1: rgb(143, 240, 164);
|
||||||
|
--green2: rgb(87, 227, 137);
|
||||||
|
--green3: rgb(51, 209, 122);
|
||||||
|
--green4: rgb(46, 194, 126);
|
||||||
|
--green5: rgb(38, 162, 105);
|
||||||
|
--yellow1: rgb(249, 240, 107);
|
||||||
|
--yellow2: rgb(248, 228, 92);
|
||||||
|
--yellow3: rgb(246, 211, 45);
|
||||||
|
--yellow4: rgb(245, 194, 17);
|
||||||
|
--yellow5: rgb(229, 165, 10);
|
||||||
|
--orange1: rgb(255, 190, 111);
|
||||||
|
--orange2: rgb(255, 163, 72);
|
||||||
|
--orange3: rgb(255, 120, 0);
|
||||||
|
--orange4: rgb(230, 97, 0);
|
||||||
|
--orange5: rgb(198, 70, 0);
|
||||||
|
--red1: rgb(246, 97, 81);
|
||||||
|
--red2: rgb(237, 51, 59);
|
||||||
|
--red3: rgb(224, 27, 36);
|
||||||
|
--red4: rgb(192, 28, 40);
|
||||||
|
--red5: rgb(165, 29, 45);
|
||||||
|
--purple1: rgb(220, 138, 221);
|
||||||
|
--purple2: rgb(192, 97, 203);
|
||||||
|
--purple3: rgb(145, 65, 172);
|
||||||
|
--purple4: rgb(129, 61, 156);
|
||||||
|
--purple5: rgb(97, 53, 131);
|
||||||
|
--brown1: rgb(205, 171, 143);
|
||||||
|
--brown2: rgb(181, 131, 90);
|
||||||
|
--brown3: rgb(152, 106, 68);
|
||||||
|
--brown4: rgb(134, 94, 60);
|
||||||
|
--brown5: rgb(99, 69, 44);
|
||||||
|
--light1: rgb(255, 255, 255);
|
||||||
|
--light2: rgb(246, 245, 244);
|
||||||
|
--light3: rgb(222, 221, 218);
|
||||||
|
--light4: rgb(192, 191, 188);
|
||||||
|
--light5: rgb(154, 153, 150);
|
||||||
|
--dark1: rgb(119, 118, 123);
|
||||||
|
--dark2: rgb(94, 92, 100);
|
||||||
|
--dark3: rgb(61, 56, 70);
|
||||||
|
--dark4: rgb(36, 31, 49);
|
||||||
|
--dark5: rgb(0, 0, 0);
|
||||||
|
--primary-color: var(--blue5);
|
||||||
|
/* Set your project color */
|
||||||
|
--borders: var(--light3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter Var';
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
font-style: oblique 0deg 10deg;
|
||||||
|
src: url("fonts/Inter.var.woff2?v=3.19") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: "Inter Var", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: #241f31;
|
||||||
|
background-color: #f6f5f4;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin: 2rem 0 -6px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.25;
|
||||||
|
font-variation-settings: "wght" 600;
|
||||||
|
/* needed for webkit */
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 100;
|
||||||
|
font-style: normal;
|
||||||
|
margin: 3rem 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 650px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 38px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body {
|
||||||
|
filter: invert(100%) hue-rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
background-color: var(--dark5);
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
video,
|
||||||
|
iframe {
|
||||||
|
filter: invert(100%) hue-rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--primary-color);
|
||||||
|
cursor: pointer;
|
||||||
|
font-variation-settings: "wght" 600;
|
||||||
|
/* needed for webkit */
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 3rem auto 4rem;
|
||||||
|
width: 40%;
|
||||||
|
opacity: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
margin: 2rem auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.pixels {
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
/* older firefox browsers */
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
.container {
|
||||||
|
width: 80%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Singletons */
|
||||||
|
#logo {
|
||||||
|
display: block;
|
||||||
|
width: 35%;
|
||||||
|
height: 35%;
|
||||||
|
color: rgba(0, 0, 0, 0);
|
||||||
|
/* make text transparent */
|
||||||
|
background: url('../icons/org.freedesktop.dabrain34.GstPipelineStudio.ico') no-repeat center;
|
||||||
|
padding: 5rem 0 3rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-logo>img {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
a.page-logo {
|
||||||
|
filter: invert(100%) hue-rotate(180deg);
|
||||||
|
/* uninvert */
|
||||||
|
background-image: url('assets/page-logo-i.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-white {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-green {
|
||||||
|
background-color: #30D475;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-black {
|
||||||
|
background-color: #201A26;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link::after {
|
||||||
|
content: " ➜";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3em 0 3em;
|
||||||
|
font-size: 1em;
|
||||||
|
margin-top: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make tables vertically aligned to the top */
|
||||||
|
tbody td {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Github Code Highlighting */
|
||||||
|
.highlight table td {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight table pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .cm {
|
||||||
|
color: #999988;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .cp {
|
||||||
|
color: #999999;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .c1 {
|
||||||
|
color: #999988;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .cs {
|
||||||
|
color: #999999;
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .c,
|
||||||
|
.highlight .ch,
|
||||||
|
.highlight .cd,
|
||||||
|
.highlight .cpf {
|
||||||
|
color: #999988;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .err {
|
||||||
|
color: #a61717;
|
||||||
|
background-color: #e3d2d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .gd {
|
||||||
|
color: #000000;
|
||||||
|
background-color: #ffdddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .ge {
|
||||||
|
color: #000000;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .gr {
|
||||||
|
color: #aa0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .gh {
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .gi {
|
||||||
|
color: #000000;
|
||||||
|
background-color: #ddffdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .go {
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .gp {
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .gs {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .gu {
|
||||||
|
color: #aaaaaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .gt {
|
||||||
|
color: #aa0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .kc {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .kd {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .kn {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .kp {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .kr {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .kt {
|
||||||
|
color: #445588;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .k,
|
||||||
|
.highlight .kv {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .mf {
|
||||||
|
color: #009999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .mh {
|
||||||
|
color: #009999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .il {
|
||||||
|
color: #009999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .mi {
|
||||||
|
color: #009999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .mo {
|
||||||
|
color: #009999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .m,
|
||||||
|
.highlight .mb,
|
||||||
|
.highlight .mx {
|
||||||
|
color: #009999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .sb {
|
||||||
|
color: #d14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .sc {
|
||||||
|
color: #d14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .sd {
|
||||||
|
color: #d14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .s2 {
|
||||||
|
color: #d14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .se {
|
||||||
|
color: #d14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .sh {
|
||||||
|
color: #d14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .si {
|
||||||
|
color: #d14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .sx {
|
||||||
|
color: #d14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .sr {
|
||||||
|
color: #009926;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .s1 {
|
||||||
|
color: #d14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .ss {
|
||||||
|
color: #990073;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .s,
|
||||||
|
.highlight .sa,
|
||||||
|
.highlight .dl {
|
||||||
|
color: #d14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .na {
|
||||||
|
color: #008080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .bp {
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .nb {
|
||||||
|
color: #0086B3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .nc {
|
||||||
|
color: #445588;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .no {
|
||||||
|
color: #008080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .nd {
|
||||||
|
color: #3c5d5d;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .ni {
|
||||||
|
color: #800080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .ne {
|
||||||
|
color: #990000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .nf,
|
||||||
|
.highlight .fm {
|
||||||
|
color: #990000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .nl {
|
||||||
|
color: #990000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .nn {
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .nt {
|
||||||
|
color: #000080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .vc {
|
||||||
|
color: #008080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .vg {
|
||||||
|
color: #008080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .vi {
|
||||||
|
color: #008080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .nv,
|
||||||
|
.highlight .vm {
|
||||||
|
color: #008080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .ow {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .o {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .w {
|
||||||
|
color: #bbbbbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Code Blocks */
|
||||||
|
.highlighter-rouge {
|
||||||
|
padding: 2px 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlighter-rouge * {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline Code */
|
||||||
|
code.highlighter-rouge {
|
||||||
|
padding: 2px 6px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
|
||||||
|
.dialog-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-button {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 90%;
|
||||||
|
padding: .4rem 1rem;
|
||||||
|
border-radius: var(--rounded-corner);
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
color: var(--dark5);
|
||||||
|
}
|
BIN
data/icons/org.freedesktop.dabrain34.GstPipelineStudio.ico
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
data/icons/org.freedesktop.dabrain34.GstPipelineStudio.png
Normal file
After Width: | Height: | Size: 67 KiB |
|
@ -15,7 +15,7 @@
|
||||||
<caption>Composition</caption>
|
<caption>Composition</caption>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
<url type="homepage">https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio</url>
|
<url type="homepage">https://dabrain34.pages.freedesktop.org/GstPipelineStudio</url>
|
||||||
<url type="bugtracker">https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/issues</url>
|
<url type="bugtracker">https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/issues</url>
|
||||||
<categories>
|
<categories>
|
||||||
<category>Audio</category>
|
<category>Audio</category>
|
||||||
|
@ -27,7 +27,17 @@
|
||||||
<translation type="gettext">@gettext-package@</translation>
|
<translation type="gettext">@gettext-package@</translation>
|
||||||
<launchable type="desktop-id">@app-id@.desktop</launchable>
|
<launchable type="desktop-id">@app-id@.desktop</launchable>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="@version@" date="@current_date@"/>
|
<release version="@version@" date="@current_date@">
|
||||||
|
<description>
|
||||||
|
<p>Welcome to GstPipelineStudio</p>
|
||||||
|
<ul>
|
||||||
|
<li>logs: receive multiple log sources such as GST logs and messages.</li>
|
||||||
|
<li>settings: add a log level selection</li>
|
||||||
|
<li>rename gst_pipeline_studio to gst-pipeline-studio</li>
|
||||||
|
<li>can open a pipeline from the command line</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
</releases>
|
</releases>
|
||||||
<content_rating type="oars-1.1">
|
<content_rating type="oars-1.1">
|
||||||
<content_attribute id="violence-cartoon">none</content_attribute>
|
<content_attribute id="violence-cartoon">none</content_attribute>
|
||||||
|
|
|
@ -3,7 +3,7 @@ Name=GstPipelineStudio
|
||||||
GenericName=GPS
|
GenericName=GPS
|
||||||
Comment=A GUI for GStreamer
|
Comment=A GUI for GStreamer
|
||||||
Type=Application
|
Type=Application
|
||||||
Exec=gst_pipeline_studio
|
Exec=gst-pipeline-studio
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=AudioVideo;Audio;Video;Midi;Settings;GNOME;GTK;
|
Categories=AudioVideo;Audio;Video;Midi;Settings;GNOME;GTK;
|
||||||
Icon=@icon@
|
Icon=@icon@
|
||||||
|
|
Before Width: | Height: | Size: 267 KiB After Width: | Height: | Size: 210 KiB |
53
index.html
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<html>
|
||||||
|
< <head>
|
||||||
|
<title>GstPipelineStudio</title>
|
||||||
|
<link rel="stylesheet" href="data/css/style.css">
|
||||||
|
<link rel="icon" href="data/icons/org.freedesktop.dabrain34.GstPipelineStudio.ico">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1 id="logo"></h1>
|
||||||
|
<h1>GstPipelineStudio: Draw your own pipeline ...</h1>
|
||||||
|
<p><img src="data/screenshots/gps_screenshot.png" alt=""></p>
|
||||||
|
<p>GstPipelineStudio aims to provide a graphical user interface to the GStreamer framework. From a first
|
||||||
|
step in the framework with a simple pipeline to a complex pipeline debugging, the tool provides a
|
||||||
|
friendly interface to add elements to a pipeline and debug it.</p>
|
||||||
|
<h2>GstPipelineStudio 0.3.5 is out, checkout the <a
|
||||||
|
href="https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/-/blob/main/ChangeLog.md?ref_type=heads#anchor-033">
|
||||||
|
Release Notes</a> !
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<p>Download:</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://flathub.org/apps/org.freedesktop.dabrain34.GstPipelineStudio">Flathub</a></li>
|
||||||
|
<li><a
|
||||||
|
href="https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/uploads/0ab4098a9385f21d72881d27530e6e27/GstPipelineStudio-0.3.5.msi">Windows
|
||||||
|
MSI</a>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li><a
|
||||||
|
href="https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/uploads/d6d703809e3023de04e7ad1449fcb4aa/GstPipelineStudio-0.3.5.dmg">MacOS</a>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="getting-gps">Contributing to GstPipelineStudio</h2>
|
||||||
|
<p>If you want to get the the code from Gitlab, you can visit this <a
|
||||||
|
href="https://flathub.org/apps/org.freedesktop.dabrain34.GstPipelineStudio">webpage</a>
|
||||||
|
and follow the <a
|
||||||
|
href="https://flathub.org/apps/org.freedesktop.dabrain34.GstPipelineStudio/README.md">README</a>
|
||||||
|
to build your own version of GstPipelineStudio.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</h2>
|
||||||
|
</body>
|
||||||
|
<footer class="site-footer">
|
||||||
|
<p>© GstPipelineStudio, 2023</p>
|
||||||
|
|
||||||
|
<p><a href="https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio">Website source</a></p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
17
installer/macos/brew_setup.sh
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
HOMEBREW_NO_INSTALL_CLEANUP=1
|
||||||
|
|
||||||
|
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||||
|
|
||||||
|
brew install pkg-config
|
||||||
|
|
||||||
|
# GTK4 support
|
||||||
|
brew install gtk4
|
||||||
|
# brew install cairo libxrandr libxi libxcursor libxdamage libxinerama
|
||||||
|
|
||||||
|
brew install npm
|
||||||
|
|
||||||
|
npm install -g appdmg
|
||||||
|
|
||||||
|
exit 0
|
186
installer/macos/deploy_macos.sh
Executable file
|
@ -0,0 +1,186 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
|
test_ok() {
|
||||||
|
$*
|
||||||
|
if [ $? != 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# dependency library:
|
||||||
|
# Make a .app file: https://gist.github.com/oubiwann/453744744da1141ccc542ff75b47e0cf
|
||||||
|
# Make a .dmg file: https://github.com/LinusU/node-appdmg
|
||||||
|
# Can't find library: https://www.jianshu.com/p/441a7553700f
|
||||||
|
BUILD_DIR=builddir
|
||||||
|
PROJECTDIR="$( cd "$(dirname "$0")/../" ; pwd -P )"
|
||||||
|
TARGETDIR="${PROJECTDIR}/${BUILD_DIR}/INSTALL_GPS"
|
||||||
|
VERSION="$(cat VERSION)"
|
||||||
|
export VERSION
|
||||||
|
echo "VERSION=$VERSION"
|
||||||
|
|
||||||
|
|
||||||
|
GSTREAMER_OPTS="
|
||||||
|
-Dforce_fallback_for=gstreamer-1.0,libffi,pcre2 \
|
||||||
|
-Dgstreamer-1.0:libav=disabled \
|
||||||
|
-Dgstreamer-1.0:examples=disabled \
|
||||||
|
-Dgstreamer-1.0:introspection=disabled \
|
||||||
|
-Dgstreamer-1.0:rtsp_server=disabled \
|
||||||
|
-Dgstreamer-1.0:devtools=disabled \
|
||||||
|
-Dgst-plugins-base:tests=disabled \
|
||||||
|
-Dgstreamer-1.0:tests=disabled \
|
||||||
|
-Dgst-plugins-bad:openexr=disabled -Dgstreamer-1.0:gst-examples=disabled \
|
||||||
|
-Dorc:gtk_doc=disabled \
|
||||||
|
-Dgstreamer-1.0:ges=disabled \
|
||||||
|
-Dgstreamer-1.0:python=disabled"
|
||||||
|
|
||||||
|
# rebuild app release version
|
||||||
|
rm -rf "${TARGETDIR}"
|
||||||
|
test_ok meson --prefix=$TARGETDIR --buildtype=release ${BUILD_DIR} ${GSTREAMER_OPTS}
|
||||||
|
test_ok ninja -C ${BUILD_DIR} install
|
||||||
|
|
||||||
|
|
||||||
|
# copy app data files to target dir
|
||||||
|
echo -n "Copy app data files......"
|
||||||
|
test_ok mkdir -p "${TARGETDIR}/etc/"
|
||||||
|
mkdir -p "${TARGETDIR}/lib/gstreamer-1.0"
|
||||||
|
mkdir -p "${TARGETDIR}/share/doc"
|
||||||
|
mkdir -p "${TARGETDIR}/share/themes"
|
||||||
|
mkdir -p "${TARGETDIR}/share/glib-2.0/schemas"
|
||||||
|
mkdir -p "${TARGETDIR}/share/licenses/GstPipelineStudio"
|
||||||
|
mkdir -p "${TARGETDIR}/share/icons/hicolor/scalable/apps"
|
||||||
|
echo "[done]"
|
||||||
|
|
||||||
|
function lib_dependency_copy
|
||||||
|
{
|
||||||
|
local target=$1
|
||||||
|
local folder=$2
|
||||||
|
|
||||||
|
lib_dir="$( cd "$( dirname "$1" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
libraries="$(otool -L $target | grep "/*.*dylib" -o | xargs)"
|
||||||
|
for lib in $libraries; do
|
||||||
|
if [[ '/usr/lib/' != ${lib:0:9} && '/System/Library/' != ${lib:0:16} ]]; then
|
||||||
|
if [[ '@' == ${lib:0:1} ]]; then
|
||||||
|
if [[ '@loader_path' == ${lib:0:12} ]]; then
|
||||||
|
cp -n "${lib/@loader_path/$lib_dir}" $folder
|
||||||
|
else
|
||||||
|
echo "Unsupported path: $lib"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $lib != $target ]]; then
|
||||||
|
cp -n $lib $folder
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function lib_dependency_analyze
|
||||||
|
{
|
||||||
|
# This function use otool to analyze library dependency.
|
||||||
|
# then copy the dependency libraries to destination path
|
||||||
|
|
||||||
|
local library_dir=$1
|
||||||
|
local targets_dir=$2
|
||||||
|
|
||||||
|
libraries="$(find $library_dir -name \*.dylib -o -name \*.so -type f)"
|
||||||
|
for lib in $libraries; do
|
||||||
|
lib_dependency_copy $lib $targets_dir
|
||||||
|
# otool -L $lib | grep "/usr/local/*.*dylib" -o | xargs -I{} cp -n "{}" "$targets_dir"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# copy app dependency library to target dir
|
||||||
|
echo -n "Copy app dependency library......"
|
||||||
|
|
||||||
|
lib_dependency_copy ${TARGETDIR}/bin/gst-pipeline-studio "${TARGETDIR}/bin"
|
||||||
|
lib_dependency_copy ${TARGETDIR}/lib/libgobject-2.0.0.dylib "${TARGETDIR}/bin"
|
||||||
|
lib_dependency_copy ${TARGETDIR}/lib/libsoup-2.4.1.dylib "${TARGETDIR}/bin"
|
||||||
|
lib_dependency_copy "${TARGETDIR}/bin/libgtk-4.1.dylib" "${TARGETDIR}/bin"
|
||||||
|
|
||||||
|
|
||||||
|
for file in ${TARGETDIR}/lib/gstreamer-1.0/*.dylib
|
||||||
|
do
|
||||||
|
echo "${file}"
|
||||||
|
lib_dependency_copy ${file} "${TARGETDIR}/lib/"
|
||||||
|
done
|
||||||
|
|
||||||
|
test_ok cp -f "${PROJECTDIR}/macos/mac_launcher.sh" "${TARGETDIR}/bin/launcher.sh"
|
||||||
|
|
||||||
|
# FIXME should build and install gtk4 instead of using homebrew
|
||||||
|
|
||||||
|
# copy GTK runtime dependencies resource
|
||||||
|
# echo -n "Copy GTK runtime resource......"
|
||||||
|
# cp -rf /usr/local/lib/gio "${TARGETDIR}/lib/"
|
||||||
|
# cp -rf /usr/local/lib/gtk-3.0 "${TARGETDIR}/lib/"
|
||||||
|
# cp -rf /usr/local/lib/gdk-pixbuf-2.0 "${TARGETDIR}/lib/"
|
||||||
|
# cp -rf /usr/local/lib/girepository-1.0 "${TARGETDIR}/lib/"
|
||||||
|
# cp -rf /usr/local/lib/libgda-5.0 "${TARGETDIR}/lib/"
|
||||||
|
# # Avoid override the latest locale file
|
||||||
|
cp -r /opt/homebrew/share/locale "${TARGETDIR}/share/"
|
||||||
|
cp -rf /opt/homebrew/share/icons "${TARGETDIR}/share/"
|
||||||
|
cp -rf /opt/homebrew/share/gtk4-0 "${TARGETDIR}/share/"
|
||||||
|
# cp -rf /usr/local/share/fontconfig "${TARGETDIR}/share/"
|
||||||
|
# cp -rf /usr/local/share/themes/Mac "${TARGETDIR}/share/themes/"
|
||||||
|
# cp -rf /usr/local/share/themes/Default "${TARGETDIR}/share/themes/"
|
||||||
|
# cp -rf /usr/local/share/gtksourceview-4 "${TARGETDIR}/share/"
|
||||||
|
# glib-compile-schemas /usr/local/share/glib-2.0/schemas
|
||||||
|
# cp -f /usr/local/share/glib-2.0/schemas/gschema* "${TARGETDIR}/share/glib-2.0/schemas"
|
||||||
|
# # find "${TARGETDIR}/bin" -type f -path '*.dll.a' -exec rm '{}' \;
|
||||||
|
lib_dependency_analyze ${TARGETDIR}/lib ${TARGETDIR}/bin
|
||||||
|
lib_dependency_analyze ${TARGETDIR}/bin ${TARGETDIR}/bin
|
||||||
|
echo "[done]"
|
||||||
|
|
||||||
|
# copy app icons and license files to target dir
|
||||||
|
echo -n "Copy app icon(svg) files......"
|
||||||
|
cp -f "${PROJECTDIR}/../data/icons/org.freedesktop.dabrain34.GstPipelineStudio.ico" "${TARGETDIR}/bin"
|
||||||
|
cp -f "${PROJECTDIR}/../data/icons/org.freedesktop.dabrain34.GstPipelineStudio.svg" "${TARGETDIR}/share/icons/hicolor/scalable/apps"
|
||||||
|
echo "[done]"
|
||||||
|
|
||||||
|
|
||||||
|
# download license file: LGPL-3.0
|
||||||
|
echo -n "Downloading the remote license file......"
|
||||||
|
cp -f "${PROJECTDIR}/../LICENSE" "${TARGETDIR}/share/licenses/GstPipelineStudio"
|
||||||
|
if [ ! -f "${TARGETDIR}/share/licenses/GstPipelineStudio/gpl-3.0.txt" ]; then
|
||||||
|
curl "https://www.gnu.org/licenses/gpl-3.0.txt" -o "${TARGETDIR}/share/licenses/GstPipelineStudio/gpl-3.0.txt"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "[done]"
|
||||||
|
else
|
||||||
|
echo "[failed]"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[done]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "make macos executable file(.app)......"
|
||||||
|
cd "${PROJECTDIR}/${BUILD_DIR}"
|
||||||
|
cp "${PROJECTDIR}/macos/installers/Info.plist" "${PROJECTDIR}/${BUILD_DIR}"
|
||||||
|
cp "${PROJECTDIR}/macos/installers/mac.icns" "${PROJECTDIR}/${BUILD_DIR}/GstPipelineStudio.icns"
|
||||||
|
../macos/mac_app_pack.sh --path "${TARGETDIR}" --name "GstPipelineStudio" --info "Info.plist" --icons "GstPipelineStudio.icns"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "[done]"
|
||||||
|
else
|
||||||
|
echo "[failed]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# make installer package
|
||||||
|
echo "make macos installer(.dmg)......"
|
||||||
|
cp "${PROJECTDIR}/macos/installers/dmg.json" gps_dmg.json
|
||||||
|
cp "${PROJECTDIR}/macos/installers/background.png" "${PROJECTDIR}/${BUILD_DIR}/gps_dmg_background.png"
|
||||||
|
rm -f ${PROJECTDIR}/GstPipelineStudio-${VERSION}.dmg
|
||||||
|
appdmg gps_dmg.json "${PROJECTDIR}/GstPipelineStudio-${VERSION}.dmg"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "[done]"
|
||||||
|
else
|
||||||
|
echo "[failed]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# make portable package
|
||||||
|
echo -n "make macos portable......"
|
||||||
|
tar czf "${PROJECTDIR}/GstPipelineStudio-${VERSION}.tar.gz" -C "${PROJECTDIR}" ${BUILD_DIR}/GstPipelineStudio.app
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "[done]"
|
||||||
|
else
|
||||||
|
echo "[failed]"
|
||||||
|
fi
|
43
installer/macos/installers/Info.plist
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<!-- Reference: https://github.com/geany/geany-osx/blob/master/Info.plist -->
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.36</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.36</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>Copyright (C)2022 Stephane Cerveau</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>Copyright (C)2022 Stephane Cerveau</string>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>GstPipelineStudio</string>
|
||||||
|
<key>GtkOSXLaunchScriptFile</key>
|
||||||
|
<string>launcher.sh</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>gst-pipeline-studio</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>GstPipelineStudio.icns</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>org.freedesktop.dabrain34.GstPipelineStudio</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.9</string>
|
||||||
|
<key>LSApplicationCategoryType</key>
|
||||||
|
<string>public.app-category.developer-tools</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
BIN
installer/macos/installers/background.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
installer/macos/installers/background.xcf
Normal file
9
installer/macos/installers/dmg.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"title": "GstPipelineStudio installer",
|
||||||
|
"icon": "GstPipelineStudio.icns",
|
||||||
|
"background": "gps_dmg_background.png",
|
||||||
|
"contents": [
|
||||||
|
{ "x": 948, "y": 270, "type": "link", "path": "/Applications" },
|
||||||
|
{ "x": 692, "y": 270, "type": "file", "path": "GstPipelineStudio.app" }
|
||||||
|
]
|
||||||
|
}
|
BIN
installer/macos/installers/mac.icns
Normal file
131
installer/macos/mac_app_pack.sh
Executable file
|
@ -0,0 +1,131 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
VERSION=4.0.1
|
||||||
|
SCRIPT=`basename "$0"`
|
||||||
|
APP_NAME="GstPipelineStudio"
|
||||||
|
APP_ICONS="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns"
|
||||||
|
OSX_VERSION=`sw_vers -productVersion`
|
||||||
|
PWD=`pwd`
|
||||||
|
|
||||||
|
function usage {
|
||||||
|
cat <<EOF
|
||||||
|
$SCRIPT v${VERSION} for for Mac OS X
|
||||||
|
Usage:
|
||||||
|
$SCRIPT [options]
|
||||||
|
Options:
|
||||||
|
-h, --help Prints this help message, then exits
|
||||||
|
-p, --path Name of the path to 'appify' (required)
|
||||||
|
-n, --name Name of the application (default "$APP_NAME")
|
||||||
|
-o, --info Name of the Info.plist
|
||||||
|
-i, --icons Name of the icons file to use when creating the app
|
||||||
|
(defaults to $APP_ICONS)
|
||||||
|
-v, --version Prints the version of this script, then exits
|
||||||
|
Description:
|
||||||
|
Creates the GTK Mac app from a GTK install path.
|
||||||
|
Appify has one required parameter, the path to appify:
|
||||||
|
$SCRIPT --path gtk-app-install-path
|
||||||
|
Note that you cannot rename appified apps. If you want to give your app
|
||||||
|
a custom name, use the '--name' option
|
||||||
|
$SCRIPT --path gtk-app-install-path --name "Sweet"
|
||||||
|
Copyright:
|
||||||
|
Copyright (c) Thomas Aylott <http://subtlegradient.com/>
|
||||||
|
Modified by Mathias Bynens <http://mathiasbynens.be/>
|
||||||
|
Modified by Andrew Dvorak <http://OhReally.net/>
|
||||||
|
Rewritten by Duncan McGreggor <http://github.com/oubiwann/>
|
||||||
|
Modified by Zuhong Tao <https://github.com/taozuhong>
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function version {
|
||||||
|
echo "v${VERSION}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function error {
|
||||||
|
echo
|
||||||
|
echo "ERROR: $1"
|
||||||
|
echo
|
||||||
|
usage
|
||||||
|
}
|
||||||
|
|
||||||
|
while :; do
|
||||||
|
case $1 in
|
||||||
|
-h | --help ) usage;;
|
||||||
|
-p | --path ) APP_BUILD="$2"; shift ;;
|
||||||
|
-n | --name ) APP_NAME="$2"; shift ;;
|
||||||
|
-o | --info ) APP_INFO="$2"; shift ;;
|
||||||
|
-i | --icons ) APP_ICONS="$2"; shift ;;
|
||||||
|
-v | --version ) version;;
|
||||||
|
-- ) shift; break ;;
|
||||||
|
* ) break ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z ${APP_BUILD+nil} ]; then
|
||||||
|
error "The GTK app path to appify must be provided!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$APP_BUILD" ]; then
|
||||||
|
error "Can't find the target path '$APP_BUILD'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -a "$APP_NAME.app" ]; then
|
||||||
|
rm -rf "$APP_NAME.app"
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRIPT_PATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||||
|
APP_OUT_DIR="$( cd "$(dirname "$APP_BUILD")" >/dev/null 2>&1 ; pwd -P )"
|
||||||
|
|
||||||
|
APP_TOP_DIR=$APP_OUT_DIR/$APP_NAME.app
|
||||||
|
APP_CON_DIR=$APP_TOP_DIR/Contents
|
||||||
|
APP_RES_DIR=$APP_CON_DIR/Resources
|
||||||
|
APP_EXE_DIR=$APP_CON_DIR/MacOS
|
||||||
|
APP_ETC_DIR=$APP_RES_DIR/etc
|
||||||
|
APP_LIB_DIR=$APP_RES_DIR/lib
|
||||||
|
|
||||||
|
# Copy GstPipelineStudio.app resource
|
||||||
|
mkdir -vp "$APP_CON_DIR"/{MacOS,Resources}
|
||||||
|
cp -f "$APP_INFO" "$APP_CON_DIR/Info.plist"
|
||||||
|
cp -f "$APP_ICONS" "$APP_RES_DIR/$APP_NAME.icns"
|
||||||
|
cp -rf "$APP_BUILD/etc" "$APP_RES_DIR"
|
||||||
|
cp -rf "$APP_BUILD/lib" "$APP_RES_DIR"
|
||||||
|
cp -rf "$APP_BUILD/share" "$APP_RES_DIR"
|
||||||
|
cp -rf "$APP_BUILD/libexec" "$APP_RES_DIR"
|
||||||
|
cp $APP_BUILD/bin/gst-pipeline-studio $APP_EXE_DIR/gst-pipeline-studio-real
|
||||||
|
cp $APP_BUILD/bin/launcher.sh $APP_EXE_DIR/gst-pipeline-studio
|
||||||
|
chmod 766 "$APP_EXE_DIR/gst-pipeline-studio"
|
||||||
|
chmod 766 "$APP_EXE_DIR/gst-pipeline-studio-real"
|
||||||
|
chmod -R 766 "$APP_RES_DIR"/libexec/gstreamer-1.0
|
||||||
|
|
||||||
|
|
||||||
|
# Copy dependency libraries and update their search path
|
||||||
|
source $SCRIPT_PATH/mac_app_path.sh
|
||||||
|
if ls $APP_BUILD/bin/*.so 1> /dev/null 2>&1; then
|
||||||
|
for sofile in $APP_BUILD/bin/*.so; do
|
||||||
|
cp $sofile $APP_LIB_DIR
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
cp $APP_BUILD/bin/*.dylib $APP_LIB_DIR
|
||||||
|
chmod -R 766 $APP_LIB_DIR
|
||||||
|
|
||||||
|
echo -n "relocate the gstreamer plugins......"
|
||||||
|
for file in $APP_LIB_DIR/gstreamer-1.0/*.dylib
|
||||||
|
do
|
||||||
|
echo "relocating ${file}"
|
||||||
|
lib_change_paths \
|
||||||
|
@executable_path/../Resources/lib \
|
||||||
|
$APP_LIB_DIR \
|
||||||
|
${file}
|
||||||
|
done
|
||||||
|
|
||||||
|
lib_change_paths \
|
||||||
|
@executable_path/../Resources/lib \
|
||||||
|
$APP_LIB_DIR \
|
||||||
|
$APP_EXE_DIR/gst-pipeline-studio-real
|
||||||
|
|
||||||
|
|
||||||
|
lib_change_siblings $APP_LIB_DIR @loader_path
|
||||||
|
|
||||||
|
echo "Mac app bundled at '$APP_TOP_DIR'"
|
119
installer/macos/mac_app_path.sh
Executable file
|
@ -0,0 +1,119 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
# https://gitlab.com/inkscape/inkscape/-/blob/master/packaging/macos/bash_d/lib_.sh
|
||||||
|
# examples:
|
||||||
|
# lib_change_path \
|
||||||
|
# @executable_path/../Resources/lib/lib2geom.1.0.0.dylib \
|
||||||
|
# $APP_LIB_DIR/inkscape/libinkscape_base.dylib \
|
||||||
|
# $APP_EXE_DIR/inkscape
|
||||||
|
#
|
||||||
|
#lib_change_paths \
|
||||||
|
# @executable_path/../lib \
|
||||||
|
# $APP_LIB_DIR \
|
||||||
|
# $APP_BIN_DIR/gs
|
||||||
|
#
|
||||||
|
# lib_change_siblings $APP_LIB_DIR dylib
|
||||||
|
# lib_change_siblings $APP_LIB_DIR so
|
||||||
|
|
||||||
|
# include_guard
|
||||||
|
|
||||||
|
### includes ###################################################################
|
||||||
|
|
||||||
|
# Nothing here.
|
||||||
|
|
||||||
|
### variables ##################################################################
|
||||||
|
|
||||||
|
# Nothing here.
|
||||||
|
|
||||||
|
### functions ##################################################################
|
||||||
|
|
||||||
|
function lib_change_path
|
||||||
|
{
|
||||||
|
# This is a simple wrapper around install_name_tool to reduce the
|
||||||
|
# number of arguments (like $source does not have to be provided
|
||||||
|
# here as it can be deducted from $target).
|
||||||
|
# Also, the requested change can be applied to multiple binaries
|
||||||
|
# at once since 2-n arguments can be supplied.
|
||||||
|
|
||||||
|
local target=$1 # new path to dynamically linked library
|
||||||
|
local binaries=${*:2} # binaries to modify
|
||||||
|
|
||||||
|
local source_lib=${target##*/} # get library filename from target location
|
||||||
|
|
||||||
|
for binary in $binaries; do # won't work with spaces in paths
|
||||||
|
if [[ $binary == *.so ]] || [[ $binary == *.dylib ]]; then
|
||||||
|
lib_reset_id $binary
|
||||||
|
fi
|
||||||
|
local source=$(otool -L $binary | grep $source_lib | awk '{ print $1 }')
|
||||||
|
install_name_tool -change $source $target $binary
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function lib_change_paths
|
||||||
|
{
|
||||||
|
# This is a slightly more advanced wrapper around install_name_tool.
|
||||||
|
# Given a directory $lib_dir that contains the libraries, all libraries
|
||||||
|
# linked in $binary can be changed at once to a specified $target path.
|
||||||
|
|
||||||
|
local target=$1 # new path to dynamically linked library
|
||||||
|
local lib_dir=$2
|
||||||
|
local binaries=${*:3}
|
||||||
|
|
||||||
|
for binary in $binaries; do
|
||||||
|
if [[ $binary == *.so ]] || [[ $binary == *.dylib ]]; then
|
||||||
|
lib_reset_id $binary
|
||||||
|
fi
|
||||||
|
for linked_lib in $(otool -L $binary | tail -n +2 | awk '{ print $1 }'); do
|
||||||
|
if [ "$(basename $binary)" != "$(basename $linked_lib)" ] &&
|
||||||
|
[ -f $lib_dir/$(basename $linked_lib) ]; then
|
||||||
|
lib_change_path $target/$(basename $linked_lib) $binary
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function lib_change_siblings
|
||||||
|
{
|
||||||
|
# This is a slightly more advanced wrapper around install_name_tool.
|
||||||
|
# All libraries inside a given $dir that are linked to libraries present
|
||||||
|
# in that $dir can be automatically adjusted.
|
||||||
|
|
||||||
|
local dir=$1
|
||||||
|
local target=$2
|
||||||
|
|
||||||
|
for lib in $dir/*.dylib; do
|
||||||
|
lib_reset_id $lib
|
||||||
|
for linked_lib in $(otool -L $lib | tail -n +2 | awk '{ print $1 }'); do
|
||||||
|
if [ "$(basename $lib)" != "$(basename $linked_lib)" ] &&
|
||||||
|
[ -f $dir/$(basename $linked_lib) ]; then
|
||||||
|
lib_change_path $target/$(basename $linked_lib) $lib
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
if ls $dir/*.so 1> /dev/null 2>&1; then
|
||||||
|
for lib in $dir/*.so; do
|
||||||
|
lib_reset_id $lib
|
||||||
|
for linked_lib in $(otool -L $lib | tail -n +2 | awk '{ print $1 }'); do
|
||||||
|
if [ "$(basename $lib)" != "$(basename $linked_lib)" ] &&
|
||||||
|
[ -f $dir/$(basename $linked_lib) ]; then
|
||||||
|
lib_change_path $target/$(basename $linked_lib) $lib
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function lib_reset_id
|
||||||
|
{
|
||||||
|
local lib=$1
|
||||||
|
|
||||||
|
install_name_tool -id $(basename $lib) $lib
|
||||||
|
}
|
||||||
|
|
||||||
|
### aliases ####################################################################
|
||||||
|
|
||||||
|
# Nothing here.
|
||||||
|
|
||||||
|
### main #######################################################################
|
||||||
|
|
||||||
|
# Nothing here.
|
170
installer/macos/mac_launcher.sh
Executable file
|
@ -0,0 +1,170 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if test "x$GTK_DEBUG_LAUNCHER" != x; then
|
||||||
|
set -x
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test "x$GTK_DEBUG_GDB" != x; then
|
||||||
|
EXEC="gdb --args"
|
||||||
|
else
|
||||||
|
EXEC=exec
|
||||||
|
fi
|
||||||
|
|
||||||
|
name=`basename "$0"`
|
||||||
|
bundle_app="$( cd "$( dirname "$0" )/../.." >/dev/null 2>&1 && pwd )"
|
||||||
|
bundle_contents="$bundle_app"/Contents
|
||||||
|
bundle_res="$bundle_contents"/Resources
|
||||||
|
bundle_lib="$bundle_res"/lib
|
||||||
|
bundle_libexec="$bundle_res"/libexec
|
||||||
|
bundle_bin="$bundle_res"/bin
|
||||||
|
bundle_data="$bundle_res"/share
|
||||||
|
bundle_etc="$bundle_res"/etc
|
||||||
|
|
||||||
|
export DYLD_LIBRARY_PATH="$bundle_lib"
|
||||||
|
export XDG_CONFIG_DIRS="$bundle_etc"/xdg
|
||||||
|
export XDG_DATA_DIRS="$bundle_data"
|
||||||
|
export GTK_DATA_PREFIX="$bundle_res"
|
||||||
|
export GTK_EXE_PREFIX="$bundle_res"
|
||||||
|
export GTK_PATH="$bundle_res"
|
||||||
|
export GST_PLUGIN_SCANNER="$bundle_libexec/gstreamer-1.0/gst-plugin-scanner"
|
||||||
|
export GIO_EXTRA_MODULES="$bundle_lib/gio/modules"
|
||||||
|
|
||||||
|
# GIO modules
|
||||||
|
export GIO_MODULE_DIR="$bundle_lib/gio/modules"
|
||||||
|
|
||||||
|
|
||||||
|
#GStreamer plugins
|
||||||
|
export GST_PLUGIN_PATH="$bundle_lib/gstreamer-1.0"
|
||||||
|
|
||||||
|
APP=$name
|
||||||
|
I18NDIR="$bundle_data/locale"
|
||||||
|
# Set the locale-related variables appropriately:
|
||||||
|
unset LANG LC_MESSAGES LC_MONETARY LC_COLLATE
|
||||||
|
|
||||||
|
# Has a language ordering been set?
|
||||||
|
# If so, set LC_MESSAGES and LANG accordingly; otherwise skip it.
|
||||||
|
# First step uses sed to clean off the quotes and commas, to change - to _, and change the names for the chinese scripts from "Hans" to CN and "Hant" to TW.
|
||||||
|
APPLELANGUAGES=`defaults read .GlobalPreferences AppleLanguages | sed -En -e 's/\-/_/' -e 's/Hant/TW/' -e 's/Hans/CN/' -e 's/[[:space:]]*\"?([[:alnum:]_]+)\"?,?/\1/p' `
|
||||||
|
if test "$APPLELANGUAGES"; then
|
||||||
|
# A language ordering exists.
|
||||||
|
# Test, item per item, to see whether there is an corresponding locale.
|
||||||
|
for L in $APPLELANGUAGES; do
|
||||||
|
#test for exact matches:
|
||||||
|
if test -f "$I18NDIR/${L}/LC_MESSAGES/$APP.mo"; then
|
||||||
|
export LANG=$L
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
#This is a special case, because often the original strings are in US
|
||||||
|
#English and there is no translation file.
|
||||||
|
if test "x$L" == "xen_US"; then
|
||||||
|
export LANG=$L
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
#OK, now test for just the first two letters:
|
||||||
|
if test -f "$I18NDIR/${L:0:2}/LC_MESSAGES/$APP.mo"; then
|
||||||
|
export LANG=${L:0:2}
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
#Same thing, but checking for any english variant.
|
||||||
|
if test "x${L:0:2}" == "xen"; then
|
||||||
|
export LANG=$L
|
||||||
|
break
|
||||||
|
fi;
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
unset APPLELANGUAGES L
|
||||||
|
|
||||||
|
# If we didn't get a language from the language list, try the Collation preference, in case it's the only setting that exists.
|
||||||
|
APPLECOLLATION=`defaults read .GlobalPreferences AppleCollationOrder`
|
||||||
|
if test -z ${LANG} -a -n $APPLECOLLATION; then
|
||||||
|
if test -f "$I18NDIR/${APPLECOLLATION:0:2}/LC_MESSAGES/$APP.mo"; then
|
||||||
|
export LANG=${APPLECOLLATION:0:2}
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if test ! -z $APPLECOLLATION; then
|
||||||
|
export LC_COLLATE=$APPLECOLLATION
|
||||||
|
fi
|
||||||
|
unset APPLECOLLATION
|
||||||
|
|
||||||
|
# Continue by attempting to find the Locale preference.
|
||||||
|
APPLELOCALE=`defaults read .GlobalPreferences AppleLocale`
|
||||||
|
|
||||||
|
if test -f "$I18NDIR/${APPLELOCALE:0:5}/LC_MESSAGES/$APP.mo"; then
|
||||||
|
if test -z $LANG; then
|
||||||
|
export LANG="${APPLELOCALE:0:5}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif test -z $LANG -a -f "$I18NDIR/${APPLELOCALE:0:2}/LC_MESSAGES/$APP.mo"; then
|
||||||
|
export LANG="${APPLELOCALE:0:2}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#Next we need to set LC_MESSAGES. If at all possible, we want a full
|
||||||
|
#5-character locale to avoid the "Locale not supported by C library"
|
||||||
|
#warning from Gtk -- even though Gtk will translate with a
|
||||||
|
#two-character code.
|
||||||
|
if test -n $LANG; then
|
||||||
|
#If the language code matches the applelocale, then that's the message
|
||||||
|
#locale; otherwise, if it's longer than two characters, then it's
|
||||||
|
#probably a good message locale and we'll go with it.
|
||||||
|
if test $LANG == ${APPLELOCALE:0:5} -o $LANG != ${LANG:0:2}; then
|
||||||
|
export LC_MESSAGES=$LANG
|
||||||
|
#Next try if the Applelocale is longer than 2 chars and the language
|
||||||
|
#bit matches $LANG
|
||||||
|
elif test $LANG == ${APPLELOCALE:0:2} -a $APPLELOCALE > ${APPLELOCALE:0:2}; then
|
||||||
|
export LC_MESSAGES=${APPLELOCALE:0:5}
|
||||||
|
#Fail. Get a list of the locales in $PREFIX/share/locale that match
|
||||||
|
#our two letter language code and pick the first one, special casing
|
||||||
|
#english to set en_US
|
||||||
|
elif test $LANG == "en"; then
|
||||||
|
export LC_MESSAGES="en_US"
|
||||||
|
else
|
||||||
|
LOC=`find $PREFIX/share/locale -name $LANG???`
|
||||||
|
for L in $LOC; do
|
||||||
|
export LC_MESSAGES=$L
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
#All efforts have failed, so default to US english
|
||||||
|
export LANG="en_US"
|
||||||
|
export LC_MESSAGES="en_US"
|
||||||
|
fi
|
||||||
|
CURRENCY=`echo $APPLELOCALE | sed -En 's/.*currency=([[:alpha:]]+).*/\1/p'`
|
||||||
|
if test "x$CURRENCY" != "x"; then
|
||||||
|
#The user has set a special currency. Gtk doesn't install LC_MONETARY files, but Apple does in /usr/share/locale, so we're going to look there for a locale to set LC_CURRENCY to.
|
||||||
|
if test -f /usr/local/share/$LC_MESSAGES/LC_MONETARY; then
|
||||||
|
if test -a `cat /usr/local/share/$LC_MESSAGES/LC_MONETARY` == $CURRENCY; then
|
||||||
|
export LC_MONETARY=$LC_MESSAGES
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if test -z "$LC_MONETARY"; then
|
||||||
|
FILES=`find /usr/share/locale -name LC_MONETARY -exec grep -H $CURRENCY {} \;`
|
||||||
|
if test -n "$FILES"; then
|
||||||
|
export LC_MONETARY=`echo $FILES | sed -En 's%/usr/share/locale/([[:alpha:]_]+)/LC_MONETARY.*%\1%p'`
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
#No currency value means that the AppleLocale governs:
|
||||||
|
if test -z "$LC_MONETARY"; then
|
||||||
|
LC_MONETARY=${APPLELOCALE:0:5}
|
||||||
|
fi
|
||||||
|
#For Gtk, which only looks at LC_ALL:
|
||||||
|
export LC_ALL=$LC_MESSAGES
|
||||||
|
|
||||||
|
unset APPLELOCALE FILES LOC
|
||||||
|
|
||||||
|
if test -f "$bundle_lib/charset.alias"; then
|
||||||
|
export CHARSETALIASDIR="$bundle_lib"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extra arguments can be added in environment.sh.
|
||||||
|
EXTRA_ARGS=
|
||||||
|
if test -f "$bundle_res/environment.sh"; then
|
||||||
|
source "$bundle_res/environment.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Strip out the argument added by the OS.
|
||||||
|
if /bin/expr "x$1" : '^x-psn_' > /dev/null; then
|
||||||
|
shift 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
$EXEC "$bundle_contents/MacOS/gst-pipeline-studio-real" "$@" $EXTRA_ARGS
|
|
@ -78,7 +78,7 @@ This is a tricky question. We believe software patents should not exist, so that
|
||||||
\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af7\langfe2052\dbch\af8\afs24\alang1081\loch\f3\fs24\lang1031\sl276\slmult1\sb0\sa200{\b0\rtlch \ltrch\loch\fs24\lang255\loch\f6
|
\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af7\langfe2052\dbch\af8\afs24\alang1081\loch\f3\fs24\lang1031\sl276\slmult1\sb0\sa200{\b0\rtlch \ltrch\loch\fs24\lang255\loch\f6
|
||||||
Software patents are widely available in the USA. Despite they are formally prohibited in the European Union, they indeed are granted by the thousand by the European Patent Office, and also some national patent offices follow the same path. In other countries they are not available.}
|
Software patents are widely available in the USA. Despite they are formally prohibited in the European Union, they indeed are granted by the thousand by the European Patent Office, and also some national patent offices follow the same path. In other countries they are not available.}
|
||||||
\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af7\langfe2052\dbch\af8\afs24\alang1081\loch\f3\fs24\lang1031\sl276\slmult1\sb0\sa200{\b0\rtlch \ltrch\loch\fs24\lang255\loch\f6
|
\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af7\langfe2052\dbch\af8\afs24\alang1081\loch\f3\fs24\lang1031\sl276\slmult1\sb0\sa200{\b0\rtlch \ltrch\loch\fs24\lang255\loch\f6
|
||||||
Since patent protection is a national state-granted monopoly, distributing software that violates patents in a given country could be entirely safe if done in another country. Fair use exceptions also exist. So we cannot advice you whether the software we provide would be considered violating patents in your country or in any other country, but that can be said for virtually all kinds of sofware. Only, since we deal with audio-video standards, and these standards are by and large designed to use certain patented technologies, it is common wisdom that the pieces of software that implement these standards are sensitive in this respect.}
|
Since patent protection is a national state-granted monopoly, distributing software that violates patents in a given country could be entirely safe if done in another country. Fair use exceptions also exist. So we cannot advice you whether the software we provide would be considered violating patents in your country or in any other country, but that can be said for virtually all kinds of software. Only, since we deal with audio-video standards, and these standards are by and large designed to use certain patented technologies, it is common wisdom that the pieces of software that implement these standards are sensitive in this respect.}
|
||||||
\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af7\langfe2052\dbch\af8\afs24\alang1081\loch\f3\fs24\lang1031\sl276\slmult1\sb0\sa200{\b0\rtlch \ltrch\loch\fs24\lang255\loch\f6
|
\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af7\langfe2052\dbch\af8\afs24\alang1081\loch\f3\fs24\lang1031\sl276\slmult1\sb0\sa200{\b0\rtlch \ltrch\loch\fs24\lang255\loch\f6
|
||||||
This is why GStreamer has taken a modular approach, so that you can use a Free plugins or a proprietary, patent royalty bearing, plugin for a given standard.}
|
This is why GStreamer has taken a modular approach, so that you can use a Free plugins or a proprietary, patent royalty bearing, plugin for a given standard.}
|
||||||
\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af7\langfe2052\dbch\af8\afs24\alang1081\loch\f3\fs24\lang1031\sl276\slmult1\sb0\sa200{\b\rtlch \ltrch\loch\fs36\lang255\loch\f6
|
\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af7\langfe2052\dbch\af8\afs24\alang1081\loch\f3\fs24\lang1031\sl276\slmult1\sb0\sa200{\b\rtlch \ltrch\loch\fs36\lang255\loch\f6
|
||||||
|
|
|
@ -15,10 +15,9 @@ $lightToolPath = Join-Path $wixFolder -ChildPath light.exe
|
||||||
$heatToolPath = Join-Path $wixFolder -ChildPath heat.exe
|
$heatToolPath = Join-Path $wixFolder -ChildPath heat.exe
|
||||||
|
|
||||||
$GPSUpgradeCode = "9B87C8FF-599C-4F20-914E-AF5E68CB3DC0"
|
$GPSUpgradeCode = "9B87C8FF-599C-4F20-914E-AF5E68CB3DC0"
|
||||||
$GPSVersion = $(git describe --always --abbrev=0)
|
|
||||||
Write-Output $GPSVersion
|
|
||||||
$GPSVersion = "0.3.0"
|
|
||||||
|
|
||||||
|
$GPSVersion = Get-Content $PSScriptRoot\..\..\VERSION -Raw
|
||||||
|
Write-Output $GPSVersion
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Push-Location $PSScriptRoot
|
Push-Location $PSScriptRoot
|
||||||
|
@ -34,13 +33,15 @@ try
|
||||||
# GST and GTK are installed in this folder by prepare_gstreamer.ps1.
|
# GST and GTK are installed in this folder by prepare_gstreamer.ps1.
|
||||||
# GST and GTK are built by the docker image.
|
# GST and GTK are built by the docker image.
|
||||||
$gstreamerInstallDir="c:\gst-install-clean"
|
$gstreamerInstallDir="c:\gst-install-clean"
|
||||||
$gstreamerBinInstallDir= Join-Path $gstreamerInstallDir -ChildPath "bin/"
|
$gstreamerBinInstallDir= Join-Path $gstreamerInstallDir -ChildPath "bin"
|
||||||
$gstreamerPluginInstallDir= Join-Path $gstreamerInstallDir -ChildPath "lib\gstreamer-1.0"
|
$gstreamerPluginInstallDir= Join-Path $gstreamerInstallDir -ChildPath "lib"
|
||||||
|
$gstreamerShareInstallDir= Join-Path $gstreamerInstallDir -ChildPath "share"
|
||||||
|
|
||||||
& "$heatToolPath" dir "$gstreamerBinInstallDir" -gg -sfrag -template:fragment -out gstreamer-1.0.wxs -cg "_gstreamer" -var var.gstreamerBinInstallDir -dr INSTALLFOLDER
|
& "$heatToolPath" dir "$gstreamerBinInstallDir" -gg -sfrag -template:fragment -out gstreamer-1.0.wxs -cg "_gstreamer" -var var.gstreamerBinInstallDir -dr INSTALLFOLDER
|
||||||
& "$heatToolPath" dir "$gstreamerPluginInstallDir" -gg -sfrag -template:fragment -out gstreamer-plugins-1.0.wxs -cg "_gstreamer_plugins" -var var.gstreamerPluginInstallDir -dr INSTALLFOLDER
|
& "$heatToolPath" dir "$gstreamerPluginInstallDir" -gg -sfrag -template:fragment -out gstreamer-plugins-1.0.wxs -cg "_gstreamer_plugins" -var var.gstreamerPluginInstallDir -dr INSTALLFOLDER
|
||||||
|
& "$heatToolPath" dir "$gstreamerShareInstallDir" -v -ke -gg -sfrag -template:fragment -out gstreamer-share-1.0.wxs -cg "_gstreamer_share" -var var.gstreamerShareInstallDir -dr INSTALLFOLDER
|
||||||
|
|
||||||
$files = "gps gstreamer-1.0 gstreamer-plugins-1.0"
|
$files = "gps gstreamer-1.0 gstreamer-plugins-1.0 gstreamer-share-1.0"
|
||||||
$wxs_files = @()
|
$wxs_files = @()
|
||||||
$obj_files = @()
|
$obj_files = @()
|
||||||
foreach ($f in $files.split(" ")){
|
foreach ($f in $files.split(" ")){
|
||||||
|
@ -52,7 +53,7 @@ try
|
||||||
# compiling wxs file into wixobj
|
# compiling wxs file into wixobj
|
||||||
$msiFileName = "GstPipelineStudio-$GPSVersion.msi"
|
$msiFileName = "GstPipelineStudio-$GPSVersion.msi"
|
||||||
foreach ($f in $wxs_files){
|
foreach ($f in $wxs_files){
|
||||||
& "$candleToolPath" "$f" -dPlatform=x64 -dGPSUpgradeCode="$GPSUpgradeCode" -dGPSVersion="$GPSVersion" -dgstreamerBinInstallDir="$gstreamerBinInstallDir" -dgstreamerPluginInstallDir="$gstreamerPluginInstallDir"
|
& "$candleToolPath" "$f" -dPlatform=x64 -dGPSUpgradeCode="$GPSUpgradeCode" -dGPSVersion="$GPSVersion" -dgstreamerBinInstallDir="$gstreamerBinInstallDir" -dgstreamerPluginInstallDir="$gstreamerPluginInstallDir" -dgstreamerShareInstallDir="$gstreamerShareInstallDir"
|
||||||
if($LASTEXITCODE -ne 0)
|
if($LASTEXITCODE -ne 0)
|
||||||
{
|
{
|
||||||
throw "Compilation of $wxsFileName failed with exit code $LASTEXITCODE"
|
throw "Compilation of $wxsFileName failed with exit code $LASTEXITCODE"
|
||||||
|
|
|
@ -3,6 +3,6 @@ set MYDIR=%~dp0
|
||||||
setlocal
|
setlocal
|
||||||
set PATH=%MYDIR%bin;%PATH%
|
set PATH=%MYDIR%bin;%PATH%
|
||||||
echo %PATH%
|
echo %PATH%
|
||||||
set GST_PLUGIN_PATH=%MYDIR%/gstreamer-1.0
|
set GST_PLUGIN_PATH=%MYDIR%\lib\gstreamer-1.0
|
||||||
echo %GST_PLUGIN_PATH%
|
echo %GST_PLUGIN_PATH%
|
||||||
gst_pipeline_studio.exe
|
gst-pipeline-studio.exe
|
|
@ -10,10 +10,12 @@
|
||||||
<Package InstallScope="perMachine" Compressed="yes" />
|
<Package InstallScope="perMachine" Compressed="yes" />
|
||||||
|
|
||||||
<MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed. Setup will now exit." />
|
<MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed. Setup will now exit." />
|
||||||
|
<WixVariable Id="WixUIBannerBmp" Value="wixbanner.bmp" />
|
||||||
|
<WixVariable Id="WixUIDialogBmp" Value="wixdialog.bmp" />
|
||||||
|
|
||||||
<MediaTemplate EmbedCab="yes" />
|
<MediaTemplate EmbedCab="yes" />
|
||||||
|
|
||||||
<UIRef Id="WixUI_Mondo" />
|
<UIRef Id="WixUI_InstallDir" />
|
||||||
<WixVariable Id="WixUILicenseRtf" Value="LICENSE.rtf" />
|
<WixVariable Id="WixUILicenseRtf" Value="LICENSE.rtf" />
|
||||||
|
|
||||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||||
|
@ -59,6 +61,7 @@
|
||||||
<ComponentRef Id="ProductComponent" />
|
<ComponentRef Id="ProductComponent" />
|
||||||
<ComponentGroupRef Id="_gstreamer" />
|
<ComponentGroupRef Id="_gstreamer" />
|
||||||
<ComponentGroupRef Id="_gstreamer_plugins" />
|
<ComponentGroupRef Id="_gstreamer_plugins" />
|
||||||
|
<ComponentGroupRef Id="_gstreamer_share" />
|
||||||
<ComponentRef Id="UninstallShortcut" />
|
<ComponentRef Id="UninstallShortcut" />
|
||||||
</Feature>
|
</Feature>
|
||||||
|
|
||||||
|
|
|
@ -9,3 +9,4 @@ Copy-Item -Path C:\gst-install\bin\*.exe -Destination c:\gst-install-clean\bin\
|
||||||
New-Item c:\gst-install-clean\lib\gstreamer-1.0 -ItemType Directory
|
New-Item c:\gst-install-clean\lib\gstreamer-1.0 -ItemType Directory
|
||||||
Copy-Item -Path C:\gst-install\lib\gstreamer-1.0\*.dll -Destination c:\gst-install-clean\lib\gstreamer-1.0
|
Copy-Item -Path C:\gst-install\lib\gstreamer-1.0\*.dll -Destination c:\gst-install-clean\lib\gstreamer-1.0
|
||||||
|
|
||||||
|
Copy-Item -Path C:\gst-install\share -Destination c:\gst-install-clean\ -Recurse
|
||||||
|
|
BIN
installer/wix/wixbanner.bmp
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
installer/wix/wixdialog.bmp
Normal file
After Width: | Height: | Size: 507 KiB |
24
meson.build
|
@ -1,23 +1,25 @@
|
||||||
project('gst_pipeline_studio',
|
project('gst-pipeline-studio',
|
||||||
version: '0.3.0',
|
version: '0.3.5',
|
||||||
meson_version: '>= 0.50.0',
|
meson_version: '>= 0.63.0',
|
||||||
default_options: [ 'warning_level=2',
|
default_options: [ 'warning_level=2',
|
||||||
],
|
],
|
||||||
license: 'GPL-3.0-or-later',
|
license: 'GPL-3.0-or-later',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
host_system = host_machine.system()
|
||||||
|
|
||||||
python_mod = import('python')
|
python_mod = import('python')
|
||||||
python3 = python_mod.find_installation()
|
python3 = python_mod.find_installation()
|
||||||
current_date = run_command(python3, '-c', 'import datetime; print(datetime.datetime.now().strftime("%Y-%m-%d"), end ="")').stdout()
|
current_date = run_command(python3, '-c', 'import datetime; print(datetime.datetime.now().strftime("%Y-%m-%d"), end ="")').stdout()
|
||||||
i18n = import('i18n')
|
i18n = import('i18n')
|
||||||
host_system = host_machine.system()
|
host_system = host_machine.system()
|
||||||
|
|
||||||
|
dependency('gstreamer-1.0', version: '>= 1.20', fallback: ['gstreamer-1.0'])
|
||||||
|
dependency('gstreamer-base-1.0', version: '>= 1.20', fallback: ['gstreamer-1.0'])
|
||||||
|
dependency('gstreamer-video-1.0', version: '>= 1.20', fallback: ['gstreamer-1.0'])
|
||||||
dependency('glib-2.0', version: '>= 2.66')
|
dependency('glib-2.0', version: '>= 2.66')
|
||||||
dependency('gio-2.0', version: '>= 2.66')
|
dependency('gio-2.0', version: '>= 2.66')
|
||||||
dependency('gtk4', version: '>= 4.0.0')
|
dependency('gtk4', version: '>= 4.0.0', fallback: ['gtk'])
|
||||||
dependency('gstreamer-1.0', version: '>= 1.18')
|
|
||||||
dependency('gstreamer-base-1.0', version: '>= 1.18')
|
|
||||||
dependency('gstreamer-plugins-base-1.0', version: '>= 1.18')
|
|
||||||
dependency('gstreamer-plugins-bad-1.0', version: '>= 1.18')
|
|
||||||
|
|
||||||
find_program('cargo', required: true)
|
find_program('cargo', required: true)
|
||||||
find_program('glib-compile-resources', required: true)
|
find_program('glib-compile-resources', required: true)
|
||||||
|
@ -34,7 +36,7 @@ localedir = prefix / get_option('localedir')
|
||||||
datadir = prefix / get_option('datadir')
|
datadir = prefix / get_option('datadir')
|
||||||
pkgdatadir = datadir / meson.project_name()
|
pkgdatadir = datadir / meson.project_name()
|
||||||
iconsdir = datadir / 'icons'
|
iconsdir = datadir / 'icons'
|
||||||
podir = meson.source_root () / 'po'
|
podir = meson.project_source_root () / 'po'
|
||||||
gettext_package = meson.project_name()
|
gettext_package = meson.project_name()
|
||||||
base_id = 'org.freedesktop.dabrain34.GstPipelineStudio'
|
base_id = 'org.freedesktop.dabrain34.GstPipelineStudio'
|
||||||
|
|
||||||
|
@ -68,8 +70,8 @@ subdir('po')
|
||||||
|
|
||||||
meson.add_dist_script(
|
meson.add_dist_script(
|
||||||
'build-aux/dist-vendor.sh',
|
'build-aux/dist-vendor.sh',
|
||||||
meson.source_root(),
|
meson.project_source_root(),
|
||||||
join_paths(meson.build_root(), 'meson-dist', meson.project_name() + '-' + version)
|
join_paths(meson.project_build_root(), 'meson-dist', meson.project_name() + '-' + version)
|
||||||
)
|
)
|
||||||
if host_system == 'linux'
|
if host_system == 'linux'
|
||||||
meson.add_install_script('build-aux/meson/postinstall.py')
|
meson.add_install_script('build-aux/meson/postinstall.py')
|
||||||
|
|
17
release.md
|
@ -3,13 +3,22 @@
|
||||||
- Update to the given version:
|
- Update to the given version:
|
||||||
- meson.build
|
- meson.build
|
||||||
- cargo.toml
|
- cargo.toml
|
||||||
|
- VERSION
|
||||||
|
- index.html
|
||||||
|
- And rebuild to regenerate the cargo.lock
|
||||||
|
- update the changelog in org.freedesktop.dabrain34.GstPipelineStudio.appdata.xml.in.in within release/description
|
||||||
|
|
||||||
- create a tag on gitlab
|
- create a tag on gitlab
|
||||||
- meson builddir -Dbuildtype=release
|
- Fetch the package from the `linux release` job or you can make it manually with:
|
||||||
- ninja -C buiddir dist
|
- meson builddir -Dbuildtype=release
|
||||||
- upload the package and the sha256 to gitlab for Flatpak in the release notes
|
- ninja -C builddir dist
|
||||||
|
- Upload the package .xz file and the sha256 to gitlab release page in the release notes
|
||||||
|
see https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/-/releases/0.3.2
|
||||||
|
|
||||||
# flathub
|
# flathub
|
||||||
|
|
||||||
https://github.com/flathub/org.freedesktop.dabrain34.GstPipelineStudio
|
https://github.com/flathub/org.freedesktop.dabrain34.GstPipelineStudio
|
||||||
|
|
||||||
- Need to update the package and the sha256 generated by ninja -C builddir dist
|
- Need to update the package and the sha256 from the release page, ie https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/-/releases/0.3.2
|
||||||
|
- Create a pull request with the package update
|
||||||
|
- Wait at lest 2-3 hours after merging to get the update available.
|
||||||
|
|
504
src/app.rs
|
@ -7,17 +7,16 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use glib::SignalHandlerId;
|
use glib::SignalHandlerId;
|
||||||
use glib::Value;
|
|
||||||
use gtk::gdk;
|
use gtk::gdk;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{gio, gio::SimpleAction, glib, graphene};
|
use gtk::{gio, gio::SimpleAction, glib, graphene};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
Application, ApplicationWindow, Builder, Button, FileChooserAction, FileChooserDialog, Paned,
|
Application, ApplicationWindow, Builder, Button, FileChooserAction, FileChooserDialog, Paned,
|
||||||
PopoverMenu, ResponseType, Statusbar, Viewport, Widget,
|
PopoverMenu, ResponseType, Statusbar, Widget,
|
||||||
};
|
};
|
||||||
use log::error;
|
use log::error;
|
||||||
use once_cell::unsync::OnceCell;
|
use once_cell::unsync::OnceCell;
|
||||||
use std::cell::RefCell;
|
use std::cell::{Cell, RefCell};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
@ -25,11 +24,12 @@ use std::ops;
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
use crate::gps as GPS;
|
use crate::gps as GPS;
|
||||||
|
use crate::graphbook;
|
||||||
use crate::logger;
|
use crate::logger;
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
use crate::ui as GPSUI;
|
use crate::ui as GPSUI;
|
||||||
|
|
||||||
use crate::{GPS_DEBUG, GPS_ERROR, GPS_INFO, GPS_TRACE, GPS_WARN};
|
use crate::{GPS_DEBUG, GPS_ERROR, GPS_TRACE, GPS_WARN};
|
||||||
|
|
||||||
use crate::graphmanager as GM;
|
use crate::graphmanager as GM;
|
||||||
use crate::graphmanager::PropertyExt;
|
use crate::graphmanager::PropertyExt;
|
||||||
|
@ -37,9 +37,9 @@ use std::fmt;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GPSAppInner {
|
pub struct GPSAppInner {
|
||||||
pub window: gtk::ApplicationWindow,
|
pub window: gtk::ApplicationWindow,
|
||||||
pub graphview: RefCell<GM::GraphView>,
|
pub current_graphtab: Cell<u32>,
|
||||||
|
pub graphbook: RefCell<HashMap<u32, graphbook::GraphTab>>,
|
||||||
pub builder: Builder,
|
pub builder: Builder,
|
||||||
pub player: RefCell<GPS::Player>,
|
|
||||||
pub plugin_list_initialized: OnceCell<bool>,
|
pub plugin_list_initialized: OnceCell<bool>,
|
||||||
pub signal_handlers: RefCell<HashMap<String, SignalHandlerId>>,
|
pub signal_handlers: RefCell<HashMap<String, SignalHandlerId>>,
|
||||||
}
|
}
|
||||||
|
@ -95,27 +95,24 @@ impl GPSApp {
|
||||||
window.set_application(Some(application));
|
window.set_application(Some(application));
|
||||||
window.set_title(Some("GStreamer Pipeline Studio"));
|
window.set_title(Some("GStreamer Pipeline Studio"));
|
||||||
|
|
||||||
let player = GPS::Player::new().expect("Unable to initialize GStreamer subsystem");
|
|
||||||
let app = GPSApp(Rc::new(GPSAppInner {
|
let app = GPSApp(Rc::new(GPSAppInner {
|
||||||
window,
|
window,
|
||||||
graphview: RefCell::new(GM::GraphView::new()),
|
current_graphtab: Cell::new(0),
|
||||||
|
graphbook: RefCell::new(HashMap::new()),
|
||||||
builder,
|
builder,
|
||||||
player: RefCell::new(player),
|
|
||||||
plugin_list_initialized: OnceCell::new(),
|
plugin_list_initialized: OnceCell::new(),
|
||||||
signal_handlers: RefCell::new(HashMap::new()),
|
signal_handlers: RefCell::new(HashMap::new()),
|
||||||
}));
|
}));
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.player.borrow().set_app(app_weak);
|
|
||||||
app.graphview.borrow_mut().set_id(0);
|
|
||||||
|
|
||||||
let settings = Settings::load_settings();
|
let settings = Settings::load_settings();
|
||||||
|
|
||||||
|
app.window
|
||||||
|
.set_default_size(settings.app_width, settings.app_height);
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
if settings.app_maximized {
|
if settings.app_maximized {
|
||||||
app.window.maximize();
|
app.window.maximize();
|
||||||
} else {
|
|
||||||
app.window
|
|
||||||
.set_size_request(settings.app_width, settings.app_height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.set_paned_position(&settings, "graph_dashboard-paned", 100);
|
app.set_paned_position(&settings, "graph_dashboard-paned", 100);
|
||||||
app.set_paned_position(&settings, "graph_logs-paned", 100);
|
app.set_paned_position(&settings, "graph_logs-paned", 100);
|
||||||
app.set_paned_position(&settings, "elements_preview-paned", 100);
|
app.set_paned_position(&settings, "elements_preview-paned", 100);
|
||||||
|
@ -153,7 +150,7 @@ impl GPSApp {
|
||||||
.insert(paned_name.to_string(), paned.position());
|
.insert(paned_name.to_string(), paned.position());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_startup(application: >k::Application) {
|
pub fn on_startup(application: >k::Application, pipeline_desc: &String) {
|
||||||
// Create application and error out if that fails for whatever reason
|
// Create application and error out if that fails for whatever reason
|
||||||
let app = match GPSApp::new(application) {
|
let app = match GPSApp::new(application) {
|
||||||
Ok(app) => app,
|
Ok(app) => app,
|
||||||
|
@ -163,13 +160,7 @@ impl GPSApp {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// When the application is activated show the UI. This happens when the first process is
|
app.build_ui(application, pipeline_desc);
|
||||||
// started, and in the first process whenever a second process is started
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
application.connect_activate(glib::clone!(@weak application => move |_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
app.build_ui(&application);
|
|
||||||
}));
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
let app_weak = app.downgrade();
|
||||||
let slider: gtk::Scale = app
|
let slider: gtk::Scale = app
|
||||||
|
@ -178,18 +169,20 @@ impl GPSApp {
|
||||||
.expect("Couldn't get status_bar");
|
.expect("Couldn't get status_bar");
|
||||||
let slider_update_signal_id = slider.connect_value_changed(move |slider| {
|
let slider_update_signal_id = slider.connect_value_changed(move |slider| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
let player = app.player.borrow();
|
|
||||||
let value = slider.value() as u64;
|
let value = slider.value() as u64;
|
||||||
GPS_TRACE!("Seeking to {} s", value);
|
GPS_TRACE!("Seeking to {} s", value);
|
||||||
if player.set_position(value).is_err() {
|
if graphbook::current_graphtab(&app)
|
||||||
|
.player()
|
||||||
|
.set_position(value)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
GPS_ERROR!("Seeking to {} failed", value);
|
GPS_ERROR!("Seeking to {} failed", value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let app_weak = app.downgrade();
|
let app_weak = app.downgrade();
|
||||||
let timeout_id =
|
let timeout_id =
|
||||||
glib::timeout_add_local(std::time::Duration::from_millis(500), move || {
|
glib::timeout_add_local(std::time::Duration::from_millis(500), move || {
|
||||||
let app = upgrade_weak!(app_weak, glib::Continue(false));
|
let app = upgrade_weak!(app_weak, glib::ControlFlow::Break);
|
||||||
let player = app.player.borrow();
|
|
||||||
|
|
||||||
let label: gtk::Label = app
|
let label: gtk::Label = app
|
||||||
.builder
|
.builder
|
||||||
|
@ -199,19 +192,21 @@ impl GPSApp {
|
||||||
.builder
|
.builder
|
||||||
.object("scale-position")
|
.object("scale-position")
|
||||||
.expect("Couldn't get status_bar");
|
.expect("Couldn't get status_bar");
|
||||||
let position = player.position();
|
let position = graphbook::current_graphtab(&app).player().position();
|
||||||
let duration = player.duration();
|
let duration = graphbook::current_graphtab(&app).player().duration();
|
||||||
slider.set_range(0.0, duration as f64 / 1000_f64);
|
slider.set_range(0.0, duration as f64 / 1000_f64);
|
||||||
slider.block_signal(&slider_update_signal_id);
|
slider.block_signal(&slider_update_signal_id);
|
||||||
slider.set_value(position as f64 / 1000_f64);
|
slider.set_value(position as f64 / 1000_f64);
|
||||||
slider.unblock_signal(&slider_update_signal_id);
|
slider.unblock_signal(&slider_update_signal_id);
|
||||||
|
|
||||||
// Query the current playing position from the underlying player.
|
// Query the current playing position from the underlying player.
|
||||||
let position_desc = player.position_description();
|
let position_desc = graphbook::current_graphtab(&app)
|
||||||
|
.player()
|
||||||
|
.position_description();
|
||||||
// Display the playing position in the gui.
|
// Display the playing position in the gui.
|
||||||
label.set_text(&position_desc);
|
label.set_text(&position_desc);
|
||||||
// Tell the callback to continue calling this closure.
|
// Tell the callback to continue calling this closure.
|
||||||
glib::Continue(true)
|
glib::ControlFlow::Continue
|
||||||
});
|
});
|
||||||
|
|
||||||
let timeout_id = RefCell::new(Some(timeout_id));
|
let timeout_id = RefCell::new(Some(timeout_id));
|
||||||
|
@ -228,8 +223,8 @@ impl GPSApp {
|
||||||
.expect("Couldn't get the main window");
|
.expect("Couldn't get the main window");
|
||||||
let mut settings = Settings::load_settings();
|
let mut settings = Settings::load_settings();
|
||||||
settings.app_maximized = window.is_maximized();
|
settings.app_maximized = window.is_maximized();
|
||||||
settings.app_width = window.width();
|
settings.app_width = window.default_width();
|
||||||
settings.app_height = window.height();
|
settings.app_height = window.default_height();
|
||||||
app.save_paned_position(&mut settings, "graph_dashboard-paned");
|
app.save_paned_position(&mut settings, "graph_dashboard-paned");
|
||||||
app.save_paned_position(&mut settings, "graph_logs-paned");
|
app.save_paned_position(&mut settings, "graph_logs-paned");
|
||||||
app.save_paned_position(&mut settings, "elements_preview-paned");
|
app.save_paned_position(&mut settings, "elements_preview-paned");
|
||||||
|
@ -262,7 +257,9 @@ impl GPSApp {
|
||||||
application.set_accels_for_action("app.open_pipeline", &["<primary>p"]);
|
application.set_accels_for_action("app.open_pipeline", &["<primary>p"]);
|
||||||
|
|
||||||
application.add_action(&gio::SimpleAction::new("save_as", None));
|
application.add_action(&gio::SimpleAction::new("save_as", None));
|
||||||
|
application.add_action(&gio::SimpleAction::new("save", None));
|
||||||
application.set_accels_for_action("app.save", &["<primary>s"]);
|
application.set_accels_for_action("app.save", &["<primary>s"]);
|
||||||
|
application.add_action(&gio::SimpleAction::new("save_as", None));
|
||||||
|
|
||||||
application.add_action(&gio::SimpleAction::new("delete", None));
|
application.add_action(&gio::SimpleAction::new("delete", None));
|
||||||
application.set_accels_for_action("app.delete", &["<primary>d", "Delete"]);
|
application.set_accels_for_action("app.delete", &["<primary>d", "Delete"]);
|
||||||
|
@ -278,6 +275,7 @@ impl GPSApp {
|
||||||
application.add_action(&gio::SimpleAction::new("logger.clear", None));
|
application.add_action(&gio::SimpleAction::new("logger.clear", None));
|
||||||
|
|
||||||
application.add_action(&gio::SimpleAction::new("graph.check", None));
|
application.add_action(&gio::SimpleAction::new("graph.check", None));
|
||||||
|
application.add_action(&gio::SimpleAction::new("graph.clear", None));
|
||||||
application.add_action(&gio::SimpleAction::new("graph.pipeline_details", None));
|
application.add_action(&gio::SimpleAction::new("graph.pipeline_details", None));
|
||||||
|
|
||||||
application.add_action(&gio::SimpleAction::new("port.delete", None));
|
application.add_action(&gio::SimpleAction::new("port.delete", None));
|
||||||
|
@ -331,7 +329,7 @@ impl GPSApp {
|
||||||
.expect("Unable to dynamic cast to SimpleAction")
|
.expect("Unable to dynamic cast to SimpleAction")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn disconnect_app_menu_action(&self, action_name: &str) {
|
pub fn disconnect_app_menu_action(&self, action_name: &str) {
|
||||||
let action = self.app_menu_action(action_name);
|
let action = self.app_menu_action(action_name);
|
||||||
|
|
||||||
if let Some(signal_handler_id) = self.signal_handlers.borrow_mut().remove(action_name) {
|
if let Some(signal_handler_id) = self.signal_handlers.borrow_mut().remove(action_name) {
|
||||||
|
@ -420,7 +418,12 @@ impl GPSApp {
|
||||||
.builder
|
.builder
|
||||||
.object("notebook-preview")
|
.object("notebook-preview")
|
||||||
.expect("Couldn't get box_preview");
|
.expect("Couldn't get box_preview");
|
||||||
if n_sink == 0 {
|
let mut n_video_sink = n_sink;
|
||||||
|
for tab in self.graphbook.borrow().values() {
|
||||||
|
n_video_sink += tab.player().n_video_sink();
|
||||||
|
}
|
||||||
|
|
||||||
|
if n_video_sink == 0 {
|
||||||
loop {
|
loop {
|
||||||
let i = notebook_preview.n_pages();
|
let i = notebook_preview.n_pages();
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
|
@ -435,32 +438,35 @@ impl GPSApp {
|
||||||
.valign(gtk::Align::Center)
|
.valign(gtk::Align::Center)
|
||||||
.build();
|
.build();
|
||||||
box_preview.append(&picture);
|
box_preview.append(&picture);
|
||||||
let label = gtk::Label::new(Some(&format!("Preview{n_sink}")));
|
let label = gtk::Label::new(Some(&format!("Preview{n_video_sink}")));
|
||||||
notebook_preview.insert_page(&box_preview, Some(&label), None);
|
notebook_preview.insert_page(&box_preview, Some(&label), None);
|
||||||
|
notebook_preview.set_current_page(Some(n_video_sink as u32));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_ui(&self, application: &Application) {
|
pub fn build_ui(&self, application: &Application, pipeline_desc: &String) {
|
||||||
let drawing_area_window: Viewport = self
|
graphbook::setup_graphbook(self);
|
||||||
.builder
|
graphbook::create_graphtab(self, 0, None);
|
||||||
.object("drawing_area")
|
let (ready_tx, ready_rx) = async_channel::unbounded::<(logger::LogType, String)>();
|
||||||
.expect("Couldn't get drawing_area");
|
|
||||||
|
|
||||||
drawing_area_window.set_child(Some(&*self.graphview.borrow()));
|
|
||||||
|
|
||||||
// Setup the logger to get messages into the TreeView
|
// Setup the logger to get messages into the TreeView
|
||||||
let (ready_tx, ready_rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
logger::init_logger(
|
logger::init_logger(
|
||||||
ready_tx,
|
ready_tx.clone(),
|
||||||
Settings::default_log_file_path()
|
Settings::log_file_path()
|
||||||
.to_str()
|
.to_str()
|
||||||
.expect("Unable to convert log file path to a string"),
|
.expect("Unable to convert log file path to a string"),
|
||||||
);
|
);
|
||||||
GPSUI::logger::setup_logger_list(self);
|
logger::init_msg_logger(ready_tx);
|
||||||
let _ = ready_rx.attach(None, move |msg: String| {
|
GPSUI::logger::setup_logger_list(self, "treeview-app-logger", logger::LogType::App);
|
||||||
let app = upgrade_weak!(app_weak, glib::Continue(false));
|
GPSUI::logger::setup_logger_list(self, "treeview-msg-logger", logger::LogType::Message);
|
||||||
GPSUI::logger::add_to_logger_list(&app, &msg);
|
GPSUI::logger::setup_logger_list(self, "treeview-gst-logger", logger::LogType::Gst);
|
||||||
glib::Continue(true)
|
let app_weak = self.downgrade();
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
while let Ok(msg) = ready_rx.recv().await {
|
||||||
|
let app = upgrade_weak!(app_weak, glib::ControlFlow::Break);
|
||||||
|
GPSUI::logger::add_to_logger_list(&app, msg.0, &msg.1);
|
||||||
|
}
|
||||||
|
glib::ControlFlow::Continue
|
||||||
});
|
});
|
||||||
|
|
||||||
let window = &self.window;
|
let window = &self.window;
|
||||||
|
@ -478,15 +484,20 @@ impl GPSApp {
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_app_menu_action("new-window", move |_, _| {
|
self.connect_app_menu_action("new-window", move |_, _| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
app.clear_graph();
|
let id = graphbook::graphbook_get_new_graphtab_id(&app);
|
||||||
GPS_ERROR!("clear graph");
|
graphbook::create_graphtab(&app, id, None);
|
||||||
|
let graphbook: gtk::Notebook = app
|
||||||
|
.builder
|
||||||
|
.object("graphbook")
|
||||||
|
.expect("Couldn't get graphbook");
|
||||||
|
graphbook.set_current_page(Some(id));
|
||||||
});
|
});
|
||||||
|
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_app_menu_action("open", move |_, _| {
|
self.connect_app_menu_action("open", move |_, _| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
GPSApp::get_file_from_dialog(&app, false, move |app, filename| {
|
GPSApp::get_file_from_dialog(&app, false, move |app, filename| {
|
||||||
app.load_graph(&filename)
|
app.load_graph(&filename, false)
|
||||||
.unwrap_or_else(|_| GPS_ERROR!("Unable to open file {}", filename));
|
.unwrap_or_else(|_| GPS_ERROR!("Unable to open file {}", filename));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -500,12 +511,31 @@ impl GPSApp {
|
||||||
&Settings::recent_pipeline_description(),
|
&Settings::recent_pipeline_description(),
|
||||||
&app,
|
&app,
|
||||||
move |app, pipeline_desc| {
|
move |app, pipeline_desc| {
|
||||||
app.load_pipeline(&pipeline_desc)
|
app.load_pipeline(&pipeline_desc).unwrap_or_else(|_| {
|
||||||
.unwrap_or_else(|_| GPS_ERROR!("Unable to open file {}", pipeline_desc));
|
GPS_ERROR!("Unable to open pipeline description {}", pipeline_desc)
|
||||||
|
});
|
||||||
Settings::set_recent_pipeline_description(&pipeline_desc);
|
Settings::set_recent_pipeline_description(&pipeline_desc);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
let app_weak = self.downgrade();
|
||||||
|
self.connect_app_menu_action("save", move |_, _| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
let gt = graphbook::current_graphtab(&app);
|
||||||
|
if gt.undefined() {
|
||||||
|
GPSApp::get_file_from_dialog(&app, true, move |app, filename| {
|
||||||
|
GPS_DEBUG!("Save file {}", filename);
|
||||||
|
app.save_graph(&filename)
|
||||||
|
.unwrap_or_else(|_| GPS_ERROR!("Unable to save file to {}", filename));
|
||||||
|
graphbook::current_graphtab_set_filename(&app, filename.as_str());
|
||||||
|
});
|
||||||
|
} else if gt.modified() {
|
||||||
|
let filename = gt.filename();
|
||||||
|
app.save_graph(&filename)
|
||||||
|
.unwrap_or_else(|_| GPS_ERROR!("Unable to save file to {}", filename));
|
||||||
|
graphbook::current_graphtab_set_filename(&app, filename.as_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_app_menu_action("save_as", move |_, _| {
|
self.connect_app_menu_action("save_as", move |_, _| {
|
||||||
|
@ -514,6 +544,7 @@ impl GPSApp {
|
||||||
GPS_DEBUG!("Save file {}", filename);
|
GPS_DEBUG!("Save file {}", filename);
|
||||||
app.save_graph(&filename)
|
app.save_graph(&filename)
|
||||||
.unwrap_or_else(|_| GPS_ERROR!("Unable to save file to {}", filename));
|
.unwrap_or_else(|_| GPS_ERROR!("Unable to save file to {}", filename));
|
||||||
|
graphbook::current_graphtab_set_filename(&app, filename.as_str());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -526,8 +557,9 @@ impl GPSApp {
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_app_menu_action("delete", move |_, _| {
|
self.connect_app_menu_action("delete", move |_, _| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
let graph_view = app.graphview.borrow();
|
graphbook::current_graphtab(&app)
|
||||||
graph_view.delete_selected();
|
.graphview()
|
||||||
|
.delete_selected();
|
||||||
});
|
});
|
||||||
|
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
|
@ -539,28 +571,28 @@ impl GPSApp {
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_button_action("button-play", move |_| {
|
self.connect_button_action("button-play", move |_| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
let graph_view = app.graphview.borrow();
|
let _ = graphbook::current_graphtab(&app).player().start_pipeline(
|
||||||
let _ = app
|
&graphbook::current_graphtab(&app).graphview(),
|
||||||
.player
|
GPS::PipelineState::Playing,
|
||||||
.borrow()
|
);
|
||||||
.start_pipeline(&graph_view, GPS::PipelineState::Playing);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_button_action("button-pause", move |_| {
|
self.connect_button_action("button-pause", move |_| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
let graph_view = app.graphview.borrow();
|
let _ = graphbook::current_graphtab(&app).player().start_pipeline(
|
||||||
let _ = app
|
&graphbook::current_graphtab(&app).graphview(),
|
||||||
.player
|
GPS::PipelineState::Paused,
|
||||||
.borrow()
|
);
|
||||||
.start_pipeline(&graph_view, GPS::PipelineState::Paused);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_button_action("button-stop", move |_| {
|
self.connect_button_action("button-stop", move |_| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
let player = app.player.borrow();
|
|
||||||
let _ = player.set_state(GPS::PipelineState::Stopped);
|
let _ = graphbook::current_graphtab(&app)
|
||||||
|
.player()
|
||||||
|
.set_state(GPS::PipelineState::Stopped);
|
||||||
});
|
});
|
||||||
|
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
|
@ -568,271 +600,27 @@ impl GPSApp {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
app.clear_graph();
|
app.clear_graph();
|
||||||
});
|
});
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.graphview.borrow().connect_local(
|
|
||||||
"node-added",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
let graph_id = values[1].get::<u32>().expect("graph id in args[1]");
|
|
||||||
let node_id = values[2].get::<u32>().expect("node id in args[2]");
|
|
||||||
GPS_INFO!("Node added node id={} in graph id={}", node_id, graph_id);
|
|
||||||
if let Some(node) = app.graphview.borrow().node(node_id) {
|
|
||||||
let description = GPS::ElementInfo::element_description(&node.name()).ok();
|
|
||||||
node.set_tooltip_markup(description.as_deref());
|
|
||||||
for port in node.all_ports(GM::PortDirection::All) {
|
|
||||||
let caps = PropertyExt::property(&port,"_caps");
|
|
||||||
GPS_TRACE!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id());
|
|
||||||
port.set_tooltip_markup(caps.as_deref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.graphview.borrow().connect_local(
|
|
||||||
"port-added",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
let graph_id = values[1].get::<u32>().expect("graph id in args[1]");
|
|
||||||
let node_id = values[2].get::<u32>().expect("node id in args[2]");
|
|
||||||
let port_id = values[3].get::<u32>().expect("port id in args[3]");
|
|
||||||
GPS_INFO!("Port added port id={} to node id={} in graph id={}", port_id, node_id, graph_id);
|
|
||||||
if let Some(node) = app.graphview.borrow().node(node_id) {
|
|
||||||
if let Some(port) = node.port(port_id) {
|
|
||||||
let caps = PropertyExt::property(&port, "_caps");
|
|
||||||
GPS_TRACE!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id());
|
|
||||||
port.set_tooltip_markup(caps.as_deref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.graphview.borrow().connect_local(
|
|
||||||
"graph-updated",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
let id = values[1].get::<u32>().expect("id in args[1]");
|
|
||||||
GPS_TRACE!("Graph updated id={}", id);
|
|
||||||
let _ = app
|
|
||||||
.save_graph(
|
|
||||||
Settings::default_graph_file_path()
|
|
||||||
.to_str()
|
|
||||||
.expect("Unable to convert to string"),
|
|
||||||
)
|
|
||||||
.map_err(|e| GPS_WARN!("Unable to save file {}", e));
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
// When user clicks on port with right button
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.graphview
|
|
||||||
.borrow()
|
|
||||||
.connect_local(
|
|
||||||
"graph-right-clicked",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
let point = values[1].get::<graphene::Point>().expect("point in args[2]");
|
|
||||||
|
|
||||||
let pop_menu = app.app_pop_menu_at_position(&*app.graphview.borrow(), point.to_vec2().x() as f64, point.to_vec2().y() as f64);
|
|
||||||
let menu: gio::MenuModel = app
|
|
||||||
.builder
|
|
||||||
.object("graph_menu")
|
|
||||||
.expect("Couldn't graph_menu");
|
|
||||||
pop_menu.set_menu_model(Some(&menu));
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("graph.check",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
let pipeline_description = app.player.borrow().pipeline_description_from_graphview(&app.graphview.borrow());
|
|
||||||
if app.player.borrow().create_pipeline(&pipeline_description).is_ok() {
|
|
||||||
GPSUI::message::display_message_dialog(&pipeline_description,gtk::MessageType::Info, |_| {});
|
|
||||||
} else {
|
|
||||||
GPSUI::message::display_error_dialog(false, &format!("Unable to render:\n\n{pipeline_description}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("graph.pipeline_details",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPSUI::properties::display_pipeline_details(&app);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
pop_menu.show();
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// When user clicks on port with right button
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.graphview.borrow().connect_local(
|
|
||||||
"port-right-clicked",
|
|
||||||
false,
|
|
||||||
move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
|
|
||||||
let port_id = values[1].get::<u32>().expect("port id args[1]");
|
|
||||||
let node_id = values[2].get::<u32>().expect("node id args[2]");
|
|
||||||
let point = values[3]
|
|
||||||
.get::<graphene::Point>()
|
|
||||||
.expect("point in args[3]");
|
|
||||||
|
|
||||||
let pop_menu = app.app_pop_menu_at_position(
|
|
||||||
&*app.graphview.borrow(),
|
|
||||||
point.to_vec2().x() as f64,
|
|
||||||
point.to_vec2().y() as f64,
|
|
||||||
);
|
|
||||||
let menu: gio::MenuModel = app
|
|
||||||
.builder
|
|
||||||
.object("port_menu")
|
|
||||||
.expect("Couldn't get menu model for port");
|
|
||||||
pop_menu.set_menu_model(Some(&menu));
|
|
||||||
|
|
||||||
if app.graphview.borrow().can_remove_port(node_id, port_id) {
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("port.delete", move |_, _| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_TRACE!("port.delete-link port {} node {}", port_id, node_id);
|
|
||||||
app.graphview.borrow().remove_port(node_id, port_id);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
app.disconnect_app_menu_action("port.delete");
|
|
||||||
}
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("port.properties", move |_, _| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_TRACE!("port.properties port {} node {}", port_id, node_id);
|
|
||||||
let node = app.node(node_id);
|
|
||||||
let port = app.port(node_id, port_id);
|
|
||||||
GPSUI::properties::display_pad_properties(
|
|
||||||
&app,
|
|
||||||
&node.name(),
|
|
||||||
&port.name(),
|
|
||||||
node_id,
|
|
||||||
port_id,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
pop_menu.show();
|
|
||||||
None
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// When user clicks on node with right button
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.graphview
|
|
||||||
.borrow()
|
|
||||||
.connect_local(
|
|
||||||
"node-right-clicked",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
|
|
||||||
let node_id = values[1].get::<u32>().expect("node id args[1]");
|
|
||||||
let point = values[2].get::<graphene::Point>().expect("point in args[2]");
|
|
||||||
|
|
||||||
let pop_menu = app.app_pop_menu_at_position(&*app.graphview.borrow(), point.to_vec2().x() as f64, point.to_vec2().y() as f64);
|
|
||||||
let menu: gio::MenuModel = app
|
|
||||||
.builder
|
|
||||||
.object("node_menu")
|
|
||||||
.expect("Couldn't get menu model for node");
|
|
||||||
pop_menu.set_menu_model(Some(&menu));
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("node.add-to-favorite",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_DEBUG!("node.add-to-favorite {}", node_id);
|
|
||||||
if let Some(node) = app.graphview.borrow().node(node_id) {
|
|
||||||
GPSUI::elements::add_to_favorite_list(&app, node.name());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("node.delete",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_DEBUG!("node.delete {}", node_id);
|
|
||||||
app.graphview.borrow_mut().remove_node(node_id);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
let node = app.node(node_id);
|
|
||||||
if let Some(input) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Input) {
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("node.request-pad-input",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_DEBUG!("node.request-pad-input {}", node_id);
|
|
||||||
app.create_port_with_caps(node_id, GM::PortDirection::Input, GM::PortPresence::Sometimes, input.caps().to_string());
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
app.disconnect_app_menu_action("node.request-pad-input");
|
|
||||||
}
|
|
||||||
let node = app.node(node_id);
|
|
||||||
if let Some(output) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Output) {
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("node.request-pad-output",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_DEBUG!("node.request-pad-output {}", node_id);
|
|
||||||
app.create_port_with_caps(node_id, GM::PortDirection::Output, GM::PortPresence::Sometimes, output.caps().to_string());
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
app.disconnect_app_menu_action("node.request-pad-output");
|
|
||||||
}
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("node.properties",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_DEBUG!("node.properties {}", node_id);
|
|
||||||
let node = app.graphview.borrow().node(node_id).unwrap();
|
|
||||||
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
pop_menu.show();
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.graphview.borrow().connect_local(
|
|
||||||
"node-double-clicked",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
let node_id = values[1].get::<u32>().expect("node id args[1]");
|
|
||||||
GPS_TRACE!("Node double clicked id={}", node_id);
|
|
||||||
let node = app.graphview.borrow().node(node_id).unwrap();
|
|
||||||
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id);
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Setup the favorite list
|
// Setup the favorite list
|
||||||
GPSUI::elements::setup_favorite_list(self);
|
GPSUI::elements::setup_favorite_list(self);
|
||||||
// Setup the favorite list
|
// Setup the favorite list
|
||||||
GPSUI::elements::setup_elements_list(self);
|
GPSUI::elements::setup_elements_list(self);
|
||||||
|
if pipeline_desc.is_empty() {
|
||||||
let _ = self
|
let _ = self
|
||||||
.load_graph(
|
.load_graph(
|
||||||
Settings::default_graph_file_path()
|
Settings::graph_file_path()
|
||||||
.to_str()
|
.to_str()
|
||||||
.expect("Unable to convert to string"),
|
.expect("Unable to convert to string"),
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
.map_err(|_e| {
|
.map_err(|_e| {
|
||||||
GPS_WARN!("Unable to load default graph");
|
GPS_WARN!("Unable to load default graph");
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
self.load_pipeline(pipeline_desc).unwrap_or_else(|_| {
|
||||||
|
GPS_ERROR!("Unable to open pipeline description {}", pipeline_desc)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Downgrade to a weak reference
|
// Downgrade to a weak reference
|
||||||
|
@ -844,25 +632,22 @@ impl GPSApp {
|
||||||
fn drop(self) {}
|
fn drop(self) {}
|
||||||
|
|
||||||
pub fn add_new_element(&self, element_name: &str) {
|
pub fn add_new_element(&self, element_name: &str) {
|
||||||
let graphview = self.graphview.borrow();
|
|
||||||
let (inputs, outputs) = GPS::PadInfo::pads(element_name, false);
|
let (inputs, outputs) = GPS::PadInfo::pads(element_name, false);
|
||||||
let node =
|
let node = graphbook::current_graphtab(self)
|
||||||
graphview.create_node(element_name, GPS::ElementInfo::element_type(element_name));
|
.graphview()
|
||||||
|
.create_node(element_name, GPS::ElementInfo::element_type(element_name));
|
||||||
let node_id = node.id();
|
let node_id = node.id();
|
||||||
if GPS::ElementInfo::element_is_uri_src_handler(element_name) {
|
if GPS::ElementInfo::element_is_uri_src_handler(element_name) {
|
||||||
GPSApp::get_file_from_dialog(self, false, move |app, filename| {
|
GPSApp::get_file_from_dialog(self, false, move |app, filename| {
|
||||||
GPS_DEBUG!("Open file {}", filename);
|
GPS_DEBUG!("Open file {}", filename);
|
||||||
let graphview = app.graphview.borrow();
|
|
||||||
let mut properties: HashMap<String, String> = HashMap::new();
|
let mut properties: HashMap<String, String> = HashMap::new();
|
||||||
properties.insert(String::from("location"), filename);
|
properties.insert(String::from("location"), filename);
|
||||||
if let Some(node) = graphview.node(node_id) {
|
if let Some(node) = graphbook::current_graphtab(&app).graphview().node(node_id) {
|
||||||
node.update_properties(&properties);
|
node.update_properties(&properties);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if GPS::ElementInfo::element_is_capsfilter(element_name) {
|
|
||||||
node.set_light(true);
|
|
||||||
}
|
}
|
||||||
graphview.add_node(node);
|
graphbook::current_graphtab(self).graphview().add_node(node);
|
||||||
for input in inputs {
|
for input in inputs {
|
||||||
self.create_port_with_caps(
|
self.create_port_with_caps(
|
||||||
node_id,
|
node_id,
|
||||||
|
@ -881,16 +666,15 @@ impl GPSApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node(&self, node_id: u32) -> GM::Node {
|
pub fn node(&self, node_id: u32) -> GM::Node {
|
||||||
let node = self
|
let node = graphbook::current_graphtab(self)
|
||||||
.graphview
|
.graphview()
|
||||||
.borrow()
|
|
||||||
.node(node_id)
|
.node(node_id)
|
||||||
.unwrap_or_else(|| panic!("Unable to retrieve node with id {}", node_id));
|
.unwrap_or_else(|| panic!("Unable to retrieve node with id {}", node_id));
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
|
|
||||||
fn port(&self, node_id: u32, port_id: u32) -> GM::Port {
|
pub fn port(&self, node_id: u32, port_id: u32) -> GM::Port {
|
||||||
let node = self.node(node_id);
|
let node = self.node(node_id);
|
||||||
node.port(port_id)
|
node.port(port_id)
|
||||||
.unwrap_or_else(|| panic!("Unable to retrieve port with id {}", port_id))
|
.unwrap_or_else(|| panic!("Unable to retrieve port with id {}", port_id))
|
||||||
|
@ -941,14 +725,17 @@ impl GPSApp {
|
||||||
GM::PortDirection::Output => String::from("src_"),
|
GM::PortDirection::Output => String::from("src_"),
|
||||||
_ => String::from("?"),
|
_ => String::from("?"),
|
||||||
};
|
};
|
||||||
let graphview = self.graphview.borrow();
|
|
||||||
let port_name = format!("{}{}", port_name, ports.len());
|
let port_name = format!("{}{}", port_name, ports.len());
|
||||||
let port = graphview.create_port(&port_name, direction, presence);
|
let port = graphbook::current_graphtab(self)
|
||||||
|
.graphview()
|
||||||
|
.create_port(&port_name, direction, presence);
|
||||||
let id = port.id();
|
let id = port.id();
|
||||||
let properties: HashMap<String, String> = HashMap::from([("_caps".to_string(), caps)]);
|
let properties: HashMap<String, String> = HashMap::from([("_caps".to_string(), caps)]);
|
||||||
port.update_properties(&properties);
|
port.update_properties(&properties);
|
||||||
if let Some(mut node) = graphview.node(node_id) {
|
if let Some(mut node) = graphbook::current_graphtab(self).graphview().node(node_id) {
|
||||||
graphview.add_port_to_node(&mut node, port);
|
graphbook::current_graphtab(self)
|
||||||
|
.graphview()
|
||||||
|
.add_port_to_node(&mut node, port);
|
||||||
}
|
}
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
@ -959,42 +746,45 @@ impl GPSApp {
|
||||||
node_to_id: u32,
|
node_to_id: u32,
|
||||||
port_from_id: u32,
|
port_from_id: u32,
|
||||||
port_to_id: u32,
|
port_to_id: u32,
|
||||||
active: bool,
|
|
||||||
) {
|
) {
|
||||||
let graphview = self.graphview.borrow();
|
let graphtab = graphbook::current_graphtab(self);
|
||||||
let link =
|
let link =
|
||||||
graphview.create_link(node_from_id, node_to_id, port_from_id, port_to_id, active);
|
graphtab
|
||||||
graphview.add_link(link);
|
.graphview()
|
||||||
|
.create_link(node_from_id, node_to_id, port_from_id, port_to_id);
|
||||||
|
graphtab.graphview().add_link(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_graph(&self) {
|
fn clear_graph(&self) {
|
||||||
let graph_view = self.graphview.borrow();
|
graphbook::current_graphtab(self).graphview().clear();
|
||||||
graph_view.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_graph(&self, filename: &str) -> anyhow::Result<()> {
|
pub fn save_graph(&self, filename: &str) -> anyhow::Result<()> {
|
||||||
let graph_view = self.graphview.borrow();
|
|
||||||
let mut file = File::create(filename)?;
|
let mut file = File::create(filename)?;
|
||||||
let buffer = graph_view.render_xml()?;
|
let buffer = graphbook::current_graphtab(self).graphview().render_xml()?;
|
||||||
file.write_all(&buffer)?;
|
file.write_all(&buffer)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_graph(&self, filename: &str) -> anyhow::Result<()> {
|
fn load_graph(&self, filename: &str, untitled: bool) -> anyhow::Result<()> {
|
||||||
let graph_view = self.graphview.borrow();
|
|
||||||
GPS_INFO!("Open graph file {}", filename);
|
|
||||||
let mut file = File::open(filename)?;
|
let mut file = File::open(filename)?;
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
file.read_to_end(&mut buffer).expect("buffer overflow");
|
file.read_to_end(&mut buffer).expect("buffer overflow");
|
||||||
graph_view.load_from_xml(buffer)?;
|
let graphtab = graphbook::current_graphtab(self);
|
||||||
|
graphtab.graphview().load_from_xml(buffer)?;
|
||||||
|
if !untitled {
|
||||||
|
graphbook::current_graphtab_set_filename(self, filename);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_pipeline(&self, pipeline_desc: &str) -> anyhow::Result<()> {
|
fn load_pipeline(&self, pipeline_desc: &str) -> anyhow::Result<()> {
|
||||||
let player = self.player.borrow();
|
let graphtab = graphbook::current_graphtab(self);
|
||||||
let graphview = self.graphview.borrow();
|
let pd_parsed = pipeline_desc.replace('\\', "");
|
||||||
player.graphview_from_pipeline_description(&graphview, pipeline_desc);
|
graphtab
|
||||||
|
.player()
|
||||||
|
.graphview_from_pipeline_description(&graphtab.graphview(), &pd_parsed);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,10 @@ use gtk::glib;
|
||||||
pub fn init() -> Result<()> {
|
pub fn init() -> Result<()> {
|
||||||
std::env::set_var("GST_XINITTHREADS", "1");
|
std::env::set_var("GST_XINITTHREADS", "1");
|
||||||
gst::init()?;
|
gst::init()?;
|
||||||
|
#[cfg(feature = "gtk4-plugin")]
|
||||||
|
{
|
||||||
|
gstgtk4::plugin_register_static().expect("Failed to register gstgtk4 plugin");
|
||||||
|
}
|
||||||
gtk::init()?;
|
gtk::init()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
pub static APP_ID: &str = @APP_ID@;
|
pub static APP_ID: &str = @APP_ID@;
|
||||||
pub static VERSION: &str = @VERSION@;
|
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
|
@ -46,6 +46,19 @@ impl ElementInfo {
|
||||||
Ok(elements)
|
Ok(elements)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn element_factory_exists(element_name: &str) -> bool {
|
||||||
|
match ElementInfo::element_feature(element_name) {
|
||||||
|
Some(_feature) => {
|
||||||
|
GPS_DEBUG!("Found element factory name {}", element_name);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
GPS_ERROR!("Unable to find element factory name {}", element_name);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn element_feature(element_name: &str) -> Option<gst::PluginFeature> {
|
pub fn element_feature(element_name: &str) -> Option<gst::PluginFeature> {
|
||||||
let registry = gst::Registry::get();
|
let registry = gst::Registry::get();
|
||||||
gst::Registry::find_feature(®istry, element_name, gst::ElementFactory::static_type())
|
gst::Registry::find_feature(®istry, element_name, gst::ElementFactory::static_type())
|
||||||
|
@ -60,6 +73,14 @@ impl ElementInfo {
|
||||||
|
|
||||||
pub fn element_description(element_name: &str) -> anyhow::Result<String> {
|
pub fn element_description(element_name: &str) -> anyhow::Result<String> {
|
||||||
let mut desc = String::from("");
|
let mut desc = String::from("");
|
||||||
|
if !ElementInfo::element_factory_exists(element_name) {
|
||||||
|
desc.push_str("<b>Factory details:</b>\n");
|
||||||
|
desc.push_str("<b>Name:</b>");
|
||||||
|
desc.push_str(element_name);
|
||||||
|
desc.push('\n');
|
||||||
|
desc.push('\n');
|
||||||
|
desc.push_str("Factory unavailable.");
|
||||||
|
} else {
|
||||||
let feature = ElementInfo::element_feature(element_name)
|
let feature = ElementInfo::element_feature(element_name)
|
||||||
.ok_or_else(|| glib::bool_error!("Failed get element feature"))?;
|
.ok_or_else(|| glib::bool_error!("Failed get element feature"))?;
|
||||||
let rank = feature.rank();
|
let rank = feature.rank();
|
||||||
|
@ -114,6 +135,7 @@ impl ElementInfo {
|
||||||
desc.push('\n');
|
desc.push('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(desc)
|
Ok(desc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,13 +156,31 @@ impl ElementInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn element_property(element: &gst::Element, property_name: &str) -> anyhow::Result<String> {
|
pub fn element_property(element: &gst::Element, property_name: &str) -> anyhow::Result<String> {
|
||||||
let value = element
|
let value = element.property_value(property_name);
|
||||||
.property_value(property_name)
|
if value.type_().is_a(glib::Type::ENUM) {
|
||||||
|
let value = value.get::<&glib::EnumValue>().unwrap().nick().to_string();
|
||||||
|
Ok(value)
|
||||||
|
} else if value.type_().is_a(glib::Type::FLAGS) {
|
||||||
|
let value = value.get::<Vec<&glib::FlagsValue>>().unwrap();
|
||||||
|
let flags = value.iter().copied().fold(0, |acc, val| acc | val.value());
|
||||||
|
Ok(flags.to_string())
|
||||||
|
} else if value.type_().is_a(glib::Type::F64) || value.type_().is_a(glib::Type::F32) {
|
||||||
|
let value = value
|
||||||
.transform::<String>()
|
.transform::<String>()
|
||||||
.expect("Unable to transform to string")
|
.expect("Unable to transform to string")
|
||||||
.get::<String>()
|
.get::<String>()
|
||||||
.unwrap_or_default();
|
.unwrap()
|
||||||
|
.replace(',', ".");
|
||||||
Ok(value)
|
Ok(value)
|
||||||
|
} else {
|
||||||
|
let value = value
|
||||||
|
.transform::<String>()
|
||||||
|
.expect("Unable to transform to string")
|
||||||
|
.get::<String>()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_lowercase();
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn element_property_by_feature_name(
|
pub fn element_property_by_feature_name(
|
||||||
|
@ -159,7 +199,7 @@ impl ElementInfo {
|
||||||
element: &gst::Element,
|
element: &gst::Element,
|
||||||
) -> anyhow::Result<HashMap<String, glib::ParamSpec>> {
|
) -> anyhow::Result<HashMap<String, glib::ParamSpec>> {
|
||||||
let mut properties_list = HashMap::new();
|
let mut properties_list = HashMap::new();
|
||||||
let params = element.class().list_properties();
|
let params = element.list_properties();
|
||||||
|
|
||||||
for param in params.iter() {
|
for param in params.iter() {
|
||||||
GPS_INFO!("Property_name {}", param.name());
|
GPS_INFO!("Property_name {}", param.name());
|
||||||
|
@ -213,10 +253,6 @@ impl ElementInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn element_is_capsfilter(element_name: &str) -> bool {
|
|
||||||
matches!(element_name, "capsfilter")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn element_supports_new_pad_request(
|
pub fn element_supports_new_pad_request(
|
||||||
element_name: &str,
|
element_name: &str,
|
||||||
direction: PortDirection,
|
direction: PortDirection,
|
||||||
|
@ -240,7 +276,7 @@ impl ElementInfo {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_fo_element(bin: &gst::Bin, element_name: &str) -> Vec<gst::Element> {
|
pub fn search_for_element(bin: &gst::Bin, element_name: &str) -> Vec<gst::Element> {
|
||||||
let mut iter = bin.iterate_elements();
|
let mut iter = bin.iterate_elements();
|
||||||
let mut elements: Vec<gst::Element> = Vec::new();
|
let mut elements: Vec<gst::Element> = Vec::new();
|
||||||
elements = loop {
|
elements = loop {
|
||||||
|
@ -248,7 +284,7 @@ impl ElementInfo {
|
||||||
Ok(Some(element)) => {
|
Ok(Some(element)) => {
|
||||||
if element.is::<gst::Bin>() {
|
if element.is::<gst::Bin>() {
|
||||||
let bin = element.dynamic_cast::<gst::Bin>().unwrap();
|
let bin = element.dynamic_cast::<gst::Bin>().unwrap();
|
||||||
let mut bin_elements = ElementInfo::search_fo_element(&bin, element_name);
|
let mut bin_elements = ElementInfo::search_for_element(&bin, element_name);
|
||||||
elements.append(&mut bin_elements);
|
elements.append(&mut bin_elements);
|
||||||
} else {
|
} else {
|
||||||
GPS_INFO!("Found factory: {}", element.factory().unwrap().name());
|
GPS_INFO!("Found factory: {}", element.factory().unwrap().name());
|
||||||
|
|
|
@ -51,10 +51,9 @@ impl PadInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pads(element_name: &str, include_on_request: bool) -> (Vec<PadInfo>, Vec<PadInfo>) {
|
pub fn pads(element_name: &str, include_on_request: bool) -> (Vec<PadInfo>, Vec<PadInfo>) {
|
||||||
let feature = ElementInfo::element_feature(element_name).expect("Unable to get feature");
|
|
||||||
let mut input = vec![];
|
let mut input = vec![];
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
|
if let Some(feature) = ElementInfo::element_feature(element_name) {
|
||||||
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
|
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
|
||||||
if factory.num_pad_templates() > 0 {
|
if factory.num_pad_templates() > 0 {
|
||||||
let pads = factory.static_pad_templates();
|
let pads = factory.static_pad_templates();
|
||||||
|
@ -87,5 +86,8 @@ impl PadInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(input, output)
|
(input, output)
|
||||||
|
} else {
|
||||||
|
(input, output)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,11 @@ use std::fmt::Write as _;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum PipelineState {
|
pub enum PipelineState {
|
||||||
Playing,
|
Playing,
|
||||||
Paused,
|
Paused,
|
||||||
|
#[default]
|
||||||
Stopped,
|
Stopped,
|
||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
@ -40,7 +41,7 @@ impl fmt::Display for PipelineState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Player(Rc<PlayerInner>);
|
pub struct Player(Rc<PlayerInner>);
|
||||||
|
|
||||||
// Deref into the contained struct to make usage a bit more ergonomic
|
// Deref into the contained struct to make usage a bit more ergonomic
|
||||||
|
@ -61,12 +62,35 @@ impl PlayerWeak {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
fn gst_log_handler(
|
||||||
|
category: gst::DebugCategory,
|
||||||
|
level: gst::DebugLevel,
|
||||||
|
file: &glib::GStr,
|
||||||
|
function: &glib::GStr,
|
||||||
|
line: u32,
|
||||||
|
_obj: Option<&gst::LoggedObject>,
|
||||||
|
message: &gst::DebugMessage,
|
||||||
|
) {
|
||||||
|
let log_message = format!(
|
||||||
|
"{}\t{}\t{}:{}:{}\t{}",
|
||||||
|
level,
|
||||||
|
category.name(),
|
||||||
|
line,
|
||||||
|
file.as_str(),
|
||||||
|
function.as_str(),
|
||||||
|
message.get().unwrap().as_str()
|
||||||
|
);
|
||||||
|
|
||||||
|
GPS_GST_LOG!("{}", log_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
pub struct PlayerInner {
|
pub struct PlayerInner {
|
||||||
app: RefCell<Option<GPSApp>>,
|
app: RefCell<Option<GPSApp>>,
|
||||||
pipeline: RefCell<Option<gst::Pipeline>>,
|
pipeline: RefCell<Option<gst::Pipeline>>,
|
||||||
current_state: Cell<PipelineState>,
|
current_state: Cell<PipelineState>,
|
||||||
n_video_sink: Cell<usize>,
|
n_video_sink: Cell<usize>,
|
||||||
|
bus_watch_guard: RefCell<Option<gst::bus::BusWatchGuard>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
|
@ -76,12 +100,9 @@ impl Player {
|
||||||
pipeline: RefCell::new(None),
|
pipeline: RefCell::new(None),
|
||||||
current_state: Cell::new(PipelineState::Stopped),
|
current_state: Cell::new(PipelineState::Stopped),
|
||||||
n_video_sink: Cell::new(0),
|
n_video_sink: Cell::new(0),
|
||||||
|
bus_watch_guard: RefCell::new(None),
|
||||||
}));
|
}));
|
||||||
#[cfg(feature = "gtk4-plugin")]
|
gst::log::add_log_function(gst_log_handler);
|
||||||
{
|
|
||||||
gstgtk4::plugin_register_static().expect("Failed to register gstgtk4 plugin");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(pipeline)
|
Ok(pipeline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,13 +124,13 @@ impl Player {
|
||||||
.parse::<bool>()
|
.parse::<bool>()
|
||||||
.expect("Should a boolean value")
|
.expect("Should a boolean value")
|
||||||
{
|
{
|
||||||
ElementInfo::element_update_rank("gtk4paintablesink", gst::Rank::Primary);
|
ElementInfo::element_update_rank("gtk4paintablesink", gst::Rank::PRIMARY);
|
||||||
} else {
|
} else {
|
||||||
ElementInfo::element_update_rank("gtk4paintablesink", gst::Rank::Marginal);
|
ElementInfo::element_update_rank("gtk4paintablesink", gst::Rank::MARGINAL);
|
||||||
}
|
}
|
||||||
|
gst::log::set_threshold_from_string(settings::Settings::gst_log_level().as_str(), true);
|
||||||
// Create pipeline from the description
|
// Create pipeline from the description
|
||||||
let pipeline = gst::parse_launch(description)?;
|
let pipeline = gst::parse::launch(description)?;
|
||||||
let pipeline = pipeline.downcast::<gst::Pipeline>();
|
let pipeline = pipeline.downcast::<gst::Pipeline>();
|
||||||
/* start playing */
|
/* start playing */
|
||||||
if pipeline.is_err() {
|
if pipeline.is_err() {
|
||||||
|
@ -121,10 +142,11 @@ impl Player {
|
||||||
self.check_for_gtk4sink(pipeline.as_ref().unwrap());
|
self.check_for_gtk4sink(pipeline.as_ref().unwrap());
|
||||||
// GPSApp is not Send(trait) ready , so we use a channel to exchange the given data with the main thread and use
|
// GPSApp is not Send(trait) ready , so we use a channel to exchange the given data with the main thread and use
|
||||||
// GPSApp.
|
// GPSApp.
|
||||||
let (ready_tx, ready_rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
let (ready_tx, ready_rx) = async_channel::unbounded::<gst::Element>();
|
||||||
let player_weak = self.downgrade();
|
let player_weak = self.downgrade();
|
||||||
let _ = ready_rx.attach(None, move |element: gst::Element| {
|
glib::spawn_future_local(async move {
|
||||||
let player = upgrade_weak!(player_weak, glib::Continue(false));
|
while let Ok(element) = ready_rx.recv().await {
|
||||||
|
let player = upgrade_weak!(player_weak, glib::ControlFlow::Break);
|
||||||
let paintable = element.property::<gdk::Paintable>("paintable");
|
let paintable = element.property::<gdk::Paintable>("paintable");
|
||||||
let n_sink = player.n_video_sink.get();
|
let n_sink = player.n_video_sink.get();
|
||||||
player
|
player
|
||||||
|
@ -134,7 +156,8 @@ impl Player {
|
||||||
.expect("App should be available")
|
.expect("App should be available")
|
||||||
.set_app_preview(&paintable, n_sink);
|
.set_app_preview(&paintable, n_sink);
|
||||||
player.n_video_sink.set(n_sink + 1);
|
player.n_video_sink.set(n_sink + 1);
|
||||||
glib::Continue(true)
|
}
|
||||||
|
glib::ControlFlow::Continue
|
||||||
});
|
});
|
||||||
let bin = pipeline.unwrap().dynamic_cast::<gst::Bin>();
|
let bin = pipeline.unwrap().dynamic_cast::<gst::Bin>();
|
||||||
if let Ok(bin) = bin.as_ref() {
|
if let Ok(bin) = bin.as_ref() {
|
||||||
|
@ -142,7 +165,7 @@ impl Player {
|
||||||
if let Some(factory) = element.factory() {
|
if let Some(factory) = element.factory() {
|
||||||
GPS_INFO!("Received the signal deep element added {}", factory.name());
|
GPS_INFO!("Received the signal deep element added {}", factory.name());
|
||||||
if factory.name() == "gtk4paintablesink" {
|
if factory.name() == "gtk4paintablesink" {
|
||||||
let _ = ready_tx.send(element.clone());
|
let _ = ready_tx.try_send(element.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -153,7 +176,7 @@ impl Player {
|
||||||
|
|
||||||
pub fn check_for_gtk4sink(&self, pipeline: &gst::Pipeline) {
|
pub fn check_for_gtk4sink(&self, pipeline: &gst::Pipeline) {
|
||||||
let bin = pipeline.clone().dynamic_cast::<gst::Bin>().unwrap();
|
let bin = pipeline.clone().dynamic_cast::<gst::Bin>().unwrap();
|
||||||
let gtksinks = ElementInfo::search_fo_element(&bin, "gtk4paintablesink");
|
let gtksinks = ElementInfo::search_for_element(&bin, "gtk4paintablesink");
|
||||||
|
|
||||||
for (first_sink, gtksink) in gtksinks.into_iter().enumerate() {
|
for (first_sink, gtksink) in gtksinks.into_iter().enumerate() {
|
||||||
let paintable = gtksink.property::<gdk::Paintable>("paintable");
|
let paintable = gtksink.property::<gdk::Paintable>("paintable");
|
||||||
|
@ -180,12 +203,13 @@ impl Player {
|
||||||
|
|
||||||
let bus = pipeline.bus().expect("Pipeline had no bus");
|
let bus = pipeline.bus().expect("Pipeline had no bus");
|
||||||
let pipeline_weak = self.downgrade();
|
let pipeline_weak = self.downgrade();
|
||||||
bus.add_watch_local(move |_bus, msg| {
|
let bus_watch_guard = bus.add_watch_local(move |_bus, msg| {
|
||||||
let pipeline = upgrade_weak!(pipeline_weak, glib::Continue(false));
|
let pipeline = upgrade_weak!(pipeline_weak, glib::ControlFlow::Break);
|
||||||
pipeline.on_pipeline_message(msg);
|
pipeline.on_pipeline_message(msg);
|
||||||
glib::Continue(true)
|
glib::ControlFlow::Continue
|
||||||
})?;
|
})?;
|
||||||
*self.pipeline.borrow_mut() = Some(pipeline);
|
*self.pipeline.borrow_mut() = Some(pipeline);
|
||||||
|
*self.bus_watch_guard.borrow_mut() = Some(bus_watch_guard);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_state(new_state).map_err(|error| {
|
self.set_state(new_state).map_err(|error| {
|
||||||
|
@ -203,6 +227,7 @@ impl Player {
|
||||||
PipelineState::Paused => pipeline.set_state(gst::State::Paused)?,
|
PipelineState::Paused => pipeline.set_state(gst::State::Paused)?,
|
||||||
PipelineState::Stopped | PipelineState::Error => {
|
PipelineState::Stopped | PipelineState::Error => {
|
||||||
pipeline.set_state(gst::State::Null)?;
|
pipeline.set_state(gst::State::Null)?;
|
||||||
|
self.n_video_sink.set(0);
|
||||||
gst::StateChangeSuccess::Success
|
gst::StateChangeSuccess::Success
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -272,6 +297,9 @@ impl Player {
|
||||||
pub fn playing(&self) -> bool {
|
pub fn playing(&self) -> bool {
|
||||||
self.state() == PipelineState::Playing || self.state() == PipelineState::Paused
|
self.state() == PipelineState::Playing || self.state() == PipelineState::Paused
|
||||||
}
|
}
|
||||||
|
pub fn n_video_sink(&self) -> usize {
|
||||||
|
self.n_video_sink.get()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn downgrade(&self) -> PlayerWeak {
|
pub fn downgrade(&self) -> PlayerWeak {
|
||||||
PlayerWeak(Rc::downgrade(&self.0))
|
PlayerWeak(Rc::downgrade(&self.0))
|
||||||
|
@ -279,6 +307,9 @@ impl Player {
|
||||||
|
|
||||||
fn on_pipeline_message(&self, msg: &gst::MessageRef) {
|
fn on_pipeline_message(&self, msg: &gst::MessageRef) {
|
||||||
use gst::MessageView;
|
use gst::MessageView;
|
||||||
|
if let Some(message) = msg.structure() {
|
||||||
|
GPS_MSG_LOG!("{:?}", message);
|
||||||
|
}
|
||||||
match msg.view() {
|
match msg.view() {
|
||||||
MessageView::Eos(_) => {
|
MessageView::Eos(_) => {
|
||||||
GPS_INFO!("EOS received");
|
GPS_INFO!("EOS received");
|
||||||
|
@ -317,7 +348,7 @@ impl Player {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.dynamic_cast::<gst::Bin>()
|
.dynamic_cast::<gst::Bin>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let elements_name: Vec<String> = ElementInfo::search_fo_element(&bin, "")
|
let elements_name: Vec<String> = ElementInfo::search_for_element(&bin, "")
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| e.factory().unwrap().name().to_string())
|
.map(|e| e.factory().unwrap().name().to_string())
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -362,6 +393,11 @@ impl Player {
|
||||||
if n_ports > 1 {
|
if n_ports > 1 {
|
||||||
let _ = write!(description, "{unique_name}. ! ");
|
let _ = write!(description, "{unique_name}. ! ");
|
||||||
} else {
|
} else {
|
||||||
|
if let Some(link) = graphview.port_link(port.id()) {
|
||||||
|
if !link.name().is_empty() {
|
||||||
|
let _ = write!(description, "! {} ", link.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
description.push_str("! ");
|
description.push_str("! ");
|
||||||
}
|
}
|
||||||
if let Some(node) = graphview.node(node_to) {
|
if let Some(node) = graphview.node(node_to) {
|
||||||
|
@ -416,7 +452,6 @@ impl Player {
|
||||||
peer_node.id(),
|
peer_node.id(),
|
||||||
port.id(),
|
port.id(),
|
||||||
peer_port.id(),
|
peer_port.id(),
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -525,15 +560,9 @@ impl Player {
|
||||||
|
|
||||||
impl Drop for PlayerInner {
|
impl Drop for PlayerInner {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// TODO: If a recording is currently running we would like to finish that first
|
|
||||||
// before quitting the pipeline and shutting down the pipeline.
|
|
||||||
if let Some(pipeline) = self.pipeline.borrow().to_owned() {
|
if let Some(pipeline) = self.pipeline.borrow().to_owned() {
|
||||||
// We ignore any errors here
|
// We ignore any errors here
|
||||||
let _ = pipeline.set_state(gst::State::Null);
|
let _ = pipeline.set_state(gst::State::Null);
|
||||||
|
|
||||||
// Remove the message watch from the bus
|
|
||||||
let bus = pipeline.bus().expect("Pipeline had no bus");
|
|
||||||
let _ = bus.remove_watch();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
503
src/graphbook.rs
Normal file
|
@ -0,0 +1,503 @@
|
||||||
|
// graphbook.rs
|
||||||
|
//
|
||||||
|
// Copyright 2021 Stéphane Cerveau <scerveau@collabora.com>
|
||||||
|
//
|
||||||
|
// This file is part of GstPipelineStudio
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
use crate::app::{GPSApp, GPSAppWeak};
|
||||||
|
use crate::gps as GPS;
|
||||||
|
use crate::graphmanager as GM;
|
||||||
|
use crate::graphmanager::PropertyExt;
|
||||||
|
use crate::logger;
|
||||||
|
use crate::settings::Settings;
|
||||||
|
use crate::ui as GPSUI;
|
||||||
|
use crate::{GPS_DEBUG, GPS_TRACE, GPS_WARN};
|
||||||
|
use glib::Value;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::{gio, glib, graphene};
|
||||||
|
use std::cell::{Cell, Ref, RefCell};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
|
enum TabState {
|
||||||
|
#[default]
|
||||||
|
Undefined = 0,
|
||||||
|
Modified,
|
||||||
|
Saved,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct GraphTab {
|
||||||
|
graphview: RefCell<GM::GraphView>,
|
||||||
|
player: RefCell<GPS::Player>,
|
||||||
|
id: Cell<u32>,
|
||||||
|
name: gtk::Label,
|
||||||
|
filename: RefCell<String>,
|
||||||
|
state: Cell<TabState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphTab {
|
||||||
|
pub fn new(app: GPSAppWeak, id: u32, filename: &str) -> Self {
|
||||||
|
let label = gtk::Label::new(Some("Untitled*"));
|
||||||
|
let graphtab = GraphTab {
|
||||||
|
id: Cell::new(id),
|
||||||
|
graphview: RefCell::new(GM::GraphView::new()),
|
||||||
|
player: RefCell::new(
|
||||||
|
GPS::Player::new().expect("Unable to initialize GStreamer subsystem"),
|
||||||
|
),
|
||||||
|
name: label,
|
||||||
|
filename: RefCell::new(filename.to_string()),
|
||||||
|
state: Cell::new(TabState::Undefined),
|
||||||
|
};
|
||||||
|
graphtab
|
||||||
|
.graphview
|
||||||
|
.borrow()
|
||||||
|
.set_id(graphbook_get_new_graphview_id(&app));
|
||||||
|
graphtab.player.borrow().set_app(app);
|
||||||
|
graphtab
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u32 {
|
||||||
|
self.id.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget_label(&self) -> >k::Label {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphview(&self) -> Ref<GM::GraphView> {
|
||||||
|
self.graphview.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn player(&self) -> Ref<GPS::Player> {
|
||||||
|
self.player.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_name(&self, name: &str) {
|
||||||
|
self.name.set_text(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn basename(&self) -> String {
|
||||||
|
Path::new(&self.filename.borrow().as_str())
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_filename(&self, filename: &str) {
|
||||||
|
self.filename.replace(filename.to_string());
|
||||||
|
self.set_name(self.basename().as_str());
|
||||||
|
self.set_modified(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filename(&self) -> String {
|
||||||
|
self.filename.borrow().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_modified(&self, modified: bool) {
|
||||||
|
let name = self.basename();
|
||||||
|
if modified {
|
||||||
|
self.set_name(&(name + "*"));
|
||||||
|
self.state.set(TabState::Modified);
|
||||||
|
} else {
|
||||||
|
self.set_name(name.as_str());
|
||||||
|
self.state.set(TabState::Saved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn undefined(&self) -> bool {
|
||||||
|
self.state.get() == TabState::Undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modified(&self) -> bool {
|
||||||
|
self.state.get() == TabState::Modified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphtab(app: &GPSApp, id: u32) -> GraphTab {
|
||||||
|
app.graphbook
|
||||||
|
.borrow()
|
||||||
|
.get(&id)
|
||||||
|
.cloned()
|
||||||
|
.expect("the current graphtab should be ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphbook_get_new_graphview_id(app_weak: &GPSAppWeak) -> u32 {
|
||||||
|
let app = app_weak.upgrade();
|
||||||
|
let mut graphview_id: u32 = 0;
|
||||||
|
for tab in app.unwrap().graphbook.borrow().values() {
|
||||||
|
if tab.graphview().id() > graphview_id {
|
||||||
|
graphview_id = tab.graphview().id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graphview_id + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphbook_get_new_graphtab_id(app: &GPSApp) -> u32 {
|
||||||
|
let mut graphtab_id: u32 = 0;
|
||||||
|
for tab in app.graphbook.borrow().values() {
|
||||||
|
if tab.id() > graphtab_id {
|
||||||
|
graphtab_id = tab.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graphtab_id + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_graphtab(app: &GPSApp) -> GraphTab {
|
||||||
|
graphtab(app, app.current_graphtab.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_graphtab_set_filename(app: &GPSApp, filename: &str) {
|
||||||
|
app.graphbook
|
||||||
|
.borrow()
|
||||||
|
.get(&app.current_graphtab.get())
|
||||||
|
.expect("the graphtab is available")
|
||||||
|
.set_filename(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_graphtab_set_modified(app: &GPSApp, modified: bool) {
|
||||||
|
app.graphbook
|
||||||
|
.borrow()
|
||||||
|
.get(&app.current_graphtab.get())
|
||||||
|
.expect("the graphtab is available")
|
||||||
|
.set_modified(modified);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup_graphbook(app: &GPSApp) {
|
||||||
|
let graphbook: gtk::Notebook = app
|
||||||
|
.builder
|
||||||
|
.object("graphbook")
|
||||||
|
.expect("Couldn't get the graphbook");
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
graphbook.connect_switch_page(move |_book, widget, page| {
|
||||||
|
let graphview = widget
|
||||||
|
.first_child()
|
||||||
|
.expect("Unable to get the child from the graphbook, ie the scrolledWindow");
|
||||||
|
if let Ok(graphview) = graphview.dynamic_cast::<GM::GraphView>() {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_TRACE!("graphview.id() {} graphbook page {}", graphview.id(), page);
|
||||||
|
app.current_graphtab.set(page);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_graphtab(app: &GPSApp, id: u32, name: Option<&str>) {
|
||||||
|
let graph_tab = GraphTab::new(app.downgrade(), id, name.unwrap_or("Untitled"));
|
||||||
|
let gt = graph_tab.clone();
|
||||||
|
app.graphbook.borrow_mut().insert(id, graph_tab);
|
||||||
|
|
||||||
|
let graphbook: gtk::Notebook = app
|
||||||
|
.builder
|
||||||
|
.object("graphbook")
|
||||||
|
.expect("Couldn't get graphbook");
|
||||||
|
|
||||||
|
let scrollwindow = gtk::ScrolledWindow::builder()
|
||||||
|
.name("graphview_scroll")
|
||||||
|
.child(&*graphtab(app, id).graphview())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let tab_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||||
|
let label = gt.widget_label();
|
||||||
|
tab_box.append(label);
|
||||||
|
let icon = gtk::Image::from_icon_name("window-close-symbolic");
|
||||||
|
let close_button = gtk::Button::builder().build();
|
||||||
|
close_button.set_child(Some(&icon));
|
||||||
|
close_button.add_css_class("small-button");
|
||||||
|
close_button.add_css_class("image-button");
|
||||||
|
close_button.add_css_class("flat");
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
close_button.connect_clicked(glib::clone!(@weak graphbook => move |_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
graphbook.remove_page(Some(current_graphtab(&app).id()));
|
||||||
|
}));
|
||||||
|
tab_box.append(&close_button);
|
||||||
|
graphbook.append_page(&scrollwindow, Some(&tab_box));
|
||||||
|
graphbook.set_tab_reorderable(&scrollwindow, true);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview().connect_local(
|
||||||
|
"graph-updated",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let id = values[1].get::<u32>().expect("id in args[1]");
|
||||||
|
GPS_DEBUG!("Graph updated id={}", id);
|
||||||
|
let _ = app
|
||||||
|
.save_graph(
|
||||||
|
Settings::graph_file_path()
|
||||||
|
.to_str()
|
||||||
|
.expect("Unable to convert to string"),
|
||||||
|
)
|
||||||
|
.map_err(|e| GPS_WARN!("Unable to save file {}", e));
|
||||||
|
current_graphtab_set_modified(&app, true);
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview().connect_local(
|
||||||
|
"node-added",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let graph_id = values[1].get::<u32>().expect("graph id in args[1]");
|
||||||
|
let node_id = values[2].get::<u32>().expect("node id in args[2]");
|
||||||
|
GPS_DEBUG!("Node added node id={} in graph id={}", node_id, graph_id);
|
||||||
|
if let Some(node) = current_graphtab(&app).graphview().node(node_id) {
|
||||||
|
let description = GPS::ElementInfo::element_description(&node.name()).ok();
|
||||||
|
node.set_tooltip_markup(description.as_deref());
|
||||||
|
if !GPS::ElementInfo::element_factory_exists(&node.name()) {
|
||||||
|
node.set_light(true);
|
||||||
|
}
|
||||||
|
for port in node.all_ports(GM::PortDirection::All) {
|
||||||
|
let caps = PropertyExt::property(&port,"_caps");
|
||||||
|
GPS_DEBUG!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id());
|
||||||
|
port.set_tooltip_markup(caps.as_deref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview().connect_local(
|
||||||
|
"port-added",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let graph_id = values[1].get::<u32>().expect("graph id in args[1]");
|
||||||
|
let node_id = values[2].get::<u32>().expect("node id in args[2]");
|
||||||
|
let port_id = values[3].get::<u32>().expect("port id in args[3]");
|
||||||
|
GPS_DEBUG!("Port added port id={} to node id={} in graph id={}", port_id, node_id, graph_id);
|
||||||
|
if let Some(node) = current_graphtab(&app).graphview().node(node_id) {
|
||||||
|
if let Some(port) = node.port(port_id) {
|
||||||
|
let caps = PropertyExt::property(&port, "_caps");
|
||||||
|
GPS_DEBUG!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id());
|
||||||
|
port.set_tooltip_markup(caps.as_deref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// When user clicks on port with right button
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview()
|
||||||
|
.connect_local(
|
||||||
|
"graph-right-clicked",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let point = values[1].get::<graphene::Point>().expect("point in args[2]");
|
||||||
|
let pop_menu = app.app_pop_menu_at_position(&*current_graphtab(&app).graphview(), point.to_vec2().x() as f64, point.to_vec2().y() as f64);
|
||||||
|
let menu: gio::MenuModel = app
|
||||||
|
.builder
|
||||||
|
.object("graph_menu")
|
||||||
|
.expect("Couldn't graph_menu");
|
||||||
|
pop_menu.set_menu_model(Some(&menu));
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("graph.clear",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
current_graphtab(&app).graphview().clear();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("graph.check",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
let render_parse_launch = current_graphtab(&app).player().pipeline_description_from_graphview(¤t_graphtab(&app).graphview());
|
||||||
|
if current_graphtab(&app).player().create_pipeline(&render_parse_launch).is_ok() {
|
||||||
|
GPSUI::message::display_message_dialog(&render_parse_launch,gtk::MessageType::Info, |_| {});
|
||||||
|
} else {
|
||||||
|
GPSUI::message::display_error_dialog(false, &format!("Unable to render:\n\n{render_parse_launch}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("graph.pipeline_details",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPSUI::properties::display_pipeline_details(&app);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
pop_menu.show();
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When user clicks on port with right button
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview()
|
||||||
|
.connect_local("port-right-clicked", false, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let port_id = values[1].get::<u32>().expect("port id args[1]");
|
||||||
|
let node_id = values[2].get::<u32>().expect("node id args[2]");
|
||||||
|
let point = values[3]
|
||||||
|
.get::<graphene::Point>()
|
||||||
|
.expect("point in args[3]");
|
||||||
|
let pop_menu = app.app_pop_menu_at_position(
|
||||||
|
&*current_graphtab(&app).graphview(),
|
||||||
|
point.to_vec2().x() as f64,
|
||||||
|
point.to_vec2().y() as f64,
|
||||||
|
);
|
||||||
|
let menu: gio::MenuModel = app
|
||||||
|
.builder
|
||||||
|
.object("port_menu")
|
||||||
|
.expect("Couldn't get menu model for port");
|
||||||
|
pop_menu.set_menu_model(Some(&menu));
|
||||||
|
|
||||||
|
if current_graphtab(&app)
|
||||||
|
.graphview()
|
||||||
|
.can_remove_port(node_id, port_id)
|
||||||
|
{
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("port.delete", move |_, _| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("port.delete-link port id {} node id {}", port_id, node_id);
|
||||||
|
current_graphtab(&app)
|
||||||
|
.graphview()
|
||||||
|
.remove_port(node_id, port_id);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
app.disconnect_app_menu_action("port.delete");
|
||||||
|
}
|
||||||
|
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("port.properties", move |_, _| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("port.properties port id {} node id {}", port_id, node_id);
|
||||||
|
let node = app.node(node_id);
|
||||||
|
let port = app.port(node_id, port_id);
|
||||||
|
GPSUI::properties::display_pad_properties(
|
||||||
|
&app,
|
||||||
|
&node.name(),
|
||||||
|
&port.name(),
|
||||||
|
node_id,
|
||||||
|
port_id,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
pop_menu.show();
|
||||||
|
None
|
||||||
|
});
|
||||||
|
|
||||||
|
// When user clicks on node with right button
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview()
|
||||||
|
.connect_local(
|
||||||
|
"node-right-clicked",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let node_id = values[1].get::<u32>().expect("node id args[1]");
|
||||||
|
let node = current_graphtab(&app).graphview().node(node_id).unwrap();
|
||||||
|
let element_exists = GPS::ElementInfo::element_factory_exists(&node.name());
|
||||||
|
let point = values[2].get::<graphene::Point>().expect("point in args[2]");
|
||||||
|
let pop_menu = app.app_pop_menu_at_position(&*current_graphtab(&app).graphview(), point.to_vec2().x() as f64, point.to_vec2().y() as f64);
|
||||||
|
let menu: gio::MenuModel = app
|
||||||
|
.builder
|
||||||
|
.object("node_menu")
|
||||||
|
.expect("Couldn't get menu model for node");
|
||||||
|
pop_menu.set_menu_model(Some(&menu));
|
||||||
|
|
||||||
|
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("node.delete",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("node.delete id: {}", node_id);
|
||||||
|
current_graphtab(&app).graphview().remove_node(node_id);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if element_exists {
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("node.add-to-favorite",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("node.add-to-favorite id: {}", node_id);
|
||||||
|
if let Some(node) = current_graphtab(&app).graphview().node(node_id) {
|
||||||
|
GPSUI::elements::add_to_favorite_list(&app, node.name());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
let node = app.node(node_id);
|
||||||
|
if let Some(input) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Input) {
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("node.request-pad-input",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("node.request-pad-input id: {}", node_id);
|
||||||
|
app.create_port_with_caps(node_id, GM::PortDirection::Input, GM::PortPresence::Sometimes, input.caps().to_string());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
app.disconnect_app_menu_action("node.request-pad-input");
|
||||||
|
}
|
||||||
|
let node = app.node(node_id);
|
||||||
|
if let Some(output) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Output) {
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("node.request-pad-output",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("node.request-pad-output id: {}", node_id);
|
||||||
|
app.create_port_with_caps(node_id, GM::PortDirection::Output, GM::PortPresence::Sometimes, output.caps().to_string());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
app.disconnect_app_menu_action("node.request-pad-output");
|
||||||
|
}
|
||||||
|
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("node.properties",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("node.properties id {}", node_id);
|
||||||
|
let node = current_graphtab(&app).graphview().node(node_id).unwrap();
|
||||||
|
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
pop_menu.show();
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview().connect_local(
|
||||||
|
"node-double-clicked",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let node_id = values[1].get::<u32>().expect("node id args[1]");
|
||||||
|
GPS_TRACE!("Node double clicked id={}", node_id);
|
||||||
|
let node = current_graphtab(&app).graphview().node(node_id).unwrap();
|
||||||
|
if GPS::ElementInfo::element_factory_exists(&node.name()) {
|
||||||
|
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview().connect_local(
|
||||||
|
"link-double-clicked",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let link_id = values[1].get::<u32>().expect("link id args[1]");
|
||||||
|
GPS_TRACE!("link double clicked id={}", link_id);
|
||||||
|
let link = current_graphtab(&app).graphview().link(link_id).unwrap();
|
||||||
|
GPSUI::dialog::create_input_dialog(
|
||||||
|
"Enter caps filter description",
|
||||||
|
"description",
|
||||||
|
&link.name(),
|
||||||
|
&app,
|
||||||
|
move |app, link_desc| {
|
||||||
|
current_graphtab(&app).graphview().set_link_name(link.id(), link_desc.as_str());
|
||||||
|
GPS_DEBUG!("link double clicked id={} name={}", link.id(), link.name());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use super::SelectionExt;
|
use super::SelectionExt;
|
||||||
use std::cell::Cell;
|
use std::cell::{Cell, RefCell};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Link {
|
pub struct Link {
|
||||||
|
@ -17,44 +17,49 @@ pub struct Link {
|
||||||
pub node_to: u32,
|
pub node_to: u32,
|
||||||
pub port_from: u32,
|
pub port_from: u32,
|
||||||
pub port_to: u32,
|
pub port_to: u32,
|
||||||
pub active: bool,
|
pub active: Cell<bool>,
|
||||||
pub selected: Cell<bool>,
|
pub selected: Cell<bool>,
|
||||||
pub thickness: u32,
|
pub thickness: u32,
|
||||||
|
pub name: RefCell<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Link {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
self.name.borrow().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_name(&self, name: &str) {
|
||||||
|
self.name.replace(name.to_string());
|
||||||
|
}
|
||||||
|
pub fn id(&self) -> u32 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
pub fn active(&self) -> bool {
|
||||||
|
self.active.get()
|
||||||
|
}
|
||||||
|
pub fn set_active(&self, active: bool) {
|
||||||
|
self.active.set(active)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait LinkExt {
|
pub trait LinkExt {
|
||||||
/// Create a new link
|
/// Create a new link
|
||||||
///
|
///
|
||||||
fn new(
|
fn new(id: u32, node_from: u32, node_to: u32, port_from: u32, port_to: u32) -> Self;
|
||||||
id: u32,
|
|
||||||
node_from: u32,
|
|
||||||
node_to: u32,
|
|
||||||
port_from: u32,
|
|
||||||
port_to: u32,
|
|
||||||
active: bool,
|
|
||||||
selected: bool,
|
|
||||||
) -> Self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LinkExt for Link {
|
impl LinkExt for Link {
|
||||||
fn new(
|
fn new(id: u32, node_from: u32, node_to: u32, port_from: u32, port_to: u32) -> Self {
|
||||||
id: u32,
|
|
||||||
node_from: u32,
|
|
||||||
node_to: u32,
|
|
||||||
port_from: u32,
|
|
||||||
port_to: u32,
|
|
||||||
active: bool,
|
|
||||||
selected: bool,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
node_from,
|
node_from,
|
||||||
node_to,
|
node_to,
|
||||||
port_from,
|
port_from,
|
||||||
port_to,
|
port_to,
|
||||||
active,
|
active: Cell::new(true),
|
||||||
selected: Cell::new(selected),
|
selected: Cell::new(false),
|
||||||
thickness: 4,
|
thickness: 4,
|
||||||
|
name: RefCell::new("".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ mod property;
|
||||||
mod selection;
|
mod selection;
|
||||||
|
|
||||||
pub use graphview::GraphView;
|
pub use graphview::GraphView;
|
||||||
pub use link::Link;
|
|
||||||
pub use node::Node;
|
pub use node::Node;
|
||||||
pub use node::NodeType;
|
pub use node::NodeType;
|
||||||
pub use port::Port;
|
pub use port::Port;
|
||||||
|
@ -104,11 +103,15 @@ mod test {
|
||||||
// Ports have been created by create_node_with_port
|
// Ports have been created by create_node_with_port
|
||||||
|
|
||||||
//Create link between node1 and node 2
|
//Create link between node1 and node 2
|
||||||
let link = graphview.create_link(1, 2, 1, 2, true);
|
let link = graphview.create_link(1, 2, 1, 2);
|
||||||
|
assert_eq!(&link.name(), "");
|
||||||
|
assert!(&link.active());
|
||||||
|
link.set_name("link1");
|
||||||
|
assert_eq!(&link.name(), "link1");
|
||||||
graphview.add_link(link);
|
graphview.add_link(link);
|
||||||
|
|
||||||
//Create link between node2 and node 3
|
//Create link between node2 and node 3
|
||||||
let link = graphview.create_link(2, 3, 3, 4, true);
|
let link = graphview.create_link(2, 3, 3, 4);
|
||||||
graphview.add_link(link);
|
graphview.add_link(link);
|
||||||
|
|
||||||
// Save the graphview in XML into a buffer
|
// Save the graphview in XML into a buffer
|
||||||
|
|
|
@ -48,7 +48,7 @@ impl PortDirection {
|
||||||
/// Port's presence
|
/// Port's presence
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Copy)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Copy)]
|
||||||
pub enum PortPresence {
|
pub enum PortPresence {
|
||||||
/// Can not be removed from his parent independantly
|
/// Can not be removed from his parent independently
|
||||||
Always,
|
Always,
|
||||||
/// Can be removed from a node
|
/// Can be removed from a node
|
||||||
Sometimes,
|
Sometimes,
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use gtk::glib::Sender;
|
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use simplelog::*;
|
use simplelog::*;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -14,6 +13,13 @@ use std::io;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
|
use chrono::Local;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref MSG_LOGGER: Mutex<Option<MessageLogger>> = Mutex::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
|
||||||
pub enum LogLevel {
|
pub enum LogLevel {
|
||||||
|
@ -25,6 +31,13 @@ pub enum LogLevel {
|
||||||
Trace,
|
Trace,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum LogType {
|
||||||
|
App,
|
||||||
|
Gst,
|
||||||
|
Message,
|
||||||
|
}
|
||||||
|
|
||||||
impl LogLevel {
|
impl LogLevel {
|
||||||
pub fn from_u32(value: u32) -> LogLevel {
|
pub fn from_u32(value: u32) -> LogLevel {
|
||||||
match value {
|
match value {
|
||||||
|
@ -49,7 +62,7 @@ impl fmt::Display for LogLevel {
|
||||||
macro_rules! GPS_ERROR (
|
macro_rules! GPS_ERROR (
|
||||||
() => ($crate::print!("\n"));
|
() => ($crate::print!("\n"));
|
||||||
($($arg:tt)*) => ({
|
($($arg:tt)*) => ({
|
||||||
logger::print_log(logger::LogLevel::Error, format_args!($($arg)*).to_string());
|
logger::print_log(logger::LogLevel::Error, format!("{}\t{}",glib::function_name!(),format_args!($($arg)*).to_string()));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -57,7 +70,7 @@ macro_rules! GPS_ERROR (
|
||||||
macro_rules! GPS_WARN (
|
macro_rules! GPS_WARN (
|
||||||
() => ($crate::print!("\n"));
|
() => ($crate::print!("\n"));
|
||||||
($($arg:tt)*) => ({
|
($($arg:tt)*) => ({
|
||||||
logger::print_log(logger::LogLevel::Warning, format_args!($($arg)*).to_string());
|
logger::print_log(logger::LogLevel::Warning, format!("{}\t{}",glib::function_name!(),format_args!($($arg)*).to_string()));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -65,7 +78,7 @@ macro_rules! GPS_WARN (
|
||||||
macro_rules! GPS_INFO (
|
macro_rules! GPS_INFO (
|
||||||
() => ($crate::print!("\n"));
|
() => ($crate::print!("\n"));
|
||||||
($($arg:tt)*) => ({
|
($($arg:tt)*) => ({
|
||||||
logger::print_log(logger::LogLevel::Info, format_args!($($arg)*).to_string());
|
logger::print_log(logger::LogLevel::Info, format!("{}\t{}",glib::function_name!(),format_args!($($arg)*).to_string()));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -73,7 +86,23 @@ macro_rules! GPS_INFO (
|
||||||
macro_rules! GPS_DEBUG (
|
macro_rules! GPS_DEBUG (
|
||||||
() => ($crate::print!("\n"));
|
() => ($crate::print!("\n"));
|
||||||
($($arg:tt)*) => ({
|
($($arg:tt)*) => ({
|
||||||
logger::print_log(logger::LogLevel::Debug, format_args!($($arg)*).to_string());
|
logger::print_log(logger::LogLevel::Debug, format!("{}\t{}",glib::function_name!(),format_args!($($arg)*).to_string()));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! GPS_MSG_LOG (
|
||||||
|
() => ($crate::print!("\n"));
|
||||||
|
($($arg:tt)*) => ({
|
||||||
|
logger::print_msg_logger(logger::LogType::Message, format_args!($($arg)*).to_string());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! GPS_GST_LOG (
|
||||||
|
() => ($crate::print!("\n"));
|
||||||
|
($($arg:tt)*) => ({
|
||||||
|
logger::print_msg_logger(logger::LogType::Gst, format_args!($($arg)*).to_string());
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -86,7 +115,7 @@ macro_rules! GPS_TRACE (
|
||||||
);
|
);
|
||||||
|
|
||||||
struct WriteAdapter {
|
struct WriteAdapter {
|
||||||
sender: Sender<String>,
|
sender: async_channel::Sender<(LogType, String)>,
|
||||||
buffer: String,
|
buffer: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,8 +126,8 @@ impl io::Write for WriteAdapter {
|
||||||
.push_str(&String::from_utf8(buf.to_vec()).unwrap());
|
.push_str(&String::from_utf8(buf.to_vec()).unwrap());
|
||||||
if self.buffer.ends_with('\n') {
|
if self.buffer.ends_with('\n') {
|
||||||
self.buffer.pop();
|
self.buffer.pop();
|
||||||
self.sender.send(self.buffer.clone()).unwrap();
|
let _ = self.sender.try_send((LogType::App, self.buffer.clone()));
|
||||||
self.buffer = String::from("");
|
self.buffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
|
@ -120,7 +149,7 @@ fn translate_to_simple_logger(log_level: LogLevel) -> LevelFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_logger(sender: Sender<String>, log_file: &str) {
|
pub fn init_logger(sender: async_channel::Sender<(LogType, String)>, log_file: &str) {
|
||||||
simplelog::CombinedLogger::init(vec![
|
simplelog::CombinedLogger::init(vec![
|
||||||
WriteLogger::new(
|
WriteLogger::new(
|
||||||
translate_to_simple_logger(LogLevel::Trace),
|
translate_to_simple_logger(LogLevel::Trace),
|
||||||
|
@ -169,3 +198,36 @@ pub fn print_log(log_level: LogLevel, msg: String) {
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MessageLogger {
|
||||||
|
sender: async_channel::Sender<(LogType, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageLogger {
|
||||||
|
pub fn new(sender: async_channel::Sender<(LogType, String)>) -> Self {
|
||||||
|
Self { sender }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_log(&self, log_type: LogType, msg: String) {
|
||||||
|
let to_send = format!("{}\t{}", Local::now().format("%H:%M:%S"), msg);
|
||||||
|
self.sender
|
||||||
|
.try_send((log_type, to_send))
|
||||||
|
.expect("Unable to send the log");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_msg_logger(sender: async_channel::Sender<(LogType, String)>) {
|
||||||
|
let mut msg_logger = MSG_LOGGER.lock().unwrap();
|
||||||
|
if msg_logger.is_none() {
|
||||||
|
// Initialize the variable
|
||||||
|
*msg_logger = Some(MessageLogger::new(sender));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_msg_logger(log_type: LogType, msg: String) {
|
||||||
|
let msg_logger = MSG_LOGGER.lock().unwrap();
|
||||||
|
if let Some(logger) = msg_logger.as_ref() {
|
||||||
|
logger.print_log(log_type, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
20
src/main.rs
|
@ -11,6 +11,7 @@ mod macros;
|
||||||
mod app;
|
mod app;
|
||||||
mod common;
|
mod common;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod graphbook;
|
||||||
mod graphmanager;
|
mod graphmanager;
|
||||||
mod ui;
|
mod ui;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -21,14 +22,29 @@ use gtk::prelude::*;
|
||||||
|
|
||||||
use crate::app::GPSApp;
|
use crate::app::GPSApp;
|
||||||
use crate::common::init;
|
use crate::common::init;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
struct Command {
|
||||||
|
#[structopt(about = "Sets the pipeline description", default_value = "")]
|
||||||
|
pipeline: String,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// gio::resources_register_include!("compiled.gresource").unwrap();
|
// gio::resources_register_include!("compiled.gresource").unwrap();
|
||||||
init().expect("Unable to init app");
|
init().expect("Unable to init app");
|
||||||
let application = gtk::Application::new(Some(config::APP_ID), Default::default());
|
let application = gtk::Application::new(
|
||||||
|
Some(config::APP_ID),
|
||||||
|
gtk::gio::ApplicationFlags::HANDLES_COMMAND_LINE,
|
||||||
|
);
|
||||||
application.connect_startup(|application| {
|
application.connect_startup(|application| {
|
||||||
GPSApp::on_startup(application);
|
let args = Command::from_args();
|
||||||
|
GPSApp::on_startup(application, &args.pipeline);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
application.connect_command_line(|_app, _cmd_line| {
|
||||||
|
// structopt already handled arguments
|
||||||
|
0
|
||||||
|
});
|
||||||
application.run();
|
application.run();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
conf = configuration_data()
|
conf = configuration_data()
|
||||||
conf.set_quoted('APP_ID', application_id)
|
conf.set_quoted('APP_ID', application_id)
|
||||||
conf.set_quoted('VERSION', version + version_suffix)
|
|
||||||
|
|
||||||
configure_file(
|
configure_file(
|
||||||
input: 'config.rs.in',
|
input: 'config.rs.in',
|
||||||
|
@ -9,7 +8,7 @@ configure_file(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Copy the config.rs output to the source directory.
|
# Copy the config.rs output to the source directory.
|
||||||
run_command(python3, '-c', 'import shutil; shutil.copyfile("@0@", "@1@")'.format(join_paths(meson.build_root(), 'src', 'config.rs'), join_paths(meson.source_root(), 'src', 'config.rs')))
|
run_command(python3, '-c', 'import shutil; shutil.copyfile("@0@", "@1@")'.format(join_paths(meson.project_build_root(), 'src', 'config.rs'), join_paths(meson.project_source_root(), 'src', 'config.rs')))
|
||||||
|
|
||||||
rust_sources = files(
|
rust_sources = files(
|
||||||
'gps/player.rs',
|
'gps/player.rs',
|
||||||
|
@ -33,13 +32,12 @@ rust_sources = files(
|
||||||
'logger.rs',
|
'logger.rs',
|
||||||
'macros.rs',
|
'macros.rs',
|
||||||
'main.rs',
|
'main.rs',
|
||||||
|
|
||||||
'settings.rs',
|
'settings.rs',
|
||||||
)
|
)
|
||||||
|
|
||||||
sources = [cargo_sources, rust_sources]
|
sources = [cargo_sources, rust_sources]
|
||||||
|
|
||||||
cargo_script = find_program(join_paths(meson.source_root(), 'build-aux/cargo.py'))
|
cargo_script = find_program(join_paths(meson.project_source_root(), 'build-aux/cargo.py'))
|
||||||
|
|
||||||
app_name = meson.project_name()
|
app_name = meson.project_name()
|
||||||
if host_system == 'windows'
|
if host_system == 'windows'
|
||||||
|
@ -56,8 +54,8 @@ cargo_release = custom_target(
|
||||||
install_dir: get_option('bindir'),
|
install_dir: get_option('bindir'),
|
||||||
command: [
|
command: [
|
||||||
cargo_script,
|
cargo_script,
|
||||||
meson.build_root(),
|
meson.project_build_root(),
|
||||||
meson.source_root(),
|
meson.project_source_root(),
|
||||||
'@OUTPUT@',
|
'@OUTPUT@',
|
||||||
get_option('buildtype'),
|
get_option('buildtype'),
|
||||||
app_name,
|
app_name,
|
||||||
|
|
|
@ -31,45 +31,55 @@ pub struct Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
fn settings_file_exist() {
|
fn create_path_if_not(s: &PathBuf) {
|
||||||
let s = Settings::settings_file_path();
|
|
||||||
|
|
||||||
if !s.exists() {
|
if !s.exists() {
|
||||||
if let Some(parent_dir) = s.parent() {
|
if let Err(e) = create_dir_all(s) {
|
||||||
if !parent_dir.exists() {
|
|
||||||
if let Err(e) = create_dir_all(parent_dir) {
|
|
||||||
GPS_ERROR!(
|
GPS_ERROR!(
|
||||||
"Error while trying to build settings snapshot_directory '{}': {}",
|
"Error while trying to build settings snapshot_directory '{}': {}",
|
||||||
parent_dir.display(),
|
s.display(),
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fn default_app_folder() -> PathBuf {
|
||||||
|
let mut path = glib::user_config_dir();
|
||||||
|
path.push(config::APP_ID);
|
||||||
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
fn settings_file_path() -> PathBuf {
|
fn settings_file_path() -> PathBuf {
|
||||||
let mut path = glib::user_config_dir();
|
let mut path = Settings::default_app_folder();
|
||||||
path.push(config::APP_ID);
|
Settings::create_path_if_not(&path);
|
||||||
path.push("settings.toml");
|
path.push("settings.toml");
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
// Public methods
|
// Public methods
|
||||||
pub fn default_graph_file_path() -> PathBuf {
|
pub fn graph_file_path() -> PathBuf {
|
||||||
let mut path = glib::user_config_dir();
|
let mut path = Settings::default_app_folder();
|
||||||
path.push(config::APP_ID);
|
Settings::create_path_if_not(&path);
|
||||||
path.push("default_graph.toml");
|
path.push("default_graph.toml");
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_log_file_path() -> PathBuf {
|
pub fn log_file_path() -> PathBuf {
|
||||||
let mut path = glib::user_config_dir();
|
let mut path = Settings::default_app_folder();
|
||||||
path.push(config::APP_ID);
|
Settings::create_path_if_not(&path);
|
||||||
path.push("gstpipelinestudio.log");
|
path.push("gstpipelinestudio.log");
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn gst_log_level() -> String {
|
||||||
|
let settings = Settings::load_settings();
|
||||||
|
let binding = "0".to_string();
|
||||||
|
let level = settings
|
||||||
|
.preferences
|
||||||
|
.get("gst_log_level")
|
||||||
|
.unwrap_or(&binding);
|
||||||
|
level.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_recent_pipeline_description(pipeline: &str) {
|
pub fn set_recent_pipeline_description(pipeline: &str) {
|
||||||
let mut settings = Settings::load_settings();
|
let mut settings = Settings::load_settings();
|
||||||
settings.recent_pipeline = pipeline.to_string();
|
settings.recent_pipeline = pipeline.to_string();
|
||||||
|
@ -105,7 +115,6 @@ impl Settings {
|
||||||
|
|
||||||
// Save the provided settings to the settings path
|
// Save the provided settings to the settings path
|
||||||
pub fn save_settings(settings: &Settings) {
|
pub fn save_settings(settings: &Settings) {
|
||||||
Settings::settings_file_exist();
|
|
||||||
let s = Settings::settings_file_path();
|
let s = Settings::settings_file_path();
|
||||||
if let Err(e) = serde_any::to_file(&s, settings) {
|
if let Err(e) = serde_any::to_file(&s, settings) {
|
||||||
GPS_ERROR!("Error while trying to save file: {} {}", s.display(), e);
|
GPS_ERROR!("Error while trying to save file: {} {}", s.display(), e);
|
||||||
|
|
|
@ -11,17 +11,21 @@
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes" comments="Primary menu entry that opens the graph">_Open</attribute>
|
<attribute name="label" translatable="yes" comments="Primary menu entry that opens the graph">_Open</attribute>
|
||||||
<attribute name="action">app.open</attribute>
|
<attribute name="action">app.open</attribute>
|
||||||
<attribute name="accel"><primary>n</attribute>
|
<attribute name="accel"><primary>o</attribute>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes" comments="Primary menu entry that opens a pipeline">_Open pipeline</attribute>
|
<attribute name="label" translatable="yes" comments="Primary menu entry that opens a pipeline">_Open pipeline</attribute>
|
||||||
<attribute name="action">app.open_pipeline</attribute>
|
<attribute name="action">app.open_pipeline</attribute>
|
||||||
<attribute name="accel"><primary>p</attribute>
|
<attribute name="accel"><primary>p</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes" comments="Primary menu entry that saves the graph">_Save</attribute>
|
||||||
|
<attribute name="action">app.save</attribute>
|
||||||
|
<attribute name="accel"><primary>s</attribute>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes" comments="Primary menu entry that saves the graph">_Save As</attribute>
|
<attribute name="label" translatable="yes" comments="Primary menu entry that saves the graph">_Save As</attribute>
|
||||||
<attribute name="action">app.save_as</attribute>
|
<attribute name="action">app.save_as</attribute>
|
||||||
<attribute name="accel"><primary>n</attribute>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes" comments="Primary menu entry that open the preferences">_Preferences</attribute>
|
<attribute name="label" translatable="yes" comments="Primary menu entry that open the preferences">_Preferences</attribute>
|
||||||
|
@ -48,6 +52,10 @@
|
||||||
</menu>
|
</menu>
|
||||||
<menu id="graph_menu">
|
<menu id="graph_menu">
|
||||||
<section>
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes" comments="graph menu entry clear graph">_Clear graph</attribute>
|
||||||
|
<attribute name="action">app.graph.clear</attribute>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes" comments="graph menu entry check graph">_Check graph</attribute>
|
<attribute name="label" translatable="yes" comments="graph menu entry check graph">_Check graph</attribute>
|
||||||
<attribute name="action">app.graph.check</attribute>
|
<attribute name="action">app.graph.check</attribute>
|
||||||
|
@ -201,24 +209,63 @@
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<property name="position">400</property>
|
<property name="position">400</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkScrolledWindow" id="drawing_area-window">
|
<object class="GtkNotebook" id="graphbook">
|
||||||
<property name="hexpand">True</property>
|
|
||||||
<property name="vexpand">True</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkViewport" id="drawing_area">
|
|
||||||
<child>
|
<child>
|
||||||
<placeholder/>
|
<placeholder/>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkNotebook" id="notebook-debug">
|
||||||
|
<child>
|
||||||
|
<object class="GtkNotebookPage">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkTreeView" id="treeview-app-logger"/>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<property name="tab">
|
||||||
|
<object class="GtkLabel" id="label-app-logger">
|
||||||
|
<property name="label" translatable="1">App</property>
|
||||||
|
</object>
|
||||||
</property>
|
</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
<object class="GtkNotebookPage">
|
||||||
|
<property name="child">
|
||||||
<object class="GtkScrolledWindow">
|
<object class="GtkScrolledWindow">
|
||||||
<property name="child">
|
<property name="child">
|
||||||
<object class="GtkTreeView" id="treeview-logger"/>
|
<object class="GtkTreeView" id="treeview-gst-logger"/>
|
||||||
</property>
|
</property>
|
||||||
</object>
|
</object>
|
||||||
|
</property>
|
||||||
|
<property name="tab">
|
||||||
|
<object class="GtkLabel" id="label-gst-logger">
|
||||||
|
<property name="label" translatable="1">GST</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkNotebookPage">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkTreeView" id="treeview-msg-logger"/>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<property name="tab">
|
||||||
|
<object class="GtkLabel" id="label-messages-logger">
|
||||||
|
<property name="label" translatable="1">Messages</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::app::GPSApp;
|
use crate::app::GPSApp;
|
||||||
|
use crate::logger;
|
||||||
use crate::ui::treeview;
|
use crate::ui::treeview;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{gio, glib};
|
use gtk::{gio, glib};
|
||||||
|
@ -18,18 +19,37 @@ fn reset_logger_list(logger_list: &TreeView) {
|
||||||
String::static_type(),
|
String::static_type(),
|
||||||
String::static_type(),
|
String::static_type(),
|
||||||
String::static_type(),
|
String::static_type(),
|
||||||
|
String::static_type(),
|
||||||
|
String::static_type(),
|
||||||
]);
|
]);
|
||||||
logger_list.set_model(Some(&model));
|
logger_list.set_model(Some(&model));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_logger_list(app: &GPSApp) {
|
pub fn setup_logger_list(app: &GPSApp, logger_name: &str, log_type: logger::LogType) {
|
||||||
treeview::add_column_to_treeview(app, "treeview-logger", "TIME", 0, false);
|
match log_type {
|
||||||
treeview::add_column_to_treeview(app, "treeview-logger", "LEVEL", 1, false);
|
logger::LogType::App => {
|
||||||
treeview::add_column_to_treeview(app, "treeview-logger", "LOG", 2, true);
|
treeview::add_column_to_treeview(app, logger_name, "TIME", 0, false);
|
||||||
|
treeview::add_column_to_treeview(app, logger_name, "LEVEL", 1, false);
|
||||||
|
treeview::add_column_to_treeview(app, logger_name, "LOG", 2, true);
|
||||||
|
}
|
||||||
|
logger::LogType::Gst => {
|
||||||
|
treeview::add_column_to_treeview(app, logger_name, "TIME", 0, false);
|
||||||
|
treeview::add_column_to_treeview(app, logger_name, "LEVEL", 1, false);
|
||||||
|
treeview::add_column_to_treeview(app, logger_name, "CATEGORY", 2, false);
|
||||||
|
treeview::add_column_to_treeview(app, logger_name, "FILE", 3, false);
|
||||||
|
treeview::add_column_to_treeview(app, logger_name, "LOG", 4, true);
|
||||||
|
}
|
||||||
|
logger::LogType::Message => {
|
||||||
|
treeview::add_column_to_treeview(app, logger_name, "TIME", 0, false);
|
||||||
|
treeview::add_column_to_treeview(app, logger_name, "LEVEL", 1, false);
|
||||||
|
treeview::add_column_to_treeview(app, logger_name, "LOG", 2, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let logger_list: TreeView = app
|
let logger_list: TreeView = app
|
||||||
.builder
|
.builder
|
||||||
.object("treeview-logger")
|
.object(logger_name)
|
||||||
.expect("Couldn't get treeview-logger");
|
.expect("Couldn't get treeview-app-logger");
|
||||||
reset_logger_list(&logger_list);
|
reset_logger_list(&logger_list);
|
||||||
|
|
||||||
let gesture = gtk::GestureClick::new();
|
let gesture = gtk::GestureClick::new();
|
||||||
|
@ -59,17 +79,45 @@ pub fn setup_logger_list(app: &GPSApp) {
|
||||||
logger_list.add_controller(gesture);
|
logger_list.add_controller(gesture);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_to_logger_list(app: &GPSApp, log_entry: &str) {
|
fn log_tree_id_from_log_type(log_type: logger::LogType) -> String {
|
||||||
|
match log_type {
|
||||||
|
logger::LogType::App => String::from("treeview-app-logger"),
|
||||||
|
logger::LogType::Gst => String::from("treeview-gst-logger"),
|
||||||
|
logger::LogType::Message => String::from("treeview-msg-logger"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_to_logger_list(app: &GPSApp, log_type: logger::LogType, log_entry: &str) {
|
||||||
|
let log_tree_name = log_tree_id_from_log_type(log_type.clone());
|
||||||
let logger_list: TreeView = app
|
let logger_list: TreeView = app
|
||||||
.builder
|
.builder
|
||||||
.object("treeview-logger")
|
.object(log_tree_name.as_str())
|
||||||
.expect("Couldn't get treeview-logger");
|
.expect("Couldn't get treeview");
|
||||||
if let Some(model) = logger_list.model() {
|
if let Some(model) = logger_list.model() {
|
||||||
let list_store = model
|
let list_store = model
|
||||||
.dynamic_cast::<ListStore>()
|
.dynamic_cast::<ListStore>()
|
||||||
.expect("Could not cast to ListStore");
|
.expect("Could not cast to ListStore");
|
||||||
|
if log_type == logger::LogType::Gst {
|
||||||
|
let log: Vec<&str> = log_entry.splitn(5, '\t').collect();
|
||||||
|
list_store.insert_with_values(
|
||||||
|
Some(0),
|
||||||
|
&[
|
||||||
|
(0, &log[0]),
|
||||||
|
(1, &log[1]),
|
||||||
|
(2, &log[2]),
|
||||||
|
(3, &log[3]),
|
||||||
|
(4, &log[4]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
let log: Vec<&str> = log_entry.splitn(3, ' ').collect();
|
let log: Vec<&str> = log_entry.splitn(3, ' ').collect();
|
||||||
list_store.insert_with_values(Some(0), &[(0, &log[0]), (1, &log[1]), (2, &log[2])]);
|
let mut indexed_vec: Vec<(u32, &dyn ToValue)> = Vec::new();
|
||||||
|
|
||||||
|
for (index, item) in log.iter().enumerate() {
|
||||||
|
indexed_vec.push((index as u32, item));
|
||||||
|
}
|
||||||
|
list_store.insert_with_values(Some(0), &indexed_vec);
|
||||||
|
}
|
||||||
// Scroll to the first element.
|
// Scroll to the first element.
|
||||||
if let Some(model) = logger_list.model() {
|
if let Some(model) = logger_list.model() {
|
||||||
if let Some(iter) = model.iter_first() {
|
if let Some(iter) = model.iter_first() {
|
||||||
|
|
|
@ -82,5 +82,16 @@ pub fn display_settings(app: &GPSApp) {
|
||||||
dialog.close();
|
dialog.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let widget = gtk::Entry::new();
|
||||||
|
widget.set_text(settings::Settings::gst_log_level().as_str());
|
||||||
|
widget.connect_changed(glib::clone!(@weak widget => move |c| {
|
||||||
|
let mut settings = settings::Settings::load_settings();
|
||||||
|
settings.preferences.insert("gst_log_level".to_string(), c.text().to_string());
|
||||||
|
settings::Settings::save_settings(&settings);
|
||||||
|
}));
|
||||||
|
let widget = widget
|
||||||
|
.dynamic_cast::<gtk::Widget>()
|
||||||
|
.expect("Should be a widget");
|
||||||
|
add_settings_widget(&grid, "GST Log level", &widget, 2);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
use crate::app::GPSApp;
|
use crate::app::GPSApp;
|
||||||
use crate::common;
|
use crate::common;
|
||||||
use crate::gps as GPS;
|
use crate::gps as GPS;
|
||||||
|
use crate::graphbook;
|
||||||
use crate::logger;
|
use crate::logger;
|
||||||
use crate::ui as GPSUI;
|
use crate::ui as GPSUI;
|
||||||
use crate::{GPS_INFO, GPS_TRACE};
|
use crate::{GPS_INFO, GPS_TRACE};
|
||||||
|
@ -56,6 +57,7 @@ pub fn property_to_widget<F: Fn(String, String) + 'static>(
|
||||||
glib::ParamSpecInt64::static_type(),
|
glib::ParamSpecInt64::static_type(),
|
||||||
glib::ParamSpecUInt64::static_type(),
|
glib::ParamSpecUInt64::static_type(),
|
||||||
glib::ParamSpecString::static_type(),
|
glib::ParamSpecString::static_type(),
|
||||||
|
glib::ParamSpecFloat::static_type(),
|
||||||
]
|
]
|
||||||
.contains(&t) =>
|
.contains(&t) =>
|
||||||
{
|
{
|
||||||
|
@ -112,7 +114,7 @@ pub fn property_to_widget<F: Fn(String, String) + 'static>(
|
||||||
let param = param
|
let param = param
|
||||||
.clone()
|
.clone()
|
||||||
.downcast::<glib::ParamSpecFlags>()
|
.downcast::<glib::ParamSpecFlags>()
|
||||||
.expect("Should be a ParamSpecEnum");
|
.expect("Should be a ParamSpecFlags");
|
||||||
let flags = param.flags_class();
|
let flags = param.flags_class();
|
||||||
for value in flags.values() {
|
for value in flags.values() {
|
||||||
combo.append_text(&format!(
|
combo.append_text(&format!(
|
||||||
|
@ -300,7 +302,10 @@ pub fn display_pipeline_details(app: &GPSApp) {
|
||||||
grid.set_row_spacing(4);
|
grid.set_row_spacing(4);
|
||||||
grid.set_margin_bottom(12);
|
grid.set_margin_bottom(12);
|
||||||
|
|
||||||
if let Some(elements) = app.player.borrow().pipeline_elements() {
|
if let Some(elements) = graphbook::current_graphtab(app)
|
||||||
|
.player()
|
||||||
|
.pipeline_elements()
|
||||||
|
{
|
||||||
let elements_list = elements.join(" ");
|
let elements_list = elements.join(" ");
|
||||||
let label = gtk::Label::builder()
|
let label = gtk::Label::builder()
|
||||||
.label(format!("{} elements:", elements.len()))
|
.label(format!("{} elements:", elements.len()))
|
||||||
|
|
8
subprojects/gstreamer-1.0.wrap
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[wrap-git]
|
||||||
|
directory=gstreamer-1.0
|
||||||
|
url=https://gitlab.freedesktop.org/gstreamer/gstreamer.git
|
||||||
|
push-url=git@gitlab.freedesktop.org:gstreamer/gstreamer.git
|
||||||
|
revision=1.22
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
dependency_names = gstreamer-1.0, gstreamer-base-1.0, gstreamer-sys-1.0, gstreamer-plugins-bad-1.0, gstreamer-video-1.0, gstreamer-player-1.0
|
9
subprojects/gtk.wrap
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[wrap-git]
|
||||||
|
directory=gtk
|
||||||
|
url=https://gitlab.gnome.org/GNOME/gtk.git
|
||||||
|
push-url=ssh://git@gitlab.gnome.org:GNOME/gtk.git
|
||||||
|
revision=4.8.2
|
||||||
|
depth=1
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
dependency_names=gtk4
|