Compare commits
55 Commits
01302214c2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 930a84a0f6 | |||
| 447d94fdd6 | |||
| f0bd2c0b73 | |||
| 119a4d0341 | |||
| 53b40a4d2f | |||
| 0533cdab23 | |||
| 8c61aa1e1d | |||
| 77924b5937 | |||
| 2c811503a2 | |||
| 18a55272a3 | |||
| 1658127bdd | |||
| 72a8c13c1f | |||
| aececd8e5d | |||
| 883cba901c | |||
| a1874f3682 | |||
| 3c429b6c51 | |||
| a4d2f7bc26 | |||
| 658806e9ff | |||
| f8db414da3 | |||
| bc419f8d5e | |||
| 3b0080849d | |||
| 53cc8edcfd | |||
| 1086039dcb | |||
| ae0231fe69 | |||
| 4205ef18d3 | |||
| bbcea1f4d2 | |||
| 91c7fdda70 | |||
| f1a7cb89e5 | |||
| fe9f839091 | |||
| 9d3467ea26 | |||
| 9d02a2187f | |||
| c164bb73bc | |||
| 92ec83b8d7 | |||
| 8d7f96d499 | |||
| 684a24fcf8 | |||
| 5c182bb4d3 | |||
| 9e9aa282b1 | |||
| 76ddddb6cd | |||
| f0e610f8c8 | |||
| 7d92f9a4a0 | |||
| 1a45b309e7 | |||
| 7f36375a73 | |||
| a6322bff51 | |||
| 6804b96078 | |||
| d4b52efee0 | |||
| ec5c78e8ce | |||
| e325ba08f1 | |||
| 03d17fad6e | |||
| 8f762928db | |||
| 94f868ebda | |||
| 2da84f8797 | |||
| fe81541eeb | |||
| 3edb08efc7 | |||
| edb9d0a14d | |||
| c6fa89f15f |
171
Cargo.lock
generated
171
Cargo.lock
generated
@@ -73,25 +73,6 @@ version = "3.19.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cbindgen"
|
|
||||||
version = "0.29.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "975982cdb7ad6a142be15bdf84aea7ec6a9e5d4d797c004d43185b24cfe4e684"
|
|
||||||
dependencies = [
|
|
||||||
"clap",
|
|
||||||
"heck",
|
|
||||||
"indexmap",
|
|
||||||
"log",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"syn",
|
|
||||||
"tempfile",
|
|
||||||
"toml 0.8.23",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -203,12 +184,6 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fastrand"
|
|
||||||
version = "2.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -249,12 +224,6 @@ version = "1.70.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.81"
|
version = "0.3.81"
|
||||||
@@ -304,6 +273,28 @@ version = "2.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_enum"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
|
||||||
|
dependencies = [
|
||||||
|
"num_enum_derive",
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_enum_derive"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
@@ -339,6 +330,15 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-crate"
|
||||||
|
version = "3.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||||
|
dependencies = [
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.101"
|
version = "1.0.101"
|
||||||
@@ -420,12 +420,6 @@ version = "1.0.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -462,28 +456,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.145"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_spanned"
|
|
||||||
version = "0.6.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -493,12 +465,27 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slotmap"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.1"
|
version = "1.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strfmt"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29fdc163db75f7b5ffa3daf0c5a7136fb0d4b2f35523cd1769da05e034159feb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@@ -516,19 +503,6 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tempfile"
|
|
||||||
version = "3.23.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
|
||||||
dependencies = [
|
|
||||||
"fastrand",
|
|
||||||
"getrandom",
|
|
||||||
"once_cell",
|
|
||||||
"rustix",
|
|
||||||
"windows-sys 0.60.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.17"
|
version = "2.0.17"
|
||||||
@@ -549,18 +523,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml"
|
|
||||||
version = "0.8.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_spanned 0.6.9",
|
|
||||||
"toml_datetime 0.6.11",
|
|
||||||
"toml_edit",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
@@ -569,22 +531,13 @@ checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"serde_spanned 1.0.3",
|
"serde_spanned",
|
||||||
"toml_datetime 0.7.3",
|
"toml_datetime",
|
||||||
"toml_parser",
|
"toml_parser",
|
||||||
"toml_writer",
|
"toml_writer",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_datetime"
|
|
||||||
version = "0.6.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@@ -596,15 +549,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.27"
|
version = "0.23.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"toml_datetime",
|
||||||
"serde_spanned 0.6.9",
|
"toml_parser",
|
||||||
"toml_datetime 0.6.11",
|
|
||||||
"toml_write",
|
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -617,12 +568,6 @@ dependencies = [
|
|||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_write"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_writer"
|
name = "toml_writer"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -664,6 +609,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.1+wasi-0.2.4"
|
version = "1.0.1+wasi-0.2.4"
|
||||||
@@ -738,6 +689,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"regex",
|
"regex",
|
||||||
|
"strfmt",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"uuid",
|
"uuid",
|
||||||
"widestring",
|
"widestring",
|
||||||
@@ -749,7 +701,8 @@ dependencies = [
|
|||||||
name = "wfassoc-cdylib"
|
name = "wfassoc-cdylib"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cbindgen",
|
"num_enum",
|
||||||
|
"slotmap",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"wfassoc",
|
"wfassoc",
|
||||||
]
|
]
|
||||||
@@ -762,7 +715,7 @@ dependencies = [
|
|||||||
"comfy-table",
|
"comfy-table",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"toml 0.9.8",
|
"toml",
|
||||||
"wfassoc",
|
"wfassoc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2022-2025 yyc12345
|
Copyright (c) 2022-2026 yyc12345
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -4,6 +4,20 @@
|
|||||||
|
|
||||||
**Work In Progress**
|
**Work In Progress**
|
||||||
|
|
||||||
|
## Preface
|
||||||
|
|
||||||
|
The final goal of this repository is making a viable solution for programmer,
|
||||||
|
who is familiar with GUI application development on POSIX system
|
||||||
|
and want to find a workable way to setup file associations on Windows for their developed software
|
||||||
|
without learning too much Win32 knowledge.
|
||||||
|
|
||||||
|
So considering this premise, this library will NOT assist those professional Windows developer
|
||||||
|
for setting up those complex Windows shell functions, such as the interaction with Explorer preview panel,
|
||||||
|
or adding extra entries in menu.
|
||||||
|
|
||||||
|
In brief words, this library only do one thing. Setup and check file associations for your application,
|
||||||
|
and make it at least works.
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
* `wfassoc`: Core Rust library. Rust programmer can directly utilize it.
|
* `wfassoc`: Core Rust library. Rust programmer can directly utilize it.
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ default = '@C:\path\to\ppic.exe,-1001'
|
|||||||
|
|
||||||
# And more string resources...
|
# And more string resources...
|
||||||
jpg = '@C:\path\to\ppic.exe,-1011'
|
jpg = '@C:\path\to\ppic.exe,-1011'
|
||||||
|
jfif = '@C:\path\to\ppic.exe,-1050'
|
||||||
gif = '@C:\path\to\ppic.exe,-1012'
|
gif = '@C:\path\to\ppic.exe,-1012'
|
||||||
bmp = '@C:\path\to\ppic.exe,-1013'
|
bmp = '@C:\path\to\ppic.exe,-1013'
|
||||||
png = '@C:\path\to\ppic.exe,-1014'
|
png = '@C:\path\to\ppic.exe,-1014'
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
# Pineapple Picture Association
|
|
||||||
|
|
||||||
TODO
|
|
||||||
241
example/qwfassoc/PROMPT.txt
Normal file
241
example/qwfassoc/PROMPT.txt
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
我要求你使用Qt Widget编写一个GUI程序。该GUI程序是wfassoc的一个可视化界面。wfassoc是一个由Rust编写,并暴露出C接口,可以操作Windows注册表,来管理应用程序的注册和卸载,以及文件关联的动态链接库。
|
||||||
|
|
||||||
|
我要求你在@example/qwfassoc/TASKS.md 中先做好详细的规划,而不是上来就写代码。我会安排其他人来负责执行你做的规划。
|
||||||
|
|
||||||
|
# 项目要求
|
||||||
|
|
||||||
|
- 使用Qt Widget编写界面,而不是QML。
|
||||||
|
- 使用UI文件而不是C++语句来构建界面。
|
||||||
|
- 我使用的是Qt 6,使用CMake作为构建系统,不要使用Qt的qmake。
|
||||||
|
- 使用toml11作为TOML读取库。
|
||||||
|
|
||||||
|
# 界面要求
|
||||||
|
|
||||||
|
这是一个基于 Qt Widgets 的标准对话框界面描述。你可以按照以下层级结构来构建代码:
|
||||||
|
|
||||||
|
## 主窗口容器 (Main Window)
|
||||||
|
|
||||||
|
* 类: `QDialog`。
|
||||||
|
* 窗口标题: "xxx选项"。xxx在应用程序初始化时,通过wfassoc的Program提供的接口,运行时获取。
|
||||||
|
* 窗口图标:在应用程序初始化时,通过wfassoc的Program提供的接口,运行时获取。
|
||||||
|
* 大小限制:固定大小480x600
|
||||||
|
|
||||||
|
## 选项卡 (Top Tabs)
|
||||||
|
|
||||||
|
* 组件: `QTabWidget`。
|
||||||
|
* 标签页 (Tabs): 从左到右依次添加以下标签页:
|
||||||
|
1. 应用程序
|
||||||
|
2. 文件关联
|
||||||
|
* 大小:选项卡占据对话框全部内容
|
||||||
|
|
||||||
|
## "应用程序"选项卡内容
|
||||||
|
|
||||||
|
该选项卡内部使用垂直布局 (`QVBoxLayout`),包含一个主要的分组区域:
|
||||||
|
|
||||||
|
### 区域内容
|
||||||
|
|
||||||
|
* 容器: `QGroupBox`。
|
||||||
|
* 区域标题:安装与卸载
|
||||||
|
* 布局: 垂直布局 (`QVBoxLayout`)。
|
||||||
|
* 上半部分
|
||||||
|
* 水平布局(`QHBoxLayout`)。
|
||||||
|
* 左侧: 一个 `QLabel` 显示图标,该图标表示要设定的应用程序的图标。在应用程序初始化时,通过wfassoc的Program提供的接口,运行时获取。
|
||||||
|
* 右侧: 一个 `QLabel` 显示文本"在此安装或卸载xxx",表示要设定的应用程序的名称。xxx在应用程序初始化时,通过wfassoc的Program提供的接口,运行时获取。 (文本需要设置自动换行 `setWordWrap(true)`)。
|
||||||
|
* 下半部分
|
||||||
|
* 水平布局(`QHBoxLayout`)。
|
||||||
|
* 内容为两个 `QPushButton`,文本分别为:
|
||||||
|
* 安装:为当前对象(系统或当前用户,由应用程序初始化时从命令行参数获取)安装应用。如果应用程序已经安装,则不可点击。
|
||||||
|
* 卸载:为当前对象(系统或当前用户,由应用程序初始化时从命令行参数获取)卸载应用。如果应用程序没有安装,则不可点击。
|
||||||
|
|
||||||
|
## "文件关联"选项卡内容
|
||||||
|
|
||||||
|
在这个选项页内部,使用一个垂直布局 (`QVBoxLayout`) 来排列以下控件:
|
||||||
|
|
||||||
|
* 说明文本:
|
||||||
|
* 组件: `QLabel`。
|
||||||
|
* 文本: "使用 xxx 关联的文件类型:"。xxx在应用程序初始化时,通过wfassoc的Program提供的接口,运行时获取。
|
||||||
|
|
||||||
|
* 功能按钮行:
|
||||||
|
* 布局: `QHBoxLayout` (水平布局)。
|
||||||
|
* 组件: 两个 `QPushButton`。
|
||||||
|
* 文本: 两个按钮的文本都是 "+"。
|
||||||
|
* 位置: 位于列表上方,用于“全选”操作(第一次点击,将所有没关联的文件扩展名(显示为空白)设置为应用程序提供的打开方式。如果没有空白内容,或第二次点击,将所有文件全部设置为应用程序提供的打开方式)。
|
||||||
|
|
||||||
|
* 文件类型列表 (核心组件):
|
||||||
|
* 组件: `QTableWidget` (表格控件)。
|
||||||
|
* 列数: 3列。
|
||||||
|
* 表头 (Header):
|
||||||
|
* 第1列标题: "类型"
|
||||||
|
* 第2列标题: "uuu" (uuu在运行时进行获取,为当前用户名)
|
||||||
|
* 第3列标题: "所有用户"
|
||||||
|
* 行内容示例:
|
||||||
|
* 第一列:一个文件类型图标,右边跟着对应的文本。表示当前文件扩展名,和当前混合视图(hybrid)下的图标。图标和文本均从wfassoc函数获取。
|
||||||
|
* 第二列:用户视图(user)下的名称。文本从wfassoc函数获取。
|
||||||
|
* 第三列:系统视图(system)下的名称。文本从wfassoc函数获取。
|
||||||
|
* 滚动条: 右侧有一个垂直滚动条 (`QScrollBar`),表示内容超出可视区域。
|
||||||
|
* 操作方式:
|
||||||
|
* 第二列和第三列的元素可点击。
|
||||||
|
* 如果元素为空白或其它打开方式,则点击后设置为当前应用程序指定的打开方式(link)。
|
||||||
|
* 如果是自身打开方式,点击后设置为空白(unlink)
|
||||||
|
* 点击后,第一列的图标需要改变,也因此你需要暂存当前应用程序提供打开方式的图标。如果第二第三列均为空,则不显示图标(仍然占位,显示为空白)。
|
||||||
|
* 点击操作并不会实时操作注册表,程序需要暂存用户的需求,然后在点击确认或应用按钮后再统一执行。
|
||||||
|
|
||||||
|
* 底部按钮栏
|
||||||
|
* 布局: `QHBoxLayout` (水平布局),通常右对齐或使用 `QDialogButtonBox`。
|
||||||
|
* 组件: 三个 `QPushButton`。
|
||||||
|
* 按钮文本 (从左到右):
|
||||||
|
1. "确定" (通常设为默认按钮 `setDefault(true)`)。
|
||||||
|
2. "取消"。
|
||||||
|
3. "应用":点击后应用修改,并留在页面 (如果没有修改,则不可用)。
|
||||||
|
|
||||||
|
额外注意:
|
||||||
|
|
||||||
|
* 如果应用程序没有安装,则本页面下所有内容均不启用。
|
||||||
|
* 如果启动时命令行指定以为当前用户安装的模式启动,则系统那一栏所有按钮都不可用
|
||||||
|
|
||||||
|
# 代码要求
|
||||||
|
|
||||||
|
- 有关wfassoc的接口,请查阅@wfassoc-cdylib/codegen/wfassoc++.h 我要求你使用这个头文件中提供的内容来进行编写。
|
||||||
|
- wfassoc++.h文件中没有注释,如果你想查看注释,请访问@wfassoc-cdylib/codegen/wfassoc.h 文件。wfassoc++.h是wfassoc.h的C++包装。
|
||||||
|
- wfassoc.h所暴露的接口是由Rust编写的,通常查看wfassoc.h可满足所有需求。如果仍有不确定的内容,可查看其对应Rust项目的源码,位于@wfassoc-cdylib/src 目录下。或更进一步地查看其依赖的源码,位于@wfassoc/src 目录下。
|
||||||
|
- 该GUI程序需要接受两个必要的命令行参数,请使用Qt内置的命令行解析器进行解析:
|
||||||
|
- `-c`或`--manifest`:指定要配置的应用程序的清单文件。
|
||||||
|
- `-f`或`--for`:指定应用程序要安装到的
|
||||||
|
- 清单文件的样例是@example/manifest/ppic.toml
|
||||||
|
- 在应用程序加载时,或者执行操作时,如果发生错误(例如底层wfassoc发生错误,丢失命令行选项等,则弹出对话框报错,然后立即退出程序)
|
||||||
|
- 程序基本流程:
|
||||||
|
- 接受命令行,检查命令行参数是否合法
|
||||||
|
- 加载命令行指定的manifest文件,并使用sanitizer检查错误。你可以阅读@wfassoc-exec/src/manifest.rs 文件来看看我是如何在Rust中检查它的。
|
||||||
|
- 按照给定的manifest文件,使用wfassoc库构建schema,然后再构建program。
|
||||||
|
- 初始化窗口。
|
||||||
|
- 调用wfassoc program提供的函数,检查应用程序是否安装,设置窗口的安装部分的按钮enable。
|
||||||
|
- 调用wfassoc program提供的函数,遍历所有文件扩展名和关联情况,设置窗口的文件关联表格。
|
||||||
|
- 用户可以在"应用程序"选项卡中注册和卸载应用程序,点击后弹出窗口表示安装或卸载成功,然后检测是否安装,并刷新各个控件的enable状态。
|
||||||
|
- 用户可以在"文件关联"选项卡中设置是否以当前应用程序打开某些扩展名。用户可以点击全选按钮或单元格来进行设置,应用程序暂存修改,等用户点击确认或应用后再应用修改。如果点击的是应用,则刷新当前页面。
|
||||||
|
|
||||||
|
# 额外要求
|
||||||
|
|
||||||
|
- 不要尝试去编译来检查错误。我会安排其他人来检查程序是否能正常运行,并汇报回来,你再修改。
|
||||||
|
- 你不需要关心能否找到Qt,wfassoc和toml11这些库。
|
||||||
|
- 对于wfassoc,你只需要将@wfassoc-cdylib/codegen/Findwfassoc.cmake 复制到@example/qwfassoc/cmake 目录下,并在此目录下编写一个README.md,表明这个文件是从哪里复制来的即可。然后把复制的cmake文件所在目录加入find_package目录,然后使用find_package寻找wfassoc即可。至于去哪里找这个库,我会安排其他人来做。
|
||||||
|
- 对于Qt和toml11我会安排其他人来做,你只需要用find_package来找他们就行,不需要操心能不能找到。
|
||||||
|
- 如果你对某项需求有疑问,请问我,而不是进行猜测。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 总结代码结构示意 (伪代码):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
QDialog *dialog = new QDialog();
|
||||||
|
dialog->setWindowTitle("选项");
|
||||||
|
|
||||||
|
QVBoxLayout *mainLayout = new QVBoxLayout(dialog);
|
||||||
|
|
||||||
|
// 1. Tab Widget
|
||||||
|
QTabWidget *tabWidget = new QTabWidget();
|
||||||
|
tabWidget->addTab(new QWidget(), "系统");
|
||||||
|
tabWidget->addTab(new QWidget(), "7-Zip");
|
||||||
|
// ... 其他 tabs
|
||||||
|
|
||||||
|
// 2. System Tab Content
|
||||||
|
QWidget *systemTab = tabWidget->widget(0);
|
||||||
|
QVBoxLayout *systemLayout = new QVBoxLayout(systemTab);
|
||||||
|
|
||||||
|
// Label
|
||||||
|
QLabel *label = new QLabel("使用 7-Zip 关联的文件类型:");
|
||||||
|
systemLayout->addWidget(label);
|
||||||
|
|
||||||
|
// Buttons (+)
|
||||||
|
QHBoxLayout *btnLayout = new QHBoxLayout();
|
||||||
|
QPushButton *btnPlus1 = new QPushButton("+");
|
||||||
|
QPushButton *btnPlus2 = new QPushButton("+");
|
||||||
|
btnLayout->addWidget(btnPlus1);
|
||||||
|
btnLayout->addWidget(btnPlus2);
|
||||||
|
systemLayout->addLayout(btnLayout);
|
||||||
|
|
||||||
|
// List (TreeWidget)
|
||||||
|
QTreeWidget *treeWidget = new QTreeWidget();
|
||||||
|
treeWidget->setColumnCount(3);
|
||||||
|
treeWidget->setHeaderLabels(QStringList() << "类型" << "yyc12345" << "所有用户");
|
||||||
|
// 添加 items...
|
||||||
|
systemLayout->addWidget(treeWidget);
|
||||||
|
|
||||||
|
// 3. Bottom Buttons
|
||||||
|
QHBoxLayout *bottomLayout = new QHBoxLayout();
|
||||||
|
bottomLayout->addStretch(); // 推挤按钮到右边
|
||||||
|
QPushButton *btnOK = new QPushButton("确定");
|
||||||
|
QPushButton *btnCancel = new QPushButton("取消");
|
||||||
|
QPushButton *btnApply = new QPushButton("应用(A)");
|
||||||
|
btnApply->setEnabled(false); // 禁用
|
||||||
|
QPushButton *btnHelp = new QPushButton("帮助");
|
||||||
|
|
||||||
|
bottomLayout->addWidget(btnOK);
|
||||||
|
bottomLayout->addWidget(btnCancel);
|
||||||
|
bottomLayout->addWidget(btnApply);
|
||||||
|
bottomLayout->addWidget(btnHelp);
|
||||||
|
|
||||||
|
mainLayout->addWidget(tabWidget);
|
||||||
|
mainLayout->addLayout(bottomLayout);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
这是一个标准的 Windows 风格属性对话框,可以通过以下 Qt Widgets 结构来描述:
|
||||||
|
|
||||||
|
### 1. 主窗口容器
|
||||||
|
* 类: `QDialog`。
|
||||||
|
* 标题: "系统属性"。
|
||||||
|
* 布局: 垂直布局 (`QVBoxLayout`)。
|
||||||
|
|
||||||
|
### 2. 顶部选项卡 (Tabs)
|
||||||
|
* 组件: `QTabWidget`。
|
||||||
|
* 标签页: 包含 "计算机名", "硬件", "高级", "系统保护", "远程"。
|
||||||
|
* 当前选中: "硬件" 标签页。
|
||||||
|
|
||||||
|
### 3. "硬件" 选项卡内容
|
||||||
|
该选项卡内部使用垂直布局 (`QVBoxLayout`),包含两个主要的分组区域(视觉上类似 `QGroupBox` 或带有边框的 `QFrame`):
|
||||||
|
|
||||||
|
#### 区域 A: 设备管理器 (上半部分)
|
||||||
|
* 容器: 一个带有边框的容器。
|
||||||
|
* 布局: 水平布局 (`QHBoxLayout`)。
|
||||||
|
* 左侧: 一个 `QLabel` 显示电脑图标。
|
||||||
|
* 中间: 一个 `QLabel` 显示说明文本:"设备管理器列出所有安装在计算机上的硬件设备。请使用设备管理器来更改设备的属性。" (文本需要设置自动换行 `setWordWrap(true)`)。
|
||||||
|
* 右侧/底部: 一个 `QPushButton`,文本为 "设备管理器(D)"。
|
||||||
|
|
||||||
|
#### 区域 B: 设备安装设置 (下半部分)
|
||||||
|
* 容器: 一个带有边框的容器。
|
||||||
|
* 布局: 垂直布局 (`QVBoxLayout`)。
|
||||||
|
* 顶部行: 水平布局 (`QHBoxLayout`)。
|
||||||
|
* 左侧: 一个 `QLabel` 显示列表/勾选图标。
|
||||||
|
* 右侧: 一个 `QLabel` 显示说明文本:"选择 Windows 是否下载制造商提供的可用于你的设备的应用和自定义图标。" (文本需要设置自动换行)。
|
||||||
|
* 底部: 一个 `QPushButton`,文本为 "设备安装设置(S)",靠右对齐。
|
||||||
|
|
||||||
|
### 4. 底部按钮栏
|
||||||
|
* 布局: 水平布局 (`QHBoxLayout`),右对齐 (通常通过 `addStretch()` 实现)。
|
||||||
|
* 组件: 三个 `QPushButton`。
|
||||||
|
* "确定"
|
||||||
|
* "取消"
|
||||||
|
* "应用(A)" (注意:截图中该按钮呈灰色,代码中需设置 `setEnabled(false)`)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
你制定的计划有一些问题,请按照下述标出的问题一一修正:
|
||||||
|
|
||||||
|
- 编写的代码和说明文件需要使用英文注释。
|
||||||
|
- 是manifest而非manifesto,表示清单文件,请修正这个拼写错误。
|
||||||
|
- 我看到你在mainwindow篇章中编写了大量的C++代码,这没有必要。你是计划者而非执行者。你需要把需要在这个头文件中实现什么?该怎么做?需要使用哪些wfassoc函数,这些函数该怎么调用?在哪里查看他们怎么调用?详细的告诉将要执行这些任务的执行者,而不是直接为他们编写好代码。你在manifest部分的任务规划就非常符合这种范式。
|
||||||
|
|
||||||
3
example/qwfassoc/README.md
Normal file
3
example/qwfassoc/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Q WFAssoc
|
||||||
|
|
||||||
|
TODO
|
||||||
1313
example/qwfassoc/TASKS.md
Normal file
1313
example/qwfassoc/TASKS.md
Normal file
File diff suppressed because it is too large
Load Diff
101
example/qwfassoc/cmake/Findwfassoc.cmake
Normal file
101
example/qwfassoc/cmake/Findwfassoc.cmake
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Findwfassoc.cmake
|
||||||
|
# ----------------
|
||||||
|
# Find wfassoc library and headers.
|
||||||
|
#
|
||||||
|
# This module requires the user to set wfassoc_ROOT to the installation
|
||||||
|
# directory of wfassoc. The directory structure under wfassoc_ROOT must be:
|
||||||
|
# bin/ - contains wfassoc_cdylib.dll
|
||||||
|
# include/ - contains wfassoc.h and wfassoc++.h
|
||||||
|
# lib/ - contains wfassoc_cdylib.dll.lib (import library)
|
||||||
|
#
|
||||||
|
# This module defines the following variables:
|
||||||
|
# wfassoc_FOUND - True if wfassoc was found
|
||||||
|
# wfassoc_INCLUDE_DIRS - Path to wfassoc include directory
|
||||||
|
# wfassoc_LIBRARIES - Path to wfassoc import library
|
||||||
|
# wfassoc_DLL - Path to wfassoc DLL
|
||||||
|
# wfassoc_ROOT - The root directory (user-provided)
|
||||||
|
#
|
||||||
|
# This module also creates the following imported targets:
|
||||||
|
# wfassoc::wfassoc - Main wfassoc library (includes both include and link)
|
||||||
|
#
|
||||||
|
|
||||||
|
set(wfassoc_FOUND FALSE)
|
||||||
|
|
||||||
|
# Require user to set wfassoc_ROOT
|
||||||
|
if(NOT wfassoc_ROOT)
|
||||||
|
message(FATAL_ERROR "wfassoc_ROOT must be set to the installation directory of wfassoc")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Check existence of required subdirectories
|
||||||
|
if(NOT EXISTS ${wfassoc_ROOT})
|
||||||
|
message(FATAL_ERROR "wfassoc_ROOT directory does not exist: ${wfassoc_ROOT}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(wfassoc_INCLUDE_DIR ${wfassoc_ROOT}/include)
|
||||||
|
set(wfassoc_LIB_DIR ${wfassoc_ROOT}/lib)
|
||||||
|
set(wfassoc_BIN_DIR ${wfassoc_ROOT}/bin)
|
||||||
|
|
||||||
|
# Find header files
|
||||||
|
if(EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc.h AND EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc++.h)
|
||||||
|
set(wfassoc_INCLUDE_DIRS ${wfassoc_INCLUDE_DIR})
|
||||||
|
else()
|
||||||
|
message(SEND_ERROR "Missing wfassoc header files in ${wfassoc_INCLUDE_DIR}")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Find import library (.lib)
|
||||||
|
find_file(wfassoc_LIBRARIES
|
||||||
|
NAMES wfassoc_cdylib.dll.lib
|
||||||
|
PATHS ${wfassoc_LIB_DIR}
|
||||||
|
NO_DEFAULT_PATH
|
||||||
|
DOC "wfassoc import library"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT wfassoc_LIBRARIES)
|
||||||
|
message(SEND_ERROR "Missing wfassoc import library (wfassoc_cdylib.dll.lib) in ${wfassoc_LIB_DIR}")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Find DLL file
|
||||||
|
find_file(wfassoc_DLL
|
||||||
|
NAMES wfassoc_cdylib.dll
|
||||||
|
PATHS ${wfassoc_BIN_DIR}
|
||||||
|
NO_DEFAULT_PATH
|
||||||
|
DOC "wfassoc dynamic library"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT wfassoc_DLL)
|
||||||
|
message(SEND_ERROR "Missing wfassoc DLL (wfassoc_cdylib.dll) in ${wfassoc_BIN_DIR}")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Everything found
|
||||||
|
set(wfassoc_FOUND TRUE)
|
||||||
|
|
||||||
|
# Mark variables as advanced for ccmake/cmake-gui
|
||||||
|
mark_as_advanced(wfassoc_INCLUDE_DIRS wfassoc_LIBRARIES wfassoc_DLL)
|
||||||
|
|
||||||
|
# Create imported target for wfassoc
|
||||||
|
if(wfassoc_FOUND AND NOT TARGET wfassoc::wfassoc)
|
||||||
|
add_library(wfassoc::wfassoc SHARED IMPORTED)
|
||||||
|
|
||||||
|
# Set include directories
|
||||||
|
set_target_properties(wfassoc::wfassoc PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES ${wfassoc_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set import library location
|
||||||
|
set_target_properties(wfassoc::wfassoc PROPERTIES
|
||||||
|
IMPORTED_IMPLIB "${wfassoc_LIBRARIES}"
|
||||||
|
IMPORTED_LOCATION "${wfassoc_DLL}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Optional: Print status message
|
||||||
|
if(wfassoc_FOUND)
|
||||||
|
message(STATUS "Found wfassoc:")
|
||||||
|
message(STATUS " Root : ${wfassoc_ROOT}")
|
||||||
|
message(STATUS " Include : ${wfassoc_INCLUDE_DIRS}")
|
||||||
|
message(STATUS " Library : ${wfassoc_LIBRARIES}")
|
||||||
|
message(STATUS " DLL : ${wfassoc_DLL}")
|
||||||
|
endif()
|
||||||
5
example/qwfassoc/cmake/README.md
Normal file
5
example/qwfassoc/cmake/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# cmake 模块说明
|
||||||
|
|
||||||
|
此目录下的 `Findwfassoc.cmake` 是从项目根目录 `wfassoc-cdylib/codegen/Findwfassoc.cmake` 复制而来。
|
||||||
|
|
||||||
|
该文件提供 `wfassoc::wfassoc` imported target。使用前需要设置 `wfassoc_ROOT` 变量指向 wfassoc 安装目录。
|
||||||
405
legacy/.gitignore
vendored
405
legacy/.gitignore
vendored
@@ -1,405 +0,0 @@
|
|||||||
# Custom ignore
|
|
||||||
# ignore build directory
|
|
||||||
builds/
|
|
||||||
Debug_MB/
|
|
||||||
Debug_UNICODE/
|
|
||||||
Release_UNICODE/
|
|
||||||
|
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
##
|
|
||||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.rsuser
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Mono auto generated files
|
|
||||||
mono_crash.*
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
[Ww][Ii][Nn]32/
|
|
||||||
[Aa][Rr][Mm]/
|
|
||||||
[Aa][Rr][Mm]64/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
[Ll]ogs/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# Visual Studio 2017 auto generated files
|
|
||||||
Generated\ Files/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUnit
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
nunit-*.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# Benchmark Results
|
|
||||||
BenchmarkDotNet.Artifacts/
|
|
||||||
|
|
||||||
# .NET Core
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
# ASP.NET Scaffolding
|
|
||||||
ScaffoldingReadMe.txt
|
|
||||||
|
|
||||||
# StyleCop
|
|
||||||
StyleCopReport.xml
|
|
||||||
|
|
||||||
# Files built by Visual Studio
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_h.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.iobj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.ipdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*_wpftmp.csproj
|
|
||||||
*.log
|
|
||||||
*.tlog
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
|
||||||
*.e2e
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
|
||||||
.axoCover/*
|
|
||||||
!.axoCover/settings.json
|
|
||||||
|
|
||||||
# Coverlet is a free, cross platform Code Coverage Tool
|
|
||||||
coverage*.json
|
|
||||||
coverage*.xml
|
|
||||||
coverage*.info
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# NuGet Symbol Packages
|
|
||||||
*.snupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/[Pp]ackages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/[Pp]ackages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/[Pp]ackages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
*.appx
|
|
||||||
*.appxbundle
|
|
||||||
*.appxupload
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!?*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Including strong name files can present a security risk
|
|
||||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
|
||||||
#*.snk
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
ServiceFabricBackup/
|
|
||||||
*.rptproj.bak
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
*.rptproj.rsuser
|
|
||||||
*- [Bb]ackup.rdl
|
|
||||||
*- [Bb]ackup ([0-9]).rdl
|
|
||||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
|
||||||
*.vbp
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
|
||||||
*.dsw
|
|
||||||
*.dsp
|
|
||||||
|
|
||||||
# Visual Studio 6 technical files
|
|
||||||
*.ncb
|
|
||||||
*.aps
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# CodeRush personal settings
|
|
||||||
.cr/personal
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Tabs Studio
|
|
||||||
*.tss
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
|
||||||
OpenCover/
|
|
||||||
|
|
||||||
# Azure Stream Analytics local run output
|
|
||||||
ASALocalRun/
|
|
||||||
|
|
||||||
# MSBuild Binary and Structured Log
|
|
||||||
*.binlog
|
|
||||||
|
|
||||||
# NVidia Nsight GPU debugger configuration file
|
|
||||||
*.nvuser
|
|
||||||
|
|
||||||
# MFractors (Xamarin productivity tool) working folder
|
|
||||||
.mfractor/
|
|
||||||
|
|
||||||
# Local History for Visual Studio
|
|
||||||
.localhistory/
|
|
||||||
|
|
||||||
# Visual Studio History (VSHistory) files
|
|
||||||
.vshistory/
|
|
||||||
|
|
||||||
# BeatPulse healthcheck temp database
|
|
||||||
healthchecksdb
|
|
||||||
|
|
||||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
|
||||||
MigrationBackup/
|
|
||||||
|
|
||||||
# Ionide (cross platform F# VS Code tools) working folder
|
|
||||||
.ionide/
|
|
||||||
|
|
||||||
# Fody - auto-generated XML schema
|
|
||||||
FodyWeavers.xsd
|
|
||||||
|
|
||||||
# VS Code files for those working on multiple tools
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
*.code-workspace
|
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
|
||||||
.history/
|
|
||||||
|
|
||||||
# Windows Installer files from build outputs
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msix
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# JetBrains Rider
|
|
||||||
*.sln.iml
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# wfassoc
|
|
||||||
|
|
||||||
**W**indows **F**ile **Assoc**iation Library
|
|
||||||
|
|
||||||
**Work In Progress**
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
* wfassoc: Main library
|
|
||||||
* wfassoc_example: A full example about how to use this library
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 16
|
|
||||||
VisualStudioVersion = 16.0.31702.278
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wfassoc", "wfassoc\wfassoc.vcxproj", "{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}"
|
|
||||||
EndProject
|
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wfassoc_example", "wfassoc_example\wfassoc_example.vcxproj", "{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug_MB|x64 = Debug_MB|x64
|
|
||||||
Debug_MB|x86 = Debug_MB|x86
|
|
||||||
Debug_UNICODE|x64 = Debug_UNICODE|x64
|
|
||||||
Debug_UNICODE|x86 = Debug_UNICODE|x86
|
|
||||||
Release_UNICODE|x64 = Release_UNICODE|x64
|
|
||||||
Release_UNICODE|x86 = Release_UNICODE|x86
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_MB|x64.ActiveCfg = Debug|x64
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_MB|x64.Build.0 = Debug|x64
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_MB|x86.ActiveCfg = Debug|Win32
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_MB|x86.Build.0 = Debug|Win32
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_UNICODE|x64.ActiveCfg = Debug|x64
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_UNICODE|x64.Build.0 = Debug|x64
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_UNICODE|x86.ActiveCfg = Debug|Win32
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_UNICODE|x86.Build.0 = Debug|Win32
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Release_UNICODE|x64.ActiveCfg = Release|x64
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Release_UNICODE|x64.Build.0 = Release|x64
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Release_UNICODE|x86.ActiveCfg = Release|Win32
|
|
||||||
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Release_UNICODE|x86.Build.0 = Release|Win32
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_MB|x64.ActiveCfg = Debug_UNICODE|x64
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_MB|x64.Build.0 = Debug_UNICODE|x64
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_MB|x86.ActiveCfg = Debug_MB|Win32
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_MB|x86.Build.0 = Debug_MB|Win32
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_UNICODE|x64.ActiveCfg = Debug_UNICODE|x64
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_UNICODE|x64.Build.0 = Debug_UNICODE|x64
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_UNICODE|x86.ActiveCfg = Debug_UNICODE|Win32
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_UNICODE|x86.Build.0 = Debug_UNICODE|Win32
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Release_UNICODE|x64.ActiveCfg = Release_UNICODE|x64
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Release_UNICODE|x64.Build.0 = Release_UNICODE|x64
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Release_UNICODE|x86.ActiveCfg = Release_UNICODE|Win32
|
|
||||||
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Release_UNICODE|x86.Build.0 = Release_UNICODE|Win32
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {1AD32362-840E-4399-A7D5-26A0B67A614D}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
LIBRARY wfassoc
|
|
||||||
EXPORTS
|
|
||||||
|
|
||||||
WFInstallApplicationW
|
|
||||||
WFInstallApplicationA
|
|
||||||
WFUninstallApplicationW
|
|
||||||
WFUninstallApplicationA
|
|
||||||
WFGenerateProgIDW
|
|
||||||
WFGenerateProgIDA
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<ItemGroup Label="ProjectConfigurations">
|
|
||||||
<ProjectConfiguration Include="Debug|Win32">
|
|
||||||
<Configuration>Debug</Configuration>
|
|
||||||
<Platform>Win32</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Release|Win32">
|
|
||||||
<Configuration>Release</Configuration>
|
|
||||||
<Platform>Win32</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Debug|x64">
|
|
||||||
<Configuration>Debug</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Release|x64">
|
|
||||||
<Configuration>Release</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
</ItemGroup>
|
|
||||||
<PropertyGroup Label="Globals">
|
|
||||||
<VCProjectVersion>16.0</VCProjectVersion>
|
|
||||||
<Keyword>Win32Proj</Keyword>
|
|
||||||
<ProjectGuid>{4aac8f0c-3e1c-4584-b682-05bbf96a813f}</ProjectGuid>
|
|
||||||
<RootNamespace>wfassoc</RootNamespace>
|
|
||||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
|
||||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
|
||||||
<UseDebugLibraries>true</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v142</PlatformToolset>
|
|
||||||
<CharacterSet>NotSet</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
|
||||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
|
||||||
<UseDebugLibraries>false</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v142</PlatformToolset>
|
|
||||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
|
||||||
<CharacterSet>NotSet</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>true</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v142</PlatformToolset>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>false</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v142</PlatformToolset>
|
|
||||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
|
||||||
<ImportGroup Label="ExtensionSettings">
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="Shared">
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<PropertyGroup Label="UserMacros" />
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
|
||||||
<LinkIncremental>true</LinkIncremental>
|
|
||||||
<OutDir>$(SolutionDir)builds\Debug\</OutDir>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
|
||||||
<LinkIncremental>false</LinkIncremental>
|
|
||||||
<OutDir>$(SolutionDir)builds\Release\</OutDir>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
|
||||||
<LinkIncremental>true</LinkIncremental>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
|
||||||
<LinkIncremental>false</LinkIncremental>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
|
||||||
<ClCompile>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
<CompileAs>CompileAsC</CompileAs>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
<ModuleDefinitionFile>wfassoc.def</ModuleDefinitionFile>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
|
||||||
<ClCompile>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
<CompileAs>CompileAsC</CompileAs>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
|
||||||
<OptimizeReferences>true</OptimizeReferences>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
<ModuleDefinitionFile>wfassoc.def</ModuleDefinitionFile>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
|
||||||
<ClCompile>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
<ModuleDefinitionFile>wfassoc.def</ModuleDefinitionFile>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
|
||||||
<ClCompile>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
|
||||||
<OptimizeReferences>true</OptimizeReferences>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
<ModuleDefinitionFile>wfassoc.def</ModuleDefinitionFile>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="wfassoc_utils.h" />
|
|
||||||
<ClInclude Include="wfassoc_core.h" />
|
|
||||||
<ClInclude Include="wfassoc_private.h" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClCompile Include="wfassoc_private.c" />
|
|
||||||
<ClCompile Include="wfassoc_utils.c" />
|
|
||||||
<ClCompile Include="wfassoc_core.c" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="wfassoc.def" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
|
||||||
<ImportGroup Label="ExtensionTargets">
|
|
||||||
</ImportGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<ItemGroup>
|
|
||||||
<Filter Include="源文件">
|
|
||||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
|
||||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="头文件">
|
|
||||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
|
||||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="资源文件">
|
|
||||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
|
||||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
|
||||||
</Filter>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="wfassoc_core.h">
|
|
||||||
<Filter>头文件</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="wfassoc_utils.h">
|
|
||||||
<Filter>头文件</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="wfassoc_private.h">
|
|
||||||
<Filter>头文件</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClCompile Include="wfassoc_core.c">
|
|
||||||
<Filter>源文件</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="wfassoc_utils.c">
|
|
||||||
<Filter>源文件</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="wfassoc_private.c">
|
|
||||||
<Filter>源文件</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="wfassoc.def">
|
|
||||||
<Filter>源文件</Filter>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,407 +0,0 @@
|
|||||||
#include "wfassoc_core.h"
|
|
||||||
#include <strsafe.h>
|
|
||||||
#include <ShlObj.h>
|
|
||||||
|
|
||||||
// private function and variable declearions.
|
|
||||||
// the function with _AL tail mean that the value returned by function is allocated from heap and should be free manually.
|
|
||||||
// otherwise, the tail of _NAL mean this function will not allocate any new memeory.
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert multi byte string to wide char string
|
|
||||||
/// Notice: this function will allocate memory for returns and it should be released safely.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The string will be converted</param>
|
|
||||||
/// <param name="error">The error happend during converting</param>
|
|
||||||
/// <returns>The string has been converted. If return NULL, it mean that the converting failed.</returns>
|
|
||||||
//WFERROR ConvMultiByteToWideChar(CHAR* source);
|
|
||||||
WFERROR WFSplitAppPath(WFString* app_path, WFString* app_name, WFString* base_path);
|
|
||||||
//WFERROR Strcat(WCHAR* str1, WCHAR* str2);
|
|
||||||
//WFERROR WFGetBasePathFromAppPath(WFString* app_path, WFString* base_path);
|
|
||||||
WFERROR WFSplitSupportedTypesString(wchar_t* typesString, WFLinkedList* list);
|
|
||||||
|
|
||||||
void WFPrintflnInDebug();
|
|
||||||
|
|
||||||
// some effective reg function
|
|
||||||
LSTATUS WFRegOpenKeyWithCreation(HKEY hkey, LPCWSTR lpSubKey, PHKEY phkResult);
|
|
||||||
LSTATUS WFRegSetStringValue(HKEY hkey, LPCWSTR lpValueName, const WCHAR* data);
|
|
||||||
|
|
||||||
#define LEGACY_PROGID_FORMAT L"%s.%s.%d"
|
|
||||||
#define WFALLOC(type,count) (type*)malloc(sizeof(type)*count);
|
|
||||||
#define SAFE_EXEC_WIN32(wfError, recvError, skipLabel, function) recvError=function; if(recvError!=ERROR_SUCCESS) {wfError = WFERROR_WIN32;goto skipLabel;}
|
|
||||||
#define SAFE_EXEC_WF_LABEL(wfError, skipLabel, function) if((wfError=function)!=WFERROR_OK) {goto skipLabel;}
|
|
||||||
#define SAFE_EXEC_WF_RETURN(wfError, function) if((wfError=function)!=WFERROR_OK) {return wfError;}
|
|
||||||
#define SAFE_EXEC_STRALLOC(wfError, skipLabel, vStr, function) vStr=function; if(vStr==NULL) {wfError = WFERROR_ALLOC;goto skipLabel;}
|
|
||||||
#define SAFE_FREE_PTR(obj) if(obj!=NULL){free(obj);obj=NULL;}
|
|
||||||
#define SAFE_FREE_HKEY(hkey) if(hkey!=NULL&&hkey!=INVALID_HANDLE_VALUE){RegCloseKey(hkey);hkey=NULL;}
|
|
||||||
|
|
||||||
// public function implements.
|
|
||||||
|
|
||||||
WFERROR WFInstallApplication(WFAPP_PROFILE* profile) {
|
|
||||||
if (profile->WFVersion != WFVERSION) return WFERROR_INVALID_VERSION;
|
|
||||||
|
|
||||||
WFERROR wf_error = WFERROR_OK;
|
|
||||||
LSTATUS win32_error = ERROR_SUCCESS;
|
|
||||||
//WCHAR* itemSupportedTypes = NULL;
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// generate necessary data
|
|
||||||
|
|
||||||
// init string
|
|
||||||
WFString* strProgID = NULL,
|
|
||||||
* strAppPath = NULL,
|
|
||||||
* strAppBasePath = NULL,
|
|
||||||
* strAppFileName = NULL;
|
|
||||||
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFString_Alloc(&strProgID)
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFString_Alloc_Wchar(&strAppPath, profile->AppPath)
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFString_Alloc(&strAppBasePath)
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFString_Alloc(&strAppFileName)
|
|
||||||
);
|
|
||||||
|
|
||||||
// write string
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFString_Printf(strProgID, LEGACY_PROGID_FORMAT, profile->ProgID_Vendor, profile->ProgID_Component, profile->ProgID_Version)
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFSplitAppPath(strAppPath, strAppFileName, strAppBasePath)
|
|
||||||
);
|
|
||||||
|
|
||||||
WFString* regpathAppPaths = NULL,
|
|
||||||
* regpathApplicationsRealName = NULL,
|
|
||||||
* regpathApplicationsProgId = NULL;
|
|
||||||
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFString_Alloc_Wchar(®pathAppPaths, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\")
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFString_Concat_String(regpathAppPaths, strAppFileName)
|
|
||||||
);
|
|
||||||
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFString_Alloc_Wchar(®pathApplicationsRealName, L"SOFTWARE\\Classes\\Applications\\")
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFString_Concat_String(regpathApplicationsRealName, strAppFileName)
|
|
||||||
);
|
|
||||||
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFString_Alloc_Wchar(®pathApplicationsProgId, L"SOFTWARE\\Classes\\Applications\\")
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WF_LABEL(wf_error, final_process,
|
|
||||||
WFString_Concat_String(regpathApplicationsProgId, strProgID)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
//WCHAR* strAppPath = NULL,
|
|
||||||
// * strPath = NULL,
|
|
||||||
// * strApplications = NULL,
|
|
||||||
// * strProgID = NULL,
|
|
||||||
// * strOpenWithList = NULL;
|
|
||||||
//SAFE_EXEC_STRALLOC(error, final_process,
|
|
||||||
// strPath, GetPathFromAppPath_AL(profile->AppPath)
|
|
||||||
//);
|
|
||||||
//SAFE_EXEC_STRALLOC(error, final_process,
|
|
||||||
// strAppPath, Strcat_AL(
|
|
||||||
// L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\",
|
|
||||||
// GetAppNameFromAppPath_NAL(profile->AppPath)
|
|
||||||
//)
|
|
||||||
//);
|
|
||||||
//SAFE_EXEC_STRALLOC(error, final_process,
|
|
||||||
// strProgID, Strcat_AL(
|
|
||||||
// L"SOFTWARE\\Classes\\Applications\\",
|
|
||||||
// profile->ProgID
|
|
||||||
//)
|
|
||||||
//);
|
|
||||||
//SAFE_EXEC_STRALLOC(error, final_process,
|
|
||||||
// strApplications, Strcat_AL(
|
|
||||||
// L"SOFTWARE\\Classes\\Applications\\",
|
|
||||||
// GetAppNameFromAppPath_NAL(profile->AppPath)
|
|
||||||
//)
|
|
||||||
//);
|
|
||||||
|
|
||||||
// generate necessary HKEY
|
|
||||||
HKEY nodeAppPath = NULL,
|
|
||||||
nodeApplications = NULL,
|
|
||||||
nodeApplications_Verb = NULL,
|
|
||||||
nodeApplications_SupportedTypes = NULL,
|
|
||||||
nodeProgID = NULL,
|
|
||||||
nodeProgID_Verb = NULL,
|
|
||||||
nodeClasses = NULL,
|
|
||||||
nodeExt = NULL,
|
|
||||||
nodeExt_OpenWithProgIds = NULL,
|
|
||||||
nodeExt_OpenWithList = NULL;
|
|
||||||
|
|
||||||
// register in `App Paths`
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegOpenKeyWithCreation(profile->RegisterForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, strAppPath, &nodeAppPath)
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegOpenKeyWithCreation(profile->RegisterForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, strApplications, &nodeApplications)
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegOpenKeyWithCreation(nodeApplications, L"shell\\open\\command", &nodeApplications_Verb)
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegOpenKeyWithCreation(nodeApplications, L"SupportedTypes", &nodeApplications_SupportedTypes)
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegOpenKeyWithCreation(profile->RegisterForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, strProgID, &nodeProgID)
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegOpenKeyWithCreation(nodeProgID, L"shell\\open\\command", &nodeProgID_Verb)
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegOpenKeyWithCreation(profile->RegisterForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"SOFTWARE\\Classes", &nodeClasses)
|
|
||||||
);
|
|
||||||
|
|
||||||
// operate HKEY
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegSetStringValue(nodeAppPath, NULL, profile->AppPath) // visit Default key
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegSetStringValue(nodeAppPath, L"Path", strPath)
|
|
||||||
);
|
|
||||||
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegSetStringValue(nodeApplications_Verb, NULL, profile->AppCommand) // visit Default key
|
|
||||||
);
|
|
||||||
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegSetStringValue(nodeProgID_Verb, NULL, profile->AppCommand) // visit Default key
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
while ((itemSupportedTypes = IterateSupportedTypesString_NAL(profile->SupportedTypes, itemSupportedTypes)) != NULL) {
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegSetStringValue(nodeApplications_SupportedTypes, itemSupportedTypes, NULL) // register supported type with blank item
|
|
||||||
);
|
|
||||||
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegOpenKeyWithCreation(nodeClasses, itemSupportedTypes, &nodeExt)
|
|
||||||
);
|
|
||||||
if (profile->SetAsDefault) {
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegSetStringValue(nodeExt, NULL, profile->ProgID) // visit Default key
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (profile->ShowInOpenWithMenu) {
|
|
||||||
if (profile->UseOpenWithList) {
|
|
||||||
// use Windows XP node
|
|
||||||
SAFE_EXEC_STRALLOC(error, final_process,
|
|
||||||
strOpenWithList, Strcat_AL(
|
|
||||||
L"OpenWithList\\",
|
|
||||||
GetAppNameFromAppPath_NAL(profile->AppPath)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegOpenKeyWithCreation(nodeExt, itemSupportedTypes, &nodeExt_OpenWithList);
|
|
||||||
);
|
|
||||||
|
|
||||||
SAFE_FREE_PTR(strOpenWithList);
|
|
||||||
SAFE_FREE_HKEY(nodeExt_OpenWithList);
|
|
||||||
} else {
|
|
||||||
// use Windows Vista node
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegOpenKeyWithCreation(nodeExt, itemSupportedTypes, &nodeExt_OpenWithProgIds);
|
|
||||||
);
|
|
||||||
SAFE_EXEC_WIN32(error, win32_error, final_process,
|
|
||||||
WFRegSetStringValue(nodeExt_OpenWithProgIds, profile->ProgID, NULL)
|
|
||||||
);
|
|
||||||
|
|
||||||
SAFE_FREE_HKEY(nodeExt_OpenWithProgIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SAFE_FREE_HKEY(nodeExt);
|
|
||||||
}
|
|
||||||
|
|
||||||
final_process:
|
|
||||||
// free HKEY and strings
|
|
||||||
SAFE_FREE_HKEY(nodeAppPath);
|
|
||||||
SAFE_FREE_HKEY(nodeApplications);
|
|
||||||
SAFE_FREE_HKEY(nodeApplications_Verb);
|
|
||||||
SAFE_FREE_HKEY(nodeApplications_SupportedTypes);
|
|
||||||
SAFE_FREE_HKEY(nodeProgID);
|
|
||||||
SAFE_FREE_HKEY(nodeProgID_Verb);
|
|
||||||
SAFE_FREE_HKEY(nodeClasses);
|
|
||||||
SAFE_FREE_HKEY(nodeExt);
|
|
||||||
SAFE_FREE_HKEY(nodeExt_OpenWithProgIds);
|
|
||||||
SAFE_FREE_HKEY(nodeExt_OpenWithList);
|
|
||||||
|
|
||||||
SAFE_FREE_PTR(strAppPath);
|
|
||||||
SAFE_FREE_PTR(strPath);
|
|
||||||
SAFE_FREE_PTR(strApplications);
|
|
||||||
SAFE_FREE_PTR(strProgID);
|
|
||||||
|
|
||||||
// order uninstall to wipe out all written data
|
|
||||||
// if function failed
|
|
||||||
if (error != WFERROR_OK)
|
|
||||||
WFUninstallApplicationW(profile);
|
|
||||||
|
|
||||||
// active changes
|
|
||||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
|
|
||||||
|
|
||||||
// return error
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
//WFERROR WFInstallApplicationA(WFAPP_PROFILEA* profile) {
|
|
||||||
// return WFERROR_OK;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//WFERROR WFUninstallApplicationW(WFAPP_PROFILEW* profile) {
|
|
||||||
// if (profile->WFVersion != WFVERSION) return WFERROR_INVALID_VERSION;
|
|
||||||
//
|
|
||||||
// return WFERROR_OK;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//WFERROR WFUninstallApplicationA(WFAPP_PROFILEA* profile) {
|
|
||||||
// return WFERROR_OK;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//WFERROR WFGenerateProgIDW(WCHAR* vendor, WCHAR* component, INT version, WCHAR* result, INT* result_length) {
|
|
||||||
// if (result_length == NULL) return WFERROR_NULLPTR;
|
|
||||||
// if (result == NULL) {
|
|
||||||
// *result_length = _snwprintf(NULL, 0, LEGACY_PROGID_FORMATW, vendor, component, version) + 1;
|
|
||||||
// } else {
|
|
||||||
// int write_result = _snwprintf(result, *result_length, LEGACY_PROGID_FORMATW, vendor, component, version);
|
|
||||||
// if (write_result < 0 || write_result >= *result_length) return WFERROR_INSUFFICIENT_BUFFER;
|
|
||||||
// }
|
|
||||||
// return WFERROR_OK;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//WFERROR WFGenerateProgIDA(CHAR* vendor, CHAR* component, INT version, CHAR* result, INT* result_length) {
|
|
||||||
// if (result_length == NULL) return WFERROR_NULLPTR;
|
|
||||||
// if (result == NULL) {
|
|
||||||
// *result_length = _snprintf(NULL, 0, LEGACY_PROGID_FORMATA, vendor, component, version) + 1;
|
|
||||||
// } else {
|
|
||||||
// int write_result = _snprintf(result, *result_length, LEGACY_PROGID_FORMATA, vendor, component, version);
|
|
||||||
// if (write_result < 0 || write_result >= *result_length) return WFERROR_INSUFFICIENT_BUFFER;
|
|
||||||
// }
|
|
||||||
// return WFERROR_OK;
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
// private function and variable implements.
|
|
||||||
|
|
||||||
//WCHAR* ConvMultiByteToWideChar_AL(CHAR* source) {
|
|
||||||
// WCHAR* dest = NULL;
|
|
||||||
// size_t sourceLength = strlen(source);
|
|
||||||
//
|
|
||||||
// int destLength = MultiByteToWideChar(CP_ACP, 0, source, sourceLength, NULL, 0);
|
|
||||||
// if (destLength <= 0) return NULL;
|
|
||||||
// destLength += 10;
|
|
||||||
//
|
|
||||||
// dest = WFALLOC(WCHAR, destLength);
|
|
||||||
// if (dest == NULL) return NULL;
|
|
||||||
//
|
|
||||||
// memset(dest, 0, sizeof(WCHAR) * destLength);
|
|
||||||
// int error = MultiByteToWideChar(CP_ACP, 0, source, sourceLength, dest, destLength);
|
|
||||||
// if (error <= 0) {
|
|
||||||
// free(dest);
|
|
||||||
// return NULL;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return dest;
|
|
||||||
//}
|
|
||||||
|
|
||||||
WFERROR WFSplitAppPath(WFString* app_path, WFString* app_name, WFString* base_path) {
|
|
||||||
WFERROR ec;
|
|
||||||
wchar_t* lastSlash, *src, *ptr;
|
|
||||||
SAFE_EXEC_WF_RETURN(ec, WFString_GetData(app_path, &ptr));
|
|
||||||
src = lastSlash = ptr;
|
|
||||||
|
|
||||||
while (*ptr != L'\0') {
|
|
||||||
if (*ptr == L'\\' || *ptr == L'/') {
|
|
||||||
lastSlash = ptr;
|
|
||||||
}
|
|
||||||
ptr++;
|
|
||||||
}
|
|
||||||
|
|
||||||
SAFE_EXEC_WF_RETURN(ec, WFString_SubString(app_path, base_path, 0, lastSlash - src));
|
|
||||||
SAFE_EXEC_WF_RETURN(ec, WFString_SetData(app_name, ++lastSlash));
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
//WCHAR* Strcat_AL(WCHAR* str1, WCHAR* str2) {
|
|
||||||
// size_t length = wcslen(str1) + wcslen(str2) + 10;
|
|
||||||
//
|
|
||||||
// WCHAR* dest = NULL;
|
|
||||||
// dest = WFALLOC(WCHAR, length);
|
|
||||||
// if (dest == NULL) return NULL;
|
|
||||||
//
|
|
||||||
// wcscpy(dest, str1);
|
|
||||||
// wcscat(dest, str2);
|
|
||||||
// return dest;
|
|
||||||
//}
|
|
||||||
|
|
||||||
WFERROR WFSplitSupportedTypesString(wchar_t* typesString, WFLinkedList* list) {
|
|
||||||
WFERROR ec;
|
|
||||||
wchar_t* ptr;
|
|
||||||
WFString* strl;
|
|
||||||
uint32_t len_str;
|
|
||||||
ptr = typesString;
|
|
||||||
|
|
||||||
while (*ptr != L'\0') {
|
|
||||||
// add into list
|
|
||||||
SAFE_EXEC_WF_RETURN(ec, WFString_Alloc(&strl, ptr));
|
|
||||||
SAFE_EXEC_WF_RETURN(ec, WFLinkedList_Add(list, strl));
|
|
||||||
|
|
||||||
// shift to next string
|
|
||||||
SAFE_EXEC_WF_RETURN(ec, WFString_GetLength(strl, &len_str));
|
|
||||||
ptr += len_str + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
//WCHAR* IterateSupportedTypesString_NAL(WCHAR* typesString, WCHAR* prev) {
|
|
||||||
// if (typesString == NULL || *typesString == L'\0') return NULL;
|
|
||||||
// if (prev == NULL) return typesString;
|
|
||||||
//
|
|
||||||
// // skip prev string
|
|
||||||
// while (*prev != L'\0') {
|
|
||||||
// prev++;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // if the next string is start with zero, it mean that the full string is over and return NULL to terminate analyse.
|
|
||||||
// // otherwise return next string
|
|
||||||
// prev++;
|
|
||||||
// if (*prev == L'\0') return NULL;
|
|
||||||
// else return prev;
|
|
||||||
//}
|
|
||||||
|
|
||||||
void WFPrintflnInDebug() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
LSTATUS WFRegOpenKeyWithCreation(HKEY hkey, LPCWSTR lpSubKey, PHKEY phkResult) {
|
|
||||||
return RegCreateKeyExW(
|
|
||||||
hkey,
|
|
||||||
lpSubKey,
|
|
||||||
0,
|
|
||||||
NULL,
|
|
||||||
REG_OPTION_NON_VOLATILE,
|
|
||||||
KEY_ALL_ACCESS,
|
|
||||||
NULL,
|
|
||||||
phkResult,
|
|
||||||
NULL
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
LSTATUS WFRegSetStringValue(HKEY hkey, LPCWSTR lpValueName, const WCHAR* data) {
|
|
||||||
return RegSetValueExW(
|
|
||||||
hkey,
|
|
||||||
lpValueName,
|
|
||||||
0,
|
|
||||||
REG_SZ,
|
|
||||||
data,
|
|
||||||
data == NULL ? 0 : ((wcslen(data) + 1) * sizeof(WCHAR))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
#if !defined(_YYCDLL_WFASSOC_H__IMPORTED_)
|
|
||||||
#define _YYCDLL_WFASSOC_H__IMPORTED_
|
|
||||||
|
|
||||||
#include <Windows.h>
|
|
||||||
#include <sal.h>
|
|
||||||
#include "wfassoc_utils.h"
|
|
||||||
|
|
||||||
// quick marco for developer and should not be used in wfassoc self
|
|
||||||
|
|
||||||
//#if defined(_UNICODE)
|
|
||||||
//#define WFAPP_PROFILE WFAPP_PROFILEW
|
|
||||||
//#define WFInstallApplication WFInstallApplicationW
|
|
||||||
//#define WFUninstallApplication WFUninstallApplicationW
|
|
||||||
//#define WFGenerateProgID WFGenerateProgIDW
|
|
||||||
//#elif defined(_MBCS)
|
|
||||||
//#define WFAPP_PROFILE WFAPP_PROFILEA
|
|
||||||
//#define WFInstallApplication WFInstallApplicationA
|
|
||||||
//#define WFUninstallApplication WFUninstallApplicationA
|
|
||||||
//#define WFGenerateProgID WFGenerateProgIDA
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Install Application via Wide Character
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="profile"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
WFERROR WFInstallApplication(WFAPP_PROFILE* profile);
|
|
||||||
WFERROR WFUninstallApplication(WFAPP_PROFILE* profile);
|
|
||||||
|
|
||||||
WFERROR WFRegisterAppPath(WFAPP_INTERNAL_PROFILE* internal_profile);
|
|
||||||
WFERROR WFRegisterApplication(WFAPP_INTERNAL_PROFILE* internal_profile);
|
|
||||||
WFERROR WFRegisterExtensions(WFAPP_INTERNAL_PROFILE* internal_profile);
|
|
||||||
|
|
||||||
//WFERROR WFProfile_Alloc(WFAPP_PROFILE** profile);
|
|
||||||
//WFERROR WFProfile_Free(WFAPP_PROFILE* profile);
|
|
||||||
//WFERROR WFProfile_SetProgIDA(WFAPP_PROFILE** profile, char* vendor, char* component, uint32_t version, BOOL is_utf8);
|
|
||||||
//WFERROR WFProfile_SetProgIDW(WFAPP_PROFILE** profile, wchar_t* vendor, wchar_t* component, uint32_t version);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate Legacy ProgID
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vendor">Vendor. Such as `Word`, `Excel`, `PowerPoint`.</param>
|
|
||||||
/// <param name="component">Component. Such as `Document`, `Sheet`, `Diagram`.</param>
|
|
||||||
/// <param name="version">Version. Such as `0`, `1`, `114514`.</param>
|
|
||||||
/// <param name="result">Pointer to output ProgID. If this variable is NULL, function will calculate proper length of receiving buffer and return it via `result_length`.</param>
|
|
||||||
/// <param name="result_length">Pointer to a int variable containing buffer's length. If `result` is not NULL, it should be the length of `result`.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
//WFERROR WFGenerateProgIDW(WCHAR* vendor, WCHAR* component, INT version, WCHAR* result, INT* result_length);
|
|
||||||
/// <summary>
|
|
||||||
/// Generate Legacy ProgID
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vendor">Vendor. Such as `Word`, `Excel`, `PowerPoint`.</param>
|
|
||||||
/// <param name="component">Component. Such as `Document`, `Sheet`, `Diagram`.</param>
|
|
||||||
/// <param name="version">Version. Such as `0`, `1`, `114514`.</param>
|
|
||||||
/// <param name="result">Pointer to output ProgID. If this variable is NULL, function will calculate proper length of receiving buffer and return it via `result_length`.</param>
|
|
||||||
/// <param name="result_length">Pointer to a int variable containing buffer's length. If `result` is not NULL, it should be the length of `result`.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
//WFERROR WFGenerateProgIDA(CHAR* vendor, CHAR* component, INT version, CHAR* result, INT* result_length);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#include "wfassoc_private.h"
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#if !defined(_YYCDLL_WFASSOC_PRIVATE_H__IMPORTED_)
|
|
||||||
#define _YYCDLL_WFASSOC_PRIVATE_H__IMPORTED_
|
|
||||||
|
|
||||||
typedef struct _WFAPP_RAWDATA {
|
|
||||||
uint32_t mVersion;
|
|
||||||
|
|
||||||
}WFAPP_RAWDATA;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
#include "wfassoc_utils.h"
|
|
||||||
|
|
||||||
#pragma region WFString
|
|
||||||
|
|
||||||
WFERROR WFString_Alloc_Wchar(WFString** strl, const wchar_t* raw_data) {
|
|
||||||
if (raw_data == NULL) return WFERROR_NULLPTR;
|
|
||||||
|
|
||||||
WFERROR ec;
|
|
||||||
if ((ec = WFString_Alloc(strl)) != WFERROR_OK) return ec;
|
|
||||||
if ((ec = WFString_SetData(*strl, raw_data)) != WFERROR_OK) return ec;
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFString_Alloc_Char(WFString** strl, const char* raw_data, BOOL is_utf8) {
|
|
||||||
if (raw_data == NULL) return WFERROR_NULLPTR;
|
|
||||||
|
|
||||||
// init string
|
|
||||||
WFERROR ec;
|
|
||||||
if ((ec = WFString_Alloc(strl)) != WFERROR_OK) return ec;
|
|
||||||
|
|
||||||
// compute expected string length
|
|
||||||
uint32_t sourceLength = strlen(raw_data);
|
|
||||||
int destLength = MultiByteToWideChar(is_utf8 ? CP_UTF8 : CP_ACP, 0, raw_data, sourceLength, NULL, 0);
|
|
||||||
if (destLength <= 0) return WFERROR_CRT;
|
|
||||||
--destLength; // remove terminal char
|
|
||||||
|
|
||||||
// resize string
|
|
||||||
if ((ec = WFString_Resize(*strl, destLength)) != WFERROR_OK) return ec;
|
|
||||||
|
|
||||||
// clear buffer and write data
|
|
||||||
wchar_t* buffer;
|
|
||||||
if ((ec = WFString_GetData(*strl, &buffer)) != WFERROR_OK) return ec;
|
|
||||||
memset(buffer, 0, sizeof(WCHAR) * destLength);
|
|
||||||
int crt_error = MultiByteToWideChar(is_utf8 ? CP_UTF8 : CP_ACP, 0, raw_data, sourceLength, buffer, destLength);
|
|
||||||
if (crt_error <= 0) return WFERROR_CRT;
|
|
||||||
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFString_Alloc_Capacity(WFString** strl, uint32_t size) {
|
|
||||||
*strl = WFNEW(WFString);
|
|
||||||
if (*strl == NULL) return WFERROR_ALLOC;
|
|
||||||
|
|
||||||
// init struct data
|
|
||||||
(*strl)->mCapacity = size;
|
|
||||||
(*strl)->mRealCapacity = size + 1;
|
|
||||||
(*strl)->mLength = 0;
|
|
||||||
(*strl)->mRealLength = 1;
|
|
||||||
|
|
||||||
(*strl)->mRawData = malloc((*strl)->mRealCapacity * sizeof(wchar_t));
|
|
||||||
if ((*strl)->mRawData == NULL) return WFERROR_ALLOC;
|
|
||||||
|
|
||||||
(*strl)->mRawData[(*strl)->mRealLength - 1] = 0;
|
|
||||||
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFString_Alloc(WFString** strl) {
|
|
||||||
return WFString_Alloc_Capacity(strl, 256);
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFString_Free(WFString* strl) {
|
|
||||||
if (strl == NULL) return WFERROR_OK;
|
|
||||||
if (strl->mRawData == NULL) return WFERROR_NULLPTR;
|
|
||||||
free(strl->mRawData);
|
|
||||||
free(strl);
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFString_Resize(WFString* strl, uint32_t new_size) {
|
|
||||||
// if remain data is not enough, we need alloc new one
|
|
||||||
if (new_size >= strl->mCapacity) {
|
|
||||||
strl->mCapacity = new_size * 2;
|
|
||||||
strl->mRealCapacity = strl->mCapacity + 1;
|
|
||||||
|
|
||||||
// alloc buffer
|
|
||||||
if (strl->mRawData != NULL)
|
|
||||||
strl->mRawData = realloc(strl->mRawData, strl->mRealCapacity * sizeof(wchar_t));
|
|
||||||
else
|
|
||||||
strl->mRawData = malloc(strl->mRealCapacity * sizeof(wchar_t));
|
|
||||||
if (strl->mRawData == NULL) return WFERROR_ALLOC;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set length as new length
|
|
||||||
strl->mLength = new_size;
|
|
||||||
strl->mRealLength = strl->mLength + 1;
|
|
||||||
|
|
||||||
// set the last one is zero
|
|
||||||
strl->mRawData[strl->mRealLength - 1] = 0;
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFString_GetData(WFString* strl, wchar_t** pdata) {
|
|
||||||
if (strl == NULL) return WFERROR_NULLPTR;
|
|
||||||
*pdata = strl->mRawData;
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFString_SetData(WFString* strl, const wchar_t* data) {
|
|
||||||
if (strl == NULL || data == NULL) return WFERROR_NULLPTR;
|
|
||||||
|
|
||||||
// get length and resize buffer
|
|
||||||
WFERROR ec;
|
|
||||||
uint32_t size = wcslen(data);
|
|
||||||
ec = WFString_Resize(strl, size);
|
|
||||||
if (ec != WFERROR_OK) return ec;
|
|
||||||
|
|
||||||
// copy data
|
|
||||||
if (size != 0)
|
|
||||||
memcpy(strl->mRawData, data, sizeof(wchar_t) * size);
|
|
||||||
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFString_GetLength(WFString* strl, uint32_t* len) {
|
|
||||||
if (strl == NULL) return WFERROR_NULLPTR;
|
|
||||||
*len = strl->mLength;
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFString_Printf(WFString* strl, const wchar_t* format, ...) {
|
|
||||||
if (strl == NULL) return WFERROR_NULLPTR;
|
|
||||||
|
|
||||||
// get expected size
|
|
||||||
va_list argptr;
|
|
||||||
va_start(argptr, format);
|
|
||||||
uint32_t count = _vsnwprintf(NULL, 0, format, argptr);
|
|
||||||
//count++;
|
|
||||||
va_end(argptr);
|
|
||||||
|
|
||||||
// resize string and get buffer ptr
|
|
||||||
WFERROR ec;
|
|
||||||
wchar_t* buffer;
|
|
||||||
if ((ec = WFString_Resize(strl, count)) != WFERROR_OK) return ec;
|
|
||||||
if ((ec = WFString_GetData(strl, &buffer)) != WFERROR_OK) return ec;
|
|
||||||
|
|
||||||
// write data to buffer
|
|
||||||
buffer[count - 1] = L'\0';
|
|
||||||
va_start(argptr, format);
|
|
||||||
int write_result = _vsnwprintf(buffer, count, format, argptr);
|
|
||||||
va_end(argptr);
|
|
||||||
if (write_result < 0 || write_result >= count) return WFERROR_CRT;
|
|
||||||
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFString_Concat_Wchar(WFString* strl, const wchar_t* extra) {
|
|
||||||
if (strl == NULL) return WFERROR_NULLPTR;
|
|
||||||
if (extra == NULL) return WFERROR_OK;
|
|
||||||
|
|
||||||
uint32_t count = wcslen(extra);
|
|
||||||
uint32_t oldlen = strl->mLength;
|
|
||||||
|
|
||||||
// if extra strl count is 0, do not copy any data.
|
|
||||||
if (count == 0) return WFERROR_OK;
|
|
||||||
|
|
||||||
WFERROR ec;
|
|
||||||
if ((ec = WFString_Resize(strl, oldlen + count)) != WFERROR_OK) return ec;
|
|
||||||
memcpy(strl->mRawData + oldlen, extra, sizeof(wchar_t) * count);
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
WFERROR WFString_Concat_String(WFString* strl, WFString* extra) {
|
|
||||||
if (strl == NULL) return WFERROR_NULLPTR;
|
|
||||||
if (extra == NULL) return WFERROR_OK;
|
|
||||||
|
|
||||||
return WFString_Concat_Wchar(strl, extra->mRawData);
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFString_SubString(WFString* strl, WFString* substring, uint32_t start_index, uint32_t length) {
|
|
||||||
if (strl == NULL) return WFERROR_NULLPTR;
|
|
||||||
if (start_index >= strl->mLength || length > strl->mLength - start_index) return WFERROR_INVALID_ARGUMENTS;
|
|
||||||
|
|
||||||
WFERROR ec;
|
|
||||||
if ((ec = WFString_Resize(substring, length)) != WFERROR_OK) return ec;
|
|
||||||
if (length != 0) {
|
|
||||||
memcpy(substring->mRawData, strl->mRawData + start_index, sizeof(wchar_t) * length);
|
|
||||||
}
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
#pragma region WFLinkedList
|
|
||||||
|
|
||||||
WFERROR WFLinkedList_Alloc(WFLinkedList** list) {
|
|
||||||
*list = WFNEW(WFLinkedList);
|
|
||||||
if (*list == NULL) return WFERROR_ALLOC;
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFLinkedList_Free(WFLinkedList* list) {
|
|
||||||
return WFLinkedList_Free_Full(list, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFLinkedList_Free_Full(WFLinkedList* list, WFLinkedListNode_FreeDataFunc free_func) {
|
|
||||||
if (list == NULL) return WFERROR_OK;
|
|
||||||
|
|
||||||
// iterate full list and remove data and node
|
|
||||||
WFLinkedListNode* node = list->mHead, * free_node = NULL;
|
|
||||||
while (node != NULL) {
|
|
||||||
// free raw data
|
|
||||||
if (free_func != NULL) (*free_func)(node->mRawData);
|
|
||||||
// move to next node and free current node
|
|
||||||
free_node = node;
|
|
||||||
node = node->mNext;
|
|
||||||
free(free_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// free list self
|
|
||||||
free(list);
|
|
||||||
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFLinkedList_Add(WFLinkedList* list, void* data) {
|
|
||||||
if (list == NULL) return WFERROR_NULLPTR;
|
|
||||||
|
|
||||||
WFLinkedListNode* new_item = WFNEW(WFLinkedListNode);
|
|
||||||
if (new_item == NULL) return WFERROR_ALLOC;
|
|
||||||
new_item->mNext = NULL;
|
|
||||||
new_item->mRawData = data;
|
|
||||||
|
|
||||||
if (list->mLength == 0) {
|
|
||||||
list->mHead = list->mTail = new_item;
|
|
||||||
} else {
|
|
||||||
list->mTail->mNext = new_item;
|
|
||||||
list->mTail = new_item;
|
|
||||||
}
|
|
||||||
++list->mLength;
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFLinkedList_NodeIterator(WFLinkedList* list, WFLinkedListNode** node_ptr) {
|
|
||||||
if (list == NULL) return WFERROR_NULLPTR;
|
|
||||||
|
|
||||||
if (*node_ptr == NULL) {
|
|
||||||
// if node_ptr is NULL, it mean that wo should iterate this list from head
|
|
||||||
*node_ptr = list->mHead;
|
|
||||||
} else {
|
|
||||||
// otherwise, move to next node
|
|
||||||
*node_ptr = (*node_ptr)->mNext;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the header is null, return end of tail error to notice caller stop iterate
|
|
||||||
if (*node_ptr == NULL) return WFERROR_END_OF_TAIL;
|
|
||||||
else return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFERROR WFLinkedListNode_GetData(WFLinkedListNode* node, void** pdata) {
|
|
||||||
if (node == NULL) return WFERROR_NULLPTR;
|
|
||||||
|
|
||||||
*pdata = node->mRawData;
|
|
||||||
return WFERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma endregion
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
#if !defined(_YYCDLL_WFASSOC_UTILS_H__IMPORTED_)
|
|
||||||
#define _YYCDLL_WFASSOC_UTILS_H__IMPORTED_
|
|
||||||
|
|
||||||
#include <Windows.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
|
|
||||||
// useful macro
|
|
||||||
|
|
||||||
#define WFNEW(type) ((type*)malloc(sizeof(type)))
|
|
||||||
#define WFNEW_ARRAY(type) ((type*)malloc(sizeof(type)*count);)
|
|
||||||
#define WFVERSION 0
|
|
||||||
#define WFSUCCESS(expr) (!expr)
|
|
||||||
#define WFFAILED(expr) expr
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// wfassoc Error Enum
|
|
||||||
/// </summary>
|
|
||||||
typedef enum _WFERROR {
|
|
||||||
/// <summary>
|
|
||||||
/// All operation done successfully
|
|
||||||
/// </summary>
|
|
||||||
WFERROR_OK = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// The filed `WFVersion` in Profile Struct is not matched. It usually mean that currently used DLL is not matched with the DLL when compiling this application.
|
|
||||||
/// </summary>
|
|
||||||
WFERROR_INVALID_VERSION = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// The buffer in some operations is insufficient, please try expanding buffer.
|
|
||||||
/// </summary>
|
|
||||||
WFERROR_INSUFFICIENT_BUFFER = 2,
|
|
||||||
/// <summary>
|
|
||||||
/// Some essential pointer variable is NULL.
|
|
||||||
/// </summary>
|
|
||||||
WFERROR_NULLPTR = 3,
|
|
||||||
WFERROR_WIN32 = 5,
|
|
||||||
WFERROR_ALLOC = 6,
|
|
||||||
WFERROR_END_OF_TAIL = 7,
|
|
||||||
WFERROR_CRT = 8,
|
|
||||||
WFERROR_INVALID_ARGUMENTS = 9
|
|
||||||
}WFERROR;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// wfassoc Profile Struct
|
|
||||||
/// </summary>
|
|
||||||
typedef struct _WFAPP_PROFILE {
|
|
||||||
/// <summary>
|
|
||||||
/// wfassoc version. Fill it with `WFVERSION` in your application. This field is used for version checking.
|
|
||||||
/// </summary>
|
|
||||||
uint32_t WFVersion;
|
|
||||||
/// <summary>
|
|
||||||
/// The path to locate your application.
|
|
||||||
/// </summary>
|
|
||||||
wchar_t* AppPath;
|
|
||||||
/// <summary>
|
|
||||||
/// The command will be executed when opening files.
|
|
||||||
/// </summary>
|
|
||||||
wchar_t* AppCommand;
|
|
||||||
/// <summary>
|
|
||||||
/// Your application's ProgID.
|
|
||||||
/// For more detail about how to create your ProgID, please browse: https://docs.microsoft.com/en-us/windows/win32/shell/fa-progids
|
|
||||||
/// We also provide a generator for creating legacy ProgID. Use `WFGenerateProgID` to create a legacy ProgID.
|
|
||||||
/// </summary>
|
|
||||||
wchar_t* ProgID_Vendor;
|
|
||||||
wchar_t* ProgID_Component;
|
|
||||||
uint32_t ProgID_Version;
|
|
||||||
/// <summary>
|
|
||||||
/// Your application supported file extensions.
|
|
||||||
/// The structure of this field is connecting all supported extensions with dot(.) end to end. For example:
|
|
||||||
/// `.jpg\0.png\0.gif\0`
|
|
||||||
/// </summary>
|
|
||||||
wchar_t* SupportedTypes;
|
|
||||||
BOOL RegisterForAllUsers;
|
|
||||||
BOOL SetAsDefault;
|
|
||||||
BOOL ShowInOpenWithMenu;
|
|
||||||
BOOL UseOpenWithList;
|
|
||||||
}WFAPP_PROFILE;
|
|
||||||
|
|
||||||
typedef struct _WFAPP_INTERNAL_PROFILE {
|
|
||||||
WFString* AppPath;
|
|
||||||
WFString* AppBasePath;
|
|
||||||
WFString* AppFileName;
|
|
||||||
|
|
||||||
WFString* ProgID;
|
|
||||||
|
|
||||||
WFString* AppCommand;
|
|
||||||
|
|
||||||
WFLinkedList* SupportedTypes;
|
|
||||||
|
|
||||||
BOOL RegisterForAllUsers;
|
|
||||||
BOOL SetAsDefault;
|
|
||||||
BOOL ShowInOpenWithMenu;
|
|
||||||
BOOL UseOpenWithList;
|
|
||||||
}WFAPP_INTERNAL_PROFILE;
|
|
||||||
|
|
||||||
typedef struct _WFString {
|
|
||||||
wchar_t* mRawData;
|
|
||||||
uint32_t mLength;
|
|
||||||
uint32_t mCapacity;
|
|
||||||
|
|
||||||
uint32_t mRealLength;
|
|
||||||
uint32_t mRealCapacity;
|
|
||||||
}WFString;
|
|
||||||
|
|
||||||
WFERROR WFString_Alloc_Wchar(WFString** strl, const wchar_t* raw_data);
|
|
||||||
WFERROR WFString_Alloc_Char(WFString** strl, const char* raw_data, BOOL is_utf8);
|
|
||||||
WFERROR WFString_Alloc_Capacity(WFString** strl, uint32_t size);
|
|
||||||
WFERROR WFString_Alloc(WFString** strl);
|
|
||||||
WFERROR WFString_Free(WFString* strl);
|
|
||||||
WFERROR WFString_Resize(WFString* strl, uint32_t new_size);
|
|
||||||
WFERROR WFString_GetData(WFString* strl, wchar_t** pdata);
|
|
||||||
WFERROR WFString_SetData(WFString* strl, const wchar_t* data);
|
|
||||||
WFERROR WFString_GetLength(WFString* strl, uint32_t* len);
|
|
||||||
WFERROR WFString_Printf(WFString* strl, const wchar_t* format, ...);
|
|
||||||
WFERROR WFString_Concat_Wchar(WFString* strl, const wchar_t* extra);
|
|
||||||
WFERROR WFString_Concat_String(WFString* strl, WFString* extra);
|
|
||||||
WFERROR WFString_SubString(WFString* strl, WFString* substring, uint32_t start_index, uint32_t length);
|
|
||||||
|
|
||||||
typedef WFERROR(*WFLinkedListNode_FreeDataFunc)(void* data);
|
|
||||||
typedef struct _WFLinkedListNode {
|
|
||||||
void* mRawData;
|
|
||||||
WFLinkedListNode* mNext;
|
|
||||||
}WFLinkedListNode;
|
|
||||||
typedef struct _WFLinkedList {
|
|
||||||
WFLinkedListNode* mHead;
|
|
||||||
WFLinkedListNode* mTail;
|
|
||||||
uint32_t mLength;
|
|
||||||
}WFLinkedList;
|
|
||||||
|
|
||||||
WFERROR WFLinkedList_Alloc(WFLinkedList** list);
|
|
||||||
WFERROR WFLinkedList_Free(WFLinkedList* list);
|
|
||||||
WFERROR WFLinkedList_Free_Full(WFLinkedList* list, WFLinkedListNode_FreeDataFunc free_func);
|
|
||||||
WFERROR WFLinkedList_Add(WFLinkedList* list, void* data);
|
|
||||||
WFERROR WFLinkedList_NodeIterator(WFLinkedList* list, WFLinkedListNode** node_ptr);
|
|
||||||
WFERROR WFLinkedListNode_GetData(WFLinkedListNode* node, void** pdata);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
#include "../wfassoc/wfassoc.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <tchar.h>
|
|
||||||
|
|
||||||
#define COMMAND_MAX_LENGTH 128
|
|
||||||
|
|
||||||
WFAPP_PROFILE* create_profile();
|
|
||||||
TCHAR* create_progId();
|
|
||||||
void free_profile(WFAPP_PROFILE* profile);
|
|
||||||
|
|
||||||
int main(int argc, char* args[]) {
|
|
||||||
|
|
||||||
// alloc application profile
|
|
||||||
WFAPP_PROFILE* profile = create_profile();
|
|
||||||
if (profile == NULL) {
|
|
||||||
puts("Error: Fail to allocating profile structure.\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// alloc input string and check it
|
|
||||||
char* input_str = malloc(sizeof(char) * (COMMAND_MAX_LENGTH + 1));
|
|
||||||
if (input_str == NULL) return;
|
|
||||||
|
|
||||||
// accept input
|
|
||||||
while (TRUE) {
|
|
||||||
gets_s(input_str, COMMAND_MAX_LENGTH);
|
|
||||||
if (!strcmp(input_str, "install")) {
|
|
||||||
_tprintf(TEXT("%s\n"), profile->ProgID);
|
|
||||||
} else if (!strcmp(input_str, "uninstall")) {
|
|
||||||
_tprintf(TEXT("%s\n"), profile->ProgID);
|
|
||||||
} else if (!strcmp(input_str, "quit")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//free input string
|
|
||||||
free(input_str);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
WFAPP_PROFILE* create_profile() {
|
|
||||||
WFAPP_PROFILE* profile = (WFAPP_PROFILE*)malloc(sizeof(WFAPP_PROFILE));
|
|
||||||
if (profile == NULL) return NULL;
|
|
||||||
memset(profile, 0, sizeof(WFAPP_PROFILE));
|
|
||||||
|
|
||||||
profile->WFVersion = WFVERSION;
|
|
||||||
profile->AppPath = TEXT("E:\\pineapple-picture\\ppic.exe");
|
|
||||||
profile->AppCommand = TEXT("E:\\pineapple-picture\\ppic.exe \"%1\"");
|
|
||||||
profile->SupportedTypes = TEXT(".png\0.jpg\0");
|
|
||||||
profile->ProgID = create_progId();
|
|
||||||
if (profile->ProgID == NULL) {
|
|
||||||
free_profile(profile);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
TCHAR* create_progId() {
|
|
||||||
TCHAR* progid = NULL;
|
|
||||||
int progIdSize;
|
|
||||||
if (WFFAILED(WFGenerateProgID(TEXT("PineapplePicture"), TEXT("Image"), 0, NULL, &progIdSize))) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
progid = (TCHAR*)malloc(sizeof(TCHAR) * progIdSize);
|
|
||||||
if (progid == NULL) return NULL;
|
|
||||||
memset(progid, 0, sizeof(TCHAR) * progIdSize);
|
|
||||||
|
|
||||||
if (WFFAILED(WFGenerateProgID(TEXT("PineapplePicture"), TEXT("Image"), 0, progid, &progIdSize))) {
|
|
||||||
free(progid);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return progid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void free_profile(WFAPP_PROFILE* profile) {
|
|
||||||
if (profile->ProgID != NULL) free(profile->ProgID);
|
|
||||||
free(profile);
|
|
||||||
}
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<ItemGroup Label="ProjectConfigurations">
|
|
||||||
<ProjectConfiguration Include="Debug_MB|Win32">
|
|
||||||
<Configuration>Debug_MB</Configuration>
|
|
||||||
<Platform>Win32</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Debug_MB|x64">
|
|
||||||
<Configuration>Debug_MB</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Debug_UNICODE|Win32">
|
|
||||||
<Configuration>Debug_UNICODE</Configuration>
|
|
||||||
<Platform>Win32</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Release_UNICODE|Win32">
|
|
||||||
<Configuration>Release_UNICODE</Configuration>
|
|
||||||
<Platform>Win32</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Debug_UNICODE|x64">
|
|
||||||
<Configuration>Debug_UNICODE</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Release_UNICODE|x64">
|
|
||||||
<Configuration>Release_UNICODE</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
</ItemGroup>
|
|
||||||
<PropertyGroup Label="Globals">
|
|
||||||
<VCProjectVersion>16.0</VCProjectVersion>
|
|
||||||
<Keyword>Win32Proj</Keyword>
|
|
||||||
<ProjectGuid>{ad275ad7-cbd5-41ca-ab24-bb707b3f7534}</ProjectGuid>
|
|
||||||
<RootNamespace>wfassocexample</RootNamespace>
|
|
||||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|Win32'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>true</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v142</PlatformToolset>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|Win32'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>true</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v142</PlatformToolset>
|
|
||||||
<CharacterSet>MultiByte</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|Win32'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>false</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v142</PlatformToolset>
|
|
||||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|x64'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>true</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v142</PlatformToolset>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|x64'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>true</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v142</PlatformToolset>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|x64'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>false</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v142</PlatformToolset>
|
|
||||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
|
||||||
<ImportGroup Label="ExtensionSettings">
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="Shared">
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|Win32'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|Win32'" Label="PropertySheets">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|Win32'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|x64'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|x64'" Label="PropertySheets">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|x64'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<PropertyGroup Label="UserMacros" />
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|Win32'">
|
|
||||||
<LinkIncremental>true</LinkIncremental>
|
|
||||||
<OutDir>$(SolutionDir)builds\Debug\</OutDir>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|Win32'">
|
|
||||||
<LinkIncremental>true</LinkIncremental>
|
|
||||||
<OutDir>$(SolutionDir)builds\Debug\</OutDir>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|Win32'">
|
|
||||||
<LinkIncremental>false</LinkIncremental>
|
|
||||||
<OutDir>$(SolutionDir)builds\Release\</OutDir>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|x64'">
|
|
||||||
<LinkIncremental>true</LinkIncremental>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|x64'">
|
|
||||||
<LinkIncremental>true</LinkIncremental>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|x64'">
|
|
||||||
<LinkIncremental>false</LinkIncremental>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|Win32'">
|
|
||||||
<ClCompile>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
<CompileAs>CompileAsC</CompileAs>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|Win32'">
|
|
||||||
<ClCompile>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
<CompileAs>CompileAsC</CompileAs>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|Win32'">
|
|
||||||
<ClCompile>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
<CompileAs>CompileAsC</CompileAs>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
|
||||||
<OptimizeReferences>true</OptimizeReferences>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|x64'">
|
|
||||||
<ClCompile>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|x64'">
|
|
||||||
<ClCompile>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|x64'">
|
|
||||||
<ClCompile>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
|
||||||
<OptimizeReferences>true</OptimizeReferences>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\wfassoc\wfassoc.vcxproj">
|
|
||||||
<Project>{4aac8f0c-3e1c-4584-b682-05bbf96a813f}</Project>
|
|
||||||
</ProjectReference>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClCompile Include="main.c" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
|
||||||
<ImportGroup Label="ExtensionTargets">
|
|
||||||
</ImportGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<ItemGroup>
|
|
||||||
<Filter Include="源文件">
|
|
||||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
|
||||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="头文件">
|
|
||||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
|
||||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="资源文件">
|
|
||||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
|
||||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
|
||||||
</Filter>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClCompile Include="main.c">
|
|
||||||
<Filter>源文件</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -12,6 +12,5 @@ crate-type = ["cdylib"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
wfassoc = { path="../wfassoc" }
|
wfassoc = { path="../wfassoc" }
|
||||||
|
slotmap = "1.1.1"
|
||||||
[build-dependencies]
|
num_enum = "0.7.6"
|
||||||
cbindgen = "0.29.0"
|
|
||||||
|
|||||||
@@ -3,3 +3,5 @@
|
|||||||
For how to utilize this library, please see our example located in `example/ppic`. It is a Qt project built with CMake and demonstrate basically all usage of exposed functions in this dynamic library.
|
For how to utilize this library, please see our example located in `example/ppic`. It is a Qt project built with CMake and demonstrate basically all usage of exposed functions in this dynamic library.
|
||||||
|
|
||||||
All crucial spots are written in this example as the code comment so I don't write them in there again.
|
All crucial spots are written in this example as the code comment so I don't write them in there again.
|
||||||
|
|
||||||
|
Due to the limitation of Rust compiler, you may need to enable `RUSTC_BOOTSTRAP=1` environment when creating binding (it is not necessary for just building this dynamic library). See [this GitHub issue](https://github.com/mozilla/cbindgen/issues/1015) for more info.
|
||||||
|
|||||||
101
wfassoc-cdylib/cbindgen/Findwfassoc.cmake
Normal file
101
wfassoc-cdylib/cbindgen/Findwfassoc.cmake
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Findwfassoc.cmake
|
||||||
|
# ----------------
|
||||||
|
# Find wfassoc library and headers.
|
||||||
|
#
|
||||||
|
# This module requires the user to set wfassoc_ROOT to the installation
|
||||||
|
# directory of wfassoc. The directory structure under wfassoc_ROOT must be:
|
||||||
|
# bin/ - contains wfassoc_cdylib.dll
|
||||||
|
# include/ - contains wfassoc.h and wfassoc++.h
|
||||||
|
# lib/ - contains wfassoc_cdylib.dll.lib (import library)
|
||||||
|
#
|
||||||
|
# This module defines the following variables:
|
||||||
|
# wfassoc_FOUND - True if wfassoc was found
|
||||||
|
# wfassoc_INCLUDE_DIRS - Path to wfassoc include directory
|
||||||
|
# wfassoc_LIBRARIES - Path to wfassoc import library
|
||||||
|
# wfassoc_DLL - Path to wfassoc DLL
|
||||||
|
# wfassoc_ROOT - The root directory (user-provided)
|
||||||
|
#
|
||||||
|
# This module also creates the following imported targets:
|
||||||
|
# wfassoc::wfassoc - Main wfassoc library (includes both include and link)
|
||||||
|
#
|
||||||
|
|
||||||
|
set(wfassoc_FOUND FALSE)
|
||||||
|
|
||||||
|
# Require user to set wfassoc_ROOT
|
||||||
|
if(NOT wfassoc_ROOT)
|
||||||
|
message(FATAL_ERROR "wfassoc_ROOT must be set to the installation directory of wfassoc")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Check existence of required subdirectories
|
||||||
|
if(NOT EXISTS ${wfassoc_ROOT})
|
||||||
|
message(FATAL_ERROR "wfassoc_ROOT directory does not exist: ${wfassoc_ROOT}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(wfassoc_INCLUDE_DIR ${wfassoc_ROOT}/include)
|
||||||
|
set(wfassoc_LIB_DIR ${wfassoc_ROOT}/lib)
|
||||||
|
set(wfassoc_BIN_DIR ${wfassoc_ROOT}/bin)
|
||||||
|
|
||||||
|
# Find header files
|
||||||
|
if(EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc.h AND EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc++.h)
|
||||||
|
set(wfassoc_INCLUDE_DIRS ${wfassoc_INCLUDE_DIR})
|
||||||
|
else()
|
||||||
|
message(SEND_ERROR "Missing wfassoc header files in ${wfassoc_INCLUDE_DIR}")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Find import library (.lib)
|
||||||
|
find_file(wfassoc_LIBRARIES
|
||||||
|
NAMES wfassoc_cdylib.dll.lib
|
||||||
|
PATHS ${wfassoc_LIB_DIR}
|
||||||
|
NO_DEFAULT_PATH
|
||||||
|
DOC "wfassoc import library"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT wfassoc_LIBRARIES)
|
||||||
|
message(SEND_ERROR "Missing wfassoc import library (wfassoc_cdylib.dll.lib) in ${wfassoc_LIB_DIR}")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Find DLL file
|
||||||
|
find_file(wfassoc_DLL
|
||||||
|
NAMES wfassoc_cdylib.dll
|
||||||
|
PATHS ${wfassoc_BIN_DIR}
|
||||||
|
NO_DEFAULT_PATH
|
||||||
|
DOC "wfassoc dynamic library"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT wfassoc_DLL)
|
||||||
|
message(SEND_ERROR "Missing wfassoc DLL (wfassoc_cdylib.dll) in ${wfassoc_BIN_DIR}")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Everything found
|
||||||
|
set(wfassoc_FOUND TRUE)
|
||||||
|
|
||||||
|
# Mark variables as advanced for ccmake/cmake-gui
|
||||||
|
mark_as_advanced(wfassoc_INCLUDE_DIRS wfassoc_LIBRARIES wfassoc_DLL)
|
||||||
|
|
||||||
|
# Create imported target for wfassoc
|
||||||
|
if(wfassoc_FOUND AND NOT TARGET wfassoc::wfassoc)
|
||||||
|
add_library(wfassoc::wfassoc SHARED IMPORTED)
|
||||||
|
|
||||||
|
# Set include directories
|
||||||
|
set_target_properties(wfassoc::wfassoc PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES ${wfassoc_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set import library location
|
||||||
|
set_target_properties(wfassoc::wfassoc PROPERTIES
|
||||||
|
IMPORTED_IMPLIB "${wfassoc_LIBRARIES}"
|
||||||
|
IMPORTED_LOCATION "${wfassoc_DLL}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Optional: Print status message
|
||||||
|
if(wfassoc_FOUND)
|
||||||
|
message(STATUS "Found wfassoc:")
|
||||||
|
message(STATUS " Root : ${wfassoc_ROOT}")
|
||||||
|
message(STATUS " Include : ${wfassoc_INCLUDE_DIRS}")
|
||||||
|
message(STATUS " Library : ${wfassoc_LIBRARIES}")
|
||||||
|
message(STATUS " DLL : ${wfassoc_DLL}")
|
||||||
|
endif()
|
||||||
290
wfassoc-cdylib/cbindgen/wfassoc++.h
Normal file
290
wfassoc-cdylib/cbindgen/wfassoc++.h
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
/**
|
||||||
|
* @file wfassoc++.h
|
||||||
|
* @brief Windows File Association C++ API header
|
||||||
|
*
|
||||||
|
* This header provides C++ API for managing Windows file associations,
|
||||||
|
* based on its C-compatible API.
|
||||||
|
* The API is designed to work with at least C++17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef WFASSOCPP_H_
|
||||||
|
#define WFASSOCPP_H_
|
||||||
|
|
||||||
|
#include "wfassoc.h"
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace wfassocpp {
|
||||||
|
|
||||||
|
using wfassoc::CStyleString;
|
||||||
|
using wfassoc::Token;
|
||||||
|
using wfassoc::HICON;
|
||||||
|
using wfassoc::INVALID_HICON;
|
||||||
|
using wfassoc::INVALID_INDEX;
|
||||||
|
using wfassoc::Scope;
|
||||||
|
using wfassoc::View;
|
||||||
|
|
||||||
|
/// @private
|
||||||
|
inline void _Check(bool result) {
|
||||||
|
if (!result) {
|
||||||
|
throw std::runtime_error(wfassoc::WFGetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @private
|
||||||
|
inline Token _INVALID_TOKEN() {
|
||||||
|
static Token v = wfassoc::WFInvalidToken();
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Schema {
|
||||||
|
public:
|
||||||
|
Schema() { _Check(wfassoc::WFSchemaCreate(&_token)); }
|
||||||
|
~Schema() {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFSchemaDestroy(_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Schema(const Schema&) = delete;
|
||||||
|
Schema& operator=(const Schema&) = delete;
|
||||||
|
Schema(Schema&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
Schema& operator=(Schema&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFSchemaDestroy(_token);
|
||||||
|
}
|
||||||
|
_token = other._token;
|
||||||
|
other._token = _INVALID_TOKEN();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetIdentifier(const char* value) { _Check(wfassoc::WFSchemaSetIdentifier(_token, value)); }
|
||||||
|
void SetPath(const char* value) { _Check(wfassoc::WFSchemaSetPath(_token, value)); }
|
||||||
|
void SetClsid(const char* value) { _Check(wfassoc::WFSchemaSetClsid(_token, value)); }
|
||||||
|
void SetName(const char* value) { _Check(wfassoc::WFSchemaSetName(_token, value)); }
|
||||||
|
void SetIcon(const char* value) { _Check(wfassoc::WFSchemaSetIcon(_token, value)); }
|
||||||
|
void SetBehavior(const char* value) { _Check(wfassoc::WFSchemaSetBehavior(_token, value)); }
|
||||||
|
void AddStr(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddStr(_token, name, value)); }
|
||||||
|
void AddIcon(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddIcon(_token, name, value)); }
|
||||||
|
void AddBehavior(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddBehavior(_token, name, value)); }
|
||||||
|
void AddExt(const char* ext, const char* ext_name, const char* ext_icon, const char* ext_behavior) {
|
||||||
|
_Check(wfassoc::WFSchemaAddExt(_token, ext, ext_name, ext_icon, ext_behavior));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Program;
|
||||||
|
/// @private
|
||||||
|
Token Release() noexcept {
|
||||||
|
Token t = _token;
|
||||||
|
_token = _INVALID_TOKEN();
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
Token _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IconRc {
|
||||||
|
public:
|
||||||
|
explicit IconRc(Token token) : _token(token) {}
|
||||||
|
~IconRc() {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFIconRcDestroy(_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IconRc(const IconRc&) = delete;
|
||||||
|
IconRc& operator=(const IconRc&) = delete;
|
||||||
|
IconRc(IconRc&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
IconRc& operator=(IconRc&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFIconRcDestroy(_token);
|
||||||
|
}
|
||||||
|
_token = other._token;
|
||||||
|
other._token = _INVALID_TOKEN();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
HICON GetIcon() {
|
||||||
|
HICON icon = nullptr;
|
||||||
|
_Check(wfassoc::WFIconRcGetIcon(_token, &icon));
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Token _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ExtStatus {
|
||||||
|
public:
|
||||||
|
explicit ExtStatus(Token token) : _token(token) {}
|
||||||
|
~ExtStatus() {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFExtStatusDestroy(_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExtStatus(const ExtStatus&) = delete;
|
||||||
|
ExtStatus& operator=(const ExtStatus&) = delete;
|
||||||
|
ExtStatus(ExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
ExtStatus& operator=(ExtStatus&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFExtStatusDestroy(_token);
|
||||||
|
}
|
||||||
|
_token = other._token;
|
||||||
|
other._token = _INVALID_TOKEN();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetName() {
|
||||||
|
const char* name = nullptr;
|
||||||
|
_Check(wfassoc::WFExtStatusGetName(_token, &name));
|
||||||
|
return std::string(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
HICON GetIcon() {
|
||||||
|
HICON icon = nullptr;
|
||||||
|
_Check(wfassoc::WFExtStatusGetIcon(_token, &icon));
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Token _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SelfExtStatus {
|
||||||
|
public:
|
||||||
|
explicit SelfExtStatus(Token token) : _token(token) {}
|
||||||
|
~SelfExtStatus() {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFSelfExtStatusDestroy(_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SelfExtStatus(const SelfExtStatus&) = delete;
|
||||||
|
SelfExtStatus& operator=(const SelfExtStatus&) = delete;
|
||||||
|
SelfExtStatus(SelfExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
SelfExtStatus& operator=(SelfExtStatus&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFSelfExtStatusDestroy(_token);
|
||||||
|
}
|
||||||
|
_token = other._token;
|
||||||
|
other._token = _INVALID_TOKEN();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetName() {
|
||||||
|
const char* name = nullptr;
|
||||||
|
_Check(wfassoc::WFSelfExtStatusGetName(_token, &name));
|
||||||
|
return std::string(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
HICON GetIcon() {
|
||||||
|
HICON icon = nullptr;
|
||||||
|
_Check(wfassoc::WFSelfExtStatusGetIcon(_token, &icon));
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetExt() {
|
||||||
|
const char* inner = nullptr;
|
||||||
|
_Check(wfassoc::WFSelfExtStatusGetExt(_token, &inner));
|
||||||
|
return std::string(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetDottedExt() {
|
||||||
|
const char* inner = nullptr;
|
||||||
|
_Check(wfassoc::WFSelfExtStatusGetDottedExt(_token, &inner));
|
||||||
|
return std::string(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Token _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Program {
|
||||||
|
public:
|
||||||
|
explicit Program(Schema&& schema) {
|
||||||
|
_Check(wfassoc::WFProgramCreate(schema.Release(), &_token));
|
||||||
|
}
|
||||||
|
~Program() {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFProgramDestroy(_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Program(const Program&) = delete;
|
||||||
|
Program& operator=(const Program&) = delete;
|
||||||
|
Program(Program&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
Program& operator=(Program&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFProgramDestroy(_token);
|
||||||
|
}
|
||||||
|
_token = other._token;
|
||||||
|
other._token = _INVALID_TOKEN();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ResolveName() {
|
||||||
|
const char* name = nullptr;
|
||||||
|
_Check(wfassoc::WFProgramResolveName(_token, &name));
|
||||||
|
return std::string(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
IconRc ResolveIcon() {
|
||||||
|
Token token = _INVALID_TOKEN();
|
||||||
|
_Check(wfassoc::WFProgramResolveIcon(_token, &token));
|
||||||
|
return IconRc(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ExtsLen() {
|
||||||
|
size_t len = 0;
|
||||||
|
_Check(wfassoc::WFProgramExtsLen(_token, &len));
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FindExt(const char* body) {
|
||||||
|
size_t index = INVALID_INDEX;
|
||||||
|
_Check(wfassoc::WFProgramFindExt(_token, body, &index));
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelfExtStatus ResolveExt(size_t index) {
|
||||||
|
Token token = _INVALID_TOKEN();
|
||||||
|
_Check(wfassoc::WFProgramResolveExt(_token, index, &token));
|
||||||
|
return SelfExtStatus(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Register(Scope scope) { _Check(wfassoc::WFProgramRegister(_token, scope)); }
|
||||||
|
void Unregister(Scope scope) { _Check(wfassoc::WFProgramUnregister(_token, scope)); }
|
||||||
|
|
||||||
|
bool IsRegistered(Scope scope) {
|
||||||
|
bool result = false;
|
||||||
|
_Check(wfassoc::WFProgramIsRegistered(_token, scope, &result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinkExt(Scope scope, size_t index) { _Check(wfassoc::WFProgramLinkExt(_token, scope, index)); }
|
||||||
|
void UnlinkExt(Scope scope, size_t index) { _Check(wfassoc::WFProgramUnlinkExt(_token, scope, index)); }
|
||||||
|
|
||||||
|
std::optional<ExtStatus> QueryExt(View view, size_t index) {
|
||||||
|
Token token = _INVALID_TOKEN();
|
||||||
|
_Check(wfassoc::WFProgramQueryExt(_token, view, index, &token));
|
||||||
|
if (token == _INVALID_TOKEN()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return ExtStatus(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Token _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace wfassocpp
|
||||||
|
|
||||||
|
#endif // WFASSOCPP_H_
|
||||||
589
wfassoc-cdylib/cbindgen/wfassoc.h
Normal file
589
wfassoc-cdylib/cbindgen/wfassoc.h
Normal file
@@ -0,0 +1,589 @@
|
|||||||
|
/**
|
||||||
|
* @file wfassoc.h
|
||||||
|
* @brief Windows File Association C API header
|
||||||
|
*
|
||||||
|
* This header provides a C-compatible API for managing Windows file associations,
|
||||||
|
* including schema creation, program registration, and extension management.
|
||||||
|
* The API is designed to at least work with both C99 and C++17 compilers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef WFASSOC_H_
|
||||||
|
#define WFASSOC_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#else // __cplusplus
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
namespace wfassoc {
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
/** Type representing a null-terminated UTF-8 C-style string */
|
||||||
|
using CStyleString = const char*;
|
||||||
|
/**
|
||||||
|
* @brief Type representing a handle/token for managed objects
|
||||||
|
*
|
||||||
|
* This library use object pool to manage any objects created during calling.
|
||||||
|
* And we expose this type as an opaque handle for visiting your created object.
|
||||||
|
*/
|
||||||
|
using Token = uint64_t;
|
||||||
|
/**
|
||||||
|
* @brief Type representing an icon handle (opaque pointer)
|
||||||
|
*
|
||||||
|
* This type is equivalent with Win32 HICON type.
|
||||||
|
*/
|
||||||
|
using HICON = void*;
|
||||||
|
#else // __cplusplus
|
||||||
|
typedef const char *CStyleString;
|
||||||
|
typedef uint64_t Token;
|
||||||
|
typedef void *HICON;
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
/** Invalid icon handle value */
|
||||||
|
constexpr HICON INVALID_HICON = nullptr;
|
||||||
|
/** Invalid index value used for error conditions */
|
||||||
|
constexpr size_t INVALID_INDEX = static_cast<size_t>(-1);
|
||||||
|
#else // __cplusplus
|
||||||
|
static const HICON INVALID_HICON = NULL;
|
||||||
|
static const size_t INVALID_INDEX = ((size_t)-1);
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
/**
|
||||||
|
* @brief Registration scope for file associations
|
||||||
|
*
|
||||||
|
* Determines whether a program is registered for the current user or system-wide.
|
||||||
|
*/
|
||||||
|
enum class Scope : uint32_t {
|
||||||
|
/** Current user scope */
|
||||||
|
User = 0u,
|
||||||
|
/** System-wide scope */
|
||||||
|
System = 1u
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @brief View mode for querying file association status
|
||||||
|
*
|
||||||
|
* Determines how the association status is viewed/queried.
|
||||||
|
*/
|
||||||
|
enum class View : uint32_t {
|
||||||
|
/** User-level view */
|
||||||
|
User = 0u,
|
||||||
|
/** System-level view */
|
||||||
|
System = 1u,
|
||||||
|
/** Combined hybrid view of both user and system */
|
||||||
|
Hybrid = 2u
|
||||||
|
};
|
||||||
|
#else // __cplusplus
|
||||||
|
typedef uint32_t Scope;
|
||||||
|
/** Current user scope */
|
||||||
|
static const Scope SCOPE_USER = 0u;
|
||||||
|
/** System-wide scope */
|
||||||
|
static const Scope SCOPE_SYSTEM = 1u;
|
||||||
|
typedef uint32_t View;
|
||||||
|
/** User-level view */
|
||||||
|
static const View VIEW_USER = 0u;
|
||||||
|
/** System-level view */
|
||||||
|
static const View VIEW_SYSTEM = 1u;
|
||||||
|
/** Combined hybrid view of both user and system */
|
||||||
|
static const View VIEW_HYBRID = 2u;
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the wfassoc library
|
||||||
|
*
|
||||||
|
* This function must be called before using the MOST of any other wfassoc functions.
|
||||||
|
*
|
||||||
|
* @return true on success, false on failure.
|
||||||
|
*/
|
||||||
|
bool WFStartup(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Shutdown the wfassoc library
|
||||||
|
*
|
||||||
|
* Cleans up all allocated resources and object pools.
|
||||||
|
* Should be called when done using the library.
|
||||||
|
*
|
||||||
|
* @return true on success, false on failure.
|
||||||
|
*/
|
||||||
|
bool WFShutdown(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the last error message
|
||||||
|
*
|
||||||
|
* Returns a human-readable error message describing the last error that occurred.
|
||||||
|
* The returned error message string is valid until the next API call.
|
||||||
|
*
|
||||||
|
* For most functions located in this library, except some special function indicated in their notes,
|
||||||
|
* they return boolean value indicating whether function is successful or not.
|
||||||
|
* Once they fail, you can call this function to get a human-readable error message.
|
||||||
|
*
|
||||||
|
* The execution of this function do not need to be wrapped by WFStartup() and WFShutdown().
|
||||||
|
*
|
||||||
|
* The string this function return use different buffer with function return string value.
|
||||||
|
* So you don't worry about that calling this function may invalidate function function return string value.
|
||||||
|
*
|
||||||
|
* @return Null-terminated UTF-8 string containing the error message.
|
||||||
|
* If no error has occurred, the string is empty.
|
||||||
|
* There is no possibility of a NULL return value.
|
||||||
|
*/
|
||||||
|
CStyleString WFGetLastError(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the current process has administrative privileges
|
||||||
|
*
|
||||||
|
* This function will not throw any error.
|
||||||
|
* There is no necessity to call WFGetLastError() after this function.
|
||||||
|
* The return value only indicates whether the current process has administrative privileges or not.
|
||||||
|
*
|
||||||
|
* The execution of this function do not need to be wrapped by WFStartup() and WFShutdown().
|
||||||
|
*
|
||||||
|
* @return true if running with admin privileges, false otherwise
|
||||||
|
*/
|
||||||
|
bool WFHasPrivilege(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get an invalid token value
|
||||||
|
*
|
||||||
|
* In theory, invalid token value should be a constant value.
|
||||||
|
* However, due to the library I used in Rust side, this value only can be fetched at runtime.
|
||||||
|
* So I expose this function to make programmer can fetch this constant value.
|
||||||
|
* Theoretically, you just need to fetch this function only once at the beginning of your program.
|
||||||
|
*
|
||||||
|
* The execution of this function do not need to be wrapped by WFStartup() and WFShutdown().
|
||||||
|
*
|
||||||
|
* @return An invalid token value
|
||||||
|
*/
|
||||||
|
Token WFInvalidToken(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new Schema object
|
||||||
|
*
|
||||||
|
* A Schema is a sketchpad of a complete program.
|
||||||
|
* For the user of this library, they should create a Schema object first.
|
||||||
|
* Then convert it to a Program object for following operations.
|
||||||
|
*
|
||||||
|
* @param[out] out_schema Pointer to receive the Schema token.
|
||||||
|
* The receiver take the ownership of this Schema object.
|
||||||
|
* And it should be freed by calling WFSchemaDestroy() when it is no longer needed,
|
||||||
|
* or consumed by creating a Program object via WFProgramCreate().
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaCreate(Token *out_schema);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy a Schema object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the Schema object.
|
||||||
|
*
|
||||||
|
* Usually you do not need to call this function,
|
||||||
|
* because the convertion function from Schema to Program will consume given Schema object to produce Program object.
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token to destroy
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaDestroy(Token in_schema);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program identifier for a Schema
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token
|
||||||
|
* @param[in] in_value Null-terminated UTF-8 string containing the identifier.
|
||||||
|
* This identifier should not be empty, must start with alphabet character,
|
||||||
|
* and follow with alphabet characters, digits, underline, or hyphens.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaSetIdentifier(Token in_schema, CStyleString in_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program path for a Schema
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token
|
||||||
|
* @param[in] in_value Null-terminated UTF-8 string containing the program path.
|
||||||
|
* This path should be the fully qualified path to the application.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaSetPath(Token in_schema, CStyleString in_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program CLSID for a Schema
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token
|
||||||
|
* @param[in] in_value Null-terminated UTF-8 string containing the CLSID.
|
||||||
|
* This CLSID string should be in the format of @c {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} .
|
||||||
|
* Please note that curly braces are required.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaSetClsid(Token in_schema, CStyleString in_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program name for a Schema (optional)
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token
|
||||||
|
* @param[in] in_value Null-terminated UTF-8 string containing the name, or NULL to clear
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaSetName(Token in_schema, CStyleString in_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program icon for a Schema (optional)
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token
|
||||||
|
* @param[in] in_value Null-terminated UTF-8 string containing the icon path, or NULL to clear
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaSetIcon(Token in_schema, CStyleString in_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program behavior for a Schema (optional)
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token
|
||||||
|
* @param[in] in_value Null-terminated UTF-8 string containing the behavior command, or NULL to clear
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaSetBehavior(Token in_schema, CStyleString in_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a string resource entry to a Schema
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token
|
||||||
|
* @param[in] in_name Null-terminated UTF-8 string containing the name of this entry
|
||||||
|
* @param[in] in_value Null-terminated UTF-8 string containing the value of this entry.
|
||||||
|
* It can be a plain string or a reference string to resource.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaAddStr(Token in_schema, CStyleString in_name, CStyleString in_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add an icon registry entry to a Schema
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token
|
||||||
|
* @param[in] in_name Null-terminated UTF-8 string containing the name of this entry
|
||||||
|
* @param[in] in_value Null-terminated UTF-8 string containing the value of this entry.
|
||||||
|
* It can be a path to icon or a reference string to resource.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaAddIcon(Token in_schema, CStyleString in_name, CStyleString in_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a behavior registry entry to a Schema
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token
|
||||||
|
* @param[in] in_name Null-terminated UTF-8 string containing the name of this entry
|
||||||
|
* @param[in] in_value Null-terminated UTF-8 string containing the value of this entry.
|
||||||
|
* It should be a valid command line string which use \c %1, \c %2, etc. to represent parameters.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaAddBehavior(Token in_schema, CStyleString in_name, CStyleString in_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a file extension to a Schema
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token
|
||||||
|
* @param[in] in_ext Null-terminated UTF-8 string containing the file extension name (without leading dot).
|
||||||
|
* @param[in] in_ext_name Null-terminated UTF-8 string containing the name pointing to associated name for this extension.
|
||||||
|
* This name should be registered by calling WFSchemaAddStr().
|
||||||
|
* @param[in] in_ext_icon Null-terminated UTF-8 string containing the name pointing to associated icon for this extension.
|
||||||
|
* This name should be registered by calling WFSchemaAddIcon().
|
||||||
|
* @param[in] in_ext_behavior Null-terminated UTF-8 string containing the name pointing to associated behavior for this extension.
|
||||||
|
* This name should be registered by calling WFSchemaAddBehavior().
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSchemaAddExt(Token in_schema,
|
||||||
|
CStyleString in_ext,
|
||||||
|
CStyleString in_ext_name,
|
||||||
|
CStyleString in_ext_icon,
|
||||||
|
CStyleString in_ext_behavior);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a Program object from a Schema
|
||||||
|
*
|
||||||
|
* Please note that this function will consume the Schema object.
|
||||||
|
* It means that the Schema object cannot be used after this call.
|
||||||
|
* And you do not need to call WFSchemaDestroy() for this Schema object after this call.
|
||||||
|
*
|
||||||
|
* Please note that the given Schema object will always be consumed,
|
||||||
|
* no matter this function return success or failure.
|
||||||
|
*
|
||||||
|
* @param[in] in_schema Schema token (will be consumed)
|
||||||
|
* @param[out] out_program Pointer to receive the Program token.
|
||||||
|
* The receiver take the ownership of this Program object.
|
||||||
|
* And it should be freed by calling WFProgramDestroy() when it is no longer needed.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramCreate(Token in_schema, Token *out_program);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy a Program object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the Program.
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token to destroy
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramDestroy(Token in_program);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resolve the provided program name of this Program
|
||||||
|
*
|
||||||
|
* The name will be user specified first,
|
||||||
|
* then fallback to program manifest file specified name,
|
||||||
|
* and finally fallback to the file name of executable.
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token
|
||||||
|
* @param[out] out_name Pointer to receive the resolved name.
|
||||||
|
* There is no possibility that this value is NULL.
|
||||||
|
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramResolveName(Token in_program, CStyleString *out_name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resolve the Program icon resource
|
||||||
|
*
|
||||||
|
* The icon will be user specified first,
|
||||||
|
* the fallback to the first icon of program,
|
||||||
|
* and finally fallback to the system default executable icon.
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token
|
||||||
|
* @param[out] out_icon_rc Pointer to receive the icon resource token.
|
||||||
|
* The caller take the ownership of created icon resource object.
|
||||||
|
* And it should be freed by calling WFIconRcDestroy() when it is no longer needed.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramResolveIcon(Token in_program, Token *out_icon_rc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the number of file extensions in the Program
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token
|
||||||
|
* @param[out] out_len Pointer to receive the number of extensions
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramExtsLen(Token in_program, size_t *out_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find a file extension by its body (extension string)
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token
|
||||||
|
* @param[in] in_body Null-terminated UTF-8 string containing the file extension name (without leading dot) to find.
|
||||||
|
* @param[out] out_index Pointer to receive the file extension index, or INVALID_INDEX if not found.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramFindExt(Token in_program, CStyleString in_body, size_t *out_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resolve this program provided extension's details by index
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token
|
||||||
|
* @param[in] in_index Index of the extension to resolve
|
||||||
|
* @param[out] out_self_ext_status Pointer to receive the self extension status token.
|
||||||
|
* The caller take the ownership of created self extension status object.
|
||||||
|
* And it should be freed by calling WFSelfExtStatusDestroy() when it is no longer needed.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramResolveExt(Token in_program, size_t in_index, Token *out_self_ext_status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register the Program in the specified scope
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token
|
||||||
|
* @param[in] in_scope Registration scope
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramRegister(Token in_program, Scope in_scope);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unregister the Program from the specified scope
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token
|
||||||
|
* @param[in] in_scope Registration scope
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramUnregister(Token in_program, Scope in_scope);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the Program is registered in the specified scope
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token
|
||||||
|
* @param[in] in_scope Registration scope for checking
|
||||||
|
* @param[out] out_is_registered Pointer to receive the registration status.
|
||||||
|
* True if the Program is registered in the specified scope, false otherwise.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramIsRegistered(Token in_program, Scope in_scope, bool *out_is_registered);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Link a file extension in the specified scope
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token
|
||||||
|
* @param[in] in_scope Registration scope
|
||||||
|
* @param[in] in_index Index of the extension to link
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramLinkExt(Token in_program, Scope in_scope, size_t in_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unlink a file extension in the specified scope
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token
|
||||||
|
* @param[in] in_scope Registration scope
|
||||||
|
* @param[in] in_index Index of the extension to unlink
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramUnlinkExt(Token in_program, Scope in_scope, size_t in_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Query the status of a file extension
|
||||||
|
*
|
||||||
|
* @param[in] in_program Program token
|
||||||
|
* @param[in] in_view View viewpoint.
|
||||||
|
* @param[in] in_index Index of the extension to query
|
||||||
|
* @param[out] out_ext_status Pointer to receive the extension status token, or invalid token if not found.
|
||||||
|
* If the extension is not found, it usually means that this extension is not registered in the specified scope.
|
||||||
|
* The caller take the ownership of created extension status object.
|
||||||
|
* And it should be freed by calling WFExtStatusDestroy() when it is no longer needed.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramQueryExt(Token in_program, View in_view, size_t in_index, Token *out_ext_status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy an extension status object
|
||||||
|
*
|
||||||
|
* @param[in] in_ext_status Extension status token to destroy
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFExtStatusDestroy(Token in_ext_status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the display name from an extension status object
|
||||||
|
*
|
||||||
|
* The display will be user specified first,
|
||||||
|
* the fallback to its ProgId verbatim.
|
||||||
|
*
|
||||||
|
* @param[in] in_ext_status Extension status token
|
||||||
|
* @param[out] out_name Pointer to receive the name.
|
||||||
|
* There is no possibility that this value is NULL.
|
||||||
|
* We will try to use localized name first, then use raw ProgId name if localized name is not available.
|
||||||
|
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFExtStatusGetName(Token in_ext_status, CStyleString *out_name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the icon from an extension status object
|
||||||
|
*
|
||||||
|
* The icon will be user specified first,
|
||||||
|
* the fallback to the system default file icon.
|
||||||
|
*
|
||||||
|
* @param[in] in_ext_status Extension status token
|
||||||
|
* @param[out] out_icon Pointer to receive the icon handle.
|
||||||
|
* This icon handle will be freed once this icon resource object is destroyed.
|
||||||
|
* Please make a copy immediately if you need to use it longer.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFExtStatusGetIcon(Token in_ext_status, HICON *out_icon);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy a self extension status object
|
||||||
|
*
|
||||||
|
* @param[in] in_self_ext_status Self extension status token to destroy
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSelfExtStatusDestroy(Token in_self_ext_status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the display name from a self extension status object
|
||||||
|
*
|
||||||
|
* The display will be user specified first,
|
||||||
|
* the fallback to its ProgId verbatim.
|
||||||
|
*
|
||||||
|
* @param[in] in_self_ext_status Self extension status token
|
||||||
|
* @param[out] out_name Pointer to receive the name string.
|
||||||
|
* There is no possibility that this value is NULL.
|
||||||
|
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSelfExtStatusGetName(Token in_self_ext_status, CStyleString *out_name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the icon from a self extension status object
|
||||||
|
*
|
||||||
|
* The icon will be user specified first,
|
||||||
|
* the fallback to the system default file icon.
|
||||||
|
*
|
||||||
|
* @param[in] in_self_ext_status Self extension status token
|
||||||
|
* @param[out] out_icon Pointer to receive the icon handle.
|
||||||
|
* This icon handle will be freed once this self extension status object is destroyed.
|
||||||
|
* Please make a copy immediately if you need to use it longer.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSelfExtStatusGetIcon(Token in_self_ext_status, HICON *out_icon);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the extension string (without leading dot) from a self extension status object
|
||||||
|
*
|
||||||
|
* @param[in] in_self_ext_status Self extension status token
|
||||||
|
* @param[out] out_inner Pointer to receive the file extension name (without leading dot).
|
||||||
|
* There is no possibility that this value is NULL.
|
||||||
|
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSelfExtStatusGetExt(Token in_self_ext_status, CStyleString *out_inner);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the dotted extension string (with leading dot) from a self extension status object
|
||||||
|
*
|
||||||
|
* @param[in] in_self_ext_status Self extension status token
|
||||||
|
* @param[out] out_inner Pointer to receive the file extension string (with leading dot).
|
||||||
|
* There is no possibility that this value is NULL.
|
||||||
|
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFSelfExtStatusGetDottedExt(Token in_self_ext_status, CStyleString *out_inner);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy an icon resource object
|
||||||
|
*
|
||||||
|
* @param[in] in_icon_rc Icon resource token to destroy
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFIconRcDestroy(Token in_icon_rc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the icon handle from an icon resource object
|
||||||
|
*
|
||||||
|
* @param[in] in_icon_rc Icon resource token
|
||||||
|
* @param[out] out_icon Pointer to receive the icon handle.
|
||||||
|
* There is no possibility that this value is INVALID_HICON.
|
||||||
|
* This icon handle will be freed once this icon resource object is destroyed.
|
||||||
|
* Please make a copy immediately if you need to use it longer.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFIconRcGetIcon(Token in_icon_rc, HICON *out_icon);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // extern "C"
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // namespace wfassoc
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#endif // WFASSOC_H_
|
||||||
103
wfassoc-cdylib/src/cstr_ffi.rs
Normal file
103
wfassoc-cdylib/src/cstr_ffi.rs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
//! When calling this dynamic library with outside programs,
|
||||||
|
//! outer programs may usually need to fetch string resource produced by Rust code.
|
||||||
|
//! However it is impossible pass Rust string directly to outer program.
|
||||||
|
//!
|
||||||
|
//! This module provide **thread independent** string cache for resolving this issue.
|
||||||
|
//! When we need pass string to outer programs, we push that string into this string as C-like format,
|
||||||
|
//! then return its pointer to outer program.
|
||||||
|
//! So that outside program can utilize it like calling C/C++ library.
|
||||||
|
//! The only thing that outer programs should note is that this string is volatile,
|
||||||
|
//! once they get it, they must dupliate it immediately before any futher calling to this dynamic library.
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ffi::{CStr, CString, c_char};
|
||||||
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
|
/// The type representing the raw pointer to immutable C-style NUL-terminated string.
|
||||||
|
pub type CStyleString = *const c_char;
|
||||||
|
|
||||||
|
// region: Error
|
||||||
|
|
||||||
|
/// Error occurs in this crate.
|
||||||
|
#[derive(Debug, TeError)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("unexpected NUL when parsing into C/C++ string")]
|
||||||
|
UnexpectedNul(#[from] std::ffi::NulError),
|
||||||
|
|
||||||
|
#[error("given string pointer is nullptr when parsing from C/C++ string")]
|
||||||
|
NullPtr,
|
||||||
|
#[error("invalid UTF8 sequence when parsing from C/C++ string")]
|
||||||
|
InvalidEncoding(#[from] std::str::Utf8Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result type used in this crate.
|
||||||
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: String Cache for Exposing
|
||||||
|
|
||||||
|
struct StringCache {
|
||||||
|
msg: CString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StringCache {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
msg: CString::new("").expect("empty string must be valid for CString"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_msg(&mut self, msg: &str) -> Result<()> {
|
||||||
|
self.msg = CString::new(msg)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_msg(&self) -> CStyleString {
|
||||||
|
self.msg.as_ptr()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_msg(&mut self) {
|
||||||
|
self.msg = CString::new("").expect("empty string must be valid for CString");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Exposed Functions
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static STRING_CACHE: RefCell<StringCache> = RefCell::new(StringCache::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set thread local string exposed for C code.
|
||||||
|
pub fn set_ffi_string(msg: &str) -> Result<()> {
|
||||||
|
STRING_CACHE.with(|e| {
|
||||||
|
e.borrow_mut().set_msg(msg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get const pointer to thread local string exposed for C code.
|
||||||
|
pub fn get_ffi_string() -> CStyleString {
|
||||||
|
STRING_CACHE.with(|e| e.borrow().get_msg())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear thread local string exposed for C code.
|
||||||
|
///
|
||||||
|
/// This function usually should be called at the beginning of every exposed C functions.
|
||||||
|
pub fn clear_ffi_string() {
|
||||||
|
STRING_CACHE.with(|e| {
|
||||||
|
e.borrow_mut().clear_msg();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse string given by C code into Rust string.
|
||||||
|
pub fn parse_ffi_string<'a>(ptr: CStyleString) -> Result<&'a str> {
|
||||||
|
if ptr.is_null() {
|
||||||
|
Err(Error::NullPtr)
|
||||||
|
} else {
|
||||||
|
let c_str = unsafe { CStr::from_ptr(ptr) };
|
||||||
|
Ok(c_str.to_str()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
73
wfassoc-cdylib/src/ffi_types.rs
Normal file
73
wfassoc-cdylib/src/ffi_types.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
//! The module including all FFI types used by this crate, except string type.
|
||||||
|
//! For string type, see also [crate::cstr_ffi].
|
||||||
|
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
|
// region: File Extension Index
|
||||||
|
|
||||||
|
pub const INVALID_INDEX: usize = usize::MAX;
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: HICON
|
||||||
|
|
||||||
|
/// The type representing Win32 HICON handle.
|
||||||
|
///
|
||||||
|
/// In theory, we can fetch HICON type from "windows_sys" crate.
|
||||||
|
/// However, I don't want to add it as this crate's dependency,
|
||||||
|
/// because I don't use anything within it except this type.
|
||||||
|
/// So I check Microsoft document, re-define it in there for this crate.
|
||||||
|
/// Reference: https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
|
||||||
|
pub type HICON = *mut c_void;
|
||||||
|
|
||||||
|
/// The invalid value of Win32 HICON handle.
|
||||||
|
///
|
||||||
|
/// The same reason like [HICON] to re-define it in there.
|
||||||
|
pub const INVALID_HICON: HICON = std::ptr::null_mut();
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Scope
|
||||||
|
|
||||||
|
/// The FFI wrapper for [wfassoc::Scope].
|
||||||
|
#[repr(u32)]
|
||||||
|
#[derive(Debug, Copy, Clone, TryFromPrimitive)]
|
||||||
|
pub enum Scope {
|
||||||
|
User = 0,
|
||||||
|
System = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Scope> for wfassoc::Scope {
|
||||||
|
fn from(value: Scope) -> Self {
|
||||||
|
match value {
|
||||||
|
Scope::User => wfassoc::Scope::User,
|
||||||
|
Scope::System => wfassoc::Scope::System,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: View
|
||||||
|
|
||||||
|
/// The FFI wrapper for [wfassoc::View].
|
||||||
|
#[repr(u32)]
|
||||||
|
#[derive(Debug, Copy, Clone, TryFromPrimitive)]
|
||||||
|
pub enum View {
|
||||||
|
User = 0,
|
||||||
|
System = 1,
|
||||||
|
Hybrid = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<View> for wfassoc::View {
|
||||||
|
fn from(value: View) -> Self {
|
||||||
|
match value {
|
||||||
|
View::User => wfassoc::View::User,
|
||||||
|
View::System => wfassoc::View::System,
|
||||||
|
View::Hybrid => wfassoc::View::Hybrid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
@@ -1,24 +1,41 @@
|
|||||||
use std::cell::RefCell;
|
mod cstr_ffi;
|
||||||
use std::ffi::{CString, c_char};
|
mod ffi_types;
|
||||||
use thiserror::Error as TeError;
|
|
||||||
|
|
||||||
mod last_error;
|
mod last_error;
|
||||||
mod string_cache;
|
mod object_pool;
|
||||||
mod wrapper;
|
|
||||||
|
use object_pool::ObjectPool;
|
||||||
|
use std::sync::{LazyLock, RwLock};
|
||||||
|
use thiserror::Error as TeError;
|
||||||
|
use wfassoc::highlevel::{Program, Schema};
|
||||||
|
|
||||||
// region: Error
|
// region: Error
|
||||||
|
|
||||||
/// Error occurs in this crate.
|
/// Error occurs in this crate.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
enum Error {
|
enum Error {
|
||||||
/// Error occurs in wfassoc core.
|
/// Error when operating Schema.
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Core(#[from] wfassoc::Error),
|
Schema(#[from] wfassoc::highlevel::SchemaError),
|
||||||
|
/// Error when parsing Schema into Program.
|
||||||
|
#[error("{0}")]
|
||||||
|
ParseProgram(#[from] wfassoc::highlevel::ParseProgramError),
|
||||||
|
/// Error when operating Program.
|
||||||
|
#[error("{0}")]
|
||||||
|
Program(#[from] wfassoc::highlevel::ProgramError),
|
||||||
|
|
||||||
#[error("error occurs when parsing into C/C++ string")]
|
/// Error when manipulating with C-style string.
|
||||||
CastIntoCStr(#[from] std::ffi::NulError),
|
#[error("C-Style string FFI error:{0}")]
|
||||||
#[error("error occurs when parsing from C/C++ string")]
|
CStrFfi(#[from] cstr_ffi::Error),
|
||||||
CastFromCStr(#[from] std::ffi::IntoStringError),
|
/// Error when manipulating with object pool.
|
||||||
|
#[error("object pool error: {0}")]
|
||||||
|
ObjectPool(#[from] object_pool::Error),
|
||||||
|
|
||||||
|
/// Error occurs when checking enum value
|
||||||
|
#[error("the enumeration value provided to FFI function is out of its range")]
|
||||||
|
EnumOutOfRange,
|
||||||
|
/// Error when manipulating with poison RwLock
|
||||||
|
#[error("concurrency error: RwLock is poisonous")]
|
||||||
|
PoisonRwLock,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result type used in this crate.
|
/// Result type used in this crate.
|
||||||
@@ -28,31 +45,749 @@ type Result<T> = std::result::Result<T, Error>;
|
|||||||
|
|
||||||
// region: Macros
|
// region: Macros
|
||||||
|
|
||||||
|
macro_rules! in_param_ty {
|
||||||
|
($t:ty) => {
|
||||||
|
$t
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! out_param_ty {
|
||||||
|
($t:ty) => {
|
||||||
|
*mut $t
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! set_out_param {
|
||||||
|
($lhs:expr, $rhs:expr) => {
|
||||||
|
unsafe { *$lhs = $rhs };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! resolve_enum {
|
||||||
|
($t:ty, $v:expr) => {
|
||||||
|
<$t>::try_from($v).map_err(|_| Error::EnumOutOfRange)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! pull_reader {
|
||||||
|
($pool:expr) => {
|
||||||
|
$pool.read().map_err(|_| Error::PoisonRwLock)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! pull_writer {
|
||||||
|
($pool:expr) => {
|
||||||
|
$pool.write().map_err(|_| Error::PoisonRwLock)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro to wrap inner function execution with standard error handling pattern.
|
||||||
|
///
|
||||||
|
/// For functions with no output parameter and no input parameters:
|
||||||
|
/// ```ignore
|
||||||
|
/// cffi_wrapper!(|| {
|
||||||
|
/// // inner function body returning Result<()>
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// For functions with no output parameter and with input parameters:
|
||||||
|
/// ```ignore
|
||||||
|
/// cffi_wrapper!(|param1: Type1, param2: Type2| {
|
||||||
|
/// // inner function body using param1, param2 returning Result<()>
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// For functions with one output parameter and no input parameters:
|
||||||
|
/// ```ignore
|
||||||
|
/// cffi_wrapper!(|| -> (out_param, OutType) {
|
||||||
|
/// // inner function body returning Result<T>
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// For functions with one output parameter and with input parameters:
|
||||||
|
/// ```ignore
|
||||||
|
/// cffi_wrapper!(|param1: Type1| -> (out_param, OutType) {
|
||||||
|
/// // inner function body using param1 returning Result<T>
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
macro_rules! cffi_wrapper {
|
||||||
|
// Case with output parameter and input parameters
|
||||||
|
(|$($param:ident: $param_ty:ty),*| -> ($out_param:ident: $out_param_ty:ty) $inner_body:block) => {{
|
||||||
|
fn inner($($param: $param_ty),*) -> Result<$out_param_ty> $inner_body
|
||||||
|
|
||||||
|
match inner($($param),*) {
|
||||||
|
Ok(rv) => {
|
||||||
|
set_out_param!($out_param, rv);
|
||||||
|
last_error::clear_last_error();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
last_error::set_last_error(e.to_string().as_str());
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
// Case with output parameter and no input parameters
|
||||||
|
(|| -> ($out_param:ident: $out_param_ty:ty) $inner_body:block) => {{
|
||||||
|
fn inner() -> Result<$out_param_ty> $inner_body
|
||||||
|
|
||||||
|
match inner() {
|
||||||
|
Ok(rv) => {
|
||||||
|
set_out_param!($out_param, rv);
|
||||||
|
last_error::clear_last_error();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
last_error::set_last_error(e.to_string().as_str());
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
// Case without output parameter but with input parameters
|
||||||
|
(|$($param:ident: $param_ty:ty),*| $inner_body:block) => {{
|
||||||
|
fn inner($($param: $param_ty),*) -> Result<()> $inner_body
|
||||||
|
|
||||||
|
match inner($($param),*) {
|
||||||
|
Ok(_) => {
|
||||||
|
last_error::clear_last_error();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
last_error::set_last_error(e.to_string().as_str());
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
// Case without output parameter and no input parameters
|
||||||
|
(|| $inner_body:block) => {{
|
||||||
|
fn inner() -> Result<()> $inner_body
|
||||||
|
|
||||||
|
match inner() {
|
||||||
|
Ok(_) => {
|
||||||
|
last_error::clear_last_error();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
last_error::set_last_error(e.to_string().as_str());
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
// region: Object Pools
|
||||||
|
|
||||||
|
static SCHEMA_POOL: LazyLock<RwLock<ObjectPool<Schema>>> =
|
||||||
|
LazyLock::new(|| RwLock::new(ObjectPool::new()));
|
||||||
|
|
||||||
|
static PROGRAM_POOL: LazyLock<RwLock<ObjectPool<Program>>> =
|
||||||
|
LazyLock::new(|| RwLock::new(ObjectPool::new()));
|
||||||
|
|
||||||
|
static EXT_STATUS_POOL: LazyLock<RwLock<ObjectPool<wfassoc::highlevel::ProgramExtStatus>>> =
|
||||||
|
LazyLock::new(|| RwLock::new(ObjectPool::new()));
|
||||||
|
|
||||||
|
static SELF_EXT_STATUS_POOL: LazyLock<
|
||||||
|
RwLock<ObjectPool<wfassoc::highlevel::ProgramSelfExtStatus>>,
|
||||||
|
> = LazyLock::new(|| RwLock::new(ObjectPool::new()));
|
||||||
|
|
||||||
|
static ICON_RC_POOL: LazyLock<RwLock<ObjectPool<wfassoc::win32::concept::IconRc>>> =
|
||||||
|
LazyLock::new(|| RwLock::new(ObjectPool::new()));
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Exposed Types
|
||||||
|
|
||||||
|
pub use cstr_ffi::CStyleString;
|
||||||
|
pub use ffi_types::{HICON, Scope, View};
|
||||||
|
pub use object_pool::Token;
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Exposed Functions
|
||||||
|
|
||||||
|
// region: Facilities
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFStartup() -> bool {
|
pub extern "C" fn WFStartup() -> bool {
|
||||||
false
|
// Initialize all pool by fetching writer from them
|
||||||
|
cffi_wrapper!(|| {
|
||||||
|
let _pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let _pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let _pool = pull_writer!(EXT_STATUS_POOL)?;
|
||||||
|
let _pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
|
||||||
|
let _pool = pull_writer!(ICON_RC_POOL)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFShutdown() -> bool {
|
pub extern "C" fn WFShutdown() -> bool {
|
||||||
false
|
// Free all pool stored objects
|
||||||
|
cffi_wrapper!(|| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
pool.clear();
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
pool.clear();
|
||||||
|
let mut pool = pull_writer!(EXT_STATUS_POOL)?;
|
||||||
|
pool.clear();
|
||||||
|
let mut pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
|
||||||
|
pool.clear();
|
||||||
|
let mut pool = pull_writer!(ICON_RC_POOL)?;
|
||||||
|
pool.clear();
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFGetLastError() -> *const c_char {
|
pub extern "C" fn WFGetLastError() -> CStyleString {
|
||||||
last_error::get_last_error()
|
last_error::get_last_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFHasPrivilege() -> bool {
|
pub extern "C" fn WFHasPrivilege() -> bool {
|
||||||
wfassoc::utilities::has_privilege()
|
wfassoc::win32::utilities::has_privilege()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFAdd(left: u32, right: u32, rv: *mut u32) -> bool {
|
pub extern "C" fn WFInvalidToken() -> Token {
|
||||||
unsafe { *rv = left + right; }
|
object_pool::invalid_token()
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Schema
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaCreate(out_schema: out_param_ty!(Token)) -> bool {
|
||||||
|
cffi_wrapper!(|| -> (out_schema: Token) {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
Ok(pool.allocate(Schema::new())?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaDestroy(in_schema: in_param_ty!(Token)) -> bool {
|
||||||
|
cffi_wrapper!(|in_schema: Token| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
Ok(pool.free(in_schema)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaSetIdentifier(
|
||||||
|
in_schema: in_param_ty!(Token),
|
||||||
|
in_value: in_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
schema.set_identifier(cstr_ffi::parse_ffi_string(in_value)?);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaSetPath(
|
||||||
|
in_schema: in_param_ty!(Token),
|
||||||
|
in_value: in_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
schema.set_path(cstr_ffi::parse_ffi_string(in_value)?);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaSetClsid(
|
||||||
|
in_schema: in_param_ty!(Token),
|
||||||
|
in_value: in_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
schema.set_clsid(cstr_ffi::parse_ffi_string(in_value)?);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaSetName(
|
||||||
|
in_schema: in_param_ty!(Token),
|
||||||
|
in_value: in_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
|
||||||
|
let name = if in_value.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(cstr_ffi::parse_ffi_string(in_value)?)
|
||||||
|
};
|
||||||
|
schema.set_name(name);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaSetIcon(
|
||||||
|
in_schema: in_param_ty!(Token),
|
||||||
|
in_value: in_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
|
||||||
|
let icon = if in_value.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(cstr_ffi::parse_ffi_string(in_value)?)
|
||||||
|
};
|
||||||
|
schema.set_icon(icon);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaSetBehavior(
|
||||||
|
in_schema: in_param_ty!(Token),
|
||||||
|
in_value: in_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
|
||||||
|
let behavior = if in_value.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(cstr_ffi::parse_ffi_string(in_value)?)
|
||||||
|
};
|
||||||
|
schema.set_behavior(behavior);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaAddStr(
|
||||||
|
in_schema: in_param_ty!(Token),
|
||||||
|
in_name: in_param_ty!(CStyleString),
|
||||||
|
in_value: in_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(
|
||||||
|
|in_schema: Token, in_name: CStyleString, in_value: CStyleString| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
schema.add_str(
|
||||||
|
cstr_ffi::parse_ffi_string(in_name)?,
|
||||||
|
cstr_ffi::parse_ffi_string(in_value)?,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaAddIcon(
|
||||||
|
in_schema: in_param_ty!(Token),
|
||||||
|
in_name: in_param_ty!(CStyleString),
|
||||||
|
in_value: in_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(
|
||||||
|
|in_schema: Token, in_name: CStyleString, in_value: CStyleString| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
schema.add_icon(
|
||||||
|
cstr_ffi::parse_ffi_string(in_name)?,
|
||||||
|
cstr_ffi::parse_ffi_string(in_value)?,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaAddBehavior(
|
||||||
|
in_schema: in_param_ty!(Token),
|
||||||
|
in_name: in_param_ty!(CStyleString),
|
||||||
|
in_value: in_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(
|
||||||
|
|in_schema: Token, in_name: CStyleString, in_value: CStyleString| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
schema.add_behavior(
|
||||||
|
cstr_ffi::parse_ffi_string(in_name)?,
|
||||||
|
cstr_ffi::parse_ffi_string(in_value)?,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSchemaAddExt(
|
||||||
|
in_schema: in_param_ty!(Token),
|
||||||
|
in_ext: in_param_ty!(CStyleString),
|
||||||
|
in_ext_name: in_param_ty!(CStyleString),
|
||||||
|
in_ext_icon: in_param_ty!(CStyleString),
|
||||||
|
in_ext_behavior: in_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_schema: Token,
|
||||||
|
in_ext: CStyleString,
|
||||||
|
in_ext_name: CStyleString,
|
||||||
|
in_ext_icon: CStyleString,
|
||||||
|
in_ext_behavior: CStyleString| {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
schema.add_ext(
|
||||||
|
cstr_ffi::parse_ffi_string(in_ext)?,
|
||||||
|
cstr_ffi::parse_ffi_string(in_ext_name)?,
|
||||||
|
cstr_ffi::parse_ffi_string(in_ext_icon)?,
|
||||||
|
cstr_ffi::parse_ffi_string(in_ext_behavior)?,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Program
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramCreate(
|
||||||
|
in_schema: in_param_ty!(Token),
|
||||||
|
out_program: out_param_ty!(Token),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_schema: Token| -> (out_program: Token) {
|
||||||
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
|
let schema = pool.pop(in_schema)?;
|
||||||
|
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let program = Program::new(schema)?;
|
||||||
|
Ok(pool.allocate(program)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramDestroy(in_program: in_param_ty!(Token)) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token| {
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
Ok(pool.free(in_program)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramResolveName(
|
||||||
|
in_program: in_param_ty!(Token),
|
||||||
|
out_name: out_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token| -> (out_name: CStyleString) {
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get_mut(in_program)?;
|
||||||
|
|
||||||
|
let name = program.resolve_name()?;
|
||||||
|
cstr_ffi::set_ffi_string(&name)?;
|
||||||
|
Ok(cstr_ffi::get_ffi_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramResolveIcon(
|
||||||
|
in_program: in_param_ty!(Token),
|
||||||
|
out_icon_rc: out_param_ty!(Token),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token| -> (out_icon_rc: Token) {
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get_mut(in_program)?;
|
||||||
|
|
||||||
|
let icon = program.resolve_icon()?;
|
||||||
|
let mut pool = pull_writer!(ICON_RC_POOL)?;
|
||||||
|
Ok(pool.allocate(icon)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramExtsLen(
|
||||||
|
in_program: in_param_ty!(Token),
|
||||||
|
out_len: out_param_ty!(usize),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token| -> (out_len: usize) {
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get_mut(in_program)?;
|
||||||
|
Ok(program.exts_len())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramFindExt(
|
||||||
|
in_program: in_param_ty!(Token),
|
||||||
|
in_body: in_param_ty!(CStyleString),
|
||||||
|
out_index: out_param_ty!(usize),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token, in_body: CStyleString| -> (out_index: usize) {
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get_mut(in_program)?;
|
||||||
|
|
||||||
|
let body = cstr_ffi::parse_ffi_string(in_body)?;
|
||||||
|
let index = match program.find_ext(body) {
|
||||||
|
Some(index) => index,
|
||||||
|
None => ffi_types::INVALID_INDEX,
|
||||||
|
};
|
||||||
|
Ok(index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramResolveExt(
|
||||||
|
in_program: in_param_ty!(Token),
|
||||||
|
in_index: in_param_ty!(usize),
|
||||||
|
out_self_ext_status: out_param_ty!(Token),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token, in_index: usize| -> (out_self_ext_status: Token) {
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get_mut(in_program)?;
|
||||||
|
|
||||||
|
let self_ext_status = program.resolve_ext(in_index)?;
|
||||||
|
let mut pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
|
||||||
|
let token = pool.allocate(self_ext_status)?;
|
||||||
|
Ok(token)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramRegister(
|
||||||
|
in_program: in_param_ty!(Token),
|
||||||
|
in_scope: in_param_ty!(u32),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token, in_scope: u32| {
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get_mut(in_program)?;
|
||||||
|
let scope = resolve_enum!(Scope, in_scope)?;
|
||||||
|
program.register(scope.into())?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramUnregister(
|
||||||
|
in_program: in_param_ty!(Token),
|
||||||
|
in_scope: in_param_ty!(u32),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token, in_scope: u32| {
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get_mut(in_program)?;
|
||||||
|
let scope = resolve_enum!(Scope, in_scope)?;
|
||||||
|
program.unregister(scope.into())?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramIsRegistered(
|
||||||
|
in_program: in_param_ty!(Token),
|
||||||
|
in_scope: in_param_ty!(u32),
|
||||||
|
out_is_registered: out_param_ty!(bool),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token, in_scope: u32| -> (out_is_registered: bool) {
|
||||||
|
let pool = pull_reader!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get(in_program)?;
|
||||||
|
let scope = resolve_enum!(Scope, in_scope)?;
|
||||||
|
Ok(program.is_registered(scope.into())?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramLinkExt(
|
||||||
|
in_program: in_param_ty!(Token),
|
||||||
|
in_scope: in_param_ty!(u32),
|
||||||
|
in_index: in_param_ty!(usize),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token, in_scope: u32, in_index: usize| {
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get_mut(in_program)?;
|
||||||
|
let scope = resolve_enum!(Scope, in_scope)?;
|
||||||
|
program.link_ext(scope.into(), in_index)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramUnlinkExt(
|
||||||
|
in_program: in_param_ty!(Token),
|
||||||
|
in_scope: in_param_ty!(u32),
|
||||||
|
in_index: in_param_ty!(usize),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token, in_scope: u32, in_index: usize| {
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get_mut(in_program)?;
|
||||||
|
let scope = resolve_enum!(Scope, in_scope)?;
|
||||||
|
program.unlink_ext(scope.into(), in_index)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFProgramQueryExt(
|
||||||
|
in_program: in_param_ty!(Token),
|
||||||
|
in_view: in_param_ty!(u32),
|
||||||
|
in_index: in_param_ty!(usize),
|
||||||
|
out_ext_status: out_param_ty!(Token),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: Token, in_view: u32, in_index: usize| -> (out_ext_status: Token) {
|
||||||
|
let pool = pull_reader!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get(in_program)?;
|
||||||
|
let view = resolve_enum!(View, in_view)?;
|
||||||
|
|
||||||
|
let ext_status = program.query_ext(view.into(), in_index)?;
|
||||||
|
let token = match ext_status {
|
||||||
|
Some(ext_status) => {
|
||||||
|
let mut pool = pull_writer!(EXT_STATUS_POOL)?;
|
||||||
|
pool.allocate(ext_status)?
|
||||||
|
},
|
||||||
|
None => object_pool::invalid_token(),
|
||||||
|
};
|
||||||
|
Ok(token)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Extension Status
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFExtStatusDestroy(in_ext_status: in_param_ty!(Token)) -> bool {
|
||||||
|
cffi_wrapper!(|in_ext_status: Token| {
|
||||||
|
let mut pool = pull_writer!(EXT_STATUS_POOL)?;
|
||||||
|
Ok(pool.free(in_ext_status)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFExtStatusGetName(
|
||||||
|
in_ext_status: in_param_ty!(Token),
|
||||||
|
out_name: out_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_ext_status: Token| -> (out_name: CStyleString) {
|
||||||
|
let pool = pull_reader!(EXT_STATUS_POOL)?;
|
||||||
|
let ext_status = pool.get(in_ext_status)?;
|
||||||
|
|
||||||
|
cstr_ffi::set_ffi_string(ext_status.get_name())?;
|
||||||
|
Ok(cstr_ffi::get_ffi_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFExtStatusGetIcon(
|
||||||
|
in_ext_status: in_param_ty!(Token),
|
||||||
|
out_icon: out_param_ty!(HICON),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_ext_status: Token| -> (out_icon: HICON) {
|
||||||
|
let pool = pull_reader!(EXT_STATUS_POOL)?;
|
||||||
|
let ext_status = pool.get(in_ext_status)?;
|
||||||
|
|
||||||
|
let icon = ext_status.get_icon();
|
||||||
|
Ok(icon.get_icon())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Self Extension Status
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSelfExtStatusDestroy(in_self_ext_status: in_param_ty!(Token)) -> bool {
|
||||||
|
cffi_wrapper!(|in_self_ext_status: Token| {
|
||||||
|
let mut pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
|
||||||
|
Ok(pool.free(in_self_ext_status)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSelfExtStatusGetName(
|
||||||
|
in_self_ext_status: in_param_ty!(Token),
|
||||||
|
out_name: out_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_self_ext_status: Token| -> (out_name: CStyleString) {
|
||||||
|
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
||||||
|
let self_ext_status = pool.get(in_self_ext_status)?;
|
||||||
|
|
||||||
|
cstr_ffi::set_ffi_string(self_ext_status.get_name())?;
|
||||||
|
Ok(cstr_ffi::get_ffi_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSelfExtStatusGetIcon(
|
||||||
|
in_self_ext_status: in_param_ty!(Token),
|
||||||
|
out_icon: out_param_ty!(HICON),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_self_ext_status: Token| -> (out_icon: HICON) {
|
||||||
|
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
||||||
|
let self_ext_status = pool.get(in_self_ext_status)?;
|
||||||
|
|
||||||
|
let icon = self_ext_status.get_icon();
|
||||||
|
Ok(icon.get_icon())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSelfExtStatusGetExt(
|
||||||
|
in_self_ext_status: in_param_ty!(Token),
|
||||||
|
out_inner: out_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_self_ext_status: Token| -> (out_inner: CStyleString) {
|
||||||
|
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
||||||
|
let self_ext_status = pool.get(in_self_ext_status)?;
|
||||||
|
|
||||||
|
cstr_ffi::set_ffi_string(self_ext_status.get_ext())?;
|
||||||
|
Ok(cstr_ffi::get_ffi_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSelfExtStatusGetDottedExt(
|
||||||
|
in_self_ext_status: in_param_ty!(Token),
|
||||||
|
out_inner: out_param_ty!(CStyleString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_self_ext_status: Token| -> (out_inner: CStyleString) {
|
||||||
|
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
||||||
|
let self_ext_status = pool.get(in_self_ext_status)?;
|
||||||
|
|
||||||
|
cstr_ffi::set_ffi_string(self_ext_status.get_dotted_ext().as_str())?;
|
||||||
|
Ok(cstr_ffi::get_ffi_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Icon Resource
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFIconRcDestroy(in_icon_rc: in_param_ty!(Token)) -> bool {
|
||||||
|
cffi_wrapper!(|in_icon_rc: Token| {
|
||||||
|
let mut pool = pull_writer!(ICON_RC_POOL)?;
|
||||||
|
Ok(pool.free(in_icon_rc)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFIconRcGetIcon(
|
||||||
|
in_icon_rc: in_param_ty!(Token),
|
||||||
|
out_icon: out_param_ty!(HICON),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_icon_rc: Token| -> (out_icon: HICON) {
|
||||||
|
let pool = pull_reader!(ICON_RC_POOL)?;
|
||||||
|
let icon_rc = pool.get(in_icon_rc)?;
|
||||||
|
|
||||||
|
Ok(icon_rc.get_icon())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|||||||
95
wfassoc-cdylib/src/object_pool.rs
Normal file
95
wfassoc-cdylib/src/object_pool.rs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
//! When exporting resources for C interface, resource management and ownership are important things.
|
||||||
|
//! In this dynamic library, we hold all resources' ownership in Rust world,
|
||||||
|
//! and only expose a token for C code manipulation.
|
||||||
|
//!
|
||||||
|
//! We need to create a container for holding all resources and providing corresponding operations.
|
||||||
|
//! So we introduce [ObjectPool] in this module for this purpose.
|
||||||
|
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
|
||||||
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
|
/// Error occurs when operating with [ObjectPool].
|
||||||
|
#[derive(Debug, TeError)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("given token is not presented in object pool")]
|
||||||
|
NoSuchToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The token for fetching object in [ObjectPool].
|
||||||
|
pub type Token = u64;
|
||||||
|
|
||||||
|
/// Get the invalid token.
|
||||||
|
///
|
||||||
|
/// Invalid token is always invalid for fetching object in pool,
|
||||||
|
/// And can be useful in FFI scenario.
|
||||||
|
pub fn invalid_token() -> Token {
|
||||||
|
DefaultKey::null().data().as_ffi()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A pool for managing objects with unique tokens.
|
||||||
|
///
|
||||||
|
/// It is highly suggested to use this pool with [std::sync::RwLock] guard.
|
||||||
|
pub struct ObjectPool<T> {
|
||||||
|
objs: SlotMap<DefaultKey, T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ObjectPool<T> {
|
||||||
|
/// Create a new [ObjectPool].
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
objs: SlotMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert [slotmap] crate's [DefaultKey] to our [Token].
|
||||||
|
fn key_to_token(key: &DefaultKey) -> Token {
|
||||||
|
key.data().as_ffi()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert our [Token] to [slotmap] crate's [DefaultKey].
|
||||||
|
fn token_to_key(token: Token) -> DefaultKey {
|
||||||
|
DefaultKey::from(KeyData::from_ffi(token))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Put given object into the pool and return its token.
|
||||||
|
///
|
||||||
|
/// The ownership of given object is transferred to the pool.
|
||||||
|
pub fn allocate(&mut self, value: T) -> Result<Token, Error> {
|
||||||
|
let key = self.objs.insert(value);
|
||||||
|
Ok(Self::key_to_token(&key))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Free the object in the pool corresponding to the given token.
|
||||||
|
pub fn free(&mut self, token: Token) -> Result<(), Error> {
|
||||||
|
let _ = self.pop(token)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the object from the pool corresponding to the given token and return it.
|
||||||
|
///
|
||||||
|
/// The ownership of the object is transferred to the caller.
|
||||||
|
pub fn pop(&mut self, token: Token) -> Result<T, Error> {
|
||||||
|
match self.objs.remove(Self::token_to_key(token)) {
|
||||||
|
Some(obj) => Ok(obj),
|
||||||
|
None => Err(Error::NoSuchToken),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear all objects in the pool.
|
||||||
|
pub fn clear(&mut self) -> () {
|
||||||
|
self.objs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the object in the pool corresponding to the given token.
|
||||||
|
pub fn get(&self, token: Token) -> Result<&T, Error> {
|
||||||
|
self.objs
|
||||||
|
.get(Self::token_to_key(token))
|
||||||
|
.ok_or(Error::NoSuchToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the object in the pool corresponding to the given token.
|
||||||
|
pub fn get_mut(&mut self, token: Token) -> Result<&mut T, Error> {
|
||||||
|
self.objs
|
||||||
|
.get_mut(Self::token_to_key(token))
|
||||||
|
.ok_or(Error::NoSuchToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
//! When calling this dynamic library with outside programs,
|
|
||||||
//! outer programs may usually need to fetch string resource produced by Rust code.
|
|
||||||
//! However it is impossible pass Rust string directly to outer program.
|
|
||||||
//!
|
|
||||||
//! This module provide **thread independent** string cache for resolving this issue.
|
|
||||||
//! When we need pass string to outer programs, we push that string into this string as C-like format,
|
|
||||||
//! then return its pointer to outer program.
|
|
||||||
//! So that outside program can utilize it like calling C/C++ library.
|
|
||||||
//! The only thing that outer programs should note is that this string is volatile,
|
|
||||||
//! once they get it, they must dupliate it immediately before any futher calling to this dynamic library.
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::ffi::{CString, c_char};
|
|
||||||
|
|
||||||
struct StringCache {
|
|
||||||
msg: CString,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StringCache {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
msg: CString::new("").expect("empty string must be valid for CString"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_msg(&mut self, msg: &str) {
|
|
||||||
self.msg = CString::new(msg).expect("unexpected blank in string push into string cache");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_msg(&self) -> *const c_char {
|
|
||||||
self.msg.as_ptr()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_msg(&mut self) {
|
|
||||||
self.msg = CString::new("").expect("empty string must be valid for CString");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
static STRING_CACHE: RefCell<StringCache> = RefCell::new(StringCache::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set thread local string cache.
|
|
||||||
pub fn set_string_cache(msg: &str) {
|
|
||||||
STRING_CACHE.with(|e| {
|
|
||||||
e.borrow_mut().set_msg(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get const pointer to thread local string cache for outside program visiting.
|
|
||||||
pub fn get_string_cache() -> *const c_char {
|
|
||||||
STRING_CACHE.with(|e| e.borrow().get_msg())
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use thiserror::Error as TeError;
|
|
||||||
|
|
||||||
/// Error occurs in this module.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
pub enum Error {}
|
|
||||||
|
|
||||||
/// Result type used in this module.
|
|
||||||
type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// Schema is the sketchpad of complete Program.
|
|
||||||
///
|
|
||||||
/// We will create a Schema first, fill some properties, add file extensions,
|
|
||||||
/// then convert it into immutable Program for following using.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Schema {
|
|
||||||
identifier: String,
|
|
||||||
path: String,
|
|
||||||
clsid: String,
|
|
||||||
icons: HashMap<String, String>,
|
|
||||||
behaviors: HashMap<String, String>,
|
|
||||||
exts: HashMap<String, SchemaExt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal used struct as the Schema file extensions hashmap value type.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct SchemaExt {
|
|
||||||
name: String,
|
|
||||||
icon: String,
|
|
||||||
behavior: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Schema {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
identifier: String::new(),
|
|
||||||
path: String::new(),
|
|
||||||
clsid: String::new(),
|
|
||||||
icons: HashMap::new(),
|
|
||||||
behaviors: HashMap::new(),
|
|
||||||
exts: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_identifier(&mut self, identifier: &str) -> Result<()> {}
|
|
||||||
|
|
||||||
pub fn set_path(&mut self, exe_path: &str) -> Result<()> {}
|
|
||||||
|
|
||||||
pub fn set_clsid(&mut self, clsid: &str) -> Result<()> {}
|
|
||||||
|
|
||||||
pub fn add_icon(&mut self, name: &str, value: &str) -> Result<()> {}
|
|
||||||
|
|
||||||
pub fn add_behavior(&mut self, name: &str, value: &str) -> Result<()> {}
|
|
||||||
|
|
||||||
pub fn add_ext(
|
|
||||||
&mut self,
|
|
||||||
ext: &str,
|
|
||||||
ext_name: &str,
|
|
||||||
ext_icon: &str,
|
|
||||||
ext_behavior: &str,
|
|
||||||
) -> Result<()> {
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_program(self) -> Result<Program> {
|
|
||||||
Program::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Program is a complete and immutable program representer
|
|
||||||
pub struct Program {}
|
|
||||||
|
|
||||||
impl TryFrom<Schema> for Program {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: Schema) -> std::result::Result<Self, Self::Error> {
|
|
||||||
Self::new(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
pub fn new(schema: Schema) -> Result<Self> {}
|
|
||||||
}
|
|
||||||
@@ -83,8 +83,8 @@ pub enum CliCommands {
|
|||||||
#[command(about = "Fetch the status of registration with given manifest and scope.")]
|
#[command(about = "Fetch the status of registration with given manifest and scope.")]
|
||||||
Status {
|
Status {
|
||||||
/// The view where fetch info.
|
/// The view where fetch info.
|
||||||
#[arg(short = 't', long = "target", value_name = "TARGET", required = true, value_enum, default_value_t = RegView::User)]
|
#[arg(short = 't', long = "target", value_name = "TARGET", required = true, value_enum, default_value_t = RegScope::User)]
|
||||||
target: RegView,
|
target: RegScope,
|
||||||
},
|
},
|
||||||
#[command(name = "ext")]
|
#[command(name = "ext")]
|
||||||
#[command(about = "File extension related operations according to given program manifest.")]
|
#[command(about = "File extension related operations according to given program manifest.")]
|
||||||
@@ -114,7 +114,7 @@ pub enum CliExtCommands {
|
|||||||
/// The scope where unlink file extension.
|
/// The scope where unlink file extension.
|
||||||
#[arg(short = 't', long = "target", value_name = "TARGET", required = true, value_enum, default_value_t = RegScope::User)]
|
#[arg(short = 't', long = "target", value_name = "TARGET", required = true, value_enum, default_value_t = RegScope::User)]
|
||||||
target: RegScope,
|
target: RegScope,
|
||||||
// The file extensions used for this operation. Specify * for all file extension.
|
// The file extensions (without leading dot) used for this operation. Specify * for all file extension.
|
||||||
#[arg(required = true, value_name = "EXTS", num_args = 1..)]
|
#[arg(required = true, value_name = "EXTS", num_args = 1..)]
|
||||||
exts: Vec<String>,
|
exts: Vec<String>,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
pub(crate) mod cli;
|
|
||||||
pub(crate) mod manifest;
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use cli::{Cli, Commands, Target};
|
|
||||||
use comfy_table::Table;
|
|
||||||
use manifest::Manifest;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::process;
|
|
||||||
use thiserror::Error as TeError;
|
|
||||||
use wfassoc::{Program, Scope, Token, View};
|
|
||||||
|
|
||||||
// region: Basic Types
|
|
||||||
|
|
||||||
/// All errors occurs in this executable.
|
|
||||||
#[derive(TeError, Debug)]
|
|
||||||
enum Error {
|
|
||||||
/// Error from wfassoc core.
|
|
||||||
#[error("{0}")]
|
|
||||||
Core(#[from] wfassoc::Error),
|
|
||||||
/// Error when parsing manifest TOML file.
|
|
||||||
#[error("invalid manifest file: {0}")]
|
|
||||||
Manifest(#[from] manifest::Error),
|
|
||||||
|
|
||||||
/// Error when specifying invalid manner name for extension.
|
|
||||||
#[error("extension {ext} associated manner {manner} is invalid in manifest file")]
|
|
||||||
InvalidMannerName { manner: String, ext: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Result type used in this executable.
|
|
||||||
type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Correponding Runner
|
|
||||||
|
|
||||||
struct Composition {
|
|
||||||
program: Program,
|
|
||||||
ext_tokens: Vec<Token>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Composition {
|
|
||||||
pub fn new(cli: &Cli) -> Result<Composition> {
|
|
||||||
// Open file and read manifest TOML file
|
|
||||||
let mf = Manifest::from_file(&cli.config_file)?;
|
|
||||||
// Create instance
|
|
||||||
let mut program = Program::new(&mf.identifier, &cli.config_file)?;
|
|
||||||
// Setup manner
|
|
||||||
let mut manner_token_map: HashMap<&str, Token> = HashMap::new();
|
|
||||||
for (k, v) in mf.manners.iter() {
|
|
||||||
let token = program.add_manner(v.as_str())?;
|
|
||||||
manner_token_map.insert(k.as_str(), token);
|
|
||||||
}
|
|
||||||
// Setup extension
|
|
||||||
let mut ext_tokens = Vec::new();
|
|
||||||
for (k, v) in mf.exts.iter() {
|
|
||||||
let token = match manner_token_map.get(v.as_str()) {
|
|
||||||
Some(v) => v,
|
|
||||||
None => {
|
|
||||||
return Err(Error::InvalidMannerName {
|
|
||||||
manner: v.to_string(),
|
|
||||||
ext: k.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let token = program.add_ext(k.as_str(), *token)?;
|
|
||||||
ext_tokens.push(token);
|
|
||||||
}
|
|
||||||
// Okey
|
|
||||||
Ok(Self {
|
|
||||||
program,
|
|
||||||
ext_tokens,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_program(&self) -> &Program {
|
|
||||||
&self.program
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_ext_tokens(&self) -> impl Iterator<Item = Token> {
|
|
||||||
self.ext_tokens.iter().copied()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_register(cli: &Cli, target: &Target) -> Result<()> {
|
|
||||||
let composition = Composition::new(cli)?;
|
|
||||||
let scope = target.clone().into();
|
|
||||||
|
|
||||||
// Register program
|
|
||||||
let program = composition.get_program();
|
|
||||||
program.register(scope)?;
|
|
||||||
// Link all file extensions
|
|
||||||
for token in composition.iter_ext_tokens() {
|
|
||||||
program.link_ext(token, scope)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Register OK.");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_unregister(cli: &Cli, target: &Target) -> Result<()> {
|
|
||||||
let composition = Composition::new(cli)?;
|
|
||||||
let scope = target.clone().into();
|
|
||||||
|
|
||||||
// Unlink all file extensions
|
|
||||||
let program = composition.get_program();
|
|
||||||
for token in composition.iter_ext_tokens() {
|
|
||||||
program.unlink_ext(token, scope)?;
|
|
||||||
}
|
|
||||||
// Unregister prorgam
|
|
||||||
program.unregister(scope)?;
|
|
||||||
|
|
||||||
println!("Unregister OK.");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_query(cli: &Cli) -> Result<()> {
|
|
||||||
let composition = Composition::new(cli)?;
|
|
||||||
|
|
||||||
// Show file association
|
|
||||||
let mut table = Table::new();
|
|
||||||
table.set_header(["Extension", "Hybrid", "User", "System"]);
|
|
||||||
|
|
||||||
let program = composition.get_program();
|
|
||||||
for token in composition.iter_ext_tokens() {
|
|
||||||
let cell_ext = program.get_ext_str(token).unwrap();
|
|
||||||
let cell_hybrid = program
|
|
||||||
.query_ext(token, View::Hybrid)?
|
|
||||||
.map(|pi| pi.to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
let cell_user = program
|
|
||||||
.query_ext(token, View::User)?
|
|
||||||
.map(|pi| pi.to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
let cell_system = program
|
|
||||||
.query_ext(token, View::System)?
|
|
||||||
.map(|pi| pi.to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
table.add_row([cell_ext, cell_hybrid, cell_user, cell_system]);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{table}");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let cli = Cli::parse();
|
|
||||||
|
|
||||||
let rv = match &cli.command {
|
|
||||||
Commands::Register { target } => run_register(&cli, target),
|
|
||||||
Commands::Unregister { target } => run_unregister(&cli, target),
|
|
||||||
Commands::Query => run_query(&cli),
|
|
||||||
};
|
|
||||||
rv.unwrap_or_else(|e| {
|
|
||||||
eprintln!("Runtime error: {}.", e);
|
|
||||||
process::exit(1)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
use crate::cli;
|
use crate::cli;
|
||||||
use crate::manifest;
|
use crate::manifest;
|
||||||
|
use comfy_table::Table;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use thiserror::Error as TeError;
|
use thiserror::Error as TeError;
|
||||||
|
use toml;
|
||||||
|
|
||||||
// region: Error Handling
|
// region: Error Handling
|
||||||
|
|
||||||
@@ -20,6 +23,19 @@ pub enum Error {
|
|||||||
/// Error when operating Program.
|
/// Error when operating Program.
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Program(#[from] wfassoc::highlevel::ProgramError),
|
Program(#[from] wfassoc::highlevel::ProgramError),
|
||||||
|
/// Error when serializing TOML
|
||||||
|
#[error("{0}")]
|
||||||
|
SerializeToml(#[from] toml::ser::Error),
|
||||||
|
|
||||||
|
/// Find duplicated name when converting extension name to index
|
||||||
|
#[error("given extension name {0} has been specified more than one time")]
|
||||||
|
DupExtName(String),
|
||||||
|
/// Find invalid name when converting extension name to index
|
||||||
|
#[error("given extension name {0} is not presented in application")]
|
||||||
|
BadExtName(String),
|
||||||
|
/// Find star (*) extension name with other extension names when converting extension name to index
|
||||||
|
#[error("wildcard extension name \"*\" is not allowed to be used with other extension names")]
|
||||||
|
ExclusiveStarExtName(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result type used in this module.
|
/// Result type used in this module.
|
||||||
@@ -27,10 +43,38 @@ type Result<T> = std::result::Result<T, Error>;
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region: Utilities Functions
|
// region: Utilities
|
||||||
|
|
||||||
fn stringified_exts_to_indices(program: &wfassoc::Program, exts: Vec<String>) -> Result<Vec<usize>> {
|
fn stringified_exts_to_indices(
|
||||||
todo!()
|
program: &wfassoc::Program,
|
||||||
|
exts: Vec<String>,
|
||||||
|
) -> Result<Vec<usize>> {
|
||||||
|
// Check for duplicate extension names
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
for ext in &exts {
|
||||||
|
if !seen.insert(ext.as_str()) {
|
||||||
|
return Err(Error::DupExtName(ext.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for star (*) with other extensions
|
||||||
|
let has_star = exts.iter().any(|ext| ext == "*");
|
||||||
|
if has_star && exts.len() > 1 {
|
||||||
|
return Err(Error::ExclusiveStarExtName("*".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If star is present alone, return fixed list from zero to the maximum ext index.
|
||||||
|
if has_star {
|
||||||
|
return Ok((0..program.exts_len()).collect());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert each extension name to index using program.find_ext()
|
||||||
|
let indices = exts
|
||||||
|
.into_iter()
|
||||||
|
.map(|ext| program.find_ext(&ext).ok_or(Error::BadExtName(ext)))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
Ok(indices)
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
@@ -38,15 +82,19 @@ fn stringified_exts_to_indices(program: &wfassoc::Program, exts: Vec<String>) ->
|
|||||||
// region: Respective Runners
|
// region: Respective Runners
|
||||||
|
|
||||||
fn run_register(mut program: wfassoc::Program, scope: wfassoc::Scope) -> Result<()> {
|
fn run_register(mut program: wfassoc::Program, scope: wfassoc::Scope) -> Result<()> {
|
||||||
Ok(program.register(scope)?)
|
program.register(scope)?;
|
||||||
|
println!("Application now is installed.");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_unregister(mut program: wfassoc::Program, scope: wfassoc::Scope) -> Result<()> {
|
fn run_unregister(mut program: wfassoc::Program, scope: wfassoc::Scope) -> Result<()> {
|
||||||
Ok(program.unregister(scope)?)
|
program.unregister(scope)?;
|
||||||
|
println!("Application now is uninstalled.");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_status(program: wfassoc::Program, view: wfassoc::View) -> Result<()> {
|
fn run_status(program: wfassoc::Program, scope: wfassoc::Scope) -> Result<()> {
|
||||||
if program.is_registered(view)? {
|
if program.is_registered(scope)? {
|
||||||
println!("Application is installed.");
|
println!("Application is installed.");
|
||||||
} else {
|
} else {
|
||||||
println!("Application is not installed.");
|
println!("Application is not installed.");
|
||||||
@@ -55,16 +103,8 @@ fn run_status(program: wfassoc::Program, view: wfassoc::View) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_ext_link(program: wfassoc::Program, scope: wfassoc::Scope, exts: Vec<String>) -> Result<()> {
|
fn run_ext_link(
|
||||||
let exts = stringified_exts_to_indices(&program, exts)?;
|
mut program: wfassoc::Program,
|
||||||
for index in exts {
|
|
||||||
program.link_ext(scope, index)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_ext_unlink(
|
|
||||||
program: wfassoc::Program,
|
|
||||||
scope: wfassoc::Scope,
|
scope: wfassoc::Scope,
|
||||||
exts: Vec<String>,
|
exts: Vec<String>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -72,6 +112,22 @@ fn run_ext_unlink(
|
|||||||
for index in exts {
|
for index in exts {
|
||||||
program.link_ext(scope, index)?;
|
program.link_ext(scope, index)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("File extension now is linked.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_ext_unlink(
|
||||||
|
mut program: wfassoc::Program,
|
||||||
|
scope: wfassoc::Scope,
|
||||||
|
exts: Vec<String>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let exts = stringified_exts_to_indices(&program, exts)?;
|
||||||
|
for index in exts {
|
||||||
|
program.link_ext(scope, index)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("File extension now is unlinked.");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +136,34 @@ fn run_ext_list(
|
|||||||
view: wfassoc::View,
|
view: wfassoc::View,
|
||||||
style: cli::ExtListStyle,
|
style: cli::ExtListStyle,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
todo!()
|
// Fetch info
|
||||||
|
let mut ext_list: HashMap<String, Option<String>> = HashMap::new();
|
||||||
|
for index in 0..program.exts_len() {
|
||||||
|
let self_ext_status = program.resolve_ext(index)?;
|
||||||
|
let status = program.query_ext(view, index)?;
|
||||||
|
ext_list.insert(
|
||||||
|
self_ext_status.get_dotted_ext(),
|
||||||
|
status.map(|s| s.get_name().to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output by styles
|
||||||
|
use cli::ExtListStyle;
|
||||||
|
match style {
|
||||||
|
ExtListStyle::Human => {
|
||||||
|
let mut table = Table::new();
|
||||||
|
table.set_header(["Extension", "Association"]);
|
||||||
|
for (k, v) in ext_list {
|
||||||
|
table.add_row([k, v.unwrap_or("".to_string())]);
|
||||||
|
}
|
||||||
|
println!("{table}");
|
||||||
|
}
|
||||||
|
ExtListStyle::Machine => {
|
||||||
|
let stoml = toml::to_string(&ext_list)?;
|
||||||
|
println!("{stoml}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
@@ -88,7 +171,6 @@ fn run_ext_list(
|
|||||||
pub fn run(c: cli::Cli) -> Result<()> {
|
pub fn run(c: cli::Cli) -> Result<()> {
|
||||||
// Read manifest file first
|
// Read manifest file first
|
||||||
let mf = manifest::Manifest::from_file(Path::new(&c.manifest_file))?;
|
let mf = manifest::Manifest::from_file(Path::new(&c.manifest_file))?;
|
||||||
println!("{:?}", mf);
|
|
||||||
// Parse it into schema
|
// Parse it into schema
|
||||||
let schema = mf.into_schema()?;
|
let schema = mf.into_schema()?;
|
||||||
// Parse it into program
|
// Parse it into program
|
||||||
|
|||||||
@@ -22,3 +22,6 @@ widestring = "1.2.1"
|
|||||||
indexmap = "2.11.4"
|
indexmap = "2.11.4"
|
||||||
regex = "1.11.3"
|
regex = "1.11.3"
|
||||||
uuid = { version = "1.18.1", features = ["v4"] }
|
uuid = { version = "1.18.1", features = ["v4"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
strfmt = "0.2.5"
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# WFassoc Core
|
# WFassoc Core
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
This crate provides low level API and high level API for manipulating Windows file associations at the same time.
|
This crate provides low level API and high level API for manipulating Windows file associations at the same time.
|
||||||
For the convenient use of this project, the root module of this crate re-expose high level API.
|
For the convenient use of this project, the root module of this crate re-expose high level API.
|
||||||
So programmers can directly use them.
|
So programmers can directly use them.
|
||||||
@@ -10,3 +12,21 @@ Oppositely, for visiting low level API, please use `lowlevel` module.
|
|||||||
|
|
||||||
If you are a programmer who want to take a deep into the internal implementations,
|
If you are a programmer who want to take a deep into the internal implementations,
|
||||||
see `win32` module and its submodules for detail.
|
see `win32` module and its submodules for detail.
|
||||||
|
|
||||||
|
## Test Notes
|
||||||
|
|
||||||
|
Some tests of this crate may be dangerous because they need to manipulate Windows Registry.
|
||||||
|
So it is highlt recommend that run these tests in sandbox environment.
|
||||||
|
In detailed words, you should run `cargo test --no-run` to build all test first,
|
||||||
|
then fetch the path to executable tests according to this command shown on console.
|
||||||
|
Then execute these executable tests in your sandbox for testing this crate.
|
||||||
|
Additionally, some tests also need Administration permission for testing,
|
||||||
|
because it requires write permission in HKLM.
|
||||||
|
|
||||||
|
If you do not test it with sandbox and administrative environment,
|
||||||
|
test program will assert paniked and tell you how to resolve these issues.
|
||||||
|
|
||||||
|
The reason why do not run `cargo test` in sandbox environment directly,
|
||||||
|
is that `cargo` can not find built tests located in host machine.
|
||||||
|
It will try to fetch all dependencies again and rebuild test in sandbox entirely.
|
||||||
|
So we use this complex way for testing.
|
||||||
|
|||||||
@@ -1,391 +0,0 @@
|
|||||||
//! The module including all struct representing Windows file association concept,
|
|
||||||
//! like file extension, ProgId, CLSID and etc.
|
|
||||||
|
|
||||||
use crate::extra::windows::{Ext, ProgId};
|
|
||||||
use crate::extra::winreg as winreg_extra;
|
|
||||||
use crate::utilities;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use thiserror::Error as TeError;
|
|
||||||
use winreg::RegKey;
|
|
||||||
use winreg::enums::{
|
|
||||||
HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// region: Error Types
|
|
||||||
|
|
||||||
/// All possible error occurs in this crate.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("error occurs when manipulating with Registry: {0}")]
|
|
||||||
BadRegOper(#[from] std::io::Error),
|
|
||||||
#[error("{0}")]
|
|
||||||
ParseExt(#[from] crate::extra::windows::ParseExtError),
|
|
||||||
#[error("{0}")]
|
|
||||||
ParseProgId(#[from] crate::extra::windows::ParseProgIdError),
|
|
||||||
#[error("{0}")]
|
|
||||||
ParseProgIdKind(#[from] ParseProgIdKindError),
|
|
||||||
#[error("{0}")]
|
|
||||||
BlankPath(#[from] crate::extra::winreg::BlankPathError),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result type used in this crate.
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Data Types
|
|
||||||
|
|
||||||
/// The token for access registered items in Program.
|
|
||||||
/// This is usually returned when you registering them.
|
|
||||||
pub type Token = usize;
|
|
||||||
|
|
||||||
// region: Scope
|
|
||||||
|
|
||||||
/// The scope where wfassoc will register and unregister application.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum Scope {
|
|
||||||
/// Scope for current user.
|
|
||||||
User,
|
|
||||||
/// Scope for all users under this computer.
|
|
||||||
System,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error occurs when cast View into Scope.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
#[error("hybrid View can not be cast into Scope")]
|
|
||||||
pub struct TryFromViewError {}
|
|
||||||
|
|
||||||
impl TryFromViewError {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<View> for Scope {
|
|
||||||
type Error = TryFromViewError;
|
|
||||||
|
|
||||||
fn try_from(value: View) -> std::result::Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
View::User => Ok(Self::User),
|
|
||||||
View::System => Ok(Self::System),
|
|
||||||
View::Hybrid => Err(TryFromViewError::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scope {
|
|
||||||
/// Check whether we have enough privilege when operating in current scope.
|
|
||||||
/// If we have, return true, otherwise false.
|
|
||||||
pub fn has_privilege(&self) -> bool {
|
|
||||||
// If we operate on System, and we do not has privilege,
|
|
||||||
// we think we do not have privilege, otherwise,
|
|
||||||
// there is no privilege required.
|
|
||||||
!matches!(self, Self::System if !utilities::has_privilege())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: View
|
|
||||||
|
|
||||||
/// The view when wfassoc querying file extension association.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum View {
|
|
||||||
/// The view of current user.
|
|
||||||
User,
|
|
||||||
/// The view of system.
|
|
||||||
System,
|
|
||||||
/// Hybrid view of User and System.
|
|
||||||
/// It can be seen as that we use System first and then use User to override any existing items.
|
|
||||||
Hybrid,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Scope> for View {
|
|
||||||
fn from(value: Scope) -> Self {
|
|
||||||
match value {
|
|
||||||
Scope::User => Self::User,
|
|
||||||
Scope::System => Self::System,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: ProgId Kind
|
|
||||||
|
|
||||||
/// The error occurs when parsing ProgId kind.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
pub enum ParseProgIdKindError {
|
|
||||||
#[error("{0}")]
|
|
||||||
FromStd(#[from] crate::extra::windows::ParseProgIdError),
|
|
||||||
#[error("given ProgId is blank")]
|
|
||||||
BlankProgId,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The variant of ProgId for the compatibility
|
|
||||||
/// with those software which do not follow Microsoft suggestions.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub enum ProgIdKind {
|
|
||||||
/// Other ProgId which not follow Microsoft standards.
|
|
||||||
Other(String),
|
|
||||||
/// Standard ProgId.
|
|
||||||
Std(ProgId),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ProgIdKind {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
ProgIdKind::Other(v) => write!(f, "{}", v),
|
|
||||||
ProgIdKind::Std(prog_id) => write!(f, "{}", prog_id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for ProgIdKind {
|
|
||||||
type Err = ParseProgIdKindError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
|
||||||
if s.is_empty() {
|
|
||||||
Err(ParseProgIdKindError::BlankProgId)
|
|
||||||
} else {
|
|
||||||
Ok(match s.parse::<ProgId>() {
|
|
||||||
Ok(v) => Self::Std(v),
|
|
||||||
Err(_) => Self::Other(s.to_string()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Registry Visitor
|
|
||||||
|
|
||||||
// region: Application Visitor
|
|
||||||
|
|
||||||
/// The static struct for visiting "Application" in registry
|
|
||||||
pub struct ApplicationVisitor {}
|
|
||||||
|
|
||||||
impl ApplicationVisitor {
|
|
||||||
const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths";
|
|
||||||
const APPLICATIONS: &str = "Software\\Classes\\Applications";
|
|
||||||
|
|
||||||
/// Open a readonly registry key to "App Paths" with given scope.
|
|
||||||
pub fn open_app_paths(scope: Scope) -> Result<RegKey> {
|
|
||||||
let hk = RegKey::predef(match scope {
|
|
||||||
Scope::User => HKEY_CURRENT_USER,
|
|
||||||
Scope::System => HKEY_LOCAL_MACHINE,
|
|
||||||
});
|
|
||||||
let app_paths = hk.open_subkey_with_flags(Self::APP_PATHS, KEY_READ)?;
|
|
||||||
|
|
||||||
Ok(app_paths)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Open a readonly registry key to "Applications" with given scope.
|
|
||||||
pub fn open_applications(scope: Scope) -> Result<RegKey> {
|
|
||||||
let hk = RegKey::predef(match scope {
|
|
||||||
Scope::User => HKEY_CURRENT_USER,
|
|
||||||
Scope::System => HKEY_LOCAL_MACHINE,
|
|
||||||
});
|
|
||||||
let applications = hk.open_subkey_with_flags(Self::APPLICATIONS, KEY_READ)?;
|
|
||||||
|
|
||||||
Ok(applications)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Classes Visitor
|
|
||||||
|
|
||||||
/// The static struct for visiting "Classes" in registry.
|
|
||||||
pub struct ClassesVisitor {}
|
|
||||||
|
|
||||||
impl ClassesVisitor {
|
|
||||||
const CLASSES: &str = "Software\\Classes";
|
|
||||||
|
|
||||||
/// Open a readonly registry key to "Classes" with given view.
|
|
||||||
pub fn open_with_view(view: View) -> Result<RegKey> {
|
|
||||||
// Fetch root key and navigate to Classes
|
|
||||||
let hk = match view {
|
|
||||||
View::User => RegKey::predef(HKEY_CURRENT_USER),
|
|
||||||
View::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
|
||||||
View::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT),
|
|
||||||
};
|
|
||||||
let classes = match view {
|
|
||||||
View::User | View::System => hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?,
|
|
||||||
View::Hybrid => hk.open_subkey_with_flags("", KEY_READ)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(classes)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Open a readonly registry key to "Classes" with given scope.
|
|
||||||
pub fn open_with_scope(scope: Scope) -> Result<RegKey> {
|
|
||||||
// Fetch root key and navigate to Classes
|
|
||||||
let hk = RegKey::predef(match scope {
|
|
||||||
Scope::User => HKEY_CURRENT_USER,
|
|
||||||
Scope::System => HKEY_LOCAL_MACHINE,
|
|
||||||
});
|
|
||||||
let classes = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?;
|
|
||||||
|
|
||||||
Ok(classes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: File Extension Registry Key
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct ExtKey {
|
|
||||||
ext: Ext,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtKey {
|
|
||||||
/// Create new file extension registry key representer.
|
|
||||||
pub fn new(s: &str) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
ext: Ext::from_str(s)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch the reference to inner extension representer.
|
|
||||||
pub fn as_inner(&self) -> &Ext {
|
|
||||||
&self.ext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtKey {
|
|
||||||
/// Set the default "Open With" of this file extension to given ProgId.
|
|
||||||
pub fn link(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> {
|
|
||||||
use winreg_extra::blank_path_guard;
|
|
||||||
|
|
||||||
// Open Classes key
|
|
||||||
let classes = ClassesVisitor::open_with_scope(scope)?;
|
|
||||||
|
|
||||||
// Open or create this extension key
|
|
||||||
let (subkey, _) =
|
|
||||||
classes.create_subkey_with_flags(blank_path_guard(self.ext.to_string())?, KEY_WRITE)?;
|
|
||||||
// Set the default way to open this file extension
|
|
||||||
subkey.set_value("", &prog_id.as_inner().to_string())?;
|
|
||||||
|
|
||||||
// Okey
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset the default "Open With" of this file extension to blank.
|
|
||||||
///
|
|
||||||
/// If the default "Open With" of this file extension is not given ProgId,
|
|
||||||
/// or there is no such file extension, this function do nothing.
|
|
||||||
pub fn unlink(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> {
|
|
||||||
use winreg_extra::{blank_path_guard, try_get_value, try_open_subkey_with_flags};
|
|
||||||
|
|
||||||
// Open Classes key
|
|
||||||
let classes = ClassesVisitor::open_with_scope(scope)?;
|
|
||||||
|
|
||||||
// Open key for this extension.
|
|
||||||
// If there is no such key, return directly.
|
|
||||||
if let Some(subkey) = try_open_subkey_with_flags(
|
|
||||||
&classes,
|
|
||||||
blank_path_guard(self.ext.to_string())?,
|
|
||||||
KEY_WRITE,
|
|
||||||
)? {
|
|
||||||
// Only delete the default key if it is equal to our ProgId
|
|
||||||
if let Some(value) = try_get_value::<String, _>(&subkey, "")? {
|
|
||||||
if value == prog_id.as_inner().to_string() {
|
|
||||||
// Delete the default key.
|
|
||||||
subkey.delete_value("")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Okey
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query the default "Open With" of this file extension associated ProgId.
|
|
||||||
///
|
|
||||||
/// This function will return its associated ProgId if "Open With" was set.
|
|
||||||
pub fn query(&self, view: View) -> Result<Option<ProgIdKey>> {
|
|
||||||
use winreg_extra::{blank_path_guard, try_get_value, try_open_subkey_with_flags};
|
|
||||||
|
|
||||||
// Open Classes key
|
|
||||||
let classes = ClassesVisitor::open_with_view(view)?;
|
|
||||||
|
|
||||||
// Open key for this extension if possible
|
|
||||||
let rv = match try_open_subkey_with_flags(
|
|
||||||
&classes,
|
|
||||||
blank_path_guard(self.ext.to_string())?,
|
|
||||||
KEY_READ,
|
|
||||||
)? {
|
|
||||||
Some(subkey) => {
|
|
||||||
// Try get associated ProgId if possible
|
|
||||||
match try_get_value::<String, _>(&subkey, "")? {
|
|
||||||
Some(value) => Some(ProgIdKey::new(value.as_str())?),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Okey
|
|
||||||
Ok(rv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: ProgId Registry Key
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct ProgIdKey {
|
|
||||||
prog_id: ProgIdKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProgIdKey {
|
|
||||||
/// Create new ProgId registry representer.
|
|
||||||
pub fn new(s: &str) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
prog_id: ProgIdKind::from_str(s)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch the reference to inner ProgId.
|
|
||||||
pub fn as_inner(&self) -> &ProgIdKind {
|
|
||||||
&self.prog_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProgIdKey {
|
|
||||||
/// Create ProgId into Registry in given scope with given parameters
|
|
||||||
pub fn create(&self, scope: Scope, command: &str) -> Result<()> {
|
|
||||||
use winreg_extra::blank_path_guard;
|
|
||||||
|
|
||||||
let classes = ClassesVisitor::open_with_scope(scope)?;
|
|
||||||
let (subkey, _) = classes
|
|
||||||
.create_subkey_with_flags(blank_path_guard(self.prog_id.to_string())?, KEY_WRITE)?;
|
|
||||||
|
|
||||||
// Create verb
|
|
||||||
let (subkey_verb, _) = subkey.create_subkey_with_flags("open", KEY_READ)?;
|
|
||||||
let (subkey_command, _) = subkey_verb.create_subkey_with_flags("command", KEY_WRITE)?;
|
|
||||||
subkey_command.set_value("", &command.to_string())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete this ProgId from registry in given scope.
|
|
||||||
pub fn delete(&self, scope: Scope) -> Result<()> {
|
|
||||||
use winreg_extra::blank_path_guard;
|
|
||||||
|
|
||||||
let classes = ClassesVisitor::open_with_scope(scope)?;
|
|
||||||
classes.delete_subkey_all(blank_path_guard(self.prog_id.to_string())?)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
@@ -1,320 +1,33 @@
|
|||||||
use crate::{lowlevel, utilities, win32};
|
use crate::lowlevel;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::path::Path;
|
|
||||||
use thiserror::Error as TeError;
|
|
||||||
|
|
||||||
|
// region: Utilities
|
||||||
|
|
||||||
|
/// The println macro only works on Debug mode
|
||||||
|
/// for tracing the execution of some important functions.
|
||||||
|
macro_rules! debug_println {
|
||||||
|
// For no argument.
|
||||||
|
() => {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
eprintln!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// For one or more arguments like println!.
|
||||||
|
($($arg:tt)*) => {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
eprintln!($($arg)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Exposed Stuff
|
||||||
|
|
||||||
|
mod schema;
|
||||||
|
mod program;
|
||||||
|
|
||||||
|
pub use schema::{Schema, SchemaError};
|
||||||
|
pub use program::{Program, ParseProgramError, ProgramError, ProgramSelfExtStatus, ProgramExtStatus};
|
||||||
pub use lowlevel::{Scope, View};
|
pub use lowlevel::{Scope, View};
|
||||||
|
|
||||||
// region: Error Type
|
|
||||||
|
|
||||||
/// Error occurs when operating with `Schema`.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
pub enum SchemaError {
|
|
||||||
#[error("duplicate key: {0}")]
|
|
||||||
DuplicateKey(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error occurs when trying converting `Schema` into `Program`.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
pub enum ParseProgramError {
|
|
||||||
#[error("{0}")]
|
|
||||||
BadFileName(#[from] win32::concept::BadFileNameError),
|
|
||||||
#[error("{0}")]
|
|
||||||
CastOsStr(#[from] utilities::CastOsStrError),
|
|
||||||
#[error("given path doesn't has legal file name part")]
|
|
||||||
NoFileNamePart,
|
|
||||||
#[error("given path doesn't has legal directory part")]
|
|
||||||
NoDirNamePart,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error occurs when operating with `Program`.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
pub enum ProgramError {}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Schema
|
|
||||||
|
|
||||||
// region: Schema Body
|
|
||||||
|
|
||||||
/// Schema is the sketchpad of complete Program.
|
|
||||||
///
|
|
||||||
/// We will create a Schema first, fill some properties, add file extensions,
|
|
||||||
/// then convert it into immutable Program for following using.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Schema {
|
|
||||||
identifier: String,
|
|
||||||
path: String,
|
|
||||||
clsid: String,
|
|
||||||
|
|
||||||
name: Option<String>,
|
|
||||||
icon: Option<String>,
|
|
||||||
behavior: Option<String>,
|
|
||||||
|
|
||||||
strs: HashMap<String, String>,
|
|
||||||
icons: HashMap<String, String>,
|
|
||||||
behaviors: HashMap<String, String>,
|
|
||||||
exts: HashMap<String, SchemaExt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Schema {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
identifier: String::new(),
|
|
||||||
path: String::new(),
|
|
||||||
clsid: String::new(),
|
|
||||||
name: None,
|
|
||||||
icon: None,
|
|
||||||
behavior: None,
|
|
||||||
strs: HashMap::new(),
|
|
||||||
icons: HashMap::new(),
|
|
||||||
behaviors: HashMap::new(),
|
|
||||||
exts: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_identifier(&mut self, identifier: &str) -> () {
|
|
||||||
self.identifier = identifier.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_path(&mut self, exe_path: &str) -> () {
|
|
||||||
self.path = exe_path.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_clsid(&mut self, clsid: &str) -> () {
|
|
||||||
self.clsid = clsid.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_name(&mut self, name: Option<&str>) -> () {
|
|
||||||
self.name = name.map(|n| n.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_icon(&mut self, icon: Option<&str>) -> () {
|
|
||||||
self.icon = icon.map(|i| i.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_behavior(&mut self, behavior: Option<&str>) -> () {
|
|
||||||
self.behavior = behavior.map(|b| b.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_str(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
|
||||||
match self.strs.insert(name.to_string(), value.to_string()) {
|
|
||||||
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
|
||||||
None => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_icon(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
|
||||||
match self.icons.insert(name.to_string(), value.to_string()) {
|
|
||||||
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
|
||||||
None => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_behavior(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
|
||||||
match self.behaviors.insert(name.to_string(), value.to_string()) {
|
|
||||||
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
|
||||||
None => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_ext(
|
|
||||||
&mut self,
|
|
||||||
ext: &str,
|
|
||||||
ext_name: &str,
|
|
||||||
ext_icon: &str,
|
|
||||||
ext_behavior: &str,
|
|
||||||
) -> Result<(), SchemaError> {
|
|
||||||
match self.exts.insert(
|
|
||||||
ext.to_string(),
|
|
||||||
SchemaExt::new(ext_name, ext_icon, ext_behavior),
|
|
||||||
) {
|
|
||||||
Some(_) => Err(SchemaError::DuplicateKey(ext.to_string())),
|
|
||||||
None => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_program(self) -> Result<Program, ParseProgramError> {
|
|
||||||
Program::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Schema Internals
|
|
||||||
|
|
||||||
/// Internal used struct as the Schema file extensions hashmap value type.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct SchemaExt {
|
|
||||||
name: String,
|
|
||||||
icon: String,
|
|
||||||
behavior: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SchemaExt {
|
|
||||||
fn new(name: &str, icon: &str, behavior: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
name: name.to_string(),
|
|
||||||
icon: icon.to_string(),
|
|
||||||
behavior: behavior.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Program
|
|
||||||
|
|
||||||
// region: Program Body
|
|
||||||
|
|
||||||
/// Program is a complete and immutable program representer
|
|
||||||
pub struct Program {
|
|
||||||
app_paths_key: lowlevel::AppPathsKey,
|
|
||||||
applications_key: lowlevel::ApplicationsKey,
|
|
||||||
file_name: String,
|
|
||||||
dir_name: String,
|
|
||||||
name: Rc<ProgramStr>,
|
|
||||||
icon: Rc<ProgramIcon>,
|
|
||||||
behavior: Rc<ProgramBehavior>,
|
|
||||||
|
|
||||||
strs: Vec<Rc<ProgramStr>>,
|
|
||||||
icons: Vec<Rc<ProgramIcon>>,
|
|
||||||
behaviors: Vec<Rc<ProgramBehavior>>,
|
|
||||||
|
|
||||||
progid_keys: Vec<Rc<ProgramProgIdKey>>,
|
|
||||||
ext_keys: Vec<ProgramExtKey>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Schema> for Program {
|
|
||||||
type Error = ParseProgramError;
|
|
||||||
|
|
||||||
fn try_from(value: Schema) -> Result<Self, Self::Error> {
|
|
||||||
Self::new(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
/// Extract the file name part from full path to application,
|
|
||||||
/// which was used in Registry path component.
|
|
||||||
fn extract_file_name(full_path: &Path) -> Result<&OsStr, ParseProgramError> {
|
|
||||||
full_path
|
|
||||||
.file_name()
|
|
||||||
.ok_or(ParseProgramError::NoFileNamePart)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract the start in path from full path to application,
|
|
||||||
/// which basically is the stem of full path.
|
|
||||||
fn extract_dir_name(full_path: &Path) -> Result<&OsStr, ParseProgramError> {
|
|
||||||
full_path
|
|
||||||
.parent()
|
|
||||||
.map(|p| p.as_os_str())
|
|
||||||
.ok_or(ParseProgramError::NoDirNamePart)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try creating Program from Schema.
|
|
||||||
pub fn new(schema: Schema) -> Result<Self, ParseProgramError> {
|
|
||||||
// Extract file name part and directory name part respectively.
|
|
||||||
let schema_path = Path::new(&schema.path);
|
|
||||||
let file_name = Self::extract_file_name(schema_path)?;
|
|
||||||
let file_name = String::from(utilities::osstr_to_str(file_name)?);
|
|
||||||
let dir_name = Self::extract_dir_name(schema_path)?;
|
|
||||||
let dir_name = String::from(utilities::osstr_to_str(dir_name)?);
|
|
||||||
// Build app paths key and applications key respectively
|
|
||||||
let key = win32::concept::FileName::new(&file_name)?;
|
|
||||||
let app_paths_key = lowlevel::AppPathsKey::new(key.clone());
|
|
||||||
let applications_key = lowlevel::ApplicationsKey::new(key.clone());
|
|
||||||
|
|
||||||
todo!();
|
|
||||||
// Ok(Self {
|
|
||||||
// app_paths_key,
|
|
||||||
// applications_key,
|
|
||||||
// file_name,
|
|
||||||
// dir_name,
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
/// Register this application.
|
|
||||||
///
|
|
||||||
/// If there is registration of this application,
|
|
||||||
/// this function will return error.
|
|
||||||
pub fn register(&mut self, scope: Scope) -> Result<(), ProgramError> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unregister this application.
|
|
||||||
///
|
|
||||||
/// If there is no registration of this application,
|
|
||||||
/// this function will return error.
|
|
||||||
pub fn unregister(&mut self, scope: Scope) -> Result<(), ProgramError> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether this application has been registered in given view.
|
|
||||||
///
|
|
||||||
/// Please note that this is a rough check and do not validate any data.
|
|
||||||
///
|
|
||||||
/// The return value only ensures the pre-requirement of `register` and `unregister`.
|
|
||||||
pub fn is_registered(&self, view: View) -> Result<bool, ProgramError> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn link_ext(&self, scope: Scope, index: usize) -> Result<(), ProgramError> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unlink_ext(&self, scope: Scope, index: usize) -> Result<(), ProgramError> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ext_status(&self, view: View, index: usize) -> Result<(), ProgramError> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Program Internals
|
|
||||||
|
|
||||||
/// Internal used enum presenting a Program string resource.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ProgramStr {
|
|
||||||
inner: lowlevel::StrResVariant,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal used enum presenting a Program icon resource.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ProgramIcon {
|
|
||||||
inner: lowlevel::IconResVariant,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal used enum presenting a Program behavior (command line setups).
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ProgramBehavior {
|
|
||||||
inner: win32::concept::CmdLine,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal used struct presenting a Program ProgId.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ProgramProgIdKey {
|
|
||||||
key: lowlevel::ProgIdKey,
|
|
||||||
name: Rc<ProgramStr>,
|
|
||||||
icon: Rc<ProgramIcon>,
|
|
||||||
behavior: Rc<ProgramBehavior>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal used struct presenting a Program file extension.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ProgramExtKey {
|
|
||||||
key: lowlevel::ExtKey,
|
|
||||||
assoc: Rc<ProgramProgIdKey>
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|||||||
716
wfassoc/src/highlevel/program.rs
Normal file
716
wfassoc/src/highlevel/program.rs
Normal file
@@ -0,0 +1,716 @@
|
|||||||
|
use super::Schema;
|
||||||
|
use crate::{
|
||||||
|
lowlevel::{self, Scope, View},
|
||||||
|
utilities,
|
||||||
|
win32::{self, concept},
|
||||||
|
};
|
||||||
|
use regex::Regex;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
|
// region: Error Type
|
||||||
|
|
||||||
|
/// Error occurs when trying converting [Schema] into [Program].
|
||||||
|
#[derive(Debug, TeError)]
|
||||||
|
pub enum ParseProgramError {
|
||||||
|
#[error("{0}")]
|
||||||
|
BadExtBody(#[from] concept::BadExtBodyError),
|
||||||
|
#[error("{0}")]
|
||||||
|
BadProgIdPart(#[from] concept::BadProgIdPartError),
|
||||||
|
#[error("{0}")]
|
||||||
|
BadFileName(#[from] concept::BadFileNameError),
|
||||||
|
#[error("{0}")]
|
||||||
|
ParseCmdLine(#[from] concept::ParseCmdLineError),
|
||||||
|
#[error("{0}")]
|
||||||
|
CastOsStr(#[from] utilities::CastOsStrError),
|
||||||
|
#[error("given path doesn't has legal file name part")]
|
||||||
|
NoFileNamePart,
|
||||||
|
#[error("given path doesn't has legal directory part")]
|
||||||
|
NoDirNamePart,
|
||||||
|
#[error("given identifier is not presented in dict")]
|
||||||
|
NoSuchIdentifier,
|
||||||
|
#[error("extension name should not be empty")]
|
||||||
|
EmptyExtension,
|
||||||
|
#[error("given program identifier is not allowed")]
|
||||||
|
BadIdentifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error occurs when operating with [Program].
|
||||||
|
#[derive(Debug, TeError)]
|
||||||
|
pub enum ProgramError {
|
||||||
|
#[error("{0}")]
|
||||||
|
Lowlevel(#[from] lowlevel::Error),
|
||||||
|
#[error("{0}")]
|
||||||
|
LoadIconRc(#[from] concept::LoadIconRcError),
|
||||||
|
#[error("given index is invalid")]
|
||||||
|
BadIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Program
|
||||||
|
|
||||||
|
/// Program is a complete and immutable program representer
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Program {
|
||||||
|
app_paths_key: lowlevel::AppPathsKey,
|
||||||
|
applications_key: lowlevel::ApplicationsKey,
|
||||||
|
app_path: String,
|
||||||
|
app_file_name: String,
|
||||||
|
app_dir_path: String,
|
||||||
|
name: Option<Arc<ProgramStr>>,
|
||||||
|
icon: Option<Arc<ProgramIcon>>,
|
||||||
|
behavior: Option<Arc<ProgramBehavior>>,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
strs: Vec<Arc<ProgramStr>>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
icons: Vec<Arc<ProgramIcon>>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
behaviors: Vec<Arc<ProgramBehavior>>,
|
||||||
|
|
||||||
|
ext_keys: Vec<ProgramProgIdExtKey>,
|
||||||
|
ext_keys_map: HashMap<String, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Schema> for Program {
|
||||||
|
type Error = ParseProgramError;
|
||||||
|
|
||||||
|
fn try_from(value: Schema) -> Result<Self, Self::Error> {
|
||||||
|
Self::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Program {
|
||||||
|
/// Extract the file name part from full path to application,
|
||||||
|
/// which was used in Registry path component.
|
||||||
|
fn extract_file_name(full_path: &Path) -> Result<&OsStr, ParseProgramError> {
|
||||||
|
full_path
|
||||||
|
.file_name()
|
||||||
|
.ok_or(ParseProgramError::NoFileNamePart)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the start in path from full path to application,
|
||||||
|
/// which basically is the stem of full path.
|
||||||
|
fn extract_dir_path(full_path: &Path) -> Result<&OsStr, ParseProgramError> {
|
||||||
|
full_path
|
||||||
|
.parent()
|
||||||
|
.map(|p| p.as_os_str())
|
||||||
|
.ok_or(ParseProgramError::NoDirNamePart)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flat_hashmap<V, U, F>(
|
||||||
|
hashmap: &HashMap<String, V>,
|
||||||
|
f: F,
|
||||||
|
) -> Result<(Vec<U>, HashMap<String, usize>), ParseProgramError>
|
||||||
|
where
|
||||||
|
F: Fn(&V) -> Result<U, ParseProgramError>,
|
||||||
|
{
|
||||||
|
let mut indexmap: HashMap<String, usize> = HashMap::with_capacity(hashmap.len());
|
||||||
|
let mut vector: Vec<U> = Vec::with_capacity(hashmap.len());
|
||||||
|
for (key, value) in hashmap.into_iter() {
|
||||||
|
indexmap.insert(key.clone(), vector.len());
|
||||||
|
vector.push(f(value)?);
|
||||||
|
}
|
||||||
|
Ok((vector, indexmap))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_index<T>(
|
||||||
|
key: &str,
|
||||||
|
vector: &Vec<Arc<T>>,
|
||||||
|
index_map: &HashMap<String, usize>,
|
||||||
|
) -> Result<Arc<T>, ParseProgramError> {
|
||||||
|
match index_map.get(key) {
|
||||||
|
Some(index) => Ok(vector
|
||||||
|
.get(*index)
|
||||||
|
.expect("unexpected invalid index")
|
||||||
|
.clone()),
|
||||||
|
None => Err(ParseProgramError::NoSuchIdentifier),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build ProgId from identifier and given file extension.
|
||||||
|
fn build_progid(
|
||||||
|
identifier: &str,
|
||||||
|
ext: &str,
|
||||||
|
) -> Result<lowlevel::LosseProgId, ParseProgramError> {
|
||||||
|
// Use Regex to check identifier
|
||||||
|
static RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
|
Regex::new(r"^[a-zA-Z][a-zA-Z0-9_-]*$").expect("unexpected bad regex pattern string")
|
||||||
|
});
|
||||||
|
let identifier = match RE.captures(identifier) {
|
||||||
|
Some(_) => identifier,
|
||||||
|
None => return Err(ParseProgramError::BadIdentifier),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capitalize first ASCII of ext
|
||||||
|
let ext = utilities::capitalize_first_ascii(ext);
|
||||||
|
|
||||||
|
// Build strict ProgId
|
||||||
|
let progid = concept::ProgId::new(identifier, &ext, None)?;
|
||||||
|
// Then build losse ProgId
|
||||||
|
let losse_progid: lowlevel::LosseProgId = progid.into();
|
||||||
|
// Return built result
|
||||||
|
Ok(losse_progid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try converting [Schema] into [Program].
|
||||||
|
///
|
||||||
|
/// During this process, some checks will be performed to ensure the validity of the data.
|
||||||
|
/// For example, the reference to icon, name, or behavior must exist in their respective dictionaries.
|
||||||
|
/// The identifier must be suit for building ProgId.
|
||||||
|
pub fn new(schema: Schema) -> Result<Self, ParseProgramError> {
|
||||||
|
// Extract file name part and directory name part respectively.
|
||||||
|
let schema_path = Path::new(schema.get_path());
|
||||||
|
let app_path = schema.get_path().to_string();
|
||||||
|
let app_file_name = Self::extract_file_name(schema_path)?;
|
||||||
|
let app_file_name = String::from(utilities::osstr_to_str(app_file_name)?);
|
||||||
|
let app_dir_path = Self::extract_dir_path(schema_path)?;
|
||||||
|
let app_dir_path = String::from(utilities::osstr_to_str(app_dir_path)?);
|
||||||
|
// Build app paths key and applications key respectively
|
||||||
|
let key = concept::FileName::new(&app_file_name)?;
|
||||||
|
let app_paths_key = lowlevel::AppPathsKey::new(key.clone());
|
||||||
|
let applications_key = lowlevel::ApplicationsKey::new(key.clone());
|
||||||
|
|
||||||
|
// Build string, icon and behavior list,
|
||||||
|
// and build mapper at the same time.
|
||||||
|
let (strs, strs_index_map) = Self::flat_hashmap(schema.get_strs(), |entry| {
|
||||||
|
let str_res_variant: lowlevel::StrResVariant = entry.as_str().into();
|
||||||
|
let program_str = ProgramStr {
|
||||||
|
inner: str_res_variant,
|
||||||
|
};
|
||||||
|
Ok(Arc::new(program_str))
|
||||||
|
})?;
|
||||||
|
let (icons, icons_index_map) = Self::flat_hashmap(schema.get_icons(), |entry| {
|
||||||
|
let icon_res_variant: lowlevel::IconResVariant = entry.as_str().into();
|
||||||
|
let program_icon = ProgramIcon {
|
||||||
|
inner: icon_res_variant,
|
||||||
|
};
|
||||||
|
Ok(Arc::new(program_icon))
|
||||||
|
})?;
|
||||||
|
let (behaviors, behaviors_index_map) =
|
||||||
|
Self::flat_hashmap(schema.get_behaviors(), |entry| {
|
||||||
|
// We simply always use "Open" verb.
|
||||||
|
let cmdline: concept::CmdLine = entry.as_str().parse()?;
|
||||||
|
let verb = concept::Verb::OPEN();
|
||||||
|
let shell_verb = lowlevel::ShellVerb::new(verb, cmdline);
|
||||||
|
let program_behavior = ProgramBehavior { inner: shell_verb };
|
||||||
|
Ok(Arc::new(program_behavior))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Setup default name, icon and behavior
|
||||||
|
let name = schema
|
||||||
|
.get_name()
|
||||||
|
.map(|name| Self::resolve_index(name, &strs, &strs_index_map))
|
||||||
|
.transpose()?;
|
||||||
|
let icon = schema
|
||||||
|
.get_icon()
|
||||||
|
.map(|icon| Self::resolve_index(icon, &icons, &icons_index_map))
|
||||||
|
.transpose()?;
|
||||||
|
let behavior = schema
|
||||||
|
.get_behavior()
|
||||||
|
.map(|behavior| Self::resolve_index(behavior, &behaviors, &behaviors_index_map))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
// We build ext keys
|
||||||
|
let mut ext_keys: Vec<ProgramProgIdExtKey> = Vec::with_capacity(schema.get_exts().len());
|
||||||
|
for (key, value) in schema.get_exts() {
|
||||||
|
// Build ProgId first.
|
||||||
|
let progid = Self::build_progid(schema.get_identifier(), key.as_str())?;
|
||||||
|
// Then build ProgId key.
|
||||||
|
let progid_key = lowlevel::ProgIdKey::new(progid);
|
||||||
|
|
||||||
|
// Build essential fields for program ProgId key struct.
|
||||||
|
let name = Self::resolve_index(value.get_name(), &strs, &strs_index_map)?;
|
||||||
|
let icon = Self::resolve_index(value.get_icon(), &icons, &icons_index_map)?;
|
||||||
|
let behavior =
|
||||||
|
Self::resolve_index(value.get_behavior(), &behaviors, &behaviors_index_map)?;
|
||||||
|
|
||||||
|
// Build Ext.
|
||||||
|
let ext = concept::Ext::new(key.as_str())?;
|
||||||
|
let ext_key = lowlevel::ExtKey::new(ext);
|
||||||
|
|
||||||
|
// Create program ProgId Ext key struct
|
||||||
|
let progid_ext_key = ProgramProgIdExtKey {
|
||||||
|
ext_key,
|
||||||
|
progid_key,
|
||||||
|
name,
|
||||||
|
icon,
|
||||||
|
behavior,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add them into list
|
||||||
|
ext_keys.push(progid_ext_key);
|
||||||
|
}
|
||||||
|
// The build ext keys map
|
||||||
|
let ext_keys_map = ext_keys
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, ext)| (ext.ext_key.inner().inner().to_string(), i))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Everything is okey
|
||||||
|
Ok(Self {
|
||||||
|
app_paths_key,
|
||||||
|
applications_key,
|
||||||
|
app_path,
|
||||||
|
app_file_name,
|
||||||
|
app_dir_path,
|
||||||
|
name,
|
||||||
|
icon,
|
||||||
|
behavior,
|
||||||
|
strs,
|
||||||
|
icons,
|
||||||
|
behaviors,
|
||||||
|
ext_keys,
|
||||||
|
ext_keys_map,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Program {
|
||||||
|
pub fn resolve_name(&self) -> Result<String, ProgramError> {
|
||||||
|
// Fecch from user specified name first
|
||||||
|
let name = self
|
||||||
|
.name
|
||||||
|
.as_ref()
|
||||||
|
.map(|name| name.inner.extract().ok())
|
||||||
|
.flatten();
|
||||||
|
if let Some(name) = name {
|
||||||
|
return Ok(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Fetch it from executable manifest file.
|
||||||
|
|
||||||
|
// Finally fallback to use executable name.
|
||||||
|
Ok(self.app_file_name.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_icon(&self) -> Result<concept::IconRc, ProgramError> {
|
||||||
|
// Fetch from user specified icon first
|
||||||
|
let icon = self
|
||||||
|
.icon
|
||||||
|
.as_ref()
|
||||||
|
.map(|icon| icon.inner.extract(concept::IconSizeKind::Small).ok())
|
||||||
|
.flatten();
|
||||||
|
if let Some(icon) = icon {
|
||||||
|
return Ok(icon);
|
||||||
|
}
|
||||||
|
// Fetch from the first icon of executable instead.
|
||||||
|
let icon = concept::IconRc::new(&self.app_path, 0, concept::IconSizeKind::Small).ok();
|
||||||
|
if let Some(icon) = icon {
|
||||||
|
return Ok(icon);
|
||||||
|
}
|
||||||
|
// Finally fallback to use system default executable icon.
|
||||||
|
Ok(concept::IconRc::GENERIC_APPLICATION(
|
||||||
|
concept::IconSizeKind::Small,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exts_len(&self) -> usize {
|
||||||
|
self.ext_keys.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_ext(&self, body: &str) -> Option<usize> {
|
||||||
|
self.ext_keys_map.get(body).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_ext(&self, index: usize) -> Result<ProgramSelfExtStatus, ProgramError> {
|
||||||
|
// Fetch data
|
||||||
|
let progid_ext_key = self.ext_keys.get(index).ok_or(ProgramError::BadIndex)?;
|
||||||
|
|
||||||
|
// Try resolving name with string resource first,
|
||||||
|
// and fallback to ProgId verbatim.
|
||||||
|
let name = progid_ext_key
|
||||||
|
.name
|
||||||
|
.inner
|
||||||
|
.extract()
|
||||||
|
.unwrap_or(progid_ext_key.progid_key.inner().to_string());
|
||||||
|
// Try to fetch icon, and fallback to system default file icon.
|
||||||
|
let icon = progid_ext_key
|
||||||
|
.icon
|
||||||
|
.inner
|
||||||
|
.extract(concept::IconSizeKind::Small)
|
||||||
|
.unwrap_or(concept::IconRc::GENERIC_DOCUMENT(
|
||||||
|
concept::IconSizeKind::Small,
|
||||||
|
)?);
|
||||||
|
|
||||||
|
// Okey, return it
|
||||||
|
Ok(ProgramSelfExtStatus::new(
|
||||||
|
progid_ext_key.ext_key.inner().clone(),
|
||||||
|
name,
|
||||||
|
icon,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Program {
|
||||||
|
/// Register this application.
|
||||||
|
///
|
||||||
|
/// If there is complete or partial registration of this application
|
||||||
|
/// (partial registration may occurs when registration failed),
|
||||||
|
/// this function does nothing.
|
||||||
|
pub fn register(&mut self, scope: Scope) -> Result<(), ProgramError> {
|
||||||
|
// Create App Paths subkey
|
||||||
|
debug_println!("Adding App Paths subkey...");
|
||||||
|
self.app_paths_key.ensure(scope)?;
|
||||||
|
// Write App Paths values
|
||||||
|
self.app_paths_key.set_default(scope, &self.app_path)?;
|
||||||
|
self.app_paths_key.set_path(scope, &self.app_dir_path)?;
|
||||||
|
|
||||||
|
// Create Applications subkey
|
||||||
|
debug_println!("Adding Applications subkey...");
|
||||||
|
self.applications_key.ensure(scope)?;
|
||||||
|
// Write Applications values
|
||||||
|
self.applications_key
|
||||||
|
.set_shell_verb(scope, self.behavior.as_ref().map(|beh| &beh.inner))?;
|
||||||
|
self.applications_key
|
||||||
|
.set_default_icon(scope, self.icon.as_ref().map(|ico| &ico.inner))?;
|
||||||
|
self.applications_key
|
||||||
|
.set_friendly_app_name(scope, self.name.as_ref().map(|name| &name.inner))?;
|
||||||
|
let exts: Vec<&concept::Ext> = self
|
||||||
|
.ext_keys
|
||||||
|
.iter()
|
||||||
|
.map(|key| key.ext_key.inner())
|
||||||
|
.collect();
|
||||||
|
self.applications_key
|
||||||
|
.set_supported_types(scope, Some(&exts))?;
|
||||||
|
self.applications_key.set_no_open_with(scope, true)?;
|
||||||
|
|
||||||
|
// Create ProgId subkeys one by one
|
||||||
|
debug_println!("Adding ProgId subkey...");
|
||||||
|
for program_key in &mut self.ext_keys {
|
||||||
|
let progid_key = &mut program_key.progid_key;
|
||||||
|
debug_println!(
|
||||||
|
"Adding ProgId \"{0}\" subkey...",
|
||||||
|
progid_key.inner().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create ProgId subkey
|
||||||
|
progid_key.ensure(scope)?;
|
||||||
|
// Write ProgId values
|
||||||
|
let name = Some(&program_key.name.inner);
|
||||||
|
progid_key.set_default(scope, name)?;
|
||||||
|
progid_key.set_shell_verb(scope, Some(&program_key.behavior.inner))?;
|
||||||
|
progid_key.set_friendly_type_name(scope, name)?;
|
||||||
|
progid_key.set_default_icon(scope, Some(&program_key.icon.inner))?;
|
||||||
|
|
||||||
|
// Add this progid to file extension "open with" list.
|
||||||
|
let ext_key = &mut program_key.ext_key;
|
||||||
|
ext_key.ensure(scope)?;
|
||||||
|
ext_key.add_into_open_with_progids(scope, progid_key.inner())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything is okey.
|
||||||
|
// Notify changes and return
|
||||||
|
win32::utilities::notify_assoc_changed();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unregister this application.
|
||||||
|
///
|
||||||
|
/// No matter whether there is registration of this application,
|
||||||
|
/// this function always make sure that there is no registration after running this function.
|
||||||
|
pub fn unregister(&mut self, scope: Scope) -> Result<(), ProgramError> {
|
||||||
|
// Delete App Paths subkey
|
||||||
|
debug_println!("Deleting App Paths subkey...");
|
||||||
|
self.app_paths_key.delete(scope)?;
|
||||||
|
|
||||||
|
// Delete Applications subkey
|
||||||
|
debug_println!("Deleting Applications subkey...");
|
||||||
|
self.applications_key.delete(scope)?;
|
||||||
|
|
||||||
|
// Delete ProgId subkeys one by one.
|
||||||
|
debug_println!("Adding ProgId subkey...");
|
||||||
|
for program_key in &mut self.ext_keys {
|
||||||
|
let progid_key = &mut program_key.progid_key;
|
||||||
|
debug_println!(
|
||||||
|
"Deleting ProgId \"{0}\" subkey...",
|
||||||
|
progid_key.inner().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// According to Microsoft document, when uninstalling application,
|
||||||
|
// there is no need to reset the default open way of file extension.
|
||||||
|
// So we simply remove it from "open with" list.
|
||||||
|
|
||||||
|
// Remove this ProgId from file extension "open with" list,
|
||||||
|
// if this file extension is existing
|
||||||
|
let ext_key = &mut program_key.ext_key;
|
||||||
|
if ext_key.is_exist(scope.into())? {
|
||||||
|
ext_key.remove_from_open_with_progids(scope, progid_key.inner())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete ProgId subkey
|
||||||
|
progid_key.delete(scope)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything is okey.
|
||||||
|
// Notify changes and return
|
||||||
|
win32::utilities::notify_assoc_changed();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether this application has been registered in given view.
|
||||||
|
///
|
||||||
|
/// Please note that this is a rough check and do not validate any data.
|
||||||
|
///
|
||||||
|
/// The return value only ensures the pre-requirement of `register` and `unregister`.
|
||||||
|
pub fn is_registered(&self, scope: Scope) -> Result<bool, ProgramError> {
|
||||||
|
// Check App Paths subkey.
|
||||||
|
debug_println!("Checking App Paths subkey...");
|
||||||
|
if !self.app_paths_key.is_exist(scope)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Application subkey.
|
||||||
|
debug_println!("Checking Applications subkey...");
|
||||||
|
if !self.applications_key.is_exist(scope.into())? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ProgId subkey.
|
||||||
|
debug_println!("Checking ProgId subkey...");
|
||||||
|
for program_key in &self.ext_keys {
|
||||||
|
let progid_key = &program_key.progid_key;
|
||||||
|
debug_println!(
|
||||||
|
"Checking ProgId \"{0}\" subkey...",
|
||||||
|
progid_key.inner().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
if !progid_key.is_exist(scope.into())? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every subkeys are roughly existing.
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn link_ext(&mut self, scope: Scope, index: usize) -> Result<(), ProgramError> {
|
||||||
|
match self.ext_keys.get_mut(index) {
|
||||||
|
Some(program_key) => {
|
||||||
|
let ext_key = &mut program_key.ext_key;
|
||||||
|
let progid_key = &program_key.progid_key;
|
||||||
|
debug_println!(
|
||||||
|
"Linking ProgId \"{0}\" to extension \"{1}\" subkey...",
|
||||||
|
progid_key.inner().to_string(),
|
||||||
|
ext_key.inner().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Before setting it, we must make sure this extension is existing
|
||||||
|
ext_key.ensure(scope)?;
|
||||||
|
ext_key.set_default(scope, Some(progid_key.inner()))?;
|
||||||
|
}
|
||||||
|
None => return Err(ProgramError::BadIndex),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Everything is okey.
|
||||||
|
// Notify changes and return
|
||||||
|
win32::utilities::notify_assoc_changed();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unlink_ext(&mut self, scope: Scope, index: usize) -> Result<(), ProgramError> {
|
||||||
|
match self.ext_keys.get_mut(index) {
|
||||||
|
Some(program_key) => {
|
||||||
|
let ext_key = &mut program_key.ext_key;
|
||||||
|
debug_println!(
|
||||||
|
"Unlinking for extension \"{0}\" subkey...",
|
||||||
|
ext_key.inner().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Before setting it, we must make sure this extension is existing
|
||||||
|
ext_key.ensure(scope)?;
|
||||||
|
ext_key.set_default(scope, None)?;
|
||||||
|
}
|
||||||
|
None => return Err(ProgramError::BadIndex),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything is okey.
|
||||||
|
// Notify changes and return
|
||||||
|
win32::utilities::notify_assoc_changed();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query_ext(
|
||||||
|
&self,
|
||||||
|
view: View,
|
||||||
|
index: usize,
|
||||||
|
) -> Result<Option<ProgramExtStatus>, ProgramError> {
|
||||||
|
match self.ext_keys.get(index) {
|
||||||
|
Some(program_key) => {
|
||||||
|
let ext_key = &program_key.ext_key;
|
||||||
|
debug_println!(
|
||||||
|
"Querying for extension \"{0}\"subkey...",
|
||||||
|
ext_key.inner().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
// If there is no such extension key, return None about this extension.
|
||||||
|
if !ext_key.is_exist(view)? {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
// Let we fetch its associated default ProgId.
|
||||||
|
// If there is no such key, return None instead.
|
||||||
|
let progid = match ext_key.get_default(view)? {
|
||||||
|
Some(progid) => progid,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
// Now we build ProgId key from gotten association
|
||||||
|
let progid_key = lowlevel::ProgIdKey::new(progid);
|
||||||
|
// If this associated ProgId key is not presented,
|
||||||
|
// we return None instead.
|
||||||
|
if !progid_key.is_exist(view)? {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
// Now try fetch its diaplay name in modern way first.
|
||||||
|
// If there is no modern way, use legacy way instead.
|
||||||
|
// If there is still no display name, use ProgId self instead as display name.
|
||||||
|
let mut name: Option<String> = None;
|
||||||
|
if let None = name {
|
||||||
|
name = progid_key
|
||||||
|
.get_friendly_type_name(view)?
|
||||||
|
.map(|name| name.extract().ok())
|
||||||
|
.flatten();
|
||||||
|
}
|
||||||
|
if let None = name {
|
||||||
|
name = progid_key
|
||||||
|
.get_default(view)?
|
||||||
|
.map(|name| name.extract().ok())
|
||||||
|
.flatten();
|
||||||
|
}
|
||||||
|
let name = name.unwrap_or(progid_key.inner().to_string());
|
||||||
|
// Now try to fetch icon and fallback to system default file icon.
|
||||||
|
let icon = progid_key
|
||||||
|
.get_default_icon(view)?
|
||||||
|
.map(|ico| ico.extract(concept::IconSizeKind::Small).ok())
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or(concept::IconRc::GENERIC_DOCUMENT(
|
||||||
|
concept::IconSizeKind::Small,
|
||||||
|
)?);
|
||||||
|
|
||||||
|
// Okey, return it.
|
||||||
|
Ok(Some(ProgramExtStatus::new(name, icon)))
|
||||||
|
}
|
||||||
|
None => Err(ProgramError::BadIndex),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Internal Stuff
|
||||||
|
|
||||||
|
/// Internal used enum presenting a Program string resource.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ProgramStr {
|
||||||
|
inner: lowlevel::StrResVariant,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal used enum presenting a Program icon resource.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ProgramIcon {
|
||||||
|
inner: lowlevel::IconResVariant,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal used enum presenting a Program behavior (command line setups).
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ProgramBehavior {
|
||||||
|
inner: lowlevel::ShellVerb,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal used struct presenting a Program ProgId and associated file extension.
|
||||||
|
///
|
||||||
|
/// Another reason combine ProgId and Ext is that we can't operate ProgId in mutable mode,
|
||||||
|
/// due to the internal immutable of Rc in Rust.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ProgramProgIdExtKey {
|
||||||
|
ext_key: lowlevel::ExtKey,
|
||||||
|
|
||||||
|
progid_key: lowlevel::ProgIdKey,
|
||||||
|
name: Arc<ProgramStr>,
|
||||||
|
icon: Arc<ProgramIcon>,
|
||||||
|
behavior: Arc<ProgramBehavior>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Exposed Stuff
|
||||||
|
|
||||||
|
/// Exposed struct representing this program provided method for opening specific file extension.
|
||||||
|
///
|
||||||
|
/// The data including the extension name, diaplay name and icon.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ProgramSelfExtStatus {
|
||||||
|
ext: concept::Ext,
|
||||||
|
name: String,
|
||||||
|
icon: concept::IconRc,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgramSelfExtStatus {
|
||||||
|
fn new(ext: concept::Ext, name: String, icon: concept::IconRc) -> Self {
|
||||||
|
Self { ext, name, icon }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the extension name without leading dot like `jpg`.
|
||||||
|
pub fn get_ext(&self) -> &str {
|
||||||
|
self.ext.inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the extension name with leading dot like `.jpg`.
|
||||||
|
pub fn get_dotted_ext(&self) -> String {
|
||||||
|
self.ext.dotted_inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the display name of this program.
|
||||||
|
///
|
||||||
|
/// The program provided display name will be used firstly.
|
||||||
|
/// If this program has no display name, the stringified ProgId will be used instead.
|
||||||
|
pub fn get_name(&self) -> &str {
|
||||||
|
self.name.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the icon of this program.
|
||||||
|
///
|
||||||
|
/// Due to the icon is optional, if there is no icon, return None.
|
||||||
|
pub fn get_icon(&self) -> &concept::IconRc {
|
||||||
|
&self.icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exposed struct representing the default associated program of specific file extension.
|
||||||
|
///
|
||||||
|
/// The data including the diaplay name and icon.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ProgramExtStatus {
|
||||||
|
name: String,
|
||||||
|
icon: concept::IconRc,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgramExtStatus {
|
||||||
|
fn new(name: String, icon: concept::IconRc) -> Self {
|
||||||
|
Self { name, icon }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the display name of this program.
|
||||||
|
///
|
||||||
|
/// The program provided display name will be used firstly.
|
||||||
|
/// If this program has no display name, the stringified ProgId will be used instead.
|
||||||
|
pub fn get_name(&self) -> &str {
|
||||||
|
self.name.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the icon of this program.
|
||||||
|
///
|
||||||
|
/// Due to the icon is optional, if there is no icon, return None.
|
||||||
|
pub fn get_icon(&self) -> &concept::IconRc {
|
||||||
|
&self.icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
215
wfassoc/src/highlevel/schema.rs
Normal file
215
wfassoc/src/highlevel/schema.rs
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use thiserror::Error as TeError;
|
||||||
|
use super::{Program, ParseProgramError};
|
||||||
|
|
||||||
|
// region: Error Type
|
||||||
|
|
||||||
|
/// Error occurs when operating with [Schema].
|
||||||
|
#[derive(Debug, TeError)]
|
||||||
|
pub enum SchemaError {
|
||||||
|
#[error("duplicate key: {0}")]
|
||||||
|
DuplicateKey(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Schema
|
||||||
|
|
||||||
|
/// The sketchpad of complete [Program].
|
||||||
|
///
|
||||||
|
/// In suggested usage, we will create a [Schema] first,
|
||||||
|
/// fill some essential and optional properties,
|
||||||
|
/// then add file extensions which we need.
|
||||||
|
/// And finally convert it into immutable [Program] for formal using.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Schema {
|
||||||
|
identifier: String,
|
||||||
|
path: String,
|
||||||
|
clsid: String,
|
||||||
|
|
||||||
|
name: Option<String>,
|
||||||
|
icon: Option<String>,
|
||||||
|
behavior: Option<String>,
|
||||||
|
|
||||||
|
strs: HashMap<String, String>,
|
||||||
|
icons: HashMap<String, String>,
|
||||||
|
behaviors: HashMap<String, String>,
|
||||||
|
exts: HashMap<String, SchemaExt>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Schema {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
identifier: String::new(),
|
||||||
|
path: String::new(),
|
||||||
|
clsid: String::new(),
|
||||||
|
name: None,
|
||||||
|
icon: None,
|
||||||
|
behavior: None,
|
||||||
|
strs: HashMap::new(),
|
||||||
|
icons: HashMap::new(),
|
||||||
|
behaviors: HashMap::new(),
|
||||||
|
exts: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try converting [Schema] into [Program].
|
||||||
|
///
|
||||||
|
/// This is equivalent to [Program::new].
|
||||||
|
pub fn into_program(self) -> Result<Program, ParseProgramError> {
|
||||||
|
Program::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Schema {
|
||||||
|
/// Set the identifier of the schema.
|
||||||
|
///
|
||||||
|
/// The identifier is used to build ProgId.
|
||||||
|
/// So it should starts with an ASCII letter and followed by zero or more ASCII letters, digits, underline and hyphen.
|
||||||
|
/// And it should not be empty.
|
||||||
|
pub fn set_identifier(&mut self, identifier: &str) -> () {
|
||||||
|
self.identifier = identifier.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the absolute path to the executable file.
|
||||||
|
pub fn set_path(&mut self, exe_path: &str) -> () {
|
||||||
|
self.path = exe_path.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_clsid(&mut self, clsid: &str) -> () {
|
||||||
|
self.clsid = clsid.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_name(&mut self, name: Option<&str>) -> () {
|
||||||
|
self.name = name.map(|n| n.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_icon(&mut self, icon: Option<&str>) -> () {
|
||||||
|
self.icon = icon.map(|i| i.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_behavior(&mut self, behavior: Option<&str>) -> () {
|
||||||
|
self.behavior = behavior.map(|b| b.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_str(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
||||||
|
match self.strs.insert(name.to_string(), value.to_string()) {
|
||||||
|
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
||||||
|
None => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_icon(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
||||||
|
match self.icons.insert(name.to_string(), value.to_string()) {
|
||||||
|
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
||||||
|
None => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_behavior(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
||||||
|
match self.behaviors.insert(name.to_string(), value.to_string()) {
|
||||||
|
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
||||||
|
None => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a file extension to the schema.
|
||||||
|
///
|
||||||
|
/// The parameter `ext` is the file extension without leading dot `.`.
|
||||||
|
pub fn add_ext(
|
||||||
|
&mut self,
|
||||||
|
ext: &str,
|
||||||
|
ext_name: &str,
|
||||||
|
ext_icon: &str,
|
||||||
|
ext_behavior: &str,
|
||||||
|
) -> Result<(), SchemaError> {
|
||||||
|
match self.exts.insert(
|
||||||
|
ext.to_string(),
|
||||||
|
SchemaExt::new(ext_name, ext_icon, ext_behavior),
|
||||||
|
) {
|
||||||
|
Some(_) => Err(SchemaError::DuplicateKey(ext.to_string())),
|
||||||
|
None => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Schema {
|
||||||
|
pub(super) fn get_identifier(&self) -> &str {
|
||||||
|
&self.identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_path(&self) -> &str {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_clsid(&self) -> &str {
|
||||||
|
&self.clsid
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_name(&self) -> Option<&str> {
|
||||||
|
self.name.as_ref().map(|v| v.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_icon(&self) -> Option<&str> {
|
||||||
|
self.icon.as_ref().map(|v| v.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_behavior(&self) -> Option<&str> {
|
||||||
|
self.behavior.as_ref().map(|v| v.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_strs(&self) -> &HashMap<String, String> {
|
||||||
|
&self.strs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_icons(&self) -> &HashMap<String, String> {
|
||||||
|
&self.icons
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_behaviors(&self) -> &HashMap<String, String> {
|
||||||
|
&self.behaviors
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_exts(&self) -> &HashMap<String, SchemaExt> {
|
||||||
|
&self.exts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Internal Stuff
|
||||||
|
|
||||||
|
/// Internal used struct as the Schema file extensions hashmap value type.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct SchemaExt {
|
||||||
|
name: String,
|
||||||
|
icon: String,
|
||||||
|
behavior: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SchemaExt {
|
||||||
|
fn new(name: &str, icon: &str, behavior: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
icon: icon.to_string(),
|
||||||
|
behavior: behavior.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SchemaExt {
|
||||||
|
pub(super) fn get_name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_icon(&self) -> &str {
|
||||||
|
&self.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_behavior(&self) -> &str {
|
||||||
|
&self.behavior
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
@@ -1,501 +0,0 @@
|
|||||||
//! This crate provide utilities fetching and manilupating Windows file association.
|
|
||||||
//! All code under crate are following Microsoft document: https://learn.microsoft.com/en-us/windows/win32/shell/customizing-file-types-bumper
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
compile_error!("Crate wfassoc is only supported on Windows.");
|
|
||||||
|
|
||||||
pub mod extra;
|
|
||||||
pub mod utilities;
|
|
||||||
pub mod assoc;
|
|
||||||
|
|
||||||
use assoc::{Ext, ProgId};
|
|
||||||
use indexmap::{IndexMap, IndexSet};
|
|
||||||
use regex::Regex;
|
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
use thiserror::Error as TeError;
|
|
||||||
use winreg::RegKey;
|
|
||||||
use winreg::enums::{
|
|
||||||
HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// region: Error Types
|
|
||||||
|
|
||||||
/// All possible error occurs in this crate.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("error occurs when manipulating with Registry: {0}")]
|
|
||||||
BadRegOper(#[from] std::io::Error),
|
|
||||||
#[error("{0}")]
|
|
||||||
CastOsStr(#[from] utilities::CastOsStrError),
|
|
||||||
#[error("{0}")]
|
|
||||||
ParseExt(#[from] assoc::ParseExtError),
|
|
||||||
|
|
||||||
#[error("no administrative privilege")]
|
|
||||||
NoPrivilege,
|
|
||||||
#[error("given identifier \"{0}\" of application is invalid")]
|
|
||||||
BadIdentifier(String),
|
|
||||||
#[error("given full path to application is invalid")]
|
|
||||||
BadFullAppPath,
|
|
||||||
#[error("manner \"{0}\" is already registered")]
|
|
||||||
DupManner(String),
|
|
||||||
#[error("file extension \"{0}\" is already registered")]
|
|
||||||
DupExt(String),
|
|
||||||
#[error("the token of manner is invalid")]
|
|
||||||
InvalidMannerToken,
|
|
||||||
#[error("the token of file extension is invalid")]
|
|
||||||
InvalidExtToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result type used in this crate.
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Types
|
|
||||||
|
|
||||||
/// The token for access registered items in Program.
|
|
||||||
/// This is usually returned when you registering them.
|
|
||||||
pub type Token = usize;
|
|
||||||
|
|
||||||
/// The scope where wfassoc will register and unregister application.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum Scope {
|
|
||||||
/// Scope for current user.
|
|
||||||
User,
|
|
||||||
/// Scope for all users under this computer.
|
|
||||||
System,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error occurs when cast View into Scope.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
#[error("hybrid View can not be cast into Scope")]
|
|
||||||
pub struct TryFromViewError {}
|
|
||||||
|
|
||||||
impl TryFromViewError {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<View> for Scope {
|
|
||||||
type Error = TryFromViewError;
|
|
||||||
|
|
||||||
fn try_from(value: View) -> std::result::Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
View::User => Ok(Self::User),
|
|
||||||
View::System => Ok(Self::System),
|
|
||||||
View::Hybrid => Err(TryFromViewError::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scope {
|
|
||||||
/// Check whether we have enough privilege when operating in current scope.
|
|
||||||
/// If we have, return true, otherwise false.
|
|
||||||
pub fn has_privilege(&self) -> bool {
|
|
||||||
// If we operate on System, and we do not has privilege,
|
|
||||||
// we think we do not have privilege, otherwise,
|
|
||||||
// there is no privilege required.
|
|
||||||
!matches!(self, Self::System if !utilities::has_privilege())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The view when wfassoc querying file extension association.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum View {
|
|
||||||
/// The view of current user.
|
|
||||||
User,
|
|
||||||
/// The view of system.
|
|
||||||
System,
|
|
||||||
/// Hybrid view of User and System.
|
|
||||||
/// It can be seen as that we use System first and then use User to override any existing items.
|
|
||||||
Hybrid,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Scope> for View {
|
|
||||||
fn from(value: Scope) -> Self {
|
|
||||||
match value {
|
|
||||||
Scope::User => Self::User,
|
|
||||||
Scope::System => Self::System,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Program
|
|
||||||
|
|
||||||
/// The struct representing a complete program for registration and unregistration.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Program {
|
|
||||||
/// The identifier of this program.
|
|
||||||
identifier: String,
|
|
||||||
/// The fully qualified path to the application.
|
|
||||||
full_path: PathBuf,
|
|
||||||
/// The collection holding all manners of this program.
|
|
||||||
manners: IndexSet<String>,
|
|
||||||
/// The collection holding all file extensions supported by this program.
|
|
||||||
/// The key is file estension and value is its associated manner for opening it.
|
|
||||||
exts: IndexMap<Ext, Token>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
/// Create a new registrar for following operations.
|
|
||||||
///
|
|
||||||
/// `identifier` is the unique name of this program.
|
|
||||||
/// If should only contain digits and alphabet chars,
|
|
||||||
/// and should not start with any digits.
|
|
||||||
/// For example, "MyApp" is okey but following names are not okey:
|
|
||||||
///
|
|
||||||
/// - `My App`
|
|
||||||
/// - `3DViewer`
|
|
||||||
/// - `我的Qt程序` (means "My Qt App" in English)
|
|
||||||
///
|
|
||||||
/// More preciously, `identifier` will be used as the vendor part of ProgId.
|
|
||||||
///
|
|
||||||
/// `full_path` is the fully qualified path to the application.
|
|
||||||
pub fn new(identifier: &str, full_path: &str) -> Result<Self> {
|
|
||||||
// Check identifier
|
|
||||||
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9]*$").unwrap());
|
|
||||||
if !RE.is_match(identifier) {
|
|
||||||
return Err(Error::BadIdentifier(identifier.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything is okey, build self.
|
|
||||||
Ok(Self {
|
|
||||||
identifier: identifier.to_string(),
|
|
||||||
// The error type of PathBuf FromStr trait is Infallible,
|
|
||||||
// so it must be okey and we can use unwrap safely.
|
|
||||||
full_path: full_path.parse().unwrap(),
|
|
||||||
manners: IndexSet::new(),
|
|
||||||
exts: IndexMap::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add manner provided by this program.
|
|
||||||
pub fn add_manner(&mut self, manner: &str) -> Result<Token> {
|
|
||||||
// TODO: Use wincmd::CmdArgs instead of String.
|
|
||||||
// Create manner from string
|
|
||||||
let manner = manner.to_string();
|
|
||||||
// Backup a stringfied manner for error output.
|
|
||||||
let manner_str = manner.to_string();
|
|
||||||
// Insert manner.
|
|
||||||
let idx = self.manners.len();
|
|
||||||
if self.manners.insert(manner) {
|
|
||||||
Ok(idx)
|
|
||||||
} else {
|
|
||||||
Err(Error::DupManner(manner_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the string display of manner represented by given token
|
|
||||||
pub fn get_manner_str(&self, token: Token) -> Option<String> {
|
|
||||||
self.manners.get_index(token).map(|s| s.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add file extension supported by this program and its associated manner.
|
|
||||||
pub fn add_ext(&mut self, ext: &str, token: Token) -> Result<Token> {
|
|
||||||
// Check manner token
|
|
||||||
if let None = self.manners.get_index(token) {
|
|
||||||
return Err(Error::InvalidMannerToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create extension from string
|
|
||||||
let ext = Ext::new(ext)?;
|
|
||||||
// Backup a stringfied extension for error output.
|
|
||||||
let ext_str = ext.to_string();
|
|
||||||
// Insert file extension
|
|
||||||
let idx = self.exts.len();
|
|
||||||
if let None = self.exts.insert(ext, token) {
|
|
||||||
Ok(idx)
|
|
||||||
} else {
|
|
||||||
Err(Error::DupExt(ext_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the string display of file extension represented by given token
|
|
||||||
pub fn get_ext_str(&self, token: Token) -> Option<String> {
|
|
||||||
self.exts.get_index(token).map(|p| p.0.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths";
|
|
||||||
const APPLICATIONS: &str = "Software\\Classes\\Applications";
|
|
||||||
|
|
||||||
/// Register this application.
|
|
||||||
pub fn register(&self, scope: Scope) -> Result<()> {
|
|
||||||
// Check privilege
|
|
||||||
if !scope.has_privilege() {
|
|
||||||
return Err(Error::NoPrivilege);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch root key.
|
|
||||||
let hk = RegKey::predef(match scope {
|
|
||||||
Scope::User => HKEY_CURRENT_USER,
|
|
||||||
Scope::System => HKEY_LOCAL_MACHINE,
|
|
||||||
});
|
|
||||||
// Fetch file name and start in path.
|
|
||||||
let file_name = self.extract_file_name()?;
|
|
||||||
let start_in = self.extract_start_in()?;
|
|
||||||
|
|
||||||
// Create App Paths subkey
|
|
||||||
debug_println!("Adding App Paths subkey...");
|
|
||||||
let subkey_parent = hk.open_subkey_with_flags(Self::APP_PATHS, KEY_READ)?;
|
|
||||||
let (subkey, _) = subkey_parent.create_subkey_with_flags(file_name, KEY_WRITE)?;
|
|
||||||
// Write App Paths values
|
|
||||||
subkey.set_value("", &utilities::path_to_str(&self.full_path)?)?;
|
|
||||||
subkey.set_value("Path", &utilities::osstr_to_str(&start_in)?)?;
|
|
||||||
|
|
||||||
// Create Applications subkey
|
|
||||||
debug_println!("Adding Applications subkey...");
|
|
||||||
let subkey_parent = hk.open_subkey_with_flags(Self::APPLICATIONS, KEY_READ)?;
|
|
||||||
let (subkey, _) = subkey_parent.create_subkey_with_flags(file_name, KEY_WRITE)?;
|
|
||||||
// Write Applications values
|
|
||||||
if !self.exts.is_empty() {
|
|
||||||
let (supported_types, _) =
|
|
||||||
subkey.create_subkey_with_flags("SupportedTypes", KEY_WRITE)?;
|
|
||||||
for ext in self.exts.keys() {
|
|
||||||
supported_types.set_value(ext.to_string(), &"")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create ProgId subkeys
|
|
||||||
debug_println!("Adding ProgId subkey...");
|
|
||||||
let subkey_parent = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?;
|
|
||||||
for (ext, manner_token) in self.exts.iter() {
|
|
||||||
let manner = self.manners.get_index(*manner_token).ok_or(Error::InvalidMannerToken)?;
|
|
||||||
let prog_id = self.build_prog_id(ext);
|
|
||||||
|
|
||||||
debug_println!("Adding ProgId \"{0}\" subkey...", prog_id.to_string());
|
|
||||||
let (subkey, _) = subkey_parent.create_subkey_with_flags(prog_id.to_string(), KEY_READ)?;
|
|
||||||
let (subkey_verb, _) = subkey.create_subkey_with_flags("open", KEY_READ)?;
|
|
||||||
let (subkey_command, _) = subkey_verb.create_subkey_with_flags("command", KEY_WRITE)?;
|
|
||||||
subkey_command.set_value("", manner)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Okey
|
|
||||||
utilities::notify_assoc_changed();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unregister this application.
|
|
||||||
pub fn unregister(&self, scope: Scope) -> Result<()> {
|
|
||||||
// Check privilege
|
|
||||||
if !scope.has_privilege() {
|
|
||||||
return Err(Error::NoPrivilege);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch root key and file name.
|
|
||||||
let hk = RegKey::predef(match scope {
|
|
||||||
Scope::User => HKEY_CURRENT_USER,
|
|
||||||
Scope::System => HKEY_LOCAL_MACHINE,
|
|
||||||
});
|
|
||||||
let file_name = self.extract_file_name()?;
|
|
||||||
|
|
||||||
// Remove App Paths subkey
|
|
||||||
debug_println!("Removing App Paths subkey...");
|
|
||||||
let subkey_parent = hk.open_subkey_with_flags(Self::APP_PATHS, KEY_WRITE)?;
|
|
||||||
subkey_parent.delete_subkey_all(file_name)?;
|
|
||||||
|
|
||||||
// Remove Applications subkey
|
|
||||||
debug_println!("Removing Applications subkey...");
|
|
||||||
let subkey_parent = hk.open_subkey_with_flags(Self::APPLICATIONS, KEY_READ)?;
|
|
||||||
subkey_parent.delete_subkey_all(file_name)?;
|
|
||||||
|
|
||||||
// Remove ProgId subkeys
|
|
||||||
debug_println!("Removing ProgId subkey...");
|
|
||||||
let subkey_parent = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?;
|
|
||||||
for ext in self.exts.keys() {
|
|
||||||
let prog_id = self.build_prog_id(ext);
|
|
||||||
|
|
||||||
debug_println!("Removing ProgId \"{0}\" subkey...", prog_id.to_string());
|
|
||||||
subkey_parent.delete_subkey_all(prog_id.to_string())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Okey
|
|
||||||
utilities::notify_assoc_changed();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether this application has been registered.
|
|
||||||
///
|
|
||||||
/// Please note that this is a rough check and do not validate any data.
|
|
||||||
pub fn is_registered(&self, scope: Scope) -> Result<bool> {
|
|
||||||
// Fetch root key and file name.
|
|
||||||
let hk = RegKey::predef(match scope {
|
|
||||||
Scope::User => HKEY_CURRENT_USER,
|
|
||||||
Scope::System => HKEY_LOCAL_MACHINE,
|
|
||||||
});
|
|
||||||
let file_name = self.extract_file_name()?;
|
|
||||||
|
|
||||||
// Check App Paths subkey.
|
|
||||||
debug_println!("Checking App Paths subkey...");
|
|
||||||
let subkey_parent = hk.open_subkey_with_flags(Self::APP_PATHS, KEY_READ)?;
|
|
||||||
if let Err(_) = subkey_parent.open_subkey_with_flags(file_name, KEY_READ) {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check Application subkey.
|
|
||||||
debug_println!("Checking Applications subkey...");
|
|
||||||
let subkey_parent = hk.open_subkey_with_flags(Self::APPLICATIONS, KEY_READ)?;
|
|
||||||
if let Err(_) = subkey_parent.open_subkey_with_flags(file_name, KEY_READ) {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both subkeys are roughly existing.
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
const CLASSES: &str = "Software\\Classes";
|
|
||||||
|
|
||||||
/// Set the default "open with" of given token associated extension to this program.
|
|
||||||
pub fn link_ext(&self, ext: Token, scope: Scope) -> Result<()> {
|
|
||||||
// Check privilege
|
|
||||||
if !scope.has_privilege() {
|
|
||||||
return Err(Error::NoPrivilege);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch file extension and build ProgId from it
|
|
||||||
let (ext, _) = match self.exts.get_index(ext) {
|
|
||||||
Some(v) => v,
|
|
||||||
None => return Err(Error::InvalidExtToken),
|
|
||||||
};
|
|
||||||
let prog_id = self.build_prog_id(ext);
|
|
||||||
|
|
||||||
// Fetch root key and navigate to Classes
|
|
||||||
let hk = RegKey::predef(match scope {
|
|
||||||
Scope::User => HKEY_CURRENT_USER,
|
|
||||||
Scope::System => HKEY_LOCAL_MACHINE,
|
|
||||||
});
|
|
||||||
let classes = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?;
|
|
||||||
|
|
||||||
// Open or create this extension key
|
|
||||||
let (subkey, _) = classes.create_subkey_with_flags(ext.to_string(), KEY_WRITE)?;
|
|
||||||
// Set the default way to open this file extension
|
|
||||||
subkey.set_value("", &prog_id.to_string())?;
|
|
||||||
|
|
||||||
// Okey
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove this program from the default "open with" of given token associated extension.
|
|
||||||
///
|
|
||||||
/// If the default "open with" of given extension is not our program,
|
|
||||||
/// or there is no such file extension, this function do nothing.
|
|
||||||
pub fn unlink_ext(&self, ext: Token, scope: Scope) -> Result<()> {
|
|
||||||
// Check privilege
|
|
||||||
if !scope.has_privilege() {
|
|
||||||
return Err(Error::NoPrivilege);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch file extension and build ProgId from it
|
|
||||||
let (ext, _) = match self.exts.get_index(ext) {
|
|
||||||
Some(v) => v,
|
|
||||||
None => return Err(Error::InvalidExtToken),
|
|
||||||
};
|
|
||||||
let prog_id = self.build_prog_id(ext);
|
|
||||||
|
|
||||||
// Fetch root key and navigate to Classes
|
|
||||||
let hk = RegKey::predef(match scope {
|
|
||||||
Scope::User => HKEY_CURRENT_USER,
|
|
||||||
Scope::System => HKEY_LOCAL_MACHINE,
|
|
||||||
});
|
|
||||||
let classes = hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?;
|
|
||||||
|
|
||||||
// Open key for this extension.
|
|
||||||
// If there is no such key, return directly.
|
|
||||||
if let Some(subkey) =
|
|
||||||
extra::winreg::try_open_subkey_with_flags(&classes, ext.to_string(), KEY_WRITE)?
|
|
||||||
{
|
|
||||||
// Only delete the default key if it is equal to our ProgId
|
|
||||||
if let Some(value) = extra::winreg::try_get_value::<String, _>(&subkey, "")? {
|
|
||||||
if value == prog_id.to_string() {
|
|
||||||
// Delete the default key.
|
|
||||||
subkey.delete_value("")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Okey
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query the default "open with" of given token associated extension.
|
|
||||||
///
|
|
||||||
/// This function will return its associated ProgId if it is existing.
|
|
||||||
pub fn query_ext(&self, ext: Token, view: View) -> Result<Option<ProgId>> {
|
|
||||||
// Fetch file extension
|
|
||||||
let (ext, _) = match self.exts.get_index(ext) {
|
|
||||||
Some(v) => v,
|
|
||||||
None => return Err(Error::InvalidExtToken),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch root key and navigate to Classes
|
|
||||||
let hk = match view {
|
|
||||||
View::User => RegKey::predef(HKEY_CURRENT_USER),
|
|
||||||
View::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
|
||||||
View::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT),
|
|
||||||
};
|
|
||||||
let classes = match view {
|
|
||||||
View::User | View::System => hk.open_subkey_with_flags(Self::CLASSES, KEY_READ)?,
|
|
||||||
View::Hybrid => hk.open_subkey_with_flags("", KEY_READ)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Open key for this extension if possible
|
|
||||||
let rv =
|
|
||||||
match extra::winreg::try_open_subkey_with_flags(&classes, ext.to_string(), KEY_READ)? {
|
|
||||||
Some(subkey) => {
|
|
||||||
// Try get associated ProgId if possible
|
|
||||||
match extra::winreg::try_get_value::<String, _>(&subkey, "")? {
|
|
||||||
Some(value) => Some(ProgId::from(value.as_str())),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Okey
|
|
||||||
Ok(rv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
/// Extract the file name part from full path to application,
|
|
||||||
/// which was used in Registry path component.
|
|
||||||
fn extract_file_name(&self) -> Result<&OsStr> {
|
|
||||||
// Get the file name part and make sure it is not empty.
|
|
||||||
// Empty checker is CRUCIAL!
|
|
||||||
self.full_path
|
|
||||||
.file_name()
|
|
||||||
.and_then(|p| if p.is_empty() { None } else { Some(p) })
|
|
||||||
.ok_or(Error::BadFullAppPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract the start in path from full path to application,
|
|
||||||
/// which basically is the stem of full path.
|
|
||||||
fn extract_start_in(&self) -> Result<&OsStr> {
|
|
||||||
// Get parent part and make sure it is not empty
|
|
||||||
// Empty checker is CRUCIAL!
|
|
||||||
self.full_path
|
|
||||||
.parent()
|
|
||||||
.map(|p| p.as_os_str())
|
|
||||||
.and_then(|p| if p.is_empty() { None } else { Some(p) })
|
|
||||||
.ok_or(Error::BadFullAppPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build ProgId from identifier and given file extension.
|
|
||||||
fn build_prog_id(&self, ext: &Ext) -> ProgId {
|
|
||||||
ProgId::Std(assoc::StdProgId::new(
|
|
||||||
&self.identifier,
|
|
||||||
&utilities::capitalize_first_ascii(ext.inner()),
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
@@ -1,582 +0,0 @@
|
|||||||
|
|
||||||
/// The expand of winreg crate according to our module requirements.
|
|
||||||
mod winregex;
|
|
||||||
|
|
||||||
use regex::Regex;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
use thiserror::Error as TeError;
|
|
||||||
use winreg::RegKey;
|
|
||||||
|
|
||||||
// region: Error Types
|
|
||||||
|
|
||||||
/// All possible error occurs in this crate.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error(
|
|
||||||
"can not register because lack essential privilege. please consider running with Administrator role"
|
|
||||||
)]
|
|
||||||
NoPrivilege,
|
|
||||||
#[error("{0}")]
|
|
||||||
Register(#[from] std::io::Error),
|
|
||||||
#[error("{0}")]
|
|
||||||
BadFileExt(#[from] ParseFileExtError),
|
|
||||||
#[error("{0}")]
|
|
||||||
BadProgId(#[from] ParseProgIdError),
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Privilege, Scope and View
|
|
||||||
|
|
||||||
/// Check whether current process has administrative privilege.
|
|
||||||
///
|
|
||||||
/// It usually means that checking whether current process is running as Administrator.
|
|
||||||
/// Return true if it is, otherwise false.
|
|
||||||
///
|
|
||||||
/// Reference: https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership
|
|
||||||
pub fn has_privilege() -> bool {
|
|
||||||
use windows_sys::Win32::Foundation::HANDLE;
|
|
||||||
use windows_sys::Win32::Security::{
|
|
||||||
AllocateAndInitializeSid, CheckTokenMembership, FreeSid, PSID, SECURITY_NT_AUTHORITY,
|
|
||||||
};
|
|
||||||
use windows_sys::Win32::System::SystemServices::{
|
|
||||||
DOMAIN_ALIAS_RID_ADMINS, SECURITY_BUILTIN_DOMAIN_RID,
|
|
||||||
};
|
|
||||||
use windows_sys::core::BOOL;
|
|
||||||
|
|
||||||
let nt_authority = SECURITY_NT_AUTHORITY.clone();
|
|
||||||
let mut administrators_group: PSID = PSID::default();
|
|
||||||
let success: BOOL = unsafe {
|
|
||||||
AllocateAndInitializeSid(
|
|
||||||
&nt_authority,
|
|
||||||
2,
|
|
||||||
SECURITY_BUILTIN_DOMAIN_RID as u32,
|
|
||||||
DOMAIN_ALIAS_RID_ADMINS as u32,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
&mut administrators_group,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
if success == 0 {
|
|
||||||
panic!("Win32 AllocateAndInitializeSid() failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut is_member: BOOL = BOOL::default();
|
|
||||||
let success: BOOL =
|
|
||||||
unsafe { CheckTokenMembership(HANDLE::default(), administrators_group, &mut is_member) };
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
FreeSid(administrators_group);
|
|
||||||
}
|
|
||||||
|
|
||||||
if success == 0 {
|
|
||||||
panic!("Win32 CheckTokenMembership() failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
is_member != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The scope where wfassoc will register and unregister.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum Scope {
|
|
||||||
/// Scope for current user.
|
|
||||||
User,
|
|
||||||
/// Scope for all users under this computer.
|
|
||||||
System,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The view when wfassoc querying infomations.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum View {
|
|
||||||
/// The view of current user.
|
|
||||||
User,
|
|
||||||
/// The view of system.
|
|
||||||
System,
|
|
||||||
/// Hybrid view of User and System.
|
|
||||||
/// It can be seen as that we use System first and then use User to override any existing items.
|
|
||||||
Hybrid,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error occurs when cast View into Scope.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
#[error("hybrid view can not be cast into any scope")]
|
|
||||||
pub struct TryFromViewError {}
|
|
||||||
|
|
||||||
impl TryFromViewError {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Scope> for View {
|
|
||||||
fn from(value: Scope) -> Self {
|
|
||||||
match value {
|
|
||||||
Scope::User => Self::User,
|
|
||||||
Scope::System => Self::System,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<View> for Scope {
|
|
||||||
type Error = TryFromViewError;
|
|
||||||
|
|
||||||
fn try_from(value: View) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
View::User => Ok(Self::User),
|
|
||||||
View::System => Ok(Self::System),
|
|
||||||
View::Hybrid => Err(TryFromViewError::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scope {
|
|
||||||
/// Check whether we have enough privilege when operating in current scope.
|
|
||||||
/// If we have, simply return, otherwise return error.
|
|
||||||
fn check_privilege(&self) -> Result<(), Error> {
|
|
||||||
if matches!(self, Self::System if !has_privilege()) {
|
|
||||||
Err(Error::NoPrivilege)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: File Extension
|
|
||||||
|
|
||||||
/// The struct representing an file extension which must start with dot (`.`)
|
|
||||||
/// and followed by at least one arbitrary characters.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FileExt {
|
|
||||||
/// The body of file extension (excluding dot).
|
|
||||||
inner: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileExt {
|
|
||||||
pub fn new(file_ext: &str) -> Result<Self, ParseFileExtError> {
|
|
||||||
Self::from_str(file_ext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error occurs when try parsing string into FileExt.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
#[error("given file extension is invalid")]
|
|
||||||
pub struct ParseFileExtError {}
|
|
||||||
|
|
||||||
impl ParseFileExtError {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for FileExt {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, ".{}", self.inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FileExt {
|
|
||||||
type Err = ParseFileExtError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\.([^\.]+)$").unwrap());
|
|
||||||
match RE.captures(s) {
|
|
||||||
Some(v) => Ok(Self {
|
|
||||||
inner: v[1].to_string(),
|
|
||||||
}),
|
|
||||||
None => Err(ParseFileExtError::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileExt {
|
|
||||||
fn open_scope(&self, scope: Scope) -> Result<RegKey, Error> {
|
|
||||||
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE};
|
|
||||||
|
|
||||||
// check privilege
|
|
||||||
scope.check_privilege()?;
|
|
||||||
// get the root key
|
|
||||||
let hk = match scope {
|
|
||||||
Scope::User => RegKey::predef(HKEY_CURRENT_USER),
|
|
||||||
Scope::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
|
||||||
};
|
|
||||||
// navigate to classes
|
|
||||||
let classes = hk.open_subkey_with_flags("Software\\Classes", KEY_READ | KEY_WRITE)?;
|
|
||||||
// okey
|
|
||||||
Ok(classes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_view(&self, view: View) -> Result<Option<RegKey>, Error> {
|
|
||||||
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ};
|
|
||||||
|
|
||||||
// navigate to extension container
|
|
||||||
let hk = match view {
|
|
||||||
View::User => RegKey::predef(HKEY_CURRENT_USER),
|
|
||||||
View::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
|
||||||
View::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT),
|
|
||||||
};
|
|
||||||
let classes = match view {
|
|
||||||
View::User | View::System => {
|
|
||||||
hk.open_subkey_with_flags("Software\\Classes", KEY_READ)?
|
|
||||||
}
|
|
||||||
View::Hybrid => hk.open_subkey_with_flags("", KEY_READ)?,
|
|
||||||
};
|
|
||||||
// check whether there is this ext
|
|
||||||
classes.
|
|
||||||
// open extension key if possible
|
|
||||||
let thisext = classes.open_subkey_with_flags(file_ext.to_string(), KEY_READ)?;
|
|
||||||
// okey
|
|
||||||
Ok(classes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_current(&self, view: View) -> Option<ProgId> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_current(&mut self, scope: Scope, prog_id: Option<&ProgId>) -> Result<(), Error> {
|
|
||||||
scope.check_privilege()?;
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_open_with(&self, view: View) -> Result<impl Iterator<Item = ProgId>, Error> {
|
|
||||||
let viewer = match self.open_view(view)? {
|
|
||||||
Some(viewer) => viewer,
|
|
||||||
None => return Ok(std::iter::empty::<ProgId>()),
|
|
||||||
};
|
|
||||||
let it = winregex::iter_sz_keys(&viewer);
|
|
||||||
let it = winregex::exclude_default_key(it);
|
|
||||||
|
|
||||||
Ok(it.map(|s| ProgId::from(s.as_str())))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_open_with(&mut self, scope: Scope, prog_id: &ProgId) -> Result<(), Error> {
|
|
||||||
scope.check_privilege()?;
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flash_open_with(
|
|
||||||
&mut self,
|
|
||||||
scope: Scope,
|
|
||||||
prog_ids: impl Iterator<Item = ProgId>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
scope.check_privilege()?;
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The association infomations of specific file extension.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FileExtAssoc {
|
|
||||||
default: String,
|
|
||||||
open_with_progids: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileExtAssoc {
|
|
||||||
fn new(file_ext: &FileExt, view: View) -> Option<Self> {
|
|
||||||
use winreg::RegKey;
|
|
||||||
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ};
|
|
||||||
|
|
||||||
// navigate to extension container
|
|
||||||
let hk = match view {
|
|
||||||
View::User => RegKey::predef(HKEY_CURRENT_USER),
|
|
||||||
View::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
|
||||||
View::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT),
|
|
||||||
};
|
|
||||||
let classes = match view {
|
|
||||||
View::User | View::System => hk
|
|
||||||
.open_subkey_with_flags("Software\\Classes", KEY_READ)
|
|
||||||
.unwrap(),
|
|
||||||
View::Hybrid => hk.open_subkey_with_flags("", KEY_READ).unwrap(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// open extension key if possible
|
|
||||||
let thisext = match classes.open_subkey_with_flags(file_ext.to_string(), KEY_READ) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// fetch extension infos.
|
|
||||||
let default = thisext.get_value("").unwrap_or(String::new());
|
|
||||||
let open_with_progids =
|
|
||||||
if let Ok(progids) = thisext.open_subkey_with_flags("OpenWithProdIds", KEY_READ) {
|
|
||||||
progids
|
|
||||||
.enum_keys()
|
|
||||||
.map(|x| x.unwrap())
|
|
||||||
.filter(|k| !k.is_empty())
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(Self {
|
|
||||||
default,
|
|
||||||
open_with_progids,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_default(&self) -> &str {
|
|
||||||
&self.default
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len_open_with_progid(&self) -> usize {
|
|
||||||
self.open_with_progids.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_open_with_progids(&self) -> impl Iterator<Item = &str> {
|
|
||||||
self.open_with_progids.iter().map(|s| s.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Executable Resource
|
|
||||||
|
|
||||||
// /// The struct representing an Windows executable resources path like
|
|
||||||
// /// `path_to_file.exe,1`.
|
|
||||||
// pub struct ExecRc {
|
|
||||||
// /// The path to binary for finding resources.
|
|
||||||
// binary: PathBuf,
|
|
||||||
// /// The inner index of resources.
|
|
||||||
// index: u32,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl ExecRc {
|
|
||||||
// pub fn new(res_str: &str) -> Result<Self, ParseExecRcError> {
|
|
||||||
// static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^([^,]+),([0-9]+)$").unwrap());
|
|
||||||
// let caps = RE.captures(res_str);
|
|
||||||
// if let Some(caps) = caps {
|
|
||||||
// let binary = PathBuf::from_str(&caps[1])?;
|
|
||||||
// let index = caps[2].parse::<u32>()?;
|
|
||||||
// Ok(Self { binary, index })
|
|
||||||
// } else {
|
|
||||||
// Err(ParseExecRcError::NoCapture)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// The error occurs when try parsing string into ExecRc.
|
|
||||||
// #[derive(Debug, TeError)]
|
|
||||||
// #[error("given string is not a valid executable resource string")]
|
|
||||||
// pub enum ParseExecRcError {
|
|
||||||
// /// Given string is not matched with format.
|
|
||||||
// NoCapture,
|
|
||||||
// /// Fail to convert executable part into path.
|
|
||||||
// BadBinaryPath(#[from] std::convert::Infallible),
|
|
||||||
// /// Fail to convert index part into valid number.
|
|
||||||
// BadIndex(#[from] std::num::ParseIntError),
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl FromStr for ExecRc {
|
|
||||||
// type Err = ParseExecRcError;
|
|
||||||
|
|
||||||
// fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
// ExecRc::new(s)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Display for ExecRc {
|
|
||||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
// write!(f, "{},{}", self.binary.to_str().unwrap(), self.index)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Programmatic Identifiers (ProgId)
|
|
||||||
|
|
||||||
/// The struct representing Programmatic Identifiers (ProgId).
|
|
||||||
///
|
|
||||||
/// Because there is optional part in standard ProgId, and not all software developers
|
|
||||||
/// are willing to following Microsoft suggestions, there is no strict constaint for ProgId.
|
|
||||||
/// So this struct is actually an enum which holding any possible ProgId format.
|
|
||||||
///
|
|
||||||
/// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids
|
|
||||||
pub enum ProgId {
|
|
||||||
Plain(String),
|
|
||||||
Loose(LosseProgId),
|
|
||||||
Strict(StrictProgId),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for ProgId {
|
|
||||||
fn from(s: &str) -> Self {
|
|
||||||
// match it for strict ProgId first
|
|
||||||
if let Ok(v) = StrictProgId::from_str(s) {
|
|
||||||
return Self::Strict(v);
|
|
||||||
}
|
|
||||||
// then match for loose ProgId
|
|
||||||
if let Ok(v) = LosseProgId::from_str(s) {
|
|
||||||
return Self::Loose(v);
|
|
||||||
}
|
|
||||||
// fallback with plain
|
|
||||||
Self::Plain(s.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ProgId {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
ProgId::Plain(v) => v.fmt(f),
|
|
||||||
ProgId::Loose(v) => v.fmt(f),
|
|
||||||
ProgId::Strict(v) => v.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error occurs when parsing ProgId.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
#[error("given ProgId string is invalid")]
|
|
||||||
pub struct ParseProgIdError {}
|
|
||||||
|
|
||||||
impl ParseProgIdError {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The ProgId similar with strict ProgId, but no version part.
|
|
||||||
pub struct LosseProgId {
|
|
||||||
vendor: String,
|
|
||||||
component: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LosseProgId {
|
|
||||||
pub fn new(vendor: &str, component: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
vendor: vendor.to_string(),
|
|
||||||
component: component.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_vendor(&self) -> &str {
|
|
||||||
&self.vendor
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_component(&self) -> &str {
|
|
||||||
&self.component
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for LosseProgId {
|
|
||||||
type Err = ParseProgIdError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
static RE: LazyLock<Regex> =
|
|
||||||
LazyLock::new(|| Regex::new(r"^([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)$").unwrap());
|
|
||||||
let caps = RE.captures(s);
|
|
||||||
if let Some(caps) = caps {
|
|
||||||
let vendor = &caps[1];
|
|
||||||
let component = &caps[2];
|
|
||||||
Ok(Self::new(vendor, component))
|
|
||||||
} else {
|
|
||||||
Err(ParseProgIdError::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for LosseProgId {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}.{}", self.vendor, self.component)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The ProgId exactly follows `[Vendor or Application].[Component].[Version]` format.
|
|
||||||
pub struct StrictProgId {
|
|
||||||
vendor: String,
|
|
||||||
component: String,
|
|
||||||
version: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StrictProgId {
|
|
||||||
pub fn new(vendor: &str, component: &str, version: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
vendor: vendor.to_string(),
|
|
||||||
component: component.to_string(),
|
|
||||||
version,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_vendor(&self) -> &str {
|
|
||||||
&self.vendor
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_component(&self) -> &str {
|
|
||||||
&self.component
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_version(&self) -> u32 {
|
|
||||||
self.version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for StrictProgId {
|
|
||||||
type Err = ParseProgIdError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
static RE: LazyLock<Regex> =
|
|
||||||
LazyLock::new(|| Regex::new(r"^([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)\.([0-9]+)$").unwrap());
|
|
||||||
let caps = RE.captures(s);
|
|
||||||
if let Some(caps) = caps {
|
|
||||||
let vendor = &caps[1];
|
|
||||||
let component = &caps[2];
|
|
||||||
let version = caps[3]
|
|
||||||
.parse::<u32>()
|
|
||||||
.map_err(|_| ParseProgIdError::new())?;
|
|
||||||
Ok(Self::new(vendor, component, version))
|
|
||||||
} else {
|
|
||||||
Err(ParseProgIdError::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for StrictProgId {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}.{}.{}", self.vendor, self.component, self.version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: Program
|
|
||||||
|
|
||||||
// /// The struct representing a complete Win32 program.
|
|
||||||
// pub struct Program {
|
|
||||||
// file_exts: Vec<FileExt>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Program {
|
|
||||||
// /// Create a program descriptor.
|
|
||||||
// pub fn new() -> Self {
|
|
||||||
// Self {
|
|
||||||
// file_exts: Vec::new(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Program {
|
|
||||||
// /// Register program in this computer
|
|
||||||
// pub fn register(&self, kind: RegisterKind) -> Result<(), Error> {
|
|
||||||
// todo!("pretend to register >_<...")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Unregister program from this computer.
|
|
||||||
// pub fn unregister(&self) -> Result<(), Error> {
|
|
||||||
// todo!("pretend to unregister >_<...")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Program {
|
|
||||||
// /// Query file extension infos which this program want to associate with.
|
|
||||||
// pub fn query(&self) -> Result<(), Error> {
|
|
||||||
// todo!("pretend to query >_<...")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
@@ -3,11 +3,9 @@ use std::fmt::Display;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use thiserror::Error as TeError;
|
use thiserror::Error as TeError;
|
||||||
use winreg::RegKey;
|
use winreg::RegKey;
|
||||||
use winreg::enums::{
|
use winreg::enums::{KEY_READ, KEY_WRITE};
|
||||||
HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// region: Error Type
|
// region: Error and Result Types
|
||||||
|
|
||||||
/// Error occurs in this module.
|
/// Error occurs in this module.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
@@ -16,19 +14,34 @@ pub enum Error {
|
|||||||
NoPrivilege,
|
NoPrivilege,
|
||||||
#[error("given registry key is inexistant")]
|
#[error("given registry key is inexistant")]
|
||||||
InexistantKey,
|
InexistantKey,
|
||||||
|
|
||||||
#[error("registry operation error: {0}")]
|
#[error("registry operation error: {0}")]
|
||||||
BadRegOp(#[from] std::io::Error),
|
BadRegOp(#[from] std::io::Error),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
UnexpectedBlankKey(#[from] regext::BlankPathError),
|
UnexpectedBlankKey(#[from] regext::BlankPathError),
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
ExpandEnvVar(#[from] concept::ExpandEnvVarError),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
LoadIconRc(#[from] concept::LoadIconRcError),
|
LoadIconRc(#[from] concept::LoadIconRcError),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
LoadStrRc(#[from] concept::LoadStrRcError),
|
LoadStrRc(#[from] concept::LoadStrRcError),
|
||||||
|
#[error("{0}")]
|
||||||
|
ParseVerb(#[from] concept::ParseVerbError),
|
||||||
|
#[error("{0}")]
|
||||||
|
ParseCmdLine(#[from] concept::ParseCmdLineError),
|
||||||
|
#[error("{0}")]
|
||||||
|
ParseExt(#[from] concept::ParseExtError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result type used in this module and all sub-modules.
|
||||||
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region: Scope and View
|
// region: Exposed Stuff
|
||||||
|
|
||||||
|
// region: Scope
|
||||||
|
|
||||||
/// The scope where wfassoc will register and unregister.
|
/// The scope where wfassoc will register and unregister.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
@@ -39,18 +52,6 @@ pub enum Scope {
|
|||||||
System,
|
System,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The view when wfassoc querying infomations.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum View {
|
|
||||||
/// The view of current user.
|
|
||||||
User,
|
|
||||||
/// The view of system.
|
|
||||||
System,
|
|
||||||
/// Hybrid view of User and System.
|
|
||||||
/// It can be seen as that we use System first and then use User to override any existing items.
|
|
||||||
Hybrid,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error occurs when cast View into Scope.
|
/// The error occurs when cast View into Scope.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("hybrid view can not be cast into any scope")]
|
#[error("hybrid view can not be cast into any scope")]
|
||||||
@@ -62,19 +63,10 @@ impl TryFromViewError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Scope> for View {
|
|
||||||
fn from(value: Scope) -> Self {
|
|
||||||
match value {
|
|
||||||
Scope::User => Self::User,
|
|
||||||
Scope::System => Self::System,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<View> for Scope {
|
impl TryFrom<View> for Scope {
|
||||||
type Error = TryFromViewError;
|
type Error = TryFromViewError;
|
||||||
|
|
||||||
fn try_from(value: View) -> Result<Self, Self::Error> {
|
fn try_from(value: View) -> std::result::Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
View::User => Ok(Self::User),
|
View::User => Ok(Self::User),
|
||||||
View::System => Ok(Self::System),
|
View::System => Ok(Self::System),
|
||||||
@@ -85,7 +77,30 @@ impl TryFrom<View> for Scope {
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region: Exposed Structs (Losse, Variant and others)
|
// region: View
|
||||||
|
|
||||||
|
/// The view when wfassoc querying infomations.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum View {
|
||||||
|
/// The view of current user.
|
||||||
|
User,
|
||||||
|
/// The view of system.
|
||||||
|
System,
|
||||||
|
/// Hybrid view of User and System.
|
||||||
|
/// It can be seen as that we use System first and then use User to override any existing items.
|
||||||
|
Hybrid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Scope> for View {
|
||||||
|
fn from(value: Scope) -> Self {
|
||||||
|
match value {
|
||||||
|
Scope::User => Self::User,
|
||||||
|
Scope::System => Self::System,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
// region: Losse ProgId
|
// region: Losse ProgId
|
||||||
|
|
||||||
@@ -143,10 +158,21 @@ pub enum IconResVariant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IconResVariant {
|
impl IconResVariant {
|
||||||
pub fn extract(&self, kind: concept::IconSizeKind) -> Result<concept::IconRc, Error> {
|
pub fn extract(&self, kind: concept::IconSizeKind) -> Result<concept::IconRc> {
|
||||||
let rc = match self {
|
let rc = match self {
|
||||||
IconResVariant::Plain(v) => concept::IconRc::with_ico_file(v.as_str(), kind)?,
|
IconResVariant::Plain(v) => concept::IconRc::with_ico_file(v.as_str(), kind)?,
|
||||||
IconResVariant::RefStr(v) => concept::IconRc::new(v.get_path(), v.get_index(), kind)?,
|
IconResVariant::RefStr(v) => {
|
||||||
|
// Try expand path part if possible
|
||||||
|
let path_part = strip_quote(v.get_path());
|
||||||
|
let path_part = match concept::ExpandString::new(path_part) {
|
||||||
|
Ok(expand_string) => expand_string.expand()?,
|
||||||
|
Err(_) => path_part.to_string(),
|
||||||
|
};
|
||||||
|
// Get index part.
|
||||||
|
let index_part = v.get_index();
|
||||||
|
// Resolve icon resource
|
||||||
|
concept::IconRc::new(&path_part, index_part, kind)?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(rc)
|
Ok(rc)
|
||||||
}
|
}
|
||||||
@@ -195,13 +221,22 @@ pub enum StrResVariant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StrResVariant {
|
impl StrResVariant {
|
||||||
pub fn extract(&self) -> Result<String, Error> {
|
pub fn extract(&self) -> Result<String> {
|
||||||
let rv = match self {
|
let rv = match self {
|
||||||
// For plain string, we just simply clone it.
|
// For plain string, we just simply clone it.
|
||||||
StrResVariant::Plain(v) => v.clone(),
|
StrResVariant::Plain(v) => v.clone(),
|
||||||
// For string reference string, we try to resolve it.
|
// For string reference string, we try to resolve it.
|
||||||
StrResVariant::RefStr(v) => {
|
StrResVariant::RefStr(v) => {
|
||||||
let rc = concept::StrRc::new(v.get_path(), v.get_index())?;
|
// Try expand path part if possible
|
||||||
|
let path_part = strip_quote(v.get_path());
|
||||||
|
let path_part = match concept::ExpandString::new(path_part) {
|
||||||
|
Ok(expand_string) => expand_string.expand()?,
|
||||||
|
Err(_) => path_part.to_string(),
|
||||||
|
};
|
||||||
|
// Get index part
|
||||||
|
let index_part = v.get_index();
|
||||||
|
// Resolve string resource
|
||||||
|
let rc = concept::StrRc::new(&path_part, index_part)?;
|
||||||
rc.into_string()
|
rc.into_string()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -265,7 +300,31 @@ impl ShellVerb {
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region: Utilities
|
// region: Internal Stuff
|
||||||
|
|
||||||
|
// region: Opened Key
|
||||||
|
|
||||||
|
/// Internal used struct representing the result of opening scope or view.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct OpenedKey {
|
||||||
|
/// The parent key of opened key which must be presented.
|
||||||
|
parent_key: RegKey,
|
||||||
|
/// The opened key which may not be presented in registry.
|
||||||
|
this_key: Option<RegKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenedKey {
|
||||||
|
fn new(parent_key: RegKey, this_key: Option<RegKey>) -> Self {
|
||||||
|
Self {
|
||||||
|
parent_key,
|
||||||
|
this_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Open Key Territory
|
||||||
|
|
||||||
/// The territory of opening key.
|
/// The territory of opening key.
|
||||||
///
|
///
|
||||||
@@ -297,6 +356,10 @@ impl From<View> for OpenKeyTerritory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Open Key Purpose
|
||||||
|
|
||||||
/// The purpose of opening this key.
|
/// The purpose of opening this key.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
enum OpenKeyPurpose {
|
enum OpenKeyPurpose {
|
||||||
@@ -306,9 +369,29 @@ enum OpenKeyPurpose {
|
|||||||
ReadWrite,
|
ReadWrite,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OpenKeyPurpose {
|
||||||
|
fn to_permission(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
OpenKeyPurpose::Read => PERM_R,
|
||||||
|
OpenKeyPurpose::ReadWrite => PERM_RW,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Permission Constants
|
||||||
|
|
||||||
|
/// The read-only permission when opening Windows Registry.
|
||||||
|
const PERM_R: u32 = KEY_READ;
|
||||||
|
/// The read-write permission when opening Windows Registry.
|
||||||
|
const PERM_RW: u32 = KEY_READ | KEY_WRITE;
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
/// Check whether we have enough privilege when operating in given territory and purpose.
|
/// Check whether we have enough privilege when operating in given territory and purpose.
|
||||||
/// If we have, simply return, otherwise return error.
|
/// If we have, simply return, otherwise return error.
|
||||||
fn check_privilege(territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<(), Error> {
|
fn check_privilege(territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<()> {
|
||||||
match purpose {
|
match purpose {
|
||||||
// Read is okey for every territory.
|
// Read is okey for every territory.
|
||||||
OpenKeyPurpose::Read => Ok(()),
|
OpenKeyPurpose::Read => Ok(()),
|
||||||
@@ -328,422 +411,34 @@ fn check_privilege(territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Resu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal used struct representing the result of opening scope or view.
|
/// Remove quote pair if possible.
|
||||||
#[derive(Debug)]
|
///
|
||||||
struct OpenedKey {
|
/// In some cases, the path part of [concept::IconRefStr] or [concept::StrRefStr] is quoted by quote.
|
||||||
/// The parent key of opened key which must be presented.
|
/// This can no be recognized by Win32 functions.
|
||||||
parent_key: RegKey,
|
/// So in this case, we should remove this quote pair.
|
||||||
/// The opened key which may not be presented in registry.
|
fn strip_quote<'a>(s: &'a str) -> &'a str {
|
||||||
this_key: Option<RegKey>,
|
let bytes = s.as_bytes();
|
||||||
}
|
if bytes.len() >= 2 && bytes[0] == b'"' && bytes[bytes.len() - 1] == b'"' {
|
||||||
|
// Because quote is 1 byte in UTF8, we can safely slice it,
|
||||||
impl OpenedKey {
|
// without worry about panic.
|
||||||
fn new(parent_key: RegKey, this_key: Option<RegKey>) -> Self {
|
&s[1..s.len() - 1]
|
||||||
Self {
|
|
||||||
parent_key,
|
|
||||||
this_key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: App Paths Key
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct AppPathsKey {
|
|
||||||
key_name: concept::FileName,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppPathsKey {
|
|
||||||
pub fn new(inner: concept::FileName) -> Self {
|
|
||||||
Self { key_name: inner }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner(&self) -> &concept::FileName {
|
|
||||||
&self.key_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppPathsKey {
|
|
||||||
const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths";
|
|
||||||
|
|
||||||
fn open_key(
|
|
||||||
&self,
|
|
||||||
territory: OpenKeyTerritory,
|
|
||||||
purpose: OpenKeyPurpose,
|
|
||||||
) -> Result<OpenedKey, Error> {
|
|
||||||
// check privilege
|
|
||||||
check_privilege(territory, purpose)?;
|
|
||||||
// Fetch the permission
|
|
||||||
let perms = match purpose {
|
|
||||||
OpenKeyPurpose::Read => KEY_READ,
|
|
||||||
OpenKeyPurpose::ReadWrite => KEY_READ | KEY_WRITE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// get the root key
|
|
||||||
let hk = match territory {
|
|
||||||
OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER),
|
|
||||||
OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
|
||||||
// There is no way that territory is hybrid.
|
|
||||||
OpenKeyTerritory::Hybrid => panic!("unexpected hybrid key territory"),
|
|
||||||
};
|
|
||||||
// navigate to App Paths
|
|
||||||
let app_paths = hk.open_subkey_with_flags(Self::APP_PATHS, perms)?;
|
|
||||||
// open file name key if possible
|
|
||||||
let this_app = regext::try_open_subkey_with_flags(
|
|
||||||
&app_paths,
|
|
||||||
regext::blank_path_guard(self.key_name.inner())?,
|
|
||||||
perms,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// okey
|
|
||||||
Ok(OpenedKey::new(app_paths, this_app))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_scope_for_read(&self, scope: Scope) -> Result<OpenedKey, Error> {
|
|
||||||
self.open_key(scope.into(), OpenKeyPurpose::Read)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey, Error> {
|
|
||||||
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_exist(&self, scope: Scope) -> Result<bool, Error> {
|
|
||||||
let key = self.open_scope_for_read(scope)?.this_key;
|
|
||||||
Ok(key.is_some())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensure this application key is presented in App Paths.
|
|
||||||
///
|
|
||||||
/// Return true if we newly create this key,
|
|
||||||
/// otherwise false indicating there already is an existing key.
|
|
||||||
pub fn ensure(&mut self, scope: Scope) -> Result<bool, Error> {
|
|
||||||
let key = self.open_scope_for_write(scope)?;
|
|
||||||
if let None = key.this_key {
|
|
||||||
let _ = key.parent_key.create_subkey_with_flags(
|
|
||||||
regext::blank_path_guard(self.key_name.inner())?,
|
|
||||||
KEY_READ | KEY_WRITE,
|
|
||||||
)?;
|
|
||||||
Ok(true)
|
|
||||||
} else {
|
} else {
|
||||||
Ok(false)
|
s
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete this application key from App Paths.
|
|
||||||
///
|
|
||||||
/// If there is no such key in App Paths,
|
|
||||||
/// this function does nothing.
|
|
||||||
pub fn delete(&mut self, scope: Scope) -> Result<(), Error> {
|
|
||||||
let key = self.open_scope_for_write(scope)?;
|
|
||||||
key.parent_key
|
|
||||||
.delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_scope_for_getter(&self, scope: Scope) -> Result<RegKey, Error> {
|
|
||||||
self.open_scope_for_read(scope)?
|
|
||||||
.this_key
|
|
||||||
.ok_or(Error::InexistantKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey, Error> {
|
|
||||||
self.open_scope_for_write(scope)?
|
|
||||||
.this_key
|
|
||||||
.ok_or(Error::InexistantKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
const NAMEOF_PATH_TO_APPLICATION: &str = "";
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// This field point to the fully qualified path to the application.
|
|
||||||
pub fn get_default(&self, scope: Scope) -> Result<String, Error> {
|
|
||||||
let key = self.open_scope_for_getter(scope)?;
|
|
||||||
Ok(key.get_value(Self::NAMEOF_PATH_TO_APPLICATION)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// This field should be filled with fully qualified path to the application.
|
|
||||||
pub fn set_default(&mut self, scope: Scope, value: &str) -> Result<(), Error> {
|
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
|
||||||
key.set_value(Self::NAMEOF_PATH_TO_APPLICATION, &value)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
const NAMEOF_APPLICATION_DIRECTORY: &str = "Path";
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// This field point to the added path for PATH environment variable.
|
|
||||||
/// Usually it is the path to application directory.
|
|
||||||
pub fn get_path(&self, scope: Scope) -> Result<String, Error> {
|
|
||||||
let key = self.open_scope_for_getter(scope)?;
|
|
||||||
Ok(key.get_value(Self::NAMEOF_APPLICATION_DIRECTORY)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// This field should be the added path for PATH environment variable.
|
|
||||||
/// Usually it is the path to application directory.
|
|
||||||
pub fn set_path(&mut self, scope: Scope, value: &str) -> Result<(), Error> {
|
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
|
||||||
key.set_value(Self::NAMEOF_APPLICATION_DIRECTORY, &value)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region: Applications Key
|
// region: Registry Keys
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
mod app_path_key;
|
||||||
pub struct ApplicationsKey {
|
mod applications_key;
|
||||||
key_name: concept::FileName,
|
mod ext_key;
|
||||||
}
|
mod progid_key;
|
||||||
|
|
||||||
impl ApplicationsKey {
|
pub use app_path_key::AppPathsKey;
|
||||||
pub fn new(inner: concept::FileName) -> Self {
|
pub use applications_key::ApplicationsKey;
|
||||||
Self { key_name: inner }
|
pub use ext_key::ExtKey;
|
||||||
}
|
pub use progid_key::ProgIdKey;
|
||||||
|
|
||||||
pub fn inner(&self) -> &concept::FileName {
|
|
||||||
&self.key_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationsKey {
|
|
||||||
const FULL_APPLICATIONS: &str = "Software\\Classes\\Applications";
|
|
||||||
const PARTIAL_APPLICATIONS: &str = "Applications";
|
|
||||||
|
|
||||||
fn open_key(
|
|
||||||
&self,
|
|
||||||
territory: OpenKeyTerritory,
|
|
||||||
purpose: OpenKeyPurpose,
|
|
||||||
) -> Result<OpenedKey, Error> {
|
|
||||||
// check privilege
|
|
||||||
check_privilege(territory, purpose)?;
|
|
||||||
// Fetch the permission
|
|
||||||
let perms = match purpose {
|
|
||||||
OpenKeyPurpose::Read => KEY_READ,
|
|
||||||
OpenKeyPurpose::ReadWrite => KEY_READ | KEY_WRITE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// get the root key
|
|
||||||
let hk = match territory {
|
|
||||||
OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER),
|
|
||||||
OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
|
||||||
OpenKeyTerritory::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT),
|
|
||||||
};
|
|
||||||
let applications = match territory {
|
|
||||||
OpenKeyTerritory::User | OpenKeyTerritory::System => {
|
|
||||||
hk.open_subkey_with_flags(Self::FULL_APPLICATIONS, perms)?
|
|
||||||
}
|
|
||||||
OpenKeyTerritory::Hybrid => {
|
|
||||||
hk.open_subkey_with_flags(Self::PARTIAL_APPLICATIONS, perms)?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// open app key if possible
|
|
||||||
let this_app = regext::try_open_subkey_with_flags(
|
|
||||||
&applications,
|
|
||||||
regext::blank_path_guard(self.key_name.inner())?,
|
|
||||||
perms,
|
|
||||||
)?;
|
|
||||||
// okey
|
|
||||||
Ok(OpenedKey::new(applications, this_app))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_view_for_read(&self, view: View) -> Result<OpenedKey, Error> {
|
|
||||||
self.open_key(view.into(), OpenKeyPurpose::Read)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey, Error> {
|
|
||||||
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_exist(&self, view: View) -> Result<bool, Error> {
|
|
||||||
let key = self.open_view_for_read(view)?.this_key;
|
|
||||||
Ok(key.is_some())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ensure(&mut self, scope: Scope) -> Result<bool, Error> {
|
|
||||||
let key = self.open_scope_for_write(scope)?;
|
|
||||||
if let None = key.this_key {
|
|
||||||
let _ = key.parent_key.create_subkey_with_flags(
|
|
||||||
regext::blank_path_guard(self.key_name.inner())?,
|
|
||||||
KEY_READ | KEY_WRITE,
|
|
||||||
)?;
|
|
||||||
Ok(true)
|
|
||||||
} else {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete(&mut self, scope: Scope) -> Result<(), Error> {
|
|
||||||
let key = self.open_scope_for_write(scope)?;
|
|
||||||
key.parent_key
|
|
||||||
.delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_view_for_getter(&self, view: View) -> Result<RegKey, Error> {
|
|
||||||
self.open_view_for_read(view)?
|
|
||||||
.this_key
|
|
||||||
.ok_or(Error::InexistantKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey, Error> {
|
|
||||||
self.open_scope_for_write(scope)?
|
|
||||||
.this_key
|
|
||||||
.ok_or(Error::InexistantKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
const NAMEOF_SHELL_VERB_PART1: &str = "shell";
|
|
||||||
const NAMEOF_SHELL_VERB_PART3: &str = "command";
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// We temporarily use String and &str as the command line argument parameter.
|
|
||||||
// We may introduce a new complete Rust struct for replacing this arbitrary string.
|
|
||||||
|
|
||||||
pub fn get_shell_verb(&self, view: View) -> Result<ShellVerb, Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_shell_verb(&mut self, scope: Scope, sv: &ShellVerb) -> Result<(), Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
const NAMEOF_DEFAULT_ICON: &str = "DefaultIcon";
|
|
||||||
|
|
||||||
pub fn get_default_icon(&self, view: View) -> Result<Option<IconResVariant>, Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_default_icon(
|
|
||||||
&self,
|
|
||||||
scope: Scope,
|
|
||||||
icon: Option<&IconResVariant>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
const NAMEOF_FRIENDLY_APP_NAME: &str = "FriendlyAppName";
|
|
||||||
|
|
||||||
pub fn get_friendly_app_name(&self, view: View) -> Result<Option<StrResVariant>, Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_friendly_app_name(
|
|
||||||
&self,
|
|
||||||
scope: Scope,
|
|
||||||
name: Option<&StrResVariant>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
const NAMEOF_SUPPORTED_TYPES: &str = "SupportedTypes";
|
|
||||||
|
|
||||||
pub fn get_supported_types(&self, view: View) -> Result<Option<Vec<concept::Ext>>, Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_supported_types(
|
|
||||||
&self,
|
|
||||||
scope: Scope,
|
|
||||||
tys: Option<&[concept::Ext]>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
const NAMEOF_NO_OPEN_WITH: &str = "NoOpenWith";
|
|
||||||
|
|
||||||
pub fn get_no_open_with(&self, view: View) -> Result<bool, Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_no_open_with(&self, scope: Scope, flag: bool) -> Result<(), Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: File Extension Key
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct ExtKey {
|
|
||||||
ext: concept::Ext,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtKey {
|
|
||||||
pub fn new(inner: concept::Ext) -> Self {
|
|
||||||
Self { ext: inner }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner(&self) -> &concept::Ext {
|
|
||||||
&self.ext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtKey {
|
|
||||||
const FULL_CLASSES: &str = "Software\\Classes";
|
|
||||||
const PARTIAL_CLASSES: &str = "";
|
|
||||||
|
|
||||||
fn open_key(
|
|
||||||
&self,
|
|
||||||
territory: OpenKeyTerritory,
|
|
||||||
purpose: OpenKeyPurpose,
|
|
||||||
) -> Result<OpenedKey, Error> {
|
|
||||||
// check privilege
|
|
||||||
check_privilege(territory, purpose)?;
|
|
||||||
// Fetch the permission
|
|
||||||
let perms = match purpose {
|
|
||||||
OpenKeyPurpose::Read => KEY_READ,
|
|
||||||
OpenKeyPurpose::ReadWrite => KEY_READ | KEY_WRITE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// navigate to extension container
|
|
||||||
let hk = match territory {
|
|
||||||
OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER),
|
|
||||||
OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
|
||||||
OpenKeyTerritory::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT),
|
|
||||||
};
|
|
||||||
let classes = match territory {
|
|
||||||
OpenKeyTerritory::User | OpenKeyTerritory::System => {
|
|
||||||
hk.open_subkey_with_flags(Self::FULL_CLASSES, perms)?
|
|
||||||
}
|
|
||||||
OpenKeyTerritory::Hybrid => hk.open_subkey_with_flags(Self::PARTIAL_CLASSES, perms)?,
|
|
||||||
};
|
|
||||||
// open extension key if possible
|
|
||||||
let this_ext = regext::try_open_subkey_with_flags(
|
|
||||||
&classes,
|
|
||||||
regext::blank_path_guard(self.ext.dotted_inner())?,
|
|
||||||
perms,
|
|
||||||
)?;
|
|
||||||
// okey
|
|
||||||
Ok(OpenedKey::new(classes, this_ext))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region: ProgId Key
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct ProgIdKey {
|
|
||||||
progid: LosseProgId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProgIdKey {
|
|
||||||
pub fn new(inner: LosseProgId) -> Self {
|
|
||||||
Self { progid: inner }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner(&self) -> &LosseProgId {
|
|
||||||
&self.progid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|||||||
148
wfassoc/src/lowlevel/app_path_key.rs
Normal file
148
wfassoc/src/lowlevel/app_path_key.rs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
use super::{
|
||||||
|
Error, OpenKeyPurpose, OpenKeyTerritory, OpenedKey, PERM_RW, Result, Scope, check_privilege,
|
||||||
|
};
|
||||||
|
use crate::win32::{concept, regext};
|
||||||
|
use winreg::RegKey;
|
||||||
|
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct AppPathsKey {
|
||||||
|
key_name: concept::FileName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppPathsKey {
|
||||||
|
pub fn new(inner: concept::FileName) -> Self {
|
||||||
|
Self { key_name: inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &concept::FileName {
|
||||||
|
&self.key_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppPathsKey {
|
||||||
|
const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths";
|
||||||
|
|
||||||
|
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
||||||
|
// check privilege
|
||||||
|
check_privilege(territory, purpose)?;
|
||||||
|
// Fetch the permission
|
||||||
|
let perms = purpose.to_permission();
|
||||||
|
|
||||||
|
// get the root key
|
||||||
|
let hk = match territory {
|
||||||
|
OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER),
|
||||||
|
OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
||||||
|
// There is no way that territory is hybrid.
|
||||||
|
OpenKeyTerritory::Hybrid => panic!("unexpected hybrid key territory"),
|
||||||
|
};
|
||||||
|
// navigate to App Paths
|
||||||
|
let app_paths = hk.open_subkey_with_flags(Self::APP_PATHS, perms)?;
|
||||||
|
// open file name key if possible
|
||||||
|
let this_app = regext::try_open_subkey_with_flags(
|
||||||
|
&app_paths,
|
||||||
|
regext::blank_path_guard(self.key_name.inner())?,
|
||||||
|
perms,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// okey
|
||||||
|
Ok(OpenedKey::new(app_paths, this_app))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_scope_for_read(&self, scope: Scope) -> Result<OpenedKey> {
|
||||||
|
self.open_key(scope.into(), OpenKeyPurpose::Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||||
|
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_exist(&self, scope: Scope) -> Result<bool> {
|
||||||
|
let key = self.open_scope_for_read(scope)?.this_key;
|
||||||
|
Ok(key.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure this application key is presented in App Paths key.
|
||||||
|
///
|
||||||
|
/// Return true if we newly create this key,
|
||||||
|
/// otherwise false indicating there already is an existing key.
|
||||||
|
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
|
||||||
|
let key = self.open_scope_for_write(scope)?;
|
||||||
|
if let None = key.this_key {
|
||||||
|
let _ = key.parent_key.create_subkey_with_flags(
|
||||||
|
regext::blank_path_guard(self.key_name.inner())?,
|
||||||
|
PERM_RW,
|
||||||
|
)?;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete this application key from App Paths key.
|
||||||
|
///
|
||||||
|
/// Return true if we successfully delete this key,
|
||||||
|
/// otherwise false indicating there is no such key (already deleted).
|
||||||
|
pub fn delete(&mut self, scope: Scope) -> Result<bool> {
|
||||||
|
let key = self.open_scope_for_write(scope)?;
|
||||||
|
Ok(regext::arbitrarily_delete_subkey_all(
|
||||||
|
&key.parent_key,
|
||||||
|
regext::blank_path_guard(self.key_name.inner())?,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_scope_for_getter(&self, scope: Scope) -> Result<RegKey> {
|
||||||
|
self.open_scope_for_read(scope)?
|
||||||
|
.this_key
|
||||||
|
.ok_or(Error::InexistantKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
||||||
|
self.open_scope_for_write(scope)?
|
||||||
|
.this_key
|
||||||
|
.ok_or(Error::InexistantKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-app-paths-subkey
|
||||||
|
|
||||||
|
const NAMEOF_DEFAULT: &str = "";
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// This field point to the fully qualified path to the application.
|
||||||
|
pub fn get_default(&self, scope: Scope) -> Result<String> {
|
||||||
|
let key = self.open_scope_for_getter(scope)?;
|
||||||
|
Ok(key.get_value(Self::NAMEOF_DEFAULT)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// This field should be filled with fully qualified path to the application.
|
||||||
|
pub fn set_default(&mut self, scope: Scope, value: &str) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
key.set_value(Self::NAMEOF_DEFAULT, &value)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_PATH: &str = "Path";
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// This field point to the added path for PATH environment variable.
|
||||||
|
/// Usually it is the path to application directory.
|
||||||
|
pub fn get_path(&self, scope: Scope) -> Result<String> {
|
||||||
|
let key = self.open_scope_for_getter(scope)?;
|
||||||
|
Ok(key.get_value(Self::NAMEOF_PATH)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// This field should be the added path for PATH environment variable.
|
||||||
|
/// Usually it is the path to application directory.
|
||||||
|
pub fn set_path(&mut self, scope: Scope, value: &str) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
key.set_value(Self::NAMEOF_PATH, &value)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
336
wfassoc/src/lowlevel/applications_key.rs
Normal file
336
wfassoc/src/lowlevel/applications_key.rs
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
use super::{
|
||||||
|
Error, IconResVariant, OpenKeyPurpose, OpenKeyTerritory, OpenedKey, PERM_R, PERM_RW, Result,
|
||||||
|
Scope, ShellVerb, StrResVariant, View, check_privilege,
|
||||||
|
};
|
||||||
|
use crate::win32::{concept, regext};
|
||||||
|
use winreg::RegKey;
|
||||||
|
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct ApplicationsKey {
|
||||||
|
key_name: concept::FileName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationsKey {
|
||||||
|
pub fn new(inner: concept::FileName) -> Self {
|
||||||
|
Self { key_name: inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &concept::FileName {
|
||||||
|
&self.key_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationsKey {
|
||||||
|
const FULL_APPLICATIONS: &str = "Software\\Classes\\Applications";
|
||||||
|
const PARTIAL_APPLICATIONS: &str = "Applications";
|
||||||
|
|
||||||
|
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
||||||
|
// check privilege
|
||||||
|
check_privilege(territory, purpose)?;
|
||||||
|
// Fetch the permission
|
||||||
|
let perms = purpose.to_permission();
|
||||||
|
|
||||||
|
// get the root key
|
||||||
|
let hk = match territory {
|
||||||
|
OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER),
|
||||||
|
OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
||||||
|
OpenKeyTerritory::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT),
|
||||||
|
};
|
||||||
|
let applications = match territory {
|
||||||
|
OpenKeyTerritory::User | OpenKeyTerritory::System => {
|
||||||
|
hk.open_subkey_with_flags(Self::FULL_APPLICATIONS, perms)?
|
||||||
|
}
|
||||||
|
OpenKeyTerritory::Hybrid => {
|
||||||
|
hk.open_subkey_with_flags(Self::PARTIAL_APPLICATIONS, perms)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// open app key if possible
|
||||||
|
let this_app = regext::try_open_subkey_with_flags(
|
||||||
|
&applications,
|
||||||
|
regext::blank_path_guard(self.key_name.inner())?,
|
||||||
|
perms,
|
||||||
|
)?;
|
||||||
|
// okey
|
||||||
|
Ok(OpenedKey::new(applications, this_app))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
|
||||||
|
self.open_key(view.into(), OpenKeyPurpose::Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||||
|
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_exist(&self, view: View) -> Result<bool> {
|
||||||
|
let key = self.open_view_for_read(view)?.this_key;
|
||||||
|
Ok(key.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure this application key is presented in Applications key.
|
||||||
|
///
|
||||||
|
/// Return true if we newly create this key,
|
||||||
|
/// otherwise false indicating there already is an existing key.
|
||||||
|
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
|
||||||
|
let key = self.open_scope_for_write(scope)?;
|
||||||
|
if let None = key.this_key {
|
||||||
|
let _ = key.parent_key.create_subkey_with_flags(
|
||||||
|
regext::blank_path_guard(self.key_name.inner())?,
|
||||||
|
PERM_RW,
|
||||||
|
)?;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete this application key from Applications key.
|
||||||
|
///
|
||||||
|
/// Return true if we successfully delete this key,
|
||||||
|
/// otherwise false indicating there is no such key (already deleted).
|
||||||
|
pub fn delete(&mut self, scope: Scope) -> Result<bool> {
|
||||||
|
let key = self.open_scope_for_write(scope)?;
|
||||||
|
Ok(regext::arbitrarily_delete_subkey_all(
|
||||||
|
&key.parent_key,
|
||||||
|
regext::blank_path_guard(self.key_name.inner())?,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-applications-subkey
|
||||||
|
|
||||||
|
fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
|
||||||
|
self.open_view_for_read(view)?
|
||||||
|
.this_key
|
||||||
|
.ok_or(Error::InexistantKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
||||||
|
self.open_scope_for_write(scope)?
|
||||||
|
.this_key
|
||||||
|
.ok_or(Error::InexistantKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_SHELL_VERB_PART1: &str = "shell";
|
||||||
|
const NAMEOF_SHELL_VERB_PART3: &str = "command";
|
||||||
|
const NAMEOF_SHELL_VERB_PART4: &str = "";
|
||||||
|
|
||||||
|
pub fn get_shell_verb(&self, view: View) -> Result<Option<ShellVerb>> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
|
||||||
|
// Get shell subkey
|
||||||
|
let shell_key = match regext::try_open_subkey_with_flags(
|
||||||
|
&key,
|
||||||
|
Self::NAMEOF_SHELL_VERB_PART1,
|
||||||
|
PERM_R,
|
||||||
|
)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
// Get verb subkey name, then get subkey itself.
|
||||||
|
let verb_key_name = match regext::get_sole_subkey_name(&shell_key)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
let verb_key = match regext::try_open_subkey_with_flags(
|
||||||
|
&shell_key,
|
||||||
|
regext::blank_path_guard(&verb_key_name)?,
|
||||||
|
PERM_R,
|
||||||
|
)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
// Get command subkey.
|
||||||
|
let command_key = match regext::try_open_subkey_with_flags(
|
||||||
|
&verb_key,
|
||||||
|
Self::NAMEOF_SHELL_VERB_PART3,
|
||||||
|
PERM_R,
|
||||||
|
)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
// Get the default value of command subkey
|
||||||
|
let command_default_value = match regext::try_get_value::<String, _>(
|
||||||
|
&command_key,
|
||||||
|
Self::NAMEOF_SHELL_VERB_PART4,
|
||||||
|
)? {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Okey, return value.
|
||||||
|
Ok(Some(ShellVerb::new(
|
||||||
|
verb_key_name.parse()?,
|
||||||
|
command_default_value.parse()?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
|
match sv {
|
||||||
|
Some(sv) => {
|
||||||
|
// Create shell subkey
|
||||||
|
let (shell_key, _) =
|
||||||
|
key.create_subkey_with_flags(Self::NAMEOF_SHELL_VERB_PART1, PERM_RW)?;
|
||||||
|
// Create verb key
|
||||||
|
let (verb_key, _) =
|
||||||
|
shell_key.create_subkey_with_flags(sv.get_verb().inner(), PERM_RW)?;
|
||||||
|
// Create command key
|
||||||
|
let (command_key, _) =
|
||||||
|
verb_key.create_subkey_with_flags(Self::NAMEOF_SHELL_VERB_PART3, PERM_RW)?;
|
||||||
|
// Set command key default value
|
||||||
|
command_key.set_value(Self::NAMEOF_SHELL_VERB_PART4, &sv.get_command().full())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Delete shell and its all subkey.
|
||||||
|
regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_SHELL_VERB_PART1)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon";
|
||||||
|
const NAMEOF_DEFAULT_ICON_PART2: &str = "";
|
||||||
|
|
||||||
|
pub fn get_default_icon(&self, view: View) -> Result<Option<IconResVariant>> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
// Get default icon subkey
|
||||||
|
let default_icon_key = match regext::try_open_subkey_with_flags(
|
||||||
|
&key,
|
||||||
|
Self::NAMEOF_DEFAULT_ICON_PART1,
|
||||||
|
PERM_R,
|
||||||
|
)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
// Get the default value of default icon subkey
|
||||||
|
let default_icon_default_value =
|
||||||
|
regext::try_get_value::<String, _>(&default_icon_key, Self::NAMEOF_DEFAULT_ICON_PART2)?;
|
||||||
|
// Transform it as result
|
||||||
|
Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_default_icon(&mut self, scope: Scope, icon: Option<&IconResVariant>) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
|
match icon {
|
||||||
|
Some(icon) => {
|
||||||
|
// Create default icon subkey
|
||||||
|
let (default_icon_key, _) =
|
||||||
|
key.create_subkey_with_flags(Self::NAMEOF_DEFAULT_ICON_PART1, PERM_RW)?;
|
||||||
|
// Set default value of default icon subkey.
|
||||||
|
default_icon_key.set_value(Self::NAMEOF_DEFAULT_ICON_PART2, &icon.to_string())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Delete shell and its all subkey.
|
||||||
|
regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_DEFAULT_ICON_PART1)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_FRIENDLY_APP_NAME: &str = "FriendlyAppName";
|
||||||
|
|
||||||
|
pub fn get_friendly_app_name(&self, view: View) -> Result<Option<StrResVariant>> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
// Get value of it
|
||||||
|
let value = regext::try_get_value::<String, _>(&key, Self::NAMEOF_FRIENDLY_APP_NAME)?;
|
||||||
|
// Transform it as result
|
||||||
|
Ok(value.map(|v| StrResVariant::from(v.as_str())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_friendly_app_name(
|
||||||
|
&mut self,
|
||||||
|
scope: Scope,
|
||||||
|
name: Option<&StrResVariant>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
|
match name {
|
||||||
|
Some(name) => {
|
||||||
|
// Set value for this key
|
||||||
|
key.set_value(Self::NAMEOF_FRIENDLY_APP_NAME, &name.to_string())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Delete this key
|
||||||
|
regext::arbitrarily_delete_value(&key, Self::NAMEOF_FRIENDLY_APP_NAME)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_SUPPORTED_TYPES: &str = "SupportedTypes";
|
||||||
|
|
||||||
|
pub fn get_supported_types(&self, view: View) -> Result<Option<Vec<concept::Ext>>> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
// Get supported types subkey
|
||||||
|
let supported_types_key =
|
||||||
|
match regext::try_open_subkey_with_flags(&key, Self::NAMEOF_SUPPORTED_TYPES, PERM_R)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
// Fetch all sub-values
|
||||||
|
let key_names = regext::get_all_string_subkey_names(&supported_types_key)?;
|
||||||
|
// Map the result
|
||||||
|
let exts = key_names
|
||||||
|
.into_iter()
|
||||||
|
.map(|name| name.parse::<concept::Ext>())
|
||||||
|
.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||||
|
// Return value
|
||||||
|
Ok(Some(exts))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_supported_types(
|
||||||
|
&mut self,
|
||||||
|
scope: Scope,
|
||||||
|
tys: Option<&[&concept::Ext]>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
|
match tys {
|
||||||
|
Some(tys) => {
|
||||||
|
// Create supported types key
|
||||||
|
let (supported_types_key, _) =
|
||||||
|
key.create_subkey_with_flags(Self::NAMEOF_SUPPORTED_TYPES, PERM_RW)?;
|
||||||
|
// Clean all contents of this key
|
||||||
|
regext::clean_all_contents(&supported_types_key)?;
|
||||||
|
// Add file types one by one
|
||||||
|
for ty in tys {
|
||||||
|
supported_types_key
|
||||||
|
.set_value(regext::blank_path_guard(ty.dotted_inner())?, &"")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Delete this subkey.
|
||||||
|
regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_SUPPORTED_TYPES)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_NO_OPEN_WITH: &str = "NoOpenWith";
|
||||||
|
|
||||||
|
pub fn get_no_open_with(&self, view: View) -> Result<bool> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
match regext::try_get_value::<String, _>(&key, Self::NAMEOF_NO_OPEN_WITH)? {
|
||||||
|
Some(_) => Ok(true),
|
||||||
|
None => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_no_open_with(&mut self, scope: Scope, flag: bool) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
if flag {
|
||||||
|
key.set_value(Self::NAMEOF_NO_OPEN_WITH, &"")?;
|
||||||
|
} else {
|
||||||
|
regext::arbitrarily_delete_value(&key, Self::NAMEOF_NO_OPEN_WITH)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
223
wfassoc/src/lowlevel/ext_key.rs
Normal file
223
wfassoc/src/lowlevel/ext_key.rs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
use super::{
|
||||||
|
Error, LosseProgId, OpenKeyPurpose, OpenKeyTerritory, OpenedKey, PERM_R, PERM_RW, Result,
|
||||||
|
Scope, View, check_privilege,
|
||||||
|
};
|
||||||
|
use crate::win32::{concept, regext};
|
||||||
|
use winreg::RegKey;
|
||||||
|
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct ExtKey {
|
||||||
|
ext: concept::Ext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtKey {
|
||||||
|
pub fn new(inner: concept::Ext) -> Self {
|
||||||
|
Self { ext: inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &concept::Ext {
|
||||||
|
&self.ext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtKey {
|
||||||
|
const FULL_CLASSES: &str = "Software\\Classes";
|
||||||
|
const PARTIAL_CLASSES: &str = "";
|
||||||
|
|
||||||
|
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
||||||
|
// check privilege
|
||||||
|
check_privilege(territory, purpose)?;
|
||||||
|
// Fetch the permission
|
||||||
|
let perms = purpose.to_permission();
|
||||||
|
|
||||||
|
// navigate to extension container
|
||||||
|
let hk = match territory {
|
||||||
|
OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER),
|
||||||
|
OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
||||||
|
OpenKeyTerritory::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT),
|
||||||
|
};
|
||||||
|
let classes = match territory {
|
||||||
|
OpenKeyTerritory::User | OpenKeyTerritory::System => {
|
||||||
|
hk.open_subkey_with_flags(Self::FULL_CLASSES, perms)?
|
||||||
|
}
|
||||||
|
OpenKeyTerritory::Hybrid => hk.open_subkey_with_flags(Self::PARTIAL_CLASSES, perms)?,
|
||||||
|
};
|
||||||
|
// open extension key if possible
|
||||||
|
let this_ext = regext::try_open_subkey_with_flags(
|
||||||
|
&classes,
|
||||||
|
regext::blank_path_guard(self.ext.dotted_inner())?,
|
||||||
|
perms,
|
||||||
|
)?;
|
||||||
|
// okey
|
||||||
|
Ok(OpenedKey::new(classes, this_ext))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
|
||||||
|
self.open_key(view.into(), OpenKeyPurpose::Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||||
|
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_exist(&self, view: View) -> Result<bool> {
|
||||||
|
let key = self.open_view_for_read(view)?.this_key;
|
||||||
|
Ok(key.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure this file extension key is presented in Classes key.
|
||||||
|
///
|
||||||
|
/// Return true if we newly create this key,
|
||||||
|
/// otherwise false indicating there already is an existing key.
|
||||||
|
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
|
||||||
|
let key = self.open_scope_for_write(scope)?;
|
||||||
|
if let None = key.this_key {
|
||||||
|
let _ = key.parent_key.create_subkey_with_flags(
|
||||||
|
regext::blank_path_guard(self.ext.dotted_inner())?,
|
||||||
|
PERM_RW,
|
||||||
|
)?;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete this file extension key from Classes key.
|
||||||
|
///
|
||||||
|
/// Return true if we successfully delete this key,
|
||||||
|
/// otherwise false indicating there is no such key (already deleted).
|
||||||
|
pub fn delete(&mut self, scope: Scope) -> Result<bool> {
|
||||||
|
let key = self.open_scope_for_write(scope)?;
|
||||||
|
Ok(regext::arbitrarily_delete_subkey_all(
|
||||||
|
&key.parent_key,
|
||||||
|
regext::blank_path_guard(self.ext.dotted_inner())?,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#setting-optional-subkeys-and-file-type-extension-attributes
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// We do not support "Content Type" and "PerceivedType"
|
||||||
|
// because current interface are enough to use,
|
||||||
|
// and these types has not been made as concept struct in Rust.
|
||||||
|
// We may expand these in future.
|
||||||
|
|
||||||
|
fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
|
||||||
|
self.open_view_for_read(view)?
|
||||||
|
.this_key
|
||||||
|
.ok_or(Error::InexistantKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
||||||
|
self.open_scope_for_write(scope)?
|
||||||
|
.this_key
|
||||||
|
.ok_or(Error::InexistantKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_DEFAULT: &str = "";
|
||||||
|
|
||||||
|
pub fn get_default(&self, view: View) -> Result<Option<LosseProgId>> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
// Get value of it
|
||||||
|
let value = regext::try_get_value::<String, _>(&key, Self::NAMEOF_DEFAULT)?;
|
||||||
|
// Transform it as result
|
||||||
|
Ok(value.map(|v| LosseProgId::from(v.as_str())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_default(&mut self, scope: Scope, pid: Option<&LosseProgId>) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
|
match pid {
|
||||||
|
Some(pid) => {
|
||||||
|
// Set value for this key
|
||||||
|
key.set_value(Self::NAMEOF_DEFAULT, &pid.to_string())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Delete this key
|
||||||
|
regext::arbitrarily_delete_value(&key, Self::NAMEOF_DEFAULT)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_OPEN_WITH_PROGIDS: &str = "OpenWithProgIds";
|
||||||
|
|
||||||
|
pub fn get_open_with_progids(&self, view: View) -> Result<Option<Vec<LosseProgId>>> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
// Get OpenWithProgIds subkey
|
||||||
|
let open_with_progids_key =
|
||||||
|
match regext::try_open_subkey_with_flags(&key, Self::NAMEOF_OPEN_WITH_PROGIDS, PERM_R)?
|
||||||
|
{
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
// Fetch all sub-values
|
||||||
|
let key_names = regext::get_all_string_subkey_names(&open_with_progids_key)?;
|
||||||
|
// Map the result
|
||||||
|
let progids = key_names
|
||||||
|
.into_iter()
|
||||||
|
.map(|name| LosseProgId::from(name.as_str()))
|
||||||
|
.collect();
|
||||||
|
// Return value
|
||||||
|
Ok(Some(progids))
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// If there is no "OpenWithProgIds" subkey, this function return false.
|
||||||
|
pub fn is_in_open_with_progids(&self, view: View, pid: &LosseProgId) -> Result<bool> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
// Get OpenWithProgIds subkey
|
||||||
|
let open_with_progids_key =
|
||||||
|
match regext::try_open_subkey_with_flags(&key, Self::NAMEOF_OPEN_WITH_PROGIDS, PERM_R)?
|
||||||
|
{
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(false),
|
||||||
|
};
|
||||||
|
// Check whether there is given ProgId
|
||||||
|
Ok(regext::try_get_value::<String, _>(
|
||||||
|
&open_with_progids_key,
|
||||||
|
regext::blank_path_guard(pid.to_string())?,
|
||||||
|
)?
|
||||||
|
.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// If there is no "OpenWithProgIds" subkey, this function will create it first,
|
||||||
|
/// then add your given ProgId into it.
|
||||||
|
pub fn add_into_open_with_progids(&mut self, scope: Scope, pid: &LosseProgId) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
// Get subkey
|
||||||
|
let (open_with_progids_key, _) =
|
||||||
|
key.create_subkey_with_flags(Self::NAMEOF_OPEN_WITH_PROGIDS, PERM_RW)?;
|
||||||
|
// Add value
|
||||||
|
open_with_progids_key.set_value(regext::blank_path_guard(pid.to_string())?, &"")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// If there is no "OpenWithProgIds" subkey, this function do nothing.
|
||||||
|
pub fn remove_from_open_with_progids(&mut self, scope: Scope, pid: &LosseProgId) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
// Try get subkey
|
||||||
|
let open_with_progids_key = match regext::try_open_subkey_with_flags(
|
||||||
|
&key,
|
||||||
|
Self::NAMEOF_OPEN_WITH_PROGIDS,
|
||||||
|
PERM_RW,
|
||||||
|
)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
// Remove given key
|
||||||
|
regext::arbitrarily_delete_value(
|
||||||
|
&open_with_progids_key,
|
||||||
|
regext::blank_path_guard(pid.to_string())?,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
302
wfassoc/src/lowlevel/progid_key.rs
Normal file
302
wfassoc/src/lowlevel/progid_key.rs
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
use super::{
|
||||||
|
Error, IconResVariant, LosseProgId, OpenKeyPurpose, OpenKeyTerritory, OpenedKey, PERM_R,
|
||||||
|
PERM_RW, Result, Scope, ShellVerb, StrResVariant, View, check_privilege,
|
||||||
|
};
|
||||||
|
use crate::win32::regext;
|
||||||
|
use winreg::RegKey;
|
||||||
|
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct ProgIdKey {
|
||||||
|
progid: LosseProgId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgIdKey {
|
||||||
|
pub fn new(inner: LosseProgId) -> Self {
|
||||||
|
Self { progid: inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &LosseProgId {
|
||||||
|
&self.progid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgIdKey {
|
||||||
|
const FULL_CLASSES: &str = "Software\\Classes";
|
||||||
|
const PARTIAL_CLASSES: &str = "";
|
||||||
|
|
||||||
|
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
||||||
|
// check privilege
|
||||||
|
check_privilege(territory, purpose)?;
|
||||||
|
// Fetch the permission
|
||||||
|
let perms = purpose.to_permission();
|
||||||
|
|
||||||
|
// navigate to ProgId container
|
||||||
|
let hk = match territory {
|
||||||
|
OpenKeyTerritory::User => RegKey::predef(HKEY_CURRENT_USER),
|
||||||
|
OpenKeyTerritory::System => RegKey::predef(HKEY_LOCAL_MACHINE),
|
||||||
|
OpenKeyTerritory::Hybrid => RegKey::predef(HKEY_CLASSES_ROOT),
|
||||||
|
};
|
||||||
|
let classes = match territory {
|
||||||
|
OpenKeyTerritory::User | OpenKeyTerritory::System => {
|
||||||
|
hk.open_subkey_with_flags(Self::FULL_CLASSES, perms)?
|
||||||
|
}
|
||||||
|
OpenKeyTerritory::Hybrid => hk.open_subkey_with_flags(Self::PARTIAL_CLASSES, perms)?,
|
||||||
|
};
|
||||||
|
// open ProgId key if possible
|
||||||
|
let this_progid = regext::try_open_subkey_with_flags(
|
||||||
|
&classes,
|
||||||
|
regext::blank_path_guard(self.progid.to_string())?,
|
||||||
|
perms,
|
||||||
|
)?;
|
||||||
|
// okey
|
||||||
|
Ok(OpenedKey::new(classes, this_progid))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
|
||||||
|
self.open_key(view.into(), OpenKeyPurpose::Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||||
|
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_exist(&self, view: View) -> Result<bool> {
|
||||||
|
let key = self.open_view_for_read(view)?.this_key;
|
||||||
|
Ok(key.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure this ProgId key is presented in Classes key.
|
||||||
|
///
|
||||||
|
/// Return true if we newly create this key,
|
||||||
|
/// otherwise false indicating there already is an existing key.
|
||||||
|
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
|
||||||
|
let key = self.open_scope_for_write(scope)?;
|
||||||
|
if let None = key.this_key {
|
||||||
|
let _ = key.parent_key.create_subkey_with_flags(
|
||||||
|
regext::blank_path_guard(self.progid.to_string())?,
|
||||||
|
PERM_RW,
|
||||||
|
)?;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete this ProgId key from Classes key.
|
||||||
|
///
|
||||||
|
/// Return true if we successfully delete this key,
|
||||||
|
/// otherwise false indicating there is no such key (already deleted).
|
||||||
|
pub fn delete(&mut self, scope: Scope) -> Result<bool> {
|
||||||
|
let key = self.open_scope_for_write(scope)?;
|
||||||
|
Ok(regext::arbitrarily_delete_subkey_all(
|
||||||
|
&key.parent_key,
|
||||||
|
regext::blank_path_guard(self.progid.to_string())?,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Currently we only support (Default), FriendlyTypeName and DefaultIcon
|
||||||
|
// to just cover the basic usage.
|
||||||
|
// We may expand these in future.
|
||||||
|
|
||||||
|
fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
|
||||||
|
self.open_view_for_read(view)?
|
||||||
|
.this_key
|
||||||
|
.ok_or(Error::InexistantKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
||||||
|
self.open_scope_for_write(scope)?
|
||||||
|
.this_key
|
||||||
|
.ok_or(Error::InexistantKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_DEFAULT: &str = "";
|
||||||
|
|
||||||
|
pub fn get_default(&self, view: View) -> Result<Option<StrResVariant>> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
// Get value of it
|
||||||
|
let value = regext::try_get_value::<String, _>(&key, Self::NAMEOF_DEFAULT)?;
|
||||||
|
// Transform it as result
|
||||||
|
Ok(value.map(|v| StrResVariant::from(v.as_str())))
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// The legacy way to set friendly name for this ProgId.
|
||||||
|
pub fn set_default(&mut self, scope: Scope, name: Option<&StrResVariant>) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
|
match name {
|
||||||
|
Some(name) => {
|
||||||
|
// Set value for this key
|
||||||
|
key.set_value(Self::NAMEOF_DEFAULT, &name.to_string())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Delete this key
|
||||||
|
regext::arbitrarily_delete_value(&key, Self::NAMEOF_DEFAULT)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_SHELL_VERB_PART1: &str = "shell";
|
||||||
|
const NAMEOF_SHELL_VERB_PART3: &str = "command";
|
||||||
|
const NAMEOF_SHELL_VERB_PART4: &str = "";
|
||||||
|
|
||||||
|
pub fn get_shell_verb(&self, view: View) -> Result<Option<ShellVerb>> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
|
||||||
|
// Get shell subkey
|
||||||
|
let shell_key = match regext::try_open_subkey_with_flags(
|
||||||
|
&key,
|
||||||
|
Self::NAMEOF_SHELL_VERB_PART1,
|
||||||
|
PERM_R,
|
||||||
|
)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
// Get verb subkey name, then get subkey itself.
|
||||||
|
let verb_key_name = match regext::get_sole_subkey_name(&shell_key)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
let verb_key = match regext::try_open_subkey_with_flags(
|
||||||
|
&shell_key,
|
||||||
|
regext::blank_path_guard(&verb_key_name)?,
|
||||||
|
PERM_R,
|
||||||
|
)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
// Get command subkey.
|
||||||
|
let command_key = match regext::try_open_subkey_with_flags(
|
||||||
|
&verb_key,
|
||||||
|
Self::NAMEOF_SHELL_VERB_PART3,
|
||||||
|
PERM_R,
|
||||||
|
)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
// Get the default value of command subkey
|
||||||
|
let command_default_value = match regext::try_get_value::<String, _>(
|
||||||
|
&command_key,
|
||||||
|
Self::NAMEOF_SHELL_VERB_PART4,
|
||||||
|
)? {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Okey, return value.
|
||||||
|
Ok(Some(ShellVerb::new(
|
||||||
|
verb_key_name.parse()?,
|
||||||
|
command_default_value.parse()?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
|
match sv {
|
||||||
|
Some(sv) => {
|
||||||
|
// Create shell subkey
|
||||||
|
let (shell_key, _) =
|
||||||
|
key.create_subkey_with_flags(Self::NAMEOF_SHELL_VERB_PART1, PERM_RW)?;
|
||||||
|
// Create verb key
|
||||||
|
let (verb_key, _) =
|
||||||
|
shell_key.create_subkey_with_flags(sv.get_verb().inner(), PERM_RW)?;
|
||||||
|
// Create command key
|
||||||
|
let (command_key, _) =
|
||||||
|
verb_key.create_subkey_with_flags(Self::NAMEOF_SHELL_VERB_PART3, PERM_RW)?;
|
||||||
|
// Set command key default value
|
||||||
|
command_key.set_value(Self::NAMEOF_SHELL_VERB_PART4, &sv.get_command().full())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Delete shell and its all subkey.
|
||||||
|
regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_SHELL_VERB_PART1)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_FRIENDLY_TYPE_NAME: &str = "FriendlyTypeName";
|
||||||
|
|
||||||
|
pub fn get_friendly_type_name(&self, view: View) -> Result<Option<StrResVariant>> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
// Get value of it
|
||||||
|
let value = regext::try_get_value::<String, _>(&key, Self::NAMEOF_FRIENDLY_TYPE_NAME)?;
|
||||||
|
// Transform it as result
|
||||||
|
Ok(value.map(|v| StrResVariant::from(v.as_str())))
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// Set this entry to a friendly name for the ProgID.
|
||||||
|
pub fn set_friendly_type_name(
|
||||||
|
&mut self,
|
||||||
|
scope: Scope,
|
||||||
|
name: Option<&StrResVariant>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
|
match name {
|
||||||
|
Some(name) => {
|
||||||
|
// Set value for this key
|
||||||
|
key.set_value(Self::NAMEOF_FRIENDLY_TYPE_NAME, &name.to_string())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Delete this key
|
||||||
|
regext::arbitrarily_delete_value(&key, Self::NAMEOF_FRIENDLY_TYPE_NAME)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon";
|
||||||
|
const NAMEOF_DEFAULT_ICON_PART2: &str = "";
|
||||||
|
|
||||||
|
pub fn get_default_icon(&self, view: View) -> Result<Option<IconResVariant>> {
|
||||||
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
// Get default icon subkey
|
||||||
|
let default_icon_key = match regext::try_open_subkey_with_flags(
|
||||||
|
&key,
|
||||||
|
Self::NAMEOF_DEFAULT_ICON_PART1,
|
||||||
|
PERM_R,
|
||||||
|
)? {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
// Get the default value of default icon subkey
|
||||||
|
let default_icon_default_value =
|
||||||
|
regext::try_get_value::<String, _>(&default_icon_key, Self::NAMEOF_DEFAULT_ICON_PART2)?;
|
||||||
|
// Transform it as result
|
||||||
|
Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_default_icon(&mut self, scope: Scope, icon: Option<&IconResVariant>) -> Result<()> {
|
||||||
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
|
match icon {
|
||||||
|
Some(icon) => {
|
||||||
|
// Create default icon subkey
|
||||||
|
let (default_icon_key, _) =
|
||||||
|
key.create_subkey_with_flags(Self::NAMEOF_DEFAULT_ICON_PART1, PERM_RW)?;
|
||||||
|
// Set default value of default icon subkey.
|
||||||
|
default_icon_key.set_value(Self::NAMEOF_DEFAULT_ICON_PART2, &icon.to_string())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Delete shell and its all subkey.
|
||||||
|
regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_DEFAULT_ICON_PART1)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,24 +5,6 @@ use std::iter::FusedIterator;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use thiserror::Error as TeError;
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
/// The println macro only works on Debug mode
|
|
||||||
/// for tracing the execution of some important functions.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! debug_println {
|
|
||||||
// For no argument.
|
|
||||||
() => {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
eprintln!();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// For one or more arguments like println!.
|
|
||||||
($($arg:tt)*) => {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
eprintln!($($arg)*);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// region: OS String Related
|
// region: OS String Related
|
||||||
|
|
||||||
/// The error occurs when casting `OsStr` into `str`.
|
/// The error occurs when casting `OsStr` into `str`.
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ impl FromStr for IconRefStr {
|
|||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
static RE: LazyLock<Regex> = LazyLock::new(|| {
|
static RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
Regex::new(r"^([^,@].*),-([0-9]+)$").expect("unexpected bad regex pattern string")
|
Regex::new(r"^([^,@][^,]*),-?([0-9]+)$").expect("unexpected bad regex pattern string")
|
||||||
});
|
});
|
||||||
let caps = RE.captures(s);
|
let caps = RE.captures(s);
|
||||||
if let Some(caps) = caps {
|
if let Some(caps) = caps {
|
||||||
@@ -461,7 +461,7 @@ impl FromStr for StrRefStr {
|
|||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
static RE: LazyLock<Regex> = LazyLock::new(|| {
|
static RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
Regex::new(r"^@(.+),-([0-9]+)$").expect("unexpected bad regex pattern string")
|
Regex::new(r"^@([^,]+),-?([0-9]+)$").expect("unexpected bad regex pattern string")
|
||||||
});
|
});
|
||||||
let caps = RE.captures(s);
|
let caps = RE.captures(s);
|
||||||
if let Some(caps) = caps {
|
if let Some(caps) = caps {
|
||||||
@@ -510,6 +510,20 @@ pub struct IconRc {
|
|||||||
icon: HICON,
|
icon: HICON,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IconRc {
|
||||||
|
/// Get the icon preset representing generic document.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn GENERIC_DOCUMENT(kind: IconSizeKind) -> Result<Self, LoadIconRcError> {
|
||||||
|
Self::new("shell32.dll", 0, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the icon preset representing generic executable.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn GENERIC_APPLICATION(kind: IconSizeKind) -> Result<Self, LoadIconRcError> {
|
||||||
|
Self::new("imageres.dll", 0, kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IconRc {
|
impl IconRc {
|
||||||
/// Load icon from executable or `.ico` file.
|
/// Load icon from executable or `.ico` file.
|
||||||
///
|
///
|
||||||
@@ -575,6 +589,18 @@ impl Drop for IconRc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// Win32 HICON can be safely moved between different threads naturally.
|
||||||
|
unsafe impl Send for IconRc {}
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// When multiple threads calling this struct, there is no conflict between them,
|
||||||
|
// because this struct only provide HICON itself.
|
||||||
|
//
|
||||||
|
// However, the caller should ensure that all calls using this HICON
|
||||||
|
// follow Win32 thread model.
|
||||||
|
unsafe impl Sync for IconRc {}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region: String Resource
|
// region: String Resource
|
||||||
@@ -699,13 +725,10 @@ pub struct ExpandString {
|
|||||||
inner: String,
|
inner: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExpandString {
|
/// Internal shared compiled regex pattern matching Variable,
|
||||||
/// Internal shared compiled regex pattern matching Variable,
|
/// the `%` braced string like `%SystemRoot%`.
|
||||||
/// the `%` braced string like `%SystemRoot%`.
|
static WINVAR_RE: LazyLock<Regex> =
|
||||||
const VAR_RE: LazyLock<Regex> = LazyLock::new(|| {
|
LazyLock::new(|| Regex::new(r"%[a-zA-Z0-9_]+%").expect("unexpected bad regex pattern string"));
|
||||||
Regex::new(r"%[a-zA-Z0-9_]+%").expect("unexpected bad regex pattern string")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExpandString {
|
impl ExpandString {
|
||||||
/// Create a new expand string
|
/// Create a new expand string
|
||||||
@@ -715,16 +738,16 @@ impl ExpandString {
|
|||||||
|
|
||||||
/// Expand the variables located in this string
|
/// Expand the variables located in this string
|
||||||
/// and produce the final usable string.
|
/// and produce the final usable string.
|
||||||
pub fn expand_string(&self) -> Result<String, ExpandEnvVarError> {
|
pub fn expand(&self) -> Result<String, ExpandEnvVarError> {
|
||||||
use windows_sys::Win32::System::Environment::ExpandEnvironmentStringsW;
|
use windows_sys::Win32::System::Environment::ExpandEnvironmentStringsW;
|
||||||
|
|
||||||
// Fetch the size of expand result
|
// Fetch the size of expand result
|
||||||
let source = WideCString::from_str(self.inner.as_str())?;
|
let source = WideCString::from_str(self.inner.as_str())?;
|
||||||
|
// The return size is including null terminal.
|
||||||
let size = unsafe { ExpandEnvironmentStringsW(source.as_ptr(), std::ptr::null_mut(), 0) };
|
let size = unsafe { ExpandEnvironmentStringsW(source.as_ptr(), std::ptr::null_mut(), 0) };
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return Err(ExpandEnvVarError::ExpandFunction);
|
return Err(ExpandEnvVarError::ExpandFunction);
|
||||||
}
|
}
|
||||||
let size_no_nul = size.checked_sub(1).ok_or(ExpandEnvVarError::Underflow)?;
|
|
||||||
|
|
||||||
// Allocate buffer for it.
|
// Allocate buffer for it.
|
||||||
let len: usize = size.try_into()?;
|
let len: usize = size.try_into()?;
|
||||||
@@ -732,7 +755,7 @@ impl ExpandString {
|
|||||||
let mut buffer = vec![0; len];
|
let mut buffer = vec![0; len];
|
||||||
// Receive result
|
// Receive result
|
||||||
let size =
|
let size =
|
||||||
unsafe { ExpandEnvironmentStringsW(source.as_ptr(), buffer.as_mut_ptr(), size_no_nul) };
|
unsafe { ExpandEnvironmentStringsW(source.as_ptr(), buffer.as_mut_ptr(), size) };
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return Err(ExpandEnvVarError::ExpandFunction);
|
return Err(ExpandEnvVarError::ExpandFunction);
|
||||||
}
|
}
|
||||||
@@ -743,7 +766,7 @@ impl ExpandString {
|
|||||||
|
|
||||||
// If the final string still has environment variable,
|
// If the final string still has environment variable,
|
||||||
// we think we fail to expand it.
|
// we think we fail to expand it.
|
||||||
if Self::VAR_RE.is_match(rv.as_str()) {
|
if WINVAR_RE.is_match(rv.as_str()) {
|
||||||
Err(ExpandEnvVarError::NoEnvVar)
|
Err(ExpandEnvVarError::NoEnvVar)
|
||||||
} else {
|
} else {
|
||||||
Ok(rv)
|
Ok(rv)
|
||||||
@@ -761,7 +784,7 @@ impl FromStr for ExpandString {
|
|||||||
type Err = ParseExpandStrError;
|
type Err = ParseExpandStrError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
if Self::VAR_RE.is_match(s) {
|
if WINVAR_RE.is_match(s) {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: s.to_string(),
|
inner: s.to_string(),
|
||||||
})
|
})
|
||||||
@@ -858,9 +881,23 @@ pub struct Verb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Verb {
|
impl Verb {
|
||||||
pub const OPEN: LazyLock<Verb> = LazyLock::new(|| Verb::new("open").expect("unexpected bad verb"));
|
/// Get the verb representing Open.
|
||||||
pub const EDIT: LazyLock<Verb> = LazyLock::new(|| Verb::new("edit").expect("unexpected bad verb"));
|
#[allow(non_snake_case)]
|
||||||
pub const PLAY: LazyLock<Verb> = LazyLock::new(|| Verb::new("play").expect("unexpected bad verb"));
|
pub fn OPEN() -> Self {
|
||||||
|
Verb::new("open").expect("unexpected bad verb")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the verb representing Edit.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn EDIT() -> Self {
|
||||||
|
Verb::new("edit").expect("unexpected bad verb")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the verb representing Play.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn PLAY() -> Self {
|
||||||
|
Verb::new("play").expect("unexpected bad verb")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Verb {
|
impl Verb {
|
||||||
@@ -941,7 +978,9 @@ pub struct CmdLine {
|
|||||||
impl CmdLine {
|
impl CmdLine {
|
||||||
/// Create a new command line.
|
/// Create a new command line.
|
||||||
pub fn new<S: AsRef<str>>(args: &[S]) -> Result<Self, BadCmdLineError> {
|
pub fn new<S: AsRef<str>>(args: &[S]) -> Result<Self, BadCmdLineError> {
|
||||||
Ok(Self { inner: args.iter().map(|s| s.as_ref().to_string()).collect() })
|
Ok(Self {
|
||||||
|
inner: args.iter().map(|s| s.as_ref().to_string()).collect(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the full command line.
|
/// Get the full command line.
|
||||||
@@ -981,7 +1020,8 @@ impl FromStr for CmdLine {
|
|||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
// We simply split it by space for rough result.
|
// We simply split it by space for rough result.
|
||||||
Ok(Self { inner: s.split(' ').map(|s| s.to_string()).collect() })
|
Ok(Self {
|
||||||
|
inner: s.split(' ').map(|s| s.to_string()).collect(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use thiserror::Error as TeError;
|
|||||||
use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND;
|
use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND;
|
||||||
use windows_sys::Win32::System::Registry::REG_SAM_FLAGS;
|
use windows_sys::Win32::System::Registry::REG_SAM_FLAGS;
|
||||||
use winreg::RegKey;
|
use winreg::RegKey;
|
||||||
|
use winreg::enums::RegType;
|
||||||
use winreg::types::FromRegValue;
|
use winreg::types::FromRegValue;
|
||||||
|
|
||||||
// region: Extra Operations
|
// region: Extra Operations
|
||||||
@@ -60,6 +61,109 @@ pub fn try_get_value<T: FromRegValue, N: AsRef<OsStr>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete all tree of given path of given key anyway.
|
||||||
|
///
|
||||||
|
/// This function was invented to fix the shortcoming of [RegKey::delete_subkey_all].
|
||||||
|
/// This function always delete given path of given key no matter it is existing.
|
||||||
|
/// Oppositely, [RegKey::delete_subkey_all] will return error if there is no such path.
|
||||||
|
///
|
||||||
|
/// Return true if we successfully delete this key,
|
||||||
|
/// otherwise false indicating there is no such key (already deleted).
|
||||||
|
pub fn arbitrarily_delete_subkey_all<P: AsRef<OsStr>>(
|
||||||
|
regkey: &RegKey,
|
||||||
|
path: P,
|
||||||
|
) -> std::io::Result<bool> {
|
||||||
|
match regkey.delete_subkey_all(path) {
|
||||||
|
Ok(()) => Ok(true),
|
||||||
|
Err(e) => match e.raw_os_error() {
|
||||||
|
Some(errno) => match errno as u32 {
|
||||||
|
ERROR_FILE_NOT_FOUND => Ok(false),
|
||||||
|
_ => Err(e),
|
||||||
|
},
|
||||||
|
_ => Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete given value key of given key anyway.
|
||||||
|
///
|
||||||
|
/// This function was invented to fix the shortcoming of [RegKey::delete_value].
|
||||||
|
/// This function always delete given value key of given key no matter it is existing.
|
||||||
|
/// Oppositely, [RegKey::delete_value] will return error if there is no such value key.
|
||||||
|
///
|
||||||
|
/// Return true if we successfully delete this value key,
|
||||||
|
/// otherwise false indicating there is no such value key (already deleted).
|
||||||
|
pub fn arbitrarily_delete_value<N: AsRef<OsStr>>(
|
||||||
|
regkey: &RegKey,
|
||||||
|
name: N,
|
||||||
|
) -> std::io::Result<bool> {
|
||||||
|
match regkey.delete_value(name) {
|
||||||
|
Ok(()) => Ok(true),
|
||||||
|
Err(e) => match e.raw_os_error() {
|
||||||
|
Some(errno) => match errno as u32 {
|
||||||
|
ERROR_FILE_NOT_FOUND => Ok(false),
|
||||||
|
_ => Err(e),
|
||||||
|
},
|
||||||
|
_ => Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the name of only subkey in given key.
|
||||||
|
///
|
||||||
|
/// If there is only one subkey in given key, the return value is its name.
|
||||||
|
/// If there is no any subkey, or has multiple subkeys, return None instead.
|
||||||
|
/// If error occurs when fetching data, return Err(_).
|
||||||
|
///
|
||||||
|
/// This is usually used for ShellVerb fetching.
|
||||||
|
pub fn get_sole_subkey_name(regkey: &RegKey) -> std::io::Result<Option<String>> {
|
||||||
|
let mut subkey_enumerator = regkey.enum_keys();
|
||||||
|
|
||||||
|
// Get first one.
|
||||||
|
let rv = match subkey_enumerator.next() {
|
||||||
|
Some(key) => key?,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check whether there is second one.
|
||||||
|
match subkey_enumerator.next() {
|
||||||
|
Some(_) => Ok(None),
|
||||||
|
None => Ok(Some(rv)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the name list of all "string" subkeys in given key.
|
||||||
|
///
|
||||||
|
/// This is usually used for "OpenWithProgIds" subkey.
|
||||||
|
pub fn get_all_string_subkey_names(regkey: &RegKey) -> std::io::Result<Vec<String>> {
|
||||||
|
regkey
|
||||||
|
.enum_values()
|
||||||
|
.filter_map(|item| match item {
|
||||||
|
Ok((key, value)) => {
|
||||||
|
// Check subkey type.
|
||||||
|
if value.vtype == RegType::REG_SZ {
|
||||||
|
Some(Ok(key))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => Some(Err(err)),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete all contents, including values and subkeys of given key.
|
||||||
|
///
|
||||||
|
/// Deleting all contents of given key rely on giving a special parameter to [RegKey::delete_subkey_all].
|
||||||
|
/// This is very dangerous and may be used by accident.
|
||||||
|
/// So I create this to explicitly indicate this behavior and avoid any mis-type in code.
|
||||||
|
pub fn clean_all_contents(regkey: &RegKey) -> std::io::Result<()> {
|
||||||
|
// There is no possibility that this key do not existing,
|
||||||
|
// because what we are cleaning is self content.
|
||||||
|
// So directly use delete_subkey_all is okey.
|
||||||
|
regkey.delete_subkey_all("")
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region: Blank Path Guard
|
// region: Blank Path Guard
|
||||||
@@ -82,6 +186,9 @@ impl BlankPathError {
|
|||||||
/// Because it will cause unexpected behavior that returning key self, rather than subkey.
|
/// Because it will cause unexpected behavior that returning key self, rather than subkey.
|
||||||
/// This is VERY dangerous especially for those registry delete functions.
|
/// This is VERY dangerous especially for those registry delete functions.
|
||||||
/// So I create this function to prevent any harmful blank path was passed into registry function.
|
/// So I create this function to prevent any harmful blank path was passed into registry function.
|
||||||
|
///
|
||||||
|
/// This function MUST be used for the value, whose content can not be confirmed at compile time,
|
||||||
|
/// and it will be passed to get/set value, or create/delete key functions.
|
||||||
pub fn blank_path_guard<P: AsRef<OsStr>>(path: P) -> std::result::Result<P, BlankPathError> {
|
pub fn blank_path_guard<P: AsRef<OsStr>>(path: P) -> std::result::Result<P, BlankPathError> {
|
||||||
if path.as_ref().is_empty() {
|
if path.as_ref().is_empty() {
|
||||||
Err(BlankPathError::new())
|
Err(BlankPathError::new())
|
||||||
|
|||||||
27
wfassoc/tests/common.rs
Normal file
27
wfassoc/tests/common.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/// Check whether we are in sandbox.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panic if we are not in sandbox (we can not perform dangerous test).
|
||||||
|
/// Return if we are in sandbox environment.
|
||||||
|
pub fn check_sandbox() {
|
||||||
|
assert!(
|
||||||
|
std::env::var("SANDBOXIE").is_ok(),
|
||||||
|
concat!(
|
||||||
|
"Non-sandbox environment detected. ",
|
||||||
|
"Executing these tests in non-sandbox environment is VERY dangerous. ",
|
||||||
|
"Please set \"SANDBOXIE\" environment variable to explicitly indicate you are running these tests in sandbox environment."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_privilege() {
|
||||||
|
assert!(
|
||||||
|
wfassoc::win32::utilities::has_privilege(),
|
||||||
|
concat!(
|
||||||
|
"You are running test without privilege. ",
|
||||||
|
"These tests must be run with some privilege because it need to manipulate Windows Registry. ",
|
||||||
|
"Please give it privilege in your sandbox environment."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::str::FromStr;
|
use std::{collections::HashMap, str::FromStr};
|
||||||
use wfassoc::win32::concept::*;
|
use wfassoc::win32::concept::*;
|
||||||
|
|
||||||
// region: File Extension
|
// region: File Extension
|
||||||
@@ -134,6 +134,13 @@ fn test_icon_ref_str() {
|
|||||||
r#"%SystemRoot%\System32\imageres.dll,-72"#,
|
r#"%SystemRoot%\System32\imageres.dll,-72"#,
|
||||||
(r#"%SystemRoot%\System32\imageres.dll"#, 72),
|
(r#"%SystemRoot%\System32\imageres.dll"#, 72),
|
||||||
);
|
);
|
||||||
|
ok_tester(
|
||||||
|
r#""D:\Software\Krita\Krita (x64)\shellex\kritafile.ico",0"#,
|
||||||
|
(
|
||||||
|
r#""D:\Software\Krita\Krita (x64)\shellex\kritafile.ico""#,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
err_tester(r#"C:\Windows\Cursors\aero_arrow.cur"#);
|
err_tester(r#"C:\Windows\Cursors\aero_arrow.cur"#);
|
||||||
err_tester(r#"This is my application, OK?"#);
|
err_tester(r#"This is my application, OK?"#);
|
||||||
err_tester(r#"@%SystemRoot%\System32\shell32.dll,-30596"#);
|
err_tester(r#"@%SystemRoot%\System32\shell32.dll,-30596"#);
|
||||||
@@ -161,6 +168,10 @@ fn test_str_ref_str() {
|
|||||||
r#"@%SystemRoot%\System32\shell32.dll,-30596"#,
|
r#"@%SystemRoot%\System32\shell32.dll,-30596"#,
|
||||||
(r#"%SystemRoot%\System32\shell32.dll"#, 30596),
|
(r#"%SystemRoot%\System32\shell32.dll"#, 30596),
|
||||||
);
|
);
|
||||||
|
ok_tester(
|
||||||
|
r#"@%SystemRoot%\System32\shell32.dll,30596"#,
|
||||||
|
(r#"%SystemRoot%\System32\shell32.dll"#, 30596),
|
||||||
|
);
|
||||||
err_tester(r#"This is my application, OK?"#);
|
err_tester(r#"This is my application, OK?"#);
|
||||||
err_tester(r#"%SystemRoot%\System32\imageres.dll,-72"#);
|
err_tester(r#"%SystemRoot%\System32\imageres.dll,-72"#);
|
||||||
}
|
}
|
||||||
@@ -175,6 +186,9 @@ fn test_str_ref_str() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_icon_rc() {
|
fn test_icon_rc() {
|
||||||
|
fn const_tester(icon: Result<IconRc, LoadIconRcError>) {
|
||||||
|
assert!(icon.is_ok())
|
||||||
|
}
|
||||||
fn ok_tester(file: &str, index: u32) {
|
fn ok_tester(file: &str, index: u32) {
|
||||||
let icon = IconRc::new(file, index, IconSizeKind::Small);
|
let icon = IconRc::new(file, index, IconSizeKind::Small);
|
||||||
assert!(icon.is_ok())
|
assert!(icon.is_ok())
|
||||||
@@ -184,6 +198,9 @@ fn test_icon_rc() {
|
|||||||
assert!(icon.is_err())
|
assert!(icon.is_err())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test 2 const value
|
||||||
|
const_tester(IconRc::GENERIC_APPLICATION(IconSizeKind::Small));
|
||||||
|
const_tester(IconRc::GENERIC_APPLICATION(IconSizeKind::Small));
|
||||||
// We pick it from "jpegfile" ProgId
|
// We pick it from "jpegfile" ProgId
|
||||||
ok_tester("imageres.dll", 72);
|
ok_tester("imageres.dll", 72);
|
||||||
ok_tester("notepad.exe", 0);
|
ok_tester("notepad.exe", 0);
|
||||||
@@ -218,16 +235,36 @@ fn test_str_rc() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_expand_string() {
|
fn test_expand_string() {
|
||||||
fn tester(s: &str) {
|
fn tester(fmt: &str, var_name: &str) {
|
||||||
let rv = ExpandString::new(s);
|
// We first insert variable name into format string to get the final string
|
||||||
assert!(rv.is_ok());
|
let mut vars = HashMap::new();
|
||||||
let rv = rv.unwrap();
|
vars.insert("0".to_string(), var_name.to_string());
|
||||||
|
let final_string = strfmt::strfmt(fmt, &vars).unwrap();
|
||||||
|
|
||||||
let rv = rv.expand_string();
|
// The we try expanding final string first
|
||||||
assert!(rv.is_ok());
|
let expand_final_string = ExpandString::new(&final_string);
|
||||||
|
assert!(expand_final_string.is_ok());
|
||||||
|
let expand_final_string = expand_final_string.unwrap();
|
||||||
|
let expanded_final_string = expand_final_string.expand();
|
||||||
|
assert!(expanded_final_string.is_ok());
|
||||||
|
let expanded_final_string = expanded_final_string.unwrap();
|
||||||
|
|
||||||
|
// Then we expand variable name individually
|
||||||
|
let expand_var_name = ExpandString::new(var_name);
|
||||||
|
assert!(expand_var_name.is_ok());
|
||||||
|
let expand_var_name = expand_var_name.unwrap();
|
||||||
|
let expanded_var_name = expand_var_name.expand();
|
||||||
|
assert!(expanded_var_name.is_ok());
|
||||||
|
let expanded_var_name = expanded_var_name.unwrap();
|
||||||
|
|
||||||
|
// Finally, we directly insert expanded variable name into format string
|
||||||
|
// to get the string which can be compared with final string.
|
||||||
|
vars.insert("0".to_string(), expanded_var_name.clone());
|
||||||
|
let built_final_string = strfmt::strfmt(fmt, &vars).unwrap();
|
||||||
|
assert_eq!(expanded_final_string.to_string(), built_final_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
tester(r#"%SystemRoot%\System32\shell32.dll"#);
|
tester(r#"{0}\System32\shell32.dll"#, "%SystemRoot%");
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|||||||
169
wfassoc/tests/highlevel.rs
Normal file
169
wfassoc/tests/highlevel.rs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
use wfassoc::highlevel::*;
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
static IDENTIFIER: &str = "Passoc";
|
||||||
|
static APP_PATH: &str = r"C:\Passoc\passoc.exe";
|
||||||
|
static CLSID: &str = "{59031a47-3f72-44a7-89c5-5595fe6b30ee}";
|
||||||
|
static EXT_BODY: &str = "pacfg";
|
||||||
|
|
||||||
|
fn make_valid_schema() -> Schema {
|
||||||
|
let mut schema = Schema::new();
|
||||||
|
schema.set_identifier(IDENTIFIER);
|
||||||
|
schema.set_path(APP_PATH);
|
||||||
|
schema.set_clsid(CLSID);
|
||||||
|
schema.add_str("main_name", "Passoc Application").unwrap();
|
||||||
|
schema.add_str("ext_name", "Pacfg File").unwrap();
|
||||||
|
schema.add_icon("main_icon", "notepad.exe,0").unwrap();
|
||||||
|
schema.add_icon("ext_icon", "notepad.exe,0").unwrap();
|
||||||
|
schema.add_behavior("main_behavior", "notepad.exe %1").unwrap();
|
||||||
|
schema.add_behavior("ext_behavior", "notepad.exe %1").unwrap();
|
||||||
|
schema.set_name(Some("main_name"));
|
||||||
|
schema.set_icon(Some("main_icon"));
|
||||||
|
schema.set_behavior(Some("main_behavior"));
|
||||||
|
schema.add_ext(EXT_BODY, "ext_name", "ext_icon", "ext_behavior").unwrap();
|
||||||
|
schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// region: Schema
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_schema() {
|
||||||
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
|
// valid schema -> valid program
|
||||||
|
let schema = make_valid_schema();
|
||||||
|
let rv = schema.into_program();
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
|
||||||
|
// missing essential parts (schema, path and etc)
|
||||||
|
let schema = Schema::new();
|
||||||
|
let rv = schema.into_program();
|
||||||
|
assert!(rv.is_err());
|
||||||
|
|
||||||
|
// invalid path
|
||||||
|
let mut schema = make_valid_schema();
|
||||||
|
schema.set_path(r"C:\");
|
||||||
|
let rv = schema.into_program();
|
||||||
|
assert!(rv.is_err());
|
||||||
|
|
||||||
|
// prepare a schema for following test
|
||||||
|
let mut schema = Schema::new();
|
||||||
|
schema.set_identifier(IDENTIFIER);
|
||||||
|
schema.set_path(APP_PATH);
|
||||||
|
|
||||||
|
// duplicate detection on add_str
|
||||||
|
let rv = schema.add_str("k", "v1");
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = schema.add_str("k", "v2");
|
||||||
|
assert!(rv.is_err());
|
||||||
|
|
||||||
|
// duplicate detection on add_icon
|
||||||
|
let rv = schema.add_icon("k", "v1");
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = schema.add_icon("k", "v2");
|
||||||
|
assert!(rv.is_err());
|
||||||
|
|
||||||
|
// duplicate detection on add_behavior
|
||||||
|
let rv = schema.add_behavior("k", "v1");
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = schema.add_behavior("k", "v2");
|
||||||
|
assert!(rv.is_err());
|
||||||
|
|
||||||
|
// duplicate detection on add_ext
|
||||||
|
let rv = schema.add_ext(EXT_BODY, "k", "k", "k");
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = schema.add_ext(EXT_BODY, "k", "k", "k");
|
||||||
|
assert!(rv.is_err());
|
||||||
|
|
||||||
|
// ext referencing non-existent map entry
|
||||||
|
schema.add_str("k2", "v").unwrap();
|
||||||
|
schema.add_icon("k2", "v").unwrap();
|
||||||
|
let rv = schema.add_ext("pacfg2", "nonexistent", "k2", "k2");
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = schema.into_program();
|
||||||
|
assert!(rv.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Program
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_program() {
|
||||||
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
|
fn tester(scope: Scope, view: View) {
|
||||||
|
// build program
|
||||||
|
let schema = make_valid_schema();
|
||||||
|
let rv = schema.into_program();
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let mut program = rv.unwrap();
|
||||||
|
|
||||||
|
// cleanup before test
|
||||||
|
let rv = program.unregister(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
|
||||||
|
// initially not registered
|
||||||
|
let rv = program.is_registered(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
|
||||||
|
// register
|
||||||
|
let rv = program.register(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
|
||||||
|
// should be registered now
|
||||||
|
let rv = program.is_registered(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), true);
|
||||||
|
|
||||||
|
// link_ext first so ext key exists before register
|
||||||
|
let rv = program.link_ext(scope, 0);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
|
||||||
|
// query_ext after link + register
|
||||||
|
let rv = program.query_ext(view, 0);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert!(rv.unwrap().is_some());
|
||||||
|
|
||||||
|
// unlink_ext
|
||||||
|
let rv = program.unlink_ext(scope, 0);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
|
||||||
|
// query_ext should return None
|
||||||
|
let rv = program.query_ext(view, 0);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert!(rv.unwrap().is_none());
|
||||||
|
|
||||||
|
// resolve_name, resolve_icon
|
||||||
|
let rv = program.resolve_name();
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = program.resolve_icon();
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
|
||||||
|
// exts_len, find_ext, resolve_ext
|
||||||
|
assert_eq!(program.exts_len(), 1);
|
||||||
|
assert_eq!(program.find_ext(EXT_BODY), Some(0));
|
||||||
|
let rv = program.resolve_ext(0);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
|
||||||
|
// bad index
|
||||||
|
let rv = program.resolve_ext(1);
|
||||||
|
assert!(rv.is_err());
|
||||||
|
let rv = program.link_ext(scope, 1);
|
||||||
|
assert!(rv.is_err());
|
||||||
|
let rv = program.query_ext(view, 1);
|
||||||
|
assert!(rv.is_err());
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
let rv = program.unregister(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
tester(Scope::User, View::User);
|
||||||
|
tester(Scope::System, View::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
334
wfassoc/tests/lowlevel.rs
Normal file
334
wfassoc/tests/lowlevel.rs
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use wfassoc::lowlevel::*;
|
||||||
|
use wfassoc::win32::concept;
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
static EXT: LazyLock<concept::Ext> = LazyLock::new(|| concept::Ext::from_str(".pacfg").unwrap());
|
||||||
|
static APP_FILE: LazyLock<concept::FileName> =
|
||||||
|
LazyLock::new(|| concept::FileName::from_str("passoc.exe").unwrap());
|
||||||
|
static PROG_ID: LazyLock<LosseProgId> = LazyLock::new(|| "Passoc.Pacfg".into());
|
||||||
|
static ICON: LazyLock<IconResVariant> =
|
||||||
|
LazyLock::new(|| r"%SystemRoot%\System32\imageres.dll,-72".into());
|
||||||
|
static VERB: LazyLock<ShellVerb> = LazyLock::new(|| {
|
||||||
|
let verb = concept::Verb::from_str("open").unwrap();
|
||||||
|
let cmdline = concept::CmdLine::from_str("notepad.exe %1").unwrap();
|
||||||
|
ShellVerb::new(verb, cmdline)
|
||||||
|
});
|
||||||
|
|
||||||
|
// region: AppPathsKey
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_app_paths_key() {
|
||||||
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
|
static APP_PATH: &str = r"C:\Program Files\Passoc\passoc.exe";
|
||||||
|
static APP_DIR: &str = r"C:\Program Files\Passoc";
|
||||||
|
|
||||||
|
fn tester(scope: Scope) {
|
||||||
|
let mut key = AppPathsKey::new(APP_FILE.clone());
|
||||||
|
|
||||||
|
// delete and ensure
|
||||||
|
let rv = key.delete(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.is_exist(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
let rv = key.ensure(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), true);
|
||||||
|
let rv = key.is_exist(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), true);
|
||||||
|
let rv = key.ensure(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
|
||||||
|
// get/set default
|
||||||
|
let rv = key.set_default(scope, APP_PATH);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_default(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), APP_PATH);
|
||||||
|
|
||||||
|
// get/set path
|
||||||
|
let rv = key.set_path(scope, APP_DIR);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_path(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), APP_DIR);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
let rv = key.delete(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
tester(Scope::User);
|
||||||
|
tester(Scope::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: ApplicationsKey
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_applications_key() {
|
||||||
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
|
static FRIENDLY_APP_NAME: LazyLock<StrResVariant> =
|
||||||
|
LazyLock::new(|| "Passoc Application".into());
|
||||||
|
|
||||||
|
fn tester(scope: Scope, view: View) {
|
||||||
|
let mut key = ApplicationsKey::new(APP_FILE.clone());
|
||||||
|
|
||||||
|
// delete and ensure
|
||||||
|
let rv = key.delete(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.is_exist(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
let rv = key.ensure(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), true);
|
||||||
|
let rv = key.is_exist(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), true);
|
||||||
|
let rv = key.ensure(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
|
||||||
|
// get/set shell verb
|
||||||
|
let rv = key.set_shell_verb(scope, Some(&VERB));
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_shell_verb(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), Some(VERB.clone()));
|
||||||
|
let rv = key.set_shell_verb(scope, None);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_shell_verb(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), None);
|
||||||
|
|
||||||
|
// get/set default icon
|
||||||
|
let rv = key.set_default_icon(scope, Some(&ICON));
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_default_icon(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), Some(ICON.clone()));
|
||||||
|
let rv = key.set_default_icon(scope, None);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_default_icon(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), None);
|
||||||
|
|
||||||
|
// get/set friendly app name
|
||||||
|
let rv = key.set_friendly_app_name(scope, Some(&FRIENDLY_APP_NAME));
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_friendly_app_name(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), Some(FRIENDLY_APP_NAME.clone()));
|
||||||
|
let rv = key.set_friendly_app_name(scope, None);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_friendly_app_name(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), None);
|
||||||
|
|
||||||
|
// get/set supported types
|
||||||
|
let rv = key.set_supported_types(scope, Some(&vec![EXT.deref()]));
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_supported_types(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), Some(vec![EXT.clone()]));
|
||||||
|
let rv = key.set_supported_types(scope, None);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_supported_types(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), None);
|
||||||
|
|
||||||
|
// get/set no open with
|
||||||
|
let rv = key.get_no_open_with(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
let rv = key.set_no_open_with(scope, true);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_no_open_with(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), true);
|
||||||
|
let rv = key.set_no_open_with(scope, false);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_no_open_with(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
let rv = key.delete(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
tester(Scope::User, View::User);
|
||||||
|
tester(Scope::System, View::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: ExtKey
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ext_key() {
|
||||||
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
|
fn tester(scope: Scope, view: View) {
|
||||||
|
let mut key = ExtKey::new(EXT.clone());
|
||||||
|
|
||||||
|
// delete and ensure
|
||||||
|
let rv = key.delete(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.is_exist(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
let rv = key.ensure(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), true);
|
||||||
|
let rv = key.is_exist(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), true);
|
||||||
|
let rv = key.ensure(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
|
||||||
|
// get/set default
|
||||||
|
let rv = key.set_default(scope, Some(&PROG_ID));
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_default(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), Some(PROG_ID.clone()));
|
||||||
|
let rv = key.set_default(scope, None);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_default(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), None);
|
||||||
|
|
||||||
|
// test open with progids
|
||||||
|
let rv = key.is_in_open_with_progids(view, &PROG_ID);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
|
||||||
|
let rv = key.add_into_open_with_progids(scope, &PROG_ID);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.is_in_open_with_progids(view, &PROG_ID);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), true);
|
||||||
|
|
||||||
|
let rv = key.get_open_with_progids(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), Some(vec![PROG_ID.clone()]));
|
||||||
|
|
||||||
|
let rv = key.remove_from_open_with_progids(scope, &PROG_ID);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.is_in_open_with_progids(view, &PROG_ID);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
let rv = key.delete(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
tester(Scope::User, View::User);
|
||||||
|
tester(Scope::System, View::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: ProgIdKey
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prog_id_key() {
|
||||||
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
|
static LEGACY_NAME: LazyLock<StrResVariant> = LazyLock::new(|| "Passoc Pacfg File".into());
|
||||||
|
static FRIENDLY_TYPE_NAME: LazyLock<StrResVariant> =
|
||||||
|
LazyLock::new(|| "Passoc Pacfg File Type".into());
|
||||||
|
|
||||||
|
fn tester(scope: Scope, view: View) {
|
||||||
|
let mut key = ProgIdKey::new(PROG_ID.clone());
|
||||||
|
|
||||||
|
// delete and ensure
|
||||||
|
let rv = key.delete(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.is_exist(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
let rv = key.ensure(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), true);
|
||||||
|
let rv = key.is_exist(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), true);
|
||||||
|
let rv = key.ensure(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), false);
|
||||||
|
|
||||||
|
// get/set default (friendly name, legacy)
|
||||||
|
let rv = key.set_default(scope, Some(&LEGACY_NAME));
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_default(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), Some(LEGACY_NAME.clone()));
|
||||||
|
let rv = key.set_default(scope, None);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_default(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), None);
|
||||||
|
|
||||||
|
// get/set friendly type name
|
||||||
|
let rv = key.set_friendly_type_name(scope, Some(&FRIENDLY_TYPE_NAME));
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_friendly_type_name(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), Some(FRIENDLY_TYPE_NAME.clone()));
|
||||||
|
let rv = key.set_friendly_type_name(scope, None);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_friendly_type_name(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), None);
|
||||||
|
|
||||||
|
// get/set shell verb
|
||||||
|
let rv = key.set_shell_verb(scope, Some(&VERB));
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_shell_verb(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), Some(VERB.clone()));
|
||||||
|
let rv = key.set_shell_verb(scope, None);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_shell_verb(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), None);
|
||||||
|
|
||||||
|
// get/set default icon
|
||||||
|
let rv = key.set_default_icon(scope, Some(&ICON));
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_default_icon(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), Some(ICON.clone()));
|
||||||
|
let rv = key.set_default_icon(scope, None);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
let rv = key.get_default_icon(view);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
assert_eq!(rv.unwrap(), None);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
let rv = key.delete(scope);
|
||||||
|
assert!(rv.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
tester(Scope::User, View::User);
|
||||||
|
tester(Scope::System, View::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
Reference in New Issue
Block a user