mirror of
https://gitlab.com/marcandre.lureau/qemu-display.git
synced 2024-12-22 05:35:20 +00:00
Remove gtk4
This commit is contained in:
parent
5906e0d045
commit
1906db2a3c
@ -1,4 +1,2 @@
|
|||||||
paths = ['../gtk4-rs/gtk4']
|
|
||||||
|
|
||||||
[alias]
|
[alias]
|
||||||
xtask = "run --package xtask --"
|
xtask = "run --package xtask --"
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
members = [
|
members = [
|
||||||
"keycodemap",
|
"keycodemap",
|
||||||
"qemu-display-listener",
|
"qemu-display-listener",
|
||||||
"qemu-gtk4",
|
|
||||||
"qemu-vnc",
|
"qemu-vnc",
|
||||||
"xtask",
|
"xtask",
|
||||||
]
|
]
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
root = true
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
charset = utf-8
|
|
||||||
|
|
||||||
[*.{build,yml,ui,yaml}]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
[*.{json,py}]
|
|
||||||
indent_size = 4
|
|
||||||
|
|
19
qemu-gtk4/.github/workflows/ci.yml
vendored
19
qemu-gtk4/.github/workflows/ci.yml
vendored
@ -1,19 +0,0 @@
|
|||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
|
||||||
name: CI
|
|
||||||
jobs:
|
|
||||||
flatpak:
|
|
||||||
name: "Flatpak"
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
container:
|
|
||||||
image: bilelmoussaoui/flatpak-github-actions:gnome-3.38
|
|
||||||
options: --privileged
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: bilelmoussaoui/flatpak-github-actions@v2
|
|
||||||
with:
|
|
||||||
bundle: "qemu-gtk4.flatpak"
|
|
||||||
manifest-path: "build-aux/org.qemu.gtk4.Devel.json"
|
|
||||||
run-tests: "true"
|
|
10
qemu-gtk4/.gitignore
vendored
10
qemu-gtk4/.gitignore
vendored
@ -1,10 +0,0 @@
|
|||||||
target/
|
|
||||||
build/
|
|
||||||
_build/
|
|
||||||
builddir/
|
|
||||||
build-aux/app
|
|
||||||
build-aux/.flatpak-builder/
|
|
||||||
src/config.rs
|
|
||||||
*.ui.in~
|
|
||||||
*.ui~
|
|
||||||
.flatpak/
|
|
@ -1,39 +0,0 @@
|
|||||||
stages:
|
|
||||||
- check
|
|
||||||
- test
|
|
||||||
|
|
||||||
flatpak:
|
|
||||||
image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/rust_bundle:3.38'
|
|
||||||
stage: test
|
|
||||||
tags:
|
|
||||||
- flatpak
|
|
||||||
variables:
|
|
||||||
BUNDLE: "qemu-gtk4-nightly.flatpak"
|
|
||||||
MANIFEST_PATH: "build-aux/org.qemu.gtk4.Devel.json"
|
|
||||||
FLATPAK_MODULE: "qemu-gtk4"
|
|
||||||
APP_ID: "org.qemu.gtk4.Devel"
|
|
||||||
RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo"
|
|
||||||
script:
|
|
||||||
- >
|
|
||||||
xvfb-run -a -s "-screen 0 1024x768x24"
|
|
||||||
flatpak-builder --keep-build-dirs --user --disable-rofiles-fuse flatpak_app --repo=repo ${BRANCH:+--default-branch=$BRANCH} ${MANIFEST_PATH}
|
|
||||||
- flatpak build-bundle repo ${BUNDLE} --runtime-repo=${RUNTIME_REPO} ${APP_ID} ${BRANCH}
|
|
||||||
artifacts:
|
|
||||||
name: 'Flatpak artifacts'
|
|
||||||
expose_as: 'Get Flatpak bundle here'
|
|
||||||
when: 'always'
|
|
||||||
paths:
|
|
||||||
- "${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
|
|
||||||
|
|
||||||
# Configure and run rustfmt
|
|
||||||
# Exits and builds fails if on bad format
|
|
||||||
rustfmt:
|
|
||||||
image: "rust:slim"
|
|
||||||
script:
|
|
||||||
- rustup component add rustfmt
|
|
||||||
- rustc -Vv && cargo -Vv
|
|
||||||
- cargo fmt --version
|
|
||||||
- cargo fmt --all -- --color=always --check
|
|
@ -1,37 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "qemu-gtk4"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["QEMU developpers <qemu-devel@nongnu.org>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
qemu-display-listener = { path = "../qemu-display-listener", features = ["glib"] }
|
|
||||||
keycodemap = { path = "../keycodemap" }
|
|
||||||
zbus = { version = "2.0.0-beta" }
|
|
||||||
log = "0.4"
|
|
||||||
pretty_env_logger = "0.4"
|
|
||||||
gettext-rs = { version = "0.5", features = ["gettext-system"] }
|
|
||||||
gtk-macros = "0.2"
|
|
||||||
once_cell = "1.5"
|
|
||||||
khronos-egl = { version = "3.0.0", features = ["dynamic"] }
|
|
||||||
libloading = "0.6"
|
|
||||||
gl = "0.14.0"
|
|
||||||
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
|
|
||||||
derivative = "2.2.0"
|
|
||||||
gst = { package = "gstreamer", version = "0.16.7" }
|
|
||||||
gst-app = { package = "gstreamer-app", version = "0.16.5" }
|
|
||||||
gst-audio = { package = "gstreamer-audio", version = "0.16.5" }
|
|
||||||
wayland-protocols = { version = "0.28.5", features = ["unstable_protocols", "client"] }
|
|
||||||
wayland-client = "0.28.5"
|
|
||||||
|
|
||||||
[dependencies.gtk]
|
|
||||||
package = "gtk4"
|
|
||||||
git = "https://github.com/gtk-rs/gtk4-rs"
|
|
||||||
|
|
||||||
[dependencies.gdk-wl]
|
|
||||||
package = "gdk4-wayland"
|
|
||||||
git = "https://github.com/gtk-rs/gtk4-rs"
|
|
||||||
|
|
||||||
[dependencies.gdk-x11]
|
|
||||||
package = "gdk4-x11"
|
|
||||||
git = "https://github.com/gtk-rs/gtk4-rs"
|
|
@ -1,7 +0,0 @@
|
|||||||
Copyright © 2019, QEMU developpers
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders X be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
|
|
||||||
Except as contained in this notice, the name of the QEMU developpers shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the QEMU developpers.
|
|
@ -1,5 +0,0 @@
|
|||||||
# QEMU Gtk4
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
Based on [GTK Rust template](https://gitlab.gnome.org/bilelmoussaoui/gtk-rust-template)
|
|
@ -1,20 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
export MESON_BUILD_ROOT="$1"
|
|
||||||
export MESON_SOURCE_ROOT="$2"
|
|
||||||
export CARGO_TARGET_DIR="$MESON_BUILD_ROOT"/target
|
|
||||||
export CARGO_HOME="$CARGO_TARGET_DIR"/cargo-home
|
|
||||||
|
|
||||||
if [[ $4 = "Devel" ]]
|
|
||||||
then
|
|
||||||
echo "DEBUG MODE"
|
|
||||||
cargo build --manifest-path \
|
|
||||||
"$MESON_SOURCE_ROOT"/Cargo.toml && \
|
|
||||||
cp "$CARGO_TARGET_DIR"/debug/$5 $3
|
|
||||||
else
|
|
||||||
echo "RELEASE MODE"
|
|
||||||
cargo build --manifest-path \
|
|
||||||
"$MESON_SOURCE_ROOT"/Cargo.toml --release && \
|
|
||||||
cp "$CARGO_TARGET_DIR"/release/$5 $3
|
|
||||||
fi
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
export DIST="$1"
|
|
||||||
export SOURCE_ROOT="$2"
|
|
||||||
|
|
||||||
cd "$SOURCE_ROOT"
|
|
||||||
mkdir "$DIST"/.cargo
|
|
||||||
cargo vendor | sed 's/^directory = ".*"/directory = "vendor"/g' > $DIST/.cargo/config
|
|
||||||
# Move vendor into dist tarball directory
|
|
||||||
mv vendor "$DIST"
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from os import environ, path
|
|
||||||
from subprocess import call
|
|
||||||
|
|
||||||
if not environ.get('DESTDIR', ''):
|
|
||||||
PREFIX = environ.get('MESON_INSTALL_PREFIX', '/usr/local')
|
|
||||||
DATA_DIR = path.join(PREFIX, 'share')
|
|
||||||
print('Updating icon cache...')
|
|
||||||
call(['gtk-update-icon-cache', '-qtf', path.join(DATA_DIR, 'icons/hicolor')])
|
|
||||||
print("Compiling new schemas...")
|
|
||||||
call(["glib-compile-schemas", path.join(DATA_DIR, 'glib-2.0/schemas')])
|
|
||||||
print("Updating desktop database...")
|
|
||||||
call(["update-desktop-database", path.join(DATA_DIR, 'applications')])
|
|
@ -1,104 +0,0 @@
|
|||||||
{
|
|
||||||
"app-id": "org.qemu.gtk4.Devel",
|
|
||||||
"runtime": "org.gnome.Platform",
|
|
||||||
"runtime-version": "3.38",
|
|
||||||
"sdk": "org.gnome.Sdk",
|
|
||||||
"sdk-extensions": ["org.freedesktop.Sdk.Extension.rust-stable"],
|
|
||||||
"command": "qemu-gtk4",
|
|
||||||
"finish-args" : [
|
|
||||||
"--socket=fallback-x11",
|
|
||||||
"--socket=wayland",
|
|
||||||
"--device=dri",
|
|
||||||
"--talk-name=org.a11y.Bus",
|
|
||||||
"--env=RUST_LOG=qemu-gtk4=debug",
|
|
||||||
"--env=G_MESSAGES_DEBUG=none"
|
|
||||||
],
|
|
||||||
"build-options" : {
|
|
||||||
"append-path" : "/usr/lib/sdk/rust-stable/bin",
|
|
||||||
"build-args" : [
|
|
||||||
"--share=network"
|
|
||||||
],
|
|
||||||
"test-args": [
|
|
||||||
"--socket=x11",
|
|
||||||
"--share=network"
|
|
||||||
],
|
|
||||||
"env" : {
|
|
||||||
"CARGO_HOME" : "/run/build/qemu-gtk4/cargo",
|
|
||||||
"RUST_BACKTRACE": "1",
|
|
||||||
"RUSTFLAGS": "-L=/app/lib"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"modules": [
|
|
||||||
{
|
|
||||||
"name": "gtk4",
|
|
||||||
"buildsystem": "meson",
|
|
||||||
"config-opts": [
|
|
||||||
"-Ddemos=false",
|
|
||||||
"-Dbuild-examples=false",
|
|
||||||
"-Dbuild-tests=false"
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "https://download.gnome.org/sources/gtk/4.0/gtk-4.0.3.tar.xz",
|
|
||||||
"sha256": "d7c9893725790b50bd9a3bb278856d9d543b44b6b9b951d7b60e7bdecc131890"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
{
|
|
||||||
"name": "pango",
|
|
||||||
"buildsystem": "meson",
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "https://download.gnome.org/sources/pango/1.48/pango-1.48.1.tar.xz",
|
|
||||||
"sha256": "08c2d550a96559f15fb317d7167b96df57ef743fef946f4e274bd8b6f2918058"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "libsass",
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "https://github.com/sass/libsass/archive/3.6.4.tar.gz",
|
|
||||||
"sha256": "f9484d9a6df60576e791566eab2f757a97fd414fce01dd41fc0a693ea5db2889"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "script",
|
|
||||||
"dest-filename": "autogen.sh",
|
|
||||||
"commands": ["autoreconf -si"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sassc",
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "https://github.com/sass/sassc/archive/3.6.1.tar.gz",
|
|
||||||
"sha256": "8cee391c49a102b4464f86fc40c4ceac3a2ada52a89c4c933d8348e3e4542a60"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "script",
|
|
||||||
"dest-filename": "autogen.sh",
|
|
||||||
"commands": ["autoreconf -si"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qemu-gtk4",
|
|
||||||
"buildsystem": "meson",
|
|
||||||
"run-tests": true,
|
|
||||||
"config-opts": ["-Dprofile=development"],
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "dir",
|
|
||||||
"path": "../"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
install_data(
|
|
||||||
'@0@.svg'.format(application_id),
|
|
||||||
install_dir: iconsdir / 'hicolor' / 'scalable' / 'apps'
|
|
||||||
)
|
|
||||||
|
|
||||||
install_data(
|
|
||||||
'@0@-symbolic.svg'.format(base_id),
|
|
||||||
install_dir: iconsdir / 'hicolor' / 'symbolic' / 'apps',
|
|
||||||
rename: '@0@-symbolic.svg'.format(application_id)
|
|
||||||
)
|
|
@ -1,59 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
|
|
||||||
<defs>
|
|
||||||
<filter id="alpha" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
|
|
||||||
<feColorMatrix type="matrix" in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
<mask id="mask0">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="16" height="16" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip1">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10818" clip-path="url(#clip1)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 123.503906 236 C 123.503906 268.863281 96.863281 295.503906 64 295.503906 C 31.136719 295.503906 4.496094 268.863281 4.496094 236 C 4.496094 203.136719 31.136719 176.496094 64 176.496094 C 96.863281 176.496094 123.503906 203.136719 123.503906 236 Z M 123.503906 236 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask1">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="16" height="16" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip2">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10821" clip-path="url(#clip2)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 29.195312 180.496094 L 98.804688 180.496094 C 103.609375 180.496094 107.503906 184.046875 107.503906 188.425781 L 107.503906 283.574219 C 107.503906 287.953125 103.609375 291.503906 98.804688 291.503906 L 29.195312 291.503906 C 24.390625 291.503906 20.496094 287.953125 20.496094 283.574219 L 20.496094 188.425781 C 20.496094 184.046875 24.390625 180.496094 29.195312 180.496094 Z M 29.195312 180.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask2">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="16" height="16" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip3">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10824" clip-path="url(#clip3)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 20.417969 184.496094 L 107.582031 184.496094 C 111.957031 184.496094 115.503906 188.042969 115.503906 192.417969 L 115.503906 279.582031 C 115.503906 283.957031 111.957031 287.503906 107.582031 287.503906 L 20.417969 287.503906 C 16.042969 287.503906 12.496094 283.957031 12.496094 279.582031 L 12.496094 192.417969 C 12.496094 188.042969 16.042969 184.496094 20.417969 184.496094 Z M 20.417969 184.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask3">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="16" height="16" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip4">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10827" clip-path="url(#clip4)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 16.425781 200.496094 L 111.574219 200.496094 C 115.953125 200.496094 119.503906 204.390625 119.503906 209.195312 L 119.503906 278.804688 C 119.503906 283.609375 115.953125 287.503906 111.574219 287.503906 L 16.425781 287.503906 C 12.046875 287.503906 8.496094 283.609375 8.496094 278.804688 L 8.496094 209.195312 C 8.496094 204.390625 12.046875 200.496094 16.425781 200.496094 Z M 16.425781 200.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
</defs>
|
|
||||||
<g id="surface10764">
|
|
||||||
<rect x="0" y="0" width="16" height="16" style="fill:rgb(94.117647%,94.117647%,94.117647%);fill-opacity:1;stroke:none;"/>
|
|
||||||
<use xlink:href="#surface10818" transform="matrix(1,0,0,1,-168,-16)" mask="url(#mask0)"/>
|
|
||||||
<use xlink:href="#surface10821" transform="matrix(1,0,0,1,-168,-16)" mask="url(#mask1)"/>
|
|
||||||
<use xlink:href="#surface10824" transform="matrix(1,0,0,1,-168,-16)" mask="url(#mask2)"/>
|
|
||||||
<use xlink:href="#surface10827" transform="matrix(1,0,0,1,-168,-16)" mask="url(#mask3)"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 4.3 KiB |
@ -1,147 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128px" height="128px" viewBox="0 0 128 128" version="1.1">
|
|
||||||
<defs>
|
|
||||||
<filter id="alpha" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
|
|
||||||
<feColorMatrix type="matrix" in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
<mask id="mask0">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip1">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10726" clip-path="url(#clip1)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 123.503906 236 C 123.503906 268.863281 96.863281 295.503906 64 295.503906 C 31.136719 295.503906 4.496094 268.863281 4.496094 236 C 4.496094 203.136719 31.136719 176.496094 64 176.496094 C 96.863281 176.496094 123.503906 203.136719 123.503906 236 Z M 123.503906 236 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask1">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip2">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10729" clip-path="url(#clip2)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 29.195312 180.496094 L 98.804688 180.496094 C 103.609375 180.496094 107.503906 184.046875 107.503906 188.425781 L 107.503906 283.574219 C 107.503906 287.953125 103.609375 291.503906 98.804688 291.503906 L 29.195312 291.503906 C 24.390625 291.503906 20.496094 287.953125 20.496094 283.574219 L 20.496094 188.425781 C 20.496094 184.046875 24.390625 180.496094 29.195312 180.496094 Z M 29.195312 180.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask2">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip3">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10732" clip-path="url(#clip3)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 20.417969 184.496094 L 107.582031 184.496094 C 111.957031 184.496094 115.503906 188.042969 115.503906 192.417969 L 115.503906 279.582031 C 115.503906 283.957031 111.957031 287.503906 107.582031 287.503906 L 20.417969 287.503906 C 16.042969 287.503906 12.496094 283.957031 12.496094 279.582031 L 12.496094 192.417969 C 12.496094 188.042969 16.042969 184.496094 20.417969 184.496094 Z M 20.417969 184.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask3">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip4">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10735" clip-path="url(#clip4)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 16.425781 200.496094 L 111.574219 200.496094 C 115.953125 200.496094 119.503906 204.390625 119.503906 209.195312 L 119.503906 278.804688 C 119.503906 283.609375 115.953125 287.503906 111.574219 287.503906 L 16.425781 287.503906 C 12.046875 287.503906 8.496094 283.609375 8.496094 278.804688 L 8.496094 209.195312 C 8.496094 204.390625 12.046875 200.496094 16.425781 200.496094 Z M 16.425781 200.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask5">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip7">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10726" clip-path="url(#clip7)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 123.503906 236 C 123.503906 268.863281 96.863281 295.503906 64 295.503906 C 31.136719 295.503906 4.496094 268.863281 4.496094 236 C 4.496094 203.136719 31.136719 176.496094 64 176.496094 C 96.863281 176.496094 123.503906 203.136719 123.503906 236 Z M 123.503906 236 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask6">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip8">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10729" clip-path="url(#clip8)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 29.195312 180.496094 L 98.804688 180.496094 C 103.609375 180.496094 107.503906 184.046875 107.503906 188.425781 L 107.503906 283.574219 C 107.503906 287.953125 103.609375 291.503906 98.804688 291.503906 L 29.195312 291.503906 C 24.390625 291.503906 20.496094 287.953125 20.496094 283.574219 L 20.496094 188.425781 C 20.496094 184.046875 24.390625 180.496094 29.195312 180.496094 Z M 29.195312 180.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask7">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip9">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10732" clip-path="url(#clip9)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 20.417969 184.496094 L 107.582031 184.496094 C 111.957031 184.496094 115.503906 188.042969 115.503906 192.417969 L 115.503906 279.582031 C 115.503906 283.957031 111.957031 287.503906 107.582031 287.503906 L 20.417969 287.503906 C 16.042969 287.503906 12.496094 283.957031 12.496094 279.582031 L 12.496094 192.417969 C 12.496094 188.042969 16.042969 184.496094 20.417969 184.496094 Z M 20.417969 184.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask8">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip10">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10735" clip-path="url(#clip10)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 16.425781 200.496094 L 111.574219 200.496094 C 115.953125 200.496094 119.503906 204.390625 119.503906 209.195312 L 119.503906 278.804688 C 119.503906 283.609375 115.953125 287.503906 111.574219 287.503906 L 16.425781 287.503906 C 12.046875 287.503906 8.496094 283.609375 8.496094 278.804688 L 8.496094 209.195312 C 8.496094 204.390625 12.046875 200.496094 16.425781 200.496094 Z M 16.425781 200.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<clipPath id="clip6">
|
|
||||||
<rect x="0" y="0" width="128" height="128"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10750" clip-path="url(#clip6)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(94.117647%,94.117647%,94.117647%);fill-opacity:1;stroke:none;"/>
|
|
||||||
<use xlink:href="#surface10726" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask5)"/>
|
|
||||||
<use xlink:href="#surface10729" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask6)"/>
|
|
||||||
<use xlink:href="#surface10732" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask7)"/>
|
|
||||||
<use xlink:href="#surface10735" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask8)"/>
|
|
||||||
<path style="fill:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(38.431373%,62.7451%,91.764706%);stroke-opacity:1;stroke-miterlimit:4;" d="M 0 289 L 128 289 " transform="matrix(1,0,0,1,0,-172)"/>
|
|
||||||
</g>
|
|
||||||
<clipPath id="clip5">
|
|
||||||
<rect x="0" y="0" width="128" height="128"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10753" clip-path="url(#clip5)" filter="url(#alpha)">
|
|
||||||
<use xlink:href="#surface10750"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask4">
|
|
||||||
<use xlink:href="#surface10753"/>
|
|
||||||
</mask>
|
|
||||||
<mask id="mask9">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.8;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<linearGradient id="linear0" gradientUnits="userSpaceOnUse" x1="300" y1="235" x2="428" y2="235" gradientTransform="matrix(0.000000000000000023,0.37,-0.98462,0.00000000000000006,295.38501,-30.360001)">
|
|
||||||
<stop offset="0" style="stop-color:rgb(97.647059%,94.117647%,41.960785%);stop-opacity:1;"/>
|
|
||||||
<stop offset="1" style="stop-color:rgb(96.078432%,76.078433%,6.666667%);stop-opacity:1;"/>
|
|
||||||
</linearGradient>
|
|
||||||
<clipPath id="clip12">
|
|
||||||
<rect x="0" y="0" width="128" height="128"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10747" clip-path="url(#clip12)">
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear0);" d="M 128 80.640625 L 128 128 L 0 128 L 0 80.640625 Z M 128 80.640625 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 13.308594 80.640625 L 60.664062 128 L 81.878906 128 L 34.519531 80.640625 Z M 55.730469 80.640625 L 103.09375 128 L 124.308594 128 L 76.945312 80.640625 Z M 98.160156 80.640625 L 128 110.480469 L 128 89.269531 L 119.371094 80.640625 Z M 0 88.546875 L 0 109.761719 L 18.238281 128 L 39.453125 128 Z M 0 88.546875 "/>
|
|
||||||
</g>
|
|
||||||
<clipPath id="clip11">
|
|
||||||
<rect x="0" y="0" width="128" height="128"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10752" clip-path="url(#clip11)">
|
|
||||||
<use xlink:href="#surface10747" mask="url(#mask9)"/>
|
|
||||||
</g>
|
|
||||||
</defs>
|
|
||||||
<g id="surface10672">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(94.117647%,94.117647%,94.117647%);fill-opacity:1;stroke:none;"/>
|
|
||||||
<use xlink:href="#surface10726" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask0)"/>
|
|
||||||
<use xlink:href="#surface10729" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask1)"/>
|
|
||||||
<use xlink:href="#surface10732" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask2)"/>
|
|
||||||
<use xlink:href="#surface10735" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask3)"/>
|
|
||||||
<path style="fill:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(38.431373%,62.7451%,91.764706%);stroke-opacity:1;stroke-miterlimit:4;" d="M 0 289 L 128 289 " transform="matrix(1,0,0,1,0,-172)"/>
|
|
||||||
<use xlink:href="#surface10752" mask="url(#mask4)"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 10 KiB |
@ -1,60 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128px" height="128px" viewBox="0 0 128 128" version="1.1">
|
|
||||||
<defs>
|
|
||||||
<filter id="alpha" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
|
|
||||||
<feColorMatrix type="matrix" in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
<mask id="mask0">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip1">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10632" clip-path="url(#clip1)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 123.503906 236 C 123.503906 268.863281 96.863281 295.503906 64 295.503906 C 31.136719 295.503906 4.496094 268.863281 4.496094 236 C 4.496094 203.136719 31.136719 176.496094 64 176.496094 C 96.863281 176.496094 123.503906 203.136719 123.503906 236 Z M 123.503906 236 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask1">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip2">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10635" clip-path="url(#clip2)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 29.195312 180.496094 L 98.804688 180.496094 C 103.609375 180.496094 107.503906 184.046875 107.503906 188.425781 L 107.503906 283.574219 C 107.503906 287.953125 103.609375 291.503906 98.804688 291.503906 L 29.195312 291.503906 C 24.390625 291.503906 20.496094 287.953125 20.496094 283.574219 L 20.496094 188.425781 C 20.496094 184.046875 24.390625 180.496094 29.195312 180.496094 Z M 29.195312 180.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask2">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip3">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10638" clip-path="url(#clip3)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 20.417969 184.496094 L 107.582031 184.496094 C 111.957031 184.496094 115.503906 188.042969 115.503906 192.417969 L 115.503906 279.582031 C 115.503906 283.957031 111.957031 287.503906 107.582031 287.503906 L 20.417969 287.503906 C 16.042969 287.503906 12.496094 283.957031 12.496094 279.582031 L 12.496094 192.417969 C 12.496094 188.042969 16.042969 184.496094 20.417969 184.496094 Z M 20.417969 184.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
<mask id="mask3">
|
|
||||||
<g filter="url(#alpha)">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<clipPath id="clip4">
|
|
||||||
<rect x="0" y="0" width="192" height="152"/>
|
|
||||||
</clipPath>
|
|
||||||
<g id="surface10641" clip-path="url(#clip4)">
|
|
||||||
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 16.425781 200.496094 L 111.574219 200.496094 C 115.953125 200.496094 119.503906 204.390625 119.503906 209.195312 L 119.503906 278.804688 C 119.503906 283.609375 115.953125 287.503906 111.574219 287.503906 L 16.425781 287.503906 C 12.046875 287.503906 8.496094 283.609375 8.496094 278.804688 L 8.496094 209.195312 C 8.496094 204.390625 12.046875 200.496094 16.425781 200.496094 Z M 16.425781 200.496094 " transform="matrix(1,0,0,1,8,-156)"/>
|
|
||||||
</g>
|
|
||||||
</defs>
|
|
||||||
<g id="surface10578">
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:rgb(94.117647%,94.117647%,94.117647%);fill-opacity:1;stroke:none;"/>
|
|
||||||
<use xlink:href="#surface10632" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask0)"/>
|
|
||||||
<use xlink:href="#surface10635" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask1)"/>
|
|
||||||
<use xlink:href="#surface10638" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask2)"/>
|
|
||||||
<use xlink:href="#surface10641" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask3)"/>
|
|
||||||
<path style="fill:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(38.431373%,62.7451%,91.764706%);stroke-opacity:1;stroke-miterlimit:4;" d="M 0 289 L 128 289 " transform="matrix(1,0,0,1,0,-172)"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 4.5 KiB |
@ -1,83 +0,0 @@
|
|||||||
subdir('icons')
|
|
||||||
# Desktop file
|
|
||||||
desktop_conf = configuration_data()
|
|
||||||
desktop_conf.set('icon', application_id)
|
|
||||||
desktop_file = i18n.merge_file(
|
|
||||||
type: 'desktop',
|
|
||||||
input: configure_file(
|
|
||||||
input: '@0@.desktop.in.in'.format(base_id),
|
|
||||||
output: '@BASENAME@',
|
|
||||||
configuration: desktop_conf
|
|
||||||
),
|
|
||||||
output: '@0@.desktop'.format(application_id),
|
|
||||||
po_dir: podir,
|
|
||||||
install: true,
|
|
||||||
install_dir: datadir / 'applications'
|
|
||||||
)
|
|
||||||
# Validate Desktop file
|
|
||||||
if desktop_file_validate.found()
|
|
||||||
test(
|
|
||||||
'validate-desktop',
|
|
||||||
desktop_file_validate,
|
|
||||||
args: [
|
|
||||||
desktop_file.full_path()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Appdata
|
|
||||||
appdata_conf = configuration_data()
|
|
||||||
appdata_conf.set('app-id', application_id)
|
|
||||||
appdata_conf.set('gettext-package', gettext_package)
|
|
||||||
appdata_file = i18n.merge_file(
|
|
||||||
input: configure_file(
|
|
||||||
input: '@0@.metainfo.xml.in.in'.format(base_id),
|
|
||||||
output: '@BASENAME@',
|
|
||||||
configuration: appdata_conf
|
|
||||||
),
|
|
||||||
output: '@0@.metainfo.xml'.format(application_id),
|
|
||||||
po_dir: podir,
|
|
||||||
install: true,
|
|
||||||
install_dir: datadir / 'metainfo'
|
|
||||||
)
|
|
||||||
# Validate Appdata
|
|
||||||
if appstream_util.found()
|
|
||||||
test(
|
|
||||||
'validate-appdata', appstream_util,
|
|
||||||
args: [
|
|
||||||
'validate', '--nonet', appdata_file.full_path()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# GSchema
|
|
||||||
gschema_conf = configuration_data()
|
|
||||||
gschema_conf.set('app-id', application_id)
|
|
||||||
gschema_conf.set('gettext-package', gettext_package)
|
|
||||||
configure_file(
|
|
||||||
input: '@0@.gschema.xml.in'.format(base_id),
|
|
||||||
output: '@0@.gschema.xml'.format(application_id),
|
|
||||||
configuration: gschema_conf,
|
|
||||||
install: true,
|
|
||||||
install_dir: datadir / 'glib-2.0' / 'schemas'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validata GSchema
|
|
||||||
if glib_compile_schemas.found()
|
|
||||||
test(
|
|
||||||
'validate-gschema', glib_compile_schemas,
|
|
||||||
args: [
|
|
||||||
'--strict', '--dry-run', meson.current_source_dir()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Resources
|
|
||||||
resources = gnome.compile_resources(
|
|
||||||
'resources',
|
|
||||||
'resources.gresource.xml',
|
|
||||||
gresource_bundle: true,
|
|
||||||
source_dir: meson.current_build_dir(),
|
|
||||||
install: true,
|
|
||||||
install_dir: pkgdatadir,
|
|
||||||
)
|
|
@ -1,11 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Name=QEMU Gtk
|
|
||||||
Comment=A GTK + Rust application boilerplate template
|
|
||||||
Type=Application
|
|
||||||
Exec=qemu-gtk4
|
|
||||||
Terminal=false
|
|
||||||
Categories=GNOME;GTK;
|
|
||||||
Keywords=Gnome;GTK;
|
|
||||||
# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
|
|
||||||
Icon=@icon@
|
|
||||||
StartupNotify=true
|
|
@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<schemalist>
|
|
||||||
<schema path="/org/qemu/gtk4/" id="@app-id@" gettext-domain="@gettext-package@">
|
|
||||||
<key name="window-width" type="i">
|
|
||||||
<default>-1</default>
|
|
||||||
<summary>Default window width</summary>
|
|
||||||
<description>Default window width</description>
|
|
||||||
</key>
|
|
||||||
<key name="window-height" type="i">
|
|
||||||
<default>-1</default>
|
|
||||||
<summary>Default window height</summary>
|
|
||||||
<description>Default window height</description>
|
|
||||||
</key>
|
|
||||||
<key name="is-maximized" type="b">
|
|
||||||
<default>false</default>
|
|
||||||
<summary>Default window maximized behaviour</summary>
|
|
||||||
<description></description>
|
|
||||||
</key>
|
|
||||||
</schema>
|
|
||||||
</schemalist>
|
|
@ -1,30 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- QEMU developpers 2021 <qemu-devel@nongnu.org> -->
|
|
||||||
<component type="desktop-application">
|
|
||||||
<id>@app-id@</id>
|
|
||||||
<metadata_license>CC0</metadata_license>
|
|
||||||
<project_license>GPL-3.0+</project_license>
|
|
||||||
<name>QEMU Gtk</name>
|
|
||||||
<summary>A GTK QEMU display.</summary>
|
|
||||||
<description>
|
|
||||||
<p>GTK application to interact with QEMU display.</p>
|
|
||||||
</description>
|
|
||||||
<url type="homepage">https://gitlab.com/qemu-project/qemu</url>
|
|
||||||
<url type="bugtracker">https://gitlab.com/qemu-project/qemu/issues</url>
|
|
||||||
<content_rating type="oars-1.0" />
|
|
||||||
<releases>
|
|
||||||
<release version="0.0.1" date="2021-01-01" />
|
|
||||||
</releases>
|
|
||||||
<kudos>
|
|
||||||
<!--
|
|
||||||
GNOME Software kudos:
|
|
||||||
https://gitlab.gnome.org/GNOME/gnome-software/blob/master/doc/kudos.md
|
|
||||||
-->
|
|
||||||
<kudo>ModernToolkit</kudo>
|
|
||||||
<kudo>HiDpiIcon</kudo>
|
|
||||||
</kudos>
|
|
||||||
<developer_name>QEMU developpers</developer_name>
|
|
||||||
<update_contact>qemu-devel@nongnu.org</update_contact>
|
|
||||||
<translation type="gettext">@gettext-package@</translation>
|
|
||||||
<launchable type="desktop-id">@app-id@.desktop</launchable>
|
|
||||||
</component>
|
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<gresources>
|
|
||||||
<gresource prefix="/org/qemu/gtk4/">
|
|
||||||
<file compressed="true" preprocess="xml-stripblanks" alias="shortcuts.ui">resources/ui/shortcuts.ui</file>
|
|
||||||
<file compressed="true" preprocess="xml-stripblanks" alias="console.ui">resources/ui/console.ui</file>
|
|
||||||
<file compressed="true" preprocess="xml-stripblanks" alias="window.ui">resources/ui/window.ui</file>
|
|
||||||
|
|
||||||
<file compressed="true" alias="style.css">resources/style.css</file>
|
|
||||||
</gresource>
|
|
||||||
</gresources>
|
|
@ -1,4 +0,0 @@
|
|||||||
.title-header{
|
|
||||||
font-size: 36px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<interface>
|
|
||||||
<template class="QemuConsole" parent="GtkWidget">
|
|
||||||
<property name="layout-manager">
|
|
||||||
<object class="GtkBinLayout"/>
|
|
||||||
</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkNotebook">
|
|
||||||
<property name="show-tabs">0</property>
|
|
||||||
<child>
|
|
||||||
<object class="QemuConsoleArea" id="area"/>
|
|
||||||
</child>
|
|
||||||
<child type="tab">
|
|
||||||
<object class="GtkLabel" id="notebook-tab">
|
|
||||||
<property name="label">Console</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="label">
|
|
||||||
<property name="label" translatable="yes">Hello world2!</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child type="tab">
|
|
||||||
<object class="GtkLabel" id="notebook-tab2">
|
|
||||||
<property name="label">Tab2</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</template>
|
|
||||||
</interface>
|
|
@ -1,30 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<interface>
|
|
||||||
<object class="GtkShortcutsWindow" id="shortcuts">
|
|
||||||
<property name="modal">True</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkShortcutsSection">
|
|
||||||
<property name="section-name">shortcuts</property>
|
|
||||||
<property name="max-height">10</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkShortcutsGroup">
|
|
||||||
<property name="title" translatable="yes" context="shortcut window">General</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkShortcutsShortcut">
|
|
||||||
<property name="title" translatable="yes" context="shortcut window">Show Shortcuts</property>
|
|
||||||
<property name="accelerator"><Primary>question</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkShortcutsShortcut">
|
|
||||||
<property name="title" translatable="yes" context="shortcut window">Quit</property>
|
|
||||||
<property name="accelerator"><Primary>Q</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</interface>
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<interface>
|
|
||||||
<menu id="primary_menu">
|
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">_Preferences</attribute>
|
|
||||||
<attribute name="action">app.preferences</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
|
|
||||||
<attribute name="action">win.show-help-overlay</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">_About GTK QEMU</attribute>
|
|
||||||
<attribute name="action">app.about</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
</menu>
|
|
||||||
<template class="QemuApplicationWindow" parent="GtkApplicationWindow">
|
|
||||||
<property name="default-width">600</property>
|
|
||||||
<property name="default-height">400</property>
|
|
||||||
<child type="titlebar">
|
|
||||||
<object class="GtkHeaderBar" id="headerbar">
|
|
||||||
<child type="end">
|
|
||||||
<object class="GtkMenuButton" id="appmenu_button">
|
|
||||||
<property name="icon-name">open-menu-symbolic</property>
|
|
||||||
<property name="menu-model">primary_menu</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="QemuConsole" id="console">
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</template>
|
|
||||||
</interface>
|
|
@ -1,54 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Source: https://gitlab.gnome.org/GNOME/fractal/blob/master/hooks/pre-commit.hook
|
|
||||||
|
|
||||||
install_rustfmt() {
|
|
||||||
if ! which rustup &> /dev/null; then
|
|
||||||
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
|
||||||
export PATH=$PATH:$HOME/.cargo/bin
|
|
||||||
if ! which rustup &> /dev/null; then
|
|
||||||
echo "Failed to install rustup. Performing the commit without style checking."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! rustup component list|grep rustfmt &> /dev/null; then
|
|
||||||
echo "Installing rustfmt…"
|
|
||||||
rustup component add rustfmt
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! which cargo &> /dev/null || ! cargo fmt --help &> /dev/null; then
|
|
||||||
echo "Unable to check Fractal’s code style, because rustfmt could not be run."
|
|
||||||
|
|
||||||
if [ ! -t 1 ]; then
|
|
||||||
# No input is possible
|
|
||||||
echo "Performing commit."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "y: Install rustfmt via rustup"
|
|
||||||
echo "n: Don't install rustfmt and perform the commit"
|
|
||||||
echo "Q: Don't install rustfmt and abort the commit"
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
read -p "Install rustfmt via rustup? [y/n/Q]: " yn
|
|
||||||
case $yn in
|
|
||||||
[Yy]* ) install_rustfmt; break;;
|
|
||||||
[Nn]* ) echo "Performing commit."; exit 0;;
|
|
||||||
[Qq]* | "" ) echo "Aborting commit."; exit -1;;
|
|
||||||
* ) echo "Invalid input";;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "--Checking style--"
|
|
||||||
cargo fmt --all -- --check
|
|
||||||
if test $? != 0; then
|
|
||||||
echo "--Checking style fail--"
|
|
||||||
echo "Please fix the above issues, either manually or by running: cargo fmt --all"
|
|
||||||
|
|
||||||
exit -1
|
|
||||||
else
|
|
||||||
echo "--Checking style pass--"
|
|
||||||
fi
|
|
@ -1,70 +0,0 @@
|
|||||||
project('qemu-gtk4',
|
|
||||||
'rust',
|
|
||||||
version: '0.0.1',
|
|
||||||
license: 'MIT',
|
|
||||||
meson_version: '>= 0.50')
|
|
||||||
|
|
||||||
i18n = import('i18n')
|
|
||||||
gnome = import('gnome')
|
|
||||||
|
|
||||||
base_id = 'org.qemu.gtk4'
|
|
||||||
|
|
||||||
dependency('glib-2.0', version: '>= 2.66')
|
|
||||||
dependency('gio-2.0', version: '>= 2.66')
|
|
||||||
dependency('gtk4', version: '>= 4.0.0')
|
|
||||||
|
|
||||||
glib_compile_resources = find_program('glib-compile-resources', required: true)
|
|
||||||
glib_compile_schemas = find_program('glib-compile-schemas', required: true)
|
|
||||||
desktop_file_validate = find_program('desktop-file-validate', required: false)
|
|
||||||
appstream_util = find_program('appstream-util', required: false)
|
|
||||||
cargo = find_program('cargo', required: true)
|
|
||||||
cargo_script = find_program('build-aux/cargo.sh')
|
|
||||||
|
|
||||||
version = meson.project_version()
|
|
||||||
version_array = version.split('.')
|
|
||||||
major_version = version_array[0].to_int()
|
|
||||||
minor_version = version_array[1].to_int()
|
|
||||||
version_micro = version_array[2].to_int()
|
|
||||||
|
|
||||||
prefix = get_option('prefix')
|
|
||||||
bindir = prefix / get_option('bindir')
|
|
||||||
localedir = prefix / get_option('localedir')
|
|
||||||
|
|
||||||
datadir = prefix / get_option('datadir')
|
|
||||||
pkgdatadir = datadir / meson.project_name()
|
|
||||||
iconsdir = datadir / 'icons'
|
|
||||||
podir = meson.source_root() / 'po'
|
|
||||||
gettext_package = meson.project_name()
|
|
||||||
|
|
||||||
if get_option('profile') == 'development'
|
|
||||||
profile = 'Devel'
|
|
||||||
vcs_tag = run_command('git', 'rev-parse', '--short', 'HEAD').stdout().strip()
|
|
||||||
if vcs_tag == ''
|
|
||||||
version_suffix = '-devel'
|
|
||||||
else
|
|
||||||
version_suffix = '-@0@'.format(vcs_tag)
|
|
||||||
endif
|
|
||||||
application_id = '@0@.@1@'.format(base_id, profile)
|
|
||||||
else
|
|
||||||
profile = ''
|
|
||||||
version_suffix = ''
|
|
||||||
application_id = base_id
|
|
||||||
endif
|
|
||||||
|
|
||||||
meson.add_dist_script(
|
|
||||||
'build-aux/dist-vendor.sh',
|
|
||||||
meson.build_root() / 'meson-dist' / meson.project_name() + '-' + version,
|
|
||||||
meson.source_root()
|
|
||||||
)
|
|
||||||
|
|
||||||
if get_option('profile') == 'development'
|
|
||||||
# Setup pre-commit hook for ensuring coding style is always consistent
|
|
||||||
message('Setting up git pre-commit hook..')
|
|
||||||
run_command('cp', '-f', 'hooks/pre-commit.hook', '.git/hooks/pre-commit')
|
|
||||||
endif
|
|
||||||
|
|
||||||
subdir('data')
|
|
||||||
subdir('po')
|
|
||||||
subdir('src')
|
|
||||||
|
|
||||||
meson.add_install_script('build-aux/meson_post_install.py')
|
|
@ -1,10 +0,0 @@
|
|||||||
option(
|
|
||||||
'profile',
|
|
||||||
type: 'combo',
|
|
||||||
choices: [
|
|
||||||
'default',
|
|
||||||
'development'
|
|
||||||
],
|
|
||||||
value: 'default',
|
|
||||||
description: 'The build profile for QEMU Gtk. One of "default" or "development".'
|
|
||||||
)
|
|
@ -1,6 +0,0 @@
|
|||||||
data/resources/ui/shortcuts.ui
|
|
||||||
data/resources/ui/console.ui.in
|
|
||||||
data/resources/ui/window.ui.in
|
|
||||||
data/org.qemu.gtk4.desktop.in.in
|
|
||||||
data/org.qemu.gtk4.gschema.xml.in
|
|
||||||
data/org.qemu.gtk4.metainfo.xml.in.in
|
|
@ -1 +0,0 @@
|
|||||||
i18n.gettext(gettext_package, preset: 'glib')
|
|
@ -1,212 +0,0 @@
|
|||||||
use crate::config;
|
|
||||||
use crate::window::QemuApplicationWindow;
|
|
||||||
use gio::ApplicationFlags;
|
|
||||||
use glib::clone;
|
|
||||||
use glib::WeakRef;
|
|
||||||
use gtk::prelude::*;
|
|
||||||
use gtk::subclass::prelude::*;
|
|
||||||
use gtk::{gdk, gio, glib};
|
|
||||||
use gtk_macros::action;
|
|
||||||
use log::{debug, info};
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
use crate::gstaudio::GstAudio;
|
|
||||||
use qemu_display_listener::{Audio, Console};
|
|
||||||
use zbus::Connection;
|
|
||||||
|
|
||||||
mod imp {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct QemuApplication {
|
|
||||||
pub window: OnceCell<WeakRef<QemuApplicationWindow>>,
|
|
||||||
pub conn: OnceCell<Connection>,
|
|
||||||
pub addr: OnceCell<String>,
|
|
||||||
pub audio: OnceCell<GstAudio>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
|
||||||
impl ObjectSubclass for QemuApplication {
|
|
||||||
const NAME: &'static str = "QemuApplication";
|
|
||||||
type Type = super::QemuApplication;
|
|
||||||
type ParentType = gtk::Application;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectImpl for QemuApplication {}
|
|
||||||
|
|
||||||
impl gio::subclass::prelude::ApplicationImpl for QemuApplication {
|
|
||||||
fn handle_local_options(
|
|
||||||
&self,
|
|
||||||
application: &Self::Type,
|
|
||||||
options: &glib::VariantDict,
|
|
||||||
) -> i32 {
|
|
||||||
if options.lookup_value("version", None).is_some() {
|
|
||||||
println!("Version: {} ({})", config::VERSION, config::PROFILE);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.parent_handle_local_options(application, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn command_line(
|
|
||||||
&self,
|
|
||||||
application: &Self::Type,
|
|
||||||
command_line: &gio::ApplicationCommandLine,
|
|
||||||
) -> i32 {
|
|
||||||
let mut opt = command_line.get_arguments().into_iter().skip(1);
|
|
||||||
if let Some(arg) = opt.next() {
|
|
||||||
self.addr.set(arg.into_string().unwrap()).unwrap();
|
|
||||||
}
|
|
||||||
application.activate();
|
|
||||||
self.parent_command_line(application, command_line)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn activate(&self, app: &Self::Type) {
|
|
||||||
debug!("GtkApplication<QemuApplication>::activate");
|
|
||||||
|
|
||||||
if let Some(window) = self.window.get() {
|
|
||||||
let window = window.upgrade().unwrap();
|
|
||||||
window.show();
|
|
||||||
window.present();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.set_resource_base_path(Some("/org/qemu/gtk4/"));
|
|
||||||
app.setup_css();
|
|
||||||
|
|
||||||
let conn = if let Some(addr) = self.addr.get() {
|
|
||||||
Connection::new_for_address(&addr, true)
|
|
||||||
} else {
|
|
||||||
Connection::new_session()
|
|
||||||
}
|
|
||||||
.expect("Failed to connect to DBus");
|
|
||||||
|
|
||||||
if Audio::available(&conn) {
|
|
||||||
if let Ok(audio) = Audio::new(&conn) {
|
|
||||||
self.audio
|
|
||||||
.set(GstAudio::new(audio).expect("Failed to setup audio"))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let console = Console::new(&conn, 0).expect("Failed to get the console");
|
|
||||||
self.conn.set(conn).expect("Connection already set.");
|
|
||||||
|
|
||||||
let window = QemuApplicationWindow::new(app, console);
|
|
||||||
self.window
|
|
||||||
.set(window.downgrade())
|
|
||||||
.expect("Window already set.");
|
|
||||||
|
|
||||||
app.setup_gactions();
|
|
||||||
app.setup_accels();
|
|
||||||
|
|
||||||
app.get_main_window().present();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn startup(&self, app: &Self::Type) {
|
|
||||||
debug!("GtkApplication<QemuApplication>::startup");
|
|
||||||
self.parent_startup(app);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GtkApplicationImpl for QemuApplication {}
|
|
||||||
}
|
|
||||||
|
|
||||||
glib::wrapper! {
|
|
||||||
pub struct QemuApplication(ObjectSubclass<imp::QemuApplication>)
|
|
||||||
@extends gio::Application, gtk::Application, @implements gio::ActionMap, gio::ActionGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QemuApplication {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let app = glib::Object::new::<Self>(&[
|
|
||||||
("application-id", &Some(config::APP_ID)),
|
|
||||||
(
|
|
||||||
"flags",
|
|
||||||
&(ApplicationFlags::NON_UNIQUE | ApplicationFlags::HANDLES_COMMAND_LINE),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.expect("Application initialization failed...");
|
|
||||||
app.add_main_option(
|
|
||||||
"version",
|
|
||||||
glib::Char(0),
|
|
||||||
glib::OptionFlags::NONE,
|
|
||||||
glib::OptionArg::None,
|
|
||||||
"Show program version",
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
app
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_main_window(&self) -> QemuApplicationWindow {
|
|
||||||
let priv_ = imp::QemuApplication::from_instance(self);
|
|
||||||
priv_.window.get().unwrap().upgrade().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_gactions(&self) {
|
|
||||||
// Quit
|
|
||||||
action!(
|
|
||||||
self,
|
|
||||||
"quit",
|
|
||||||
clone!(@weak self as app => move |_, _| {
|
|
||||||
// This is needed to trigger the delete event
|
|
||||||
// and saving the window state
|
|
||||||
app.get_main_window().close();
|
|
||||||
app.quit();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// About
|
|
||||||
action!(
|
|
||||||
self,
|
|
||||||
"about",
|
|
||||||
clone!(@weak self as app => move |_, _| {
|
|
||||||
app.show_about_dialog();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets up keyboard shortcuts
|
|
||||||
fn setup_accels(&self) {
|
|
||||||
self.set_accels_for_action("app.quit", &["<primary>q"]);
|
|
||||||
self.set_accels_for_action("win.show-help-overlay", &["<primary>question"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_css(&self) {
|
|
||||||
let provider = gtk::CssProvider::new();
|
|
||||||
provider.load_from_resource("/org/qemu/gtk4/style.css");
|
|
||||||
if let Some(display) = gdk::Display::get_default() {
|
|
||||||
gtk::StyleContext::add_provider_for_display(
|
|
||||||
&display,
|
|
||||||
&provider,
|
|
||||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_about_dialog(&self) {
|
|
||||||
let dialog = gtk::AboutDialogBuilder::new()
|
|
||||||
.program_name("QEMU Gtk")
|
|
||||||
.logo_icon_name(config::APP_ID)
|
|
||||||
.license_type(gtk::License::MitX11)
|
|
||||||
.website("https://gitlab.com/qemu-project/qemu/")
|
|
||||||
.version(config::VERSION)
|
|
||||||
.transient_for(&self.get_main_window())
|
|
||||||
.modal(true)
|
|
||||||
.authors(vec!["QEMU developpers".into()])
|
|
||||||
.artists(vec!["QEMU developpers".into()])
|
|
||||||
.build();
|
|
||||||
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&self) {
|
|
||||||
info!("QEMU Gtk ({})", config::APP_ID);
|
|
||||||
info!("Version: {} ({})", config::VERSION, config::PROFILE);
|
|
||||||
info!("Datadir: {}", config::PKGDATADIR);
|
|
||||||
|
|
||||||
let args: Vec<String> = env::args().collect();
|
|
||||||
ApplicationExtManual::run(self, &args);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
pub const APP_ID: &str = @APP_ID@;
|
|
||||||
pub const GETTEXT_PACKAGE: &str = @GETTEXT_PACKAGE@;
|
|
||||||
pub const LOCALEDIR: &str = @LOCALEDIR@;
|
|
||||||
pub const PKGDATADIR: &str = @PKGDATADIR@;
|
|
||||||
pub const PROFILE: &str = @PROFILE@;
|
|
||||||
pub const RESOURCES_FILE: &str = concat!(@PKGDATADIR@, "/resources.gresource");
|
|
||||||
pub const VERSION: &str = @VERSION@;
|
|
@ -1,427 +0,0 @@
|
|||||||
use glib::clone;
|
|
||||||
use glib::subclass::prelude::*;
|
|
||||||
use gtk::glib::translate::FromGlibPtrBorrow;
|
|
||||||
use gtk::prelude::*;
|
|
||||||
use gtk::{gdk, glib, CompositeTemplate};
|
|
||||||
use log::debug;
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use std::cell::{Cell, RefCell};
|
|
||||||
|
|
||||||
use wayland_client::{Display, GlobalManager};
|
|
||||||
use wayland_protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
|
||||||
use wayland_protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{
|
|
||||||
Lifetime, ZwpPointerConstraintsV1,
|
|
||||||
};
|
|
||||||
use wayland_protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
|
|
||||||
use wayland_protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::{
|
|
||||||
Event as RelEvent, ZwpRelativePointerV1,
|
|
||||||
};
|
|
||||||
|
|
||||||
use keycodemap::*;
|
|
||||||
use qemu_display_listener::{Console, ConsoleEvent as Event, MouseButton};
|
|
||||||
|
|
||||||
mod imp {
|
|
||||||
use super::*;
|
|
||||||
use gtk::subclass::prelude::*;
|
|
||||||
|
|
||||||
#[derive(CompositeTemplate, Default)]
|
|
||||||
#[template(resource = "/org/qemu/gtk4/console.ui")]
|
|
||||||
pub struct QemuConsole {
|
|
||||||
#[template_child]
|
|
||||||
pub area: TemplateChild<crate::console_area::QemuConsoleArea>,
|
|
||||||
#[template_child]
|
|
||||||
pub label: TemplateChild<gtk::Label>,
|
|
||||||
pub console: OnceCell<Console>,
|
|
||||||
pub wait_rendering: Cell<usize>,
|
|
||||||
pub shortcuts_inhibited_id: Cell<Option<glib::SignalHandlerId>>,
|
|
||||||
pub ungrab_shortcut: OnceCell<gtk::ShortcutTrigger>,
|
|
||||||
pub key_controller: OnceCell<gtk::EventControllerKey>,
|
|
||||||
pub event_queue: OnceCell<wayland_client::EventQueue>,
|
|
||||||
pub rel_manager: OnceCell<wayland_client::Main<ZwpRelativePointerManagerV1>>,
|
|
||||||
pub rel_pointer: RefCell<Option<wayland_client::Main<ZwpRelativePointerV1>>>,
|
|
||||||
pub pointer_constraints: OnceCell<wayland_client::Main<ZwpPointerConstraintsV1>>,
|
|
||||||
pub lock_pointer: RefCell<Option<wayland_client::Main<ZwpLockedPointerV1>>>,
|
|
||||||
pub has_grab: Cell<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
|
||||||
impl ObjectSubclass for QemuConsole {
|
|
||||||
const NAME: &'static str = "QemuConsole";
|
|
||||||
type Type = super::QemuConsole;
|
|
||||||
type ParentType = gtk::Widget;
|
|
||||||
|
|
||||||
fn class_init(klass: &mut Self::Class) {
|
|
||||||
Self::bind_template(klass);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
|
||||||
obj.init_template();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectImpl for QemuConsole {
|
|
||||||
fn constructed(&self, obj: &Self::Type) {
|
|
||||||
self.parent_constructed(obj);
|
|
||||||
|
|
||||||
// TODO: implement a custom trigger with only modifiers, ala spice-gtk?
|
|
||||||
let ungrab = gtk::ShortcutTrigger::parse_string("<ctrl><alt>g").unwrap();
|
|
||||||
self.ungrab_shortcut.set(ungrab).unwrap();
|
|
||||||
|
|
||||||
let ec = gtk::EventControllerKey::new();
|
|
||||||
ec.set_propagation_phase(gtk::PropagationPhase::Capture);
|
|
||||||
self.area.add_controller(&ec);
|
|
||||||
ec.connect_key_pressed(
|
|
||||||
clone!(@weak obj => @default-panic, move |_, _keyval, keycode, _state| {
|
|
||||||
let c = obj.qemu_console();
|
|
||||||
if let Some(qnum) = KEYMAP_XORGEVDEV2QNUM.get(keycode as usize) {
|
|
||||||
let _ = c.keyboard.press(*qnum as u32);
|
|
||||||
}
|
|
||||||
glib::signal::Inhibit(true)
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
ec.connect_key_released(clone!(@weak obj => move |_, _keyval, keycode, _state| {
|
|
||||||
let c = obj.qemu_console();
|
|
||||||
if let Some(qnum) = KEYMAP_XORGEVDEV2QNUM.get(keycode as usize) {
|
|
||||||
let _ = c.keyboard.release(*qnum as u32);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
self.key_controller.set(ec).unwrap();
|
|
||||||
|
|
||||||
let ec = gtk::EventControllerMotion::new();
|
|
||||||
self.area.add_controller(&ec);
|
|
||||||
ec.connect_motion(clone!(@weak obj => move |_, x, y| {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
let c = obj.qemu_console();
|
|
||||||
if c.mouse.is_absolute().unwrap_or(true) {
|
|
||||||
priv_.motion(x, y);
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
|
|
||||||
let ec = gtk::GestureClick::new();
|
|
||||||
ec.set_button(0);
|
|
||||||
self.area.add_controller(&ec);
|
|
||||||
ec.connect_pressed(clone!(@weak obj => @default-panic, move |gesture, _n_press, x, y| {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
let c = obj.qemu_console();
|
|
||||||
let button = from_gdk_button(gesture.get_current_button());
|
|
||||||
priv_.motion(x, y);
|
|
||||||
let _ = c.mouse.press(button);
|
|
||||||
|
|
||||||
if !c.mouse.is_absolute().unwrap_or(true) {
|
|
||||||
priv_.area.set_cursor_abs(false);
|
|
||||||
if let Some(device) = gesture.get_device() {
|
|
||||||
if let Ok(device) = device.downcast::<gdk_wl::WaylandDevice>() {
|
|
||||||
let pointer = device.get_wl_pointer();
|
|
||||||
if priv_.lock_pointer.borrow().is_none() {
|
|
||||||
if let Some(constraints) = priv_.pointer_constraints.get() {
|
|
||||||
if let Some(surf) = priv_.area.get_native()
|
|
||||||
.and_then(|n| n.get_surface())
|
|
||||||
.and_then(|s| s.downcast::<gdk_wl::WaylandSurface>().ok())
|
|
||||||
.map(|w| w.get_wl_surface()) {
|
|
||||||
let lock = constraints.lock_pointer(&surf, &pointer, None, Lifetime::Persistent as _);
|
|
||||||
lock.quick_assign(move |_, event, _| {
|
|
||||||
debug!("{:?}", event);
|
|
||||||
});
|
|
||||||
priv_.lock_pointer.replace(Some(lock));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if priv_.rel_pointer.borrow().is_none() {
|
|
||||||
if let Some(rel_manager) = priv_.rel_manager.get() {
|
|
||||||
let rel_pointer = rel_manager.get_relative_pointer(&pointer);
|
|
||||||
rel_pointer.quick_assign(clone!(@weak obj => @default-panic, move |_, event, _| {
|
|
||||||
if let RelEvent::RelativeMotion { dx_unaccel, dy_unaccel, .. } = event {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
let c = obj.qemu_console();
|
|
||||||
let scale = priv_.area.get_scale_factor();
|
|
||||||
let _ = c.mouse.rel_motion(dx_unaccel as i32 / scale, dy_unaccel as i32 / scale);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
priv_.rel_pointer.replace(Some(rel_pointer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(toplevel) = priv_.get_toplevel() {
|
|
||||||
if !toplevel.get_property_shortcuts_inhibited() {
|
|
||||||
toplevel.inhibit_system_shortcuts::<gdk::ButtonEvent>(None);
|
|
||||||
|
|
||||||
let ec = gtk::EventControllerKey::new();
|
|
||||||
ec.set_propagation_phase(gtk::PropagationPhase::Capture);
|
|
||||||
ec.connect_key_pressed(clone!(@weak obj, @weak toplevel => @default-panic, move |ec, keyval, keycode, state| {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
if let Some(ref e) = ec.get_current_event() {
|
|
||||||
if priv_.ungrab_shortcut.get().unwrap().trigger(e, false) == gdk::KeyMatch::Exact {
|
|
||||||
//widget.remove_controller(ec); here crashes badly
|
|
||||||
glib::idle_add_local(clone!(@weak ec, @weak toplevel, @weak obj => @default-panic, move || {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
if let Some(widget) = ec.get_widget() {
|
|
||||||
widget.remove_controller(&ec);
|
|
||||||
}
|
|
||||||
toplevel.restore_system_shortcuts();
|
|
||||||
if let Some(lock) = priv_.lock_pointer.take() {
|
|
||||||
lock.destroy();
|
|
||||||
}
|
|
||||||
if let Some(rel_pointer) = priv_.rel_pointer.take() {
|
|
||||||
rel_pointer.destroy();
|
|
||||||
}
|
|
||||||
priv_.area.set_cursor_abs(true);
|
|
||||||
priv_.has_grab.set(false);
|
|
||||||
glib::Continue(false)
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
priv_.key_controller.get().unwrap().emit_by_name("key-pressed", &[&*keyval, &keycode, &state]).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glib::signal::Inhibit(true)
|
|
||||||
}));
|
|
||||||
ec.connect_key_released(clone!(@weak obj => @default-panic, move |_ec, keyval, keycode, state| {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
priv_.key_controller.get().unwrap().emit_by_name("key-released", &[&*keyval, &keycode, &state]).unwrap();
|
|
||||||
}));
|
|
||||||
if let Some(root) = priv_.area.get_root() {
|
|
||||||
root.add_controller(&ec);
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = toplevel.connect_property_shortcuts_inhibited_notify(clone!(@weak obj => @default-panic, move |toplevel| {
|
|
||||||
let inhibited = toplevel.get_property_shortcuts_inhibited();
|
|
||||||
debug!("shortcuts-inhibited: {}", inhibited);
|
|
||||||
if !inhibited {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
let id = priv_.shortcuts_inhibited_id.take();
|
|
||||||
toplevel.disconnect(id.unwrap());
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
priv_.shortcuts_inhibited_id.set(Some(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
priv_.has_grab.set(true);
|
|
||||||
priv_.area.grab_focus();
|
|
||||||
}));
|
|
||||||
ec.connect_released(clone!(@weak obj => move |gesture, _n_press, x, y| {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
let c = obj.qemu_console();
|
|
||||||
let button = from_gdk_button(gesture.get_current_button());
|
|
||||||
|
|
||||||
priv_.motion(x, y);
|
|
||||||
let _ = c.mouse.release(button);
|
|
||||||
}));
|
|
||||||
|
|
||||||
let ec = gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::BOTH_AXES);
|
|
||||||
self.area.add_controller(&ec);
|
|
||||||
ec.connect_scroll(clone!(@weak obj => @default-panic, move |_, _dx, dy| {
|
|
||||||
let c = obj.qemu_console();
|
|
||||||
|
|
||||||
let button = if dy >= 1.0 {
|
|
||||||
Some(MouseButton::WheelDown)
|
|
||||||
} else if dy <= -1.0 {
|
|
||||||
Some(MouseButton::WheelUp)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if let Some(button) = button {
|
|
||||||
let _ = c.mouse.press(button);
|
|
||||||
let _ = c.mouse.release(button);
|
|
||||||
}
|
|
||||||
glib::signal::Inhibit(true)
|
|
||||||
}));
|
|
||||||
|
|
||||||
self.area.set_sensitive(true);
|
|
||||||
self.area.set_focusable(true);
|
|
||||||
self.area.set_focus_on_click(true);
|
|
||||||
|
|
||||||
self.area
|
|
||||||
.connect_create_context(clone!(@weak obj => @default-panic, move |_| {
|
|
||||||
// can't connect-after create-context yet, so idle it
|
|
||||||
glib::idle_add_local(clone!(@weak ec => @default-panic, move || {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
priv_.attach_qemu_console(&obj);
|
|
||||||
glib::Continue(false)
|
|
||||||
}));
|
|
||||||
None
|
|
||||||
}));
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
self.area.connect_notify_unsafe(
|
|
||||||
Some("resize-hack"),
|
|
||||||
clone!(@weak obj => move |_, _| {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
let alloc = priv_.area.get_allocation();
|
|
||||||
if let Err(e) = obj.qemu_console().proxy.set_ui_info(0, 0, 0, 0, alloc.width as u32, alloc.height as u32) {
|
|
||||||
eprintln!("Failed to SetUIInfo: {}", e);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Needed for direct subclasses of GtkWidget;
|
|
||||||
// Here you need to unparent all direct children
|
|
||||||
// of your template.
|
|
||||||
fn dispose(&self, obj: &Self::Type) {
|
|
||||||
while let Some(child) = obj.get_first_child() {
|
|
||||||
child.unparent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WidgetImpl for QemuConsole {
|
|
||||||
fn realize(&self, widget: &Self::Type) {
|
|
||||||
self.parent_realize(widget);
|
|
||||||
|
|
||||||
if let Ok(dpy) = widget.get_display().downcast::<gdk_wl::WaylandDisplay>() {
|
|
||||||
let display = unsafe {
|
|
||||||
Display::from_external_display(dpy.get_wl_display().as_ref().c_ptr() as *mut _)
|
|
||||||
};
|
|
||||||
let mut event_queue = display.create_event_queue();
|
|
||||||
let attached_display = display.attach(event_queue.token());
|
|
||||||
let globals = GlobalManager::new(&attached_display);
|
|
||||||
event_queue
|
|
||||||
.sync_roundtrip(&mut (), |_, _, _| unreachable!())
|
|
||||||
.unwrap();
|
|
||||||
let rel_manager = globals
|
|
||||||
.instantiate_exact::<ZwpRelativePointerManagerV1>(1)
|
|
||||||
.unwrap();
|
|
||||||
self.rel_manager.set(rel_manager).unwrap();
|
|
||||||
let pointer_constraints = globals
|
|
||||||
.instantiate_exact::<ZwpPointerConstraintsV1>(1)
|
|
||||||
.unwrap();
|
|
||||||
self.pointer_constraints.set(pointer_constraints).unwrap();
|
|
||||||
let fd = display.get_connection_fd();
|
|
||||||
let _ = glib::unix_fd_add_local(fd, glib::IOCondition::IN, move |_, _| {
|
|
||||||
event_queue
|
|
||||||
.sync_roundtrip(&mut (), |_, _, _| unreachable!())
|
|
||||||
.unwrap();
|
|
||||||
glib::Continue(true)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QemuConsole {
|
|
||||||
fn get_toplevel(&self) -> Option<gdk::Toplevel> {
|
|
||||||
self.area
|
|
||||||
.get_root()
|
|
||||||
.and_then(|r| r.get_native())
|
|
||||||
.and_then(|n| n.get_surface())
|
|
||||||
.and_then(|s| s.downcast::<gdk::Toplevel>().ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn motion(&self, x: f64, y: f64) {
|
|
||||||
if let Some((x, y)) = self.area.transform_input(x, y) {
|
|
||||||
let c = self.console.get().unwrap();
|
|
||||||
let _ = c.mouse.set_abs_position(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn attach_qemu_console(&self, obj: &super::QemuConsole) {
|
|
||||||
let console = match self.console.get() {
|
|
||||||
Some(console) => console,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
if !obj.get_realized() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (rx, wait_tx) = console
|
|
||||||
.glib_listen()
|
|
||||||
.expect("Failed to listen to the console");
|
|
||||||
self.area
|
|
||||||
.connect_render(clone!(@weak obj => @default-panic, move |_, _| {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
let wait_rendering = priv_.wait_rendering.get();
|
|
||||||
if wait_rendering > 0 {
|
|
||||||
if let Err(e) = wait_tx.send(()) {
|
|
||||||
eprintln!("Failed to ack rendering: {}", e);
|
|
||||||
}
|
|
||||||
priv_.wait_rendering.set(wait_rendering - 1);
|
|
||||||
}
|
|
||||||
glib::signal::Inhibit(false)
|
|
||||||
}));
|
|
||||||
rx.attach(
|
|
||||||
None,
|
|
||||||
clone!(@weak obj => @default-panic, move |t| {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(&obj);
|
|
||||||
debug!("Console event: {:?}", t);
|
|
||||||
match t {
|
|
||||||
Event::Scanout(s) => {
|
|
||||||
priv_.area.set_scanout(s);
|
|
||||||
priv_.area.queue_render();
|
|
||||||
}
|
|
||||||
Event::Update(u) => {
|
|
||||||
priv_.area.update(u);
|
|
||||||
priv_.area.queue_render();
|
|
||||||
}
|
|
||||||
Event::ScanoutDMABUF(s) => {
|
|
||||||
priv_.label.set_label(&format!("{:?}", s));
|
|
||||||
priv_.area.set_scanout_dmabuf(s);
|
|
||||||
}
|
|
||||||
Event::UpdateDMABUF { .. } => {
|
|
||||||
priv_.wait_rendering.set(priv_.wait_rendering.get() + 1);
|
|
||||||
// we don't simply queue_render, as we want a copy immediately
|
|
||||||
priv_.area.make_current();
|
|
||||||
priv_.area.attach_buffers();
|
|
||||||
let _ = unsafe {
|
|
||||||
glib::Object::from_glib_borrow(priv_.area.as_ptr() as *mut glib::gobject_ffi::GObject)
|
|
||||||
.emit_by_name("render", &[&priv_.area.get_context().as_ref()])
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
priv_.area.queue_draw();
|
|
||||||
}
|
|
||||||
Event::Disconnected => {
|
|
||||||
priv_.label.set_label("Console disconnected!");
|
|
||||||
}
|
|
||||||
Event::CursorDefine { width, height, hot_x, hot_y, data }=> {
|
|
||||||
let scale = priv_.area.get_scale_factor();
|
|
||||||
let pb = gdk::gdk_pixbuf::Pixbuf::from_mut_slice(data, gdk::gdk_pixbuf::Colorspace::Rgb, true, 8, width, height, width * 4);
|
|
||||||
let pb = pb.scale_simple(width * scale, height * scale, gdk::gdk_pixbuf::InterpType::Bilinear).unwrap();
|
|
||||||
let tex = gdk::Texture::new_for_pixbuf(&pb);
|
|
||||||
let cur = gdk::Cursor::from_texture(&tex, hot_x * scale, hot_y * scale, None);
|
|
||||||
priv_.area.cursor_define(cur);
|
|
||||||
}
|
|
||||||
Event::MouseSet(m) => {
|
|
||||||
priv_.area.mouse_set(m);
|
|
||||||
let c = obj.qemu_console();
|
|
||||||
let abs = c.mouse.is_absolute().unwrap_or(true);
|
|
||||||
if priv_.has_grab.get() {
|
|
||||||
priv_.area.set_cursor_abs(abs);
|
|
||||||
}
|
|
||||||
priv_.area.queue_render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Continue(true)
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glib::wrapper! {
|
|
||||||
pub struct QemuConsole(ObjectSubclass<imp::QemuConsole>) @extends gtk::Widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QemuConsole {
|
|
||||||
pub fn set_qemu_console(&self, console: Console) {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(self);
|
|
||||||
priv_.console.set(console).unwrap();
|
|
||||||
priv_.attach_qemu_console(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn qemu_console(&self) -> &Console {
|
|
||||||
let priv_ = imp::QemuConsole::from_instance(self);
|
|
||||||
priv_.console.get().expect("Console is not yet set!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_gdk_button(button: u32) -> MouseButton {
|
|
||||||
match button {
|
|
||||||
1 => MouseButton::Left,
|
|
||||||
2 => MouseButton::Middle,
|
|
||||||
3 => MouseButton::Right,
|
|
||||||
_ => MouseButton::Extra,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,479 +0,0 @@
|
|||||||
use glib::subclass::prelude::*;
|
|
||||||
use glib::translate::*;
|
|
||||||
use gtk::prelude::*;
|
|
||||||
use gtk::subclass::widget::WidgetImplExt;
|
|
||||||
use gtk::{gdk, glib, graphene};
|
|
||||||
use std::cell::{Cell, RefCell};
|
|
||||||
use std::ffi::{CStr, CString};
|
|
||||||
|
|
||||||
use crate::egl;
|
|
||||||
use crate::error::*;
|
|
||||||
use gl::{self, types::*};
|
|
||||||
use qemu_display_listener::{MouseSet, Scanout, ScanoutDMABUF, Update};
|
|
||||||
|
|
||||||
mod imp {
|
|
||||||
use super::*;
|
|
||||||
use gtk::subclass::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct QemuConsoleArea {
|
|
||||||
pub tex_id: Cell<GLuint>,
|
|
||||||
pub texture_blit_vao: Cell<GLuint>,
|
|
||||||
pub texture_blit_prog: Cell<GLuint>,
|
|
||||||
pub texture_blit_flip_prog: Cell<GLuint>,
|
|
||||||
pub scanout: Cell<Option<ScanoutDMABUF>>,
|
|
||||||
pub scanout_size: Cell<(u32, u32)>,
|
|
||||||
pub cursor_abs: Cell<bool>,
|
|
||||||
pub cursor: RefCell<Option<gdk::Cursor>>,
|
|
||||||
pub mouse: Cell<Option<MouseSet>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
|
||||||
impl ObjectSubclass for QemuConsoleArea {
|
|
||||||
const NAME: &'static str = "QemuConsoleArea";
|
|
||||||
type Type = super::QemuConsoleArea;
|
|
||||||
type ParentType = gtk::GLArea;
|
|
||||||
|
|
||||||
fn class_init(_klass: &mut Self::Class) {
|
|
||||||
// GL loading could be done earlier?
|
|
||||||
let egl = egl::egl();
|
|
||||||
|
|
||||||
gl::load_with(|s| {
|
|
||||||
egl.get_proc_address(s)
|
|
||||||
.map(|f| f as _)
|
|
||||||
.unwrap_or(std::ptr::null())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectImpl for QemuConsoleArea {
|
|
||||||
fn constructed(&self, obj: &Self::Type) {
|
|
||||||
self.parent_constructed(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
|
||||||
vec![glib::ParamSpec::boolean(
|
|
||||||
"resize-hack",
|
|
||||||
"resize-hack",
|
|
||||||
"Resize hack to notify parent",
|
|
||||||
false,
|
|
||||||
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT,
|
|
||||||
)]
|
|
||||||
});
|
|
||||||
PROPERTIES.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_property(
|
|
||||||
&self,
|
|
||||||
_obj: &Self::Type,
|
|
||||||
_id: usize,
|
|
||||||
_value: &glib::Value,
|
|
||||||
_pspec: &glib::ParamSpec,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WidgetImpl for QemuConsoleArea {
|
|
||||||
fn realize(&self, widget: &Self::Type) {
|
|
||||||
widget.set_has_depth_buffer(false);
|
|
||||||
widget.set_has_stencil_buffer(false);
|
|
||||||
widget.set_auto_render(false);
|
|
||||||
widget.set_required_version(3, 2);
|
|
||||||
self.parent_realize(widget);
|
|
||||||
widget.make_current();
|
|
||||||
|
|
||||||
if let Err(e) = unsafe { self.realize_gl() } {
|
|
||||||
let e = glib::Error::new(QemuGtkError::GL, &e);
|
|
||||||
widget.set_error(Some(&e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size_allocate(&self, widget: &Self::Type, width: i32, height: i32, baseline: i32) {
|
|
||||||
self.parent_size_allocate(widget, width, height, baseline);
|
|
||||||
widget.notify("resize-hack");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn snapshot(&self, widget: &Self::Type, snapshot: >k::Snapshot) {
|
|
||||||
self.parent_snapshot(widget, snapshot);
|
|
||||||
|
|
||||||
if !self.cursor_abs.get() {
|
|
||||||
if let Some(mouse) = self.mouse.get() {
|
|
||||||
if mouse.on != 0 {
|
|
||||||
if let Some(cursor) = &*self.cursor.borrow() {
|
|
||||||
if let Some(texture) = cursor.get_texture() {
|
|
||||||
let sf = widget.get_scale_factor();
|
|
||||||
snapshot.append_texture(
|
|
||||||
&texture,
|
|
||||||
&graphene::Rect::new(
|
|
||||||
(mouse.x - cursor.get_hotspot_x() / sf) as f32,
|
|
||||||
(mouse.y - cursor.get_hotspot_y() / sf) as f32,
|
|
||||||
(texture.get_width() / sf) as f32,
|
|
||||||
(texture.get_height() / sf) as f32,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GLAreaImpl for QemuConsoleArea {
|
|
||||||
fn render(&self, gl_area: &Self::Type, _context: &gdk::GLContext) -> bool {
|
|
||||||
unsafe {
|
|
||||||
gl::ClearColor(0.1, 0.1, 0.1, 1.0);
|
|
||||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
|
||||||
gl::Disable(gl::BLEND);
|
|
||||||
|
|
||||||
let vp = self.viewport(gl_area);
|
|
||||||
gl::Viewport(vp.x, vp.y, vp.width, vp.height);
|
|
||||||
self.texture_blit(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// parent will return to update call
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QemuConsoleArea {
|
|
||||||
pub fn borders(&self, gl_area: &super::QemuConsoleArea) -> (u32, u32) {
|
|
||||||
let sf = gl_area.get_scale_factor();
|
|
||||||
let (w, h) = (gl_area.get_width() * sf, gl_area.get_height() * sf);
|
|
||||||
let (gw, gh) = gl_area.scanout_size();
|
|
||||||
let (sw, sh) = (w as f32 / gw as f32, h as f32 / gh as f32);
|
|
||||||
|
|
||||||
if sw < sh {
|
|
||||||
let bh = h - (h as f32 * sw / sh) as i32;
|
|
||||||
(0, bh as u32 / 2)
|
|
||||||
} else {
|
|
||||||
let bw = w - (w as f32 * sh / sw) as i32;
|
|
||||||
(bw as u32 / 2, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn viewport(&self, gl_area: &super::QemuConsoleArea) -> gdk::Rectangle {
|
|
||||||
let sf = gl_area.get_scale_factor();
|
|
||||||
let (w, h) = (gl_area.get_width() * sf, gl_area.get_height() * sf);
|
|
||||||
let (borderw, borderh) = self.borders(gl_area);
|
|
||||||
let (borderw, borderh) = (borderw as i32, borderh as i32);
|
|
||||||
gdk::Rectangle {
|
|
||||||
x: borderw,
|
|
||||||
y: borderh,
|
|
||||||
width: w - borderw * 2,
|
|
||||||
height: h - borderh * 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn realize_gl(&self) -> Result<(), String> {
|
|
||||||
let texture_blit_vs = CString::new(include_str!("texture-blit.vert")).unwrap();
|
|
||||||
let texture_blit_flip_vs =
|
|
||||||
CString::new(include_str!("texture-blit-flip.vert")).unwrap();
|
|
||||||
let texture_blit_fs = CString::new(include_str!("texture-blit.frag")).unwrap();
|
|
||||||
|
|
||||||
let texture_blit_prg =
|
|
||||||
compile_prog(texture_blit_vs.as_c_str(), texture_blit_fs.as_c_str())?;
|
|
||||||
self.texture_blit_prog.set(texture_blit_prg);
|
|
||||||
let texture_blit_flip_prg =
|
|
||||||
compile_prog(texture_blit_flip_vs.as_c_str(), texture_blit_fs.as_c_str())?;
|
|
||||||
self.texture_blit_flip_prog.set(texture_blit_flip_prg);
|
|
||||||
|
|
||||||
let mut vao = 0;
|
|
||||||
gl::GenVertexArrays(1, &mut vao);
|
|
||||||
gl::BindVertexArray(vao);
|
|
||||||
let mut vb = 0;
|
|
||||||
gl::GenBuffers(1, &mut vb);
|
|
||||||
gl::BindBuffer(gl::ARRAY_BUFFER, vb);
|
|
||||||
static POS: [f32; 8] = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];
|
|
||||||
gl::BufferData(
|
|
||||||
gl::ARRAY_BUFFER,
|
|
||||||
std::mem::size_of::<[f32; 8]>() as _,
|
|
||||||
POS.as_ptr() as _,
|
|
||||||
gl::STATIC_DRAW,
|
|
||||||
);
|
|
||||||
let in_pos = gl::GetAttribLocation(
|
|
||||||
texture_blit_prg,
|
|
||||||
CString::new("in_position").unwrap().as_c_str().as_ptr(),
|
|
||||||
) as u32;
|
|
||||||
gl::VertexAttribPointer(in_pos, 2, gl::FLOAT, gl::FALSE, 0, std::ptr::null());
|
|
||||||
gl::EnableVertexAttribArray(in_pos);
|
|
||||||
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
|
||||||
gl::BindVertexArray(0);
|
|
||||||
self.texture_blit_vao.set(vao);
|
|
||||||
|
|
||||||
let tex_unit = gl::GetUniformLocation(
|
|
||||||
texture_blit_prg,
|
|
||||||
CString::new("tex_unit").unwrap().as_c_str().as_ptr(),
|
|
||||||
);
|
|
||||||
gl::ProgramUniform1i(texture_blit_prg, tex_unit, 0);
|
|
||||||
|
|
||||||
let mut tex_id = 0;
|
|
||||||
gl::GenTextures(1, &mut tex_id);
|
|
||||||
self.tex_id.set(tex_id);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn texture_blit(&self, flip: bool) {
|
|
||||||
gl::UseProgram(if flip {
|
|
||||||
todo!();
|
|
||||||
//self.texture_blit_flip_prog.get()
|
|
||||||
} else {
|
|
||||||
self.texture_blit_prog.get()
|
|
||||||
});
|
|
||||||
gl::ActiveTexture(gl::TEXTURE0);
|
|
||||||
gl::BindTexture(gl::TEXTURE_2D, self.tex_id());
|
|
||||||
gl::BindVertexArray(self.texture_blit_vao.get());
|
|
||||||
gl::DrawArrays(gl::TRIANGLE_STRIP, 0, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tex_id(&self) -> GLuint {
|
|
||||||
self.tex_id.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_to_png(&self, widget: &super::QemuConsoleArea, filename: &str) {
|
|
||||||
let (gw, gh) = self.scanout_size.get();
|
|
||||||
let ctxt = widget.get_context().unwrap();
|
|
||||||
let tex = unsafe { gdk::GLTexture::new(&ctxt, self.tex_id(), gw as _, gh as _) };
|
|
||||||
tex.save_to_png(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_scanout(&self, widget: &super::QemuConsoleArea, s: Scanout) {
|
|
||||||
widget.make_current();
|
|
||||||
|
|
||||||
if s.format != 0x20020888 {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
gl::BindTexture(gl::TEXTURE_2D, self.tex_id());
|
|
||||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _);
|
|
||||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
|
|
||||||
gl::PixelStorei(gl::UNPACK_ROW_LENGTH, s.stride as i32 / 4);
|
|
||||||
gl::TexImage2D(
|
|
||||||
gl::TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
gl::RGB as _,
|
|
||||||
s.width as _,
|
|
||||||
s.height as _,
|
|
||||||
0,
|
|
||||||
gl::BGRA,
|
|
||||||
gl::UNSIGNED_BYTE,
|
|
||||||
s.data.as_ptr() as _,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scanout_size.set((s.width, s.height));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&self, widget: &super::QemuConsoleArea, u: Update) {
|
|
||||||
widget.make_current();
|
|
||||||
|
|
||||||
if u.format != 0x20020888 {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
gl::BindTexture(gl::TEXTURE_2D, self.tex_id());
|
|
||||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _);
|
|
||||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
|
|
||||||
gl::PixelStorei(gl::UNPACK_ROW_LENGTH, u.stride as i32 / 4);
|
|
||||||
gl::TexSubImage2D(
|
|
||||||
gl::TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
u.x,
|
|
||||||
u.y,
|
|
||||||
u.w,
|
|
||||||
u.h,
|
|
||||||
gl::BGRA,
|
|
||||||
gl::UNSIGNED_BYTE,
|
|
||||||
u.data.as_ptr() as _,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_scanout_dmabuf(&self, widget: &super::QemuConsoleArea, s: ScanoutDMABUF) {
|
|
||||||
widget.make_current();
|
|
||||||
let egl = egl::egl();
|
|
||||||
|
|
||||||
let egl_dpy = if let Ok(dpy) = widget.get_display().downcast::<gdk_wl::WaylandDisplay>()
|
|
||||||
{
|
|
||||||
let wl_dpy = dpy.get_wl_display();
|
|
||||||
egl.get_display(wl_dpy.as_ref().c_ptr() as _)
|
|
||||||
.expect("Failed to get EGL display")
|
|
||||||
} else if let Ok(dpy) = widget.get_display().downcast::<gdk_x11::X11Display>() {
|
|
||||||
let _dpy =
|
|
||||||
unsafe { gdk_x11::ffi::gdk_x11_display_get_xdisplay(dpy.to_glib_none().0) };
|
|
||||||
eprintln!("X11: unsupported display kind, todo: EGL");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
eprintln!("Unsupported display kind");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let attribs = vec![
|
|
||||||
egl::WIDTH as usize,
|
|
||||||
s.width as usize,
|
|
||||||
egl::HEIGHT as usize,
|
|
||||||
s.height as usize,
|
|
||||||
egl::LINUX_DRM_FOURCC_EXT as usize,
|
|
||||||
s.fourcc as usize,
|
|
||||||
egl::DMA_BUF_PLANE0_FD_EXT as usize,
|
|
||||||
s.fd as usize,
|
|
||||||
egl::DMA_BUF_PLANE0_PITCH_EXT as usize,
|
|
||||||
s.stride as usize,
|
|
||||||
egl::DMA_BUF_PLANE0_OFFSET_EXT as usize,
|
|
||||||
0,
|
|
||||||
egl::DMA_BUF_PLANE0_MODIFIER_LO_EXT as usize,
|
|
||||||
(s.modifier & 0xffffffff) as usize,
|
|
||||||
egl::DMA_BUF_PLANE0_MODIFIER_HI_EXT as usize,
|
|
||||||
(s.modifier >> 32 & 0xffffffff) as usize,
|
|
||||||
egl::NONE as usize,
|
|
||||||
];
|
|
||||||
|
|
||||||
let img = egl
|
|
||||||
.create_image(
|
|
||||||
egl_dpy,
|
|
||||||
unsafe { egl::Context::from_ptr(egl::NO_CONTEXT) },
|
|
||||||
egl::LINUX_DMA_BUF_EXT,
|
|
||||||
unsafe { egl::ClientBuffer::from_ptr(std::ptr::null_mut()) },
|
|
||||||
&attribs,
|
|
||||||
)
|
|
||||||
.expect("Failed to eglCreateImage");
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
gl::BindTexture(gl::TEXTURE_2D, self.tex_id());
|
|
||||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _);
|
|
||||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
|
|
||||||
}
|
|
||||||
if let Some(image_target) = egl::image_target_texture_2d_oes() {
|
|
||||||
image_target(gl::TEXTURE_2D, img.as_ptr() as gl::types::GLeglImageOES);
|
|
||||||
} else {
|
|
||||||
eprintln!("Failed to set texture image");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scanout_size.set((s.width, s.height));
|
|
||||||
self.scanout.set(Some(s));
|
|
||||||
|
|
||||||
if let Err(e) = egl.destroy_image(egl_dpy, img) {
|
|
||||||
eprintln!("Destroy image failed: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glib::wrapper! {
|
|
||||||
pub struct QemuConsoleArea(ObjectSubclass<imp::QemuConsoleArea>)
|
|
||||||
@extends gtk::Widget, gtk::GLArea;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QemuConsoleArea {
|
|
||||||
pub fn scanout_size(&self) -> (u32, u32) {
|
|
||||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
|
||||||
|
|
||||||
priv_.scanout_size.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_scanout(&self, s: Scanout) {
|
|
||||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
|
||||||
|
|
||||||
priv_.set_scanout(self, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&self, u: Update) {
|
|
||||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
|
||||||
|
|
||||||
priv_.update(self, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_scanout_dmabuf(&self, s: ScanoutDMABUF) {
|
|
||||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
|
||||||
|
|
||||||
priv_.set_scanout_dmabuf(self, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_to_png(&self, filename: &str) {
|
|
||||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
|
||||||
|
|
||||||
priv_.save_to_png(self, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn transform_input(&self, x: f64, y: f64) -> Option<(u32, u32)> {
|
|
||||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
|
||||||
|
|
||||||
let vp = priv_.viewport(self);
|
|
||||||
let x = x as i32 * self.get_scale_factor();
|
|
||||||
let y = y as i32 * self.get_scale_factor();
|
|
||||||
if !vp.contains_point(x, y) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let (sw, sh) = priv_.scanout_size.get();
|
|
||||||
let x = (x - vp.x) as f64 * (sw as f64 / vp.width as f64);
|
|
||||||
let y = (y - vp.y) as f64 * (sh as f64 / vp.height as f64);
|
|
||||||
Some((x as u32, y as u32))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_abs(&self, abs: bool) {
|
|
||||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
|
||||||
|
|
||||||
priv_.cursor_abs.set(abs);
|
|
||||||
if abs {
|
|
||||||
if let Some(cursor) = priv_.cursor.borrow().clone() {
|
|
||||||
self.set_cursor(Some(&cursor));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.set_cursor_from_name(Some("none"))
|
|
||||||
}
|
|
||||||
self.queue_render();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cursor_define(&self, cursor: gdk::Cursor) {
|
|
||||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
|
||||||
priv_.cursor.replace(Some(cursor));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mouse_set(&self, mouse: MouseSet) {
|
|
||||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
|
||||||
priv_.mouse.set(Some(mouse));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn compile_shader(type_: GLenum, src: &CStr) -> GLuint {
|
|
||||||
let shader = gl::CreateShader(type_);
|
|
||||||
gl::ShaderSource(shader, 1, &src.as_ptr(), std::ptr::null());
|
|
||||||
gl::CompileShader(shader);
|
|
||||||
shader
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cstring_new_len(len: usize) -> CString {
|
|
||||||
let buffer: Vec<u8> = Vec::with_capacity(len + 1);
|
|
||||||
unsafe { CString::from_vec_unchecked(buffer) }
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn compile_prog(vs: &CStr, fs: &CStr) -> Result<GLuint, String> {
|
|
||||||
let vs = compile_shader(gl::VERTEX_SHADER, vs);
|
|
||||||
let fs = compile_shader(gl::FRAGMENT_SHADER, fs);
|
|
||||||
let prog = gl::CreateProgram();
|
|
||||||
|
|
||||||
gl::AttachShader(prog, vs);
|
|
||||||
gl::AttachShader(prog, fs);
|
|
||||||
gl::LinkProgram(prog);
|
|
||||||
|
|
||||||
let mut status: i32 = 0;
|
|
||||||
gl::GetProgramiv(prog, gl::LINK_STATUS, &mut status);
|
|
||||||
if status == 0 {
|
|
||||||
let mut len: GLint = 0;
|
|
||||||
gl::GetProgramiv(prog, gl::INFO_LOG_LENGTH, &mut len);
|
|
||||||
let error = cstring_new_len(len as usize);
|
|
||||||
gl::GetProgramInfoLog(
|
|
||||||
prog,
|
|
||||||
len,
|
|
||||||
std::ptr::null_mut(),
|
|
||||||
error.as_ptr() as *mut gl::types::GLchar,
|
|
||||||
);
|
|
||||||
return Err(error.to_string_lossy().into_owned());
|
|
||||||
}
|
|
||||||
gl::DeleteShader(vs);
|
|
||||||
gl::DeleteShader(fs);
|
|
||||||
Ok(prog)
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
pub use khronos_egl::*;
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
|
|
||||||
type EglInstance = Instance<khronos_egl::Dynamic<libloading::Library, khronos_egl::EGL1_5>>;
|
|
||||||
|
|
||||||
pub(crate) fn egl() -> &'static EglInstance {
|
|
||||||
static INSTANCE: OnceCell<EglInstance> = OnceCell::new();
|
|
||||||
INSTANCE.get_or_init(|| {
|
|
||||||
let lib = libloading::Library::new("libEGL.so").expect("unable to find libEGL.so");
|
|
||||||
unsafe {
|
|
||||||
khronos_egl::DynamicInstance::<khronos_egl::EGL1_5>::load_required_from(lib)
|
|
||||||
.expect("unable to load libEGL.so")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const LINUX_DMA_BUF_EXT: Enum = 0x3270;
|
|
||||||
pub const LINUX_DRM_FOURCC_EXT: Int = 0x3271;
|
|
||||||
pub const DMA_BUF_PLANE0_FD_EXT: Int = 0x3272;
|
|
||||||
pub const DMA_BUF_PLANE0_OFFSET_EXT: Int = 0x3273;
|
|
||||||
pub const DMA_BUF_PLANE0_PITCH_EXT: Int = 0x3274;
|
|
||||||
pub const DMA_BUF_PLANE0_MODIFIER_LO_EXT: Int = 0x3443;
|
|
||||||
pub const DMA_BUF_PLANE0_MODIFIER_HI_EXT: Int = 0x3444;
|
|
||||||
|
|
||||||
// GLAPI void APIENTRY glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image);
|
|
||||||
|
|
||||||
pub type ImageTargetTexture2DOesFn = extern "C" fn(gl::types::GLenum, gl::types::GLeglImageOES);
|
|
||||||
|
|
||||||
pub fn image_target_texture_2d_oes() -> Option<ImageTargetTexture2DOesFn> {
|
|
||||||
unsafe {
|
|
||||||
egl()
|
|
||||||
.get_proc_address("glEGLImageTargetTexture2DOES")
|
|
||||||
.map(|f| std::mem::transmute::<_, ImageTargetTexture2DOesFn>(f))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
use gtk::glib;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, glib::GErrorDomain)]
|
|
||||||
#[gerror_domain(name = "QemuGtk")]
|
|
||||||
pub enum QemuGtkError {
|
|
||||||
GL,
|
|
||||||
Failed,
|
|
||||||
}
|
|
@ -1,140 +0,0 @@
|
|||||||
use gst::prelude::*;
|
|
||||||
use gst_audio::StreamVolumeExt;
|
|
||||||
use std::thread::{self, JoinHandle};
|
|
||||||
use std::{collections::HashMap, error::Error};
|
|
||||||
|
|
||||||
use qemu_display_listener::{Audio, PCMInfo};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct OutStream {
|
|
||||||
pipeline: gst::Pipeline,
|
|
||||||
src: gst_app::AppSrc,
|
|
||||||
sink: gst::Element,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pcminfo_as_caps(info: &PCMInfo) -> String {
|
|
||||||
let format = format!(
|
|
||||||
"{}{}{}",
|
|
||||||
if info.is_float {
|
|
||||||
"F"
|
|
||||||
} else if info.is_signed {
|
|
||||||
"S"
|
|
||||||
} else {
|
|
||||||
"U"
|
|
||||||
},
|
|
||||||
info.bits,
|
|
||||||
if info.be { "BE" } else { "LE" }
|
|
||||||
);
|
|
||||||
format!(
|
|
||||||
"audio/x-raw,format={format},channels={channels},rate={rate},layout=interleaved",
|
|
||||||
format = format,
|
|
||||||
channels = info.nchannels,
|
|
||||||
rate = info.freq,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutStream {
|
|
||||||
fn new(info: &PCMInfo) -> Result<Self, Box<dyn Error>> {
|
|
||||||
let caps = pcminfo_as_caps(info);
|
|
||||||
let pipeline = &format!("appsrc name=src is-live=1 do-timestamp=0 format=time caps=\"{}\" ! queue ! audioconvert ! audioresample ! autoaudiosink name=sink", caps);
|
|
||||||
let pipeline = gst::parse_launch(pipeline)?;
|
|
||||||
let pipeline = pipeline.dynamic_cast::<gst::Pipeline>().unwrap();
|
|
||||||
let src = pipeline
|
|
||||||
.get_by_name("src")
|
|
||||||
.unwrap()
|
|
||||||
.dynamic_cast::<gst_app::AppSrc>()
|
|
||||||
.unwrap();
|
|
||||||
let sink = pipeline.get_by_name("sink").unwrap();
|
|
||||||
Ok(Self {
|
|
||||||
pipeline,
|
|
||||||
src,
|
|
||||||
sink,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct GstAudio {
|
|
||||||
out_thread: JoinHandle<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GstAudio {
|
|
||||||
pub fn new(audio: Audio) -> Result<Self, Box<dyn Error>> {
|
|
||||||
gst::init()?;
|
|
||||||
|
|
||||||
// TODO audio.listen_in() for capture.
|
|
||||||
let rx = audio.listen_out()?;
|
|
||||||
let mut out = HashMap::new();
|
|
||||||
let out_thread = thread::spawn(move || loop {
|
|
||||||
match rx.recv() {
|
|
||||||
Ok(event) => {
|
|
||||||
use qemu_display_listener::AudioOutEvent::*;
|
|
||||||
match event {
|
|
||||||
Init { id, info } => {
|
|
||||||
if out.contains_key(&id) {
|
|
||||||
eprintln!("Invalid Init, id {} is already setup", id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
match OutStream::new(&info) {
|
|
||||||
Ok(s) => {
|
|
||||||
out.insert(id, s);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Failed to create stream: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Fini { id } => {
|
|
||||||
out.remove(&id);
|
|
||||||
}
|
|
||||||
SetEnabled { id, enabled } => {
|
|
||||||
if let Some(s) = out.get(&id) {
|
|
||||||
if let Err(e) = s.pipeline.set_state(if enabled {
|
|
||||||
gst::State::Playing
|
|
||||||
} else {
|
|
||||||
gst::State::Ready
|
|
||||||
}) {
|
|
||||||
eprintln!("Failed to change state: {}", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("Stream was not setup yet: {}", id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SetVolume { id, volume } => {
|
|
||||||
if let Some(s) = out.get(&id) {
|
|
||||||
if let Some(stream_vol) = s
|
|
||||||
.pipeline
|
|
||||||
.get_by_interface(gst_audio::StreamVolume::static_type())
|
|
||||||
{
|
|
||||||
let stream_vol = stream_vol
|
|
||||||
.dynamic_cast::<gst_audio::StreamVolume>()
|
|
||||||
.unwrap();
|
|
||||||
stream_vol.set_mute(volume.mute);
|
|
||||||
if let Some(vol) = volume.volume.first() {
|
|
||||||
let vol = *vol as f64 / 255f64;
|
|
||||||
stream_vol
|
|
||||||
.set_volume(gst_audio::StreamVolumeFormat::Cubic, vol);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("Volume not implemented for this pipeline");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("Stream was not setup yet: {}", id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Write { id, data } => {
|
|
||||||
if let Some(s) = out.get(&id) {
|
|
||||||
let b = gst::Buffer::from_slice(data);
|
|
||||||
let _ = s.src.push_buffer(b);
|
|
||||||
} else {
|
|
||||||
eprintln!("Stream was not setup yet: {}", id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => eprintln!("Audio thread error: {}", e),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Ok(Self { out_thread })
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
#[allow(clippy::new_without_default)]
|
|
||||||
mod application;
|
|
||||||
#[rustfmt::skip]
|
|
||||||
mod config;
|
|
||||||
mod console;
|
|
||||||
mod console_area;
|
|
||||||
mod egl;
|
|
||||||
mod error;
|
|
||||||
mod gstaudio;
|
|
||||||
mod window;
|
|
||||||
|
|
||||||
use application::QemuApplication;
|
|
||||||
use config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE};
|
|
||||||
use gettextrs::*;
|
|
||||||
use gtk::gio;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// Initialize logger, debug is carried out via debug!, info!, and warn!.
|
|
||||||
pretty_env_logger::init();
|
|
||||||
|
|
||||||
// Prepare i18n
|
|
||||||
setlocale(LocaleCategory::LcAll, "");
|
|
||||||
bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
|
|
||||||
textdomain(GETTEXT_PACKAGE);
|
|
||||||
|
|
||||||
gtk::glib::set_application_name("QEMU Gtk");
|
|
||||||
gtk::glib::set_prgname(Some("qemu-gtk4"));
|
|
||||||
|
|
||||||
gtk::init().expect("Unable to start GTK4");
|
|
||||||
|
|
||||||
let res = gio::Resource::load(RESOURCES_FILE).expect("Could not load gresource file");
|
|
||||||
gio::resources_register(&res);
|
|
||||||
|
|
||||||
let app = QemuApplication::new();
|
|
||||||
app.run();
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
global_conf = configuration_data()
|
|
||||||
global_conf.set_quoted('APP_ID', application_id)
|
|
||||||
global_conf.set_quoted('PKGDATADIR', pkgdatadir)
|
|
||||||
global_conf.set_quoted('PROFILE', profile)
|
|
||||||
global_conf.set_quoted('VERSION', version + version_suffix)
|
|
||||||
global_conf.set_quoted('GETTEXT_PACKAGE', gettext_package)
|
|
||||||
global_conf.set_quoted('LOCALEDIR', localedir)
|
|
||||||
config = configure_file(
|
|
||||||
input: 'config.rs.in',
|
|
||||||
output: 'config.rs',
|
|
||||||
configuration: global_conf
|
|
||||||
)
|
|
||||||
# Copy the config.rs output to the source directory.
|
|
||||||
run_command(
|
|
||||||
'cp',
|
|
||||||
meson.build_root() / 'src' / 'config.rs',
|
|
||||||
meson.source_root() / 'src' / 'config.rs',
|
|
||||||
check: true
|
|
||||||
)
|
|
||||||
|
|
||||||
sources = files(
|
|
||||||
'application.rs',
|
|
||||||
'config.rs',
|
|
||||||
'console.rs',
|
|
||||||
'console_area.rs',
|
|
||||||
'egl.rs',
|
|
||||||
'main.rs',
|
|
||||||
'window.rs',
|
|
||||||
)
|
|
||||||
|
|
||||||
custom_target(
|
|
||||||
'cargo-build',
|
|
||||||
build_by_default: true,
|
|
||||||
input: sources,
|
|
||||||
output: meson.project_name(),
|
|
||||||
console: true,
|
|
||||||
install: true,
|
|
||||||
install_dir: bindir,
|
|
||||||
depends: resources,
|
|
||||||
command: [
|
|
||||||
cargo_script,
|
|
||||||
meson.build_root(),
|
|
||||||
meson.source_root(),
|
|
||||||
'@OUTPUT@',
|
|
||||||
profile,
|
|
||||||
meson.project_name(),
|
|
||||||
]
|
|
||||||
)
|
|
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
#version 130
|
|
||||||
|
|
||||||
in vec2 in_position;
|
|
||||||
out vec2 ex_tex_coord;
|
|
||||||
|
|
||||||
void main(void) {
|
|
||||||
gl_Position = vec4(in_position, 0.0, 1.0);
|
|
||||||
ex_tex_coord = vec2(1.0 + in_position.x, 1.0 + in_position.y) * 0.5;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
#version 130
|
|
||||||
|
|
||||||
uniform sampler2D tex_unit;
|
|
||||||
in mediump vec2 ex_tex_coord;
|
|
||||||
out mediump vec4 out_frag_color;
|
|
||||||
|
|
||||||
void main(void) {
|
|
||||||
out_frag_color = texture(tex_unit, ex_tex_coord);
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
#version 130
|
|
||||||
|
|
||||||
in vec2 in_position;
|
|
||||||
out vec2 ex_tex_coord;
|
|
||||||
|
|
||||||
void main(void) {
|
|
||||||
gl_Position = vec4(in_position, 0.0, 1.0);
|
|
||||||
ex_tex_coord = vec2(1.0 + in_position.x, 1.0 - in_position.y) * 0.5;
|
|
||||||
}
|
|
@ -1,125 +0,0 @@
|
|||||||
use crate::application::QemuApplication;
|
|
||||||
use crate::config::{APP_ID, PROFILE};
|
|
||||||
use crate::console::QemuConsole;
|
|
||||||
use glib::signal::Inhibit;
|
|
||||||
use gtk::subclass::prelude::*;
|
|
||||||
use gtk::{self, prelude::*};
|
|
||||||
use gtk::{gio, glib, CompositeTemplate};
|
|
||||||
use log::warn;
|
|
||||||
|
|
||||||
use qemu_display_listener::Console;
|
|
||||||
|
|
||||||
mod imp {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Debug, CompositeTemplate)]
|
|
||||||
#[template(resource = "/org/qemu/gtk4/window.ui")]
|
|
||||||
pub struct QemuApplicationWindow {
|
|
||||||
#[template_child]
|
|
||||||
pub headerbar: TemplateChild<gtk::HeaderBar>,
|
|
||||||
#[template_child]
|
|
||||||
pub console: TemplateChild<QemuConsole>,
|
|
||||||
pub settings: gio::Settings,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
|
||||||
impl ObjectSubclass for QemuApplicationWindow {
|
|
||||||
const NAME: &'static str = "QemuApplicationWindow";
|
|
||||||
type Type = super::QemuApplicationWindow;
|
|
||||||
type ParentType = gtk::ApplicationWindow;
|
|
||||||
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
headerbar: TemplateChild::default(),
|
|
||||||
console: TemplateChild::default(),
|
|
||||||
settings: gio::Settings::new(APP_ID),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn class_init(klass: &mut Self::Class) {
|
|
||||||
Self::bind_template(klass);
|
|
||||||
}
|
|
||||||
|
|
||||||
// You must call `Widget`'s `init_template()` within `instance_init()`.
|
|
||||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
|
||||||
obj.init_template();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectImpl for QemuApplicationWindow {
|
|
||||||
fn constructed(&self, obj: &Self::Type) {
|
|
||||||
self.parent_constructed(obj);
|
|
||||||
|
|
||||||
let builder = gtk::Builder::from_resource("/org/qemu/gtk4/shortcuts.ui");
|
|
||||||
let shortcuts = builder.get_object("shortcuts").unwrap();
|
|
||||||
obj.set_help_overlay(Some(&shortcuts));
|
|
||||||
|
|
||||||
// Devel Profile
|
|
||||||
if PROFILE == "Devel" {
|
|
||||||
obj.get_style_context().add_class("devel");
|
|
||||||
}
|
|
||||||
|
|
||||||
// load latest window state
|
|
||||||
obj.load_window_size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowImpl for QemuApplicationWindow {
|
|
||||||
// save window state on delete event
|
|
||||||
fn close_request(&self, obj: &Self::Type) -> Inhibit {
|
|
||||||
if let Err(err) = obj.save_window_size() {
|
|
||||||
warn!("Failed to save window state, {}", &err);
|
|
||||||
}
|
|
||||||
Inhibit(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WidgetImpl for QemuApplicationWindow {}
|
|
||||||
impl ApplicationWindowImpl for QemuApplicationWindow {}
|
|
||||||
}
|
|
||||||
|
|
||||||
glib::wrapper! {
|
|
||||||
pub struct QemuApplicationWindow(ObjectSubclass<imp::QemuApplicationWindow>)
|
|
||||||
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, @implements gio::ActionMap, gio::ActionGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QemuApplicationWindow {
|
|
||||||
pub fn new(app: &QemuApplication, console: Console) -> Self {
|
|
||||||
let window: Self = glib::Object::new(&[]).expect("Failed to create QemuApplicationWindow");
|
|
||||||
window.set_application(Some(app));
|
|
||||||
|
|
||||||
let win = &imp::QemuApplicationWindow::from_instance(&window);
|
|
||||||
win.console.set_qemu_console(console);
|
|
||||||
// Set icons for shell
|
|
||||||
gtk::Window::set_default_icon_name(APP_ID);
|
|
||||||
|
|
||||||
window
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_window_size(&self) -> Result<(), glib::BoolError> {
|
|
||||||
let settings = &imp::QemuApplicationWindow::from_instance(self).settings;
|
|
||||||
|
|
||||||
let size = self.get_default_size();
|
|
||||||
|
|
||||||
settings.set_int("window-width", size.0)?;
|
|
||||||
settings.set_int("window-height", size.1)?;
|
|
||||||
|
|
||||||
settings.set_boolean("is-maximized", self.is_maximized())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_window_size(&self) {
|
|
||||||
let settings = &imp::QemuApplicationWindow::from_instance(self).settings;
|
|
||||||
|
|
||||||
let width = settings.get_int("window-width");
|
|
||||||
let height = settings.get_int("window-height");
|
|
||||||
let is_maximized = settings.get_boolean("is-maximized");
|
|
||||||
|
|
||||||
self.set_default_size(width, height);
|
|
||||||
|
|
||||||
if is_maximized {
|
|
||||||
self.maximize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user