mirror of
https://github.com/vlinkz/nix-software-center.git
synced 2024-11-22 04:04:06 +03:00
Initial commit
This commit is contained in:
commit
175e7c23e9
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.envrc
|
||||
/.direnv
|
||||
/.vscode
|
||||
/result
|
||||
/src/config.rs
|
||||
/target
|
3089
Cargo.lock
generated
Normal file
3089
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
41
Cargo.toml
Normal file
41
Cargo.toml
Normal file
@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "nix-software-center"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
default-run = "nix-software-center"
|
||||
|
||||
[dependencies]
|
||||
relm4 = { git = "https://github.com/Relm4/Relm4", tag = "v0.5.0-beta.2", features = ["all"] }
|
||||
relm4-components = { package = "relm4-components", git = "https://github.com/Relm4/Relm4", tag = "v0.5.0-beta.2"}
|
||||
adw = { package = "libadwaita", git = "https://gitlab.gnome.org/World/Rust/libadwaita-rs", features = ["v1_2"] }
|
||||
tokio = { version = "1.20", features = ["rt", "macros", "time", "rt-multi-thread", "sync", "process"] }
|
||||
sourceview5 = { git = "https://gitlab.gnome.org/World/Rust/sourceview5-rs/", rev = "6082210f7d1fc32b100bd9c714e9521eecacb3f7", features = ["v5_4"] }
|
||||
tracker = "0.1"
|
||||
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
simd-json = { version = "0.6", features = ["allow-non-simd"] }
|
||||
|
||||
nix-editor = "0.2.12"
|
||||
|
||||
html2pango = "0.4"
|
||||
brotli = "3.3"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
flate2 = "1.0"
|
||||
quick-xml = { version = "0.23", features = ["serialize"] }
|
||||
rand = "0.8"
|
||||
reqwest = { version = "0.11", features = ["blocking"] }
|
||||
sha256 = "1.0"
|
||||
image = "0.24"
|
||||
spdx = "0.9"
|
||||
# { git = "https://github.com/EmbarkStudios/spdx", version = "0.8" }
|
||||
edit-distance = "2.1"
|
||||
ijson = "0.1"
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
|
||||
[workspace]
|
||||
members = [".", "nsc-helper"]
|
||||
default-members = [".", "nsc-helper"]
|
9
build-aux/dist-vendor.sh
Normal file
9
build-aux/dist-vendor.sh
Normal file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
export SOURCE_ROOT="$1"
|
||||
export DIST="$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"
|
12
data/dev.vlinkz.NixSoftwareCenter.desktop.in.in
Normal file
12
data/dev.vlinkz.NixSoftwareCenter.desktop.in.in
Normal file
@ -0,0 +1,12 @@
|
||||
[Desktop Entry]
|
||||
Name=Software Center
|
||||
Comment=Install Applications
|
||||
Type=Application
|
||||
Exec=nix-software-center
|
||||
Terminal=false
|
||||
Categories=Settings;System;Utility;
|
||||
# Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
|
||||
Keywords=Nix;Nixos;nix;nixos;software;package;
|
||||
# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
|
||||
Icon=@icon@
|
||||
StartupNotify=true
|
5
data/dev.vlinkz.NixSoftwareCenter.gschema.xml.in
Normal file
5
data/dev.vlinkz.NixSoftwareCenter.gschema.xml.in
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<schemalist>
|
||||
<schema path="/dev/vlinkz/NixSoftwareCenter/" id="@app-id@" gettext-domain="@gettext-package@">
|
||||
</schema>
|
||||
</schemalist>
|
27
data/dev.vlinkz.NixSoftwareCenter.metainfo.xml.in.in
Normal file
27
data/dev.vlinkz.NixSoftwareCenter.metainfo.xml.in.in
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>@app-id@</id>
|
||||
<metadata_license>CC-BY-SA-4.0</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<name>Nix Software Center</name>
|
||||
<summary>A simple application to manage your NixOS packages.</summary>
|
||||
<description>
|
||||
<p>A simple software center to install and manage Nix packages, built with libadwaita, GTK4, and Relm4.</p>
|
||||
</description>
|
||||
<screenshots>
|
||||
<!-- <screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/vlinkz/nixos-conf-editor/main/data/screenshots/listviewlight.png</image>
|
||||
<caption>Main window</caption>
|
||||
</screenshot>
|
||||
<screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/vlinkz/nixos-conf-editor/main/data/screenshots/optionlight2.png</image>
|
||||
<caption>Option Selection</caption>
|
||||
</screenshot> -->
|
||||
</screenshots>
|
||||
<url type="homepage">https://github.com/vlinkz/nix-software-center</url>
|
||||
<url type="bugtracker">https://github.com/vlinkz/nix-software-center/issues</url>
|
||||
<developer_name>Victor Fuentes</developer_name>
|
||||
<update_contact>vmfuentes64@gmail.com</update_contact>
|
||||
<translation type="gettext">@gettext-package@</translation>
|
||||
<launchable type="desktop-id">@app-id@.desktop</launchable>
|
||||
</component>
|
17
data/dev.vlinkz.NixSoftwareCenter.policy.in.in
Normal file
17
data/dev.vlinkz.NixSoftwareCenter.policy.in.in
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
<vendor>Victor Fuentes</vendor>
|
||||
<vendor_url>https://github.com/vlinkz</vendor_url>
|
||||
<action id="dev.vlinkz.NixSoftwareCenter">
|
||||
<description>Give Nix Software Center root access</description>
|
||||
<message>Authentication is required to install NixOS system packages</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">@pkglibexecdir@/nsc-helper</annotate>
|
||||
</action>
|
||||
</policyconfig>
|
84
data/icons/dev.vlinkz.NixSoftwareCenter.Devel.svg
Normal file
84
data/icons/dev.vlinkz.NixSoftwareCenter.Devel.svg
Normal file
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="128px" viewBox="0 0 128 128" width="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<linearGradient id="a" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#99b2e5"/>
|
||||
<stop offset="0.243452" stop-color="#a9c2e5"/>
|
||||
<stop offset="1" stop-color="#c2cdeb"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1="29.258924657" x2="52.12594020815" xlink:href="#a" y1="63.99513142558" y2="103.54451872902"/>
|
||||
<linearGradient id="c" gradientTransform="matrix(0.127763 0.221291 -0.221291 0.127763 96.086827 -56.897127)" x1="200.59668" x2="290.087006" xlink:href="#a" y1="351.411163" y2="506.188141"/>
|
||||
<linearGradient id="d" gradientTransform="matrix(0.127763 -0.221291 0.221291 0.127763 -54.103341 92.070101)" x1="200.59668" x2="290.087006" xlink:href="#a" y1="351.411163" y2="506.188141"/>
|
||||
<linearGradient id="e" gradientTransform="matrix(-0.255525 0 0 -0.255525 150.106936 147.648442)" x1="200.59668" x2="290.087006" xlink:href="#a" y1="351.411163" y2="506.188141"/>
|
||||
<linearGradient id="f" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#636378"/>
|
||||
<stop offset="0.231686" stop-color="#6a6b8f"/>
|
||||
<stop offset="1" stop-color="#444465"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="g" x1="31.62242739098" x2="54.0836659203" xlink:href="#f" y1="62.6498143668" y2="102.35286903923"/>
|
||||
<linearGradient id="h" gradientTransform="matrix(-0.127763 0.221291 -0.221291 -0.127763 177.158345 261.09881)" x1="-584.199341" x2="-496.297028" xlink:href="#f" y1="782.335632" y2="937.713989"/>
|
||||
<linearGradient id="i" gradientTransform="matrix(-0.127763 -0.221291 0.221291 -0.127763 -166.023458 58.751427)" x1="-584.199341" x2="-496.297028" xlink:href="#f" y1="782.335632" y2="937.713989"/>
|
||||
<clipPath id="j">
|
||||
<rect height="128" width="128"/>
|
||||
</clipPath>
|
||||
<clipPath id="k">
|
||||
<rect height="128" width="128"/>
|
||||
</clipPath>
|
||||
<filter id="l" height="100%" width="100%" x="0%" y="0%">
|
||||
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
|
||||
</filter>
|
||||
<mask id="m">
|
||||
<g clip-path="url(#k)" filter="url(#l)">
|
||||
<g clip-path="url(#j)">
|
||||
<path d="m 39.046875 62.296875 l 31.222656 54.085937 l -14.347656 0.136719 l -8.335937 -14.53125 l -8.398438 14.453125 l -7.128906 -0.003906 l -3.652344 -6.308594 l 11.960938 -20.566406 l -8.488282 -14.777344 z m 0 0" fill="url(#b)" fill-rule="evenodd"/>
|
||||
<path d="m 89.023438 65.707031 l -31.222657 -54.089843 l 0.039063 -6.152344 l 22.644531 20.550781 l 19.214844 -14.296875 l -0.035157 6.152344 l -11.960937 20.566406 l 8.527344 8.625 l -0.039063 6.152344 z m 0 0" fill="#d7dded" fill-rule="evenodd"/>
|
||||
<path d="m 75.277344 44.613281 l -62.457032 -0.003906 l -0.007812 -6.140625 l 23.820312 -6.304688 l -8.320312 -14.5 l -0.007812 -6.140624 l 10.867187 -0.039063 l 11.828125 20.640625 l 24.265625 6.347656 z m 0 0" fill="#3d3846" fill-rule="evenodd"/>
|
||||
<path d="m 50.277344 46.167969 l -31.226563 54.085937 l -7.292969 -12.359375 l 0.039063 -6.152343 l 8.378906 -8.332032 l -16.714843 -0.042968 l -3.5625 -6.175782 l 0.0351558 -6.152344 l 27.3945312 -0.089843 l 22.988281 -20.933594 z m 0 0" fill="#d7dded" fill-rule="evenodd"/>
|
||||
<path d="m 52.675781 89.402344 l 0.035157 -6.152344 l 62.453124 0.003906 l -0.035156 6.152344 l -7.058594 12.496094 l -16.753906 -0.046875 l 8.359375 8.34375 l -0.039062 6.152343 l -3.566407 6.175782 l -7.289062 0.007812 l -11.832031 -20.644531 l -17.042969 -0.035156 z m 0 0" fill="#d7dded" fill-rule="evenodd"/>
|
||||
<path d="m 31.878906 74.785156 l 38.390625 41.597656 l -0.035156 6.15625 l -14.351563 0.132813 l -8.335937 -14.53125 l -8.394531 14.453125 l -7.128906 -0.003906 l -3.652344 -6.308594 l 0.035156 -6.152344 l 11.925781 -14.414062 l -8.492187 -14.777344 z m 0 0" fill="#3d3846" fill-rule="evenodd"/>
|
||||
<path d="m 77.710938 87.972656 l 0.011718 -6.148437 l 38.519532 -41.726563 l -0.007813 6.148438 l -8.417969 14.488281 l 20.285156 0.070313 l -0.007812 6.148437 l -3.636719 6.316406 l -23.792969 -0.074219 l -8.550781 14.742188 z m 0 0" fill="#3d3846" fill-rule="evenodd"/>
|
||||
<path d="m 50.316406 40.015625 l -31.230468 54.085937 l -7.289063 -12.359374 l 8.414063 -14.484376 l -16.714844 -0.042968 l -3.5625002 -6.175782 l 3.6367182 -6.320312 l 23.792969 0.078125 l 8.550781 -14.742187 z m 0 0" fill="url(#c)" fill-rule="evenodd"/>
|
||||
<path d="m 52.710938 83.25 l 62.453124 0.003906 l -7.054687 12.496094 l -16.753906 -0.046875 l 8.320312 14.496094 l -3.570312 6.175781 l -7.289063 0.007812 l -11.828125 -20.644531 l -17.042969 -0.035156 z m 0 0" fill="url(#d)" fill-rule="evenodd"/>
|
||||
<path d="m 89.0625 59.554688 l -31.222656 -54.089844 l 14.347656 -0.136719 l 8.335938 14.535156 l 8.394531 -14.457031 l 7.132812 0.003906 l 3.648438 6.308594 l -11.960938 20.566406 l 8.492188 14.777344 z m 0 0" fill="url(#e)" fill-rule="evenodd"/>
|
||||
<path d="m 39.046875 62.296875 l 31.222656 54.085937 l -14.347656 0.136719 l -8.335937 -14.53125 l -8.398438 14.453125 l -7.128906 -0.003906 l -3.652344 -6.308594 l 11.960938 -20.566406 l -8.488282 -14.777344 z m 0 0" fill="url(#g)" fill-rule="evenodd"/>
|
||||
<path d="m 75.265625 38.472656 l -62.453125 -0.003906 l 7.058594 -12.492188 l 16.753906 0.046876 l -8.320312 -14.5 l 3.566406 -6.171876 l 7.289062 -0.007812 l 11.832032 20.640625 l 17.042968 0.035156 z m 0 0" fill="url(#h)" fill-rule="evenodd"/>
|
||||
<path d="m 77.722656 81.824219 l 31.226563 -54.085938 l 7.292969 12.359375 l -8.417969 14.484375 l 16.714843 0.046875 l 3.5625 6.175782 l -3.636718 6.316406 l -23.792969 -0.074219 l -8.550781 14.738281 z m 0 0" fill="url(#i)" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</g>
|
||||
</mask>
|
||||
<mask id="n">
|
||||
<g filter="url(#l)">
|
||||
<rect fill-opacity="0.8" height="128" width="128"/>
|
||||
</g>
|
||||
</mask>
|
||||
<linearGradient id="o" gradientTransform="matrix(0 0.37 -0.98462 0 295.38501 -30.360001)" gradientUnits="userSpaceOnUse" x1="300" x2="428" y1="235" y2="235">
|
||||
<stop offset="0" stop-color="#f9f06b"/>
|
||||
<stop offset="1" stop-color="#f5c211"/>
|
||||
</linearGradient>
|
||||
<clipPath id="p">
|
||||
<rect height="128" width="128"/>
|
||||
</clipPath>
|
||||
<clipPath id="q">
|
||||
<rect height="128" width="128"/>
|
||||
</clipPath>
|
||||
<g fill-rule="evenodd">
|
||||
<path d="m 39.046875 62.296875 l 31.222656 54.085937 l -14.347656 0.136719 l -8.335937 -14.53125 l -8.398438 14.453125 l -7.128906 -0.003906 l -3.652344 -6.308594 l 11.960938 -20.566406 l -8.488282 -14.777344 z m 0 0" fill="url(#b)"/>
|
||||
<path d="m 89.023438 65.707031 l -31.222657 -54.089843 l 0.039063 -6.152344 l 22.644531 20.550781 l 19.214844 -14.296875 l -0.035157 6.152344 l -11.960937 20.566406 l 8.527344 8.625 l -0.039063 6.152344 z m 0 0" fill="#d7dded"/>
|
||||
<path d="m 75.277344 44.613281 l -62.457032 -0.003906 l -0.007812 -6.140625 l 23.820312 -6.304688 l -8.320312 -14.5 l -0.007812 -6.140624 l 10.867187 -0.039063 l 11.828125 20.640625 l 24.265625 6.347656 z m 0 0" fill="#3d3846"/>
|
||||
<path d="m 50.277344 46.167969 l -31.226563 54.085937 l -7.292969 -12.359375 l 0.039063 -6.152343 l 8.378906 -8.332032 l -16.714843 -0.042968 l -3.5625 -6.175782 l 0.0351558 -6.152344 l 27.3945312 -0.089843 l 22.988281 -20.933594 z m 0 0" fill="#d7dded"/>
|
||||
<path d="m 52.675781 89.402344 l 0.035157 -6.152344 l 62.453124 0.003906 l -0.035156 6.152344 l -7.058594 12.496094 l -16.753906 -0.046875 l 8.359375 8.34375 l -0.039062 6.152343 l -3.566407 6.175782 l -7.289062 0.007812 l -11.832031 -20.644531 l -17.042969 -0.035156 z m 0 0" fill="#d7dded"/>
|
||||
<path d="m 31.878906 74.785156 l 38.390625 41.597656 l -0.035156 6.15625 l -14.351563 0.132813 l -8.335937 -14.53125 l -8.394531 14.453125 l -7.128906 -0.003906 l -3.652344 -6.308594 l 0.035156 -6.152344 l 11.925781 -14.414062 l -8.492187 -14.777344 z m 0 0" fill="#3d3846"/>
|
||||
<path d="m 77.710938 87.972656 l 0.011718 -6.148437 l 38.519532 -41.726563 l -0.007813 6.148438 l -8.417969 14.488281 l 20.285156 0.070313 l -0.007812 6.148437 l -3.636719 6.316406 l -23.792969 -0.074219 l -8.550781 14.742188 z m 0 0" fill="#3d3846"/>
|
||||
<path d="m 50.316406 40.015625 l -31.230468 54.085937 l -7.289063 -12.359374 l 8.414063 -14.484376 l -16.714844 -0.042968 l -3.5625002 -6.175782 l 3.6367182 -6.320312 l 23.792969 0.078125 l 8.550781 -14.742187 z m 0 0" fill="url(#c)"/>
|
||||
<path d="m 52.710938 83.25 l 62.453124 0.003906 l -7.054687 12.496094 l -16.753906 -0.046875 l 8.320312 14.496094 l -3.570312 6.175781 l -7.289063 0.007812 l -11.828125 -20.644531 l -17.042969 -0.035156 z m 0 0" fill="url(#d)"/>
|
||||
<path d="m 89.0625 59.554688 l -31.222656 -54.089844 l 14.347656 -0.136719 l 8.335938 14.535156 l 8.394531 -14.457031 l 7.132812 0.003906 l 3.648438 6.308594 l -11.960938 20.566406 l 8.492188 14.777344 z m 0 0" fill="url(#e)"/>
|
||||
<path d="m 39.046875 62.296875 l 31.222656 54.085937 l -14.347656 0.136719 l -8.335937 -14.53125 l -8.398438 14.453125 l -7.128906 -0.003906 l -3.652344 -6.308594 l 11.960938 -20.566406 l -8.488282 -14.777344 z m 0 0" fill="url(#g)"/>
|
||||
<path d="m 75.265625 38.472656 l -62.453125 -0.003906 l 7.058594 -12.492188 l 16.753906 0.046876 l -8.320312 -14.5 l 3.566406 -6.171876 l 7.289062 -0.007812 l 11.832032 20.640625 l 17.042968 0.035156 z m 0 0" fill="url(#h)"/>
|
||||
<path d="m 77.722656 81.824219 l 31.226563 -54.085938 l 7.292969 12.359375 l -8.417969 14.484375 l 16.714843 0.046875 l 3.5625 6.175782 l -3.636718 6.316406 l -23.792969 -0.074219 l -8.550781 14.738281 z m 0 0" fill="url(#i)"/>
|
||||
</g>
|
||||
<g clip-path="url(#q)" mask="url(#m)">
|
||||
<g clip-path="url(#p)" mask="url(#n)">
|
||||
<path d="m 128 80.640625 v 47.359375 h -128 v -47.359375 z m 0 0" fill="url(#o)"/>
|
||||
<path d="m 13.308594 80.640625 l 47.355468 47.359375 h 21.214844 l -47.359375 -47.359375 z m 42.421875 0 l 47.363281 47.359375 h 21.214844 l -47.363282 -47.359375 z m 42.429687 0 l 29.839844 29.839844 v -21.210938 l -8.628906 -8.628906 z m -98.160156 7.90625 v 21.214844 l 18.238281 18.238281 h 21.214844 z m 0 0"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 10 KiB |
35
data/icons/dev.vlinkz.NixSoftwareCenter.svg
Normal file
35
data/icons/dev.vlinkz.NixSoftwareCenter.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="128px" viewBox="0 0 128 128" width="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<linearGradient id="a" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#99b2e5"/>
|
||||
<stop offset="0.243452" stop-color="#a9c2e5"/>
|
||||
<stop offset="1" stop-color="#c2cdeb"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1="29.258924657" x2="52.12594020815" xlink:href="#a" y1="63.99513142558" y2="103.54451872902"/>
|
||||
<linearGradient id="c" gradientTransform="matrix(0.127763 0.221291 -0.221291 0.127763 96.086827 -56.897127)" x1="200.59668" x2="290.087006" xlink:href="#a" y1="351.411163" y2="506.188141"/>
|
||||
<linearGradient id="d" gradientTransform="matrix(0.127763 -0.221291 0.221291 0.127763 -54.103341 92.070101)" x1="200.59668" x2="290.087006" xlink:href="#a" y1="351.411163" y2="506.188141"/>
|
||||
<linearGradient id="e" gradientTransform="matrix(-0.255525 0 0 -0.255525 150.106936 147.648442)" x1="200.59668" x2="290.087006" xlink:href="#a" y1="351.411163" y2="506.188141"/>
|
||||
<linearGradient id="f" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#636378"/>
|
||||
<stop offset="0.231686" stop-color="#6a6b8f"/>
|
||||
<stop offset="1" stop-color="#444465"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="g" x1="31.62242739098" x2="54.0836659203" xlink:href="#f" y1="62.6498143668" y2="102.35286903923"/>
|
||||
<linearGradient id="h" gradientTransform="matrix(-0.127763 0.221291 -0.221291 -0.127763 177.158345 261.09881)" x1="-584.199341" x2="-496.297028" xlink:href="#f" y1="782.335632" y2="937.713989"/>
|
||||
<linearGradient id="i" gradientTransform="matrix(-0.127763 -0.221291 0.221291 -0.127763 -166.023458 58.751427)" x1="-584.199341" x2="-496.297028" xlink:href="#f" y1="782.335632" y2="937.713989"/>
|
||||
<g fill-rule="evenodd">
|
||||
<path d="m 39.046875 62.296875 l 31.222656 54.085937 l -14.347656 0.136719 l -8.335937 -14.53125 l -8.398438 14.453125 l -7.128906 -0.003906 l -3.652344 -6.308594 l 11.960938 -20.566406 l -8.488282 -14.777344 z m 0 0" fill="url(#b)"/>
|
||||
<path d="m 89.023438 65.707031 l -31.222657 -54.089843 l 0.039063 -6.152344 l 22.644531 20.550781 l 19.214844 -14.296875 l -0.035157 6.152344 l -11.960937 20.566406 l 8.527344 8.625 l -0.039063 6.152344 z m 0 0" fill="#d7dded"/>
|
||||
<path d="m 75.277344 44.613281 l -62.457032 -0.003906 l -0.007812 -6.140625 l 23.820312 -6.304688 l -8.320312 -14.5 l -0.007812 -6.140624 l 10.867187 -0.039063 l 11.828125 20.640625 l 24.265625 6.347656 z m 0 0" fill="#3d3846"/>
|
||||
<path d="m 50.277344 46.167969 l -31.226563 54.085937 l -7.292969 -12.359375 l 0.039063 -6.152343 l 8.378906 -8.332032 l -16.714843 -0.042968 l -3.5625 -6.175782 l 0.0351558 -6.152344 l 27.3945312 -0.089843 l 22.988281 -20.933594 z m 0 0" fill="#d7dded"/>
|
||||
<path d="m 52.675781 89.402344 l 0.035157 -6.152344 l 62.453124 0.003906 l -0.035156 6.152344 l -7.058594 12.496094 l -16.753906 -0.046875 l 8.359375 8.34375 l -0.039062 6.152343 l -3.566407 6.175782 l -7.289062 0.007812 l -11.832031 -20.644531 l -17.042969 -0.035156 z m 0 0" fill="#d7dded"/>
|
||||
<path d="m 31.878906 74.785156 l 38.390625 41.597656 l -0.035156 6.15625 l -14.351563 0.132813 l -8.335937 -14.53125 l -8.394531 14.453125 l -7.128906 -0.003906 l -3.652344 -6.308594 l 0.035156 -6.152344 l 11.925781 -14.414062 l -8.492187 -14.777344 z m 0 0" fill="#3d3846"/>
|
||||
<path d="m 77.710938 87.972656 l 0.011718 -6.148437 l 38.519532 -41.726563 l -0.007813 6.148438 l -8.417969 14.488281 l 20.285156 0.070313 l -0.007812 6.148437 l -3.636719 6.316406 l -23.792969 -0.074219 l -8.550781 14.742188 z m 0 0" fill="#3d3846"/>
|
||||
<path d="m 50.316406 40.015625 l -31.230468 54.085937 l -7.289063 -12.359374 l 8.414063 -14.484376 l -16.714844 -0.042968 l -3.5625002 -6.175782 l 3.6367182 -6.320312 l 23.792969 0.078125 l 8.550781 -14.742187 z m 0 0" fill="url(#c)"/>
|
||||
<path d="m 52.710938 83.25 l 62.453124 0.003906 l -7.054687 12.496094 l -16.753906 -0.046875 l 8.320312 14.496094 l -3.570312 6.175781 l -7.289063 0.007812 l -11.828125 -20.644531 l -17.042969 -0.035156 z m 0 0" fill="url(#d)"/>
|
||||
<path d="m 89.0625 59.554688 l -31.222656 -54.089844 l 14.347656 -0.136719 l 8.335938 14.535156 l 8.394531 -14.457031 l 7.132812 0.003906 l 3.648438 6.308594 l -11.960938 20.566406 l 8.492188 14.777344 z m 0 0" fill="url(#e)"/>
|
||||
<path d="m 39.046875 62.296875 l 31.222656 54.085937 l -14.347656 0.136719 l -8.335937 -14.53125 l -8.398438 14.453125 l -7.128906 -0.003906 l -3.652344 -6.308594 l 11.960938 -20.566406 l -8.488282 -14.777344 z m 0 0" fill="url(#g)"/>
|
||||
<path d="m 75.265625 38.472656 l -62.453125 -0.003906 l 7.058594 -12.492188 l 16.753906 0.046876 l -8.320312 -14.5 l 3.566406 -6.171876 l 7.289062 -0.007812 l 11.832032 20.640625 l 17.042968 0.035156 z m 0 0" fill="url(#h)"/>
|
||||
<path d="m 77.722656 81.824219 l 31.226563 -54.085938 l 7.292969 12.359375 l -8.417969 14.484375 l 16.714843 0.046875 l 3.5625 6.175782 l -3.636718 6.316406 l -23.792969 -0.074219 l -8.550781 14.738281 z m 0 0" fill="url(#i)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.0 KiB |
825
data/icons/dev.vlinkz.NixSoftwareCenter.template.svg
Normal file
825
data/icons/dev.vlinkz.NixSoftwareCenter.template.svg
Normal file
@ -0,0 +1,825 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-filename="Template.png"
|
||||
width="128"
|
||||
height="128"
|
||||
id="svg11300"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
sodipodi:docname="template.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
version="1.0"
|
||||
style="display:inline;enable-background:new"
|
||||
viewBox="0 0 128 128"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<title
|
||||
id="title4162">Adwaita Icon Template</title>
|
||||
<defs
|
||||
id="defs3">
|
||||
<linearGradient
|
||||
y2="236"
|
||||
x2="96"
|
||||
y1="236"
|
||||
x1="32"
|
||||
gradientTransform="translate(604.81684,170.58641)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient1099"
|
||||
xlink:href="#linearGradient1036" />
|
||||
<linearGradient
|
||||
id="linearGradient1036">
|
||||
<stop
|
||||
id="stop1032"
|
||||
offset="0"
|
||||
style="stop-color:#d5d3cf;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop1034"
|
||||
offset="1"
|
||||
style="stop-color:#f6f5f4;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
r="32"
|
||||
fy="-76"
|
||||
fx="-244"
|
||||
cy="-76"
|
||||
cx="-244"
|
||||
gradientTransform="matrix(0.88333331,0,0,0.88333331,-460.35018,463.11973)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient1103"
|
||||
xlink:href="#linearGradient1069" />
|
||||
<linearGradient
|
||||
id="linearGradient1069">
|
||||
<stop
|
||||
id="stop1065"
|
||||
offset="0"
|
||||
style="stop-color:#d5d3cf;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop1067-1"
|
||||
offset="1"
|
||||
style="stop-color:#949390;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="232"
|
||||
x2="64"
|
||||
y1="262.5"
|
||||
x1="64"
|
||||
id="linearGradient1027"
|
||||
xlink:href="#linearGradient1025"
|
||||
gradientTransform="translate(-470.5864,432.81685)" />
|
||||
<linearGradient
|
||||
id="linearGradient1025">
|
||||
<stop
|
||||
id="stop1021"
|
||||
offset="0"
|
||||
style="stop-color:#9a9996;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop1023"
|
||||
offset="1"
|
||||
style="stop-color:#77767b;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<inkscape:path-effect
|
||||
effect="spiro"
|
||||
id="path-effect35304-9"
|
||||
is_visible="true" />
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath1609-7">
|
||||
<path
|
||||
sodipodi:nodetypes="cccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path1611-5"
|
||||
d="m 252,116 28,-28 v -8 h -36 v 36 z"
|
||||
style="fill:#e74747;stroke:none;stroke-width:0.25px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<linearGradient
|
||||
y2="515.97058"
|
||||
x2="282.26105"
|
||||
y1="338.62445"
|
||||
x1="213.95642"
|
||||
gradientTransform="translate(983.36076,601.38885)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient5855"
|
||||
xlink:href="#linearGradient5960"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient5960"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop5962"
|
||||
offset="0"
|
||||
style="stop-color:#637ddf;stop-opacity:1" />
|
||||
<stop
|
||||
style="stop-color:#649afa;stop-opacity:1"
|
||||
offset="0.23168644"
|
||||
id="stop5964" />
|
||||
<stop
|
||||
id="stop5966"
|
||||
offset="1"
|
||||
style="stop-color:#719efa;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5562"
|
||||
id="linearGradient4328"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(70.650339,-1055.1511)"
|
||||
x1="200.59668"
|
||||
y1="351.41116"
|
||||
x2="290.08701"
|
||||
y2="506.18814" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient5562">
|
||||
<stop
|
||||
style="stop-color:#99b2e5;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5564" />
|
||||
<stop
|
||||
id="stop5566"
|
||||
offset="0.24345198"
|
||||
style="stop-color:#a9c2e5;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#c2cdeb;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop5568" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5053"
|
||||
id="linearGradient4330"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(864.69589,-1491.3405)"
|
||||
x1="-584.19934"
|
||||
y1="782.33563"
|
||||
x2="-496.29703"
|
||||
y2="937.71399" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient5053">
|
||||
<stop
|
||||
style="stop-color:#636378;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5055" />
|
||||
<stop
|
||||
id="stop5057"
|
||||
offset="0.23168644"
|
||||
style="stop-color:#6a6b8f;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#444465;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop5059" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
stroke="#ef2929"
|
||||
fill="#f57900"
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="0.25490196"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="4.3387241"
|
||||
inkscape:cx="64.880825"
|
||||
inkscape:cy="82.051772"
|
||||
inkscape:current-layer="layer4"
|
||||
showgrid="false"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1131"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
width="400px"
|
||||
height="300px"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-bbox="true"
|
||||
objecttolerance="7"
|
||||
gridtolerance="12"
|
||||
guidetolerance="13"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pagecheckerboard="false"
|
||||
showguides="false"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:locked="false"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
showborder="false"
|
||||
inkscape:snap-center="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-text-baseline="true"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid5883"
|
||||
spacingx="2"
|
||||
spacingy="2"
|
||||
enabled="true"
|
||||
visible="true"
|
||||
empspacing="4"
|
||||
originx="0"
|
||||
originy="0" />
|
||||
<sodipodi:guide
|
||||
position="64,8"
|
||||
orientation="0,1"
|
||||
id="guide1073"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="12,64"
|
||||
orientation="1,0"
|
||||
id="guide1075"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="64,104"
|
||||
orientation="0,1"
|
||||
id="guide1099"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="64,128"
|
||||
orientation="0,1"
|
||||
id="guide993"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="104,64"
|
||||
orientation="1,0"
|
||||
id="guide995"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="9.2651362e-08,64"
|
||||
orientation="1,0"
|
||||
id="guide867"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="120,64"
|
||||
orientation="1,0"
|
||||
id="guide869"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="64,116"
|
||||
orientation="0,1"
|
||||
id="guide871"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid873"
|
||||
spacingx="1"
|
||||
spacingy="1"
|
||||
empspacing="8"
|
||||
color="#000000"
|
||||
opacity="0.49019608"
|
||||
empcolor="#000000"
|
||||
empopacity="0.08627451"
|
||||
dotted="true" />
|
||||
<sodipodi:guide
|
||||
position="24,64"
|
||||
orientation="1,0"
|
||||
id="guide877"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="116,64"
|
||||
orientation="1,0"
|
||||
id="guide879"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="64,120"
|
||||
orientation="0,1"
|
||||
id="guide881"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="64,12"
|
||||
orientation="0,1"
|
||||
id="guide883"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="8,64"
|
||||
orientation="1,0"
|
||||
id="guide885"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="128,64"
|
||||
orientation="1,0"
|
||||
id="guide887"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="64,0"
|
||||
orientation="0,1"
|
||||
id="guide897"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="64,24"
|
||||
orientation="0,1"
|
||||
id="guide899"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="256,256"
|
||||
orientation="-0.70710678,0.70710678"
|
||||
id="guide950"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
<sodipodi:guide
|
||||
position="64,64"
|
||||
orientation="0.70710678,0.70710678"
|
||||
id="guide952"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,0,255)" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>GNOME Design Team</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:source />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||
<dc:title>Adwaita Icon Template</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag />
|
||||
</dc:subject>
|
||||
<dc:date />
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title />
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title />
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier />
|
||||
<dc:relation />
|
||||
<dc:language />
|
||||
<dc:coverage />
|
||||
<dc:description />
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title />
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="App Icon"
|
||||
inkscape:groupmode="layer"
|
||||
style="display:inline"
|
||||
transform="translate(0,-172)">
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="baseplate"
|
||||
style="display:inline"
|
||||
sodipodi:insensitive="true">
|
||||
<g
|
||||
style="display:inline;fill:#000000;enable-background:new"
|
||||
transform="matrix(7.9911709,0,0,8.0036407,-167.7909,-4846.0776)"
|
||||
id="g12027"
|
||||
inkscape:export-xdpi="12"
|
||||
inkscape:export-ydpi="12" />
|
||||
<rect
|
||||
style="display:inline;overflow:visible;visibility:visible;fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5;marker:none;enable-background:accumulate"
|
||||
id="rect13805"
|
||||
width="128"
|
||||
height="128"
|
||||
x="9.2651362e-08"
|
||||
y="172"
|
||||
inkscape:label="512x512" />
|
||||
<g
|
||||
id="g883"
|
||||
style="fill:none;fill-opacity:0.25098039;stroke:#a579b3;stroke-opacity:1"
|
||||
transform="translate(-24,24)" />
|
||||
<g
|
||||
id="g900"
|
||||
style="fill:none;fill-opacity:0.25098039;stroke:#a579b3;stroke-opacity:1"
|
||||
transform="translate(-24,24)" />
|
||||
<rect
|
||||
inkscape:label="512x512"
|
||||
y="172"
|
||||
x="160"
|
||||
height="16"
|
||||
width="16"
|
||||
id="rect859"
|
||||
style="display:inline;overflow:visible;visibility:visible;fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5;marker:none;enable-background:accumulate" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4px;line-height:125%;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.332649;enable-background:new"
|
||||
x="0"
|
||||
y="164"
|
||||
id="text863"
|
||||
inkscape:label="icon-name"><tspan
|
||||
style="font-size:4px;stroke-width:0.332649"
|
||||
sodipodi:role="line"
|
||||
id="tspan861"
|
||||
x="0"
|
||||
y="164">Hicolor</tspan></text>
|
||||
<text
|
||||
inkscape:label="icon-name"
|
||||
id="text867"
|
||||
y="164"
|
||||
x="160"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4px;line-height:125%;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.332649;enable-background:new"
|
||||
xml:space="preserve"><tspan
|
||||
y="164"
|
||||
x="160"
|
||||
id="tspan865"
|
||||
sodipodi:role="line"
|
||||
style="font-size:4px;stroke-width:0.332649">Symbolic</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer9"
|
||||
inkscape:label="icons"
|
||||
style="display:inline" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="hairlines"
|
||||
style="display:inline"
|
||||
sodipodi:insensitive="true">
|
||||
<circle
|
||||
cx="64.000031"
|
||||
cy="236"
|
||||
r="59.504131"
|
||||
id="circle2892"
|
||||
style="display:inline;opacity:0.1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.99000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.99000001, 0.99000001;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new" />
|
||||
<rect
|
||||
ry="7.9292889"
|
||||
rx="8.701004"
|
||||
y="180.49496"
|
||||
x="20.495007"
|
||||
height="111.01005"
|
||||
width="87.009987"
|
||||
id="rect2894"
|
||||
style="display:inline;opacity:0.1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.99000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.99000001, 0.99000001;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new" />
|
||||
<rect
|
||||
ry="7.9238095"
|
||||
rx="7.9238095"
|
||||
y="184.49524"
|
||||
x="12.495266"
|
||||
height="103.00952"
|
||||
width="103.00952"
|
||||
id="rect2896"
|
||||
style="display:inline;opacity:0.1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.99000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.99000001, 0.99000001;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new" />
|
||||
<rect
|
||||
ry="8.701005"
|
||||
rx="7.9292889"
|
||||
y="200.49496"
|
||||
x="8.4950066"
|
||||
height="87.010048"
|
||||
width="111.01004"
|
||||
id="rect2898"
|
||||
style="display:inline;opacity:0.1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.99000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.99000001, 0.99000001;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path2900"
|
||||
d="M 2.6203015e-5,288.99999 H 128.00003"
|
||||
style="display:inline;fill:none;stroke:#62a0ea;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;enable-background:new" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer4"
|
||||
inkscape:label="devicon"
|
||||
style="display:inline;enable-background:new"
|
||||
transform="matrix(0.23955471,0,0,0.23955471,31.935221,217.79694)">
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer7"
|
||||
inkscape:label="bg"
|
||||
style="display:none"
|
||||
transform="matrix(1.0666667,0,0,1.0666667,-159.08108,-195.28677)">
|
||||
<rect
|
||||
transform="translate(-132.5822,958.04022)"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect5389"
|
||||
width="1543.4283"
|
||||
height="483.7439"
|
||||
x="132.5822"
|
||||
y="-957.77832" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer6"
|
||||
inkscape:label="logo-guide"
|
||||
style="display:none"
|
||||
transform="matrix(1.0666667,0,0,1.0666667,-300.5021,826.62283)">
|
||||
<rect
|
||||
y="-958.02759"
|
||||
x="132.65129"
|
||||
height="484.30399"
|
||||
width="550.41602"
|
||||
id="rect5379"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5c201e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
inkscape:export-filename="/home/tim/dev/nix/homepage/logo/nix-wiki.png"
|
||||
inkscape:export-xdpi="22.07"
|
||||
inkscape:export-ydpi="22.07" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#c24a46;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect5372"
|
||||
width="501.94415"
|
||||
height="434.30405"
|
||||
x="156.12303"
|
||||
y="-933.02759"
|
||||
inkscape:export-filename="/home/tim/dev/nix/homepage/logo/nixos-logo-only-hires-print.png"
|
||||
inkscape:export-xdpi="212.2"
|
||||
inkscape:export-ydpi="212.2" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#d98d8a;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect5381"
|
||||
width="24.939611"
|
||||
height="24.939611"
|
||||
x="658.02826"
|
||||
y="-958.04022" />
|
||||
</g>
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3336-6"
|
||||
d="m 309.54892,-710.38827 122.19683,211.67512 -56.15706,0.5268 -32.6236,-56.8692 -32.85645,56.5653 -27.90237,-0.011 -14.29086,-24.6896 46.81047,-80.4901 -33.22946,-57.8257 z"
|
||||
style="display:inline;enable-background:new;opacity:1;fill:url(#linearGradient4328);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:label="path3336-6"
|
||||
transform="matrix(1.0666667,0,0,1.0666667,-300.5021,826.62282)" />
|
||||
<g
|
||||
inkscape:label="print-logo"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1-5"
|
||||
style="display:inline"
|
||||
transform="matrix(1.0666667,0,0,1.0666667,-300.50356,852.16062)"
|
||||
sodipodi:insensitive="true">
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:none;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#3d3846;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 309.40365,-710.2521 122.34347,187.59727 -0.14664,24.07783 -56.15706,0.5268 -32.6236,-56.8692 -32.85645,56.5653 -27.90237,-0.011 -14.29086,-24.6896 0.14664,-24.07783 46.66383,-56.41237 -33.22946,-57.8256 0.14664,-24.07773 z"
|
||||
id="path4861"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccc" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#d7dded;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 505.14318,-720.9886 -122.19683,-211.6751 0.14663,-24.07782 88.63403,80.42022 75.19631,-55.94252 -0.14663,24.07782 -46.81047,80.4902 33.37609,33.74778 -0.14663,24.07782 z"
|
||||
id="use4867"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccccc"
|
||||
transform="translate(3.5325942e-7,-1.2250463e-5)" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="use4875"
|
||||
d="m 451.3364,-803.53264 -244.4144,-0.012 -0.0331,-24.02908 93.21726,-24.68602 -32.55875,-56.73717 -0.033,-24.02908 42.52119,-0.16093 46.3013,80.78414 94.96643,24.84107 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#3d3846;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
transform="translate(-1.7899967e-6)" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#d7dded;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 353.50926,-797.4433 -122.21756,211.6631 -28.53477,-48.37 0.14666,-24.07787 32.79173,-32.60963 -65.41521,-0.1719 -13.9414,-24.1698 0.14638,-24.07767 107.20176,-0.34943 89.96911,-81.91465 z"
|
||||
id="use4863"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccc" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#d7dded;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 362.88537,-628.243 0.14658,-24.07778 244.41444,0.012 -0.14663,24.07778 -27.62229,48.8968 -65.56199,-0.1817 32.70539,32.6594 -0.14663,24.0777 -13.96098,24.1585 -28.52722,0.032 -46.3013,-80.7841 -66.69317,-0.1353 z"
|
||||
id="use4865"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4873"
|
||||
d="m 281.49779,-685.44833 150.24933,162.7935 -0.14664,24.07783 -56.15706,0.5268 -32.6236,-56.8692 -32.85645,56.5653 -27.90237,-0.011 -14.29086,-24.6896 0.14664,-24.07783 46.66383,-56.41237 -33.22946,-57.8256 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#3d3846;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3633"
|
||||
d="m 460.87178,-633.8425 0.0347,-24.06524 150.75237,-163.29308 -0.0349,24.06531 -32.93839,56.68751 79.39183,0.27618 -0.0353,24.06542 -14.23636,24.7211 -93.11177,-0.294 -33.46371,57.6904 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#3d3846;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<g
|
||||
id="layer2-3"
|
||||
inkscape:label="guides"
|
||||
style="display:none"
|
||||
transform="translate(72.039038,-1799.4476)">
|
||||
<path
|
||||
d="M 460.60629,594.72881 209.74183,594.7288 84.309616,377.4738 209.74185,160.21882 l 250.86446,1e-5 125.43222,217.255 z"
|
||||
inkscape:randomized="0"
|
||||
inkscape:rounded="0"
|
||||
inkscape:flatsided="true"
|
||||
sodipodi:arg2="1.5707963"
|
||||
sodipodi:arg1="1.0471976"
|
||||
sodipodi:r2="217.25499"
|
||||
sodipodi:r1="250.86446"
|
||||
sodipodi:cy="377.47382"
|
||||
sodipodi:cx="335.17407"
|
||||
sodipodi:sides="6"
|
||||
id="path6032"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.236;fill:#4e4d52;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
sodipodi:type="star" />
|
||||
<path
|
||||
transform="translate(0,-308.26772)"
|
||||
sodipodi:type="star"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#4e4d52;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="path5875"
|
||||
sodipodi:sides="6"
|
||||
sodipodi:cx="335.17407"
|
||||
sodipodi:cy="685.74158"
|
||||
sodipodi:r1="100.83495"
|
||||
sodipodi:r2="87.32563"
|
||||
sodipodi:arg1="1.0471976"
|
||||
sodipodi:arg2="1.5707963"
|
||||
inkscape:flatsided="true"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 385.59154,773.06721 -100.83495,0 -50.41747,-87.32564 50.41748,-87.32563 100.83495,10e-6 50.41748,87.32563 z" />
|
||||
<path
|
||||
transform="translate(0,-308.26772)"
|
||||
sodipodi:nodetypes="ccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5851"
|
||||
d="m 1216.5591,938.53395 123.0545,228.14035 -42.6807,-1.2616 -43.4823,-79.7725 -39.6506,80.3267 -32.6875,-19.7984 53.4737,-100.2848 -37.1157,-73.88955 z"
|
||||
style="fill:url(#linearGradient5855);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.415;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#c53a3a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect5884"
|
||||
width="48.834862"
|
||||
height="226.22897"
|
||||
x="-34.74221"
|
||||
y="446.17056"
|
||||
transform="rotate(-30)" />
|
||||
<path
|
||||
transform="translate(0,-308.26772)"
|
||||
sodipodi:type="star"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.509;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path3428"
|
||||
sodipodi:sides="6"
|
||||
sodipodi:cx="223.93674"
|
||||
sodipodi:cy="878.63831"
|
||||
sodipodi:r1="28.048939"
|
||||
sodipodi:r2="24.291094"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="0.52359878"
|
||||
inkscape:flatsided="true"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 251.98568,878.63831 -14.02447,24.29109 h -28.04894 l -14.02447,-24.29109 14.02447,-24.2911 h 28.04894 z" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#rect5884"
|
||||
id="use4252"
|
||||
transform="rotate(60,268.29786,489.4515)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:0.650794;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect4254"
|
||||
width="5.3947482"
|
||||
height="115.12564"
|
||||
x="545.71014"
|
||||
y="467.07007"
|
||||
transform="rotate(30,575.23539,-154.13386)" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3-5"
|
||||
inkscape:label="gradient-logo"
|
||||
style="display:inline"
|
||||
transform="matrix(1.0666667,0,0,1.0666667,-300.5021,826.62283)"
|
||||
sodipodi:insensitive="true">
|
||||
<use
|
||||
height="100%"
|
||||
width="100%"
|
||||
transform="matrix(0.46874999,0.81189879,-0.81189879,0.46874999,395.66027,-853.96471)"
|
||||
id="use3439-6"
|
||||
inkscape:transform-center-y="151.59082"
|
||||
inkscape:transform-center-x="124.43045"
|
||||
xlink:href="#path3336-6"
|
||||
y="0"
|
||||
x="0"
|
||||
style="display:inline" />
|
||||
<use
|
||||
height="100%"
|
||||
width="100%"
|
||||
transform="matrix(0.46874999,-0.81189879,0.81189879,0.46874999,293.19668,-636.56447)"
|
||||
id="use3445-0"
|
||||
inkscape:transform-center-y="75.573958"
|
||||
inkscape:transform-center-x="-168.20651"
|
||||
xlink:href="#path3336-6"
|
||||
y="0"
|
||||
x="0"
|
||||
style="display:inline" />
|
||||
<use
|
||||
height="100%"
|
||||
width="100%"
|
||||
transform="matrix(-0.93749997,0,0,-0.93749997,533.11665,-656.55412)"
|
||||
id="use3449-5"
|
||||
inkscape:transform-center-y="-139.94592"
|
||||
inkscape:transform-center-x="59.669705"
|
||||
xlink:href="#path3336-6"
|
||||
y="0"
|
||||
x="0"
|
||||
style="display:inline" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient4330);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 309.54892,-710.38827 122.19683,211.67512 -56.15706,0.5268 -32.6236,-56.8692 -32.85645,56.5653 -27.90237,-0.011 -14.29086,-24.6896 46.81047,-80.4901 -33.22946,-57.8256 z"
|
||||
id="path4260-0"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccccc" />
|
||||
<use
|
||||
height="100%"
|
||||
width="100%"
|
||||
transform="rotate(120,407.33916,-716.08356)"
|
||||
id="use4354-5"
|
||||
xlink:href="#path4260-0"
|
||||
y="0"
|
||||
x="0"
|
||||
style="display:inline" />
|
||||
<use
|
||||
height="100%"
|
||||
width="100%"
|
||||
transform="rotate(-120,407.28823,-715.86995)"
|
||||
id="use4362-2"
|
||||
xlink:href="#path4260-0"
|
||||
y="0"
|
||||
x="0"
|
||||
style="display:inline;fill:#77767b"
|
||||
inkscape:label="use4362-2"
|
||||
inkscape:highlight-color="#5a3be3" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 37 KiB |
10
data/icons/meson.build
Normal file
10
data/icons/meson.build
Normal file
@ -0,0 +1,10 @@
|
||||
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)
|
||||
)
|
93
data/meson.build
Normal file
93
data/meson.build
Normal file
@ -0,0 +1,93 @@
|
||||
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()
|
||||
],
|
||||
depends: desktop_file,
|
||||
)
|
||||
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()
|
||||
],
|
||||
depends: appdata_file,
|
||||
)
|
||||
endif
|
||||
|
||||
# Policy file
|
||||
dataconf = configuration_data()
|
||||
dataconf.set('pkglibexecdir',
|
||||
libexecdir
|
||||
)
|
||||
i18n.merge_file(
|
||||
input : configure_file(
|
||||
configuration: dataconf,
|
||||
input : '@0@.policy.in.in'.format(base_id),
|
||||
output: '@BASENAME@'
|
||||
),
|
||||
output: '@0@.policy'.format(base_id),
|
||||
po_dir: podir,
|
||||
install: true,
|
||||
install_dir: datadir / 'polkit-1' / 'actions'
|
||||
)
|
||||
|
||||
# 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_build_dir()
|
||||
],
|
||||
)
|
||||
endif
|
71
default.nix
Normal file
71
default.nix
Normal file
@ -0,0 +1,71 @@
|
||||
{
|
||||
pkgs ? import <nixpkgs> {},
|
||||
lib ? import <nixpkgs/lib>,
|
||||
}:
|
||||
let
|
||||
libadwaita-git = pkgs.libadwaita.overrideAttrs (oldAttrs: rec {
|
||||
version = "1.2.beta";
|
||||
src = pkgs.fetchFromGitLab {
|
||||
domain = "gitlab.gnome.org";
|
||||
owner = "GNOME";
|
||||
repo = "libadwaita";
|
||||
rev = version;
|
||||
hash = "sha256-QBblkeNAgfHi5YQxaV9ceqNDyDIGu8d6pvLcT6apm6o=";
|
||||
};
|
||||
});
|
||||
nixos-appstream-data = (import (pkgs.fetchFromGitHub {
|
||||
owner = "vlinkz";
|
||||
repo = "nixos-appstream-data";
|
||||
rev = "66b3399e6d81017c10265611a151d1109ff1af1b";
|
||||
hash = "sha256-oiEZD4sMpb2djxReg99GUo0RHWAehxSyQBbiz8Z4DJk=";
|
||||
}) {stdenv = pkgs.stdenv; lib = pkgs.lib; pkgs = pkgs; });
|
||||
in pkgs.stdenv.mkDerivation rec {
|
||||
pname = "nix-software-center";
|
||||
version = "0.0.1";
|
||||
|
||||
src = [ ./. ];
|
||||
|
||||
cargoDeps = pkgs.rustPlatform.fetchCargoTarball {
|
||||
inherit src;
|
||||
name = "${pname}-${version}";
|
||||
hash = "sha256-EI9zULrlN+GvtDO0PvtAEA1YjJAbK+SDZ8NSRZf+2Rw=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
appstream-glib
|
||||
polkit
|
||||
gettext
|
||||
desktop-file-utils
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
git
|
||||
wrapGAppsHook4
|
||||
] ++ (with pkgs.rustPlatform; [
|
||||
cargoSetupHook
|
||||
rust.cargo
|
||||
rust.rustc
|
||||
]);
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk4
|
||||
gtksourceview5
|
||||
libadwaita-git
|
||||
openssl
|
||||
wayland
|
||||
gnome.adwaita-icon-theme
|
||||
desktop-file-utils
|
||||
nixos-appstream-data
|
||||
];
|
||||
|
||||
# mesonFlags = [
|
||||
# "-Dprofile=development"
|
||||
# ];
|
||||
|
||||
patchPhase = ''
|
||||
substituteInPlace ./src/lib.rs \
|
||||
--replace "/usr/share/app-info" "${nixos-appstream-data}/share/app-info"
|
||||
'';
|
||||
}
|
109
flake.lock
Normal file
109
flake.lock
Normal file
@ -0,0 +1,109 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1642700792,
|
||||
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"mach-nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"pypi-deps-db": "pypi-deps-db"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1661359648,
|
||||
"narHash": "sha256-BRz30Xg/g535oRJA3xEcXf0KM6GTJPugt2lgaom3D6g=",
|
||||
"owner": "DavHau",
|
||||
"repo": "mach-nix",
|
||||
"rev": "6cd3929b1561c3eef68f5fc6a08b57cf95c41ec1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "mach-nix",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1643805626,
|
||||
"narHash": "sha256-AXLDVMG+UaAGsGSpOtQHPIKB+IZ0KSd9WS77aanGzgc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "554d2d8aa25b6e583575459c297ec23750adb6cb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1661361016,
|
||||
"narHash": "sha256-Bjf6ZDnDc6glTwIIItvwfcaeJ5zWFM6GYfPajSArdUY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b784c5ae63dd288375af1b4d37b8a27dd8061887",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pypi-deps-db": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1661155889,
|
||||
"narHash": "sha256-t00mBTZhmZBT4jteO6pJbU0wyRS6/ep4pKmQNeztEms=",
|
||||
"owner": "DavHau",
|
||||
"repo": "pypi-deps-db",
|
||||
"rev": "49c620f3de2b557c9d5c44f58a00fee59f27d1b0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "DavHau",
|
||||
"repo": "pypi-deps-db",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"mach-nix": "mach-nix",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"locked": {
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
71
flake.nix
Normal file
71
flake.nix
Normal file
@ -0,0 +1,71 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, utils, mach-nix }:
|
||||
utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
libadwaita-git = pkgs.libadwaita.overrideAttrs (oldAttrs: rec {
|
||||
version = "1.2.beta";
|
||||
src = pkgs.fetchFromGitLab {
|
||||
domain = "gitlab.gnome.org";
|
||||
owner = "GNOME";
|
||||
repo = "libadwaita";
|
||||
rev = version;
|
||||
hash = "sha256-QBblkeNAgfHi5YQxaV9ceqNDyDIGu8d6pvLcT6apm6o=";
|
||||
};
|
||||
});
|
||||
nixos-appstream-data = pkgs.fetchFromGitHub {
|
||||
owner = "vlinkz";
|
||||
repo = "nixos-appstream-data";
|
||||
rev = "66b3399e6d81017c10265611a151d1109ff1af1b";
|
||||
hash = "sha256-oiEZD4sMpb2djxReg99GUo0RHWAehxSyQBbiz8Z4DJk=";
|
||||
};
|
||||
name = "nix-software-center";
|
||||
in
|
||||
rec
|
||||
{
|
||||
packages.${name} = pkgs.callPackage ./default.nix {
|
||||
inherit (inputs);
|
||||
};
|
||||
|
||||
# `nix build`
|
||||
defaultPackage = packages.${name};
|
||||
|
||||
# `nix run`
|
||||
apps.${name} = utils.lib.mkApp {
|
||||
inherit name;
|
||||
drv = packages.${name};
|
||||
};
|
||||
defaultApp = packages.${name};
|
||||
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
cargo
|
||||
clippy
|
||||
rust-analyzer
|
||||
rustc
|
||||
rustfmt
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
gobject-introspection
|
||||
graphene
|
||||
gtk4
|
||||
gtksourceview5
|
||||
libadwaita-git
|
||||
openssl
|
||||
pandoc
|
||||
pango
|
||||
pkgconfig
|
||||
wrapGAppsHook4
|
||||
nixos-appstream-data
|
||||
];
|
||||
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||
};
|
||||
});
|
||||
}
|
70
meson.build
Normal file
70
meson.build
Normal file
@ -0,0 +1,70 @@
|
||||
project(
|
||||
'nix-software-center',
|
||||
'rust',
|
||||
version: '0.0.1',
|
||||
meson_version: '>= 0.59',
|
||||
license: 'MIT',
|
||||
)
|
||||
|
||||
i18n = import('i18n')
|
||||
gnome = import('gnome')
|
||||
|
||||
base_id = 'dev.vlinkz.NixSoftwareCenter'
|
||||
|
||||
dependency('openssl', version: '>= 1.0')
|
||||
dependency('glib-2.0', version: '>= 2.66')
|
||||
dependency('gio-2.0', version: '>= 2.66')
|
||||
dependency('gtk4', version: '>= 4.0.0')
|
||||
dependency('libadwaita-1', version: '>=1.2.alpha')
|
||||
dependency('polkit-gobject-1', version: '>= 0.103')
|
||||
|
||||
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)
|
||||
|
||||
version = meson.project_version()
|
||||
|
||||
prefix = get_option('prefix')
|
||||
bindir = prefix / get_option('bindir')
|
||||
libexecdir = prefix / get_option('libexecdir')
|
||||
localedir = prefix / get_option('localedir')
|
||||
|
||||
datadir = prefix / get_option('datadir')
|
||||
pkgdatadir = datadir / meson.project_name()
|
||||
iconsdir = datadir / 'icons'
|
||||
podir = meson.project_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.project_build_root() / 'meson-dist' / meson.project_name() + '-' + version,
|
||||
meson.project_source_root()
|
||||
)
|
||||
|
||||
subdir('data')
|
||||
subdir('po')
|
||||
subdir('src')
|
||||
subdir('nsc-helper/src')
|
||||
|
||||
gnome.post_install(
|
||||
gtk_update_icon_cache: true,
|
||||
glib_compile_schemas: true,
|
||||
update_desktop_database: true,
|
||||
)
|
9
meson_options.txt
Normal file
9
meson_options.txt
Normal file
@ -0,0 +1,9 @@
|
||||
option(
|
||||
'profile',
|
||||
type: 'combo',
|
||||
choices: [
|
||||
'default',
|
||||
'development'
|
||||
],
|
||||
value: 'default',
|
||||
)
|
2
nsc-helper/.gitignore
vendored
Normal file
2
nsc-helper/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/.vscode
|
12
nsc-helper/Cargo.toml
Normal file
12
nsc-helper/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "nsc-helper"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "3.2", features = ["derive"] }
|
||||
users = "0.11"
|
||||
|
||||
[[bin]]
|
||||
name = "nsc-helper"
|
||||
path = "src/main.rs"
|
134
nsc-helper/src/main.rs
Normal file
134
nsc-helper/src/main.rs
Normal file
@ -0,0 +1,134 @@
|
||||
use clap::{self, FromArgMatches, Subcommand};
|
||||
use std::{
|
||||
error::Error,
|
||||
fs::{File, self},
|
||||
io::{self, Read, Write},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
#[derive(clap::Subcommand)]
|
||||
enum SubCommands {
|
||||
Config {
|
||||
/// Write stdin to file in path output
|
||||
#[clap(short, long)]
|
||||
output: String,
|
||||
/// Run `nixos-rebuild` with the given arguments
|
||||
arguments: Vec<String>,
|
||||
},
|
||||
Rebuild {
|
||||
/// Run `nixos-rebuild` with the given arguments
|
||||
arguments: Vec<String>,
|
||||
},
|
||||
Channel {
|
||||
/// Whether to rebuild the system after updating channels
|
||||
#[clap(short, long)]
|
||||
rebuild: bool,
|
||||
/// Run `nixos-rebuild` with the given arguments
|
||||
arguments: Vec<String>,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = SubCommands::augment_subcommands(clap::Command::new(
|
||||
"Helper binary for Nix Software Center",
|
||||
));
|
||||
let matches = cli.get_matches();
|
||||
let derived_subcommands = SubCommands::from_arg_matches(&matches)
|
||||
.map_err(|err| err.exit())
|
||||
.unwrap();
|
||||
|
||||
if users::get_effective_uid() != 0 {
|
||||
eprintln!("nsc-helper must be run as root");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
match derived_subcommands {
|
||||
SubCommands::Config { output, arguments } => {
|
||||
let old = fs::read_to_string(&output);
|
||||
match write_file(&output) {
|
||||
Ok(_) => match rebuild(arguments) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
if let Ok(o) = old {
|
||||
if fs::write(&output, o).is_err() {
|
||||
eprintln!("Could not restore old file");
|
||||
}
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
SubCommands::Rebuild { arguments } => match rebuild(arguments) {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
SubCommands::Channel { rebuild: dorebuild, arguments } => {
|
||||
match dorebuild {
|
||||
true => match rebuild(arguments) {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
false => match channel() {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_file(path: &str) -> Result<(), Box<dyn Error>> {
|
||||
let stdin = io::stdin();
|
||||
let mut buf = String::new();
|
||||
stdin.lock().read_to_string(&mut buf)?;
|
||||
let mut file = File::create(path)?;
|
||||
write!(file, "{}", &buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rebuild(args: Vec<String>) -> Result<(), Box<dyn Error>> {
|
||||
let mut cmd = Command::new("nixos-rebuild")
|
||||
.args(args)
|
||||
.spawn()?;
|
||||
let x = cmd.wait()?;
|
||||
if x.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
eprintln!("nixos-rebuild failed with exit code {}", x.code().unwrap());
|
||||
Err(Box::new(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"nixos-rebuild failed",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn channel() -> Result<(), Box<dyn Error>> {
|
||||
let mut cmd = Command::new("nix-channel")
|
||||
.arg("--update")
|
||||
.spawn()?;
|
||||
let x = cmd.wait()?;
|
||||
if x.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
eprintln!("nixos-rebuild failed with exit code {}", x.code().unwrap());
|
||||
Err(Box::new(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"nix-channel failed",
|
||||
)))
|
||||
}
|
||||
}
|
32
nsc-helper/src/meson.build
Normal file
32
nsc-helper/src/meson.build
Normal file
@ -0,0 +1,32 @@
|
||||
global_conf = configuration_data()
|
||||
cargo_options = [ '--manifest-path', meson.project_source_root() / 'nsc-helper' / 'Cargo.toml' ]
|
||||
cargo_options += [ '--target-dir', meson.project_build_root() / 'nsc-helper' / 'src' ]
|
||||
|
||||
if get_option('profile') == 'default'
|
||||
cargo_options += [ '--release' ]
|
||||
rust_target = 'release'
|
||||
message('Building in release mode')
|
||||
else
|
||||
rust_target = 'debug'
|
||||
message('Building in debug mode')
|
||||
endif
|
||||
|
||||
cargo_env = [ 'CARGO_HOME=' + meson.project_build_root() / 'nsc-helper' / 'cargo-home' ]
|
||||
|
||||
cargo_build = custom_target(
|
||||
'cargo-build',
|
||||
build_by_default: true,
|
||||
build_always_stale: true,
|
||||
output: 'nsc-helper',
|
||||
console: true,
|
||||
install: true,
|
||||
install_dir: get_option('libexecdir'),
|
||||
command: [
|
||||
'env',
|
||||
cargo_env,
|
||||
cargo, 'build',
|
||||
cargo_options,
|
||||
'&&',
|
||||
'cp', 'src' / rust_target / 'nsc-helper', '@OUTPUT@',
|
||||
]
|
||||
)
|
0
po/LINGUAS
Normal file
0
po/LINGUAS
Normal file
4
po/POTFILES.in
Normal file
4
po/POTFILES.in
Normal file
@ -0,0 +1,4 @@
|
||||
data/dev.vlinkz.NixSoftwareCenter.policy.in.in
|
||||
data/dev.vlinkz.NixSoftwareCenter.desktop.in.in
|
||||
data/dev.vlinkz.NixSoftwareCenter.metainfo.xml.in.in
|
||||
data/dev.vlinkz.NixSoftwareCenter.metainfo.gschema.xml.in
|
1
po/meson.build
Normal file
1
po/meson.build
Normal file
@ -0,0 +1 @@
|
||||
i18n.gettext(gettext_package, preset: 'glib')
|
7
src/config.rs.in
Normal file
7
src/config.rs.in
Normal file
@ -0,0 +1,7 @@
|
||||
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@;
|
4
src/lib.rs
Normal file
4
src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod ui;
|
||||
pub mod parse;
|
||||
pub mod config;
|
||||
static APPINFO: &str = "/usr/share/app-info";
|
8
src/main.rs
Normal file
8
src/main.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use nix_software_center::ui::window::AppModel;
|
||||
use relm4::*;
|
||||
fn main() {
|
||||
pretty_env_logger::init();
|
||||
let app = RelmApp::new("dev.vlinkz.NixSoftwareCenter");
|
||||
let application = app.app.clone();
|
||||
app.run::<AppModel>(application);
|
||||
}
|
51
src/meson.build
Normal file
51
src/meson.build
Normal file
@ -0,0 +1,51 @@
|
||||
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.project_build_root() / 'src' / 'config.rs',
|
||||
meson.project_source_root() / 'src' / 'config.rs',
|
||||
check: true
|
||||
)
|
||||
|
||||
cargo_options = [ '--manifest-path', meson.project_source_root() / 'Cargo.toml' ]
|
||||
cargo_options += [ '--target-dir', meson.project_build_root() / 'src' ]
|
||||
|
||||
if get_option('profile') == 'default'
|
||||
cargo_options += [ '--release' ]
|
||||
rust_target = 'release'
|
||||
message('Building in release mode')
|
||||
else
|
||||
rust_target = 'debug'
|
||||
message('Building in debug mode')
|
||||
endif
|
||||
|
||||
cargo_env = [ 'CARGO_HOME=' + meson.project_build_root() / 'cargo-home' ]
|
||||
|
||||
cargo_build = custom_target(
|
||||
'cargo-build',
|
||||
build_by_default: true,
|
||||
build_always_stale: true,
|
||||
output: meson.project_name(),
|
||||
console: true,
|
||||
install: true,
|
||||
install_dir: bindir,
|
||||
command: [
|
||||
'env',
|
||||
cargo_env,
|
||||
cargo, 'build',
|
||||
cargo_options,
|
||||
'&&',
|
||||
'cp', 'src' / rust_target / meson.project_name(), '@OUTPUT@',
|
||||
]
|
||||
)
|
263
src/parse/cache.rs
Normal file
263
src/parse/cache.rs
Normal file
@ -0,0 +1,263 @@
|
||||
use ijson::IString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
error::Error,
|
||||
fs::{self, File},
|
||||
io::{BufReader, Read, Write},
|
||||
path::Path,
|
||||
process::Command,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct NewPackageBase {
|
||||
packages: HashMap<String, NewPackage>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct NewPackage {
|
||||
version: IString,
|
||||
}
|
||||
|
||||
pub fn checkcache() -> Result<(), Box<dyn Error>> {
|
||||
setuppkgscache()?;
|
||||
setupupdatecache()?;
|
||||
setupnewestver()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uptodate() -> Result<Option<(String, String)>, Box<dyn Error>> {
|
||||
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
|
||||
let oldversion = fs::read_to_string(format!("{}/sysver.txt", cachedir))?
|
||||
.trim()
|
||||
.to_string();
|
||||
let newversion = fs::read_to_string(format!("{}/chnver.txt", cachedir))?
|
||||
.trim()
|
||||
.to_string();
|
||||
if oldversion == newversion {
|
||||
println!("System is up to date");
|
||||
Ok(None)
|
||||
} else {
|
||||
println!("OLD {:?} != NEW {:?}", oldversion, newversion);
|
||||
Ok(Some((oldversion, newversion)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn channelver() -> Result<Option<(String, String)>, Box<dyn Error>> {
|
||||
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
|
||||
let oldversion = fs::read_to_string(format!("{}/chnver.txt", cachedir))?
|
||||
.trim()
|
||||
.to_string();
|
||||
let newversion = fs::read_to_string(format!("{}/newver.txt", cachedir))?
|
||||
.trim()
|
||||
.to_string();
|
||||
if oldversion == newversion {
|
||||
println!("Channels match");
|
||||
Ok(None)
|
||||
} else {
|
||||
println!("chnver {:?} != newver {:?}", oldversion, newversion);
|
||||
Ok(Some((oldversion, newversion)))
|
||||
}
|
||||
}
|
||||
|
||||
fn setuppkgscache() -> Result<(), Box<dyn Error>> {
|
||||
let vout = Command::new("nix-instantiate")
|
||||
.arg("-I")
|
||||
.arg("nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos")
|
||||
.arg("<nixpkgs/lib>")
|
||||
.arg("-A")
|
||||
.arg("version")
|
||||
.arg("--eval")
|
||||
.arg("--json")
|
||||
.output()?;
|
||||
|
||||
let dlver = String::from_utf8_lossy(&vout.stdout)
|
||||
.to_string()
|
||||
.replace('"', "");
|
||||
|
||||
let mut relver = dlver.split('.').collect::<Vec<&str>>().join(".")[0..5].to_string();
|
||||
|
||||
if dlver.len() >= 8 && &dlver[5..8] == "pre" {
|
||||
relver = "unstable".to_string();
|
||||
}
|
||||
|
||||
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
|
||||
fs::create_dir_all(&cachedir).expect("Failed to create cache directory");
|
||||
let url = format!(
|
||||
"https://releases.nixos.org/nixos/{}/nixos-{}/packages.json.br",
|
||||
relver, dlver
|
||||
);
|
||||
|
||||
println!("VERSION {}", relver);
|
||||
// let response = reqwest::blocking::get(url)?;
|
||||
// if let Some(latest) = response.url().to_string().split('/').last() {
|
||||
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
|
||||
if !Path::new(&cachedir).exists() {
|
||||
fs::create_dir_all(&cachedir).expect("Failed to create cache directory");
|
||||
}
|
||||
|
||||
if !Path::new(&format!("{}/chnver.txt", &cachedir)).exists() {
|
||||
let mut sysver = fs::File::create(format!("{}/chnver.txt", &cachedir))?;
|
||||
sysver.write_all(dlver.as_bytes())?;
|
||||
}
|
||||
|
||||
if Path::new(format!("{}/chnver.txt", &cachedir).as_str()).exists()
|
||||
&& fs::read_to_string(&Path::new(format!("{}/chnver.txt", &cachedir).as_str()))? == dlver
|
||||
&& Path::new(format!("{}/packages.json", &cachedir).as_str()).exists()
|
||||
{
|
||||
return Ok(());
|
||||
} else {
|
||||
let oldver = fs::read_to_string(&Path::new(format!("{}/chnver.txt", &cachedir).as_str()))?;
|
||||
let sysver = &dlver;
|
||||
// Change to debug msg
|
||||
println!("OLD: {}, != NEW: {}", oldver, sysver);
|
||||
}
|
||||
if Path::new(format!("{}/chnver.txt", &cachedir).as_str()).exists() {
|
||||
fs::remove_file(format!("{}/chnver.txt", &cachedir).as_str())?;
|
||||
}
|
||||
let mut sysver = fs::File::create(format!("{}/chnver.txt", &cachedir))?;
|
||||
sysver.write_all(dlver.as_bytes())?;
|
||||
let outfile = format!("{}/packages.json", &cachedir);
|
||||
dlfile(&url, &outfile)?;
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setupupdatecache() -> Result<(), Box<dyn Error>> {
|
||||
let dlver = fs::read_to_string("/run/current-system/nixos-version")?;
|
||||
|
||||
let mut relver = dlver.split('.').collect::<Vec<&str>>().join(".")[0..5].to_string();
|
||||
|
||||
if dlver.len() >= 8 && &dlver[5..8] == "pre" {
|
||||
relver = "unstable".to_string();
|
||||
}
|
||||
|
||||
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
|
||||
fs::create_dir_all(&cachedir).expect("Failed to create cache directory");
|
||||
let url = format!(
|
||||
"https://releases.nixos.org/nixos/{}/nixos-{}/packages.json.br",
|
||||
relver, dlver
|
||||
);
|
||||
|
||||
println!("VERSION {}", relver);
|
||||
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
|
||||
if !Path::new(&cachedir).exists() {
|
||||
fs::create_dir_all(&cachedir).expect("Failed to create cache directory");
|
||||
}
|
||||
|
||||
if !Path::new(&format!("{}/sysver.txt", &cachedir)).exists() {
|
||||
let mut sysver = fs::File::create(format!("{}/sysver.txt", &cachedir))?;
|
||||
sysver.write_all(dlver.as_bytes())?;
|
||||
}
|
||||
|
||||
if Path::new(format!("{}/sysver.txt", &cachedir).as_str()).exists()
|
||||
&& fs::read_to_string(&Path::new(format!("{}/sysver.txt", &cachedir).as_str()))? == dlver
|
||||
&& Path::new(format!("{}/syspackages.json", &cachedir).as_str()).exists()
|
||||
{
|
||||
return Ok(());
|
||||
} else {
|
||||
let oldver = fs::read_to_string(&Path::new(format!("{}/sysver.txt", &cachedir).as_str()))?;
|
||||
let sysver = &dlver;
|
||||
// Change to debug msg
|
||||
println!("OLD: {}, != NEW: {}", oldver, sysver);
|
||||
}
|
||||
if Path::new(format!("{}/sysver.txt", &cachedir).as_str()).exists() {
|
||||
fs::remove_file(format!("{}/sysver.txt", &cachedir).as_str())?;
|
||||
}
|
||||
let mut sysver = fs::File::create(format!("{}/sysver.txt", &cachedir))?;
|
||||
sysver.write_all(dlver.as_bytes())?;
|
||||
let outfile = format!("{}/syspackages.json", &cachedir);
|
||||
dlfile(&url, &outfile)?;
|
||||
let file = File::open(&outfile)?;
|
||||
let reader = BufReader::new(file);
|
||||
let pkgbase: NewPackageBase = simd_json::serde::from_reader(reader).unwrap();
|
||||
let mut outbase = HashMap::new();
|
||||
for (pkg, ver) in pkgbase.packages {
|
||||
outbase.insert(pkg.clone(), ver.version.clone());
|
||||
}
|
||||
let out = simd_json::serde::to_string(&outbase)?;
|
||||
fs::write(&outfile, out)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setupnewestver() -> Result<(), Box<dyn Error>> {
|
||||
let version = fs::read_to_string("/run/current-system/nixos-version")?;
|
||||
|
||||
let mut relver = version.split('.').collect::<Vec<&str>>().join(".")[0..5].to_string();
|
||||
|
||||
if version.len() >= 8 && &version[5..8] == "pre" {
|
||||
relver = "unstable".to_string();
|
||||
}
|
||||
println!("VERSION {}", relver);
|
||||
let response = reqwest::blocking::get(format!("https://channels.nixos.org/nixos-{}", relver))?;
|
||||
if let Some(latest) = response.url().to_string().split('/').last() {
|
||||
let latest = latest.strip_prefix("nixos-").unwrap_or(latest);
|
||||
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
|
||||
if !Path::new(&cachedir).exists() {
|
||||
fs::create_dir_all(&cachedir).expect("Failed to create cache directory");
|
||||
}
|
||||
|
||||
if !Path::new(format!("{}/newver.txt", &cachedir).as_str()).exists() {
|
||||
let mut newver = fs::File::create(format!("{}/newver.txt", &cachedir))?;
|
||||
newver.write_all(latest.as_bytes())?;
|
||||
}
|
||||
|
||||
if Path::new(format!("{}/newver.txt", &cachedir).as_str()).exists()
|
||||
&& fs::read_to_string(&Path::new(format!("{}/newver.txt", &cachedir).as_str()))?
|
||||
== latest
|
||||
{
|
||||
return Ok(());
|
||||
} else {
|
||||
let oldver =
|
||||
fs::read_to_string(&Path::new(format!("{}/newver.txt", &cachedir).as_str()))?;
|
||||
let newver = latest;
|
||||
// Change to debug msg
|
||||
println!("OLD: {}, != NEW: {}", oldver, newver);
|
||||
}
|
||||
if Path::new(format!("{}/newver.txt", &cachedir).as_str()).exists() {
|
||||
fs::remove_file(format!("{}/newver.txt", &cachedir).as_str())?;
|
||||
}
|
||||
let mut newver = fs::File::create(format!("{}/newver.txt", &cachedir))?;
|
||||
newver.write_all(latest.as_bytes())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dlfile(url: &str, path: &str) -> Result<(), Box<dyn Error>> {
|
||||
println!("Downloading {}", url);
|
||||
let response = reqwest::blocking::get(url)?;
|
||||
if response.status().is_success() {
|
||||
let cachedir = format!("{}/.cache/nix-software-center", env::var("HOME")?);
|
||||
if !Path::new(&cachedir).exists() {
|
||||
fs::create_dir_all(&cachedir).expect("Failed to create cache directory");
|
||||
}
|
||||
|
||||
let dst: Vec<u8> = response.bytes()?.to_vec();
|
||||
{
|
||||
let mut file = File::create(path)?;
|
||||
let mut reader = brotli::Decompressor::new(
|
||||
dst.as_slice(),
|
||||
4096, // buffer size
|
||||
);
|
||||
let mut buf = [0u8; 4096];
|
||||
loop {
|
||||
match reader.read(&mut buf[..]) {
|
||||
Err(e) => {
|
||||
if let std::io::ErrorKind::Interrupted = e.kind() {
|
||||
continue;
|
||||
}
|
||||
return Err(Box::new(e));
|
||||
}
|
||||
Ok(size) => {
|
||||
if size == 0 {
|
||||
break;
|
||||
}
|
||||
file.write_all(&buf[..size])?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
98
src/parse/config.rs
Normal file
98
src/parse/config.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use std::{error::Error, env, path::Path, fs::{self, File}, io::Write};
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
|
||||
pub struct NscConfig {
|
||||
pub systemconfig: String,
|
||||
pub flake: Option<String>,
|
||||
}
|
||||
|
||||
pub fn getconfig() -> NscConfig {
|
||||
if let Ok(c) = getconfigval() {
|
||||
c
|
||||
} else {
|
||||
NscConfig {
|
||||
systemconfig: String::from("/etc/nixos/configuration.nix"),
|
||||
flake: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn getconfigval() -> Result<NscConfig, Box<dyn Error>> {
|
||||
let configfile = checkconfig()?;
|
||||
let config: NscConfig = serde_json::from_reader(File::open(format!("{}/config.json", configfile))?)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn checkconfig() -> Result<String, Box<dyn Error>> {
|
||||
let cfgdir = format!("{}/.config/nix-software-center", env::var("HOME")?);
|
||||
if !Path::is_file(Path::new(&format!("{}/config.json", &cfgdir))) {
|
||||
if !Path::is_file(Path::new("/etc/nix-software-center/config.json")) {
|
||||
createdefaultconfig()?;
|
||||
Ok(cfgdir)
|
||||
} else {
|
||||
Ok("/etc/nix-software-center/".to_string())
|
||||
}
|
||||
} else {
|
||||
Ok(cfgdir)
|
||||
}
|
||||
}
|
||||
|
||||
// fn configexists() -> Result<bool, Box<dyn Error>> {
|
||||
// let cfgdir = format!("{}/.config/nix-software-center", env::var("HOME")?);
|
||||
// if !Path::is_file(Path::new(&format!("{}/config.json", &cfgdir))) {
|
||||
// if !Path::is_file(Path::new("/etc/nix-software-center/config.json")) {
|
||||
// Ok(false)
|
||||
// } else {
|
||||
// Ok(true)
|
||||
// }
|
||||
// } else {
|
||||
// Ok(true)
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn editconfig(config: NscConfig) -> Result<(), Box<dyn Error>> {
|
||||
let cfgdir = format!("{}/.config/nix-software-center", env::var("HOME")?);
|
||||
fs::create_dir_all(&cfgdir)?;
|
||||
let json = serde_json::to_string_pretty(&config)?;
|
||||
let mut file = File::create(format!("{}/config.json", cfgdir))?;
|
||||
file.write_all(json.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn createdefaultconfig() -> Result<(), Box<dyn Error>> {
|
||||
let cfgdir = format!("{}/.config/nix-software-center", env::var("HOME")?);
|
||||
fs::create_dir_all(&cfgdir)?;
|
||||
let config = NscConfig {
|
||||
systemconfig: "/etc/nixos/configuration.nix".to_string(),
|
||||
flake: None,
|
||||
};
|
||||
let json = serde_json::to_string_pretty(&config)?;
|
||||
let mut file = File::create(format!("{}/config.json", cfgdir))?;
|
||||
file.write_all(json.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
// pub fn readconfig(cfg: String) -> Result<(String, Option<String>), Box<dyn Error>> {
|
||||
// let file = fs::read_to_string(cfg)?;
|
||||
// let config: NscConfig = match serde_json::from_str(&file) {
|
||||
// Ok(x) => x,
|
||||
// Err(_) => {
|
||||
// createdefaultconfig()?;
|
||||
// return Ok((
|
||||
// "/etc/nixos/configuration.nix".to_string(),
|
||||
// None,
|
||||
// ));
|
||||
// }
|
||||
// };
|
||||
// if Path::is_file(Path::new(&config.systemconfig)) {
|
||||
// Ok((config.systemconfig, config.flake))
|
||||
// } else {
|
||||
// Ok((
|
||||
// "/etc/nixos/configuration.nix".to_string(),
|
||||
// None,
|
||||
// ))
|
||||
// }
|
||||
// }
|
3
src/parse/mod.rs
Normal file
3
src/parse/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod cache;
|
||||
pub mod packages;
|
||||
pub mod config;
|
190
src/parse/packages.rs
Normal file
190
src/parse/packages.rs
Normal file
@ -0,0 +1,190 @@
|
||||
use flate2::bufread::GzDecoder;
|
||||
use ijson::IString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Read;
|
||||
use std::{self, fs::File, collections::HashMap, error::Error, env, io::BufReader};
|
||||
|
||||
use crate::APPINFO;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PackageBase {
|
||||
packages: HashMap<String, Package>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub struct Package {
|
||||
pub system: IString,
|
||||
pub pname: IString,
|
||||
pub meta: Meta,
|
||||
pub version: IString,
|
||||
#[serde(skip_deserializing)]
|
||||
pub appdata: Option<AppData>,
|
||||
}
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub struct Meta {
|
||||
pub broken: Option<bool>,
|
||||
pub insecure: Option<bool>,
|
||||
pub unsupported: Option<bool>,
|
||||
pub unfree: Option<bool>,
|
||||
pub description: Option<IString>,
|
||||
#[serde(rename = "longDescription")]
|
||||
pub longdescription: Option<IString>,
|
||||
pub homepage: Option<StrOrVec>,
|
||||
pub maintainers: Option<Vec<PkgMaintainer>>,
|
||||
pub position: Option<IString>,
|
||||
pub license: Option<LicenseEnum>,
|
||||
pub platforms: Option<Platform>
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum StrOrVec {
|
||||
Single(IString),
|
||||
List(Vec<IString>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum Platform {
|
||||
Single(IString),
|
||||
List(Vec<IString>),
|
||||
ListList(Vec<Vec<IString>>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum LicenseEnum {
|
||||
Single(License),
|
||||
List(Vec<License>),
|
||||
SingleStr(IString),
|
||||
VecStr(Vec<IString>),
|
||||
Mixed(Vec<LicenseEnum>)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||
pub struct License {
|
||||
pub free: Option<bool>,
|
||||
#[serde(rename = "fullName")]
|
||||
pub fullname: Option<IString>,
|
||||
#[serde(rename = "spdxId")]
|
||||
pub spdxid: Option<IString>,
|
||||
pub url: Option<IString>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||
pub struct PkgMaintainer {
|
||||
pub email: IString,
|
||||
pub github: IString,
|
||||
pub matrix: Option<IString>,
|
||||
pub name: Option<IString>
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct AppData {
|
||||
#[serde(rename = "Type")]
|
||||
pub metatype: IString,
|
||||
#[serde(rename = "ID")]
|
||||
pub id: String,
|
||||
#[serde(rename = "Package")]
|
||||
pub package: String,
|
||||
#[serde(rename = "Name")]
|
||||
pub name: Option<HashMap<String, String>>,
|
||||
#[serde(rename = "Description")]
|
||||
pub description: Option<HashMap<String, String>>,
|
||||
#[serde(rename = "Summary")]
|
||||
pub summary: Option<HashMap<String, String>>,
|
||||
#[serde(rename = "Url")]
|
||||
pub url: Option<AppUrl>,
|
||||
#[serde(rename = "Icon")]
|
||||
pub icon: Option<AppIconList>,
|
||||
#[serde(rename = "Launchable")]
|
||||
pub launchable: Option<AppLaunchable>,
|
||||
#[serde(rename = "Provides")]
|
||||
pub provides: Option<AppProvides>,
|
||||
#[serde(rename = "Screenshots")]
|
||||
pub screenshots: Option<Vec<AppScreenshot>>,
|
||||
#[serde(rename = "Categories")]
|
||||
pub categories: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct AppUrl {
|
||||
pub homepage: Option<String>,
|
||||
pub bugtracker: Option<String>,
|
||||
pub help: Option<String>,
|
||||
pub donation: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct AppIconList {
|
||||
pub cached: Option<Vec<AppIcon>>,
|
||||
pub stock: Option<String>,
|
||||
// TODO: add support for other icon types
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct AppIcon {
|
||||
pub name: String,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct AppLaunchable {
|
||||
#[serde(rename = "desktop-id")]
|
||||
pub desktopid: Vec<String>
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct AppProvides {
|
||||
pub binaries: Option<Vec<String>>,
|
||||
pub ids: Option<Vec<String>>,
|
||||
pub mediatypes: Option<Vec<String>>,
|
||||
pub libraries: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct AppScreenshot {
|
||||
pub default: Option<bool>,
|
||||
pub thumbnails: Option<Vec<String>>,
|
||||
#[serde(rename = "source-image")]
|
||||
pub sourceimage: Option<AppScreenshotImage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct AppScreenshotImage {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
pub async fn readpkgs() -> Result<HashMap<String, Package>, Box<dyn Error + Send + Sync>> {
|
||||
let cachedir = format!("{}/.cache/nix-software-center/", env::var("HOME")?);
|
||||
let cachefile = format!("{}/packages.json", cachedir);
|
||||
let file = File::open(cachefile).unwrap();
|
||||
let reader = BufReader::new(file);
|
||||
let pkgbase: PackageBase = simd_json::serde::from_reader(reader).unwrap();
|
||||
let mut pkgs = pkgbase.packages;
|
||||
println!("APPDATADIR {}", APPINFO);
|
||||
let appdata = File::open(&format!("{}/xmls/nixos_x86_64_linux.yml.gz", APPINFO)).unwrap();
|
||||
let appreader = BufReader::new(appdata);
|
||||
let mut d = GzDecoder::new(appreader);
|
||||
let mut s = String::new();
|
||||
d.read_to_string(&mut s).unwrap();
|
||||
let mut files = s.split("\n---\n").collect::<Vec<_>>();
|
||||
files.remove(0);
|
||||
for f in files {
|
||||
let appstream: AppData = serde_yaml::from_str(f).unwrap();
|
||||
if let Some(p) = pkgs.get_mut(&appstream.package.to_string()) {
|
||||
p.appdata = Some(appstream);
|
||||
}
|
||||
}
|
||||
Ok(pkgs)
|
||||
}
|
||||
|
||||
pub fn readsyspkgs() -> Result<HashMap<String, String>, Box<dyn Error + Send + Sync>> {
|
||||
let cachedir = format!("{}/.cache/nix-software-center/", env::var("HOME")?);
|
||||
let cachefile = format!("{}/syspackages.json", cachedir);
|
||||
let file = File::open(cachefile)?;
|
||||
let reader = BufReader::new(file);
|
||||
let newpkgs: HashMap<String, String> = simd_json::serde::from_reader(reader).unwrap();
|
||||
Ok(newpkgs)
|
||||
}
|
64
src/ui/about.rs
Normal file
64
src/ui/about.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use adw::prelude::*;
|
||||
use relm4::*;
|
||||
|
||||
use crate::config;
|
||||
|
||||
use super::window::AppMsg;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AboutPageModel {
|
||||
hidden: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AboutPageMsg {
|
||||
Show,
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for AboutPageModel {
|
||||
type InitParams = gtk::Window;
|
||||
type Input = AboutPageMsg;
|
||||
type Output = AppMsg;
|
||||
type Widgets = AboutPageWidgets;
|
||||
|
||||
view! {
|
||||
adw::AboutWindow {
|
||||
#[watch]
|
||||
set_visible: !model.hidden,
|
||||
set_transient_for: Some(&parent_window),
|
||||
set_modal: true,
|
||||
set_application_name: "Nix Software Center",
|
||||
set_application_icon: config::APP_ID,
|
||||
set_developer_name: "Victor Fuentes",
|
||||
set_version: env!("CARGO_PKG_VERSION"),
|
||||
set_issue_url: "https://github.com/vlinkz/nix-software-center/issues",
|
||||
set_license_type: gtk::License::MitX11,
|
||||
set_website: "https://github.com/vlinkz/nix-software-center",
|
||||
set_developers: &["Victor Fuentes https://github.com/vlinkz"],
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
parent_window: Self::InitParams,
|
||||
root: &Self::Root,
|
||||
_sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let model = AboutPageModel {
|
||||
hidden: true,
|
||||
};
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
|
||||
match msg {
|
||||
AboutPageMsg::Show => {
|
||||
self.hidden = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
105
src/ui/categories.rs
Normal file
105
src/ui/categories.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use relm4::adw::prelude::*;
|
||||
use relm4::gtk::pango;
|
||||
use relm4::{factory::*, *};
|
||||
use strum_macros::{EnumIter, Display};
|
||||
|
||||
use super::window::AppMsg;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct PkgGroup {
|
||||
pub category: PkgCategory,
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Hash, EnumIter, Eq, PartialEq, Clone)]
|
||||
pub enum PkgCategory {
|
||||
Audio,
|
||||
Development,
|
||||
Games,
|
||||
Graphics,
|
||||
Network,
|
||||
Video,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PkgCategoryMsg {
|
||||
Open(PkgCategory),
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for PkgGroup {
|
||||
type CommandOutput = ();
|
||||
type Init = PkgCategory;
|
||||
type Input = ();
|
||||
type Output = PkgCategoryMsg;
|
||||
type Widgets = PkgGroupWidgets;
|
||||
type ParentWidget = gtk::FlowBox;
|
||||
type ParentMsg = AppMsg;
|
||||
|
||||
view! {
|
||||
gtk::FlowBoxChild {
|
||||
set_width_request: 210,
|
||||
set_height_request: 70,
|
||||
gtk::Button {
|
||||
add_css_class: "card",
|
||||
gtk::Box {
|
||||
set_margin_start: 15,
|
||||
set_margin_end: 15,
|
||||
set_margin_top: 10,
|
||||
set_margin_bottom: 10,
|
||||
set_spacing: 10,
|
||||
set_halign: gtk::Align::Center,
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_icon_name: match self.category {
|
||||
PkgCategory::Audio => Some("audio-x-generic"),
|
||||
PkgCategory::Development => Some("computer"),
|
||||
PkgCategory::Games => Some("input-gaming"),
|
||||
PkgCategory::Graphics => Some("image-x-generic"),
|
||||
PkgCategory::Network => Some("network-server"),
|
||||
PkgCategory::Video => Some("video-x-generic"),
|
||||
},
|
||||
set_pixel_size: 32,
|
||||
},
|
||||
gtk::Label {
|
||||
add_css_class: "title-2",
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_label: match self.category {
|
||||
PkgCategory::Audio => "Audio",
|
||||
PkgCategory::Development => "Development",
|
||||
PkgCategory::Games => "Games",
|
||||
PkgCategory::Graphics => "Graphics",
|
||||
PkgCategory::Network => "Network",
|
||||
PkgCategory::Video => "Video",
|
||||
},
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
}
|
||||
},
|
||||
connect_clicked[sender, category = self.category.clone()] => move |_| {
|
||||
println!("CLICKED {}", category);
|
||||
sender.output(PkgCategoryMsg::Open(category.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_model(
|
||||
parent: Self::Init,
|
||||
_index: &DynamicIndex,
|
||||
_sender: FactoryComponentSender<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
category: parent,
|
||||
}
|
||||
}
|
||||
|
||||
fn output_to_parent_msg(output: Self::Output) -> Option<AppMsg> {
|
||||
Some(match output {
|
||||
PkgCategoryMsg::Open(x) => AppMsg::OpenCategoryPage(x),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
204
src/ui/categorypage.rs
Normal file
204
src/ui/categorypage.rs
Normal file
@ -0,0 +1,204 @@
|
||||
use super::{window::*, categories::PkgCategory, categorytile::CategoryTile};
|
||||
use adw::prelude::*;
|
||||
use relm4::{factory::*, *};
|
||||
|
||||
#[tracker::track]
|
||||
#[derive(Debug)]
|
||||
pub struct CategoryPageModel {
|
||||
category: PkgCategory,
|
||||
#[tracker::no_eq]
|
||||
recommendedapps: FactoryVecDeque<CategoryTile>,
|
||||
#[tracker::no_eq]
|
||||
apps: FactoryVecDeque<CategoryTile>,
|
||||
busy: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CategoryPageMsg {
|
||||
Close,
|
||||
OpenPkg(String),
|
||||
Open(PkgCategory, Vec<CategoryTile>, Vec<CategoryTile>),
|
||||
Loading(PkgCategory),
|
||||
UpdateInstalled(Vec<String>, Vec<String>)
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for CategoryPageModel {
|
||||
type InitParams = ();
|
||||
type Input = CategoryPageMsg;
|
||||
type Output = AppMsg;
|
||||
type Widgets = CategoryPageWidgets;
|
||||
|
||||
view! {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
adw::HeaderBar {
|
||||
pack_start = >k::Button {
|
||||
add_css_class: "flat",
|
||||
gtk::Image {
|
||||
set_icon_name: Some("go-previous-symbolic"),
|
||||
},
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(CategoryPageMsg::Close)
|
||||
},
|
||||
},
|
||||
#[wrap(Some)]
|
||||
set_title_widget = >k::Label {
|
||||
#[watch]
|
||||
set_label: &model.category.to_string(),
|
||||
},
|
||||
},
|
||||
gtk::ScrolledWindow {
|
||||
set_vexpand: true,
|
||||
set_hexpand: true,
|
||||
set_hscrollbar_policy: gtk::PolicyType::Never,
|
||||
set_vscrollbar_policy: gtk::PolicyType::Automatic,
|
||||
#[track(model.changed(CategoryPageModel::category()))]
|
||||
set_vadjustment: gtk::Adjustment::NONE,
|
||||
adw::Clamp {
|
||||
set_maximum_size: 1000,
|
||||
set_tightening_threshold: 750,
|
||||
if model.busy {
|
||||
gtk::Spinner {
|
||||
set_halign: gtk::Align::Center,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_spinning: true,
|
||||
set_height_request: 32,
|
||||
}
|
||||
} else {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_valign: gtk::Align::Start,
|
||||
set_margin_all: 15,
|
||||
set_spacing: 15,
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "title-4",
|
||||
set_label: "Recommended",
|
||||
},
|
||||
#[local_ref]
|
||||
recbox -> gtk::FlowBox {
|
||||
set_halign: gtk::Align::Fill,
|
||||
set_hexpand: true,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
set_homogeneous: true,
|
||||
set_max_children_per_line: 3,
|
||||
set_min_children_per_line: 1,
|
||||
set_column_spacing: 14,
|
||||
set_row_spacing: 14,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "title-4",
|
||||
set_label: "Other",
|
||||
},
|
||||
#[local_ref]
|
||||
allbox -> gtk::FlowBox {
|
||||
set_halign: gtk::Align::Fill,
|
||||
set_hexpand: true,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
set_homogeneous: true,
|
||||
set_max_children_per_line: 3,
|
||||
set_min_children_per_line: 1,
|
||||
set_column_spacing: 14,
|
||||
set_row_spacing: 14,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
(): Self::InitParams,
|
||||
root: &Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let model = CategoryPageModel {
|
||||
category: PkgCategory::Audio,
|
||||
recommendedapps: FactoryVecDeque::new(gtk::FlowBox::new(), &sender.input),
|
||||
apps: FactoryVecDeque::new(gtk::FlowBox::new(), &sender.input),
|
||||
busy: true,
|
||||
tracker: 0
|
||||
};
|
||||
|
||||
let recbox = model.recommendedapps.widget();
|
||||
let allbox = model.apps.widget();
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
self.reset();
|
||||
match msg {
|
||||
CategoryPageMsg::Close => {
|
||||
let mut recapps_guard = self.recommendedapps.guard();
|
||||
let mut apps_guard = self.apps.guard();
|
||||
recapps_guard.clear();
|
||||
apps_guard.clear();
|
||||
sender.output(AppMsg::FrontFrontPage)
|
||||
}
|
||||
CategoryPageMsg::OpenPkg(pkg) => {
|
||||
sender.output(AppMsg::OpenPkg(pkg))
|
||||
}
|
||||
CategoryPageMsg::Open(category, catrec, catall) => {
|
||||
println!("CategoryPageMsg::Open");
|
||||
self.set_category(category);
|
||||
let mut recapps_guard = self.recommendedapps.guard();
|
||||
recapps_guard.clear();
|
||||
for app in catrec {
|
||||
recapps_guard.push_back(app);
|
||||
}
|
||||
let mut apps_guard = self.apps.guard();
|
||||
apps_guard.clear();
|
||||
for app in catall {
|
||||
apps_guard.push_back(app);
|
||||
}
|
||||
self.busy = false;
|
||||
}
|
||||
CategoryPageMsg::Loading(category) => {
|
||||
println!("CATEGORY PAGE LOADING {}", category);
|
||||
self.set_category(category);
|
||||
self.busy = true;
|
||||
}
|
||||
CategoryPageMsg::UpdateInstalled(installeduserpkgs, installedsystempkgs) => {
|
||||
let mut recapps_guard = self.recommendedapps.guard();
|
||||
for i in 0..recapps_guard.len() {
|
||||
let app = recapps_guard.get_mut(i).unwrap();
|
||||
if installeduserpkgs.contains(&app.pname) {
|
||||
app.installeduser = true;
|
||||
} else {
|
||||
app.installeduser = false;
|
||||
}
|
||||
if installedsystempkgs.contains(&app.pkg) {
|
||||
app.installedsystem = true;
|
||||
} else {
|
||||
app.installedsystem = false;
|
||||
}
|
||||
}
|
||||
let mut apps_guard = self.apps.guard();
|
||||
for i in 0..apps_guard.len() {
|
||||
let app = apps_guard.get_mut(i).unwrap();
|
||||
if installeduserpkgs.contains(&app.pname) {
|
||||
app.installeduser = true;
|
||||
} else {
|
||||
app.installeduser = false;
|
||||
}
|
||||
if installedsystempkgs.contains(&app.pkg) {
|
||||
app.installedsystem = true;
|
||||
} else {
|
||||
app.installedsystem = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
185
src/ui/categorytile.rs
Normal file
185
src/ui/categorytile.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use std::path::Path;
|
||||
|
||||
use crate::APPINFO;
|
||||
|
||||
use super::categorypage::CategoryPageMsg;
|
||||
use relm4::adw::prelude::*;
|
||||
use relm4::gtk::pango;
|
||||
use relm4::{factory::*, *};
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Clone)]
|
||||
pub struct CategoryTile {
|
||||
pub name: String,
|
||||
pub pkg: String,
|
||||
pub pname: String,
|
||||
pub summary: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub installeduser: bool,
|
||||
pub installedsystem: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CategoryTileMsg {
|
||||
Open(String),
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for CategoryTile {
|
||||
type CommandOutput = ();
|
||||
type Init = CategoryTile;
|
||||
type Input = ();
|
||||
type Output = CategoryTileMsg;
|
||||
type Widgets = CategoryTileWidgets;
|
||||
type ParentWidget = gtk::FlowBox;
|
||||
type ParentMsg = CategoryPageMsg;
|
||||
|
||||
view! {
|
||||
gtk::FlowBoxChild {
|
||||
set_width_request: 270,
|
||||
gtk::Overlay {
|
||||
add_overlay = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_valign: gtk::Align::Start,
|
||||
set_halign: gtk::Align::End,
|
||||
gtk::Image {
|
||||
add_css_class: "accent",
|
||||
set_valign: gtk::Align::Start,
|
||||
set_halign: gtk::Align::End,
|
||||
set_pixel_size: 16,
|
||||
set_margin_top: 8,
|
||||
set_margin_end: 8,
|
||||
set_icon_name: Some("emblem-default-symbolic"),
|
||||
#[watch]
|
||||
set_visible: self.installeduser,
|
||||
},
|
||||
gtk::Image {
|
||||
add_css_class: "success",
|
||||
set_valign: gtk::Align::Start,
|
||||
set_halign: gtk::Align::End,
|
||||
set_pixel_size: 16,
|
||||
set_margin_top: 8,
|
||||
set_margin_end: 8,
|
||||
set_icon_name: Some("emblem-default-symbolic"),
|
||||
#[watch]
|
||||
set_visible: self.installedsystem,
|
||||
}
|
||||
},
|
||||
gtk::Button {
|
||||
add_css_class: "card",
|
||||
connect_clicked[sender, pkg = self.pkg.clone()] => move |_| {
|
||||
println!("CLICKED");
|
||||
sender.output(CategoryTileMsg::Open(pkg.to_string()))
|
||||
},
|
||||
set_can_focus: false,
|
||||
gtk::Box {
|
||||
set_margin_start: 15,
|
||||
set_margin_end: 15,
|
||||
set_margin_top: 10,
|
||||
set_margin_bottom: 10,
|
||||
set_spacing: 20,
|
||||
append = if self.icon.is_some() {
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_halign: gtk::Align::Start,
|
||||
set_from_file: {
|
||||
if let Some(i) = &self.icon {
|
||||
let iconpath = format!("{}/icons/nixos/128x128/{}", APPINFO, i);
|
||||
let iconpath64 = format!("{}/icons/nixos/64x64/{}", APPINFO, i);
|
||||
if Path::new(&iconpath).is_file() {
|
||||
Some(iconpath)
|
||||
} else if Path::new(&iconpath64).is_file() {
|
||||
Some(iconpath64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
set_pixel_size: 64,
|
||||
}
|
||||
} else {
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_halign: gtk::Align::Start,
|
||||
set_icon_name: Some("package-x-generic"),
|
||||
set_pixel_size: 64,
|
||||
}
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_halign: gtk::Align::Fill,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_spacing: 3,
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "heading",
|
||||
set_label: &self.name,
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "dim-label",
|
||||
add_css_class: "caption",
|
||||
set_label: &self.pkg,
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
// add_css_class: "dim-label",
|
||||
#[watch]
|
||||
set_visible: self.summary.is_some(),
|
||||
set_label: &(if let Some(s) = &self.summary { s.to_string() } else { String::default() }),
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 2,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_model(
|
||||
parent: Self::Init,
|
||||
_index: &DynamicIndex,
|
||||
_sender: FactoryComponentSender<Self>,
|
||||
) -> Self {
|
||||
let mut sum = parent.summary;
|
||||
sum = sum.map(|mut s| {
|
||||
s.trim().to_string();
|
||||
while s.contains('\n') {
|
||||
s = s.replace('\n', " ");
|
||||
}
|
||||
while s.contains(" ") {
|
||||
s = s.replace(" ", " ");
|
||||
}
|
||||
s
|
||||
});
|
||||
|
||||
Self {
|
||||
name: parent.name,
|
||||
pkg: parent.pkg,
|
||||
pname: parent.pname,
|
||||
summary: sum,
|
||||
icon: parent.icon,
|
||||
installeduser: parent.installeduser,
|
||||
installedsystem: parent.installedsystem,
|
||||
}
|
||||
}
|
||||
|
||||
fn output_to_parent_msg(output: Self::Output) -> Option<CategoryPageMsg> {
|
||||
Some(match output {
|
||||
CategoryTileMsg::Open(x) => CategoryPageMsg::OpenPkg(x),
|
||||
})
|
||||
}
|
||||
}
|
1
src/ui/installdialog.rs
Normal file
1
src/ui/installdialog.rs
Normal file
@ -0,0 +1 @@
|
||||
// TODO add a dialog where user can view `nix-env` and `nixos-rebuild` output
|
378
src/ui/installedpage.rs
Normal file
378
src/ui/installedpage.rs
Normal file
@ -0,0 +1,378 @@
|
||||
use std::path::Path;
|
||||
use crate::APPINFO;
|
||||
|
||||
use super::{window::*, pkgpage::{InstallType, WorkPkg, PkgAction, NotifyPage}};
|
||||
use adw::prelude::*;
|
||||
use relm4::{factory::*, *, gtk::pango};
|
||||
|
||||
#[tracker::track]
|
||||
#[derive(Debug)]
|
||||
pub struct InstalledPageModel {
|
||||
#[tracker::no_eq]
|
||||
installeduserlist: FactoryVecDeque<InstalledItemModel>,
|
||||
#[tracker::no_eq]
|
||||
installedsystemlist: FactoryVecDeque<InstalledItemModel>,
|
||||
updatetracker: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InstalledPageMsg {
|
||||
Update(Vec<InstalledItem>, Vec<InstalledItem>),
|
||||
OpenRow(usize, InstallType),
|
||||
Remove(InstalledItem),
|
||||
UnsetBusy(WorkPkg),
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for InstalledPageModel {
|
||||
type InitParams = ();
|
||||
type Input = InstalledPageMsg;
|
||||
type Output = AppMsg;
|
||||
type Widgets = InstalledPageWidgets;
|
||||
|
||||
view! {
|
||||
gtk::ScrolledWindow {
|
||||
set_hscrollbar_policy: gtk::PolicyType::Never,
|
||||
#[track(model.changed(InstalledPageModel::updatetracker()))]
|
||||
set_vadjustment: gtk::Adjustment::NONE,
|
||||
adw::Clamp {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_valign: gtk::Align::Start,
|
||||
set_margin_all: 15,
|
||||
set_spacing: 15,
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "title-4",
|
||||
set_label: "User (nix-env)",
|
||||
},
|
||||
#[local_ref]
|
||||
installeduserlist -> gtk::ListBox {
|
||||
set_valign: gtk::Align::Start,
|
||||
add_css_class: "boxed-list",
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
connect_row_activated[sender] => move |listbox, row| {
|
||||
if let Some(i) = listbox.index_of_child(row) {
|
||||
sender.input(InstalledPageMsg::OpenRow(i as usize, InstallType::User))
|
||||
}
|
||||
// sender.input(InstalledPageMsg::OpenRow(row.clone(), InstallType::User));
|
||||
}
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "title-4",
|
||||
set_label: "System (configuration.nix)",
|
||||
},
|
||||
#[local_ref]
|
||||
installedsystemlist -> gtk::ListBox {
|
||||
set_valign: gtk::Align::Start,
|
||||
add_css_class: "boxed-list",
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
connect_row_activated[sender] => move |listbox, row| {
|
||||
if let Some(i) = listbox.index_of_child(row) {
|
||||
sender.input(InstalledPageMsg::OpenRow(i as usize, InstallType::System))
|
||||
}
|
||||
// sender.input(InstalledPageMsg::OpenRow(row.clone(), InstallType::System));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
(): Self::InitParams,
|
||||
root: &Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let model = InstalledPageModel {
|
||||
installeduserlist: FactoryVecDeque::new(gtk::ListBox::new(), &sender.input),
|
||||
installedsystemlist: FactoryVecDeque::new(gtk::ListBox::new(), &sender.input),
|
||||
updatetracker: 0,
|
||||
tracker: 0
|
||||
};
|
||||
|
||||
let installeduserlist = model.installeduserlist.widget();
|
||||
let installedsystemlist = model.installedsystemlist.widget();
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
self.reset();
|
||||
match msg {
|
||||
InstalledPageMsg::Update(installeduserlist, installedsystemlist) => {
|
||||
self.update_updatetracker(|_| ());
|
||||
let mut installeduserlist_guard = self.installeduserlist.guard();
|
||||
installeduserlist_guard.clear();
|
||||
for installeduser in installeduserlist {
|
||||
installeduserlist_guard.push_back(installeduser);
|
||||
}
|
||||
let mut installedsystemlist_guard = self.installedsystemlist.guard();
|
||||
installedsystemlist_guard.clear();
|
||||
for installedsystem in installedsystemlist {
|
||||
installedsystemlist_guard.push_back(installedsystem);
|
||||
}
|
||||
}
|
||||
InstalledPageMsg::OpenRow(row, pkgtype) => {
|
||||
match pkgtype {
|
||||
InstallType::User => {
|
||||
let installeduserlist_guard = self.installeduserlist.guard();
|
||||
if let Some(item) = installeduserlist_guard.get(row) {
|
||||
if let Some(pkg) = &item.item.pkg {
|
||||
sender.output(AppMsg::OpenPkg(pkg.to_string()));
|
||||
}
|
||||
}
|
||||
// for (i, child) in installeduserlist_guard.widget().iter_children().enumerate() {
|
||||
// if child == row {
|
||||
// if let Some(item) = installeduserlist_guard.get(i) {
|
||||
// if let Some(pkg) = &item.item.pkg {
|
||||
// sender.output(AppMsg::OpenPkg(pkg.to_string()));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
InstallType::System => {
|
||||
let installedsystemlist_guard = self.installedsystemlist.guard();
|
||||
if let Some(item) = installedsystemlist_guard.get(row) {
|
||||
if let Some(pkg) = &item.item.pkg {
|
||||
sender.output(AppMsg::OpenPkg(pkg.to_string()));
|
||||
}
|
||||
}
|
||||
// for (i, child) in installedsystemlist_guard.widget().iter_children().enumerate() {
|
||||
// if child == row {
|
||||
// if let Some(item) = installedsystemlist_guard.get(i) {
|
||||
// if let Some(pkg) = &item.item.pkg {
|
||||
// sender.output(AppMsg::OpenPkg(pkg.to_string()));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
InstalledPageMsg::Remove(item) => {
|
||||
let work = WorkPkg {
|
||||
pkg: item.pkg.unwrap_or_default(),
|
||||
pname: item.pname,
|
||||
pkgtype: item.pkgtype,
|
||||
action: PkgAction::Remove,
|
||||
block: false,
|
||||
notify: Some(NotifyPage::Installed)
|
||||
};
|
||||
sender.output(AppMsg::AddInstalledToWorkQueue(work))
|
||||
}
|
||||
InstalledPageMsg::UnsetBusy(work) => {
|
||||
match work.pkgtype {
|
||||
InstallType::User => {
|
||||
let mut installeduserlist_guard = self.installeduserlist.guard();
|
||||
for i in 0..installeduserlist_guard.len() {
|
||||
if let Some(item) = installeduserlist_guard.get_mut(i) {
|
||||
if item.item.pname == work.pname && item.item.pkgtype == work.pkgtype {
|
||||
item.item.busy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
InstallType::System => {
|
||||
let mut installedsystemlist_guard = self.installedsystemlist.guard();
|
||||
for i in 0..installedsystemlist_guard.len() {
|
||||
if let Some(item) = installedsystemlist_guard.get_mut(i) {
|
||||
if item.item.pkg == Some(work.pkg.clone()) && item.item.pkgtype == work.pkgtype {
|
||||
item.item.busy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct InstalledItem {
|
||||
pub name: String,
|
||||
pub pkg: Option<String>,
|
||||
pub pname: String,
|
||||
pub summary: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub pkgtype: InstallType,
|
||||
pub busy: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct InstalledItemModel {
|
||||
pub item: InstalledItem,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InstalledItemMsg {
|
||||
Delete(InstalledItem),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InstalledItemInputMsg {
|
||||
Busy(bool),
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for InstalledItemModel {
|
||||
type CommandOutput = ();
|
||||
type Init = InstalledItem;
|
||||
type Input = InstalledItemInputMsg;
|
||||
type Output = InstalledItemMsg;
|
||||
type Widgets = InstalledItemWidgets;
|
||||
type ParentWidget = adw::gtk::ListBox;
|
||||
type ParentMsg = InstalledPageMsg;
|
||||
|
||||
view! {
|
||||
adw::PreferencesRow {
|
||||
set_activatable: self.item.pkg.is_some(),
|
||||
set_can_focus: false,
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_hexpand: true,
|
||||
set_spacing: 10,
|
||||
set_margin_all: 10,
|
||||
adw::Bin {
|
||||
set_valign: gtk::Align::Center,
|
||||
#[wrap(Some)]
|
||||
set_child = if self.item.icon.is_some() {
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_halign: gtk::Align::Start,
|
||||
set_from_file: {
|
||||
if let Some(i) = &self.item.icon {
|
||||
let iconpath = format!("{}/icons/nixos/128x128/{}", APPINFO, i);
|
||||
let iconpath64 = format!("{}/icons/nixos/64x64/{}", APPINFO, i);
|
||||
if Path::new(&iconpath).is_file() {
|
||||
Some(iconpath)
|
||||
} else if Path::new(&iconpath64).is_file() {
|
||||
Some(iconpath64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
set_pixel_size: 64,
|
||||
}
|
||||
} else {
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_halign: gtk::Align::Start,
|
||||
set_icon_name: Some("package-x-generic"),
|
||||
set_pixel_size: 64,
|
||||
}
|
||||
}
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_halign: gtk::Align::Fill,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_spacing: 2,
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
set_label: self.item.name.as_str(),
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "dim-label",
|
||||
add_css_class: "caption",
|
||||
set_label: if let Some(p) = &self.item.pkg { p } else { &self.item.pname },
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
set_label: self.item.summary.as_deref().unwrap_or(""),
|
||||
set_visible: self.item.summary.is_some(),
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
},
|
||||
if self.item.busy {
|
||||
gtk::Spinner {
|
||||
set_spinning: true,
|
||||
}
|
||||
} else {
|
||||
gtk::Button {
|
||||
add_css_class: "destructive-action",
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::End,
|
||||
set_icon_name: "user-trash-symbolic",
|
||||
set_can_focus: false,
|
||||
connect_clicked[sender, item = self.item.clone()] => move |_| {
|
||||
sender.input(InstalledItemInputMsg::Busy(true));
|
||||
sender.output(InstalledItemMsg::Delete(item.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_model(
|
||||
parent: Self::Init,
|
||||
_index: &DynamicIndex,
|
||||
_sender: FactoryComponentSender<Self>,
|
||||
) -> Self {
|
||||
let sum = if let Some(s) = parent.summary {
|
||||
let mut sum = s.trim().to_string();
|
||||
while sum.contains('\n') {
|
||||
sum = sum.replace('\n', " ");
|
||||
}
|
||||
while sum.contains(" ") {
|
||||
sum = sum.replace(" ", " ");
|
||||
}
|
||||
Some(sum)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let item = InstalledItem {
|
||||
name: parent.name,
|
||||
pkg: parent.pkg,
|
||||
pname: parent.pname,
|
||||
summary: sum,
|
||||
icon: parent.icon,
|
||||
pkgtype: parent.pkgtype,
|
||||
busy: parent.busy,
|
||||
};
|
||||
|
||||
Self {
|
||||
item,
|
||||
}
|
||||
}
|
||||
|
||||
fn output_to_parent_msg(output: Self::Output) -> Option<InstalledPageMsg> {
|
||||
Some(match output {
|
||||
InstalledItemMsg::Delete(item) => InstalledPageMsg::Remove(item),
|
||||
})
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, _sender: FactoryComponentSender<Self>) {
|
||||
match msg {
|
||||
InstalledItemInputMsg::Busy(b) => self.item.busy = b,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
346
src/ui/installworker.rs
Normal file
346
src/ui/installworker.rs
Normal file
@ -0,0 +1,346 @@
|
||||
use crate::parse::config::NscConfig;
|
||||
|
||||
use super::pkgpage::{InstallType, PkgAction, PkgMsg, WorkPkg};
|
||||
use relm4::*;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::process::Stdio;
|
||||
use std::{fs, io};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
|
||||
|
||||
#[tracker::track]
|
||||
#[derive(Debug)]
|
||||
pub struct InstallAsyncHandler {
|
||||
#[tracker::no_eq]
|
||||
process: Option<JoinHandle<()>>,
|
||||
work: Option<WorkPkg>,
|
||||
systemconfig: String,
|
||||
flakeargs: Option<String>,
|
||||
pid: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InstallAsyncHandlerMsg {
|
||||
SetConfig(NscConfig),
|
||||
Process(WorkPkg),
|
||||
CancelProcess,
|
||||
SetPid(Option<u32>),
|
||||
}
|
||||
|
||||
impl Worker for InstallAsyncHandler {
|
||||
type InitParams = ();
|
||||
type Input = InstallAsyncHandlerMsg;
|
||||
type Output = PkgMsg;
|
||||
|
||||
fn init(_params: Self::InitParams, _sender: relm4::ComponentSender<Self>) -> Self {
|
||||
Self {
|
||||
process: None,
|
||||
work: None,
|
||||
systemconfig: String::new(),
|
||||
flakeargs: None,
|
||||
pid: None,
|
||||
tracker: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
self.reset();
|
||||
match msg {
|
||||
InstallAsyncHandlerMsg::SetConfig(config) => {
|
||||
self.systemconfig = config.systemconfig;
|
||||
self.flakeargs = config.flake;
|
||||
}
|
||||
InstallAsyncHandlerMsg::Process(work) => {
|
||||
if work.block {
|
||||
return;
|
||||
}
|
||||
let systemconfig = self.systemconfig.clone();
|
||||
let rebuildargs = self.flakeargs.clone();
|
||||
match work.pkgtype {
|
||||
InstallType::User => {
|
||||
match work.action {
|
||||
PkgAction::Install => {
|
||||
println!("Installing user package: {}", work.pkg);
|
||||
self.process = Some(relm4::spawn(async move {
|
||||
let mut p = tokio::process::Command::new("nix-env")
|
||||
.arg("-iA")
|
||||
.arg(format!("nixos.{}", work.pkg))
|
||||
.kill_on_drop(true)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to run nix-env");
|
||||
|
||||
let stderr = p.stderr.take().unwrap();
|
||||
let reader = tokio::io::BufReader::new(stderr);
|
||||
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
println!("CAUGHT LINE: {}", line);
|
||||
}
|
||||
|
||||
match p.wait().await {
|
||||
Ok(o) => {
|
||||
if o.success() {
|
||||
println!(
|
||||
"Removed user package: {} success",
|
||||
work.pkg
|
||||
);
|
||||
// println!("{}", String::from_utf8_lossy(&pstdout));
|
||||
sender.output(PkgMsg::FinishedProcess(work))
|
||||
} else {
|
||||
println!(
|
||||
"Removed user package: {} failed",
|
||||
work.pkg
|
||||
);
|
||||
// println!("{}", String::from_utf8_lossy(&p.stderr));
|
||||
sender.output(PkgMsg::FailedProcess(work));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error removing user package: {}", e);
|
||||
sender.output(PkgMsg::FailedProcess(work));
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
PkgAction::Remove => {
|
||||
println!("Removing user package: {}", work.pkg);
|
||||
self.process = Some(relm4::spawn(async move {
|
||||
let mut p = tokio::process::Command::new("nix-env")
|
||||
.arg("-e")
|
||||
.arg(&work.pname)
|
||||
.kill_on_drop(true)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to run nix-env");
|
||||
let stderr = p.stderr.take().unwrap();
|
||||
let reader = tokio::io::BufReader::new(stderr);
|
||||
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
println!("CAUGHT LINE: {}", line);
|
||||
}
|
||||
match p.wait().await {
|
||||
Ok(o) => {
|
||||
if o.success() {
|
||||
println!(
|
||||
"Removed user package: {} success",
|
||||
work.pkg
|
||||
);
|
||||
// println!("{}", String::from_utf8_lossy(&pstdout));
|
||||
sender.output(PkgMsg::FinishedProcess(work))
|
||||
} else {
|
||||
println!(
|
||||
"Removed user package: {} failed",
|
||||
work.pkg
|
||||
);
|
||||
// println!("{}", String::from_utf8_lossy(&p.stderr));
|
||||
sender.output(PkgMsg::FailedProcess(work));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error removing user package: {}", e);
|
||||
sender.output(PkgMsg::FailedProcess(work));
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
InstallType::System => match work.action {
|
||||
PkgAction::Install => {
|
||||
println!("Installing system package: {}", work.pkg);
|
||||
self.process = Some(relm4::spawn(async move {
|
||||
match installsys(
|
||||
work.pkg.to_string(),
|
||||
work.action.clone(),
|
||||
systemconfig,
|
||||
rebuildargs,
|
||||
sender.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(b) => {
|
||||
if b {
|
||||
sender.output(PkgMsg::FinishedProcess(work))
|
||||
} else {
|
||||
sender.output(PkgMsg::FailedProcess(work))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
sender.output(PkgMsg::FailedProcess(work));
|
||||
println!("Error installing system package: {}", e);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
PkgAction::Remove => {
|
||||
println!("Removing system package: {}", work.pkg);
|
||||
self.process = Some(relm4::spawn(async move {
|
||||
match installsys(
|
||||
work.pkg.to_string(),
|
||||
work.action.clone(),
|
||||
systemconfig,
|
||||
rebuildargs,
|
||||
sender.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(b) => {
|
||||
if b {
|
||||
sender.output(PkgMsg::FinishedProcess(work))
|
||||
} else {
|
||||
sender.output(PkgMsg::FailedProcess(work))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
sender.output(PkgMsg::FailedProcess(work));
|
||||
println!("Error removing system package: {}", e);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
InstallAsyncHandlerMsg::CancelProcess => {
|
||||
println!("CANCELING PROCESS");
|
||||
// if let Some(p) = self.pid {
|
||||
// println!("Killing process: {}", p);
|
||||
// Command::new("pkexec")
|
||||
// .arg("kill")
|
||||
// .arg("-INT")
|
||||
// .arg(p.to_string())
|
||||
// .spawn()
|
||||
// .expect("Failed to kill process");
|
||||
// }
|
||||
if let Some(p) = &mut self.process {
|
||||
p.abort()
|
||||
}
|
||||
self.process = None;
|
||||
self.pid = None;
|
||||
sender.output(PkgMsg::CancelFinished);
|
||||
}
|
||||
InstallAsyncHandlerMsg::SetPid(p) => self.pid = p,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn installsys(
|
||||
pkg: String,
|
||||
action: PkgAction,
|
||||
systemconfig: String,
|
||||
flakeargs: Option<String>,
|
||||
_sender: ComponentSender<InstallAsyncHandler>,
|
||||
) -> Result<bool, Box<dyn Error>> {
|
||||
let mut p = pkg;
|
||||
let f = fs::read_to_string(&systemconfig)?;
|
||||
if let Ok(s) = nix_editor::read::getwithvalue(&f, "environment.systemPackages") {
|
||||
if !s.contains(&"pkgs".to_string()) {
|
||||
p = format!("pkgs.{}", p);
|
||||
}
|
||||
} else {
|
||||
return Err(Box::new(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Failed to write configuration.nix",
|
||||
)));
|
||||
}
|
||||
|
||||
let out = match action {
|
||||
PkgAction::Install => {
|
||||
match nix_editor::write::addtoarr(&f, "environment.systemPackages", vec![p]) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return Err(Box::new(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Failed to write configuration.nix",
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
PkgAction::Remove => {
|
||||
match nix_editor::write::rmarr(&f, "environment.systemPackages", vec![p]) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return Err(Box::new(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Failed to write configuration.nix",
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let exe = match std::env::current_exe() {
|
||||
Ok(mut e) => {
|
||||
e.pop(); // root/bin
|
||||
// e.pop(); // root/
|
||||
// e.push("libexec"); // root/libexec
|
||||
e.push("nsc-helper");
|
||||
let x = e.to_string_lossy().to_string();
|
||||
println!("CURRENT PATH {}", x);
|
||||
if Path::new(&x).is_file() {
|
||||
x
|
||||
} else {
|
||||
String::from("nsc-helper")
|
||||
}
|
||||
}
|
||||
Err(_) => String::from("nsc-helper"),
|
||||
};
|
||||
|
||||
println!("EXECUTING {}", exe);
|
||||
|
||||
// let rebuildargs = match flakeargs {
|
||||
// Some(x) => format!("--flake {}", x),//.split(' ').map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
// None => String::default(),
|
||||
// };
|
||||
|
||||
let rebuildargs = if let Some(x) = flakeargs {
|
||||
let mut v = vec![String::from("--flake")];
|
||||
for arg in x.split(' ') {
|
||||
if !arg.is_empty() {
|
||||
v.push(String::from(arg));
|
||||
}
|
||||
}
|
||||
v
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
println!("Rebuild args: {:?}", rebuildargs);
|
||||
|
||||
let mut cmd = tokio::process::Command::new("pkexec")
|
||||
.arg(&exe)
|
||||
.arg("config")
|
||||
.arg("--output")
|
||||
.arg(&systemconfig)
|
||||
.arg("--")
|
||||
.arg("switch")
|
||||
.args(&rebuildargs)
|
||||
.stderr(Stdio::piped())
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
// sender.input(InstallAsyncHandlerMsg::SetPid(cmd.id()));
|
||||
|
||||
cmd.stdin.take().unwrap().write_all(out.as_bytes()).await?;
|
||||
println!("SENT INPUT");
|
||||
let stderr = cmd.stderr.take().unwrap();
|
||||
let reader = tokio::io::BufReader::new(stderr);
|
||||
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
println!("CAUGHT LINE: {}", line);
|
||||
}
|
||||
println!("READER DONE");
|
||||
if cmd.wait().await?.success() {
|
||||
println!("SUCCESS");
|
||||
// sender.input(InstallAsyncHandlerMsg::SetPid(None));
|
||||
Ok(true)
|
||||
} else {
|
||||
println!("FAILURE");
|
||||
// sender.input(InstallAsyncHandlerMsg::SetPid(None));
|
||||
Ok(false)
|
||||
}
|
||||
}
|
16
src/ui/mod.rs
Normal file
16
src/ui/mod.rs
Normal file
@ -0,0 +1,16 @@
|
||||
pub mod window;
|
||||
pub mod windowloading;
|
||||
pub mod pkgtile;
|
||||
pub mod categories;
|
||||
pub mod pkgpage;
|
||||
pub mod screenshotfactory;
|
||||
pub mod installworker;
|
||||
pub mod searchpage;
|
||||
pub mod installedpage;
|
||||
pub mod updatepage;
|
||||
pub mod updatedialog;
|
||||
pub mod updateworker;
|
||||
pub mod about;
|
||||
pub mod preferencespage;
|
||||
pub mod categorypage;
|
||||
pub mod categorytile;
|
1472
src/ui/pkgpage.rs
Normal file
1472
src/ui/pkgpage.rs
Normal file
File diff suppressed because it is too large
Load Diff
180
src/ui/pkgtile.rs
Normal file
180
src/ui/pkgtile.rs
Normal file
@ -0,0 +1,180 @@
|
||||
use std::path::Path;
|
||||
|
||||
use relm4::adw::prelude::*;
|
||||
use relm4::gtk::pango;
|
||||
use relm4::{factory::*, *};
|
||||
|
||||
use crate::APPINFO;
|
||||
|
||||
use super::window::AppMsg;
|
||||
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
pub struct PkgTile {
|
||||
pub name: String,
|
||||
pub pkg: String,
|
||||
pub pname: String,
|
||||
pub summary: String,
|
||||
pub icon: Option<String>,
|
||||
pub installeduser: bool,
|
||||
pub installedsystem: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PkgTileMsg {
|
||||
Open(String),
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for PkgTile {
|
||||
type CommandOutput = ();
|
||||
type Init = PkgTile;
|
||||
type Input = ();
|
||||
type Output = PkgTileMsg;
|
||||
type Widgets = PkgTileWidgets;
|
||||
type ParentWidget = gtk::FlowBox;
|
||||
type ParentMsg = AppMsg;
|
||||
|
||||
view! {
|
||||
gtk::FlowBoxChild {
|
||||
set_width_request: 270,
|
||||
gtk::Overlay {
|
||||
add_overlay = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_valign: gtk::Align::Start,
|
||||
set_halign: gtk::Align::End,
|
||||
gtk::Image {
|
||||
add_css_class: "accent",
|
||||
set_valign: gtk::Align::Start,
|
||||
set_halign: gtk::Align::End,
|
||||
set_pixel_size: 16,
|
||||
set_margin_top: 8,
|
||||
set_margin_end: 8,
|
||||
set_icon_name: Some("emblem-default-symbolic"),
|
||||
#[watch]
|
||||
set_visible: self.installeduser,
|
||||
},
|
||||
gtk::Image {
|
||||
add_css_class: "success",
|
||||
set_valign: gtk::Align::Start,
|
||||
set_halign: gtk::Align::End,
|
||||
set_pixel_size: 16,
|
||||
set_margin_top: 8,
|
||||
set_margin_end: 8,
|
||||
set_icon_name: Some("emblem-default-symbolic"),
|
||||
#[watch]
|
||||
set_visible: self.installedsystem,
|
||||
}
|
||||
},
|
||||
gtk::Button {
|
||||
add_css_class: "card",
|
||||
connect_clicked[sender, pkg = self.pkg.clone()] => move |_| {
|
||||
println!("CLICKED");
|
||||
sender.output(PkgTileMsg::Open(pkg.to_string()))
|
||||
},
|
||||
gtk::Box {
|
||||
set_margin_start: 15,
|
||||
set_margin_end: 15,
|
||||
set_margin_top: 10,
|
||||
set_margin_bottom: 10,
|
||||
set_spacing: 20,
|
||||
append = if self.icon.is_some() {
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_halign: gtk::Align::Start,
|
||||
set_from_file: {
|
||||
if let Some(i) = &self.icon {
|
||||
let iconpath = format!("{}/icons/nixos/128x128/{}", APPINFO, i);
|
||||
let iconpath64 = format!("{}/icons/nixos/64x64/{}", APPINFO, i);
|
||||
if Path::new(&iconpath).is_file() {
|
||||
Some(iconpath)
|
||||
} else if Path::new(&iconpath64).is_file() {
|
||||
Some(iconpath64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
set_pixel_size: 64,
|
||||
}
|
||||
} else {
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_halign: gtk::Align::Start,
|
||||
set_icon_name: Some("package-x-generic"),
|
||||
set_pixel_size: 64,
|
||||
}
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_halign: gtk::Align::Fill,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_spacing: 3,
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "heading",
|
||||
set_label: &self.name,
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "dim-label",
|
||||
add_css_class: "caption",
|
||||
set_label: &self.pkg,
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
// add_css_class: "dim-label",
|
||||
set_label: &self.summary,
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 2,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_model(
|
||||
parent: Self::Init,
|
||||
_index: &DynamicIndex,
|
||||
_sender: FactoryComponentSender<Self>,
|
||||
) -> Self {
|
||||
|
||||
let mut sum = parent.summary.trim().to_string();
|
||||
while sum.contains('\n') {
|
||||
sum = sum.replace('\n', " ");
|
||||
}
|
||||
while sum.contains(" ") {
|
||||
sum = sum.replace(" ", " ");
|
||||
}
|
||||
|
||||
Self {
|
||||
name: parent.name,
|
||||
pkg: parent.pkg,
|
||||
pname: parent.pname,
|
||||
summary: sum,
|
||||
icon: parent.icon,
|
||||
installeduser: parent.installeduser,
|
||||
installedsystem: parent.installedsystem,
|
||||
}
|
||||
}
|
||||
|
||||
fn output_to_parent_msg(output: Self::Output) -> Option<AppMsg> {
|
||||
Some(match output {
|
||||
PkgTileMsg::Open(x) => AppMsg::OpenPkg(x),
|
||||
})
|
||||
}
|
||||
}
|
236
src/ui/preferencespage.rs
Normal file
236
src/ui/preferencespage.rs
Normal file
@ -0,0 +1,236 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::window::AppMsg;
|
||||
use adw::prelude::*;
|
||||
use relm4::*;
|
||||
use relm4_components::open_dialog::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PreferencesPageModel {
|
||||
hidden: bool,
|
||||
configpath: PathBuf,
|
||||
flake: Option<(PathBuf, String)>,
|
||||
open_dialog: Controller<OpenDialog>,
|
||||
flake_file_dialog: Controller<OpenDialog>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PreferencesPageMsg {
|
||||
Show(PathBuf, Option<(PathBuf, String)>),
|
||||
Open,
|
||||
OpenFlake,
|
||||
SetConfigPath(PathBuf),
|
||||
SetFlake(Option<(PathBuf, String)>),
|
||||
SetFlakePath(PathBuf),
|
||||
SetFlakeArg(String),
|
||||
ModifyFlake,
|
||||
Ignore,
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for PreferencesPageModel {
|
||||
type InitParams = gtk::Window;
|
||||
type Input = PreferencesPageMsg;
|
||||
type Output = AppMsg;
|
||||
type Widgets = PreferencesPageWidgets;
|
||||
|
||||
view! {
|
||||
adw::PreferencesWindow {
|
||||
#[watch]
|
||||
set_visible: !model.hidden,
|
||||
set_transient_for: Some(&parent_window),
|
||||
set_modal: true,
|
||||
set_search_enabled: false,
|
||||
add = &adw::PreferencesPage {
|
||||
add = &adw::PreferencesGroup {
|
||||
// set_title: "Preferences",
|
||||
add = &adw::ActionRow {
|
||||
set_title: "Configuration file",
|
||||
add_suffix = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_halign: gtk::Align::End,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_spacing: 10,
|
||||
gtk::Button {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
gtk::Image {
|
||||
set_icon_name: Some("document-open-symbolic"),
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_label: {
|
||||
let x = model.configpath.file_name().unwrap_or_default().to_str().unwrap_or_default();
|
||||
if x.is_empty() {
|
||||
"(None)"
|
||||
} else {
|
||||
x
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(PreferencesPageMsg::Open);
|
||||
}
|
||||
},
|
||||
gtk::Button {
|
||||
add_css_class: "flat",
|
||||
set_icon_name: "view-refresh-symbolic",
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(PreferencesPageMsg::SetConfigPath(PathBuf::from("/etc/nixos/configuration.nix")));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
add = &adw::ActionRow {
|
||||
set_title: "Use nix flakes",
|
||||
add_suffix = >k::Switch {
|
||||
set_valign: gtk::Align::Center,
|
||||
connect_state_set[sender] => move |_, b| {
|
||||
if b {
|
||||
sender.input(PreferencesPageMsg::SetFlake(Some((PathBuf::new(), String::default()))));
|
||||
} else {
|
||||
sender.input(PreferencesPageMsg::SetFlake(None));
|
||||
}
|
||||
gtk::Inhibit(false)
|
||||
}
|
||||
}
|
||||
},
|
||||
add = &adw::ActionRow {
|
||||
set_title: "Flake file",
|
||||
#[watch]
|
||||
set_visible: model.flake.is_some(),
|
||||
add_suffix = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_halign: gtk::Align::End,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_spacing: 10,
|
||||
gtk::Button {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
gtk::Image {
|
||||
set_icon_name: Some("document-open-symbolic"),
|
||||
},
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_label: {
|
||||
let x = if let Some((f, _)) = &model.flake {
|
||||
f.file_name().unwrap_or_default().to_str().unwrap_or_default()
|
||||
} else {
|
||||
""
|
||||
};
|
||||
if x.is_empty() {
|
||||
"(None)"
|
||||
} else {
|
||||
x
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(PreferencesPageMsg::OpenFlake);
|
||||
}
|
||||
},
|
||||
gtk::Button {
|
||||
add_css_class: "flat",
|
||||
set_icon_name: "user-trash-symbolic",
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(PreferencesPageMsg::SetFlakePath(PathBuf::new()));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
add = &adw::EntryRow {
|
||||
#[watch]
|
||||
set_visible: model.flake.is_some(),
|
||||
set_title: "Flake arguments (--flake path/to/flake.nix#<THIS ENTRY>)",
|
||||
connect_changed[sender] => move |x| {
|
||||
sender.input(PreferencesPageMsg::SetFlakeArg(x.text().to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
parent_window: Self::InitParams,
|
||||
root: &Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let open_dialog = OpenDialog::builder()
|
||||
.transient_for_native(root)
|
||||
.launch(OpenDialogSettings::default())
|
||||
.forward(&sender.input, |response| match response {
|
||||
OpenDialogResponse::Accept(path) => PreferencesPageMsg::SetConfigPath(path),
|
||||
OpenDialogResponse::Cancel => PreferencesPageMsg::Ignore,
|
||||
});
|
||||
let flake_file_dialog = OpenDialog::builder()
|
||||
.transient_for_native(root)
|
||||
.launch(OpenDialogSettings::default())
|
||||
.forward(&sender.input, |response| match response {
|
||||
OpenDialogResponse::Accept(path) => PreferencesPageMsg::SetFlakePath(path),
|
||||
OpenDialogResponse::Cancel => PreferencesPageMsg::Ignore,
|
||||
});
|
||||
let model = PreferencesPageModel {
|
||||
hidden: true,
|
||||
configpath: PathBuf::new(),
|
||||
flake: None,
|
||||
open_dialog,
|
||||
flake_file_dialog,
|
||||
};
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
match msg {
|
||||
PreferencesPageMsg::Show(path, flake) => {
|
||||
self.configpath = path;
|
||||
self.flake = flake;
|
||||
self.hidden = false;
|
||||
println!("FLAKE {:?}", self.flake);
|
||||
}
|
||||
PreferencesPageMsg::Open => self.open_dialog.emit(OpenDialogMsg::Open),
|
||||
PreferencesPageMsg::OpenFlake => self.flake_file_dialog.emit(OpenDialogMsg::Open),
|
||||
PreferencesPageMsg::SetConfigPath(path) => {
|
||||
self.configpath = path.clone();
|
||||
sender.output(AppMsg::UpdateSysconfig(path.to_string_lossy().to_string()));
|
||||
}
|
||||
PreferencesPageMsg::SetFlake(flake) => {
|
||||
self.flake = flake;
|
||||
println!("FLAKE {:?}", self.flake);
|
||||
sender.input(PreferencesPageMsg::ModifyFlake)
|
||||
}
|
||||
PreferencesPageMsg::SetFlakePath(path) => {
|
||||
self.flake = Some((
|
||||
path,
|
||||
self.flake.as_ref().map(|x| x.1.clone()).unwrap_or_default(),
|
||||
));
|
||||
println!("FLAKE {:?}", self.flake);
|
||||
sender.input(PreferencesPageMsg::ModifyFlake)
|
||||
}
|
||||
PreferencesPageMsg::SetFlakeArg(arg) => {
|
||||
self.flake = Some((
|
||||
self.flake.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
|
||||
arg,
|
||||
));
|
||||
println!("FLAKE {:?}", self.flake);
|
||||
sender.input(PreferencesPageMsg::ModifyFlake)
|
||||
}
|
||||
PreferencesPageMsg::ModifyFlake => {
|
||||
let out = self
|
||||
.flake
|
||||
.as_ref()
|
||||
.map(|(path, arg)| format!("{}#{}", path.to_string_lossy(), arg));
|
||||
sender.output(AppMsg::UpdateFlake(out.map(|x| x.strip_suffix('#').unwrap_or(&x).to_string())));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
73
src/ui/screenshotfactory.rs
Normal file
73
src/ui/screenshotfactory.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use relm4::adw::prelude::*;
|
||||
use relm4::{factory::*, *};
|
||||
|
||||
use super::pkgpage::PkgMsg;
|
||||
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
pub struct ScreenshotItem {
|
||||
pub path: Option<String>,
|
||||
pub error: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScreenshotItemMsg {}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for ScreenshotItem {
|
||||
type CommandOutput = ();
|
||||
type Init = ();
|
||||
type Input = ();
|
||||
type Output = ScreenshotItemMsg;
|
||||
type Widgets = PkgTileWidgets;
|
||||
type ParentWidget = adw::Carousel;
|
||||
type ParentMsg = PkgMsg;
|
||||
|
||||
view! {
|
||||
gtk::Box {
|
||||
set_margin_all: 15,
|
||||
set_halign: gtk::Align::Center,
|
||||
set_valign: gtk::Align::Fill,
|
||||
set_vexpand: true,
|
||||
gtk::Picture {
|
||||
#[watch]
|
||||
set_visible: self.path.is_some() && !self.error,
|
||||
#[watch]
|
||||
set_filename: self.path.as_ref(),
|
||||
set_halign: gtk::Align::Center,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_vexpand: true,
|
||||
},
|
||||
gtk::Spinner {
|
||||
set_halign: gtk::Align::Center,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_vexpand: true,
|
||||
#[watch]
|
||||
set_visible: self.path.is_none() && !self.error,
|
||||
set_spinning: true,
|
||||
set_height_request: 80,
|
||||
set_width_request: 80,
|
||||
set_margin_all: 30,
|
||||
},
|
||||
gtk::Image {
|
||||
add_css_class: "error",
|
||||
set_pixel_size: 64,
|
||||
set_icon_name: Some("dialog-error-symbolic"),
|
||||
#[watch]
|
||||
set_visible: self.error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_model(
|
||||
_parent: Self::Init,
|
||||
_index: &DynamicIndex,
|
||||
_sender: FactoryComponentSender<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
path: None,
|
||||
error: false,
|
||||
}
|
||||
}
|
||||
}
|
274
src/ui/searchpage.rs
Normal file
274
src/ui/searchpage.rs
Normal file
@ -0,0 +1,274 @@
|
||||
use std::{path::Path, collections::HashSet};
|
||||
use crate::APPINFO;
|
||||
|
||||
use super::window::*;
|
||||
use adw::prelude::*;
|
||||
use relm4::{factory::*, *, gtk::pango};
|
||||
|
||||
#[tracker::track]
|
||||
#[derive(Debug)]
|
||||
pub struct SearchPageModel {
|
||||
#[tracker::no_eq]
|
||||
searchitems: FactoryVecDeque<SearchItemModel>,
|
||||
searchitemtracker: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SearchPageMsg {
|
||||
Open,
|
||||
Search(Vec<SearchItem>),
|
||||
UpdateInstalled(HashSet<String>, HashSet<String>),
|
||||
Close,
|
||||
OpenRow(gtk::ListBoxRow)
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for SearchPageModel {
|
||||
type InitParams = ();
|
||||
type Input = SearchPageMsg;
|
||||
type Output = AppMsg;
|
||||
type Widgets = SearchPageWidgets;
|
||||
|
||||
view! {
|
||||
gtk::ScrolledWindow {
|
||||
set_hscrollbar_policy: gtk::PolicyType::Never,
|
||||
#[track(model.changed(SearchPageModel::searchitemtracker()))]
|
||||
set_vadjustment: gtk::Adjustment::NONE,
|
||||
adw::Clamp {
|
||||
gtk::Stack {
|
||||
set_margin_all: 20,
|
||||
#[local_ref]
|
||||
searchlist -> gtk::ListBox {
|
||||
set_valign: gtk::Align::Start,
|
||||
add_css_class: "boxed-list",
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
connect_row_activated[sender] => move |_, row| {
|
||||
sender.input(SearchPageMsg::OpenRow(row.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
(): Self::InitParams,
|
||||
root: &Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let model = SearchPageModel {
|
||||
searchitems: FactoryVecDeque::new(gtk::ListBox::new(), &sender.input),
|
||||
searchitemtracker: 0,
|
||||
tracker: 0,
|
||||
};
|
||||
|
||||
let searchlist = model.searchitems.widget();
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
self.reset();
|
||||
match msg {
|
||||
SearchPageMsg::Search(items) => {
|
||||
let mut searchitem_guard = self.searchitems.guard();
|
||||
searchitem_guard.clear();
|
||||
for item in items {
|
||||
searchitem_guard.push_back(item);
|
||||
}
|
||||
searchitem_guard.drop();
|
||||
self.update_searchitemtracker(|_| ());
|
||||
}
|
||||
SearchPageMsg::Open => {}
|
||||
SearchPageMsg::Close => {}
|
||||
SearchPageMsg::OpenRow(row) => {
|
||||
let searchitem_guard = self.searchitems.guard();
|
||||
for (i, child) in searchitem_guard.widget().iter_children().enumerate() {
|
||||
if child == row {
|
||||
if let Some(item) = searchitem_guard.get(i) {
|
||||
let pkg = &item.get_item().pkg;
|
||||
sender.output(AppMsg::OpenPkg(pkg.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SearchPageMsg::UpdateInstalled(installeduserpkgs, installedsystempkgs) => {
|
||||
let mut searchitem_guard = self.searchitems.guard();
|
||||
for i in 0..searchitem_guard.len() {
|
||||
if let Some(item) = searchitem_guard.get_mut(i) {
|
||||
let mut pkgitem = item.get_mut_item();
|
||||
pkgitem.installeduser = installeduserpkgs.contains(&pkgitem.pname.to_string());
|
||||
pkgitem.installedsystem = installedsystempkgs.contains(&pkgitem.pkg.to_string());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
pub struct SearchItem {
|
||||
pub name: String,
|
||||
pub pkg: String,
|
||||
pub pname: String,
|
||||
pub summary: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub installeduser: bool,
|
||||
pub installedsystem: bool,
|
||||
}
|
||||
|
||||
#[tracker::track]
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
pub struct SearchItemModel {
|
||||
pub item: SearchItem,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SearchItemMsg {}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for SearchItemModel {
|
||||
type CommandOutput = ();
|
||||
type Init = SearchItem;
|
||||
type Input = ();
|
||||
type Output = SearchItemMsg;
|
||||
type Widgets = SearchItemWidgets;
|
||||
type ParentWidget = adw::gtk::ListBox;
|
||||
type ParentMsg = SearchPageMsg;
|
||||
|
||||
view! {
|
||||
adw::PreferencesRow {
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_hexpand: true,
|
||||
set_spacing: 10,
|
||||
set_margin_all: 10,
|
||||
adw::Bin {
|
||||
set_valign: gtk::Align::Center,
|
||||
#[wrap(Some)]
|
||||
set_child = if self.item.icon.is_some() {
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_halign: gtk::Align::Start,
|
||||
set_from_file: {
|
||||
if let Some(i) = &self.item.icon {
|
||||
let iconpath = format!("{}/icons/nixos/128x128/{}", APPINFO, i);
|
||||
let iconpath64 = format!("{}/icons/nixos/64x64/{}", APPINFO, i);
|
||||
if Path::new(&iconpath).is_file() {
|
||||
Some(iconpath)
|
||||
} else if Path::new(&iconpath64).is_file() {
|
||||
Some(iconpath64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
set_pixel_size: 64,
|
||||
}
|
||||
} else {
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_halign: gtk::Align::Start,
|
||||
set_icon_name: Some("package-x-generic"),
|
||||
set_pixel_size: 64,
|
||||
}
|
||||
}
|
||||
},
|
||||
gtk::Overlay {
|
||||
add_overlay = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_valign: gtk::Align::Start,
|
||||
set_halign: gtk::Align::End,
|
||||
gtk::Image {
|
||||
add_css_class: "accent",
|
||||
set_valign: gtk::Align::Start,
|
||||
set_halign: gtk::Align::End,
|
||||
set_icon_name: Some("emblem-default-symbolic"),
|
||||
#[watch]
|
||||
set_visible: self.item.installeduser,
|
||||
},
|
||||
gtk::Image {
|
||||
add_css_class: "success",
|
||||
set_valign: gtk::Align::Start,
|
||||
set_halign: gtk::Align::End,
|
||||
set_icon_name: Some("emblem-default-symbolic"),
|
||||
#[watch]
|
||||
set_visible: self.item.installedsystem,
|
||||
}
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_halign: gtk::Align::Fill,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_spacing: 2,
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
set_label: self.item.name.as_str(),
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "dim-label",
|
||||
add_css_class: "caption",
|
||||
set_label: self.item.pkg.as_str(),
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
set_label: self.item.summary.as_deref().unwrap_or(""),
|
||||
set_visible: self.item.summary.is_some(),
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_model(
|
||||
parent: Self::Init,
|
||||
_index: &DynamicIndex,
|
||||
_sender: FactoryComponentSender<Self>,
|
||||
) -> Self {
|
||||
let sum = if let Some(s) = parent.summary {
|
||||
let mut sum = s.trim().to_string();
|
||||
while sum.contains('\n') {
|
||||
sum = sum.replace('\n', " ");
|
||||
}
|
||||
while sum.contains(" ") {
|
||||
sum = sum.replace(" ", " ");
|
||||
}
|
||||
Some(sum)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let item = SearchItem {
|
||||
name: parent.name,
|
||||
pkg: parent.pkg,
|
||||
pname: parent.pname,
|
||||
summary: sum,
|
||||
icon: parent.icon,
|
||||
installeduser: parent.installeduser,
|
||||
installedsystem: parent.installedsystem,
|
||||
};
|
||||
|
||||
Self { item, tracker: 0 }
|
||||
}
|
||||
}
|
137
src/ui/updatedialog.rs
Normal file
137
src/ui/updatedialog.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use adw::prelude::*;
|
||||
use relm4::*;
|
||||
|
||||
use super::updatepage::UpdatePageMsg;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpdateDialogModel {
|
||||
hidden: bool,
|
||||
message: String,
|
||||
done: bool,
|
||||
failed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UpdateDialogMsg {
|
||||
Show(String),
|
||||
Close,
|
||||
Done,
|
||||
Failed,
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for UpdateDialogModel {
|
||||
type InitParams = gtk::Window;
|
||||
type Input = UpdateDialogMsg;
|
||||
type Output = UpdatePageMsg;
|
||||
type Widgets = UpdateDialogWidgets;
|
||||
|
||||
view! {
|
||||
dialog = adw::Window {
|
||||
#[watch]
|
||||
set_visible: !model.hidden,
|
||||
set_transient_for: Some(&parent_window),
|
||||
set_modal: true,
|
||||
set_resizable: false,
|
||||
set_default_width: 500,
|
||||
set_default_height: 200,
|
||||
add_css_class: "dialog",
|
||||
add_css_class: "message",
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_halign: gtk::Align::Fill,
|
||||
set_valign: gtk::Align::Fill,
|
||||
set_hexpand: true,
|
||||
set_vexpand: true,
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_halign: gtk::Align::Center,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_vexpand: true,
|
||||
set_margin_all: 15,
|
||||
set_spacing: 10,
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_visible: !model.message.is_empty(),
|
||||
add_css_class: "title-1",
|
||||
#[watch]
|
||||
set_label: &model.message,
|
||||
},
|
||||
if model.done {
|
||||
gtk::Image {
|
||||
add_css_class: "success",
|
||||
set_icon_name: Some("emblem-ok-symbolic"),
|
||||
set_pixel_size: 128,
|
||||
}
|
||||
} else if model.failed {
|
||||
gtk::Image {
|
||||
add_css_class: "error",
|
||||
set_icon_name: Some("dialog-error-symbolic"),
|
||||
set_pixel_size: 128,
|
||||
}
|
||||
} else {
|
||||
gtk::Spinner {
|
||||
#[watch]
|
||||
set_visible: !model.done,
|
||||
#[watch]
|
||||
set_spinning: !model.done,
|
||||
}
|
||||
}
|
||||
},
|
||||
gtk::Box {
|
||||
#[watch]
|
||||
set_visible: model.done || model.failed,
|
||||
add_css_class: "dialog-action-area",
|
||||
set_valign: gtk::Align::End,
|
||||
set_vexpand: true,
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_homogeneous: true,
|
||||
gtk::Button {
|
||||
set_label: "Close",
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(UpdateDialogMsg::Close);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
parent_window: Self::InitParams,
|
||||
root: &Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let model = UpdateDialogModel {
|
||||
hidden: true,
|
||||
done: false,
|
||||
failed: false,
|
||||
message: String::default(),
|
||||
};
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
|
||||
match msg {
|
||||
UpdateDialogMsg::Show(desc) => {
|
||||
self.message = desc;
|
||||
self.hidden = false;
|
||||
self.done = false;
|
||||
self.failed = false;
|
||||
}
|
||||
UpdateDialogMsg::Close => {
|
||||
self.hidden = true;
|
||||
}
|
||||
UpdateDialogMsg::Done => {
|
||||
self.done = true;
|
||||
}
|
||||
UpdateDialogMsg::Failed => {
|
||||
self.failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
537
src/ui/updatepage.rs
Normal file
537
src/ui/updatepage.rs
Normal file
@ -0,0 +1,537 @@
|
||||
use crate::{parse::{cache::channelver, config::{getconfig, NscConfig}}, APPINFO};
|
||||
|
||||
use super::{pkgpage::InstallType, window::*, updatedialog::{UpdateDialogModel, UpdateDialogMsg}, updateworker::{UpdateAsyncHandler, UpdateAsyncHandlerMsg}};
|
||||
use adw::prelude::*;
|
||||
use relm4::{factory::*, gtk::pango, *};
|
||||
use std::{path::Path, convert::identity};
|
||||
|
||||
#[tracker::track]
|
||||
#[derive(Debug)]
|
||||
pub struct UpdatePageModel {
|
||||
#[tracker::no_eq]
|
||||
updateuserlist: FactoryVecDeque<UpdateItemModel>,
|
||||
#[tracker::no_eq]
|
||||
updatesystemlist: FactoryVecDeque<UpdateItemModel>,
|
||||
channelupdate: Option<(String, String)>,
|
||||
#[tracker::no_eq]
|
||||
updatedialog: Controller<UpdateDialogModel>,
|
||||
#[tracker::no_eq]
|
||||
updateworker: WorkerController<UpdateAsyncHandler>,
|
||||
config: NscConfig,
|
||||
updatetracker: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UpdatePageMsg {
|
||||
UpdateConfig(NscConfig),
|
||||
Update(Vec<UpdateItem>, Vec<UpdateItem>),
|
||||
OpenRow(usize, InstallType),
|
||||
UpdateSystem,
|
||||
UpdateAllUser,
|
||||
UpdateUser(String),
|
||||
UpdateChannels,
|
||||
UpdateSystemAndChannels,
|
||||
UpdateAll,
|
||||
DoneWorking,
|
||||
DoneLoading,
|
||||
FailedWorking,
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for UpdatePageModel {
|
||||
type InitParams = gtk::Window;
|
||||
type Input = UpdatePageMsg;
|
||||
type Output = AppMsg;
|
||||
type Widgets = UpdatePageWidgets;
|
||||
|
||||
view! {
|
||||
gtk::ScrolledWindow {
|
||||
set_hscrollbar_policy: gtk::PolicyType::Never,
|
||||
#[track(model.changed(UpdatePageModel::updatetracker()))]
|
||||
set_vadjustment: gtk::Adjustment::NONE,
|
||||
adw::Clamp {
|
||||
if model.channelupdate.is_some() || !model.updateuserlist.is_empty() || !model.updatesystemlist.is_empty() {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_valign: gtk::Align::Start,
|
||||
set_margin_all: 15,
|
||||
set_spacing: 15,
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_hexpand: true,
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "title-2",
|
||||
set_label: "Updates",
|
||||
},
|
||||
gtk::Button {
|
||||
add_css_class: "suggested-action",
|
||||
set_halign: gtk::Align::End,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_label: "Update Everything",
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(UpdatePageMsg::UpdateAll);
|
||||
}
|
||||
}
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_hexpand: true,
|
||||
#[watch]
|
||||
set_visible: model.channelupdate.is_some(),
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "title-4",
|
||||
set_label: "Channels",
|
||||
},
|
||||
},
|
||||
gtk::ListBox {
|
||||
set_valign: gtk::Align::Start,
|
||||
add_css_class: "boxed-list",
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
#[watch]
|
||||
set_visible: model.channelupdate.is_some(),
|
||||
adw::PreferencesRow {
|
||||
set_activatable: false,
|
||||
set_can_focus: false,
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_hexpand: true,
|
||||
set_spacing: 10,
|
||||
set_margin_all: 10,
|
||||
adw::Bin {
|
||||
set_valign: gtk::Align::Center,
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_halign: gtk::Align::Start,
|
||||
set_icon_name: Some("application-x-addon"),
|
||||
set_pixel_size: 64,
|
||||
}
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_halign: gtk::Align::Fill,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_spacing: 2,
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
set_label: "nixos",
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "dim-label",
|
||||
add_css_class: "caption",
|
||||
set_label: {
|
||||
&(if let Some((old, new)) = &model.channelupdate {
|
||||
format!("{} → {}", old, new)
|
||||
} else {
|
||||
String::default()
|
||||
})
|
||||
},
|
||||
set_visible: model.channelupdate.is_some(),
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
set_halign: gtk::Align::End,
|
||||
set_valign: gtk::Align::Center,
|
||||
gtk::Button {
|
||||
add_css_class: "suggested-action",
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::End,
|
||||
set_label: "Update channel and system",
|
||||
set_can_focus: false,
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(UpdatePageMsg::UpdateSystemAndChannels);
|
||||
}
|
||||
},
|
||||
gtk::Button {
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::End,
|
||||
set_label: "Update channel only",
|
||||
set_can_focus: false,
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(UpdatePageMsg::UpdateChannels);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_hexpand: true,
|
||||
#[watch]
|
||||
set_visible: !model.updateuserlist.is_empty(),
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "title-4",
|
||||
set_label: "User (nix-env)",
|
||||
},
|
||||
gtk::Button {
|
||||
add_css_class: "suggested-action",
|
||||
set_halign: gtk::Align::End,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_label: "Update All",
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(UpdatePageMsg::UpdateAllUser);
|
||||
}
|
||||
}
|
||||
},
|
||||
#[local_ref]
|
||||
updateuserlist -> gtk::ListBox {
|
||||
set_valign: gtk::Align::Start,
|
||||
add_css_class: "boxed-list",
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
connect_row_activated[sender] => move |listbox, row| {
|
||||
if let Some(i) = listbox.index_of_child(row) {
|
||||
sender.input(UpdatePageMsg::OpenRow(i as usize, InstallType::User));
|
||||
}
|
||||
},
|
||||
#[watch]
|
||||
set_visible: !model.updateuserlist.is_empty(),
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_hexpand: true,
|
||||
#[watch]
|
||||
set_visible: !model.updatesystemlist.is_empty(),
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "title-4",
|
||||
set_label: "System (configuration.nix)",
|
||||
},
|
||||
gtk::Button {
|
||||
add_css_class: "suggested-action",
|
||||
set_halign: gtk::Align::End,
|
||||
set_hexpand: true,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_label: "Update All",
|
||||
connect_clicked[sender] => move |_|{
|
||||
sender.input(UpdatePageMsg::UpdateSystem);
|
||||
},
|
||||
}
|
||||
},
|
||||
#[local_ref]
|
||||
updatesystemlist -> gtk::ListBox {
|
||||
set_valign: gtk::Align::Start,
|
||||
add_css_class: "boxed-list",
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
connect_row_activated[sender] => move |listbox, row| {
|
||||
if let Some(i) = listbox.index_of_child(row) {
|
||||
sender.input(UpdatePageMsg::OpenRow(i as usize, InstallType::System));
|
||||
}
|
||||
},
|
||||
#[watch]
|
||||
set_visible: !model.updatesystemlist.is_empty(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_vexpand: true,
|
||||
set_spacing: 10,
|
||||
gtk::Image {
|
||||
add_css_class: "success",
|
||||
set_icon_name: Some("emblem-ok-symbolic"),
|
||||
set_pixel_size: 256,
|
||||
},
|
||||
gtk::Label {
|
||||
add_css_class: "title-1",
|
||||
set_label: "Everything is up to date!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
parent_window: Self::InitParams,
|
||||
root: &Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let updatedialog = UpdateDialogModel::builder()
|
||||
.launch(parent_window.upcast())
|
||||
.forward(sender.input_sender(), identity);
|
||||
let updateworker = UpdateAsyncHandler::builder()
|
||||
.detach_worker(())
|
||||
.forward(sender.input_sender(), identity);
|
||||
|
||||
let config = getconfig();
|
||||
updateworker.emit(UpdateAsyncHandlerMsg::UpdateConfig(config.clone()));
|
||||
|
||||
let model = UpdatePageModel {
|
||||
updateuserlist: FactoryVecDeque::new(gtk::ListBox::new(), &sender.input),
|
||||
updatesystemlist: FactoryVecDeque::new(gtk::ListBox::new(), &sender.input),
|
||||
channelupdate: None,
|
||||
updatetracker: 0,
|
||||
updatedialog,
|
||||
updateworker,
|
||||
config,
|
||||
tracker: 0,
|
||||
};
|
||||
|
||||
let updateuserlist = model.updateuserlist.widget();
|
||||
let updatesystemlist = model.updatesystemlist.widget();
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
self.reset();
|
||||
match msg {
|
||||
UpdatePageMsg::UpdateConfig(config) => {
|
||||
self.config = config;
|
||||
self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateConfig(self.config.clone()));
|
||||
}
|
||||
UpdatePageMsg::Update(updateuserlist, updatesystemlist) => {
|
||||
self.channelupdate = channelver().unwrap_or(None);
|
||||
self.update_updatetracker(|_| ());
|
||||
let mut updateuserlist_guard = self.updateuserlist.guard();
|
||||
updateuserlist_guard.clear();
|
||||
for updateuser in updateuserlist {
|
||||
updateuserlist_guard.push_back(updateuser);
|
||||
}
|
||||
let mut updatesystemlist_guard = self.updatesystemlist.guard();
|
||||
updatesystemlist_guard.clear();
|
||||
for updatesystem in updatesystemlist {
|
||||
updatesystemlist_guard.push_back(updatesystem);
|
||||
}
|
||||
}
|
||||
UpdatePageMsg::OpenRow(row, pkgtype) => match pkgtype {
|
||||
InstallType::User => {
|
||||
let updateuserlist_guard = self.updateuserlist.guard();
|
||||
if let Some(item) = updateuserlist_guard.get(row) {
|
||||
if let Some(pkg) = &item.item.pkg {
|
||||
sender.output(AppMsg::OpenPkg(pkg.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
InstallType::System => {
|
||||
let updatesystemlist_guard = self.updatesystemlist.guard();
|
||||
if let Some(item) = updatesystemlist_guard.get(row) {
|
||||
if let Some(pkg) = &item.item.pkg {
|
||||
sender.output(AppMsg::OpenPkg(pkg.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
UpdatePageMsg::UpdateChannels => {
|
||||
self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating channels...")));
|
||||
self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateChannels);
|
||||
}
|
||||
UpdatePageMsg::UpdateSystemAndChannels => {
|
||||
self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating system and channels...")));
|
||||
self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateChannelsAndSystem);
|
||||
}
|
||||
UpdatePageMsg::UpdateSystem => {
|
||||
self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating system...")));
|
||||
self.updateworker.emit(UpdateAsyncHandlerMsg::RebuildSystem);
|
||||
}
|
||||
UpdatePageMsg::UpdateUser(pkg) => {
|
||||
println!("UPDATE USER PKG: {}", pkg);
|
||||
eprintln!("unimplemented");
|
||||
// self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating user...")));
|
||||
// self.updateworker.emit(UpdateAsyncHandlerMsg::RebuildUser);
|
||||
}
|
||||
UpdatePageMsg::UpdateAllUser => {
|
||||
self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating all user packages...")));
|
||||
self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateUserPkgs);
|
||||
}
|
||||
UpdatePageMsg::UpdateAll => {
|
||||
self.updatedialog.emit(UpdateDialogMsg::Show(String::from("Updating everything...")));
|
||||
self.updateworker.emit(UpdateAsyncHandlerMsg::UpdateAll);
|
||||
}
|
||||
UpdatePageMsg::DoneWorking => {
|
||||
sender.output(AppMsg::ReloadUpdate);
|
||||
}
|
||||
UpdatePageMsg::DoneLoading => {
|
||||
self.updatedialog.emit(UpdateDialogMsg::Done);
|
||||
}
|
||||
UpdatePageMsg::FailedWorking => {
|
||||
self.updatedialog.emit(UpdateDialogMsg::Failed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct UpdateItem {
|
||||
pub name: String,
|
||||
pub pkg: Option<String>,
|
||||
pub pname: String,
|
||||
pub summary: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub pkgtype: InstallType,
|
||||
pub verfrom: Option<String>,
|
||||
pub verto: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct UpdateItemModel {
|
||||
item: UpdateItem,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UpdateItemMsg {}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for UpdateItemModel {
|
||||
type CommandOutput = ();
|
||||
type Init = UpdateItem;
|
||||
type Input = ();
|
||||
type Output = UpdateItemMsg;
|
||||
type Widgets = UpdateItemWidgets;
|
||||
type ParentWidget = adw::gtk::ListBox;
|
||||
type ParentMsg = UpdatePageMsg;
|
||||
|
||||
view! {
|
||||
adw::PreferencesRow {
|
||||
set_activatable: self.item.pkg.is_some(),
|
||||
set_can_focus: false,
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_hexpand: true,
|
||||
set_spacing: 10,
|
||||
set_margin_all: 10,
|
||||
adw::Bin {
|
||||
set_valign: gtk::Align::Center,
|
||||
#[wrap(Some)]
|
||||
set_child = if self.item.icon.is_some() {
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_halign: gtk::Align::Start,
|
||||
set_from_file: {
|
||||
if let Some(i) = &self.item.icon {
|
||||
let iconpath = format!("{}/icons/nixos/128x128/{}", APPINFO, i);
|
||||
let iconpath64 = format!("{}/icons/nixos/64x64/{}", APPINFO, i);
|
||||
if Path::new(&iconpath).is_file() {
|
||||
Some(iconpath)
|
||||
} else if Path::new(&iconpath64).is_file() {
|
||||
Some(iconpath64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
set_pixel_size: 64,
|
||||
}
|
||||
} else {
|
||||
gtk::Image {
|
||||
add_css_class: "icon-dropshadow",
|
||||
set_halign: gtk::Align::Start,
|
||||
set_icon_name: Some("package-x-generic"),
|
||||
set_pixel_size: 64,
|
||||
}
|
||||
}
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_halign: gtk::Align::Fill,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_hexpand: true,
|
||||
set_spacing: 2,
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
set_label: self.item.name.as_str(),
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "dim-label",
|
||||
add_css_class: "caption",
|
||||
set_label: {
|
||||
&(if let Some(old) = &self.item.verfrom {
|
||||
if let Some(new) = &self.item.verto {
|
||||
format!("{} → {}", old, new)
|
||||
} else {
|
||||
String::default()
|
||||
}
|
||||
} else {
|
||||
String::default()
|
||||
})
|
||||
},
|
||||
set_visible: self.item.verfrom.is_some() && self.item.verto.is_some(),
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
gtk::Label {
|
||||
set_halign: gtk::Align::Start,
|
||||
set_label: self.item.summary.as_deref().unwrap_or(""),
|
||||
set_visible: self.item.summary.is_some(),
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
set_lines: 1,
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 0,
|
||||
},
|
||||
},
|
||||
// gtk::Button {
|
||||
// set_visible: self.item.pkgtype == InstallType::User,
|
||||
// set_valign: gtk::Align::Center,
|
||||
// set_halign: gtk::Align::End,
|
||||
// set_label: "Update",
|
||||
// set_can_focus: false,
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_model(
|
||||
parent: Self::Init,
|
||||
_index: &DynamicIndex,
|
||||
_sender: FactoryComponentSender<Self>,
|
||||
) -> Self {
|
||||
let sum = if let Some(s) = parent.summary {
|
||||
let mut sum = s.trim().to_string();
|
||||
while sum.contains('\n') {
|
||||
sum = sum.replace('\n', " ");
|
||||
}
|
||||
while sum.contains(" ") {
|
||||
sum = sum.replace(" ", " ");
|
||||
}
|
||||
Some(sum)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let item = UpdateItem {
|
||||
name: parent.name,
|
||||
pkg: parent.pkg,
|
||||
pname: parent.pname,
|
||||
summary: sum,
|
||||
icon: parent.icon,
|
||||
pkgtype: parent.pkgtype,
|
||||
verfrom: parent.verfrom,
|
||||
verto: parent.verto,
|
||||
};
|
||||
|
||||
Self { item }
|
||||
}
|
||||
}
|
261
src/ui/updateworker.rs
Normal file
261
src/ui/updateworker.rs
Normal file
@ -0,0 +1,261 @@
|
||||
use std::{error::Error, path::Path, process::Stdio};
|
||||
use relm4::*;
|
||||
use tokio::io::AsyncBufReadExt;
|
||||
|
||||
use crate::parse::config::NscConfig;
|
||||
|
||||
use super::updatepage::UpdatePageMsg;
|
||||
|
||||
#[tracker::track]
|
||||
#[derive(Debug)]
|
||||
pub struct UpdateAsyncHandler {
|
||||
#[tracker::no_eq]
|
||||
process: Option<JoinHandle<()>>,
|
||||
systemconfig: String,
|
||||
flakeargs: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UpdateAsyncHandlerMsg {
|
||||
UpdateConfig(NscConfig),
|
||||
|
||||
UpdateChannels,
|
||||
UpdateChannelsAndSystem,
|
||||
|
||||
RebuildSystem,
|
||||
UpdateUserPkgs,
|
||||
|
||||
UpdateAll,
|
||||
}
|
||||
|
||||
enum NscCmd {
|
||||
Rebuild,
|
||||
Channel,
|
||||
All,
|
||||
}
|
||||
|
||||
impl Worker for UpdateAsyncHandler {
|
||||
type InitParams = ();
|
||||
type Input = UpdateAsyncHandlerMsg;
|
||||
type Output = UpdatePageMsg;
|
||||
|
||||
fn init(_params: Self::InitParams, _sender: relm4::ComponentSender<Self>) -> Self {
|
||||
Self {
|
||||
process: None,
|
||||
systemconfig: String::default(),
|
||||
flakeargs: None,
|
||||
tracker: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
match msg {
|
||||
UpdateAsyncHandlerMsg::UpdateConfig(config) => {
|
||||
self.systemconfig = config.systemconfig;
|
||||
self.flakeargs = config.flake;
|
||||
}
|
||||
UpdateAsyncHandlerMsg::UpdateChannels => {
|
||||
let systenconfig = self.systemconfig.clone();
|
||||
let flakeargs = self.flakeargs.clone();
|
||||
relm4::spawn(async move {
|
||||
println!("STARTED");
|
||||
let result = runcmd(NscCmd::Channel, systenconfig, flakeargs).await;
|
||||
match result {
|
||||
Ok(true) => {
|
||||
println!("CHANNEL DONE");
|
||||
sender.output(UpdatePageMsg::DoneWorking);
|
||||
}
|
||||
_ => {
|
||||
println!("CHANNEL FAILED");
|
||||
sender.output(UpdatePageMsg::FailedWorking);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
UpdateAsyncHandlerMsg::UpdateChannelsAndSystem => {
|
||||
let systenconfig = self.systemconfig.clone();
|
||||
let flakeargs = self.flakeargs.clone();
|
||||
relm4::spawn(async move {
|
||||
println!("STARTED");
|
||||
let result = runcmd(NscCmd::All, systenconfig, flakeargs).await;
|
||||
match result {
|
||||
Ok(true) => {
|
||||
println!("ALL DONE");
|
||||
sender.output(UpdatePageMsg::DoneWorking);
|
||||
}
|
||||
_ => {
|
||||
println!("ALL FAILED");
|
||||
sender.output(UpdatePageMsg::FailedWorking);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
UpdateAsyncHandlerMsg::RebuildSystem => {
|
||||
let systenconfig = self.systemconfig.clone();
|
||||
let flakeargs = self.flakeargs.clone();
|
||||
relm4::spawn(async move {
|
||||
println!("STARTED");
|
||||
let result = runcmd(NscCmd::Rebuild, systenconfig, flakeargs).await;
|
||||
match result {
|
||||
Ok(true) => {
|
||||
println!("REBUILD DONE");
|
||||
sender.output(UpdatePageMsg::DoneWorking);
|
||||
}
|
||||
_ => {
|
||||
println!("REBUILD FAILED");
|
||||
sender.output(UpdatePageMsg::FailedWorking);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
UpdateAsyncHandlerMsg::UpdateUserPkgs => {
|
||||
relm4::spawn(async move {
|
||||
println!("STARTED");
|
||||
let result = updateenv().await;
|
||||
match result {
|
||||
Ok(true) => {
|
||||
println!("USER DONE");
|
||||
sender.output(UpdatePageMsg::DoneWorking);
|
||||
}
|
||||
_ => {
|
||||
println!("USER FAILED");
|
||||
sender.output(UpdatePageMsg::FailedWorking);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
UpdateAsyncHandlerMsg::UpdateAll => {
|
||||
let systenconfig = self.systemconfig.clone();
|
||||
let flakeargs = self.flakeargs.clone();
|
||||
relm4::spawn(async move {
|
||||
println!("STARTED");
|
||||
let result = runcmd(NscCmd::All, systenconfig, flakeargs).await;
|
||||
match result {
|
||||
Ok(true) => {
|
||||
println!("ALL pkexec DONE");
|
||||
match updateenv().await {
|
||||
Ok(true) => {
|
||||
println!("ALL DONE");
|
||||
sender.output(UpdatePageMsg::DoneWorking);
|
||||
}
|
||||
_ => {
|
||||
println!("ALL FAILED");
|
||||
sender.output(UpdatePageMsg::FailedWorking);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("ALL FAILED");
|
||||
sender.output(UpdatePageMsg::FailedWorking);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn runcmd(
|
||||
cmd: NscCmd,
|
||||
_systemconfig: String,
|
||||
flakeargs: Option<String>,
|
||||
) -> Result<bool, Box<dyn Error + Send + Sync>> {
|
||||
let exe = match std::env::current_exe() {
|
||||
Ok(mut e) => {
|
||||
e.pop(); // root/bin
|
||||
// e.pop(); // root/
|
||||
// e.push("libexec"); // root/libexec
|
||||
e.push("nsc-helper");
|
||||
let x = e.to_string_lossy().to_string();
|
||||
println!("CURRENT PATH {}", x);
|
||||
if Path::new(&x).is_file() {
|
||||
x
|
||||
} else {
|
||||
String::from("nsc-helper")
|
||||
}
|
||||
}
|
||||
Err(_) => String::from("nsc-helper"),
|
||||
};
|
||||
|
||||
let rebuildargs = if let Some(x) = flakeargs {
|
||||
let mut v = vec![String::from("--flake")];
|
||||
for arg in x.split(' ') {
|
||||
if !arg.is_empty() {
|
||||
v.push(String::from(arg));
|
||||
}
|
||||
}
|
||||
v
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let mut cmd = match cmd {
|
||||
NscCmd::Rebuild => tokio::process::Command::new("pkexec")
|
||||
.arg(&exe)
|
||||
.arg("rebuild")
|
||||
.arg("--")
|
||||
.arg("switch")
|
||||
.args(&rebuildargs)
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?,
|
||||
NscCmd::Channel => tokio::process::Command::new("pkexec")
|
||||
.arg(&exe)
|
||||
.arg("channel")
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?,
|
||||
NscCmd::All => tokio::process::Command::new("pkexec")
|
||||
.arg(&exe)
|
||||
.arg("channel")
|
||||
.arg("--rebuild")
|
||||
.arg("--")
|
||||
.arg("switch")
|
||||
.args(&rebuildargs)
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?,
|
||||
};
|
||||
|
||||
println!("SENT INPUT");
|
||||
let stderr = cmd.stderr.take().unwrap();
|
||||
let reader = tokio::io::BufReader::new(stderr);
|
||||
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
println!("CAUGHT REBUILD LINE: {}", line);
|
||||
}
|
||||
println!("READER DONE");
|
||||
if cmd.wait().await?.success() {
|
||||
println!("SUCCESS");
|
||||
// sender.input(InstallAsyncHandlerMsg::SetPid(None));
|
||||
Ok(true)
|
||||
} else {
|
||||
println!("FAILURE");
|
||||
// sender.input(InstallAsyncHandlerMsg::SetPid(None));
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
async fn updateenv() -> Result<bool, Box<dyn Error + Send + Sync>> {
|
||||
let mut cmd = tokio::process::Command::new("nix-env")
|
||||
.arg("-u")
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
println!("SENT INPUT");
|
||||
let stderr = cmd.stderr.take().unwrap();
|
||||
let reader = tokio::io::BufReader::new(stderr);
|
||||
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
println!("CAUGHT NIXENV LINE: {}", line);
|
||||
}
|
||||
println!("READER DONE");
|
||||
if cmd.wait().await?.success() {
|
||||
println!("SUCCESS");
|
||||
// sender.input(InstallAsyncHandlerMsg::SetPid(None));
|
||||
Ok(true)
|
||||
} else {
|
||||
println!("FAILURE");
|
||||
// sender.input(InstallAsyncHandlerMsg::SetPid(None));
|
||||
Ok(false)
|
||||
}
|
||||
}
|
1236
src/ui/window.rs
Normal file
1236
src/ui/window.rs
Normal file
File diff suppressed because it is too large
Load Diff
481
src/ui/windowloading.rs
Normal file
481
src/ui/windowloading.rs
Normal file
@ -0,0 +1,481 @@
|
||||
use super::window::AppMsg;
|
||||
use crate::parse::cache::checkcache;
|
||||
use crate::parse::packages::readpkgs;
|
||||
use crate::parse::packages::readsyspkgs;
|
||||
use crate::parse::packages::Package;
|
||||
use crate::ui::categories::PkgCategory;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use relm4::adw::prelude::*;
|
||||
use relm4::*;
|
||||
use std::{collections::HashMap, env};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
pub struct WindowAsyncHandler;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WindowAsyncHandlerMsg {
|
||||
CheckCache(CacheReturn),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CacheReturn {
|
||||
Init,
|
||||
Update,
|
||||
}
|
||||
|
||||
impl Worker for WindowAsyncHandler {
|
||||
type InitParams = ();
|
||||
type Input = WindowAsyncHandlerMsg;
|
||||
type Output = AppMsg;
|
||||
|
||||
fn init(_params: Self::InitParams, _sender: relm4::ComponentSender<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
match msg {
|
||||
WindowAsyncHandlerMsg::CheckCache(cr) => {
|
||||
println!("CHECK CACHE");
|
||||
relm4::spawn(async move {
|
||||
match checkcache() {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
println!("FAILED TO CHECK CACHE");
|
||||
sender.output(AppMsg::LoadError(
|
||||
String::from("Could not load cache"),
|
||||
String::from(
|
||||
"Try connecting to the internet or launching the application again",
|
||||
),
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
let pkgs = match readpkgs().await {
|
||||
Ok(pkgs) => pkgs,
|
||||
Err(_) => {
|
||||
println!("FAILED TO LOAD PKGS");
|
||||
sender.output(AppMsg::LoadError(
|
||||
String::from("Could not load packages"),
|
||||
String::from(
|
||||
"Try connecting to the internet or launching the application again",
|
||||
),
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let newpkgs = match readsyspkgs() {
|
||||
Ok(newpkgs) => newpkgs,
|
||||
Err(_) => {
|
||||
println!("FAILED TO LOAD NEW PKGS");
|
||||
sender.output(AppMsg::LoadError(
|
||||
String::from("Could not load new packages"),
|
||||
String::from(
|
||||
"Try connecting to the internet or launching the application again",
|
||||
),
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
println!("GOT PKGS");
|
||||
|
||||
let mut recpicks = vec![];
|
||||
let mut catpicks: HashMap<PkgCategory, Vec<String>> = HashMap::new();
|
||||
let mut catpkgs: HashMap<PkgCategory, Vec<String>> = HashMap::new();
|
||||
|
||||
|
||||
if cr == CacheReturn::Init {
|
||||
println!("INIT");
|
||||
let desktopenv = env::var("XDG_CURRENT_DESKTOP").unwrap_or_default();
|
||||
let appdatapkgs = pkgs
|
||||
.iter()
|
||||
.filter(|(x, _)| {
|
||||
if let Some(p) = pkgs.get(*x) {
|
||||
if let Some(data) = &p.appdata {
|
||||
(if let Some(i) = &data.icon {
|
||||
i.cached.is_some()
|
||||
} else {
|
||||
false
|
||||
}) && data.description.is_some()
|
||||
&& data.name.is_some()
|
||||
&& data.launchable.is_some()
|
||||
&& data.screenshots.is_some()
|
||||
&& (!x.starts_with("gnome.") || desktopenv == "GNOME")
|
||||
&& (!x.starts_with("xfce.") || desktopenv == "XFCE")
|
||||
&& (!x.starts_with("mate.") || desktopenv == "MATE")
|
||||
&& (!x.starts_with("cinnamon.")
|
||||
|| desktopenv == "X-Cinnamon")
|
||||
&& (!x.starts_with("libsForQt5") || desktopenv == "KDE")
|
||||
&& (!x.starts_with("pantheon.")
|
||||
|| desktopenv == "Pantheon")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut recommendedpkgs = appdatapkgs
|
||||
.keys()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let mut rng = thread_rng();
|
||||
recommendedpkgs.shuffle(&mut rng);
|
||||
|
||||
let mut desktoppicks = recommendedpkgs
|
||||
.iter()
|
||||
.filter(|x| {
|
||||
if desktopenv == "GNOME" {
|
||||
x.starts_with("gnome.") || x.starts_with("gnome-")
|
||||
} else if desktopenv == "XFCE" {
|
||||
x.starts_with("xfce.")
|
||||
} else if desktopenv == "MATE" {
|
||||
x.starts_with("mate.")
|
||||
} else if desktopenv == "X-Cinnamon" {
|
||||
x.starts_with("cinnamon.")
|
||||
} else if desktopenv == "KDE" {
|
||||
x.starts_with("libsForQt5")
|
||||
} else if desktopenv == "Pantheon" {
|
||||
x.starts_with("pantheon.")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for p in desktoppicks.iter().take(3) {
|
||||
recpicks.push(p.to_string());
|
||||
}
|
||||
for category in PkgCategory::iter() {
|
||||
println!("CATEGORY: {}", category);
|
||||
desktoppicks.shuffle(&mut rng);
|
||||
let mut cvec = vec![];
|
||||
let mut allvec = vec![];
|
||||
let mut rpkgs = recommendedpkgs.clone();
|
||||
fn checkpkgs(
|
||||
pkg: String,
|
||||
pkgs: &HashMap<&String, &Package>,
|
||||
category: PkgCategory,
|
||||
) -> bool {
|
||||
match category {
|
||||
PkgCategory::Audio => {
|
||||
// Audio:
|
||||
// - pkgs/applications/audio
|
||||
if let Some(p) = pkgs.get(&pkg) {
|
||||
if let Some(pos) = &p.meta.position {
|
||||
if pos.starts_with("pkgs/applications/audio") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(data) = &p.appdata {
|
||||
if let Some(categories) = &data.categories {
|
||||
if categories.contains(&String::from("Audio")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
PkgCategory::Development => {
|
||||
// Development:
|
||||
// - pkgs/development
|
||||
// - pkgs/applications/terminal-emulators
|
||||
// - xdg: Development
|
||||
if let Some(p) = pkgs.get(&pkg) {
|
||||
if let Some(pos) = &p.meta.position {
|
||||
if pos.starts_with("pkgs/development")
|
||||
|| pos.starts_with(
|
||||
"pkgs/applications/terminal-emulators",
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(data) = &p.appdata {
|
||||
if let Some(categories) = &data.categories {
|
||||
if categories
|
||||
.contains(&String::from("Development"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
PkgCategory::Games => {
|
||||
// Games:
|
||||
// - pkgs/games
|
||||
// - pkgs/applications/emulators
|
||||
// - pkgs/tools/games
|
||||
// - xdg::Games
|
||||
if let Some(p) = pkgs.get(&pkg) {
|
||||
if let Some(pos) = &p.meta.position {
|
||||
if pos.starts_with("pkgs/games")
|
||||
|| pos.starts_with(
|
||||
"pkgs/applications/emulators",
|
||||
)
|
||||
|| pos.starts_with("pkgs/tools/games")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(data) = &p.appdata {
|
||||
if let Some(categories) = &data.categories {
|
||||
if categories.contains(&String::from("Games")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
PkgCategory::Graphics => {
|
||||
// Graphics:
|
||||
// - pkgs/applications/graphics
|
||||
// - xdg: Graphics
|
||||
if let Some(p) = pkgs.get(&pkg) {
|
||||
if let Some(pos) = &p.meta.position {
|
||||
if pos.starts_with("pkgs/applications/graphics")
|
||||
|| pos.starts_with("xdg:Graphics")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(data) = &p.appdata {
|
||||
if let Some(categories) = &data.categories {
|
||||
if categories.contains(&String::from("Graphics")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
PkgCategory::Network => {
|
||||
// Network:
|
||||
// - pkgs/applications/networking
|
||||
// - xdg: Network
|
||||
if let Some(p) = pkgs.get(&pkg) {
|
||||
if let Some(pos) = &p.meta.position {
|
||||
if pos.starts_with("pkgs/applications/networking")
|
||||
|| pos.starts_with("xdg:Network")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(data) = &p.appdata {
|
||||
if let Some(categories) = &data.categories {
|
||||
if categories.contains(&String::from("Network")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
PkgCategory::Video => {
|
||||
// Video:
|
||||
// - pkgs/applications/video
|
||||
// - xdg: Video
|
||||
if let Some(p) = pkgs.get(&pkg) {
|
||||
if let Some(pos) = &p.meta.position {
|
||||
if pos.starts_with("pkgs/applications/video")
|
||||
|| pos.starts_with("xdg:Video")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(data) = &p.appdata {
|
||||
if let Some(categories) = &data.categories {
|
||||
if categories.contains(&String::from("Video")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for pkg in desktoppicks.iter().take(3) {
|
||||
if checkpkgs(pkg.to_string(), &appdatapkgs, category.clone()) {
|
||||
cvec.push(pkg.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
while cvec.len() < 12 {
|
||||
if let Some(pkg) = rpkgs.pop() {
|
||||
if !cvec.contains(&pkg.to_string())
|
||||
&& checkpkgs(
|
||||
pkg.to_string(),
|
||||
&appdatapkgs,
|
||||
category.clone(),
|
||||
)
|
||||
{
|
||||
cvec.push(pkg.to_string());
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
println!("{} PICKS {:#?}", category, cvec);
|
||||
|
||||
let catagortypkgs = pkgs
|
||||
.iter()
|
||||
.filter(|(x, _)| {
|
||||
if let Some(p) = appdatapkgs.get(*x) {
|
||||
if let Some(position) = &p.meta.position {
|
||||
(position.starts_with("pkgs/applications/audio") && category == PkgCategory::Audio)
|
||||
|| (position.starts_with("pkgs/applications/terminal-emulators") && category == PkgCategory::Development)
|
||||
|| (position.starts_with("pkgs/applications/emulators") && category == PkgCategory::Games)
|
||||
|| (position.starts_with("pkgs/applications/graphics") && category == PkgCategory::Graphics)
|
||||
|| (position.starts_with("pkgs/applications/networking") && category == PkgCategory::Network)
|
||||
|| (position.starts_with("pkgs/applications/video") && category == PkgCategory::Video)
|
||||
|| (position.starts_with("pkgs/tools/games") && category == PkgCategory::Games)
|
||||
|| (position.starts_with("pkgs/games") && category == PkgCategory::Games)
|
||||
|| (position.starts_with("pkgs/development") && category == PkgCategory::Development)
|
||||
|| appdatapkgs.contains_key(x)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
for pkg in catagortypkgs.keys() {
|
||||
if checkpkgs(pkg.to_string(), &catagortypkgs, category.clone()) {
|
||||
allvec.push(pkg.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
println!("{} ALL {:#?}", category, allvec);
|
||||
cvec.shuffle(&mut rng);
|
||||
allvec.sort_by_key(|x| x.to_lowercase());
|
||||
catpicks.insert(category.clone(), cvec);
|
||||
catpkgs.insert(category.clone(), allvec);
|
||||
}
|
||||
|
||||
while recpicks.len() < 12 {
|
||||
if let Some(p) = recommendedpkgs.pop() {
|
||||
if !recpicks.contains(&p.to_string()) {
|
||||
recpicks.push(p);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
recpicks.shuffle(&mut rng);
|
||||
}
|
||||
|
||||
println!("SEND INIT");
|
||||
match cr {
|
||||
CacheReturn::Init => {
|
||||
sender.output(AppMsg::Initialize(pkgs, recpicks, newpkgs, catpicks, catpkgs));
|
||||
}
|
||||
CacheReturn::Update => {
|
||||
sender.output(AppMsg::ReloadUpdateItems(pkgs, newpkgs));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LoadErrorModel {
|
||||
hidden: bool,
|
||||
msg: String,
|
||||
msg2: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LoadErrorMsg {
|
||||
Show(String, String),
|
||||
Retry,
|
||||
Close,
|
||||
// Preferences,
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for LoadErrorModel {
|
||||
type InitParams = gtk::Window;
|
||||
type Input = LoadErrorMsg;
|
||||
type Output = AppMsg;
|
||||
type Widgets = LoadErrorWidgets;
|
||||
|
||||
view! {
|
||||
dialog = gtk::MessageDialog {
|
||||
set_transient_for: Some(&parent_window),
|
||||
set_modal: true,
|
||||
#[watch]
|
||||
set_visible: !model.hidden,
|
||||
#[watch]
|
||||
set_text: Some(&model.msg),
|
||||
#[watch]
|
||||
set_secondary_text: Some(&model.msg2),
|
||||
set_use_markup: true,
|
||||
set_secondary_use_markup: true,
|
||||
add_button: ("Retry", gtk::ResponseType::Accept),
|
||||
// add_button: ("Preferences", gtk::ResponseType::Help),
|
||||
add_button: ("Quit", gtk::ResponseType::Close),
|
||||
connect_response[sender] => move |_, resp| {
|
||||
sender.input(match resp {
|
||||
gtk::ResponseType::Accept => LoadErrorMsg::Retry,
|
||||
gtk::ResponseType::Close => LoadErrorMsg::Close,
|
||||
// gtk::ResponseType::Help => LoadErrorMsg::Preferences,
|
||||
_ => unreachable!(),
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
parent_window: Self::InitParams,
|
||||
root: &Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let model = LoadErrorModel {
|
||||
hidden: true,
|
||||
msg: String::default(),
|
||||
msg2: String::default(),
|
||||
};
|
||||
let widgets = view_output!();
|
||||
let accept_widget = widgets
|
||||
.dialog
|
||||
.widget_for_response(gtk::ResponseType::Accept)
|
||||
.expect("No button for accept response set");
|
||||
accept_widget.add_css_class("warning");
|
||||
// let pref_widget = widgets
|
||||
// .dialog
|
||||
// .widget_for_response(gtk::ResponseType::Help)
|
||||
// .expect("No button for help response set");
|
||||
// pref_widget.add_css_class("suggested-action");
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
match msg {
|
||||
LoadErrorMsg::Show(s, s2) => {
|
||||
self.hidden = false;
|
||||
self.msg = s;
|
||||
self.msg2 = s2;
|
||||
}
|
||||
LoadErrorMsg::Retry => {
|
||||
self.hidden = true;
|
||||
sender.output(AppMsg::TryLoad)
|
||||
}
|
||||
LoadErrorMsg::Close => sender.output(AppMsg::Close),
|
||||
// LoadErrorMsg::Preferences => sender.output(AppMsg::ShowPrefMenu),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user