This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
language: "en-US"
|
||||
early_access: false
|
||||
reviews:
|
||||
profile: "chill"
|
||||
request_changes_workflow: false
|
||||
high_level_summary: false
|
||||
poem: false
|
||||
review_status: true
|
||||
review_details: false
|
||||
auto_review:
|
||||
enabled: true
|
||||
drafts: false
|
||||
@@ -0,0 +1,6 @@
|
||||
result
|
||||
.direnv
|
||||
/.pre-commit-config.yaml
|
||||
*.AppImage
|
||||
*.exe
|
||||
/target
|
||||
@@ -0,0 +1,9 @@
|
||||
extends: default
|
||||
rules:
|
||||
line-length:
|
||||
max: 160
|
||||
truthy:
|
||||
check-keys: false
|
||||
document-start:
|
||||
present: false
|
||||
comments-indentation: disable
|
||||
Generated
+561
@@ -0,0 +1,561 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "duct"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e66e9c0c03d094e1a0ba1be130b849034aa80c3a2ab8ee94316bc809f3fa684"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"os_pipe",
|
||||
"shared_child",
|
||||
"shared_thread",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "mutually_exclusive_features"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.31.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "runner"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"duct",
|
||||
"mutually_exclusive_features",
|
||||
"nix",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"walkdir",
|
||||
"xdgdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_child"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"sigchld",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_thread"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0"
|
||||
|
||||
[[package]]
|
||||
name = "sigchld"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"os_pipe",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "xdgdir"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c93fba109a63f08e168ab02529ddabd99e4ebb78b98ed6bce82e3da52522d235"
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/runner"]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
version = "0.1.0"
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "affinity-nix"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
pedantic = { level = "deny" }
|
||||
nursery = { level = "deny" }
|
||||
+674
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,206 @@
|
||||
# affinity-nix
|
||||
|
||||

|
||||
|
||||
## About
|
||||
|
||||
Affinity v3 & v2 packaged with Nix!
|
||||
|
||||
Based on https://github.com/lf-/affinity-crimes and https://affinity.liz.pet/, and uses [ElementalWarrior's wine](https://gitlab.winehq.org/ElementalWarrior/wine).
|
||||
|
||||
We also install https://github.com/noahc3/AffinityPluginLoader for a far more pleasant experience.
|
||||
|
||||
> [!WARNING]
|
||||
> Affinity on Wine is far from perfect and you will likely experience bugs and issues. Please have patience and [report any issues you experience](https://github.com/mrshmllow/affinity-nix/issues/new/choose).
|
||||
|
||||
## Support
|
||||
|
||||
Thank you to my GitHub sponsors!
|
||||
|
||||

|
||||
|
||||
## Preamble
|
||||
|
||||
> [!TIP]
|
||||
> We provide a binary cache for this repo. Please [add cache.forall.systems as a substituter](https://cache.forall.systems/) to avoid compiling yourself.
|
||||
|
||||
> [!NOTE]
|
||||
> This repo does not attempt to redistribute affinity archives. Any instance of caching Canva IP should be reported as a bug.
|
||||
|
||||
User preferences are located in `$XDG_DATA_HOME/affinity/` or `$XDG_DATA_HOME/affinity-v3/` falling back to `$HOME/.local/share/affinity/` or `$HOME/.local/share/affinity-v3/`.
|
||||
|
||||
## How it works
|
||||
|
||||
A wine prefix containing all the necessary dependencies and the affinity installation is built in nix and mounted at runtime.
|
||||
Overlayfs is used to keep your user preferences intact. [fuse-overlayfs](https://github.com/containers/fuse-overlayfs) will be fallen back on if your kernel rejects unprivileged user namespaces, common on hardened systems. This can reduce performance.
|
||||
|
||||
```mermaid
|
||||
graph LR;
|
||||
A([Winetricks])-.->B[Nix Store];
|
||||
C([Affinity])-.->B;
|
||||
D([Wine])-.->B;
|
||||
|
||||
B e1@-->E[Overlayfs];
|
||||
F[User Data] e2@<-->E;
|
||||
|
||||
E<-- Mounted @ Runtime -->G[Affinity Application];
|
||||
```
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
### Running Ad-hoc
|
||||
|
||||
```bash
|
||||
$ nix run github:mrshmllow/affinity-nix#affinity-v3
|
||||
|
||||
-- v2 versions:
|
||||
|
||||
$ nix run github:mrshmllow/affinity-nix#affinity-photo
|
||||
$ nix run github:mrshmllow/affinity-nix#affinity-designer
|
||||
$ nix run github:mrshmllow/affinity-nix#affinity-publisher
|
||||
```
|
||||
|
||||
### Installing the applications on your system (Optional)
|
||||
|
||||
#### Install with nix-profile
|
||||
|
||||
```bash
|
||||
$ nix profile install github:mrshmllow/affinity-nix#affinity-v3
|
||||
|
||||
-- v2 versions:
|
||||
|
||||
$ nix profile install github:mrshmllow/affinity-nix#affinity-photo
|
||||
$ nix profile install github:mrshmllow/affinity-nix#affinity-designer
|
||||
$ nix profile install github:mrshmllow/affinity-nix#affinity-publisher
|
||||
```
|
||||
|
||||
#### Install on NixOS / Home Manager with Flakes
|
||||
|
||||
Installing via an overlay is recommended, as the package is `unfree`
|
||||
making it difficult to use directly.
|
||||
|
||||
Install with NixOS:
|
||||
|
||||
```nix
|
||||
# flake.nix
|
||||
{
|
||||
inputs = {
|
||||
affinity-nix.url = "github:mrshmllow/affinity-nix";
|
||||
# ...
|
||||
};
|
||||
|
||||
outputs = inputs @ {
|
||||
affinity-nix,
|
||||
...
|
||||
}: {
|
||||
nixosConfigurations.my-system = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs = {inherit inputs;};
|
||||
modules = [
|
||||
# ...
|
||||
({ pkgs, ... }: {
|
||||
nixpkgs.overlays = [ affinity-nix.overlays.default ];
|
||||
|
||||
environment.systemPackages = [ pkgs.affinity-v3 ];
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Install with Home Manager:
|
||||
|
||||
```nix
|
||||
# flake.nix
|
||||
{
|
||||
inputs = {
|
||||
affinity-nix.url = "github:mrshmllow/affinity-nix";
|
||||
# ...
|
||||
};
|
||||
|
||||
outputs = inputs @ {
|
||||
affinity-nix,
|
||||
...
|
||||
}: {
|
||||
homeConfigurations.my-user = home-manager.lib.homeManagerConfiguration {
|
||||
pkgs = nixpkgs.legacyPackages."x86_64-linux";
|
||||
extraSpecialArgs = {inherit inputs;};
|
||||
modules = [
|
||||
# ...
|
||||
({ pkgs, ... }: {
|
||||
nixpkgs.overlays = [ affinity-nix.overlays.default ];
|
||||
|
||||
home.packages = [ pkgs.affinity-v3 ];
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Install on NixOS without Flakes
|
||||
|
||||
Install without flakes:
|
||||
|
||||
```nixos
|
||||
# configuration.nix
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
let
|
||||
affinity = import (fetchTarball "https://github.com/mrshmllow/affinity-nix/archive/refs/heads/main.tar.gz");
|
||||
in
|
||||
{
|
||||
nixpkgs.overlays = [
|
||||
affinity.overlays.default
|
||||
];
|
||||
|
||||
users.users.my-user.packages = with pkgs; [
|
||||
affinity-photo
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Troubleshooting, winetricks, wineboot, and more
|
||||
|
||||
Each package (`v3|photo|designer|publisher`) has the following usage:
|
||||
|
||||
```sh
|
||||
$ affinity-v3 --help
|
||||
Usage: affinity-v3 [OPTIONS] [AFFINITY_ARGUMENTS]... [COMMAND]
|
||||
|
||||
Commands:
|
||||
wine Run wine within sandbox
|
||||
winetricks Run winetricks within sandbox
|
||||
wineboot Run wineboot within sandbox
|
||||
wineserver Run wineserver within sandbox
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Arguments:
|
||||
[AFFINITY_ARGUMENTS]... Arguments for affinity application
|
||||
|
||||
Options:
|
||||
--verbose Make Wine far more verbose
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Armed with these you should be able to follow https://affinity.liz.pet/v2/misc-troubleshooting/ for troubleshooting steps.
|
||||
|
||||
For example, accessing `wine`:
|
||||
|
||||
```sh
|
||||
$ affinity-v3 wine
|
||||
Usage: wine PROGRAM [ARGUMENTS...] Run the specified program
|
||||
wine --help Display this help and exit
|
||||
wine --version Output version information and exit
|
||||
|
||||
```
|
||||
|
||||
Or `winecfg`:
|
||||
|
||||
```sh
|
||||
$ affinity-v3 wine winecfg
|
||||
```
|
||||
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "runner"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.102"
|
||||
clap = { version = "4.6.0", features = ["derive"] }
|
||||
duct = "1.1.1"
|
||||
mutually_exclusive_features = "0.1.0"
|
||||
nix = { version = "0.31.2", features = ["sched", "mount"] }
|
||||
tracing = "0.1.44"
|
||||
tracing-subscriber = "0.3.23"
|
||||
walkdir = "2.5.0"
|
||||
xdgdir = "0.8.0"
|
||||
|
||||
[features]
|
||||
default = ["v3"]
|
||||
|
||||
v3 = []
|
||||
photo = []
|
||||
designer = []
|
||||
publisher = []
|
||||
@@ -0,0 +1,115 @@
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use duct::cmd;
|
||||
use tracing::{info, instrument};
|
||||
|
||||
use crate::make_env;
|
||||
|
||||
pub static REGISTRY_PATCHES: LazyLock<PathBuf> =
|
||||
LazyLock::new(|| PathBuf::from(env!("REGISTRY_PATCHES")));
|
||||
pub static ON_LINUX: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from(env!("ON_LINUX")));
|
||||
pub const RSYNC: &str = env!("RSYNC");
|
||||
|
||||
const LATEST_REVISION: u32 = 10;
|
||||
|
||||
#[instrument(skip_all, ret)]
|
||||
pub fn read_revision(wine_prefix: &Path) -> Result<Option<u32>> {
|
||||
let revision_file = wine_prefix.join(".revision");
|
||||
|
||||
if !revision_file.is_file() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let revision: u32 = fs::read_to_string(revision_file)
|
||||
.context("reading revision file")?
|
||||
.split_whitespace()
|
||||
.collect::<String>()
|
||||
.parse()
|
||||
.context("parsing revision to u32")?;
|
||||
|
||||
return Ok(Some(revision));
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn sync_v2_settings(wine_prefix: &Path, user: &str) -> Result<()> {
|
||||
for app in ["Photo", "Designer", "Publisher"] {
|
||||
let app_settings_dst = wine_prefix.join(format!(
|
||||
"drive_c/users/{user}/AppData/Roaming/Affinity/{app}/2.0/"
|
||||
));
|
||||
let app_settings_src = ON_LINUX.join(format!("Auxiliary/Settings/{app}/2.0/"));
|
||||
|
||||
fs::create_dir_all(&app_settings_dst).context(format!(
|
||||
"creating settings directory for {app} w/ user {user}: {}",
|
||||
app_settings_dst.display()
|
||||
))?;
|
||||
|
||||
let rsync = cmd!(
|
||||
RSYNC,
|
||||
"-v",
|
||||
"--ignore-existing",
|
||||
"--chmod=D755,F644",
|
||||
"--recursive",
|
||||
&app_settings_src,
|
||||
&app_settings_dst
|
||||
)
|
||||
.stderr_to_stdout()
|
||||
.read()
|
||||
.context(format!(
|
||||
"syncing {app} settings with rsync: dst {}, src {}",
|
||||
app_settings_dst.display(),
|
||||
app_settings_src.display()
|
||||
))?;
|
||||
|
||||
for line in rsync.lines().filter(|x| !x.is_empty()) {
|
||||
info!(app = ?app, "{line}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_revision(wine_prefix: &Path) -> Result<()> {
|
||||
let revision_file = wine_prefix.join(".revision");
|
||||
|
||||
fs::write(&revision_file, LATEST_REVISION.to_string()).context("writing revision to file")?;
|
||||
|
||||
info!("wrote {LATEST_REVISION:?} to {revision_file:?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn perform_migrations(wine_prefix: &Path) -> Result<()> {
|
||||
let revision = crate::check::read_revision(wine_prefix)?.unwrap_or(0);
|
||||
|
||||
if revision < 10 {
|
||||
let migration = make_env(
|
||||
&cmd!(
|
||||
crate::WINE,
|
||||
"regedit",
|
||||
"/S",
|
||||
REGISTRY_PATCHES.join("one.reg")
|
||||
),
|
||||
wine_prefix,
|
||||
true,
|
||||
)
|
||||
.stderr_to_stdout()
|
||||
.read()
|
||||
.context("applying one.reg")?;
|
||||
|
||||
for line in migration.lines() {
|
||||
info!("vkd3d migration: {line}");
|
||||
}
|
||||
|
||||
info!("finished vkd3d migration.");
|
||||
}
|
||||
|
||||
write_revision(wine_prefix).context("writing revision")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,619 @@
|
||||
use std::{
|
||||
env,
|
||||
fs::{self},
|
||||
io::{self, BufRead, BufReader},
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::{Path, PathBuf},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::{Parser, Subcommand};
|
||||
use duct::cmd;
|
||||
use mutually_exclusive_features::exactly_one_of;
|
||||
use nix::{
|
||||
libc::{self, S_IWUSR},
|
||||
mount::{MsFlags, mount, umount},
|
||||
sched::{CloneFlags, unshare},
|
||||
sys::wait::{WaitStatus, waitpid},
|
||||
unistd::{ForkResult, fork},
|
||||
};
|
||||
use tracing::{Level, error, info, instrument};
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
use walkdir::WalkDir;
|
||||
use xdgdir::BaseDir;
|
||||
|
||||
use crate::check::{perform_migrations, sync_v2_settings};
|
||||
|
||||
mod check;
|
||||
mod migrate;
|
||||
|
||||
exactly_one_of!("v3", "photo", "designer", "publisher");
|
||||
|
||||
const MOUNT_FAIL_STATUS: i32 = 111;
|
||||
|
||||
pub(crate) const WINE: &str = env!("WINE");
|
||||
pub(crate) const WINESERVER: &str = env!("WINESERVER");
|
||||
pub(crate) const WINETRICKS: &str = env!("WINETRICKS");
|
||||
pub(crate) const FUSE_OVERLAYFS: &str = env!("FUSE_OVERLAYFS");
|
||||
pub(crate) const GNUTAR: &str = env!("GNUTAR");
|
||||
pub(crate) const ZENITY: &str = env!("ZENITY");
|
||||
|
||||
pub(crate) static LOWER_DIR: LazyLock<PathBuf> =
|
||||
LazyLock::new(|| PathBuf::from(env::var("LOWER_DIR").expect("LOWER_DIR env var to exist")));
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Arguments {
|
||||
/// Make Wine far more verbose.
|
||||
#[arg(long)]
|
||||
verbose: bool,
|
||||
|
||||
/// Specified tool to use. If none matches, defaults to the affinity application.
|
||||
#[command(subcommand)]
|
||||
subcommand: Option<Program>,
|
||||
|
||||
/// Arguments for affinity application
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
affinity: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Program {
|
||||
/// Run wine within sandbox
|
||||
Wine {
|
||||
/// arguments to wine
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
arguments: Vec<String>,
|
||||
},
|
||||
/// Run winetricks within sandbox
|
||||
Winetricks {
|
||||
/// arguments to winetricks
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
arguments: Vec<String>,
|
||||
},
|
||||
/// Run wineboot within sandbox
|
||||
Wineboot {
|
||||
/// arguments to wineboot
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
arguments: Vec<String>,
|
||||
},
|
||||
/// Run wineserver within sandbox
|
||||
Wineserver {
|
||||
/// arguments to wineserver
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
arguments: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ProgramToExecute {
|
||||
Affinity { arguments: Vec<String> },
|
||||
Other(Program),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Paths {
|
||||
upper: PathBuf,
|
||||
work: PathBuf,
|
||||
wine_prefix: PathBuf,
|
||||
}
|
||||
|
||||
impl Paths {
|
||||
fn ensure_created(&self) -> anyhow::Result<()> {
|
||||
let work_dir = self.work.join("work");
|
||||
|
||||
if work_dir.is_dir() {
|
||||
let mut perms = fs::metadata(&work_dir)?.permissions();
|
||||
perms.set_mode(0o700);
|
||||
fs::set_permissions(&work_dir, perms).context("set workdir permissions")?;
|
||||
|
||||
fs::remove_dir_all(&work_dir).context("delete workdir")?;
|
||||
}
|
||||
|
||||
fs::create_dir_all(&self.upper).context("creating upper")?;
|
||||
fs::create_dir_all(&self.work).context("creating work")?;
|
||||
fs::create_dir_all(&self.wine_prefix).context("creating wine_prefix")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn init_tracing() {
|
||||
let subscriber = FmtSubscriber::builder()
|
||||
.with_max_level(Level::TRACE)
|
||||
.finish();
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
||||
}
|
||||
|
||||
fn make_env(expression: &duct::Expression, wine_prefix: &Path, verbose: bool) -> duct::Expression {
|
||||
let expression = expression.env("WINEPREFIX", wine_prefix.display().to_string());
|
||||
|
||||
if verbose {
|
||||
return expression;
|
||||
}
|
||||
|
||||
expression.env("WINEDEBUG", "-all,fixme-all".to_string())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn warmup_prefix_directories(destination: &PathBuf) -> io::Result<()> {
|
||||
for entry in WalkDir::new(&*LOWER_DIR)
|
||||
.into_iter()
|
||||
.filter_map(std::result::Result::ok)
|
||||
{
|
||||
if !entry.file_type().is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let relative_path = entry.path().strip_prefix(&*LOWER_DIR).unwrap();
|
||||
let target_path = destination.join(relative_path);
|
||||
|
||||
if let Err(err) = fs::create_dir_all(&target_path) {
|
||||
error!(target_path = ?target_path, err = ?err, "failed to create");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn warmup_prefix_registry(destination: &PathBuf, user: &String) -> io::Result<()> {
|
||||
let important_files = vec!["system.reg", "user.reg", "userdef.reg", ".update-timestamp"];
|
||||
|
||||
for file in important_files {
|
||||
let src_path = LOWER_DIR.join(file);
|
||||
let dst_path = destination.join(file);
|
||||
|
||||
if !src_path.try_exists()? || dst_path.try_exists()? {
|
||||
continue;
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&src_path)?;
|
||||
let modified_content = content.replace("nixbld", user);
|
||||
|
||||
fs::write(&dst_path, modified_content)?;
|
||||
|
||||
let mut perms = fs::metadata(&dst_path)?.permissions();
|
||||
let current_mode = perms.mode();
|
||||
|
||||
// set write bit
|
||||
perms.set_mode(current_mode | S_IWUSR);
|
||||
fs::set_permissions(&dst_path, perms)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
fn wineboot_update(wine_prefix: &Path, verbose: bool) -> anyhow::Result<()> {
|
||||
let handle = make_env(
|
||||
&cmd!(WINE, "wineboot", "--update")
|
||||
.stderr_to_stdout()
|
||||
.unchecked(),
|
||||
wine_prefix,
|
||||
verbose,
|
||||
)
|
||||
.reader()?;
|
||||
|
||||
let lines = BufReader::new(&handle).lines();
|
||||
|
||||
for line in lines {
|
||||
let line = line?;
|
||||
info!("{line}");
|
||||
}
|
||||
|
||||
match handle.try_wait()? {
|
||||
Some(output) => {
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"wineboot --update failed: {:?}",
|
||||
output.status
|
||||
));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("wineboot child is still running in some way.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
fn execute(paths: &Paths, program: &ProgramToExecute, verbose: bool) -> anyhow::Result<()> {
|
||||
let user = std::env::var("USER")?;
|
||||
|
||||
info!(user = ?user);
|
||||
|
||||
if let Err(e) = warmup_prefix_directories(&paths.upper) {
|
||||
error!(error = %e, "warmup prefix directories failed (non-fatal)");
|
||||
}
|
||||
if let Err(e) = warmup_prefix_registry(&paths.upper, &user) {
|
||||
error!(error = %e, "warmup prefix registry failed (non-fatal)");
|
||||
}
|
||||
wineboot_update(&paths.wine_prefix, verbose)?;
|
||||
|
||||
info!("finished warming & wineboot");
|
||||
|
||||
perform_migrations(&paths.wine_prefix).context("performing migrations")?;
|
||||
|
||||
if cfg!(not(feature = "v3")) {
|
||||
sync_v2_settings(&paths.wine_prefix, &user).context("sync v2 settings")?;
|
||||
}
|
||||
|
||||
info!("finished pre-check");
|
||||
|
||||
let application_handle = match program {
|
||||
ProgramToExecute::Affinity { arguments } => {
|
||||
let exe_suffix = if cfg!(feature = "v3") {
|
||||
"Affinity/AffinityHook.exe"
|
||||
} else if cfg!(feature = "photo") {
|
||||
"Photo 2/Photo.exe"
|
||||
} else if cfg!(feature = "publisher") {
|
||||
"Publisher 2/Publisher.exe"
|
||||
} else if cfg!(feature = "designer") {
|
||||
"Designer 2/Designer.exe"
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let exe_path = format!(
|
||||
"{}/drive_c/Program Files/Affinity/{}",
|
||||
paths.wine_prefix.display(),
|
||||
exe_suffix
|
||||
);
|
||||
|
||||
info!("using exe {exe_path:?}");
|
||||
|
||||
cmd(
|
||||
WINE,
|
||||
std::iter::once(exe_path).chain(arguments.iter().cloned()),
|
||||
)
|
||||
}
|
||||
ProgramToExecute::Other(Program::Wine { arguments }) => cmd(WINE, arguments),
|
||||
ProgramToExecute::Other(Program::Winetricks { arguments }) => cmd(WINETRICKS, arguments),
|
||||
ProgramToExecute::Other(Program::Wineboot { arguments }) => cmd(
|
||||
WINE,
|
||||
std::iter::once("wineboot".to_string()).chain(arguments.iter().cloned()),
|
||||
),
|
||||
ProgramToExecute::Other(Program::Wineserver { arguments }) => cmd(WINESERVER, arguments),
|
||||
};
|
||||
|
||||
let application_handle = make_env(
|
||||
&application_handle.stderr_to_stdout().unchecked(),
|
||||
&paths.wine_prefix,
|
||||
verbose,
|
||||
)
|
||||
.reader()?;
|
||||
|
||||
let lines = BufReader::new(&application_handle).lines();
|
||||
|
||||
for line in lines {
|
||||
let line = line?;
|
||||
println!("{line}");
|
||||
}
|
||||
|
||||
match application_handle.try_wait()? {
|
||||
Some(output) => {
|
||||
if output.status.success() {
|
||||
info!(status = ?output.status, "application process ended cleanly");
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"application process failed: {:?}",
|
||||
output.status
|
||||
));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("application process is still running in some way.");
|
||||
}
|
||||
}
|
||||
|
||||
let wineserver_wait = make_env(
|
||||
&cmd!(WINESERVER, "-w").stderr_to_stdout().unchecked(),
|
||||
&paths.wine_prefix,
|
||||
verbose,
|
||||
)
|
||||
.reader()?;
|
||||
|
||||
let lines = BufReader::new(&wineserver_wait).lines();
|
||||
|
||||
for line in lines {
|
||||
let line = line?;
|
||||
info!("{line}");
|
||||
}
|
||||
|
||||
match wineserver_wait.try_wait()? {
|
||||
Some(output) => {
|
||||
if output.status.success() {
|
||||
info!(status = ?output.status, "wineserver -w exited cleanly");
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("wineserver -w failed: {:?}", output.status));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("wineserver child is still running in some way.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all, ret)]
|
||||
fn make_mount_options(paths: &Paths) -> String {
|
||||
format!(
|
||||
"lowerdir={},upperdir={},workdir={}",
|
||||
LOWER_DIR.display(),
|
||||
paths.upper.display(),
|
||||
paths.work.display()
|
||||
)
|
||||
}
|
||||
|
||||
#[instrument(skip(paths))]
|
||||
fn cleanup_fuse(paths: &Paths) -> anyhow::Result<()> {
|
||||
let fusermount3 = cmd!(
|
||||
"/usr/bin/env",
|
||||
"fusermount3",
|
||||
"-u",
|
||||
"-z",
|
||||
paths.wine_prefix.display().to_string()
|
||||
)
|
||||
.stdout_to_stderr()
|
||||
.stderr_capture()
|
||||
.run();
|
||||
|
||||
let success = match &fusermount3 {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
true
|
||||
} else {
|
||||
error!(status = %output.status, stderr = ?output.stderr, "`fusermount3` failed");
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(error = ?err, "`fusermount3` failed");
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !success {
|
||||
let fusermount = cmd!(
|
||||
"/usr/bin/env",
|
||||
"fusermount",
|
||||
"-u",
|
||||
"-z",
|
||||
paths.wine_prefix.display().to_string()
|
||||
)
|
||||
.stdout_to_stderr()
|
||||
.stderr_capture()
|
||||
.run()?;
|
||||
|
||||
if !fusermount.status.success() {
|
||||
error!(status = %fusermount.status, stderr = ?String::from_utf8_lossy(&fusermount.stderr), "`fusermount2` failed");
|
||||
|
||||
return Err(anyhow::anyhow!(
|
||||
"both `fusermount3` & `fusermount` failed! fusermount2: {:?}",
|
||||
String::from_utf8_lossy(&fusermount.stderr)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
info!("unmounted fuse cleanly");
|
||||
|
||||
let _ = fs::remove_dir(&paths.wine_prefix)
|
||||
.inspect_err(|err| error!(error = ?err, "failed to remove wine_prefix"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
fn mount_run_unprivileged(
|
||||
paths: &Paths,
|
||||
program: &ProgramToExecute,
|
||||
verbose: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let handle = cmd!(
|
||||
FUSE_OVERLAYFS,
|
||||
"-o",
|
||||
make_mount_options(paths),
|
||||
&paths.wine_prefix
|
||||
)
|
||||
.stderr_to_stdout()
|
||||
.unchecked()
|
||||
.reader()?;
|
||||
|
||||
let lines = BufReader::new(&handle).lines();
|
||||
|
||||
for line in lines {
|
||||
let line = line?;
|
||||
info!("{line}");
|
||||
}
|
||||
|
||||
match handle.try_wait()? {
|
||||
Some(output) => {
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"fuse-overlayfs failed to mount: {:?}",
|
||||
output.status
|
||||
));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("fuse-overlayfs child is still running in some way.");
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = execute(paths, program, verbose) {
|
||||
error!(error = ?err, "Running with fuse failed.");
|
||||
let _ = cleanup_fuse(paths);
|
||||
return Err(anyhow::anyhow!(
|
||||
"failed to run application with `fuse-overlayfs`: {err:?}"
|
||||
));
|
||||
}
|
||||
|
||||
cleanup_fuse(paths)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(paths))]
|
||||
fn cleanup_privileged(paths: &Paths) {
|
||||
let _ = umount(&paths.wine_prefix)
|
||||
.inspect_err(|err| error!(error = ?err, "failed to unmount wine_prefix"));
|
||||
let _ = fs::remove_dir(&paths.wine_prefix)
|
||||
.inspect_err(|err| error!(error = ?err, "failed to remove wine_prefix"));
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
fn mount_execute_privileged(paths: &Paths, program: &ProgramToExecute, verbose: bool) {
|
||||
match mount(
|
||||
Some("overlay"),
|
||||
&paths.wine_prefix,
|
||||
Some("overlay"),
|
||||
MsFlags::empty(),
|
||||
Some(make_mount_options(paths).as_str()),
|
||||
) {
|
||||
Ok(()) => {
|
||||
if let Err(err) = execute(paths, program, verbose) {
|
||||
error!(error = ?err, "Running privileged failed.");
|
||||
cleanup_privileged(paths);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(errorno = ?err, "Mount failed.");
|
||||
std::process::exit(MOUNT_FAIL_STATUS);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup_privileged(paths);
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
fn mount_run_privileged(
|
||||
paths: &Paths,
|
||||
ids: (u32, u32),
|
||||
program: &ProgramToExecute,
|
||||
verbose: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Err(err) = fs::write("/proc/self/setgroups", b"deny\n") {
|
||||
error!(error = ?err, "failed to write setgroups");
|
||||
mount_run_unprivileged(paths, program, verbose)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let uid_map = format!("0 {} 1\n", ids.0);
|
||||
if let Err(err) = fs::write("/proc/self/uid_map", uid_map) {
|
||||
error!(error = ?err, "failed to write uid_map");
|
||||
mount_run_unprivileged(paths, program, verbose)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let gid_map = format!("0 {} 1\n", ids.1);
|
||||
if let Err(err) = fs::write("/proc/self/gid_map", gid_map) {
|
||||
error!(error = ?err, "failed to write gid_map");
|
||||
mount_run_unprivileged(paths, program, verbose)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match unsafe { fork() } {
|
||||
Ok(ForkResult::Parent { child }) => match waitpid(child, None) {
|
||||
Ok(WaitStatus::Exited(_, status)) if status == MOUNT_FAIL_STATUS => {
|
||||
info!("Falling back on unprivileged due to failed mount");
|
||||
mount_run_unprivileged(paths, program, verbose)?;
|
||||
}
|
||||
Ok(WaitStatus::Exited(_, status)) => {
|
||||
std::process::exit(status);
|
||||
}
|
||||
Ok(WaitStatus::Signaled(_, signal, _)) => {
|
||||
info!(signal= ?signal, "child was killed by signal");
|
||||
std::process::exit(1);
|
||||
}
|
||||
Err(err) => {
|
||||
error!(errorno = ?err, "failed to wait for child");
|
||||
std::process::exit(1);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Ok(ForkResult::Child) => {
|
||||
unsafe {
|
||||
// make sure this namespace dies if the parent ends
|
||||
nix::libc::prctl(nix::libc::PR_SET_PDEATHSIG, nix::libc::SIGKILL);
|
||||
}
|
||||
mount_execute_privileged(paths, program, verbose);
|
||||
std::process::exit(0);
|
||||
}
|
||||
Err(err) => {
|
||||
error!(errorno = ?err, "Fork failed.");
|
||||
info!("Falling back on unprivileged");
|
||||
mount_run_unprivileged(paths, program, verbose)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
init_tracing();
|
||||
|
||||
let args = Arguments::parse();
|
||||
|
||||
let base = BaseDir::new(if cfg!(feature = "v3") {
|
||||
"affinity-v3"
|
||||
} else {
|
||||
"affinity"
|
||||
})
|
||||
.expect("obtaining xdg basedir failed");
|
||||
|
||||
let paths = Paths {
|
||||
upper: base.data,
|
||||
work: base.state,
|
||||
wine_prefix: base
|
||||
.runtime
|
||||
.unwrap_or_else(std::env::temp_dir)
|
||||
.join(format!("affinity-nix-prefix-{}", std::process::id())),
|
||||
};
|
||||
|
||||
info!(paths = ?paths, lower = ?LOWER_DIR.display());
|
||||
|
||||
if !LOWER_DIR.is_dir() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"{} does not exist or is not a directory! If $LOWER_DIR does not exist, you are likely using the wrong package, refer to affinity-nix's readme for correct usage.",
|
||||
LOWER_DIR.display()
|
||||
));
|
||||
}
|
||||
|
||||
paths
|
||||
.ensure_created()
|
||||
.context("setting up tmp directories")?;
|
||||
migrate::migrate(&paths).context("migrating to new overlayfs runner")?;
|
||||
|
||||
let uid = unsafe { libc::getuid() };
|
||||
let gid = unsafe { libc::getgid() };
|
||||
|
||||
let unshare =
|
||||
unshare(CloneFlags::CLONE_NEWUSER | CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWPID);
|
||||
|
||||
let program = if let Some(subcommand) = args.subcommand {
|
||||
ProgramToExecute::Other(subcommand)
|
||||
} else {
|
||||
ProgramToExecute::Affinity {
|
||||
arguments: args.affinity,
|
||||
}
|
||||
};
|
||||
|
||||
info!(program = ?program);
|
||||
|
||||
match unshare {
|
||||
Ok(()) => mount_run_privileged(&paths, (uid, gid), &program, args.verbose),
|
||||
Err(err) => {
|
||||
error!(errorno = ?err, "Unshare failed.");
|
||||
mount_run_unprivileged(&paths, &program, args.verbose)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
use std::{
|
||||
env, fs,
|
||||
io::{BufRead, BufReader},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use duct::cmd;
|
||||
use tracing::{error, info, instrument};
|
||||
|
||||
use crate::{GNUTAR, Paths, ZENITY};
|
||||
|
||||
const MESSAGE: &str = "There have been upgrades to affinity-nix!
|
||||
|
||||
Affinity and it's dependencies are no longer installed directly, reducing startup time and manual updating.
|
||||
|
||||
To migrate, we need to delete the old installation. Your registry files and user data won't be touched.";
|
||||
|
||||
fn make_message(backup_location: &Path) -> String {
|
||||
format!(
|
||||
"{MESSAGE} A backup will be created in at {}. Is that OK?",
|
||||
backup_location.display()
|
||||
)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn migrate(paths: &Paths) -> anyhow::Result<()> {
|
||||
let Some(revision) = crate::check::read_revision(&paths.upper)? else {
|
||||
info!("revision does not exist, skipping backup");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if revision >= 9 {
|
||||
info!("revision more than or equal to 9, skipping backup");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let backup_path = Path::new(&env::var("HOME").context("finding $HOME var")?).join(format!(
|
||||
"affinity-nix-backup-{}.tar.zst",
|
||||
std::process::id()
|
||||
));
|
||||
|
||||
let question = cmd!(
|
||||
ZENITY,
|
||||
"--question",
|
||||
"--width",
|
||||
"480",
|
||||
format!("--text={}", make_message(&backup_path))
|
||||
)
|
||||
.unchecked()
|
||||
.run()?;
|
||||
|
||||
if !question.status.success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"User refused to migrate, we cannot continue or there may be data loss."
|
||||
));
|
||||
}
|
||||
|
||||
let tar_handle = cmd!(GNUTAR, "--zstd", "-cvpf", backup_path, &paths.upper)
|
||||
.stdout_to_stderr()
|
||||
.stderr_capture()
|
||||
.reader()?;
|
||||
|
||||
let lines = BufReader::new(&tar_handle).lines();
|
||||
|
||||
for line in lines {
|
||||
let line = line?;
|
||||
info!("{line}");
|
||||
}
|
||||
|
||||
match tar_handle.try_wait()? {
|
||||
Some(output) => {
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"backing up with tar failed: {:?}",
|
||||
output.status
|
||||
));
|
||||
}
|
||||
|
||||
info!(status = %output.status, "tar ended cleanly");
|
||||
}
|
||||
None => {
|
||||
error!("tar child is still running in some way.");
|
||||
}
|
||||
}
|
||||
|
||||
let drive_c = paths.upper.join("drive_c");
|
||||
|
||||
if !drive_c.is_dir() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for entry in drive_c.read_dir().context("reading drive_c")? {
|
||||
let entry = entry.context("reading drive_c entry")?;
|
||||
|
||||
if entry.file_name() == "users" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = entry.path();
|
||||
|
||||
info!(path = ?path, "removing entry");
|
||||
|
||||
if entry
|
||||
.file_type()
|
||||
.context("reading entry filetype")?
|
||||
.is_dir()
|
||||
{
|
||||
fs::remove_dir_all(&path)
|
||||
.with_context(|| format!("removing directory {}", path.display()))?;
|
||||
} else {
|
||||
fs::remove_file(&path).with_context(|| format!("removing file {}", path.display()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
let
|
||||
lockFile = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||
flake-compat-node = lockFile.nodes.${lockFile.nodes.root.inputs.flake-compat};
|
||||
flake-compat = builtins.fetchTarball {
|
||||
inherit (flake-compat-node.locked) url;
|
||||
sha256 = flake-compat-node.locked.narHash;
|
||||
};
|
||||
|
||||
flake = import flake-compat {
|
||||
src = ./.;
|
||||
};
|
||||
in
|
||||
flake.defaultNix
|
||||
Generated
+247
@@ -0,0 +1,247 @@
|
||||
{
|
||||
"nodes": {
|
||||
"corefonts": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1431183738,
|
||||
"narHash": "sha256-610Km9GCtYWxi2SH5jH/XYPXc5f58q5lB05/5XxO/qM=",
|
||||
"owner": "pushcx",
|
||||
"repo": "corefonts",
|
||||
"rev": "8f8627b0004b5f1b952c09cf6390e4ae46475e59",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "pushcx",
|
||||
"repo": "corefonts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1787326676,
|
||||
"narHash": "sha256-lWhBbBvC05/xwivKBBiM2YNizpmgqCgyOIzomvRuwxs=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "692f7e9ef2ece8125b466f66f2af532b3edaed0d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"locked": {
|
||||
"lastModified": 1751685974,
|
||||
"narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=",
|
||||
"rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1",
|
||||
"type": "tarball",
|
||||
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz?rev=549f2762aebeff29a2e5ece7a7dc0f955281a1d1"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
|
||||
}
|
||||
},
|
||||
"flake-compat_2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1785627969,
|
||||
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"git-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat_2",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1787138109,
|
||||
"narHash": "sha256-O2tBn84NNuHrnqNVxx/XqsXwfYvS1YwBh+7CBnbCYsk=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "928612b364dc241a05bf6567ad44d499efb6b9aa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1782918843,
|
||||
"narHash": "sha256-ETYnV9U7Sr+A45dohzZdfCZKOss4qrTkO+wgNZNvEc0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e8273b29fe1390ec8d4603f2477357555291432e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1785031560,
|
||||
"narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-wine": {
|
||||
"locked": {
|
||||
"lastModified": 1735834308,
|
||||
"narHash": "sha256-dklw3AXr3OGO4/XT1Tu3Xz9n/we8GctZZ75ZWVqAVhk=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6df24922a1400241dae323af55f30e4318a6ca65",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6df24922a1400241dae323af55f30e4318a6ca65",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1787135253,
|
||||
"narHash": "sha256-RD2kNWCG+Bjo6h+JVjWVNntZs2GtRoeY2xHjts/FNkA=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ffb3c9b700e759be2ef13237c9d8f953b32a1e46",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1770107345,
|
||||
"narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4533d9293756b63904b7238acb84ac8fe4c8c2c4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"on-linux": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1787365621,
|
||||
"narHash": "sha256-aRS0sZ6p7YJagjFaZFrS1zuvN3PVKNBJOze1Ek/bOi8=",
|
||||
"owner": "seapear",
|
||||
"repo": "AffinityOnLinux",
|
||||
"rev": "0fe210754999a2ace3039c11fbbd11b45b5ee399",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "seapear",
|
||||
"repo": "AffinityOnLinux",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"plugin-loader-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1776972538,
|
||||
"narHash": "sha256-2EvrOYQCB2Z2YXhcPnjR67KoOCvwTVW3TfLcmU3//ak=",
|
||||
"owner": "noahc3",
|
||||
"repo": "AffinityPluginLoader",
|
||||
"rev": "1d7956d5b791bd6a213e8b28c1e25e1f4bcc6166",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "noahc3",
|
||||
"repo": "AffinityPluginLoader",
|
||||
"rev": "1d7956d5b791bd6a213e8b28c1e25e1f4bcc6166",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"corefonts": "corefonts",
|
||||
"crane": "crane",
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-parts": "flake-parts",
|
||||
"git-hooks": "git-hooks",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs-wine": "nixpkgs-wine",
|
||||
"on-linux": "on-linux",
|
||||
"plugin-loader-src": "plugin-loader-src",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1786901030,
|
||||
"narHash": "sha256-WSFCsDSE5ffgD2MqzkM2CYjeFiKhRF/dJUN8uedb6YE=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "27b3b12a8e6375f28ebe122f07d230ca5459bbfa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
description = "An attempt at packaging affinity photo for nix";
|
||||
|
||||
inputs = {
|
||||
# a known working revision of nixpkgs for wine. somewhat tracked by #63
|
||||
nixpkgs-wine.url = "github:nixos/nixpkgs/6df24922a1400241dae323af55f30e4318a6ca65";
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
|
||||
on-linux = {
|
||||
url = "github:seapear/AffinityOnLinux";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
plugin-loader-src = {
|
||||
url = "github:noahc3/AffinityPluginLoader/1d7956d5b791bd6a213e8b28c1e25e1f4bcc6166";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
treefmt-nix.url = "github:numtide/treefmt-nix";
|
||||
git-hooks.url = "github:cachix/git-hooks.nix";
|
||||
|
||||
flake-compat.url = "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz";
|
||||
|
||||
corefonts = {
|
||||
url = "github:pushcx/corefonts";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
crane.url = "github:ipetkov/crane";
|
||||
};
|
||||
|
||||
outputs =
|
||||
inputs@{
|
||||
flake-parts,
|
||||
git-hooks,
|
||||
treefmt-nix,
|
||||
...
|
||||
}:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
imports = [
|
||||
git-hooks.flakeModule
|
||||
treefmt-nix.flakeModule
|
||||
./nix/hooks.nix
|
||||
./nix/fmt.nix
|
||||
./nix/shells.nix
|
||||
./packages
|
||||
./tests/default.nix
|
||||
./overlay.nix
|
||||
];
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
];
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
_module.args = {
|
||||
craneLib = inputs.crane.mkLib pkgs;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
perSystem = _: {
|
||||
treefmt = {
|
||||
projectRootFile = "flake.nix";
|
||||
programs = {
|
||||
nixfmt.enable = true;
|
||||
yamlfmt.enable = true;
|
||||
shfmt.enable = true;
|
||||
rustfmt.enable = true;
|
||||
taplo.enable = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
pre-commit = {
|
||||
settings = {
|
||||
hooks = {
|
||||
statix.enable = true;
|
||||
zizmor.enable = true;
|
||||
typos.enable = true;
|
||||
deadnix = {
|
||||
enable = true;
|
||||
settings.edit = true;
|
||||
};
|
||||
fmt = {
|
||||
enable = true;
|
||||
name = "nix fmt";
|
||||
entry = "${lib.getExe config.formatter} --no-cache";
|
||||
};
|
||||
ty = {
|
||||
enable = true;
|
||||
name = "ty check";
|
||||
files = "\\.py$";
|
||||
entry = lib.getExe (
|
||||
pkgs.writeShellScriptBin "ty-check" ''
|
||||
cd tests/
|
||||
${lib.getExe pkgs.uv} run ty check
|
||||
''
|
||||
);
|
||||
};
|
||||
actionlint = {
|
||||
enable = true;
|
||||
files = "^\\.github/workflows/.*\\.(yml|yaml)$";
|
||||
};
|
||||
yamllint = {
|
||||
enable = true;
|
||||
settings.configPath = ".yamllint.yaml";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{ inputs, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
craneLib,
|
||||
pkgs,
|
||||
self',
|
||||
wine-packages,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.pre-commit;
|
||||
in
|
||||
{
|
||||
# Adapted from
|
||||
# https://github.com/cachix/git-hooks.nix/blob/dcf5072734cb576d2b0c59b2ac44f5050b5eac82/flake-module.nix#L66-L78
|
||||
devShells.default = craneLib.devShell {
|
||||
packages = lib.flatten [
|
||||
cfg.settings.enabledPackages
|
||||
cfg.settings.package
|
||||
|
||||
pkgs.uv
|
||||
pkgs.rust-analyzer
|
||||
];
|
||||
|
||||
env = {
|
||||
WINE = lib.getExe self'.packages.wine;
|
||||
WINESERVER = lib.getExe wine-packages.wineserver;
|
||||
WINETRICKS = lib.getExe wine-packages.winetricks;
|
||||
FUSE_OVERLAYFS = lib.getExe pkgs.fuse-overlayfs;
|
||||
GNUTAR = lib.getExe pkgs.gnutar;
|
||||
ZENITY = lib.getExe pkgs.zenity;
|
||||
RSYNC = lib.getExe pkgs.rsync;
|
||||
REGISTRY_PATCHES = (pkgs.callPackage ../packages/registry-patches.nix { }).combined;
|
||||
ON_LINUX = inputs.on-linux.outPath;
|
||||
};
|
||||
|
||||
shellHook = builtins.concatStringsSep "\n" [
|
||||
cfg.installationScript
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{ withSystem, inputs, ... }:
|
||||
{
|
||||
flake.overlays.default =
|
||||
_final: prev:
|
||||
withSystem prev.stdenv.hostPlatform.system (_: {
|
||||
affinity-v3 = prev.callPackage ./packages/affinity-v3/package.nix {
|
||||
inherit inputs;
|
||||
};
|
||||
affinity-photo = prev.callPackage ./packages/affinity/package.nix {
|
||||
name = "Photo";
|
||||
inherit inputs;
|
||||
};
|
||||
affinity-designer = prev.callPackage ./packages/affinity/package.nix {
|
||||
name = "Designer";
|
||||
inherit inputs;
|
||||
};
|
||||
affinity-publisher = prev.callPackage ./packages/affinity/package.nix {
|
||||
name = "Publisher";
|
||||
inherit inputs;
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{ inputs, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
self',
|
||||
...
|
||||
}:
|
||||
{
|
||||
packages = {
|
||||
affinity-v3 = pkgs.callPackage ./package.nix {
|
||||
inherit inputs;
|
||||
};
|
||||
|
||||
default = self'.packages.affinity-v3;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
makeDesktopItem,
|
||||
lib,
|
||||
affinity-v3 ? null,
|
||||
}:
|
||||
{
|
||||
affinity-v3 = makeDesktopItem rec {
|
||||
desktopName = "Affinity";
|
||||
name = "affinity-v3";
|
||||
exec = "${lib.getExe affinity-v3} %U";
|
||||
icon = name;
|
||||
type = "Application";
|
||||
categories = [ "Graphics" ];
|
||||
keywords = [
|
||||
"Graphics"
|
||||
"2DGraphics"
|
||||
"RasterGraphics"
|
||||
"VectorGraphics"
|
||||
"image"
|
||||
"editor"
|
||||
"vector"
|
||||
"drawing"
|
||||
];
|
||||
mimeTypes = [
|
||||
"application/afphoto"
|
||||
"application/afdesign"
|
||||
];
|
||||
startupWMClass = "affinity.exe";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
fetchurl,
|
||||
pkgs,
|
||||
lib,
|
||||
}:
|
||||
rec {
|
||||
icon = fetchurl {
|
||||
url = "https://web.archive.org/web/20260310003857/https://static.canva.com/domain-assets/affinity/static/images/apple-touch-180x180-1.png";
|
||||
sha256 = "sha256-KLT9loFiD2y8uNXNzc27DZ4A73yEs6Ntr4h3WULG5HM=";
|
||||
};
|
||||
|
||||
iconPackage = pkgs.runCommand "affinity-v3-icons" { } ''
|
||||
mkdir -p $out/share/icons/hicolor/256x256/apps
|
||||
|
||||
${lib.getExe pkgs.imagemagick} \
|
||||
${icon} \
|
||||
-resize 256x256 \
|
||||
$out/share/icons/hicolor/256x256/apps/affinity-v3.png
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
symlinkJoin,
|
||||
callPackage,
|
||||
inputs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
wine-packages = callPackage ../wine/packages.nix {
|
||||
inherit inputs;
|
||||
};
|
||||
|
||||
apl-combined = callPackage ../apl/apl-combined.nix {
|
||||
src = inputs.plugin-loader-src;
|
||||
};
|
||||
|
||||
prefixBase = callPackage ../prefixWithAffinity.nix {
|
||||
inherit inputs wine-packages apl-combined;
|
||||
};
|
||||
|
||||
runner = callPackage ../runner/package.nix {
|
||||
inherit prefixBase inputs wine-packages;
|
||||
registry-patches = (callPackage ../registry-patches.nix { }).combined;
|
||||
name = "v3";
|
||||
};
|
||||
|
||||
desktop = callPackage ./desktopItems.nix {
|
||||
affinity-v3 = runner.package;
|
||||
};
|
||||
|
||||
icons = callPackage ./icons.nix { };
|
||||
icon-package = icons.iconPackage;
|
||||
in
|
||||
symlinkJoin {
|
||||
name = "Affinity v3";
|
||||
pname = "affinity-v3";
|
||||
paths = [
|
||||
runner.package
|
||||
desktop.affinity-v3
|
||||
icon-package
|
||||
];
|
||||
meta = {
|
||||
mainProgram = "affinity-v3";
|
||||
|
||||
description = "Affinity v3";
|
||||
license = lib.licenses.unfree;
|
||||
homepage = "https://affinity.serif.com/";
|
||||
platforms = [ "x86_64-linux" ];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{ inputs, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
makeV2Package =
|
||||
name:
|
||||
(pkgs.callPackage ./package.nix {
|
||||
inherit inputs name;
|
||||
});
|
||||
in
|
||||
{
|
||||
packages = {
|
||||
affinity-photo = makeV2Package "Photo";
|
||||
affinity-designer = makeV2Package "Designer";
|
||||
affinity-publisher = makeV2Package "Publisher";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
makeDesktopItem,
|
||||
lib,
|
||||
photo ? null,
|
||||
publisher ? null,
|
||||
designer ? null,
|
||||
}:
|
||||
{
|
||||
photo = makeDesktopItem rec {
|
||||
desktopName = "Affinity Photo 2";
|
||||
name = "affinity-photo";
|
||||
exec = "${lib.getExe photo} %U";
|
||||
icon = name;
|
||||
type = "Application";
|
||||
categories = [ "Graphics" ];
|
||||
keywords = [
|
||||
"Graphics"
|
||||
"2DGraphics"
|
||||
"RasterGraphics"
|
||||
"image"
|
||||
"editor"
|
||||
"vector"
|
||||
"drawing"
|
||||
];
|
||||
mimeTypes = [ "application/afphoto" ];
|
||||
startupWMClass = "photo.exe";
|
||||
};
|
||||
designer = makeDesktopItem rec {
|
||||
desktopName = "Affinity Designer 2";
|
||||
name = "affinity-designer";
|
||||
exec = "${lib.getExe designer} %U";
|
||||
icon = name;
|
||||
type = "Application";
|
||||
categories = [ "Graphics" ];
|
||||
keywords = [
|
||||
"Graphics"
|
||||
"2DGraphics"
|
||||
"VectorGraphics"
|
||||
"image"
|
||||
"editor"
|
||||
"vector"
|
||||
"drawing"
|
||||
];
|
||||
mimeTypes = [ "application/afdesign" ];
|
||||
startupWMClass = "designer.exe";
|
||||
};
|
||||
publisher = makeDesktopItem rec {
|
||||
desktopName = "Affinity Publisher 2";
|
||||
name = "affinity-publisher";
|
||||
exec = "${lib.getExe publisher} %U";
|
||||
icon = name;
|
||||
type = "Application";
|
||||
categories = [ "Graphics" ];
|
||||
keywords = [
|
||||
"Graphics"
|
||||
"2DGraphics"
|
||||
"RasterGraphics"
|
||||
"VectorGraphics"
|
||||
"image"
|
||||
"editor"
|
||||
"vector"
|
||||
"drawing"
|
||||
];
|
||||
mimeTypes = [
|
||||
"application/afdesign"
|
||||
"application/afphoto"
|
||||
# Note that there is no mime type for a `.afpub` file
|
||||
];
|
||||
startupWMClass = "publisher.exe";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
fetchurl,
|
||||
pkgs,
|
||||
lib,
|
||||
}:
|
||||
rec {
|
||||
icons = {
|
||||
photo = fetchurl {
|
||||
url = "https://cdn.serif.com/store/img/logos/affinity-photo-2-020520191502.svg";
|
||||
sha256 = "04bnf12znp9dgkwpk46c01381cw298gq14ga7j7dwccyl3m556d7";
|
||||
};
|
||||
designer = fetchurl {
|
||||
url = "https://cdn.serif.com/store/img/logos/affinity-designer-2-020520191502.svg";
|
||||
sha256 = "025j4aq8py2a5r34hx39nlhdmps06z6506h7wxchyxbbz6xz3d11";
|
||||
};
|
||||
publisher = fetchurl {
|
||||
url = "https://cdn.serif.com/store/img/logos/affinity-publisher-2-020520191502.svg";
|
||||
sha256 = "16ghh3mc2vjhvphbyy9zgan066ccp3xj1ilb6z5lmvsb94dp37aj";
|
||||
};
|
||||
};
|
||||
|
||||
mkIconPackageFor =
|
||||
name:
|
||||
pkgs.runCommand "${name}-icons" { } ''
|
||||
mkdir -p $out/share/icons/hicolor/{256x256,scalable}/apps
|
||||
|
||||
cp ${icons.${lib.toLower name}} \
|
||||
$out/share/icons/hicolor/scalable/apps/affinity-${lib.toLower name}.svg
|
||||
|
||||
${lib.getExe pkgs.inkscape} \
|
||||
-w 256 -h 256 \
|
||||
${icons.${lib.toLower name}} \
|
||||
-o - > $out/share/icons/hicolor/256x256/apps/affinity-${lib.toLower name}.png
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
symlinkJoin,
|
||||
callPackage,
|
||||
lib,
|
||||
name,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
wine-packages = callPackage ../wine/packages.nix {
|
||||
inherit inputs;
|
||||
};
|
||||
|
||||
apl-combined = callPackage ../apl/apl-combined.nix {
|
||||
src = inputs.plugin-loader-src;
|
||||
};
|
||||
|
||||
prefixBase = callPackage ../prefixWithAffinity.nix {
|
||||
inherit inputs wine-packages apl-combined;
|
||||
v3 = false;
|
||||
};
|
||||
|
||||
runner = callPackage ../runner/package.nix {
|
||||
inherit
|
||||
prefixBase
|
||||
inputs
|
||||
wine-packages
|
||||
name
|
||||
;
|
||||
registry-patches = (callPackage ../registry-patches.nix { }).combined;
|
||||
};
|
||||
|
||||
desktop = callPackage ./desktopItems.nix {
|
||||
${lib.toLower name} = runner.package;
|
||||
};
|
||||
|
||||
icons = callPackage ./icons.nix { };
|
||||
icon-package = icons.mkIconPackageFor name;
|
||||
|
||||
# last version of affinity v2 released
|
||||
lastV2Version = "2.6.5";
|
||||
in
|
||||
symlinkJoin {
|
||||
name = "affinity-${lib.toLower name}-${lastV2Version}";
|
||||
pname = "affinity-${lib.toLower name}";
|
||||
paths = [
|
||||
runner.package
|
||||
desktop.${lib.toLower name}
|
||||
icon-package
|
||||
];
|
||||
meta = {
|
||||
mainProgram = "affinity-${lib.toLower name}";
|
||||
|
||||
description = "Affinity ${name} ${lastV2Version}";
|
||||
license = lib.licenses.unfree;
|
||||
homepage = "https://affinity.serif.com/";
|
||||
platforms = [ "x86_64-linux" ];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
callPackage,
|
||||
symlinkJoin,
|
||||
src,
|
||||
...
|
||||
}:
|
||||
let
|
||||
version = "unstable";
|
||||
|
||||
apl = callPackage ./apl.nix {
|
||||
inherit version src;
|
||||
};
|
||||
|
||||
bootstrap = callPackage ./bootstrap.nix {
|
||||
inherit version src;
|
||||
};
|
||||
in
|
||||
symlinkJoin {
|
||||
pname = "apl-combined";
|
||||
inherit version;
|
||||
paths = [
|
||||
apl
|
||||
bootstrap
|
||||
];
|
||||
|
||||
postBuild = ''
|
||||
mv $out/lib/AffinityPluginLoader/* $out
|
||||
rm -rf $out/lib
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
buildDotnetModule,
|
||||
src,
|
||||
version,
|
||||
...
|
||||
}:
|
||||
buildDotnetModule rec {
|
||||
pname = "AffinityPluginLoader";
|
||||
|
||||
inherit version src;
|
||||
|
||||
projectFile = "AffinityPluginLoader.sln";
|
||||
|
||||
patches = [ ./no-login.patch ];
|
||||
|
||||
nugetDeps = ./deps.json;
|
||||
|
||||
postInstall = ''
|
||||
pushd $out/lib/${pname}
|
||||
rm *.pdb
|
||||
rm *.config
|
||||
|
||||
mkdir -p ./apl/plugins
|
||||
mv ./WineFix.dll ./apl/plugins
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
pkgs,
|
||||
version,
|
||||
src,
|
||||
...
|
||||
}:
|
||||
pkgs.pkgsCross.mingwW64.stdenv.mkDerivation {
|
||||
pname = "affinity-bootstrap";
|
||||
|
||||
inherit version;
|
||||
|
||||
src = "${src}/AffinityBootstrap";
|
||||
|
||||
nativeBuildInputs = [ pkgs.wine64 ];
|
||||
|
||||
buildPhase = ''
|
||||
mkdir -p build
|
||||
|
||||
cat << 'EOF' > mscoree.def
|
||||
LIBRARY mscoree.dll
|
||||
EXPORTS
|
||||
CLRCreateInstance
|
||||
EOF
|
||||
|
||||
x86_64-w64-mingw32-dlltool -d mscoree.def -l build/libmscoree.a
|
||||
|
||||
$CC -shared -o build/AffinityBootstrap.dll bootstrap.c \
|
||||
-I${pkgs.wine64}/include/wine/windows \
|
||||
-Lbuild \
|
||||
-lole32 -loleaut32 -luuid -lmscoree
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/lib/AffinityPluginLoader
|
||||
cp build/AffinityBootstrap.dll $out/lib/AffinityPluginLoader
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{ inputs, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
packages.apl-combined = pkgs.callPackage ./apl-combined.nix {
|
||||
src = inputs.plugin-loader-src;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"pname": "Lib.Harmony",
|
||||
"version": "2.4.2",
|
||||
"hash": "sha256-1kWS5TCQRkVZ/OSGEsnKfI3HMROEE3a3qjRV9G/F1Xk="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETFramework.ReferenceAssemblies",
|
||||
"version": "1.0.3",
|
||||
"hash": "sha256-FBoJP5DHZF0QHM0xLm9yd4HJZVQOuSpSKA+VQRpphEE="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETFramework.ReferenceAssemblies.net48",
|
||||
"version": "1.0.3",
|
||||
"hash": "sha256-in40hTjn65E1FpaRFon0nj1PY/i6tRdDK74Vm4sRBKI="
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,589 @@
|
||||
From 45ff4e8c5615af1e8f0a41e92580506788a10b24 Mon Sep 17 00:00:00 2001
|
||||
From: marshmallow <git@althaea.zone>
|
||||
Date: Mon, 13 Apr 2026 20:11:35 +1000
|
||||
Subject: [PATCH] Revert "Restore Canva sign-in: Textbox to capture
|
||||
authorization token (#44)"
|
||||
|
||||
This reverts commit 915e1cc5096475f433b8447fc7c6b7cb3b52b7d9.
|
||||
---
|
||||
WineFix/Patches/CanvaSignInPatch.cs | 435 -----------------------
|
||||
WineFix/Patches/MainWindowLoadedPatch.cs | 55 +++
|
||||
WineFix/WineFix.csproj | 4 -
|
||||
WineFix/WineFixPlugin.cs | 14 -
|
||||
4 files changed, 55 insertions(+), 453 deletions(-)
|
||||
delete mode 100644 WineFix/Patches/CanvaSignInPatch.cs
|
||||
|
||||
diff --git a/WineFix/Patches/CanvaSignInPatch.cs b/WineFix/Patches/CanvaSignInPatch.cs
|
||||
deleted file mode 100644
|
||||
index 7c05fcb..0000000
|
||||
--- a/WineFix/Patches/CanvaSignInPatch.cs
|
||||
+++ /dev/null
|
||||
@@ -1,435 +0,0 @@
|
||||
-using System;
|
||||
-using System.Globalization;
|
||||
-using System.Linq;
|
||||
-using System.Reflection;
|
||||
-using System.Windows;
|
||||
-using System.Windows.Controls;
|
||||
-using System.Windows.Data;
|
||||
-using System.Windows.Media;
|
||||
-using System.Windows.Media.Imaging;
|
||||
-using HarmonyLib;
|
||||
-using AffinityPluginLoader.Core;
|
||||
-
|
||||
-namespace WineFix.Patches
|
||||
-{
|
||||
- /// <summary>
|
||||
- /// Adds a paste-URL textbox to the Canva sign-in dialog so users can manually
|
||||
- /// paste the authentication redirect URL from their browser, bypassing the need
|
||||
- /// for the affinity:// protocol handler which doesn't work under Wine.
|
||||
- /// </summary>
|
||||
- public static class CanvaSignInPatch
|
||||
- {
|
||||
- private static FieldInfo _authHookField;
|
||||
- private static object _cloudServicesService;
|
||||
- private static Border _overlayPanel;
|
||||
-
|
||||
- public static void ApplyPatches(Harmony harmony)
|
||||
- {
|
||||
- Logger.Info("Applying CanvaSignIn patch...");
|
||||
-
|
||||
- var serifAffinity = AppDomain.CurrentDomain.GetAssemblies()
|
||||
- .FirstOrDefault(a => a.GetName().Name == "Serif.Affinity");
|
||||
- var serifInterop = AppDomain.CurrentDomain.GetAssemblies()
|
||||
- .FirstOrDefault(a => a.GetName().Name == "Serif.Interop.Persona");
|
||||
-
|
||||
- if (serifAffinity == null || serifInterop == null)
|
||||
- {
|
||||
- Logger.Error("Required assemblies not found");
|
||||
- return;
|
||||
- }
|
||||
-
|
||||
- // Resolve the AuthHookReceived backing field on CloudServicesService
|
||||
- var cssType = serifInterop.GetType("Serif.Interop.Persona.Services.CloudServicesService");
|
||||
- _authHookField = cssType?.GetField("<backing_store>AuthHookReceived",
|
||||
- BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
-
|
||||
- if (_authHookField == null)
|
||||
- {
|
||||
- // Try the mangled name from decompilation
|
||||
- _authHookField = cssType?.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
- .FirstOrDefault(f => f.Name.Contains("AuthHookReceived") && f.FieldType == typeof(Action<Uri>));
|
||||
- }
|
||||
-
|
||||
- if (_authHookField == null)
|
||||
- {
|
||||
- Logger.Error("Could not find AuthHookReceived backing field");
|
||||
- return;
|
||||
- }
|
||||
-
|
||||
- // Patch ProductKeyDialog_Loaded to inject our UI
|
||||
- var dialogType = serifAffinity.GetType("Serif.Affinity.UI.ProductKeyDialog");
|
||||
- var loadedMethod = dialogType?.GetMethod("ProductKeyDialog_Loaded",
|
||||
- BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
-
|
||||
- if (loadedMethod == null)
|
||||
- {
|
||||
- Logger.Error("ProductKeyDialog_Loaded not found");
|
||||
- return;
|
||||
- }
|
||||
-
|
||||
- harmony.Patch(loadedMethod,
|
||||
- postfix: new HarmonyMethod(typeof(CanvaSignInPatch), nameof(OnDialogLoaded)));
|
||||
- Logger.Info("Patched ProductKeyDialog_Loaded");
|
||||
- }
|
||||
-
|
||||
- public static void OnDialogLoaded(object __instance)
|
||||
- {
|
||||
- try
|
||||
- {
|
||||
- var window = __instance as Window;
|
||||
- if (window == null) return;
|
||||
-
|
||||
- // Get CloudServicesService via Application.Current.GetService<T>()
|
||||
- var app = Application.Current;
|
||||
- var getServiceMethod = app.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
- .Where(m => m.Name == "GetService" && m.IsGenericMethod)
|
||||
- .FirstOrDefault();
|
||||
-
|
||||
- if (getServiceMethod != null)
|
||||
- {
|
||||
- var cssType = _authHookField.DeclaringType;
|
||||
- var bound = getServiceMethod.MakeGenericMethod(cssType);
|
||||
- _cloudServicesService = bound.Invoke(app, null);
|
||||
- }
|
||||
-
|
||||
- // Listen for DataContext property changes to detect state transitions
|
||||
- var model = ((FrameworkElement)window).DataContext;
|
||||
- if (model is System.ComponentModel.INotifyPropertyChanged npc)
|
||||
- {
|
||||
- npc.PropertyChanged += (s, e) =>
|
||||
- {
|
||||
- if (e.PropertyName == "State")
|
||||
- InjectPasteUI(window);
|
||||
- };
|
||||
- }
|
||||
-
|
||||
- // Also try immediately in case we're already in SigningIn
|
||||
- InjectPasteUI(window);
|
||||
- }
|
||||
- catch (Exception ex)
|
||||
- {
|
||||
- Logger.Error($"OnDialogLoaded failed: {ex.Message}");
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- private static void InjectPasteUI(Window window)
|
||||
- {
|
||||
- try
|
||||
- {
|
||||
- var model = ((FrameworkElement)window).DataContext;
|
||||
- var stateProp = model.GetType().GetProperty("State");
|
||||
- if (stateProp == null) return;
|
||||
-
|
||||
- var state = stateProp.GetValue(model);
|
||||
- if (state.ToString() == "SigningIn")
|
||||
- {
|
||||
- window.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Loaded,
|
||||
- new Action(() => InjectPasteUIImpl(window)));
|
||||
- }
|
||||
- else
|
||||
- {
|
||||
- HideOverlay();
|
||||
- }
|
||||
- }
|
||||
- catch (Exception ex)
|
||||
- {
|
||||
- Logger.Error($"InjectPasteUI failed: {ex.Message}");
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- private static void InjectPasteUIImpl(Window window)
|
||||
- {
|
||||
- try
|
||||
- {
|
||||
- if (_overlayPanel != null)
|
||||
- {
|
||||
- _overlayPanel.Visibility = Visibility.Visible;
|
||||
- return;
|
||||
- }
|
||||
-
|
||||
- var existingContent = window.Content as UIElement;
|
||||
- if (existingContent == null) return;
|
||||
-
|
||||
- // Wrap existing window content in a new Grid so we can overlay on top
|
||||
- var wrapperGrid = new Grid();
|
||||
- window.Content = wrapperGrid;
|
||||
- wrapperGrid.Children.Add(existingContent);
|
||||
-
|
||||
- // Build overlay content
|
||||
- var panel = new StackPanel
|
||||
- {
|
||||
- VerticalAlignment = VerticalAlignment.Center,
|
||||
- Margin = new Thickness(30)
|
||||
- };
|
||||
-
|
||||
- var heading = new TextBlock
|
||||
- {
|
||||
- Text = "Running under Wine?",
|
||||
- FontSize = 14,
|
||||
- FontWeight = FontWeights.SemiBold,
|
||||
- Foreground = new SolidColorBrush(Color.FromRgb(0x33, 0x33, 0x33)),
|
||||
- Margin = new Thickness(0, 0, 0, 8)
|
||||
- };
|
||||
-
|
||||
- var instructions = new TextBlock
|
||||
- {
|
||||
- Text = "After signing in, your browser will show a " +
|
||||
- "\"Launching Affinity\" page. Copy the full URL " +
|
||||
- "from the address bar and paste it below:",
|
||||
- Foreground = new SolidColorBrush(Color.FromRgb(0x65, 0x65, 0x65)),
|
||||
- FontSize = 12,
|
||||
- TextWrapping = TextWrapping.Wrap,
|
||||
- Margin = new Thickness(0, 0, 0, 10)
|
||||
- };
|
||||
-
|
||||
- var screenshot = LoadEmbeddedImage("WineFix.Resources.signin.png");
|
||||
- Image screenshotImage = null;
|
||||
- if (screenshot != null)
|
||||
- {
|
||||
- screenshotImage = new Image
|
||||
- {
|
||||
- Source = screenshot,
|
||||
- Stretch = Stretch.Uniform,
|
||||
- HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
- Margin = new Thickness(0, 0, 0, 12)
|
||||
- };
|
||||
- }
|
||||
-
|
||||
- var textBox = new TextBox
|
||||
- {
|
||||
- Height = 28,
|
||||
- FontSize = 12,
|
||||
- VerticalContentAlignment = VerticalAlignment.Center,
|
||||
- HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
- };
|
||||
-
|
||||
- var placeholder = new TextBlock
|
||||
- {
|
||||
- Text = "https://page.service.serif.com/canva-auth-redirect/...",
|
||||
- Foreground = new SolidColorBrush(Color.FromRgb(0xAA, 0xAA, 0xAA)),
|
||||
- FontSize = 12,
|
||||
- IsHitTestVisible = false,
|
||||
- VerticalAlignment = VerticalAlignment.Center,
|
||||
- Margin = new Thickness(4, 0, 0, 0)
|
||||
- };
|
||||
- var textBoxContainer = new Grid { Height = 28, HorizontalAlignment = HorizontalAlignment.Stretch };
|
||||
- textBoxContainer.Children.Add(textBox);
|
||||
- textBoxContainer.Children.Add(placeholder);
|
||||
- textBox.TextChanged += (s, e) => placeholder.Visibility =
|
||||
- string.IsNullOrEmpty(textBox.Text) ? Visibility.Visible : Visibility.Collapsed;
|
||||
- textBox.GotFocus += (s, e) => placeholder.Visibility = Visibility.Collapsed;
|
||||
- textBox.LostFocus += (s, e) => placeholder.Visibility =
|
||||
- string.IsNullOrEmpty(textBox.Text) ? Visibility.Visible : Visibility.Collapsed;
|
||||
-
|
||||
- var button = new Button
|
||||
- {
|
||||
- Content = "Submit",
|
||||
- Margin = new Thickness(0, 8, 0, 0),
|
||||
- Padding = new Thickness(0, 8, 0, 8),
|
||||
- HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
- };
|
||||
-
|
||||
- var errorText = new TextBlock
|
||||
- {
|
||||
- Foreground = Brushes.Red,
|
||||
- FontSize = 11,
|
||||
- TextWrapping = TextWrapping.Wrap,
|
||||
- Visibility = Visibility.Collapsed,
|
||||
- Margin = new Thickness(0, 4, 0, 0)
|
||||
- };
|
||||
-
|
||||
- button.Click += (s, e) => OnSubmitUrl(textBox.Text, errorText);
|
||||
-
|
||||
- panel.Children.Add(heading);
|
||||
- panel.Children.Add(instructions);
|
||||
- if (screenshotImage != null)
|
||||
- panel.Children.Add(screenshotImage);
|
||||
- panel.Children.Add(textBoxContainer);
|
||||
- panel.Children.Add(button);
|
||||
- panel.Children.Add(errorText);
|
||||
-
|
||||
- // Overlay: right-aligned, 50% width, full height, white background
|
||||
- _overlayPanel = new Border
|
||||
- {
|
||||
- Background = new SolidColorBrush(Color.FromArgb(0xF0, 0xFF, 0xFF, 0xFF)),
|
||||
- HorizontalAlignment = HorizontalAlignment.Right,
|
||||
- VerticalAlignment = VerticalAlignment.Stretch,
|
||||
- Child = panel
|
||||
- };
|
||||
- _overlayPanel.SetBinding(FrameworkElement.WidthProperty,
|
||||
- new Binding("ActualWidth")
|
||||
- {
|
||||
- Source = window,
|
||||
- Converter = new HalfWidthConverter()
|
||||
- });
|
||||
-
|
||||
- wrapperGrid.Children.Add(_overlayPanel);
|
||||
-
|
||||
- // Nudge the "Back" button left so it's not covered by the overlay
|
||||
- var contentControl = FindChild<ContentControl>(existingContent,
|
||||
- c => c.ContentTemplateSelector != null);
|
||||
- if (contentControl != null)
|
||||
- {
|
||||
- var backButton = FindChild<Button>(contentControl, b =>
|
||||
- Grid.GetRow(b) == 4 && Grid.GetColumnSpan(b) == 3
|
||||
- && b.HorizontalAlignment == HorizontalAlignment.Center);
|
||||
- if (backButton != null)
|
||||
- backButton.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
- }
|
||||
-
|
||||
- Logger.Info("Injected paste-URL overlay into SigningIn dialog");
|
||||
- }
|
||||
- catch (Exception ex)
|
||||
- {
|
||||
- Logger.Error($"InjectPasteUIImpl failed: {ex.Message}");
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- private static void HideOverlay()
|
||||
- {
|
||||
- if (_overlayPanel != null)
|
||||
- _overlayPanel.Visibility = Visibility.Collapsed;
|
||||
- }
|
||||
-
|
||||
- private static void OnSubmitUrl(string input, TextBlock errorText)
|
||||
- {
|
||||
- try
|
||||
- {
|
||||
- errorText.Visibility = Visibility.Collapsed;
|
||||
-
|
||||
- if (string.IsNullOrWhiteSpace(input))
|
||||
- {
|
||||
- ShowError(errorText, "Please paste a URL.");
|
||||
- return;
|
||||
- }
|
||||
-
|
||||
- var affinityUri = ExtractAffinityUri(input.Trim());
|
||||
- if (affinityUri == null)
|
||||
- {
|
||||
- ShowError(errorText, "Could not find an authentication URL. Please paste the full URL from your browser's address bar after signing in.");
|
||||
- return;
|
||||
- }
|
||||
-
|
||||
- if (_cloudServicesService == null || _authHookField == null)
|
||||
- {
|
||||
- ShowError(errorText, "Internal error: cloud services not available.");
|
||||
- return;
|
||||
- }
|
||||
-
|
||||
- var handler = _authHookField.GetValue(_cloudServicesService) as Action<Uri>;
|
||||
- if (handler == null)
|
||||
- {
|
||||
- ShowError(errorText, "Internal error: auth hook not registered.");
|
||||
- return;
|
||||
- }
|
||||
-
|
||||
- Logger.Info($"Invoking AuthHookReceived with: {affinityUri}");
|
||||
- handler.Invoke(affinityUri);
|
||||
- }
|
||||
- catch (Exception ex)
|
||||
- {
|
||||
- Logger.Error($"OnSubmitUrl failed: {ex}");
|
||||
- ShowError(errorText, $"Error: {ex.Message}");
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- /// <summary>
|
||||
- /// Extracts the affinity:// URI from either:
|
||||
- /// - A direct affinity://canva/authorize?... URL
|
||||
- /// - A redirect page URL: https://page.service.serif.com/canva-auth-redirect/?url=affinity%3A%2F%2F...
|
||||
- /// </summary>
|
||||
- private static Uri ExtractAffinityUri(string input)
|
||||
- {
|
||||
- if (input.StartsWith("affinity://", StringComparison.OrdinalIgnoreCase))
|
||||
- return new Uri(input);
|
||||
-
|
||||
- try
|
||||
- {
|
||||
- var uri = new Uri(input);
|
||||
- var query = uri.Query;
|
||||
- if (string.IsNullOrEmpty(query)) return null;
|
||||
-
|
||||
- var decoded = ParseQueryParam(query, "url");
|
||||
- if (decoded != null && decoded.StartsWith("affinity://", StringComparison.OrdinalIgnoreCase))
|
||||
- return new Uri(decoded);
|
||||
- }
|
||||
- catch { }
|
||||
-
|
||||
- return null;
|
||||
- }
|
||||
-
|
||||
- private static string ParseQueryParam(string query, string name)
|
||||
- {
|
||||
- if (query.StartsWith("?")) query = query.Substring(1);
|
||||
-
|
||||
- foreach (var part in query.Split('&'))
|
||||
- {
|
||||
- var eq = part.IndexOf('=');
|
||||
- if (eq < 0) continue;
|
||||
- var key = Uri.UnescapeDataString(part.Substring(0, eq));
|
||||
- if (string.Equals(key, name, StringComparison.OrdinalIgnoreCase))
|
||||
- return Uri.UnescapeDataString(part.Substring(eq + 1));
|
||||
- }
|
||||
- return null;
|
||||
- }
|
||||
-
|
||||
- private static void ShowError(TextBlock tb, string msg)
|
||||
- {
|
||||
- tb.Text = msg;
|
||||
- tb.Visibility = Visibility.Visible;
|
||||
- }
|
||||
-
|
||||
- private static BitmapImage LoadEmbeddedImage(string resourceName)
|
||||
- {
|
||||
- try
|
||||
- {
|
||||
- var assembly = typeof(CanvaSignInPatch).Assembly;
|
||||
- using (var stream = assembly.GetManifestResourceStream(resourceName))
|
||||
- {
|
||||
- if (stream == null) return null;
|
||||
- var bitmap = new BitmapImage();
|
||||
- bitmap.BeginInit();
|
||||
- bitmap.CacheOption = BitmapCacheOption.OnLoad;
|
||||
- bitmap.StreamSource = stream;
|
||||
- bitmap.EndInit();
|
||||
- bitmap.Freeze();
|
||||
- return bitmap;
|
||||
- }
|
||||
- }
|
||||
- catch (Exception ex)
|
||||
- {
|
||||
- Logger.Error($"Failed to load embedded image: {ex.Message}");
|
||||
- return null;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- private static T FindChild<T>(DependencyObject parent, Func<T, bool> predicate = null)
|
||||
- where T : DependencyObject
|
||||
- {
|
||||
- var count = VisualTreeHelper.GetChildrenCount(parent);
|
||||
- for (int i = 0; i < count; i++)
|
||||
- {
|
||||
- var child = VisualTreeHelper.GetChild(parent, i);
|
||||
- if (child is T t && (predicate == null || predicate(t)))
|
||||
- return t;
|
||||
- var result = FindChild(child, predicate);
|
||||
- if (result != null) return result;
|
||||
- }
|
||||
- return null;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- internal class HalfWidthConverter : IValueConverter
|
||||
- {
|
||||
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
- {
|
||||
- if (value is double d) return d * 0.5;
|
||||
- return value;
|
||||
- }
|
||||
-
|
||||
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
- {
|
||||
- throw new NotImplementedException();
|
||||
- }
|
||||
- }
|
||||
-}
|
||||
diff --git a/WineFix/Patches/MainWindowLoadedPatch.cs b/WineFix/Patches/MainWindowLoadedPatch.cs
|
||||
index 5c658b5..8e32a33 100644
|
||||
--- a/WineFix/Patches/MainWindowLoadedPatch.cs
|
||||
+++ b/WineFix/Patches/MainWindowLoadedPatch.cs
|
||||
@@ -52,10 +52,12 @@ namespace WineFix.Patches
|
||||
}
|
||||
|
||||
// Transpiler that replaces the call to HasPreviousPackageInstalled() with loading 'false'
|
||||
+ // and also removes the call to base.OnMainWindowLoaded()
|
||||
public static IEnumerable<CodeInstruction> OnMainWindowLoaded_Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
var codes = new List<CodeInstruction>(instructions);
|
||||
bool patchedPackageCheck = false;
|
||||
+ bool patchedBaseCall = false;
|
||||
|
||||
for (int i = 0; i < codes.Count; i++)
|
||||
{
|
||||
@@ -97,6 +99,54 @@ namespace WineFix.Patches
|
||||
patchedPackageCheck = true;
|
||||
continue;
|
||||
}
|
||||
+
|
||||
+ // Look for: call instance void Serif.Interop.Persona.Application::OnMainWindowLoaded(...)
|
||||
+ // (Index 22 in the IL - base class is Serif.Interop.Persona.Application, not System.Windows.Application!)
|
||||
+ if (!patchedBaseCall &&
|
||||
+ instruction.opcode == OpCodes.Call &&
|
||||
+ instruction.operand is MethodInfo baseMethod &&
|
||||
+ baseMethod.Name == "OnMainWindowLoaded" &&
|
||||
+ baseMethod.DeclaringType != null &&
|
||||
+ (baseMethod.DeclaringType.FullName == "Serif.Interop.Persona.Application" ||
|
||||
+ baseMethod.DeclaringType.FullName == "System.Windows.Application"))
|
||||
+ {
|
||||
+ Logger.Debug($"Found base.OnMainWindowLoaded call at instruction {i}");
|
||||
+
|
||||
+ // The IL sequence is (indices 20-22):
|
||||
+ // ldarg.0 ; load 'this'
|
||||
+ // ldarg.1 ; load 'mainWindow'
|
||||
+ // call base.OnMainWindowLoaded
|
||||
+ // Replace all three with NOPs, preserving labels
|
||||
+ if (i >= 2 &&
|
||||
+ codes[i - 2].opcode == OpCodes.Ldarg_0 &&
|
||||
+ codes[i - 1].opcode == OpCodes.Ldarg_1)
|
||||
+ {
|
||||
+ // Create NOPs but preserve labels
|
||||
+ var nop1 = new CodeInstruction(OpCodes.Nop);
|
||||
+ nop1.labels.AddRange(codes[i - 2].labels);
|
||||
+ codes[i - 2] = nop1;
|
||||
+
|
||||
+ var nop2 = new CodeInstruction(OpCodes.Nop);
|
||||
+ nop2.labels.AddRange(codes[i - 1].labels);
|
||||
+ codes[i - 1] = nop2;
|
||||
+
|
||||
+ var nop3 = new CodeInstruction(OpCodes.Nop);
|
||||
+ nop3.labels.AddRange(codes[i].labels);
|
||||
+ codes[i] = nop3;
|
||||
+
|
||||
+ Logger.Debug($"Removed base.OnMainWindowLoaded call (indices {i-2} to {i})");
|
||||
+ patchedBaseCall = true;
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ Logger.Warning($"WARNING: Found base.OnMainWindowLoaded but preceding instructions don't match expected pattern");
|
||||
+ var nop = new CodeInstruction(OpCodes.Nop);
|
||||
+ nop.labels.AddRange(codes[i].labels);
|
||||
+ codes[i] = nop;
|
||||
+ patchedBaseCall = true;
|
||||
+ }
|
||||
+ continue;
|
||||
+ }
|
||||
}
|
||||
|
||||
if (!patchedPackageCheck)
|
||||
@@ -104,6 +154,11 @@ namespace WineFix.Patches
|
||||
Logger.Warning($"WARNING: Could not find HasPreviousPackageInstalled call to patch");
|
||||
}
|
||||
|
||||
+ if (!patchedBaseCall)
|
||||
+ {
|
||||
+ Logger.Warning($"WARNING: Could not find base.OnMainWindowLoaded call to patch");
|
||||
+ }
|
||||
+
|
||||
return codes.AsEnumerable();
|
||||
}
|
||||
}
|
||||
diff --git a/WineFix/WineFix.csproj b/WineFix/WineFix.csproj
|
||||
index b1d3c82..c62939d 100644
|
||||
--- a/WineFix/WineFix.csproj
|
||||
+++ b/WineFix/WineFix.csproj
|
||||
@@ -29,10 +29,6 @@
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
- <Reference Include="System.Xaml" />
|
||||
- </ItemGroup>
|
||||
- <ItemGroup>
|
||||
- <EmbeddedResource Include="Resources\signin.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AffinityPluginLoader\AffinityPluginLoader.csproj">
|
||||
diff --git a/WineFix/WineFixPlugin.cs b/WineFix/WineFixPlugin.cs
|
||||
index 4ed9f42..a6ccd09 100644
|
||||
--- a/WineFix/WineFixPlugin.cs
|
||||
+++ b/WineFix/WineFixPlugin.cs
|
||||
@@ -25,10 +25,6 @@ namespace WineFix
|
||||
{
|
||||
return new PluginSettingsDefinition(PluginId)
|
||||
.AddSection("Patches")
|
||||
- .AddBool(SettingCanvaSignInHelper, "Canva sign-in helper",
|
||||
- defaultValue: true,
|
||||
- restartRequired: true,
|
||||
- description: "Patch the Canva sign-in dialog to include a helper textbox input and instructions to complete Canva sign-in without needing a protocol URL handler.")
|
||||
.AddBool(BezierRenderingFixKey, "Bezier curves: Fix tool preview path rendering",
|
||||
defaultValue: true,
|
||||
restartRequired: true,
|
||||
@@ -109,16 +105,6 @@ namespace WineFix
|
||||
{
|
||||
Logger.Info("Skipping ColorPicker Wayland fix (setting: " + magnifierFix + ")");
|
||||
}
|
||||
-
|
||||
- if (context.Settings.GetEffectiveValue<bool>(SettingCanvaSignInHelper))
|
||||
- {
|
||||
- context.Patch("Canva sign-in paste URL fix",
|
||||
- h => Patches.CanvaSignInPatch.ApplyPatches(h));
|
||||
- }
|
||||
- else
|
||||
- {
|
||||
- Logger.Info("Skipping Canva sign-in helper (disabled in settings)");
|
||||
- }
|
||||
}
|
||||
}
|
||||
}
|
||||
--
|
||||
2.53.0
|
||||
@@ -0,0 +1,193 @@
|
||||
{
|
||||
linkFarm,
|
||||
fetchurl,
|
||||
writeText,
|
||||
runCommand,
|
||||
callPackage,
|
||||
xvfb-run,
|
||||
lib,
|
||||
inputs,
|
||||
wine-packages,
|
||||
...
|
||||
}:
|
||||
let
|
||||
verbs = [
|
||||
"tahoma"
|
||||
"vcrun2022"
|
||||
#"dotnet20"
|
||||
"dotnet48"
|
||||
"corefonts"
|
||||
"win11"
|
||||
];
|
||||
|
||||
inherit (wine-packages)
|
||||
wine
|
||||
winetricks
|
||||
wineserver
|
||||
;
|
||||
|
||||
dependencies = callPackage ./dependencies.nix { };
|
||||
winetricksCache = linkFarm "winetricks-cache" [
|
||||
{
|
||||
name = "vcrun2022/vc_redist.x64.exe";
|
||||
path = fetchurl {
|
||||
urls = [
|
||||
"https://download.visualstudio.microsoft.com/download/pr/7ebf5fdb-36dc-4145-b0a0-90d3d5990a61/CC0FF0EB1DC3F5188AE6300FAEF32BF5BEEBA4BDD6E8E445A9184072096B713B/VC_redist.x64.exe"
|
||||
];
|
||||
hash = "sha256-zA/w6x3D9RiK5jAPrvMr9b7rpL3W6ORFqRhAcglrcTs=";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "vcrun2022/vc_redist.x86.exe";
|
||||
path = fetchurl {
|
||||
urls = [
|
||||
"https://download.visualstudio.microsoft.com/download/pr/7ebf5fdb-36dc-4145-b0a0-90d3d5990a61/0C09F2611660441084CE0DF425C51C11E147E6447963C3690F97E0B25C55ED64/VC_redist.x86.exe"
|
||||
];
|
||||
hash = "sha256-DAnyYRZgRBCEzg30JcUcEeFH5kR5Y8NpD5fgslxV7WQ=";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "dotnet48/ndp48-x86-x64-allos-enu.exe";
|
||||
path = fetchurl {
|
||||
urls = [
|
||||
"https://download.visualstudio.microsoft.com/download/pr/7afca223-55d2-470a-8edc-6a1739ae3252/abd170b4b0ec15ad0222a809b761a036/ndp48-x86-x64-allos-enu.exe"
|
||||
];
|
||||
hash = "sha256-lYidbePyBwwHeQrWzyAA0z2aG9/Go4FyWrgqscMU/VM=";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "dotnet40/dotNetFx40_Full_x86_x64.exe";
|
||||
path = fetchurl {
|
||||
urls = [
|
||||
"https://download.microsoft.com/download/9/5/A/95A9616B-7A37-4AF6-BC36-D6EA96C8DAAE/dotNetFx40_Full_x86_x64.exe"
|
||||
];
|
||||
hash = "sha256-ZeBkJY8uQYgWswT2Rv+eh68QHkyVUqsGS7dNKBw4ZZ8=";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "tahoma/IELPKTH.CAB";
|
||||
path = fetchurl {
|
||||
url = "https://downloads.sourceforge.net/corefonts/OldFiles/IELPKTH.CAB";
|
||||
hash = "sha256-wb4/uPAEJXC+duxtqgOpkULIg2fBvIECQLhYJ8cVlho=";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "dotnet20/NetFx64.exe";
|
||||
path = fetchurl {
|
||||
urls = [
|
||||
"https://download.microsoft.com/download/9/8/6/98610406-c2b7-45a4-bdc3-9db1b1c5f7e2/NetFx20SP1_x64.exe"
|
||||
];
|
||||
hash = "sha256-FzHlPeX0i6rgljZ3JXZg3xMpVJ6BxItNfbf38/Iymqs=";
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
layer_1 = runCommand "base-prefix-1" { } ''
|
||||
set -x -e
|
||||
mkdir -p $out
|
||||
export WINEPREFIX="$out"
|
||||
export WINETRICKS_UPDATE_CHECK=0
|
||||
export WINETRICKS_LATEST_VERSION_CHECK=disabled
|
||||
|
||||
mkdir -p /tmp/cache
|
||||
export XDG_CACHE_HOME="/tmp/cache"
|
||||
|
||||
${lib.getExe wine} wineboot --update
|
||||
|
||||
# by diffing a registry dump we found that you can disable the file association
|
||||
# through a registry key.
|
||||
${lib.getExe wine} regedit /S "${(writeText "file-association-disable.reg" ''
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Wine\DllOverrides]
|
||||
"winemenubuilder.exe"=""
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Wine\FileOpenAssociations]
|
||||
"Enable"="N"
|
||||
'').outPath}"
|
||||
|
||||
${lib.getExe winetricks} renderer=vulkan
|
||||
|
||||
install -D -t "$WINEPREFIX/drive_c/windows/system32/WinMetadata/" ${dependencies}/*.winmd
|
||||
|
||||
${lib.getExe wineserver} -w
|
||||
'';
|
||||
|
||||
layer_2 =
|
||||
runCommand "base-prefix-2"
|
||||
{
|
||||
nativeBuildInputs = [
|
||||
xvfb-run
|
||||
];
|
||||
}
|
||||
''
|
||||
set -x -e
|
||||
|
||||
mkdir -p $out
|
||||
cp -a ${layer_1}/. $out
|
||||
chmod -R +w $out
|
||||
export WINEPREFIX="$out"
|
||||
|
||||
mkdir -p /tmp/cache
|
||||
export XDG_CACHE_HOME="/tmp/cache"
|
||||
|
||||
${lib.getExe wine} winecfg -v win7
|
||||
xvfb-run ${lib.getExe wine} "${dependencies}/MicrosoftEdgeWebView2RuntimeInstallerX64.exe" /silent /install
|
||||
${lib.getExe wine} winecfg -v win11
|
||||
|
||||
${lib.getExe wine} regedit /S "${(writeText "webview2-regedit-changes.reg" ''
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\edgeupdate]
|
||||
"Start"=dword:00000004
|
||||
|
||||
[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\edgeupdatem]
|
||||
"Start"=dword:00000004
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Wine\AppDefaults]
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Wine\AppDefaults\msedgewebview2.exe]
|
||||
"Version"="win7"
|
||||
'').outPath}"
|
||||
|
||||
# The Edge Update service gets to start before we can deactivate it, so it must be stopped manually
|
||||
${lib.getExe wine} taskkill /f /im MicrosoftEdgeUpdate.exe
|
||||
|
||||
${lib.getExe wineserver} -w
|
||||
'';
|
||||
in
|
||||
runCommand "base-prefix-3"
|
||||
{
|
||||
nativeBuildInputs = [
|
||||
xvfb-run
|
||||
];
|
||||
}
|
||||
''
|
||||
set -x -e
|
||||
|
||||
${lib.strings.toShellVars {
|
||||
inherit verbs;
|
||||
}}
|
||||
|
||||
mkdir -p /tmp/cache/winetricks/corefonts
|
||||
|
||||
mkdir -p $out
|
||||
cp -a ${layer_2}/. $out
|
||||
chmod -R +w $out
|
||||
export WINEPREFIX="$out"
|
||||
|
||||
export XDG_CACHE_HOME="/tmp/cache"
|
||||
export WINETRICKS_UPDATE_CHECK=0
|
||||
export WINETRICKS_LATEST_VERSION_CHECK=disabled
|
||||
|
||||
export WINEDEBUG=-all
|
||||
export WINEESYNC=0
|
||||
export WINEFSYNC=0
|
||||
|
||||
cp -R ${winetricksCache}/* /tmp/cache/winetricks
|
||||
cp -R ${inputs.corefonts}/*.exe /tmp/cache/winetricks/corefonts
|
||||
|
||||
xvfb-run ${lib.getExe winetricks} -q -f "''${verbs[@]}"
|
||||
|
||||
${lib.getExe wineserver} -w
|
||||
''
|
||||
@@ -0,0 +1,28 @@
|
||||
{ inputs, ... }:
|
||||
{
|
||||
imports = [
|
||||
./wine
|
||||
./affinity
|
||||
./affinity-v3
|
||||
./apl
|
||||
./runner
|
||||
];
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
wine-packages,
|
||||
...
|
||||
}:
|
||||
{
|
||||
# this function builds the prefix right up until the affinity sources
|
||||
# are used. this separation exists for build caching, as the affinity sources
|
||||
# should never be exposed to a cache
|
||||
packages.base-prefix-pre-affinity = pkgs.callPackage ./basePrefix.nix {
|
||||
inherit inputs wine-packages;
|
||||
};
|
||||
|
||||
_module.args = {
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
fetchurl,
|
||||
lib,
|
||||
runCommand,
|
||||
}:
|
||||
let
|
||||
paths = {
|
||||
"Windows.Services.winmd" = fetchurl {
|
||||
url = "https://archive.org/download/windows.services-system.winmd/Windows.Services.winmd";
|
||||
hash = "sha256-l9TRXyyacQFHoVsq6s8zGvJ+SLl4pTNv6yesNksAzdQ=";
|
||||
};
|
||||
"Windows.System.winmd" = fetchurl {
|
||||
url = "https://archive.org/download/windows.services-system.winmd/Windows.System.winmd";
|
||||
hash = "sha256-EOh5DJXzF+gdqW1Jc4lS1/J2x3hC/SxRkHMzzS4h0TM=";
|
||||
};
|
||||
"MicrosoftEdgeWebView2RuntimeInstallerX64.exe" = fetchurl {
|
||||
url = "https://archive.org/download/microsoft-edge-web-view-2-runtime-installer-v109.0.1518.78/MicrosoftEdgeWebView2RuntimeInstallerX64.exe";
|
||||
hash = "sha256-8sxJhj4iFALUZk2fqUSkfyJUPaLcs2NDjD5Zh4m5/Vs=";
|
||||
};
|
||||
};
|
||||
in
|
||||
runCommand "dependencies" { } ''
|
||||
mkdir -p $out
|
||||
|
||||
${lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (name: content: ''
|
||||
ln -s ${content} $out/${name}
|
||||
'') paths
|
||||
)}
|
||||
''
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
callPackage,
|
||||
fetchzip,
|
||||
runCommand,
|
||||
lndir,
|
||||
zstd,
|
||||
lib,
|
||||
|
||||
inputs,
|
||||
v3 ? true,
|
||||
wine-packages,
|
||||
apl-combined,
|
||||
...
|
||||
}:
|
||||
let
|
||||
layer_3 = callPackage ./basePrefix.nix {
|
||||
inherit inputs wine-packages;
|
||||
};
|
||||
|
||||
installers = callPackage ./sources.nix { };
|
||||
registry-patches = callPackage ./registry-patches.nix { };
|
||||
|
||||
vkd3d = fetchzip {
|
||||
url = "https://github.com/HansKristian-Work/vkd3d-proton/releases/download/v3.0.1/vkd3d-proton-3.0.1.tar.zst";
|
||||
nativeBuildInputs = [ zstd ];
|
||||
hash = "sha256-xHJFtVDD/ZHVHF2Fn7TEEX0fMUWJvujNyNt2Xyw9F7o=";
|
||||
};
|
||||
|
||||
inherit (wine-packages) wine wineserver;
|
||||
in
|
||||
runCommand "base-prefix-4" { } ''
|
||||
set -x -e
|
||||
|
||||
mkdir -p $out
|
||||
cp -a ${layer_3}/. $out
|
||||
chmod -R +w $out
|
||||
export WINEPREFIX="$out"
|
||||
|
||||
cp ${vkd3d}/x64/d3d12.dll "$WINEPREFIX/drive_c/windows/system32"
|
||||
cp ${vkd3d}/x64/d3d12core.dll "$WINEPREFIX/drive_c/windows/system32"
|
||||
|
||||
${lib.getExe wine} regedit /S "${registry-patches.one-vkd3d}"
|
||||
|
||||
${lib.optionalString v3 ''
|
||||
${lib.getExe lndir} ${installers.v3} "$WINEPREFIX/drive_c/Program Files/"
|
||||
|
||||
pushd "$WINEPREFIX/drive_c/Program Files/Affinity/Affinity"
|
||||
cp -r "${apl-combined}/." .
|
||||
popd
|
||||
''}
|
||||
|
||||
${lib.optionalString (!v3) ''
|
||||
${lib.getExe lndir} ${installers.photo} "$WINEPREFIX/drive_c/Program Files/"
|
||||
${lib.getExe lndir} ${installers.designer} "$WINEPREFIX/drive_c/Program Files/"
|
||||
${lib.getExe lndir} ${installers.publisher} "$WINEPREFIX/drive_c/Program Files/"
|
||||
''}
|
||||
|
||||
${lib.getExe wineserver} -w
|
||||
|
||||
rm -rf $WINEPREFIX/drive_c/users/nixbld
|
||||
''
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
pkgs,
|
||||
}:
|
||||
rec {
|
||||
# vkd3d was added in revision 10
|
||||
one-vkd3d =
|
||||
(pkgs.writeText "vkd3d-regedit-changes.reg" ''
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Wine\DllOverrides]
|
||||
"d3d12"="native"
|
||||
"d3d12core"="native"
|
||||
'').outPath;
|
||||
|
||||
combined = pkgs.linkFarm "registry-patches-combined" [
|
||||
{
|
||||
name = "one.reg";
|
||||
path = one-vkd3d;
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{ inputs, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
wine-packages,
|
||||
...
|
||||
}:
|
||||
let
|
||||
runner = pkgs.callPackage ./package.nix {
|
||||
inherit inputs wine-packages;
|
||||
|
||||
registry-patches = (pkgs.callPackage ../registry-patches.nix { }).combined;
|
||||
prefixBase = "/non-functional-runner";
|
||||
name = "v3";
|
||||
};
|
||||
in
|
||||
{
|
||||
checks = {
|
||||
runner = runner.package;
|
||||
|
||||
runner-clippy = runner.package-clippy;
|
||||
};
|
||||
|
||||
packages = {
|
||||
runner = runner.package;
|
||||
runner-artifacts = runner.package-artifacts;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
|
||||
inputs,
|
||||
registry-patches,
|
||||
wine-packages,
|
||||
prefixBase,
|
||||
name,
|
||||
...
|
||||
}:
|
||||
let
|
||||
craneLib = inputs.crane.mkLib pkgs;
|
||||
src = craneLib.cleanCargoSource ../..;
|
||||
commonArgs = {
|
||||
inherit src;
|
||||
strictDeps = true;
|
||||
};
|
||||
|
||||
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||
|
||||
fileSetForCrate =
|
||||
crate:
|
||||
lib.fileset.toSource {
|
||||
root = ../..;
|
||||
fileset = lib.fileset.unions [
|
||||
../../Cargo.toml
|
||||
../../Cargo.lock
|
||||
(craneLib.fileset.commonCargoSources crate)
|
||||
];
|
||||
};
|
||||
|
||||
env = {
|
||||
WINE = lib.getExe wine-packages.wine;
|
||||
WINESERVER = lib.getExe wine-packages.wineserver;
|
||||
WINETRICKS = lib.getExe wine-packages.winetricks;
|
||||
FUSE_OVERLAYFS = lib.getExe pkgs.fuse-overlayfs;
|
||||
GNUTAR = lib.getExe pkgs.gnutar;
|
||||
ZENITY = lib.getExe pkgs.zenity;
|
||||
RSYNC = lib.getExe pkgs.rsync;
|
||||
REGISTRY_PATCHES = registry-patches;
|
||||
ON_LINUX = inputs.on-linux.outPath;
|
||||
};
|
||||
|
||||
executableName = "affinity-${lib.toLower name}";
|
||||
|
||||
executable = craneLib.buildPackage (
|
||||
commonArgs
|
||||
// {
|
||||
inherit cargoArtifacts env;
|
||||
|
||||
pname = executableName;
|
||||
|
||||
cargoExtraArgs = "-p runner --no-default-features --features ${lib.toLower name}";
|
||||
src = fileSetForCrate ../../crates/runner;
|
||||
|
||||
meta.mainProgram = executableName;
|
||||
|
||||
postInstall = ''
|
||||
mv $out/bin/runner $out/bin/affinity-${lib.toLower name}
|
||||
'';
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
package =
|
||||
pkgs.runCommand executableName
|
||||
{
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
}
|
||||
''
|
||||
mkdir -p $out/bin
|
||||
cp ${lib.getExe executable} $out/bin/${executableName}
|
||||
|
||||
wrapProgram $out/bin/${executableName} \
|
||||
--set "LOWER_DIR" "${prefixBase}" \
|
||||
'';
|
||||
|
||||
package-clippy = craneLib.cargoClippy (
|
||||
commonArgs
|
||||
// {
|
||||
inherit cargoArtifacts env;
|
||||
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
|
||||
}
|
||||
);
|
||||
|
||||
package-artifacts = cargoArtifacts;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
||||
mkExtract =
|
||||
source: segment:
|
||||
pkgs.runCommand "affinity-extracted-sources"
|
||||
{
|
||||
nativeBuildInputs = [
|
||||
pkgs._7zz
|
||||
pkgs.msitools
|
||||
];
|
||||
|
||||
meta.license = lib.licenses.unfree;
|
||||
}
|
||||
''
|
||||
7zz e ${source} -tpe "${segment}"
|
||||
msiextract -C $out ./*
|
||||
'';
|
||||
in
|
||||
{
|
||||
v3 = mkExtract (pkgs.fetchurl {
|
||||
url = "https://downloads.affinity.studio/Affinity%20x64.exe";
|
||||
hash = "sha256-cnPdRZlZrl5Pp6Vr+GkO7br98GrcMDnnJPXcAboxkC4=";
|
||||
}) ".rsrc/2057/BIN/135";
|
||||
|
||||
photo = mkExtract (pkgs.fetchurl {
|
||||
name = "affinity-photo-msi-2.6.5.exe";
|
||||
url = "https://archive.org/download/affinity_20251030/affinity-photo-msi-2.6.5.exe";
|
||||
hash = "sha256-waCX33rWVzZ8AYZWmv2EOqS6ikT6p7s9qRJ+8rDo6Wk=";
|
||||
}) ".rsrc/2057/BIN/135";
|
||||
|
||||
designer = mkExtract (pkgs.fetchurl {
|
||||
name = "affinity-designer-msi-2.6.5.exe";
|
||||
url = "https://archive.org/download/affinity_20251030/affinity-designer-msi-2.6.5.exe";
|
||||
hash = "sha256-b5K1FtsCWCjyn/NC4trB0g9Wv+AfLoDWgaZK2ZGXW0w=";
|
||||
}) ".rsrc/2057/BIN/135";
|
||||
|
||||
publisher = mkExtract (pkgs.fetchurl {
|
||||
name = "affinity-publisher-msi-2.6.5.exe";
|
||||
url = "https://archive.org/download/affinity_20251030/affinity-publisher-msi-2.6.5.exe";
|
||||
hash = "sha256-76ikkAnbAghaSU5YLYLxXiHfAuqVfoEcQK16rHd6+7A=";
|
||||
}) ".rsrc/2057/BIN/135";
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{ inputs, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
wine-packages = pkgs.callPackage ./packages.nix {
|
||||
inherit inputs;
|
||||
};
|
||||
in
|
||||
{
|
||||
packages.wine = wine-packages.wine;
|
||||
|
||||
_module.args = {
|
||||
inherit (wine-packages) wineUnwrapped;
|
||||
inherit wine-packages;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
stdenv,
|
||||
callPackage,
|
||||
pkgs,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
wineUnstable = inputs.nixpkgs.legacyPackages.${stdenv.hostPlatform.system}.wineWow64Packages.full;
|
||||
|
||||
symlink = callPackage ./symlink.nix { };
|
||||
|
||||
wineUnwrapped = symlink {
|
||||
wine = wineUnstable;
|
||||
};
|
||||
|
||||
wrapWithPrefix = callPackage ./wrapWithPrefix.nix {
|
||||
inherit wineUnwrapped;
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit wineUnwrapped;
|
||||
|
||||
wine = wrapWithPrefix wineUnwrapped "wine";
|
||||
winetricks = wrapWithPrefix pkgs.winetricks "winetricks";
|
||||
wineserver = wrapWithPrefix wineUnwrapped "wineserver";
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{ runCommand }:
|
||||
{ wine }:
|
||||
runCommand "wine-symlink" { inherit wine; } ''
|
||||
mkdir -p $out/bin
|
||||
for f in $wine/bin/*; do
|
||||
cp -L "$f" $out/bin/
|
||||
done
|
||||
|
||||
ln -s "$wine/bin/wine" $out/bin/wine64
|
||||
for dir in include lib share; do
|
||||
ln -s "$wine/$dir" $out
|
||||
done
|
||||
|
||||
patchShebangs $out
|
||||
''
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
stdenv,
|
||||
lib,
|
||||
pkgs,
|
||||
wineUnwrapped,
|
||||
}:
|
||||
pkg: pname:
|
||||
stdenv.mkDerivation rec {
|
||||
name = "affinity-${pname}";
|
||||
src = ./.;
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
installPhase = ''
|
||||
makeWrapper ${lib.getExe' pkg pname} $out/bin/${name} \
|
||||
--set LD_LIBRARY_PATH "${wineUnwrapped}/lib:$LD_LIBRARY_PATH" \
|
||||
--set WINESERVER "${lib.getExe' wineUnwrapped "wineserver"}" \
|
||||
--set WINELOADER "${lib.getExe' wineUnwrapped "wine"}" \
|
||||
--set WINEDLLPATH "${wineUnwrapped}/lib/wine" \
|
||||
--set WINE "${lib.getExe' wineUnwrapped "wine"}"
|
||||
'';
|
||||
meta.mainProgram = name;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
self',
|
||||
...
|
||||
}:
|
||||
let
|
||||
stripTyping =
|
||||
value:
|
||||
let
|
||||
split = builtins.split "(from typing import TYPE_CHECKING|# typing-end)" value;
|
||||
in
|
||||
(builtins.elemAt split 0) + (builtins.elemAt split 4);
|
||||
|
||||
defaults = {
|
||||
virtualisation.memorySize = 4096;
|
||||
|
||||
imports = [ "${pkgs.path}/nixos/tests/common/x11.nix" ];
|
||||
};
|
||||
in
|
||||
{
|
||||
checks = {
|
||||
test-affinity-v3 = pkgs.testers.runNixOSTest {
|
||||
name = "test-affinity-v3";
|
||||
enableOCR = true;
|
||||
inherit defaults;
|
||||
|
||||
nodes.machine.environment.systemPackages = [ self'.packages.affinity-v3 ];
|
||||
|
||||
testScript = stripTyping (builtins.readFile ./v3.py);
|
||||
};
|
||||
|
||||
test-affinity-v2 = pkgs.testers.runNixOSTest {
|
||||
name = "test-affinity-v2";
|
||||
enableOCR = true;
|
||||
inherit defaults;
|
||||
|
||||
nodes = {
|
||||
photo.environment.systemPackages = [ self'.packages.affinity-photo ];
|
||||
publisher.environment.systemPackages = [ self'.packages.affinity-publisher ];
|
||||
designer.environment.systemPackages = [ self'.packages.affinity-designer ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
${stripTyping (builtins.readFile ./v2.py)}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
[project]
|
||||
name = "affinity-nix-vm-tests"
|
||||
version = "0.0.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"colorama>=0.4.6",
|
||||
"ipython>=9.8.0",
|
||||
"junit-xml>=1.9",
|
||||
"nixos-test-driver",
|
||||
"ptpython>=3.0.32",
|
||||
"remote-pdb>=2.1.0",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
nixos-test-driver = { git = "https://github.com/NixOS/nixpkgs", subdirectory = "nixos/lib/test-driver/src", branch = "nixos-25.11" }
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty>=0.0.4"]
|
||||
Generated
+302
@@ -0,0 +1,302 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "affinity-nix-vm-tests"
|
||||
version = "0.0.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "colorama" },
|
||||
{ name = "ipython" },
|
||||
{ name = "junit-xml" },
|
||||
{ name = "nixos-test-driver" },
|
||||
{ name = "ptpython" },
|
||||
{ name = "remote-pdb" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "ty" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "colorama", specifier = ">=0.4.6" },
|
||||
{ name = "ipython", specifier = ">=9.8.0" },
|
||||
{ name = "junit-xml", specifier = ">=1.9" },
|
||||
{ name = "nixos-test-driver", git = "https://github.com/NixOS/nixpkgs?subdirectory=nixos%2Flib%2Ftest-driver%2Fsrc&branch=nixos-25.11" },
|
||||
{ name = "ptpython", specifier = ">=3.0.32" },
|
||||
{ name = "remote-pdb", specifier = ">=2.1.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "ty", specifier = ">=0.0.4" }]
|
||||
|
||||
[[package]]
|
||||
name = "appdirs"
|
||||
version = "1.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asttokens"
|
||||
version = "3.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decorator"
|
||||
version = "5.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "executing"
|
||||
version = "2.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipython"
|
||||
version = "9.12.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "decorator" },
|
||||
{ name = "ipython-pygments-lexers" },
|
||||
{ name = "jedi" },
|
||||
{ name = "matplotlib-inline" },
|
||||
{ name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "prompt-toolkit" },
|
||||
{ name = "pygments" },
|
||||
{ name = "stack-data" },
|
||||
{ name = "traitlets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/73/7114f80a8f9cabdb13c27732dce24af945b2923dcab80723602f7c8bc2d8/ipython-9.12.0.tar.gz", hash = "sha256:01daa83f504b693ba523b5a407246cabde4eb4513285a3c6acaff11a66735ee4", size = 4428879, upload-time = "2026-03-27T09:42:45.312Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl", hash = "sha256:0f2701e8ee86e117e37f50563205d36feaa259d2e08d4a6bc6b6d74b18ce128d", size = 625661, upload-time = "2026-03-27T09:42:42.831Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipython-pygments-lexers"
|
||||
version = "1.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.19.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "parso" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "junit-xml"
|
||||
version = "1.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/af/bc988c914dd1ea2bc7540ecc6a0265c2b6faccc6d9cdb82f20e2094a8229/junit-xml-1.9.tar.gz", hash = "sha256:de16a051990d4e25a3982b2dd9e89d671067548718866416faec14d9de56db9f", size = 7349, upload-time = "2023-01-24T18:42:00.836Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/93/2d896b5fd3d79b4cadd8882c06650e66d003f465c9d12c488d92853dff78/junit_xml-1.9-py2.py3-none-any.whl", hash = "sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732", size = 7130, upload-time = "2020-02-22T20:41:37.661Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib-inline"
|
||||
version = "0.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "traitlets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nixos-test-driver"
|
||||
version = "0.0.0"
|
||||
source = { git = "https://github.com/NixOS/nixpkgs?subdirectory=nixos%2Flib%2Ftest-driver%2Fsrc&branch=nixos-25.11#54170c54449ea4d6725efd30d719c5e505f1c10e" }
|
||||
|
||||
[[package]]
|
||||
name = "parso"
|
||||
version = "0.8.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pexpect"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ptyprocess" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.52"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "wcwidth" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptpython"
|
||||
version = "3.0.32"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "appdirs" },
|
||||
{ name = "jedi" },
|
||||
{ name = "prompt-toolkit" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/8c/7e904ceeb512b4530c7ca1d918d3565d694a1fa7df337cdfc36a16347d68/ptpython-3.0.32.tar.gz", hash = "sha256:11651778236de95c582b42737294e50a66ba4a21fa01c0090ea70815af478fe0", size = 74080, upload-time = "2025-11-20T21:20:48.27Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/ac/0e35e5d7afd47ab0e2c71293ed2ad18df91a2a4a008c0ff59c2f22def377/ptpython-3.0.32-py3-none-any.whl", hash = "sha256:16435d323e5fc0a685d5f4dc5bb4494fb68ac68736689cd1247e1eda9369b616", size = 68099, upload-time = "2025-11-20T21:20:46.634Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptyprocess"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pure-eval"
|
||||
version = "0.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.20.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remote-pdb"
|
||||
version = "2.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/b5/4944cac06fd9fc4a2e168313ec220aa25ed96ce83947b63eea5b4045b22d/remote-pdb-2.1.0.tar.gz", hash = "sha256:2d70c6f41e0eabf0165e8f1be58f82aa7a605aaeab8f2aefeb9ce246431091c1", size = 22295, upload-time = "2020-07-24T13:31:32.985Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/71/c5/d208c66344bb785d800adb61aef512290d3473052b9e7697890f0547aff2/remote_pdb-2.1.0-py2.py3-none-any.whl", hash = "sha256:94f73a92ac1248cf16189211011f97096bdada8a7baac8c79372663bbb57b5d0", size = 6304, upload-time = "2020-07-24T13:31:31.535Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stack-data"
|
||||
version = "0.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asttokens" },
|
||||
{ name = "executing" },
|
||||
{ name = "pure-eval" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "traitlets"
|
||||
version = "5.14.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty"
|
||||
version = "0.0.29"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/d5/853561de49fae38c519e905b2d8da9c531219608f1fccc47a0fc2c896980/ty-0.0.29.tar.gz", hash = "sha256:e7936cca2f691eeda631876c92809688dbbab68687c3473f526cd83b6a9228d8", size = 5469221, upload-time = "2026-04-05T15:01:21.328Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/03/b7/911f9962115acfa24e3b2ec9d4992dd994c38e8769e1b1d7680bb4d28a51/ty-0.0.29-py3-none-linux_armv6l.whl", hash = "sha256:b8a40955f7660d3eaceb0d964affc81b790c0765e7052921a5f861ff8a471c30", size = 10568206, upload-time = "2026-04-05T15:01:19.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/c3/fcae2167d4c77a97269f92f11d1b43b03617f81de1283d5d05b43432110c/ty-0.0.29-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6b6849adae15b00bbe2d3c5b078967dcb62eba37d38936b8eeb4c81a82d2e3b8", size = 10442530, upload-time = "2026-04-05T15:01:28.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/33/5a6bfa240cfcb9c36046ae2459fa9ea23238d20130d8656ff5ac4d6c012a/ty-0.0.29-py3-none-macosx_11_0_arm64.whl", hash = "sha256:dcdd9b17209788152f7b7ea815eda07989152325052fe690013537cc7904ce49", size = 9915735, upload-time = "2026-04-05T15:01:10.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/1e/318f45fae232118e81a6306c30f50de42c509c412128d5bd231eab699ffb/ty-0.0.29-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d8ed4789bae78ffaf94462c0d25589a734cab0366b86f2bbcb1bb90e1a7a169", size = 10419748, upload-time = "2026-04-05T15:01:32.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/a8/5687872e2ab5a0f7dd4fd8456eac31e9381ad4dc74961f6f29965ad4dd91/ty-0.0.29-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91ec374b8565e0ad0900011c24641ebbef2da51adbd4fb69ff3280c8a7eceb02", size = 10394738, upload-time = "2026-04-05T15:01:06.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/68/015d118097eeb95e6a44c4abce4c0a28b7b9dfb3085b7f0ee48e4f099633/ty-0.0.29-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:298a8d5faa2502d3810bbbb47a030b9455495b9921594206043c785dd61548cf", size = 10910613, upload-time = "2026-04-05T15:01:17.17Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/01/47ce3c6c53e0670eadbe80756b167bf80ed6681d1ba57cfde2e8065a13d1/ty-0.0.29-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c8fba1a3524c6109d1e020d92301c79d41bf442fa8d335b9fa366239339cb70", size = 11475750, upload-time = "2026-04-05T15:01:30.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/cf/e361845b1081c9264ad5b7c963231bab03f2666865a9f2a115c4233f2137/ty-0.0.29-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c48adf88a70d264128c39ee922ed14a947817fced1e93c08c1a89c9244edcde", size = 11190055, upload-time = "2026-04-05T15:01:12.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/12/0fb0857e9a62cb11586e9a712103877bbf717f5fb570d16634408cfdefee/ty-0.0.29-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ce0a7a0e96bc7b42518cd3a1a6a6298ef64ff40ca4614355c1aa807059b5c6f", size = 11020539, upload-time = "2026-04-05T15:01:37.022Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/36/5a26753802083f80cd125db6c4348ad42b3c982ec36e718e0bf4c18f75e5/ty-0.0.29-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6ac86a05b4a3731d45365ab97780acc7b8146fa62fccb3cbe94fe6546c67a97", size = 10396399, upload-time = "2026-04-05T15:01:26.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/e6/b4e75b5752239ab3ab400f19faef4dbef81d05aab5d3419fda0c062a3765/ty-0.0.29-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6bbbf53141af0f3150bf288d716263f1a3550054e4b3551ca866d38192ba9891", size = 10421461, upload-time = "2026-04-05T15:01:08.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/21/1084b5b609f9abed62070ec0b31c283a403832a6310c8bbc208bd45ee1e6/ty-0.0.29-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1c9e06b770c1d0ff5efc51e34312390db31d53fcf3088163f413030b42b74f84", size = 10599187, upload-time = "2026-04-05T15:01:23.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/a1/ce19a2ca717bbcc1ee11378aba52ef70b6ce5b87245162a729d9fdc2360f/ty-0.0.29-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0307fe37e3f000ef1a4ae230bbaf511508a78d24a5e51b40902a21b09d5e6037", size = 11121198, upload-time = "2026-04-05T15:01:15.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/6b/f1430b279af704321566ce7ec2725d3d8258c2f815ebd93e474c64cd4543/ty-0.0.29-py3-none-win32.whl", hash = "sha256:7a2a898217960a825f8bc0087e1fdbaf379606175e98f9807187221d53a4a8ed", size = 9995331, upload-time = "2026-04-05T15:01:01.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/ef/3ef01c17785ff9a69378465c7d0faccd48a07b163554db0995e5d65a5a23/ty-0.0.29-py3-none-win_amd64.whl", hash = "sha256:fc1294200226b91615acbf34e0a9ad81caf98c081e9c6a912a31b0a7b603bc3f", size = 11023644, upload-time = "2026-04-05T15:01:04.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/55/87280a994d6a2d2647c65e12abbc997ed49835794366153c04c4d9304d76/ty-0.0.29-py3-none-win_arm64.whl", hash = "sha256:f9794bbd1bb3ce13f78c191d0c89ae4c63f52c12b6daa0c6fe220b90d019d12c", size = 10428165, upload-time = "2026-04-05T15:01:34.665Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" },
|
||||
]
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from test_driver.machine import Machine
|
||||
|
||||
photo: Machine = None # ty: ignore[invalid-assignment]
|
||||
publisher: Machine = None # ty: ignore[invalid-assignment]
|
||||
designer: Machine = None # ty: ignore[invalid-assignment]
|
||||
|
||||
# typing-end
|
||||
|
||||
photo.wait_for_x()
|
||||
publisher.wait_for_x()
|
||||
designer.wait_for_x()
|
||||
|
||||
photo.succeed("affinity-photo >&2 &")
|
||||
designer.succeed("affinity-designer >&2 &")
|
||||
publisher.succeed("affinity-publisher >&2 &")
|
||||
|
||||
photo.wait_for_window(r"^Affinity Photo 2$")
|
||||
photo.screenshot("screenshot-photo")
|
||||
|
||||
designer.wait_for_window(r"^Affinity Designer 2$")
|
||||
designer.screenshot("screenshot-designer")
|
||||
|
||||
publisher.wait_for_window(r"^Affinity Publisher 2$")
|
||||
publisher.screenshot("screenshot-publisher")
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from test_driver.machine import Machine
|
||||
|
||||
machine: Machine = None # ty: ignore[invalid-assignment]
|
||||
|
||||
# typing-end
|
||||
|
||||
machine.start()
|
||||
machine.wait_for_x()
|
||||
|
||||
machine.succeed("affinity-v3 >&2 &")
|
||||
machine.wait_for_window(r"^Affinity$")
|
||||
machine.screenshot("screenshot")
|
||||
@@ -0,0 +1,9 @@
|
||||
[files]
|
||||
extend-exclude = ["packages/apl/no-login.patch"]
|
||||
|
||||
[type.nix.extend-words]
|
||||
# this is a 7zz argument...
|
||||
tpe = "tpe"
|
||||
|
||||
[type.md.extend-words]
|
||||
Canva = "Canva"
|
||||
Reference in New Issue
Block a user